|
| 1 | +--- |
| 2 | +name: generate-types |
| 3 | +description: Generate or update FHIR TypeScript types using @atomic-ehr/codegen with tree-shaking |
| 4 | +--- |
| 5 | + |
| 6 | +# FHIR Type Generation with @atomic-ehr/codegen |
| 7 | + |
| 8 | +Generate type-safe FHIR R4 TypeScript interfaces from StructureDefinitions. Uses tree-shaking to only include the resource types you need. |
| 9 | + |
| 10 | +## Setup (if not already configured) |
| 11 | + |
| 12 | +### 1. Install the codegen package |
| 13 | + |
| 14 | +```sh |
| 15 | +# npm |
| 16 | +npm install -D @atomic-ehr/codegen |
| 17 | + |
| 18 | +# pnpm |
| 19 | +pnpm add -D @atomic-ehr/codegen |
| 20 | + |
| 21 | +# yarn |
| 22 | +yarn add -D @atomic-ehr/codegen |
| 23 | + |
| 24 | +# bun |
| 25 | +bun add -d @atomic-ehr/codegen |
| 26 | +``` |
| 27 | + |
| 28 | +For npm/pnpm/yarn projects, also install `tsx` to run the TypeScript generation script: |
| 29 | + |
| 30 | +```sh |
| 31 | +npm install -D tsx |
| 32 | +``` |
| 33 | + |
| 34 | +### 2. Create the generation script |
| 35 | + |
| 36 | +Create `scripts/generate-types.ts`: |
| 37 | + |
| 38 | +```ts |
| 39 | +import { APIBuilder, prettyReport } from "@atomic-ehr/codegen"; |
| 40 | + |
| 41 | +const builder = new APIBuilder() |
| 42 | + .throwException() |
| 43 | + .fromPackage("hl7.fhir.r4.core", "4.0.1") |
| 44 | + .typescript({ |
| 45 | + withDebugComment: false, |
| 46 | + generateProfile: false, |
| 47 | + openResourceTypeSet: false, |
| 48 | + }) |
| 49 | + .typeSchema({ |
| 50 | + treeShake: { |
| 51 | + "hl7.fhir.r4.core": { |
| 52 | + // Add the resource types you need here. |
| 53 | + // All dependency types (Identifier, Reference, CodeableConcept, etc.) |
| 54 | + // are included automatically. |
| 55 | + "http://hl7.org/fhir/StructureDefinition/Patient": {}, |
| 56 | + "http://hl7.org/fhir/StructureDefinition/Bundle": {}, |
| 57 | + "http://hl7.org/fhir/StructureDefinition/OperationOutcome": {}, |
| 58 | + }, |
| 59 | + }, |
| 60 | + }) |
| 61 | + .outputTo("./src/fhir-types") |
| 62 | + .cleanOutput(true); |
| 63 | + |
| 64 | +const report = await builder.generate(); |
| 65 | +console.log(prettyReport(report)); |
| 66 | +if (!report.success) process.exit(1); |
| 67 | +``` |
| 68 | + |
| 69 | +### 3. Add the script to package.json |
| 70 | + |
| 71 | +```json |
| 72 | +{ |
| 73 | + "scripts": { |
| 74 | + "generate-types": "bun run scripts/generate-types.ts" |
| 75 | + } |
| 76 | +} |
| 77 | +``` |
| 78 | + |
| 79 | +For npm/pnpm/yarn projects (using tsx instead of bun): |
| 80 | + |
| 81 | +```json |
| 82 | +{ |
| 83 | + "scripts": { |
| 84 | + "generate-types": "tsx scripts/generate-types.ts" |
| 85 | + } |
| 86 | +} |
| 87 | +``` |
| 88 | + |
| 89 | +### 4. Add to .gitignore |
| 90 | + |
| 91 | +``` |
| 92 | +.codegen-cache/ |
| 93 | +``` |
| 94 | + |
| 95 | +### 5. Include scripts in tsconfig.json |
| 96 | + |
| 97 | +```json |
| 98 | +{ |
| 99 | + "include": ["src/**/*", "scripts/**/*"] |
| 100 | +} |
| 101 | +``` |
| 102 | + |
| 103 | +### 6. Run generation |
| 104 | + |
| 105 | +```sh |
| 106 | +# bun |
| 107 | +bun run generate-types |
| 108 | + |
| 109 | +# npm |
| 110 | +npm run generate-types |
| 111 | + |
| 112 | +# pnpm |
| 113 | +pnpm run generate-types |
| 114 | +``` |
| 115 | + |
| 116 | +This outputs typed interfaces to `src/fhir-types/hl7-fhir-r4-core/`. Commit these files — they are the project's FHIR type definitions. |
| 117 | + |
| 118 | +## APIBuilder reference |
| 119 | + |
| 120 | +### Loading FHIR packages |
| 121 | + |
| 122 | +```ts |
| 123 | +// FHIR R4 base (required) |
| 124 | +.fromPackage("hl7.fhir.r4.core", "4.0.1") |
| 125 | + |
| 126 | +// Implementation Guides (optional) — load from tgz URL |
| 127 | +.fromPackageRef("https://fs.get-ig.org/-/hl7.fhir.us.core-7.0.0.tgz") |
| 128 | +.fromPackageRef("https://fs.get-ig.org/-/fhir.r4.ukcore.stu2-2.0.2.tgz") |
| 129 | +``` |
| 130 | + |
| 131 | +### TypeScript options |
| 132 | + |
| 133 | +```ts |
| 134 | +.typescript({ |
| 135 | + withDebugComment: false, // omit debug comments in generated files |
| 136 | + generateProfile: false, // set true when loading IGs with constrained profiles |
| 137 | + openResourceTypeSet: false, // stricter resource type unions |
| 138 | +}) |
| 139 | +``` |
| 140 | + |
| 141 | +### Tree-shaking |
| 142 | + |
| 143 | +Tree-shaking controls which resource types are generated. Without it, ALL FHIR resources are included (~150+ files). With it, only the listed types and their transitive dependencies are generated. |
| 144 | + |
| 145 | +```ts |
| 146 | +.typeSchema({ |
| 147 | + treeShake: { |
| 148 | + "<package-name>": { |
| 149 | + "<StructureDefinition canonical URL>": {}, |
| 150 | + // ... |
| 151 | + }, |
| 152 | + }, |
| 153 | +}) |
| 154 | +``` |
| 155 | + |
| 156 | +**StructureDefinition URL patterns:** |
| 157 | + |
| 158 | +| Source | Pattern | Example | |
| 159 | +|--------|---------|---------| |
| 160 | +| FHIR R4 base | `http://hl7.org/fhir/StructureDefinition/{ResourceType}` | `http://hl7.org/fhir/StructureDefinition/Patient` | |
| 161 | +| US Core | `http://hl7.org/fhir/us/core/StructureDefinition/{profile}` | `http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient` | |
| 162 | +| UK Core | `https://fhir.hl7.org.uk/StructureDefinition/{profile}` | `https://fhir.hl7.org.uk/StructureDefinition/UKCore-Patient` | |
| 163 | + |
| 164 | +**Common FHIR R4 resource types:** |
| 165 | + |
| 166 | +```ts |
| 167 | +"http://hl7.org/fhir/StructureDefinition/Patient": {}, |
| 168 | +"http://hl7.org/fhir/StructureDefinition/Encounter": {}, |
| 169 | +"http://hl7.org/fhir/StructureDefinition/Observation": {}, |
| 170 | +"http://hl7.org/fhir/StructureDefinition/Condition": {}, |
| 171 | +"http://hl7.org/fhir/StructureDefinition/Procedure": {}, |
| 172 | +"http://hl7.org/fhir/StructureDefinition/MedicationRequest": {}, |
| 173 | +"http://hl7.org/fhir/StructureDefinition/DiagnosticReport": {}, |
| 174 | +"http://hl7.org/fhir/StructureDefinition/AllergyIntolerance": {}, |
| 175 | +"http://hl7.org/fhir/StructureDefinition/Immunization": {}, |
| 176 | +"http://hl7.org/fhir/StructureDefinition/Location": {}, |
| 177 | +"http://hl7.org/fhir/StructureDefinition/Organization": {}, |
| 178 | +"http://hl7.org/fhir/StructureDefinition/Practitioner": {}, |
| 179 | +"http://hl7.org/fhir/StructureDefinition/Bundle": {}, |
| 180 | +"http://hl7.org/fhir/StructureDefinition/OperationOutcome": {}, |
| 181 | +"http://hl7.org/fhir/StructureDefinition/Questionnaire": {}, |
| 182 | +"http://hl7.org/fhir/StructureDefinition/QuestionnaireResponse": {}, |
| 183 | +``` |
| 184 | + |
| 185 | +### Output options |
| 186 | + |
| 187 | +```ts |
| 188 | +.outputTo("./src/fhir-types") // output directory |
| 189 | +.cleanOutput(true) // delete output dir before regenerating |
| 190 | +``` |
| 191 | + |
| 192 | +## Example: Adding an Implementation Guide |
| 193 | + |
| 194 | +To generate US Core profiled types alongside base R4: |
| 195 | + |
| 196 | +```ts |
| 197 | +import { APIBuilder, prettyReport } from "@atomic-ehr/codegen"; |
| 198 | + |
| 199 | +const builder = new APIBuilder() |
| 200 | + .throwException() |
| 201 | + .fromPackage("hl7.fhir.r4.core", "4.0.1") |
| 202 | + .fromPackageRef("https://fs.get-ig.org/-/hl7.fhir.us.core-7.0.0.tgz") |
| 203 | + .typescript({ |
| 204 | + withDebugComment: false, |
| 205 | + generateProfile: true, // enable for IG profiles |
| 206 | + openResourceTypeSet: false, |
| 207 | + }) |
| 208 | + .typeSchema({ |
| 209 | + treeShake: { |
| 210 | + "hl7.fhir.r4.core": { |
| 211 | + "http://hl7.org/fhir/StructureDefinition/Bundle": {}, |
| 212 | + "http://hl7.org/fhir/StructureDefinition/OperationOutcome": {}, |
| 213 | + }, |
| 214 | + "hl7.fhir.us.core": { |
| 215 | + "http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient": {}, |
| 216 | + "http://hl7.org/fhir/us/core/StructureDefinition/us-core-condition-encounter-diagnosis": {}, |
| 217 | + "http://hl7.org/fhir/us/core/StructureDefinition/us-core-observation-lab": {}, |
| 218 | + }, |
| 219 | + }, |
| 220 | + }) |
| 221 | + .outputTo("./src/fhir-types") |
| 222 | + .cleanOutput(true); |
| 223 | + |
| 224 | +const report = await builder.generate(); |
| 225 | +console.log(prettyReport(report)); |
| 226 | +if (!report.success) process.exit(1); |
| 227 | +``` |
| 228 | + |
| 229 | +## What gets generated |
| 230 | + |
| 231 | +For each resource type, a `.ts` file is generated containing: |
| 232 | + |
| 233 | +- **Main interface** — e.g., `Patient`, `Encounter`, `Bundle` |
| 234 | +- **BackboneElement interfaces** — nested structures like `EncounterLocation`, `PatientContact`, `BundleEntry` |
| 235 | +- **Type guard function** — e.g., `isPatient(resource)`, `isEncounter(resource)` |
| 236 | +- **Re-exports** of dependency types (Identifier, Reference, CodeableConcept, etc.) |
| 237 | +- **Barrel export** — `index.ts` re-exports everything for convenience |
| 238 | + |
| 239 | +Generated types are fully typed with FHIR value set enums where applicable: |
| 240 | + |
| 241 | +```ts |
| 242 | +// Encounter.status is a union of valid FHIR values |
| 243 | +status: ("planned" | "arrived" | "triaged" | "in-progress" | "onleave" | "finished" | "cancelled" | "entered-in-error" | "unknown"); |
| 244 | + |
| 245 | +// References are typed with target resource types |
| 246 | +subject?: Reference<"Group" | "Patient">; |
| 247 | +location: Reference<"Location">; |
| 248 | +``` |
| 249 | + |
| 250 | +## Import patterns |
| 251 | + |
| 252 | +```ts |
| 253 | +// Direct file import (preferred — explicit about what you use) |
| 254 | +import type { Patient } from "./fhir-types/hl7-fhir-r4-core/Patient"; |
| 255 | +import type { Encounter, EncounterLocation } from "./fhir-types/hl7-fhir-r4-core/Encounter"; |
| 256 | +import type { Bundle, BundleEntry } from "./fhir-types/hl7-fhir-r4-core/Bundle"; |
| 257 | +import type { Identifier } from "./fhir-types/hl7-fhir-r4-core/Identifier"; |
| 258 | +import type { Reference } from "./fhir-types/hl7-fhir-r4-core/Reference"; |
| 259 | + |
| 260 | +// Barrel import (convenient for grabbing many types) |
| 261 | +import type { Patient, Encounter, Location, Bundle } from "./fhir-types/hl7-fhir-r4-core"; |
| 262 | + |
| 263 | +// Type guards (value imports, not type-only) |
| 264 | +import { isPatient, isEncounter } from "./fhir-types/hl7-fhir-r4-core"; |
| 265 | +``` |
| 266 | + |
| 267 | +## Workflow for adding a new resource type |
| 268 | + |
| 269 | +1. Edit `scripts/generate-types.ts` — add the StructureDefinition URL to `treeShake` |
| 270 | +2. Run `npm run generate-types` (or `bun run generate-types`, `pnpm run generate-types`) |
| 271 | +3. Import the new type in your code |
| 272 | +4. Run typecheck to verify |
| 273 | +5. Commit the updated `src/fhir-types/` directory |
0 commit comments