babelfhir-ts 1.5.21 → 1.6.0
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 +6 -3
- package/out/src/generator/core/extensionSdReader.js +2 -1
- package/out/src/generator/core/utils.js +69 -0
- package/out/src/generator/emitters/class/classGeneratorHelpers.js +10 -2
- package/out/src/generator/emitters/class/sliceElementDefaults.js +10 -1
- package/out/src/generator/emitters/interface/interfaceFieldProcessor.js +15 -12
- package/out/src/generator/emitters/interface/interfaceFieldUtils.js +6 -3
- package/out/src/generator/emitters/interface/postProcessExtensions.js +8 -2
- package/out/src/generator/emitters/interface/processNestedField.js +4 -6
- package/out/src/generator/emitters/namingsystem/namingSystemGenerator.js +136 -0
- package/out/src/generator/emitters/prefab/prefabEmitter.js +2 -15
- package/out/src/generator/emitters/prefab/prefabRenderer.js +2 -19
- package/out/src/generator/emitters/validator/closedSlicingValidation.js +5 -2
- package/out/src/generator/emitters/validator/fhirpathStubInstaller.js +7 -1
- package/out/src/generator/emitters/validator/sliceBackboneValidation.js +27 -12
- package/out/src/generator/emitters/validator/sliceValidatorGenerator.js +15 -27
- package/out/src/generator/emitters/validator/sliceValidatorUtils.js +5 -1
- package/out/src/generator/emitters/validator/subExtensionScope.js +10 -2
- package/out/src/generator/emitters/validator/validatorExpressions.js +44 -0
- package/out/src/generator/emitters/validator/validatorFieldBuilders.js +34 -17
- package/out/src/generator/emitters/validator/validatorGenerator.js +31 -40
- package/out/src/generator/emitters/validator/validatorRuntime.js +13 -2
- package/out/src/generator/emitters/valueset/valueSetGenerator.js +6 -10
- package/out/src/generator/emitters/zod/zodRefinementBuilder.js +39 -3
- package/out/src/generator/emitters/zod/zodSchemaGenerator.js +36 -8
- package/out/src/generator/generationHelpers.js +59 -7
- package/out/src/generator/index.js +90 -47
- package/out/src/generator/parser/packageParser.js +76 -0
- package/out/src/generator/parser/vsParser.js +4 -2
- package/out/src/generator/sdProcessor.js +30 -21
- package/out/src/main.js +5 -0
- package/package.json +12 -9
- package/parity-matrix.json +6 -0
|
@@ -10,7 +10,7 @@ import path from 'path';
|
|
|
10
10
|
import { generateClass } from './emitters/class/classGenerator.js';
|
|
11
11
|
import { generateInterfaces } from './emitters/interface/interfaceGenerator.js';
|
|
12
12
|
import { fetchStructureDefinition, parseStructureDefinition } from './parser/sdParser.js';
|
|
13
|
-
import { writeInterfaceAndValidatorToFile, mergeFields, alignFields, writeToFile, getBaseResource, sanitizeIdentifier, toPascalCase, stripVersionFromCanonicalUrl } from './core/utils.js';
|
|
13
|
+
import { writeInterfaceAndValidatorToFile, mergeFields, alignFields, writeToFile, getBaseResource, sanitizeIdentifier, toPascalCase, stripVersionFromCanonicalUrl, stripSliceSuffix } from './core/utils.js';
|
|
14
14
|
import { extensionElements, collectSubExtensionSlices, readSubExtensionMin, readBinding } from './core/extensionSdReader.js';
|
|
15
15
|
import { generateValidateProfileFunction } from './emitters/validator/validatorGenerator.js';
|
|
16
16
|
import { generateZodSchema } from './emitters/zod/zodSchemaGenerator.js';
|
|
@@ -113,7 +113,10 @@ async function fillUnstatedFromBaseResource(fields, resourceType) {
|
|
|
113
113
|
if (!current)
|
|
114
114
|
continue;
|
|
115
115
|
for (let i = next; i < segments.length && current; i++) {
|
|
116
|
-
|
|
116
|
+
const segment = segments[i];
|
|
117
|
+
if (segment === undefined)
|
|
118
|
+
break;
|
|
119
|
+
current = resolveComplexTypeChildType(current, segment);
|
|
117
120
|
}
|
|
118
121
|
if (current) {
|
|
119
122
|
field.type = current;
|
|
@@ -193,11 +196,12 @@ async function fillSliceProfileUrlsFromBaseProfiles(fields, baseDefinition) {
|
|
|
193
196
|
async function resolveExtensionMetadata(fields) {
|
|
194
197
|
const extUrls = new Set();
|
|
195
198
|
for (const f of fields) {
|
|
196
|
-
|
|
199
|
+
const firstProfile = f.sliceName ? f.profileUrls?.[0] : undefined;
|
|
200
|
+
if (!firstProfile)
|
|
197
201
|
continue;
|
|
198
|
-
const basePath = (f.name || '').replace(/^[^.]+\./, '')
|
|
202
|
+
const basePath = stripSliceSuffix((f.name || '').replace(/^[^.]+\./, ''));
|
|
199
203
|
if (basePath.endsWith('extension') || basePath.endsWith('modifierExtension')) {
|
|
200
|
-
extUrls.add(
|
|
204
|
+
extUrls.add(firstProfile);
|
|
201
205
|
}
|
|
202
206
|
}
|
|
203
207
|
if (extUrls.size === 0)
|
|
@@ -242,8 +246,9 @@ async function resolveExtensionMetadata(fields) {
|
|
|
242
246
|
// Check binding on value[x] first, then fall back to typed slices
|
|
243
247
|
// (e.g., Extension.valueCode may carry the binding instead of Extension.value[x])
|
|
244
248
|
let binding = readBinding(valueEl);
|
|
245
|
-
|
|
246
|
-
|
|
249
|
+
const onlyType = types.length === 1 ? types[0] : undefined;
|
|
250
|
+
if (!binding && onlyType) {
|
|
251
|
+
const suffix = onlyType.charAt(0).toUpperCase() + onlyType.slice(1);
|
|
247
252
|
const sliceEl = els.find(el => el.id === `Extension.value[x]:value${suffix}` ||
|
|
248
253
|
el.path === `Extension.value${suffix}` ||
|
|
249
254
|
el.id === `Extension.value${suffix}`);
|
|
@@ -274,12 +279,13 @@ async function resolveExtensionMetadata(fields) {
|
|
|
274
279
|
async function resolveDatatypeProfilePins(fields) {
|
|
275
280
|
const urls = new Set();
|
|
276
281
|
for (const f of fields) {
|
|
277
|
-
|
|
282
|
+
const firstProfile = f.sliceName ? f.profileUrls?.[0] : undefined;
|
|
283
|
+
if (!firstProfile)
|
|
278
284
|
continue;
|
|
279
|
-
const basePath = (f.name || '').replace(/^[^.]+\./, '')
|
|
285
|
+
const basePath = stripSliceSuffix((f.name || '').replace(/^[^.]+\./, ''));
|
|
280
286
|
if (basePath.endsWith('extension') || basePath.endsWith('modifierExtension'))
|
|
281
287
|
continue;
|
|
282
|
-
urls.add(
|
|
288
|
+
urls.add(firstProfile);
|
|
283
289
|
}
|
|
284
290
|
if (urls.size === 0)
|
|
285
291
|
return undefined;
|
|
@@ -514,8 +520,11 @@ export async function processStructureDefinition(sd, ctx) {
|
|
|
514
520
|
continue;
|
|
515
521
|
if (f.isOptional || f.isForbidden)
|
|
516
522
|
continue;
|
|
517
|
-
const
|
|
518
|
-
|
|
523
|
+
const parentSegment = parts[1];
|
|
524
|
+
if (!parentSegment)
|
|
525
|
+
continue;
|
|
526
|
+
const parentFieldName = stripSliceSuffix(parentSegment);
|
|
527
|
+
let relativePath = parts.slice(2).map((p) => stripSliceSuffix(p)).join('.');
|
|
519
528
|
if (relativePath.includes('[x]')) {
|
|
520
529
|
const resolvedPath = resolveChoiceSegments(f, parts, candidateRequiredFields);
|
|
521
530
|
// An intermediate choice we cannot narrow to one type has no property name.
|
|
@@ -566,9 +575,9 @@ export async function processStructureDefinition(sd, ctx) {
|
|
|
566
575
|
const parentPath = parts.slice(0, -1).join('.');
|
|
567
576
|
const parentField = candidateRequiredFields.find(pf => pf?.name === parentPath && pf.type && pf.type !== 'any' && pf.type !== 'unknown')
|
|
568
577
|
|| parsed.oldFields.find(pf => pf?.name === parentPath && pf.type && pf.type !== 'any' && pf.type !== 'unknown');
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
fieldType = resolveComplexTypeChildType(parentField.type,
|
|
578
|
+
const leafSegment = parts.at(-1);
|
|
579
|
+
if (parentField?.type && leafSegment) {
|
|
580
|
+
fieldType = resolveComplexTypeChildType(parentField.type, stripSliceSuffix(leafSegment));
|
|
572
581
|
}
|
|
573
582
|
}
|
|
574
583
|
}
|
|
@@ -640,8 +649,8 @@ export async function processStructureDefinition(sd, ctx) {
|
|
|
640
649
|
if (originalField?.typeOptions?.[0]?.targetProfileUrls?.[0]) {
|
|
641
650
|
const targetUrl = stripVersionFromCanonicalUrl(originalField.typeOptions[0].targetProfileUrls[0]);
|
|
642
651
|
const match = targetUrl.match(/\/([^/]+)$/);
|
|
643
|
-
|
|
644
|
-
|
|
652
|
+
const candidate = match?.[1];
|
|
653
|
+
if (candidate) {
|
|
645
654
|
const knownResourceNames = versionCtx().resourceNames;
|
|
646
655
|
if (knownResourceNames.includes(candidate)) {
|
|
647
656
|
rf.targetResourceType = candidate;
|
|
@@ -651,9 +660,9 @@ export async function processStructureDefinition(sd, ctx) {
|
|
|
651
660
|
let resolved = false;
|
|
652
661
|
for (const bf of baseFields) {
|
|
653
662
|
for (const tp of bf.typeOptions?.[0]?.targetProfileUrls ?? []) {
|
|
654
|
-
const
|
|
655
|
-
if (
|
|
656
|
-
rf.targetResourceType =
|
|
663
|
+
const bmCandidate = tp.match(/\/([^/]+)$/)?.[1];
|
|
664
|
+
if (bmCandidate && knownResourceNames.includes(bmCandidate)) {
|
|
665
|
+
rf.targetResourceType = bmCandidate;
|
|
657
666
|
resolved = true;
|
|
658
667
|
break;
|
|
659
668
|
}
|
|
@@ -732,7 +741,7 @@ export async function processStructureDefinition(sd, ctx) {
|
|
|
732
741
|
writeToFile(path.join(outputDir, `${interfaceName}Class.ts`), classContent);
|
|
733
742
|
}
|
|
734
743
|
if (flags?.schema === 'zod') {
|
|
735
|
-
const zodResult = generateZodSchema(interfaceName, aligned.alignedNewFields, baseResource, parsed.oldFields, valueSets);
|
|
744
|
+
const zodResult = generateZodSchema(interfaceName, aligned.alignedNewFields, baseResource, parsed.oldFields, valueSets, !!flags?.zodInvariants);
|
|
736
745
|
writeToFile(path.join(outputDir, `${interfaceName}.zod.ts`), zodResult.code);
|
|
737
746
|
}
|
|
738
747
|
if (flags?.prefab) {
|
package/out/src/main.js
CHANGED
|
@@ -57,6 +57,7 @@ function printUsage() {
|
|
|
57
57
|
console.log(" --no-classes Only generate interfaces and types (skip class generation)");
|
|
58
58
|
console.log(" --no-client Skip FHIR client generation (client generated by default)");
|
|
59
59
|
console.log(" --schema <format> Generate schema files alongside outputs (supported: zod)");
|
|
60
|
+
console.log(" --zod-invariants Emit FHIRPath invariants into Zod schemas (fuller checking, slower parse)");
|
|
60
61
|
console.log(" --dicomweb Generate DICOMweb helpers typed to ImagingStudy profiles in the IG");
|
|
61
62
|
console.log(" --prefab Generate Prefab UI render functions per profile (@maxhealth.tech/prefab)");
|
|
62
63
|
console.log(" --prefab-styles <path> Path to styles module (.ts/.js) copied into the generated prefab/ folder");
|
|
@@ -366,6 +367,10 @@ export function parseCliArgs(argv) {
|
|
|
366
367
|
i++;
|
|
367
368
|
continue;
|
|
368
369
|
}
|
|
370
|
+
if (arg === '--zod-invariants') {
|
|
371
|
+
out.flags.zodInvariants = true;
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
369
374
|
if (arg === '--fhir-version') {
|
|
370
375
|
const val = argv[i + 1];
|
|
371
376
|
if (!val || val.startsWith('-'))
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "babelfhir-ts",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "BabelFHIR-TS: generate TypeScript interfaces, validators, and helper classes from FHIR R4/R4B/R5 StructureDefinitions (profiles) directly inside package archives.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "out/src/main.js",
|
|
@@ -87,23 +87,23 @@
|
|
|
87
87
|
"devDependencies": {
|
|
88
88
|
"@eslint/js": "^10.0.1",
|
|
89
89
|
"@max-health/config": "github:max-health-inc/config",
|
|
90
|
-
"@maxhealth.tech/prefab": "^0.3.
|
|
90
|
+
"@maxhealth.tech/prefab": "^0.3.7",
|
|
91
91
|
"@types/node": "^25.9.5",
|
|
92
|
-
"@types/semver": "^7.
|
|
92
|
+
"@types/semver": "^7.8.0",
|
|
93
93
|
"@types/unzipper": "^0.10.11",
|
|
94
94
|
"@vitest/coverage-v8": "^4.1.10",
|
|
95
|
-
"@vitest/eslint-plugin": "^1.6.
|
|
95
|
+
"@vitest/eslint-plugin": "^1.6.26",
|
|
96
96
|
"@vitest/ui": "^4.1.10",
|
|
97
97
|
"cross-env": "^10.1.0",
|
|
98
98
|
"eslint": "^10.8.0",
|
|
99
|
-
"fhirpath": "^5.
|
|
100
|
-
"globals": "^17.
|
|
99
|
+
"fhirpath": "^5.1.0",
|
|
100
|
+
"globals": "^17.9.0",
|
|
101
101
|
"rimraf": "^6.1.3",
|
|
102
102
|
"semver": "^7.8.5",
|
|
103
103
|
"typescript": "^6.0.3",
|
|
104
|
-
"typescript-eslint": "^8.
|
|
104
|
+
"typescript-eslint": "^8.66.0",
|
|
105
105
|
"vitepress": "^1.6.4",
|
|
106
|
-
"vitepress-plugin-llms": "^1.13.
|
|
106
|
+
"vitepress-plugin-llms": "^1.13.4",
|
|
107
107
|
"vitest": "^4.1.10",
|
|
108
108
|
"zod": "4.3.6"
|
|
109
109
|
},
|
|
@@ -111,7 +111,10 @@
|
|
|
111
111
|
"@types/fhir": "^0.0.44",
|
|
112
112
|
"consola": "^3.4.2",
|
|
113
113
|
"tar": "^7.5.22",
|
|
114
|
-
"tsx": "^4.23.
|
|
114
|
+
"tsx": "^4.23.7",
|
|
115
115
|
"unzipper": "^0.12.5"
|
|
116
|
+
},
|
|
117
|
+
"overrides": {
|
|
118
|
+
"brace-expansion": "^5.0.9"
|
|
116
119
|
}
|
|
117
120
|
}
|
package/parity-matrix.json
CHANGED