babelfhir-ts 1.2.0 → 1.2.1
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.
|
@@ -5,7 +5,7 @@ import { versionSlug } from '../../fhir/versionContext.js';
|
|
|
5
5
|
import { buildRequiredConstraints, buildMaxCardinalityConstraints, buildForbiddenFieldConstraintEntries, buildPatternValidations, } from './validatorConstraintBuilders.js';
|
|
6
6
|
import { buildNestedRequiredValidations, buildFixedValueValidations, buildProhibitedFieldValidations, buildPrimitiveFormatValidations, } from './validatorFieldBuilders.js';
|
|
7
7
|
import { buildBindingValidations } from './validatorBindingBuilder.js';
|
|
8
|
-
import { generateBundleRefValidation, generateExtensionStructuralValidation } from './validatorTemplates.js';
|
|
8
|
+
import { generateBundleRefValidation, generateContainedRefValidation, generateExtensionStructuralValidation } from './validatorTemplates.js';
|
|
9
9
|
const log = logger.withTag('validator');
|
|
10
10
|
/**
|
|
11
11
|
* Returns the content of ValidatorOptions.ts — the shared runtime options type
|
|
@@ -146,6 +146,7 @@ export function generateValidateProfileFunction(interfaceName, fields, valueSets
|
|
|
146
146
|
});
|
|
147
147
|
const primitiveFormatValidations = buildPrimitiveFormatValidations(fields);
|
|
148
148
|
const bundleRefValidation = generateBundleRefValidation(baseResourceType);
|
|
149
|
+
const containedRefValidation = generateContainedRefValidation(baseResourceType);
|
|
149
150
|
const extensionStructuralValidation = generateExtensionStructuralValidation();
|
|
150
151
|
// ── Deduplicate & filter constraints ────────────────────────────────────
|
|
151
152
|
const uniqueConstraints = Array.from(new Map(constraintsWithContext.map((item) => [item.constraint.expression + "|" + item.fieldPath, item])).values());
|
|
@@ -229,7 +230,7 @@ export async function validate${interfaceName}(resource: ${interfaceName}, optio
|
|
|
229
230
|
const errors: string[] = [];
|
|
230
231
|
const warnings: string[] = [];
|
|
231
232
|
void options;
|
|
232
|
-
${extensionStructuralValidation}
|
|
233
|
+
${extensionStructuralValidation}${containedRefValidation}
|
|
233
234
|
return { errors, warnings };
|
|
234
235
|
}`,
|
|
235
236
|
valueSetImports
|
|
@@ -265,7 +266,7 @@ ${extensionStructuralValidation}
|
|
|
265
266
|
const errors: string[] = [];
|
|
266
267
|
const warnings: string[] = [];
|
|
267
268
|
${fhirpathOptionsBlock}
|
|
268
|
-
${validationLogic}${fixedPatternValidations.join('')}${nestedRequiredValidations.join('')}${fixedValueValidations.join('')}${(bindingValidations.length > 0 || prohibitedFieldValidations.length > 0 || primitiveFormatValidations.length > 0) ? `\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const _bRes = resource as Record<string, any>;` : ''}${prohibitedFieldValidations.join('')}${bindingValidations.join('')}${primitiveFormatValidations.join('')}${sliceValidations.join('')}${extensionStructuralValidation}${bundleRefValidation}
|
|
269
|
+
${validationLogic}${fixedPatternValidations.join('')}${nestedRequiredValidations.join('')}${fixedValueValidations.join('')}${(bindingValidations.length > 0 || prohibitedFieldValidations.length > 0 || primitiveFormatValidations.length > 0) ? `\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const _bRes = resource as Record<string, any>;` : ''}${prohibitedFieldValidations.join('')}${bindingValidations.join('')}${primitiveFormatValidations.join('')}${sliceValidations.join('')}${extensionStructuralValidation}${containedRefValidation}${bundleRefValidation}
|
|
269
270
|
return { errors, warnings };
|
|
270
271
|
}`,
|
|
271
272
|
valueSetImports
|
|
@@ -31,7 +31,7 @@ export function generateBundleRefValidation(baseResourceType) {
|
|
|
31
31
|
const ref = rec.reference as string;
|
|
32
32
|
if (ref.startsWith('urn:uuid:') || ref.startsWith('urn:oid:')) {
|
|
33
33
|
if (!_fullUrls.has(ref)) {
|
|
34
|
-
errors.push('
|
|
34
|
+
errors.push('Bundle reference not found: ' + ref);
|
|
35
35
|
}
|
|
36
36
|
} else if (/^[A-Za-z]+\\//.test(ref)) {
|
|
37
37
|
if (!_resIds.has(ref)) {
|
|
@@ -40,7 +40,7 @@ export function generateBundleRefValidation(baseResourceType) {
|
|
|
40
40
|
if (_fu.endsWith('/' + ref) || _fu.endsWith(ref)) { _found = true; break; }
|
|
41
41
|
}
|
|
42
42
|
if (!_found) {
|
|
43
|
-
errors.push('
|
|
43
|
+
errors.push('Bundle reference not found: ' + ref);
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
}
|
|
@@ -56,6 +56,46 @@ export function generateBundleRefValidation(baseResourceType) {
|
|
|
56
56
|
}
|
|
57
57
|
`;
|
|
58
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* Generate standalone contained reference resolution validation code.
|
|
61
|
+
* For non-Bundle resources, verifies that #fragment references resolve to contained[] entries.
|
|
62
|
+
* Bundle validation is handled separately by generateBundleRefValidation.
|
|
63
|
+
*/
|
|
64
|
+
export function generateContainedRefValidation(baseResourceType) {
|
|
65
|
+
// Bundle resources have their own reference resolution check; skip them here
|
|
66
|
+
if (baseResourceType === 'Bundle')
|
|
67
|
+
return '';
|
|
68
|
+
return `
|
|
69
|
+
// Standalone contained reference resolution: verify #id references resolve to contained[] entries
|
|
70
|
+
{
|
|
71
|
+
const _res = resource as unknown as Record<string, unknown>;
|
|
72
|
+
const _containedIds = new Set<string>();
|
|
73
|
+
if (Array.isArray(_res.contained)) {
|
|
74
|
+
for (const _c of _res.contained as Array<Record<string, unknown>>) {
|
|
75
|
+
if (_c && typeof _c.id === 'string') _containedIds.add(_c.id);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const _checkContainedRef = (obj: unknown): void => {
|
|
79
|
+
if (!obj || typeof obj !== 'object') return;
|
|
80
|
+
if (Array.isArray(obj)) { obj.forEach(_checkContainedRef); return; }
|
|
81
|
+
const _rec = obj as Record<string, unknown>;
|
|
82
|
+
if (typeof _rec.reference === 'string') {
|
|
83
|
+
const _ref = _rec.reference as string;
|
|
84
|
+
if (_ref.startsWith('#')) {
|
|
85
|
+
const _id = _ref.substring(1);
|
|
86
|
+
if (_id && !_containedIds.has(_id)) {
|
|
87
|
+
errors.push('Contained reference not found: ' + _ref);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
for (const [_k, _v] of Object.entries(_rec)) {
|
|
92
|
+
if (_k !== 'contained') _checkContainedRef(_v);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
_checkContainedRef(_res);
|
|
96
|
+
}
|
|
97
|
+
`;
|
|
98
|
+
}
|
|
59
99
|
/**
|
|
60
100
|
* Generate extension structural validation code.
|
|
61
101
|
* Checks extension.url required, ext-1 constraint, empty objects.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
2
|
import fs from 'fs';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
|
-
import { extractPackage, readStructureDefinitionsFromDir, createPackageFromDir, readValueSetCodesWithDependencies, readValueSetsFromDir, detectFhirVersion, ensureDependenciesDownloaded } from './parser/packageParser.js';
|
|
4
|
+
import { extractPackage, readStructureDefinitionsFromDir, readStructureDefinitionsFromDependencies, createPackageFromDir, readValueSetCodesWithDependencies, readValueSetsFromDir, detectFhirVersion, ensureDependenciesDownloaded } from './parser/packageParser.js';
|
|
5
5
|
import { fetchStructureDefinitions, fetchStructureDefinition, registerLocalStructureDefinitions, clearLocalStructureDefinitions, collectValueSetBindingUrls } from './parser/sdParser.js';
|
|
6
6
|
import { ensureDirectoryExists, downloadFile } from './core/utils.js';
|
|
7
7
|
import { getFhirPackagesCacheDir } from './core/cacheConfig.js';
|
|
@@ -235,6 +235,9 @@ export async function generate(fhirSource, outputDir, flags) {
|
|
|
235
235
|
structureDefinitions = readStructureDefinitionsFromDir(extracted);
|
|
236
236
|
// Ensure dependency packages are downloaded before loading ValueSets
|
|
237
237
|
await ensureDependenciesDownloaded(extracted);
|
|
238
|
+
// Register SDs from dependency packages so external profile resolution can find them
|
|
239
|
+
const depSDs = readStructureDefinitionsFromDependencies(extracted);
|
|
240
|
+
registerLocalStructureDefinitions(depSDs);
|
|
238
241
|
// Load ValueSets from package AND its dependencies (for binding resolution)
|
|
239
242
|
valueSetCodesMap = readValueSetCodesWithDependencies(extracted);
|
|
240
243
|
valueSets = readValueSetsFromDir(extracted);
|
|
@@ -326,6 +329,9 @@ export async function generateIntoPackage(packageArchivePath, outArchivePath, fl
|
|
|
326
329
|
const { existingStructureDefinitions, profileIdToName, profileUrlToName } = buildProfileRegistries(structureDefinitions, fhirInterfaceNames);
|
|
327
330
|
// Register all local StructureDefinitions for resolution before HTTP fetches
|
|
328
331
|
registerLocalStructureDefinitions(structureDefinitions);
|
|
332
|
+
// Register SDs from dependency packages so external profile resolution can find them
|
|
333
|
+
const depSDs = readStructureDefinitionsFromDependencies(extractedRoot);
|
|
334
|
+
registerLocalStructureDefinitions(depSDs);
|
|
329
335
|
for (const sd of structureDefinitions) {
|
|
330
336
|
await processStructureDefinition(sd, { outputDir, fhirSourceHint: '', valueSetCodesMap, valueSets, existingStructureDefinitions, profileIdToName, profileUrlToName, flags });
|
|
331
337
|
}
|
|
@@ -390,11 +396,21 @@ export async function generateIntoPackage(packageArchivePath, outArchivePath, fl
|
|
|
390
396
|
// Install fhirpath type stub so tsc can resolve validator imports
|
|
391
397
|
const { installFhirpathStub, removeFhirpathStub } = await import('./emitters/validator/fhirpathStubInstaller.js');
|
|
392
398
|
installFhirpathStub(outputDir);
|
|
399
|
+
// Install minimal @types/node stub so tsc can resolve `import { createRequire } from 'module'`
|
|
400
|
+
const nodeTypesDir = path.join(outputDir, 'node_modules', '@types', 'node');
|
|
401
|
+
fs.mkdirSync(nodeTypesDir, { recursive: true });
|
|
402
|
+
fs.writeFileSync(path.join(nodeTypesDir, 'index.d.ts'), `declare module 'module' {\n export function createRequire(filename: string | URL): NodeRequire;\n}\n`);
|
|
403
|
+
fs.writeFileSync(path.join(nodeTypesDir, 'package.json'), JSON.stringify({ name: '@types/node', version: '0.0.0-stub', types: 'index.d.ts' }));
|
|
393
404
|
// Compile TypeScript to JavaScript
|
|
394
405
|
logger.log('Compiling TypeScript to JavaScript...');
|
|
395
406
|
await compileTypeScriptToJS(outputDir);
|
|
396
407
|
// Remove fhirpath stub — the real package is a peer dependency
|
|
397
408
|
removeFhirpathStub(outputDir);
|
|
409
|
+
// Remove @types/node stub — only needed for compilation
|
|
410
|
+
try {
|
|
411
|
+
fs.rmSync(path.join(outputDir, 'node_modules', '@types'), { recursive: true, force: true });
|
|
412
|
+
}
|
|
413
|
+
catch { /* ignore */ }
|
|
398
414
|
// Remove base client type stubs — they were only needed for tsc to resolve
|
|
399
415
|
// @babelfhir-ts/client-<version> imports during compilation. The real package is
|
|
400
416
|
// installed by the consumer via npm.
|
|
@@ -467,6 +483,9 @@ export async function generateForDirectory(inputDir, outputDir, flags) {
|
|
|
467
483
|
const valueSets = readValueSetsFromDir(inputDir);
|
|
468
484
|
// Ensure dependency packages are downloaded before loading ValueSets
|
|
469
485
|
await ensureDependenciesDownloaded(inputDir);
|
|
486
|
+
// Register SDs from dependency packages so external profile resolution can find them
|
|
487
|
+
const depSDs = readStructureDefinitionsFromDependencies(inputDir);
|
|
488
|
+
registerLocalStructureDefinitions(depSDs);
|
|
470
489
|
const valueSetCodesMap = readValueSetCodesWithDependencies(inputDir);
|
|
471
490
|
logger.log(`Loaded ${valueSets.size} ValueSets from ${inputDir} (${Array.from(valueSets.values()).filter(vs => vs.isSmall).length} suitable for union types)`);
|
|
472
491
|
const entries = fs.readdirSync(inputDir);
|
|
@@ -521,6 +521,52 @@ export async function ensureDependenciesDownloaded(extractedRoot) {
|
|
|
521
521
|
}
|
|
522
522
|
await walkDeps(extractedRoot);
|
|
523
523
|
}
|
|
524
|
+
/**
|
|
525
|
+
* Reads StructureDefinitions from all dependency packages (recursively).
|
|
526
|
+
* Returns SDs from dependencies only — the main package SDs are loaded separately.
|
|
527
|
+
* This allows external profile resolution to find extension SDs from dependency packages
|
|
528
|
+
* (e.g., hl7.fhir.uv.extensions) without HTTP fetching.
|
|
529
|
+
*/
|
|
530
|
+
export function readStructureDefinitionsFromDependencies(extractedRoot) {
|
|
531
|
+
const cacheDir = getFhirPackagesCacheDir();
|
|
532
|
+
const visited = new Set();
|
|
533
|
+
const allSDs = [];
|
|
534
|
+
function walkDeps(pkgDir, isRoot) {
|
|
535
|
+
const pkgDirNorm = path.normalize(pkgDir);
|
|
536
|
+
if (visited.has(pkgDirNorm))
|
|
537
|
+
return;
|
|
538
|
+
visited.add(pkgDirNorm);
|
|
539
|
+
// Read SDs from this package (skip the root — those are loaded separately)
|
|
540
|
+
if (!isRoot) {
|
|
541
|
+
const sds = readStructureDefinitionsFromDir(pkgDir);
|
|
542
|
+
allSDs.push(...sds);
|
|
543
|
+
}
|
|
544
|
+
// Find package.json and recurse into dependencies
|
|
545
|
+
const packageJsonPath = path.join(pkgDir, 'package', 'package.json');
|
|
546
|
+
const altPath = path.join(pkgDir, 'package.json');
|
|
547
|
+
const pkgJsonPath = fs.existsSync(packageJsonPath) ? packageJsonPath
|
|
548
|
+
: fs.existsSync(altPath) ? altPath : null;
|
|
549
|
+
if (!pkgJsonPath)
|
|
550
|
+
return;
|
|
551
|
+
try {
|
|
552
|
+
const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'));
|
|
553
|
+
const deps = pkgJson.dependencies || {};
|
|
554
|
+
for (const [depName, depVersion] of Object.entries(deps)) {
|
|
555
|
+
for (const sep of ['@', '#']) {
|
|
556
|
+
const depDir = path.join(cacheDir, `${depName}${sep}${depVersion}`);
|
|
557
|
+
if (fs.existsSync(depDir)) {
|
|
558
|
+
walkDeps(depDir, false);
|
|
559
|
+
break;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
catch { /* skip */ }
|
|
565
|
+
}
|
|
566
|
+
walkDeps(extractedRoot, true);
|
|
567
|
+
log.info(`Loaded ${allSDs.length} StructureDefinitions from dependency packages`);
|
|
568
|
+
return allSDs;
|
|
569
|
+
}
|
|
524
570
|
/**
|
|
525
571
|
* Reads ValueSet codes from a package and all its dependencies (recursively).
|
|
526
572
|
* Looks for dependency packages in the FHIR package cache directory.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "babelfhir-ts",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "BabelFHIR-TS: generate TypeScript interfaces, validators, and helper classes from FHIR R4/R4B StructureDefinitions (profiles) directly inside package archives.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "out/src/main.js",
|