babelfhir-ts 1.0.15 → 1.0.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +115 -31
- package/out/main.js +41 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
## Why BabelFHIR-TS?
|
|
6
6
|
|
|
7
|
-
**The FHIR Challenge**: Implementation Guides define strict profiles
|
|
7
|
+
**The FHIR Challenge**: Implementation Guides define strict profiles that constrain base FHIR resources with required or must-support elements, custom extensions, value set bindings, and cardinality rules. Existing TypeScript libraries can't capture these requirements when you need profile-specific types, leading to an overhead when using TypeScript to build apps that interact with FHIR servers.
|
|
8
8
|
|
|
9
|
-
**The BabelFHIR-TS Solution**: Automatically generates TypeScript interfaces and validation logic directly from StructureDefinition JSON. Your IDE autocompletes
|
|
9
|
+
**The BabelFHIR-TS Solution**: Automatically generates TypeScript interfaces and validation logic directly from StructureDefinition JSON. Your IDE autocompletes required fields, flags missing extensions at compile-time, and validates FHIRPath invariants at runtime.
|
|
10
10
|
|
|
11
11
|
### What you get
|
|
12
12
|
|
|
@@ -15,19 +15,11 @@
|
|
|
15
15
|
- **Type-safe extension handling** with proper slicing and nested extension support
|
|
16
16
|
- **Random data builders** for testing and development (when class generation is enabled)
|
|
17
17
|
- **Zero manual mapping**—consume any FHIR package or Implementation Guide directly from registries
|
|
18
|
+
- **Fast and lightweight**—pure TypeScript code generation with no runtime dependencies except `fhirpath` for validation
|
|
19
|
+
- **Install any FHIR profile as a node module**—use `babelfhir-ts install` to add Implementation Guides directly to your TypeScript project
|
|
18
20
|
|
|
19
21
|
Use it to build FHIR-compliant APIs, validate incoming resources against profiles, or generate type-safe client SDKs from Implementation Guides.
|
|
20
22
|
|
|
21
|
-
## Key features
|
|
22
|
-
|
|
23
|
-
- 🎯 **Profile-first design** – generate interfaces that understand your IG's must-support elements, slices, and extensions
|
|
24
|
-
- ⚙️ **CLI-first workflow** – install globally or run with `npx`; works against package archives, entire directories, or single `.json` profiles
|
|
25
|
-
- ✅ **Built-in validation** – every generated class includes an async `validate()` function that executes profile constraints through FHIRPath *(high-level checks only; see details below)*
|
|
26
|
-
- 🔌 **Extension-aware** – properly types extension slices, nested extensions, and sidecar `_field` elements for primitive extensions
|
|
27
|
-
- 📦 **Package-friendly** – re-embeds generated code back into `.tgz` archives or expands into a target folder for direct consumption
|
|
28
|
-
- 🌐 **Registry integration** – download and process packages directly from Simplifier, HL7, or custom FHIR registries
|
|
29
|
-
- ♻️ **Caching + cleanup** – opt into `--no-cache` to remove temporary `.cache` directories after generation
|
|
30
|
-
|
|
31
23
|
## Installation
|
|
32
24
|
|
|
33
25
|
Install globally (recommended when using the CLI frequently):
|
|
@@ -85,18 +77,18 @@ const { errors, warnings } = await patient.validate();
|
|
|
85
77
|
babelfhir-ts [options] [<input> [output]]
|
|
86
78
|
```
|
|
87
79
|
|
|
88
|
-
| Argument / option
|
|
89
|
-
|
|
|
90
|
-
| `<input>`
|
|
91
|
-
| `<output>`
|
|
92
|
-
| `install`
|
|
93
|
-
| `--package <pkg@version>` | Fetch a package from a registry and process it without manual download.
|
|
94
|
-
| `--registry <url>`
|
|
95
|
-
| `--log <level>`
|
|
96
|
-
| `--no-cache`
|
|
97
|
-
| `--no-classes`
|
|
98
|
-
| `-h, --help`
|
|
99
|
-
| `-v, --version`
|
|
80
|
+
| Argument / option | Description |
|
|
81
|
+
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
82
|
+
| `<input>` | Directory of FHIR packages/StructureDefinitions, single package (`.tgz`/`.zip`), or single StructureDefinition `.json`. Defaults to `./input` when omitted. |
|
|
83
|
+
| `<output>` | Destination directory or archive. Defaults to `./output` when omitted. |
|
|
84
|
+
| `install` | Downloads, processes, and installs a package as a project dependency. |
|
|
85
|
+
| `--package <pkg@version>` | Fetch a package from a registry and process it without manual download. |
|
|
86
|
+
| `--registry <url>` | Custom registry base URL (default:`https://packages.simplifier.net`). |
|
|
87
|
+
| `--log <level>` | Control logging output:`none` (default), `console`, or `file`. |
|
|
88
|
+
| `--no-cache` | Remove the `.cache` directory once generation completes. |
|
|
89
|
+
| `--no-classes` | Skip emitting helper classes (interfaces & validators only). |
|
|
90
|
+
| `-h, --help` | Print usage help. |
|
|
91
|
+
| `-v, --version` | Print the BabelFHIR-TS version. |
|
|
100
92
|
|
|
101
93
|
### Supported inputs
|
|
102
94
|
|
|
@@ -115,21 +107,113 @@ For each StructureDefinition the generator produces:
|
|
|
115
107
|
- `validate()` that evaluates FHIR invariant expressions via `fhirpath`
|
|
116
108
|
- optional validation parity artefacts when you run the test suite (`npm test validatorParity`)
|
|
117
109
|
|
|
118
|
-
The validator relies on `fhirpath.evaluate(...)` to enforce profile invariants (including min cardinalities, slices, and custom expressions) without requiring a full FHIR server. It intentionally performs **high-level** checks only: terminology expansion, reference resolution, and other server-backed logic are out of scope, so keep a downstream validator (e.g
|
|
110
|
+
The validator relies on `fhirpath.evaluate(...)` to enforce profile invariants (including min cardinalities, slices, and custom expressions) without requiring a full FHIR server. It intentionally performs **high-level** checks only: terminology expansion, reference resolution, and other server-backed logic are out of scope, so keep a downstream validator (e.g. the HL7 Java validator CLI) in your QA pipeline for full conformance.
|
|
111
|
+
|
|
112
|
+
### Example: Generated US Core Encounter Profile
|
|
113
|
+
|
|
114
|
+
Given the US Core Encounter profile, BabelFHIR-TS generates TypeScript interfaces that capture all must-support elements and profile extensions:
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
import { Encounter, Meta, Extension, Identifier, Coding, CodeableConcept,
|
|
118
|
+
Reference, EncounterParticipant, Period, EncounterHospitalization,
|
|
119
|
+
EncounterLocation } from "fhir/r4";
|
|
120
|
+
|
|
121
|
+
// Extension interface with type-safe URL
|
|
122
|
+
export interface UsCoreInterpreterNeeded extends Extension {
|
|
123
|
+
url: 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-interpreter-needed'
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Profile interface extending base Encounter with must-support constraints
|
|
127
|
+
export interface USCoreEncounterProfile extends Encounter {
|
|
128
|
+
/** Must Support */
|
|
129
|
+
meta?: Meta;
|
|
130
|
+
/** Must Support */
|
|
131
|
+
identifier?: Identifier[];
|
|
132
|
+
/** Must Support */
|
|
133
|
+
class: Coding;
|
|
134
|
+
/** Must Support */
|
|
135
|
+
type: CodeableConcept[];
|
|
136
|
+
/** Must Support */
|
|
137
|
+
subject: Reference;
|
|
138
|
+
/** Must Support */
|
|
139
|
+
participant?: EncounterParticipant[];
|
|
140
|
+
/** Must Support */
|
|
141
|
+
period?: Period;
|
|
142
|
+
/** Must Support */
|
|
143
|
+
reasonCode?: CodeableConcept[];
|
|
144
|
+
/** Must Support */
|
|
145
|
+
reasonReference?: Reference[];
|
|
146
|
+
/** Must Support */
|
|
147
|
+
hospitalization?: EncounterHospitalization;
|
|
148
|
+
/** Must Support */
|
|
149
|
+
location?: EncounterLocation[];
|
|
150
|
+
/** Must Support */
|
|
151
|
+
serviceProvider?: Reference;
|
|
152
|
+
|
|
153
|
+
// Typed extension slicing
|
|
154
|
+
extension?: (Extension | UsCoreInterpreterNeeded)[];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Generated validator function
|
|
158
|
+
export async function validateUSCoreEncounterProfile(
|
|
159
|
+
resource: USCoreEncounterProfile
|
|
160
|
+
): Promise<{ errors: string[], warnings: string[] }>;
|
|
161
|
+
|
|
162
|
+
```
|
|
119
163
|
|
|
120
164
|
## Scripts for contributors
|
|
121
165
|
|
|
122
|
-
| Script
|
|
123
|
-
|
|
|
124
|
-
| `npm run generate`
|
|
125
|
-
| `npm run generate:check`
|
|
126
|
-
| `npm test`
|
|
127
|
-
| `npm run download-validator` | Fetch the official HL7 validation jar used for parity testing.
|
|
166
|
+
| Script | Purpose |
|
|
167
|
+
| ------------------------------ | ---------------------------------------------------------------------------- |
|
|
168
|
+
| `npm run generate` | Execute the CLI against the local `input/` folder and refresh `output/`. |
|
|
169
|
+
| `npm run generate:check` | End-to-end check: generate, type-check, and lint the emitted output. |
|
|
170
|
+
| `npm test` | Type-check and run all Vitest suites (coverage enabled). |
|
|
171
|
+
| `npm run download-validator` | Fetch the official HL7 validation jar used for parity testing. |
|
|
128
172
|
|
|
129
173
|
## Caching notes
|
|
130
174
|
|
|
131
175
|
The generator caches downloaded StructureDefinitions and packages inside `.cache/`. When you need a clean run, pass `--no-cache` or manually remove the folder. Temporary downloads land in `.temp-*` directories and are cleaned up automatically.
|
|
132
176
|
|
|
177
|
+
## Limitations
|
|
178
|
+
|
|
179
|
+
BabelFHIR-TS is a code generation tool that parses FHIR StructureDefinitions and produces TypeScript interfaces and validators. While it handles many common FHIR profiling patterns, there are important limitations to be aware of:
|
|
180
|
+
|
|
181
|
+
### Profile Mapping Accuracy
|
|
182
|
+
|
|
183
|
+
- **Not guaranteed for all IGs**: The generator uses heuristics to interpret StructureDefinition constraints, slicing rules, and extensions. Complex or unusual profiling patterns may not map correctly to TypeScript.
|
|
184
|
+
- **Test before production**: Always validate the generated code against your specific Implementation Guide's examples and test cases. We recommend running the official FHIR validator alongside BabelFHIR-TS in your QA pipeline.
|
|
185
|
+
- **Edge cases**: Rare profiling constructs (deeply nested slicing, conditional constraints, complex discriminators) may generate suboptimal or incomplete types.
|
|
186
|
+
|
|
187
|
+
### Validation Scope
|
|
188
|
+
|
|
189
|
+
- **High-level checks only**: The generated `validate()` functions execute FHIRPath expressions from profile invariants but do **not** perform:
|
|
190
|
+
- Terminology expansion or ValueSet validation
|
|
191
|
+
- Reference resolution (checking that referenced resources exist)
|
|
192
|
+
- Full cardinality enforcement for complex slicing scenarios
|
|
193
|
+
- Server-side business logic or workflow rules
|
|
194
|
+
- **Use the official validator**: For production conformance testing, use the [HL7 FHIR Validator](https://github.com/hapifhir/org.hl7.fhir.core) alongside BabelFHIR-TS.
|
|
195
|
+
|
|
196
|
+
### TypeScript Limitations
|
|
197
|
+
|
|
198
|
+
- **Runtime type checking is limited**: TypeScript types are erased at compile time. The generated interfaces provide compile-time safety but cannot enforce constraints at runtime without the validator functions.
|
|
199
|
+
- **Extension slicing**: While the generator creates typed extension interfaces, TypeScript cannot enforce that extension arrays contain exactly the required slices at compile time (this is validated at runtime).
|
|
200
|
+
- **Choice types**: FHIR's `[x]` choice types (e.g., `value[x]`) are represented as union types in TypeScript, which may require runtime type narrowing.
|
|
201
|
+
|
|
202
|
+
### FHIR Version Support
|
|
203
|
+
|
|
204
|
+
- **R4 only**: The current version targets FHIR R4. Support for R5, DSTU2, or STU3 is not yet available.
|
|
205
|
+
- **Dependencies**: Generated code depends on `@types/fhir` (R4 definitions) and `fhirpath` (R4 compatible).
|
|
206
|
+
|
|
207
|
+
### Reporting Issues
|
|
208
|
+
|
|
209
|
+
If you encounter an Implementation Guide that doesn't generate correctly, please [open an issue](https://github.com/quotentiroler/BabelFHIR-ts/issues) with:
|
|
210
|
+
- The package name and version
|
|
211
|
+
- The specific StructureDefinition URL
|
|
212
|
+
- Expected vs. actual generated output
|
|
213
|
+
- Any validation errors or type mismatches
|
|
214
|
+
|
|
215
|
+
We continuously improve the generator based on real-world IG usage, and your feedback helps make BabelFHIR-TS more robust.
|
|
216
|
+
|
|
133
217
|
## License
|
|
134
218
|
|
|
135
219
|
ISC © Maximilian Nussbaumer
|
package/out/main.js
CHANGED
|
@@ -210,6 +210,8 @@ async function run() {
|
|
|
210
210
|
console.error(' or: babelfhir-ts install ./package.tgz');
|
|
211
211
|
process.exit(1);
|
|
212
212
|
}
|
|
213
|
+
// Force no-cache for install command
|
|
214
|
+
flags.noCache = true;
|
|
213
215
|
try {
|
|
214
216
|
let downloadedPath;
|
|
215
217
|
// Check if packageToInstall is a local file path
|
|
@@ -234,38 +236,55 @@ async function run() {
|
|
|
234
236
|
}
|
|
235
237
|
console.log(`✓ Package processed successfully: ${path.basename(outputArchive)}`);
|
|
236
238
|
// Extract the archive to access generated folder
|
|
237
|
-
console.log(`Extracting
|
|
239
|
+
console.log(`Extracting generated code...`);
|
|
238
240
|
const { extract } = await import('tar');
|
|
239
241
|
const extractDir = path.join(process.cwd(), '.temp-install');
|
|
240
242
|
if (!fs.existsSync(extractDir)) {
|
|
241
243
|
fs.mkdirSync(extractDir, { recursive: true });
|
|
242
244
|
}
|
|
243
245
|
await extract({ cwd: extractDir, file: outputArchive });
|
|
244
|
-
//
|
|
245
|
-
|
|
246
|
+
// Look for generated folder - could be at root or under 'package' subfolder
|
|
247
|
+
let generatedDir = path.join(extractDir, 'generated');
|
|
248
|
+
if (!fs.existsSync(generatedDir)) {
|
|
249
|
+
generatedDir = path.join(extractDir, 'package', 'generated');
|
|
250
|
+
}
|
|
246
251
|
if (!fs.existsSync(generatedDir)) {
|
|
247
252
|
throw new Error('Generated folder not found in package archive');
|
|
248
253
|
}
|
|
249
|
-
|
|
254
|
+
// Verify package.json exists
|
|
255
|
+
const pkgJsonPath = path.join(generatedDir, 'package.json');
|
|
256
|
+
if (!fs.existsSync(pkgJsonPath)) {
|
|
257
|
+
throw new Error(`package.json not found in generated folder: ${generatedDir}`);
|
|
258
|
+
}
|
|
259
|
+
// Create a tarball from the generated folder to avoid symlink issues on Windows
|
|
260
|
+
const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8'));
|
|
261
|
+
const packageName = pkgJson.name;
|
|
262
|
+
console.log(`Creating package tarball for ${packageName}...`);
|
|
263
|
+
// Use npm pack to create a proper tarball
|
|
250
264
|
const { create } = await import('tar');
|
|
251
|
-
const
|
|
265
|
+
const tarballName = `${packageName.replace(/[@/]/g, '-')}.tgz`;
|
|
266
|
+
const tarballPath = path.join(process.cwd(), tarballName);
|
|
267
|
+
// Create tarball from the generated directory
|
|
252
268
|
await create({
|
|
253
269
|
gzip: true,
|
|
254
|
-
file:
|
|
255
|
-
cwd: generatedDir
|
|
256
|
-
}, [
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
270
|
+
file: tarballPath,
|
|
271
|
+
cwd: path.dirname(generatedDir)
|
|
272
|
+
}, [path.basename(generatedDir)]);
|
|
273
|
+
console.log(`Installing ${packageName}...`);
|
|
274
|
+
// Install from the tarball (this creates a proper copy, not a symlink)
|
|
275
|
+
await npmInstall(tarballPath);
|
|
276
|
+
// Clean up temporary files
|
|
260
277
|
console.log(`Cleaning up temporary files...`);
|
|
261
278
|
if (fs.existsSync(extractDir)) {
|
|
262
279
|
fs.rmSync(extractDir, { recursive: true, force: true });
|
|
263
280
|
}
|
|
264
281
|
if (fs.existsSync(outputArchive)) {
|
|
265
282
|
fs.unlinkSync(outputArchive);
|
|
283
|
+
console.log(`✓ Removed ${path.basename(outputArchive)}`);
|
|
266
284
|
}
|
|
267
|
-
if (fs.existsSync(
|
|
268
|
-
fs.unlinkSync(
|
|
285
|
+
if (fs.existsSync(tarballPath)) {
|
|
286
|
+
fs.unlinkSync(tarballPath);
|
|
287
|
+
console.log(`✓ Removed ${path.basename(tarballPath)}`);
|
|
269
288
|
}
|
|
270
289
|
if (flags.noCache) {
|
|
271
290
|
cleanupCache();
|
|
@@ -274,7 +293,7 @@ async function run() {
|
|
|
274
293
|
return;
|
|
275
294
|
}
|
|
276
295
|
catch (error) {
|
|
277
|
-
// Clean up
|
|
296
|
+
// Clean up temporary files on error
|
|
278
297
|
const tempDir = path.join(process.cwd(), '.temp-download');
|
|
279
298
|
if (fs.existsSync(tempDir)) {
|
|
280
299
|
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
@@ -283,6 +302,14 @@ async function run() {
|
|
|
283
302
|
if (fs.existsSync(extractDir)) {
|
|
284
303
|
fs.rmSync(extractDir, { recursive: true, force: true });
|
|
285
304
|
}
|
|
305
|
+
// Clean up any leftover archive
|
|
306
|
+
const baseName = packageToInstall.includes('@')
|
|
307
|
+
? packageToInstall.replace('@', '-').replace(/\//g, '-')
|
|
308
|
+
: path.basename(packageToInstall, path.extname(packageToInstall));
|
|
309
|
+
const outputArchive = path.join(process.cwd(), `${baseName}.with-generated.tgz`);
|
|
310
|
+
if (fs.existsSync(outputArchive)) {
|
|
311
|
+
fs.unlinkSync(outputArchive);
|
|
312
|
+
}
|
|
286
313
|
console.error(`Error: ${error.message}`);
|
|
287
314
|
process.exit(1);
|
|
288
315
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "babelfhir-ts",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.17",
|
|
4
4
|
"description": "BabelFHIR-TS: generate TypeScript interfaces, validators, and helper classes from FHIR R4 StructureDefinitions (profiles) directly inside package archives.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "out/main.js",
|