@atomic-ehr/codegen 0.0.18 → 0.0.19-canary.20260921150939.55823d6
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 +71 -0
- package/assets/api/writer-generator/python/profile_helpers.py +29 -1
- package/assets/api/writer-generator/typescript/profile-helpers.ts +66 -4
- package/dist/cli/index.js +21 -10
- package/dist/index.d.ts +108 -14
- package/dist/index.js +1355 -608
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import pc from 'picocolors';
|
|
2
2
|
import assert4 from 'assert';
|
|
3
3
|
import * as fs from 'fs';
|
|
4
|
-
import fs__default from 'fs';
|
|
4
|
+
import fs__default, { existsSync } from 'fs';
|
|
5
5
|
import * as Path5 from 'path';
|
|
6
6
|
import Path5__default from 'path';
|
|
7
7
|
import { CanonicalManager } from '@atomic-ehr/fhir-canonical-manager';
|
|
8
|
-
import {
|
|
8
|
+
import { excludeCanonical } from '@atomic-ehr/fhir-canonical-manager/patch';
|
|
9
9
|
import * as fsPromises from 'fs/promises';
|
|
10
10
|
import { createHash } from 'crypto';
|
|
11
|
+
import { fileURLToPath } from 'url';
|
|
11
12
|
import * as YAML from 'yaml';
|
|
12
13
|
import YAML__default from 'yaml';
|
|
13
14
|
import * as fhirschema from '@atomic-ehr/fhirschema';
|
|
@@ -108,6 +109,42 @@ function mkLogger(opts = {}) {
|
|
|
108
109
|
|
|
109
110
|
// src/utils/log.ts
|
|
110
111
|
var mkCodegenLogger = (opts = {}) => mkLogger(opts);
|
|
112
|
+
var codeableReferenceInR4 = "Use CodeableReference which is not provided by FHIR R4.";
|
|
113
|
+
var availabilityInR4 = "Use Availability which is not provided by FHIR R4.";
|
|
114
|
+
var R4_EXTENSIONS = [
|
|
115
|
+
["biologicallyderivedproduct-manipulation", codeableReferenceInR4],
|
|
116
|
+
["biologicallyderivedproduct-processing", codeableReferenceInR4],
|
|
117
|
+
["extended-contact-availability", availabilityInR4],
|
|
118
|
+
["immunization-procedure", codeableReferenceInR4],
|
|
119
|
+
["specimen-additive", codeableReferenceInR4],
|
|
120
|
+
["workflow-barrier", codeableReferenceInR4],
|
|
121
|
+
["workflow-protectiveFactor", codeableReferenceInR4],
|
|
122
|
+
["workflow-reason", codeableReferenceInR4]
|
|
123
|
+
];
|
|
124
|
+
var R4_EXTENSIONS_BROKEN_VERSIONS = ["0.1.0", "1.0.0", "5.1.0", "5.2.0"];
|
|
125
|
+
var builtinPatches = {
|
|
126
|
+
indexEntry: [
|
|
127
|
+
...R4_EXTENSIONS_BROKEN_VERSIONS.flatMap(
|
|
128
|
+
(version) => R4_EXTENSIONS.map(
|
|
129
|
+
([name, reason]) => excludeCanonical({
|
|
130
|
+
package: { name: "hl7.fhir.uv.extensions.r4", version },
|
|
131
|
+
url: `http://hl7.org/fhir/StructureDefinition/${name}`,
|
|
132
|
+
reason
|
|
133
|
+
})
|
|
134
|
+
)
|
|
135
|
+
),
|
|
136
|
+
excludeCanonical({
|
|
137
|
+
package: { name: "hl7.fhir.r5.core", version: "5.0.0" },
|
|
138
|
+
url: "http://hl7.org/fhir/StructureDefinition/shareablecodesystem",
|
|
139
|
+
reason: "FIXME: CodeSystem.concept.concept defined by ElementReference. FHIR Schema generator output broken value in it, so we just skip it for now."
|
|
140
|
+
}),
|
|
141
|
+
excludeCanonical({
|
|
142
|
+
package: { name: "hl7.fhir.r5.core", version: "5.0.0" },
|
|
143
|
+
url: "http://hl7.org/fhir/StructureDefinition/publishablecodesystem",
|
|
144
|
+
reason: "Uses R5-only base types not available in R4 generation."
|
|
145
|
+
})
|
|
146
|
+
]
|
|
147
|
+
};
|
|
111
148
|
|
|
112
149
|
// src/api/writer-generator/utils.ts
|
|
113
150
|
var WORD_SPLIT_RE = /(?<=[a-z])(?=[A-Z])|[-_.\s]/;
|
|
@@ -220,7 +257,7 @@ var FileSystemWriter = class {
|
|
|
220
257
|
absPath: Path5.resolve(destination),
|
|
221
258
|
tokens: [content]
|
|
222
259
|
};
|
|
223
|
-
fs.cpSync(source, destination);
|
|
260
|
+
if (!this.opts.inMemoryOnly) fs.cpSync(source, destination);
|
|
224
261
|
}
|
|
225
262
|
cp(source, destination) {
|
|
226
263
|
if (!this.opts.resolveAssets) throw new Error("resolveAssets is not defined");
|
|
@@ -232,7 +269,7 @@ var FileSystemWriter = class {
|
|
|
232
269
|
absPath: Path5.resolve(destination),
|
|
233
270
|
tokens: [content]
|
|
234
271
|
};
|
|
235
|
-
fs.cpSync(source, destination);
|
|
272
|
+
if (!this.opts.inMemoryOnly) fs.cpSync(source, destination);
|
|
236
273
|
}
|
|
237
274
|
writtenFiles() {
|
|
238
275
|
return Object.values(this.writtenFilesBuffer).map(({ relPath, absPath, tokens }) => {
|
|
@@ -240,6 +277,7 @@ var FileSystemWriter = class {
|
|
|
240
277
|
}).sort((a, b) => a.relPath.localeCompare(b.relPath));
|
|
241
278
|
}
|
|
242
279
|
async flushAsync() {
|
|
280
|
+
if (this.opts.inMemoryOnly) return;
|
|
243
281
|
const files = this.writtenFiles();
|
|
244
282
|
const dirs = /* @__PURE__ */ new Set();
|
|
245
283
|
for (const file of files) {
|
|
@@ -457,6 +495,22 @@ var enrichValueSet = (vs, packageMeta2) => {
|
|
|
457
495
|
url: vs.url
|
|
458
496
|
};
|
|
459
497
|
};
|
|
498
|
+
var ASSET_ROOT_SEGMENTS = ["assets", "api", "writer-generator"];
|
|
499
|
+
var MAX_LOOKUP_DEPTH = 12;
|
|
500
|
+
var resolveGeneratorAsset = (moduleUrl, language, fn) => {
|
|
501
|
+
const searched = [];
|
|
502
|
+
let directory = Path5.dirname(fileURLToPath(moduleUrl));
|
|
503
|
+
for (let depth = 0; depth < MAX_LOOKUP_DEPTH; depth++) {
|
|
504
|
+
const candidate = Path5.resolve(directory, ...ASSET_ROOT_SEGMENTS, language, fn);
|
|
505
|
+
searched.push(candidate);
|
|
506
|
+
if (existsSync(candidate)) return candidate;
|
|
507
|
+
const parent = Path5.dirname(directory);
|
|
508
|
+
if (parent === directory) break;
|
|
509
|
+
directory = parent;
|
|
510
|
+
}
|
|
511
|
+
throw new Error(`Cannot locate generator asset ${language}/${fn}. Looked in:
|
|
512
|
+
${searched.join("\n ")}`);
|
|
513
|
+
};
|
|
460
514
|
|
|
461
515
|
// src/api/writer-generator/csharp/formatHelper.ts
|
|
462
516
|
var ops = {
|
|
@@ -505,14 +559,7 @@ function formatName(input) {
|
|
|
505
559
|
}
|
|
506
560
|
|
|
507
561
|
// src/api/writer-generator/csharp/csharp.ts
|
|
508
|
-
var resolveCSharpAssets = (fn) =>
|
|
509
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
510
|
-
const __dirname = Path5__default.dirname(__filename);
|
|
511
|
-
if (__filename.endsWith("dist/index.js")) {
|
|
512
|
-
return Path5__default.resolve(__dirname, "..", "assets", "api", "writer-generator", "csharp", fn);
|
|
513
|
-
}
|
|
514
|
-
return Path5__default.resolve(__dirname, "../../../..", "assets", "api", "writer-generator", "csharp", fn);
|
|
515
|
-
};
|
|
562
|
+
var resolveCSharpAssets = (fn) => resolveGeneratorAsset(import.meta.url, "csharp", fn);
|
|
516
563
|
var PRIMITIVE_TYPE_MAP = {
|
|
517
564
|
boolean: "bool",
|
|
518
565
|
instant: "string",
|
|
@@ -851,6 +898,7 @@ var PYTHON_KEYWORDS = /* @__PURE__ */ new Set([
|
|
|
851
898
|
var fixReservedWords = (name) => {
|
|
852
899
|
return PYTHON_KEYWORDS.has(name) ? `${name}_` : name;
|
|
853
900
|
};
|
|
901
|
+
var LEADING_DIGIT_RE3 = /^\d/;
|
|
854
902
|
var canonicalToName2 = (canonical, dropFragment = true) => {
|
|
855
903
|
if (!canonical) return void 0;
|
|
856
904
|
let localName = canonical.split("/").pop();
|
|
@@ -859,7 +907,7 @@ var canonicalToName2 = (canonical, dropFragment = true) => {
|
|
|
859
907
|
localName = localName.split("#")[0];
|
|
860
908
|
}
|
|
861
909
|
if (!localName) return void 0;
|
|
862
|
-
if (
|
|
910
|
+
if (LEADING_DIGIT_RE3.test(localName)) {
|
|
863
911
|
localName = `number_${localName}`;
|
|
864
912
|
}
|
|
865
913
|
return snakeCase(localName);
|
|
@@ -898,13 +946,8 @@ var pyTypeFromIdentifier = (id) => {
|
|
|
898
946
|
};
|
|
899
947
|
var pyReferenceTypeParam = (field, tsIndex) => {
|
|
900
948
|
if (!field.reference || field.reference.resource.length === 0) return void 0;
|
|
901
|
-
const isFamilyType = (ref) => {
|
|
902
|
-
const schema = tsIndex.resolveType(ref);
|
|
903
|
-
if (!schema || !("typeFamily" in schema)) return false;
|
|
904
|
-
return (schema.typeFamily?.resources?.length ?? 0) > 0;
|
|
905
|
-
};
|
|
906
949
|
const resolved = field.reference.resource.map((ref) => tsIndex.findLastSpecializationByIdentifier(ref));
|
|
907
|
-
if (resolved.some(isFamilyType)) return void 0;
|
|
950
|
+
if (resolved.some(tsIndex.isFamilyType)) return void 0;
|
|
908
951
|
const names = [...new Set(resolved.map((ref) => ref.name))];
|
|
909
952
|
return `Literal[${names.map((n) => JSON.stringify(n)).join(", ")}]`;
|
|
910
953
|
};
|
|
@@ -1181,7 +1224,7 @@ var mkTypeSchemaIndex = (schemas, {
|
|
|
1181
1224
|
append(schema);
|
|
1182
1225
|
}
|
|
1183
1226
|
populateTypeFamily(schemas);
|
|
1184
|
-
const
|
|
1227
|
+
const resolve5 = ((id) => {
|
|
1185
1228
|
if (isSnapshotProfileIdentifier(id)) return snapshotIndex[id.url]?.[id.package];
|
|
1186
1229
|
return index[id.url]?.[id.package];
|
|
1187
1230
|
});
|
|
@@ -1226,7 +1269,7 @@ var mkTypeSchemaIndex = (schemas, {
|
|
|
1226
1269
|
const base = cur.base;
|
|
1227
1270
|
if (base === void 0) break;
|
|
1228
1271
|
if (isNestedIdentifier(base)) break;
|
|
1229
|
-
const resolved =
|
|
1272
|
+
const resolved = resolve5(base);
|
|
1230
1273
|
if (!resolved) {
|
|
1231
1274
|
logger?.warn(
|
|
1232
1275
|
"#resolveBase",
|
|
@@ -1260,6 +1303,11 @@ var mkTypeSchemaIndex = (schemas, {
|
|
|
1260
1303
|
if (isNestedTypeSchema(resolved)) return findLastSpecializationByIdentifier(resolved.base);
|
|
1261
1304
|
return findLastSpecialization(resolved).identifier;
|
|
1262
1305
|
};
|
|
1306
|
+
const isFamilyType = (id) => {
|
|
1307
|
+
const schema = resolveType(id);
|
|
1308
|
+
if (!schema || !("typeFamily" in schema)) return false;
|
|
1309
|
+
return (schema.typeFamily?.resources?.length ?? 0) > 0;
|
|
1310
|
+
};
|
|
1263
1311
|
const narrowMergedChoiceDeclarations = (mergedFields, constraintSchemas, baseFields = {}) => {
|
|
1264
1312
|
const result = { ...mergedFields };
|
|
1265
1313
|
const declNames = /* @__PURE__ */ new Set([...Object.keys(result), ...Object.keys(baseFields)]);
|
|
@@ -1388,6 +1436,14 @@ var mkTypeSchemaIndex = (schemas, {
|
|
|
1388
1436
|
}
|
|
1389
1437
|
return void 0;
|
|
1390
1438
|
};
|
|
1439
|
+
const sliceChoiceVariants = (pkgName, baseTypeId, sliceElements, name) => {
|
|
1440
|
+
const baseSchema = resolveByUrl(pkgName, baseTypeId.url);
|
|
1441
|
+
if (!baseSchema || !("fields" in baseSchema) || !baseSchema.fields) return void 0;
|
|
1442
|
+
const field = baseSchema.fields[name];
|
|
1443
|
+
if (!field || !isChoiceDeclarationField(field)) return void 0;
|
|
1444
|
+
const narrowed = field.choices.filter((c) => sliceElements.includes(c));
|
|
1445
|
+
return narrowed.length > 0 ? narrowed : field.choices;
|
|
1446
|
+
};
|
|
1391
1447
|
const isWithMetaField = (profile) => {
|
|
1392
1448
|
const genealogy = tryHierarchy(profile);
|
|
1393
1449
|
if (!genealogy) return false;
|
|
@@ -1431,15 +1487,17 @@ var mkTypeSchemaIndex = (schemas, {
|
|
|
1431
1487
|
collectLogicalModels: () => schemas.filter(isLogicalTypeSchema),
|
|
1432
1488
|
collectProfiles: () => schemas.filter(isProfileTypeSchema),
|
|
1433
1489
|
collectSnapshotProfiles,
|
|
1434
|
-
resolve:
|
|
1490
|
+
resolve: resolve5,
|
|
1435
1491
|
resolveType,
|
|
1436
1492
|
resolveByUrl,
|
|
1437
1493
|
tryHierarchy,
|
|
1438
1494
|
hierarchy,
|
|
1439
1495
|
findLastSpecialization,
|
|
1440
1496
|
findLastSpecializationByIdentifier,
|
|
1497
|
+
isFamilyType,
|
|
1441
1498
|
flatProfile,
|
|
1442
1499
|
constrainedChoice,
|
|
1500
|
+
sliceChoiceVariants,
|
|
1443
1501
|
isWithMetaField,
|
|
1444
1502
|
entityTree,
|
|
1445
1503
|
exportTree,
|
|
@@ -1452,7 +1510,7 @@ var mkTypeSchemaIndex = (schemas, {
|
|
|
1452
1510
|
var normalizePyName = (n) => {
|
|
1453
1511
|
let out = n.replace(/\[x\]/g, "_x_").replace(/[- :./]/g, "_");
|
|
1454
1512
|
if (PYTHON_KEYWORDS.has(out)) out = `${out}_`;
|
|
1455
|
-
if (
|
|
1513
|
+
if (LEADING_DIGIT_RE3.test(out)) out = `_${out}`;
|
|
1456
1514
|
return out;
|
|
1457
1515
|
};
|
|
1458
1516
|
var pySnakeName = (name) => {
|
|
@@ -1997,7 +2055,7 @@ var collectProfileFactoryInfo = (tsIndex, flatProfile) => {
|
|
|
1997
2055
|
tryPromoteChoice(field, fields, params, promotedChoices, tsIndex);
|
|
1998
2056
|
continue;
|
|
1999
2057
|
}
|
|
2000
|
-
if (field.valueConstraint) {
|
|
2058
|
+
if (field.valueConstraint && !field.valueConstraint.validateOnly) {
|
|
2001
2059
|
const value = JSON.stringify(field.valueConstraint.value);
|
|
2002
2060
|
autoFields.push({ name, value: field.array ? `[${value}]` : value });
|
|
2003
2061
|
if (isNotChoiceDeclarationField(field) && field.type) {
|
|
@@ -2216,11 +2274,10 @@ var collectRegularFieldValidation = (field, fieldSlicing, pyName, helpers, error
|
|
|
2216
2274
|
errorLines.push(`errors.extend(validate_required(self._resource, profile_name, ${JSON.stringify(pyName)}))`);
|
|
2217
2275
|
}
|
|
2218
2276
|
if (field.valueConstraint) {
|
|
2219
|
-
|
|
2277
|
+
const fn = field.valueConstraint.validateOnly ? "validate_pattern_value" : "validate_fixed_value";
|
|
2278
|
+
helpers.add(fn);
|
|
2220
2279
|
const value = JSON.stringify(field.valueConstraint.value);
|
|
2221
|
-
errorLines.push(
|
|
2222
|
-
`errors.extend(validate_fixed_value(self._resource, profile_name, ${JSON.stringify(pyName)}, ${value}))`
|
|
2223
|
-
);
|
|
2280
|
+
errorLines.push(`errors.extend(${fn}(self._resource, profile_name, ${JSON.stringify(pyName)}, ${value}))`);
|
|
2224
2281
|
}
|
|
2225
2282
|
if (isNotChoiceDeclarationField(field)) {
|
|
2226
2283
|
if (field.enum) {
|
|
@@ -2245,6 +2302,23 @@ var collectRegularFieldValidation = (field, fieldSlicing, pyName, helpers, error
|
|
|
2245
2302
|
}
|
|
2246
2303
|
}
|
|
2247
2304
|
};
|
|
2305
|
+
var collectSliceRequirements = (slice, match, field, tsIndex, formatName2) => {
|
|
2306
|
+
const requiredFields = [];
|
|
2307
|
+
const choiceGroups = [];
|
|
2308
|
+
const matchKeys = new Set(Object.keys(match));
|
|
2309
|
+
const requiredNames = (slice.required ?? []).filter((rf) => !matchKeys.has(rf));
|
|
2310
|
+
const fieldType = field.type;
|
|
2311
|
+
for (const rf of requiredNames) {
|
|
2312
|
+
const variants = fieldType ? tsIndex.sliceChoiceVariants(fieldType.package, fieldType, slice.elements ?? [], rf) : void 0;
|
|
2313
|
+
if (variants && variants.length > 0) choiceGroups.push(variants.map((v) => pyFieldName(v, formatName2)));
|
|
2314
|
+
else requiredFields.push(pyFieldName(rf, formatName2));
|
|
2315
|
+
}
|
|
2316
|
+
if (fieldType && slice.elements) {
|
|
2317
|
+
const cc = tsIndex.constrainedChoice(fieldType.package, fieldType, slice.elements);
|
|
2318
|
+
if (cc && !requiredNames.includes(cc.choiceBase)) requiredFields.push(pyFieldName(cc.variant, formatName2));
|
|
2319
|
+
}
|
|
2320
|
+
return { requiredFields, choiceGroups };
|
|
2321
|
+
};
|
|
2248
2322
|
var collectSliceValidation = (field, fieldSlicing, name, helpers, errorLines, tsIndex, formatName2) => {
|
|
2249
2323
|
if (!fieldSlicing.slices) return;
|
|
2250
2324
|
for (const [sliceName, slice] of Object.entries(fieldSlicing.slices)) {
|
|
@@ -2258,25 +2332,21 @@ var collectSliceValidation = (field, fieldSlicing, name, helpers, errorLines, ts
|
|
|
2258
2332
|
`errors.extend(validate_slice_cardinality(self._resource, profile_name, ${JSON.stringify(name)}, ${JSON.stringify(match)}, ${JSON.stringify(sliceName)}, ${min}, ${max}))`
|
|
2259
2333
|
);
|
|
2260
2334
|
}
|
|
2261
|
-
const
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
if (cc) sliceRequiredFields.push(pyFieldName(cc.variant, formatName2));
|
|
2269
|
-
}
|
|
2270
|
-
if (sliceRequiredFields.length > 0) {
|
|
2271
|
-
helpers.add("validate_slice_fields");
|
|
2272
|
-
pushListValidation(
|
|
2273
|
-
errorLines,
|
|
2274
|
-
"errors",
|
|
2275
|
-
"validate_slice_fields",
|
|
2276
|
-
[JSON.stringify(name), JSON.stringify(match), JSON.stringify(sliceName)],
|
|
2277
|
-
sliceRequiredFields
|
|
2278
|
-
);
|
|
2335
|
+
const { requiredFields, choiceGroups } = collectSliceRequirements(slice, match, field, tsIndex, formatName2);
|
|
2336
|
+
if (requiredFields.length === 0 && choiceGroups.length === 0) continue;
|
|
2337
|
+
helpers.add("validate_slice_fields");
|
|
2338
|
+
const args = [JSON.stringify(name), JSON.stringify(match), JSON.stringify(sliceName)];
|
|
2339
|
+
if (choiceGroups.length === 0) {
|
|
2340
|
+
pushListValidation(errorLines, "errors", "validate_slice_fields", args, requiredFields);
|
|
2341
|
+
continue;
|
|
2279
2342
|
}
|
|
2343
|
+
pushListValidation(
|
|
2344
|
+
errorLines,
|
|
2345
|
+
"errors",
|
|
2346
|
+
"validate_slice_fields",
|
|
2347
|
+
[...args, JSON.stringify(requiredFields)],
|
|
2348
|
+
choiceGroups
|
|
2349
|
+
);
|
|
2280
2350
|
}
|
|
2281
2351
|
};
|
|
2282
2352
|
|
|
@@ -2511,7 +2581,7 @@ var generateProfileModule = (w, tsIndex, flatProfile) => {
|
|
|
2511
2581
|
const sliceDefs = collectSliceDefs(tsIndex, flatProfile);
|
|
2512
2582
|
const typedResources = [
|
|
2513
2583
|
...new Set(
|
|
2514
|
-
sliceDefs.filter((s) => s.isTypeDiscriminated
|
|
2584
|
+
sliceDefs.filter((s) => s.isTypeDiscriminated).map((s) => s.typeDiscriminatorResource).filter((resource) => resource !== void 0)
|
|
2515
2585
|
)
|
|
2516
2586
|
];
|
|
2517
2587
|
const annotatedBaseTypeName = typedResources.length > 0 ? `${baseTypeName}[${typedResources.join(" | ")}, Resource]` : baseTypeName;
|
|
@@ -2610,14 +2680,7 @@ var generateNewProfiles = (w, tsIndex, profiles) => {
|
|
|
2610
2680
|
};
|
|
2611
2681
|
|
|
2612
2682
|
// src/api/writer-generator/python/writer.ts
|
|
2613
|
-
var resolvePyAssets = (fn) =>
|
|
2614
|
-
const __dirname = Path5.dirname(fileURLToPath(import.meta.url));
|
|
2615
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
2616
|
-
if (__filename.endsWith("dist/index.js")) {
|
|
2617
|
-
return Path5.resolve(__dirname, "..", "assets", "api", "writer-generator", "python", fn);
|
|
2618
|
-
}
|
|
2619
|
-
return Path5.resolve(__dirname, "../../../..", "assets", "api", "writer-generator", "python", fn);
|
|
2620
|
-
};
|
|
2683
|
+
var resolvePyAssets = (fn) => resolveGeneratorAsset(import.meta.url, "python", fn);
|
|
2621
2684
|
var AVAILABLE_STRING_FORMATS = {
|
|
2622
2685
|
snake_case: snakeCase,
|
|
2623
2686
|
PascalCase: pascalCase,
|
|
@@ -3192,372 +3255,198 @@ var collisionSourcesKey = (sources) => JSON.stringify(
|
|
|
3192
3255
|
);
|
|
3193
3256
|
var compareCollisionVariants = (left, right) => right.sources.length - left.sources.length || compareStrings(collisionSourcesKey(left.sources), collisionSourcesKey(right.sources)) || compareStrings(left.schemaHash, right.schemaHash);
|
|
3194
3257
|
|
|
3195
|
-
// src/
|
|
3196
|
-
var
|
|
3197
|
-
|
|
3198
|
-
var skipList = {
|
|
3199
|
-
"hl7.fhir.uv.extensions.r4": {
|
|
3200
|
-
"http://hl7.org/fhir/StructureDefinition/biologicallyderivedproduct-manipulation": codeableReferenceInR4,
|
|
3201
|
-
"http://hl7.org/fhir/StructureDefinition/biologicallyderivedproduct-processing": codeableReferenceInR4,
|
|
3202
|
-
"http://hl7.org/fhir/StructureDefinition/extended-contact-availability": availabilityInR4,
|
|
3203
|
-
"http://hl7.org/fhir/StructureDefinition/immunization-procedure": codeableReferenceInR4,
|
|
3204
|
-
"http://hl7.org/fhir/StructureDefinition/specimen-additive": codeableReferenceInR4,
|
|
3205
|
-
"http://hl7.org/fhir/StructureDefinition/workflow-barrier": codeableReferenceInR4,
|
|
3206
|
-
"http://hl7.org/fhir/StructureDefinition/workflow-protectiveFactor": codeableReferenceInR4,
|
|
3207
|
-
"http://hl7.org/fhir/StructureDefinition/workflow-reason": codeableReferenceInR4
|
|
3208
|
-
},
|
|
3209
|
-
"hl7.fhir.r5.core#5.0.0": {
|
|
3210
|
-
"http://hl7.org/fhir/StructureDefinition/shareablecodesystem": "FIXME: CodeSystem.concept.concept defined by ElementReference. FHIR Schema generator output broken value in it, so we just skip it for now.",
|
|
3211
|
-
"http://hl7.org/fhir/StructureDefinition/publishablecodesystem": "Uses R5-only base types not available in R4 generation."
|
|
3212
|
-
}
|
|
3258
|
+
// src/fhir-types/hl7-fhir-r4-core/CodeSystem.ts
|
|
3259
|
+
var isCodeSystem = (resource) => {
|
|
3260
|
+
return resource !== null && typeof resource === "object" && resource.resourceType === "CodeSystem";
|
|
3213
3261
|
};
|
|
3214
|
-
function shouldSkipCanonical(packageMeta2, canonicalUrl) {
|
|
3215
|
-
const pkgId = `${packageMeta2.name}#${packageMeta2.version}`;
|
|
3216
|
-
const reasonByPkgId = skipList[pkgId]?.[canonicalUrl];
|
|
3217
|
-
if (reasonByPkgId) {
|
|
3218
|
-
return { shouldSkip: true, reason: reasonByPkgId };
|
|
3219
|
-
}
|
|
3220
|
-
const reasonByName = skipList[packageMeta2.name]?.[canonicalUrl];
|
|
3221
|
-
if (reasonByName) {
|
|
3222
|
-
return { shouldSkip: true, reason: reasonByName };
|
|
3223
|
-
}
|
|
3224
|
-
return { shouldSkip: false };
|
|
3225
|
-
}
|
|
3226
3262
|
|
|
3227
|
-
// src/
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
return baseUrl ? baseUrl : url;
|
|
3231
|
-
}
|
|
3232
|
-
function getVersionFromUrl(url) {
|
|
3233
|
-
const version = url.split("|")[1];
|
|
3234
|
-
return version;
|
|
3235
|
-
}
|
|
3236
|
-
var identifierBase = (fhirSchema) => ({
|
|
3237
|
-
package: fhirSchema.package_meta.name,
|
|
3238
|
-
version: fhirSchema.package_meta.version,
|
|
3239
|
-
name: fhirSchema.name,
|
|
3240
|
-
url: fhirSchema.url
|
|
3241
|
-
});
|
|
3242
|
-
function mkIdentifier(fhirSchema) {
|
|
3243
|
-
const fields = identifierBase(fhirSchema);
|
|
3244
|
-
if (fhirSchema.derivation === "constraint") return { kind: "profile", ...fields };
|
|
3245
|
-
if (fhirSchema.kind === "primitive-type") return { kind: "primitive-type", ...fields };
|
|
3246
|
-
if (fhirSchema.kind === "complex-type") return { kind: "complex-type", ...fields };
|
|
3247
|
-
if (fhirSchema.kind === "resource") return { kind: "resource", ...fields };
|
|
3248
|
-
if (fhirSchema.kind === "logical") return { kind: "logical", ...fields };
|
|
3249
|
-
return { kind: "resource", ...fields };
|
|
3250
|
-
}
|
|
3251
|
-
var VALUE_SET_NAME_SPLIT_RE = /[-_]/;
|
|
3252
|
-
var OPAQUE_VALUE_SET_ID_RE = /^[a-zA-Z0-9_-]{20,}$/;
|
|
3253
|
-
var getValueSetName = (url) => {
|
|
3254
|
-
const urlParts = url.split("/");
|
|
3255
|
-
const lastSegment = urlParts[urlParts.length - 1];
|
|
3256
|
-
if (lastSegment && lastSegment.length > 0) {
|
|
3257
|
-
return lastSegment.split(VALUE_SET_NAME_SPLIT_RE).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
|
|
3258
|
-
}
|
|
3259
|
-
return url;
|
|
3263
|
+
// src/fhir-types/hl7-fhir-r4-core/ValueSet.ts
|
|
3264
|
+
var isValueSet = (resource) => {
|
|
3265
|
+
return resource !== null && typeof resource === "object" && resource.resourceType === "ValueSet";
|
|
3260
3266
|
};
|
|
3261
|
-
function mkValueSetIdentifierByUrl(register, pkg, fullValueSetUrl) {
|
|
3262
|
-
const valueSetUrl = dropVersionFromUrl(fullValueSetUrl);
|
|
3263
|
-
const valueSetNameFallback = getValueSetName(valueSetUrl);
|
|
3264
|
-
const valuesSetFallback = {
|
|
3265
|
-
package_meta: {
|
|
3266
|
-
name: "missing_valuesets",
|
|
3267
|
-
version: getVersionFromUrl(valueSetUrl) || "0.0.0"
|
|
3268
|
-
},
|
|
3269
|
-
id: fullValueSetUrl};
|
|
3270
|
-
const valueSet = register.resolveVs(pkg, valueSetUrl) || valuesSetFallback;
|
|
3271
|
-
const valueSetName = valueSet?.id && !OPAQUE_VALUE_SET_ID_RE.test(valueSet.id) ? valueSet.id : valueSetNameFallback;
|
|
3272
|
-
return {
|
|
3273
|
-
kind: "value-set",
|
|
3274
|
-
package: valueSet.package_meta.name,
|
|
3275
|
-
version: valueSet.package_meta.version,
|
|
3276
|
-
name: valueSetName,
|
|
3277
|
-
url: valueSetUrl
|
|
3278
|
-
};
|
|
3279
|
-
}
|
|
3280
|
-
function mkBindingIdentifier(fhirSchema, path, element) {
|
|
3281
|
-
const bindingName = element.binding?.bindingName;
|
|
3282
|
-
const pathStr = path.join(".");
|
|
3283
|
-
const [pkg, name, url] = bindingName ? [{ name: "shared", version: "1.0.0" }, bindingName, `urn:fhir:binding:${bindingName}`] : [fhirSchema.package_meta, `${fhirSchema.name}.${pathStr}_binding`, `${fhirSchema.url}#${pathStr}_binding`];
|
|
3284
|
-
return {
|
|
3285
|
-
kind: "binding",
|
|
3286
|
-
package: pkg.name,
|
|
3287
|
-
version: pkg.version,
|
|
3288
|
-
name,
|
|
3289
|
-
url
|
|
3290
|
-
};
|
|
3291
|
-
}
|
|
3292
3267
|
|
|
3293
|
-
// src/typeschema/
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
};
|
|
3268
|
+
// src/typeschema/register.ts
|
|
3269
|
+
var BARE_RESOURCE_NAME_RE = /^[a-zA-Z0-9]+$/;
|
|
3270
|
+
var FHIR_BASE_CANONICAL = "http://hl7.org/fhir/StructureDefinition/Base";
|
|
3271
|
+
var isVirtualFhirBaseCanonical = (ref) => {
|
|
3272
|
+
const [name, version] = ref.split("|");
|
|
3273
|
+
const canonical = BARE_RESOURCE_NAME_RE.test(name) ? `http://hl7.org/fhir/StructureDefinition/${name}` : name;
|
|
3274
|
+
if (canonical !== FHIR_BASE_CANONICAL) return false;
|
|
3275
|
+
return version === void 0 || version.startsWith("4.");
|
|
3276
|
+
};
|
|
3277
|
+
var readPackageDependencies = async (manager, packageMeta2) => {
|
|
3278
|
+
const packageJSON = await manager.packageJson(packageMeta2.name);
|
|
3279
|
+
if (!packageJSON) return [];
|
|
3280
|
+
const dependencies = packageJSON.dependencies;
|
|
3281
|
+
if (dependencies !== void 0) {
|
|
3282
|
+
return Object.entries(dependencies).map(([name, version]) => {
|
|
3283
|
+
return { name, version };
|
|
3309
3284
|
});
|
|
3310
3285
|
}
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
const codeSystem = register.resolveAny(include.system);
|
|
3325
|
-
if (codeSystem?.concept) {
|
|
3326
|
-
const extractConcepts = (conceptList, system) => {
|
|
3327
|
-
for (const concept of conceptList) {
|
|
3328
|
-
concepts.push({
|
|
3329
|
-
system,
|
|
3330
|
-
code: concept.code,
|
|
3331
|
-
display: concept.display
|
|
3332
|
-
});
|
|
3333
|
-
if (concept.concept) {
|
|
3334
|
-
extractConcepts(concept.concept, system);
|
|
3335
|
-
}
|
|
3336
|
-
}
|
|
3337
|
-
};
|
|
3338
|
-
extractConcepts(codeSystem.concept, include.system);
|
|
3339
|
-
}
|
|
3340
|
-
} catch {
|
|
3341
|
-
}
|
|
3286
|
+
return [];
|
|
3287
|
+
};
|
|
3288
|
+
var flattenTerminologyConcepts = (concepts) => {
|
|
3289
|
+
const flattened = [];
|
|
3290
|
+
const stack = [...concepts ?? []].reverse();
|
|
3291
|
+
while (stack.length > 0) {
|
|
3292
|
+
const concept = stack.pop();
|
|
3293
|
+
if (!concept) continue;
|
|
3294
|
+
flattened.push(concept);
|
|
3295
|
+
if (concept.concept) {
|
|
3296
|
+
for (let index = concept.concept.length - 1; index >= 0; index -= 1) {
|
|
3297
|
+
const nested = concept.concept[index];
|
|
3298
|
+
if (nested) stack.push(nested);
|
|
3342
3299
|
}
|
|
3343
3300
|
}
|
|
3344
3301
|
}
|
|
3345
|
-
return
|
|
3346
|
-
}
|
|
3347
|
-
var
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
"
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
}
|
|
3371
|
-
const shouldGenerateEnum = strength === "required" || strength === "extensible" || strength === "preferred";
|
|
3372
|
-
if (!shouldGenerateEnum) return void 0;
|
|
3373
|
-
const concepts = extractValueSetConceptsByUrl(register, fhirSchema.package_meta, valueSetUrl);
|
|
3374
|
-
if (!concepts || concepts.length === 0) return void 0;
|
|
3375
|
-
const codes = concepts.map((c) => c.code).filter((code) => code && typeof code === "string" && code.trim().length > 0);
|
|
3376
|
-
const onlyCode = codes.length === 1 ? codes[0] : void 0;
|
|
3377
|
-
if (onlyCode && PLACEHOLDER_ONLY_ENUM_CODES.has(onlyCode)) {
|
|
3378
|
-
logger?.dryWarn(
|
|
3379
|
-
"#placeholderValueSet",
|
|
3380
|
-
`Value set ${valueSetUrl} only expands to placeholder code '${onlyCode}'; skipping enum generation.`
|
|
3381
|
-
);
|
|
3382
|
-
return void 0;
|
|
3302
|
+
return flattened;
|
|
3303
|
+
};
|
|
3304
|
+
var mkTerminologyEntries = (packageTerminology, verification, logger) => {
|
|
3305
|
+
const { packageMeta: pkg, resources } = packageTerminology;
|
|
3306
|
+
const byCanonical = /* @__PURE__ */ new Map();
|
|
3307
|
+
for (const resource of resources) {
|
|
3308
|
+
const key = `${resource.resourceType}\0${resource.url}`;
|
|
3309
|
+
const matching = byCanonical.get(key) ?? [];
|
|
3310
|
+
matching.push(resource);
|
|
3311
|
+
byCanonical.set(key, matching);
|
|
3312
|
+
}
|
|
3313
|
+
const identity = (resource) => `${resource.id ?? ""}\0${resource.name ?? ""}`;
|
|
3314
|
+
const deduped = [];
|
|
3315
|
+
for (const matching of byCanonical.values()) {
|
|
3316
|
+
const candidates = matching.slice().sort((left, right) => identity(left).localeCompare(identity(right)));
|
|
3317
|
+
const winner = candidates[0];
|
|
3318
|
+
if (!winner) continue;
|
|
3319
|
+
if (candidates.length > 1) {
|
|
3320
|
+
const identities = candidates.map((candidate) => candidate.id ?? candidate.name ?? candidate.url);
|
|
3321
|
+
logger?.dryWarn(
|
|
3322
|
+
"#duplicateCanonical",
|
|
3323
|
+
`Package ${packageMetaToNpm(pkg)} contains duplicate ${winner.resourceType} canonical URL ${JSON.stringify(winner.url)} for resources ${identities.join(", ")}; keeping ${winner.id ?? winner.name ?? winner.url}`
|
|
3324
|
+
);
|
|
3325
|
+
}
|
|
3326
|
+
deduped.push(winner);
|
|
3383
3327
|
}
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3328
|
+
return deduped.map((resource) => {
|
|
3329
|
+
const base = {
|
|
3330
|
+
canonicalUrl: resource.url,
|
|
3331
|
+
packageId: pkg.name,
|
|
3332
|
+
packageVersion: pkg.version,
|
|
3333
|
+
verification
|
|
3334
|
+
};
|
|
3335
|
+
if (resource.resourceType === "ValueSet")
|
|
3336
|
+
return { resource, entry: { ...base, resourceType: resource.resourceType } };
|
|
3337
|
+
if (resource.resourceType === "NamingSystem")
|
|
3338
|
+
return { resource, entry: { ...base, resourceType: resource.resourceType } };
|
|
3339
|
+
const codeSystemEntry = {
|
|
3340
|
+
...base,
|
|
3341
|
+
resourceType: "CodeSystem",
|
|
3342
|
+
...resource.content !== void 0 ? { contentMode: resource.content } : {}
|
|
3343
|
+
};
|
|
3344
|
+
const embedsCodes = resource.content === "complete" && verification !== "unverifiable";
|
|
3345
|
+
if (!embedsCodes) return { resource, entry: codeSystemEntry };
|
|
3346
|
+
const concepts = flattenTerminologyConcepts(resource.concept);
|
|
3347
|
+
const seenCodes = /* @__PURE__ */ new Set();
|
|
3348
|
+
const displays = {};
|
|
3349
|
+
for (const concept of concepts) {
|
|
3350
|
+
if (seenCodes.has(concept.code))
|
|
3351
|
+
throw new Error(`CodeSystem ${resource.url} repeats code ${JSON.stringify(concept.code)}`);
|
|
3352
|
+
seenCodes.add(concept.code);
|
|
3353
|
+
if (concept.display !== void 0)
|
|
3354
|
+
Object.defineProperty(displays, concept.code, {
|
|
3355
|
+
value: concept.display,
|
|
3356
|
+
enumerable: true,
|
|
3357
|
+
writable: true,
|
|
3358
|
+
configurable: true
|
|
3359
|
+
});
|
|
3360
|
+
}
|
|
3361
|
+
const entry = {
|
|
3362
|
+
...codeSystemEntry,
|
|
3363
|
+
contentMode: "complete",
|
|
3364
|
+
codes: concepts.map(({ code }) => code),
|
|
3365
|
+
displays
|
|
3366
|
+
};
|
|
3367
|
+
return { resource, entry };
|
|
3368
|
+
});
|
|
3369
|
+
};
|
|
3370
|
+
var projectTerminologyConcepts = (concepts) => {
|
|
3371
|
+
if (!Array.isArray(concepts)) return void 0;
|
|
3372
|
+
const projected = [];
|
|
3373
|
+
const stack = [{ source: concepts, target: projected, index: 0 }];
|
|
3374
|
+
while (stack.length > 0) {
|
|
3375
|
+
const frame = stack[stack.length - 1];
|
|
3376
|
+
if (!frame) break;
|
|
3377
|
+
if (frame.index >= frame.source.length) {
|
|
3378
|
+
if (frame.parent && frame.target.length === 0) delete frame.parent.concept;
|
|
3379
|
+
stack.pop();
|
|
3380
|
+
continue;
|
|
3381
|
+
}
|
|
3382
|
+
const concept = frame.source[frame.index];
|
|
3383
|
+
frame.index += 1;
|
|
3384
|
+
if (concept === null || typeof concept !== "object") continue;
|
|
3385
|
+
const candidate = concept;
|
|
3386
|
+
if (typeof candidate.code !== "string") continue;
|
|
3387
|
+
const copy = {
|
|
3388
|
+
code: candidate.code,
|
|
3389
|
+
...typeof candidate.display === "string" ? { display: candidate.display } : {}
|
|
3390
|
+
};
|
|
3391
|
+
frame.target.push(copy);
|
|
3392
|
+
if (Array.isArray(candidate.concept)) {
|
|
3393
|
+
const nested = [];
|
|
3394
|
+
copy.concept = nested;
|
|
3395
|
+
stack.push({ source: candidate.concept, target: nested, index: 0, parent: copy });
|
|
3396
|
+
}
|
|
3397
|
+
}
|
|
3398
|
+
return projected;
|
|
3399
|
+
};
|
|
3400
|
+
var namingSystemIdentity = (candidate, logger) => {
|
|
3401
|
+
const uniqueIds = Array.isArray(candidate.uniqueId) ? candidate.uniqueId.filter(
|
|
3402
|
+
(identifier2) => identifier2 !== null && typeof identifier2 === "object" && typeof identifier2.type === "string" && typeof identifier2.value === "string" && identifier2.value.length > 0
|
|
3403
|
+
) : [];
|
|
3404
|
+
const preferred = uniqueIds.filter(({ preferred: preferred2 }) => preferred2 === true);
|
|
3405
|
+
const candidates = preferred.length > 0 ? preferred : uniqueIds;
|
|
3406
|
+
const identifier = candidates.find(({ type }) => type === "uri") ?? candidates[0];
|
|
3407
|
+
if (identifier) {
|
|
3408
|
+
if (identifier.type === "oid" && !identifier.value.startsWith("urn:oid:")) return `urn:oid:${identifier.value}`;
|
|
3409
|
+
if (identifier.type === "uuid" && !identifier.value.startsWith("urn:uuid:"))
|
|
3410
|
+
return `urn:uuid:${identifier.value}`;
|
|
3411
|
+
return identifier.value;
|
|
3412
|
+
}
|
|
3413
|
+
if (typeof candidate.id === "string" && candidate.id.length > 0) return `NamingSystem/${candidate.id}`;
|
|
3414
|
+
if (typeof candidate.name === "string" && candidate.name.length > 0) return `NamingSystem/${candidate.name}`;
|
|
3415
|
+
logger?.dryWarn("NamingSystem has no uniqueId, id, or name and cannot be emitted.");
|
|
3416
|
+
return void 0;
|
|
3417
|
+
};
|
|
3418
|
+
var asTerminologyResource = (resource, logger) => {
|
|
3419
|
+
if (isCodeSystem(resource) || isValueSet(resource)) {
|
|
3420
|
+
if (typeof resource.url !== "string" || resource.url.length === 0) return void 0;
|
|
3421
|
+
const concepts = isCodeSystem(resource) ? projectTerminologyConcepts(resource.concept) : void 0;
|
|
3422
|
+
return {
|
|
3423
|
+
resourceType: resource.resourceType,
|
|
3424
|
+
...typeof resource.id === "string" ? { id: resource.id } : {},
|
|
3425
|
+
...typeof resource.name === "string" ? { name: resource.name } : {},
|
|
3426
|
+
url: resource.url,
|
|
3427
|
+
...isCodeSystem(resource) && typeof resource.content === "string" ? { content: resource.content } : {},
|
|
3428
|
+
...concepts && concepts.length > 0 ? { concept: concepts } : {}
|
|
3429
|
+
};
|
|
3390
3430
|
}
|
|
3391
|
-
if (
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
if (
|
|
3396
|
-
const identifier = mkBindingIdentifier(fhirSchema, path, element);
|
|
3397
|
-
const valueSetIdentifier = mkValueSetIdentifierByUrl(
|
|
3398
|
-
register,
|
|
3399
|
-
fhirSchema.package_meta,
|
|
3400
|
-
element.binding.valueSet
|
|
3401
|
-
);
|
|
3402
|
-
const enumResult = buildEnum(register, fhirSchema, element, logger);
|
|
3431
|
+
if (resource === null || typeof resource !== "object") return void 0;
|
|
3432
|
+
const candidate = resource;
|
|
3433
|
+
if (candidate.resourceType !== "NamingSystem") return void 0;
|
|
3434
|
+
const url = typeof candidate.url === "string" && candidate.url.length > 0 ? candidate.url : namingSystemIdentity(candidate, logger);
|
|
3435
|
+
if (url === void 0 || url.length === 0) return void 0;
|
|
3403
3436
|
return {
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
dependencies: [valueSetIdentifier]
|
|
3437
|
+
resourceType: "NamingSystem",
|
|
3438
|
+
...typeof candidate.id === "string" ? { id: candidate.id } : {},
|
|
3439
|
+
...typeof candidate.name === "string" ? { name: candidate.name } : {},
|
|
3440
|
+
url
|
|
3409
3441
|
};
|
|
3410
|
-
}
|
|
3411
|
-
function collectBindingSchemas(register, fhirSchema, logger) {
|
|
3412
|
-
const processedPaths = /* @__PURE__ */ new Set();
|
|
3413
|
-
if (!fhirSchema.elements) return [];
|
|
3414
|
-
const bindings = [];
|
|
3415
|
-
function collectBindings(elements, parentPath) {
|
|
3416
|
-
for (const [key, element] of Object.entries(elements)) {
|
|
3417
|
-
const path = [...parentPath, key];
|
|
3418
|
-
const pathKey = path.join(".");
|
|
3419
|
-
const elemSnapshot = register.resolveElementSnapshot(fhirSchema, path);
|
|
3420
|
-
if (processedPaths.has(pathKey)) continue;
|
|
3421
|
-
processedPaths.add(pathKey);
|
|
3422
|
-
if (elemSnapshot.binding) {
|
|
3423
|
-
const binding = generateBindingSchema(register, fhirSchema, path, elemSnapshot, logger);
|
|
3424
|
-
if (binding) {
|
|
3425
|
-
bindings.push(binding);
|
|
3426
|
-
}
|
|
3427
|
-
}
|
|
3428
|
-
if (element.elements) {
|
|
3429
|
-
collectBindings(element.elements, path);
|
|
3430
|
-
}
|
|
3431
|
-
}
|
|
3432
|
-
}
|
|
3433
|
-
collectBindings(fhirSchema.elements, []);
|
|
3434
|
-
bindings.sort((a, b) => a.identifier.name.localeCompare(b.identifier.name));
|
|
3435
|
-
const uniqueBindings = [];
|
|
3436
|
-
const seenUrls = /* @__PURE__ */ new Set();
|
|
3437
|
-
for (const binding of bindings) {
|
|
3438
|
-
if (!seenUrls.has(binding.identifier.url)) {
|
|
3439
|
-
seenUrls.add(binding.identifier.url);
|
|
3440
|
-
uniqueBindings.push(binding);
|
|
3441
|
-
}
|
|
3442
|
-
}
|
|
3443
|
-
return uniqueBindings;
|
|
3444
|
-
}
|
|
3445
|
-
|
|
3446
|
-
// src/typeschema/core/name-candidates.ts
|
|
3447
|
-
var normalizeName = (s) => {
|
|
3448
|
-
const cleaned = s.replace(/\[x\]/g, "").replace(/[- :.]/g, "_");
|
|
3449
|
-
if (!cleaned) return "";
|
|
3450
|
-
return uppercaseFirstLetter(cleaned);
|
|
3451
|
-
};
|
|
3452
|
-
var normalizeCamelName = (s) => {
|
|
3453
|
-
const cleaned = s.replace(/\[x\]/g, "").replace(/:/g, "_");
|
|
3454
|
-
if (!cleaned) return "";
|
|
3455
|
-
return uppercaseFirstLetter(camelCase(cleaned));
|
|
3456
|
-
};
|
|
3457
|
-
var extensionCandidates = (name, path) => {
|
|
3458
|
-
const base = normalizeCamelName(name) || "Extension";
|
|
3459
|
-
const pathParts = path.split(".").filter((p) => p && p !== "extension").join("_");
|
|
3460
|
-
const pathPart = pathParts ? normalizeCamelName(pathParts) : "";
|
|
3461
|
-
const qualified = `${pathPart}${base}`;
|
|
3462
|
-
return [base, qualified, `${qualified}Extension`];
|
|
3463
|
-
};
|
|
3464
|
-
var sliceCandidates = (fieldName, sliceName) => {
|
|
3465
|
-
const base = normalizeName(sliceName) || "Slice";
|
|
3466
|
-
const fieldPart = normalizeCamelName(fieldName) || "Field";
|
|
3467
|
-
const qualified = `${fieldPart}${base}`;
|
|
3468
|
-
return [base, qualified, `${qualified}Slice`];
|
|
3469
|
-
};
|
|
3470
|
-
var countBy = (entries, level, reserved) => entries.reduce(
|
|
3471
|
-
(counts, e) => {
|
|
3472
|
-
const name = e.candidates[level] ?? "";
|
|
3473
|
-
counts[name] = (counts[name] ?? 0) + 1;
|
|
3474
|
-
if (reserved.has(name)) counts[name] = (counts[name] ?? 0) + 1;
|
|
3475
|
-
return counts;
|
|
3476
|
-
},
|
|
3477
|
-
{}
|
|
3478
|
-
);
|
|
3479
|
-
var resolveNameCollisions = (entries, reserved) => {
|
|
3480
|
-
const levels = entries[0]?.candidates.length ?? 0;
|
|
3481
|
-
const resolve6 = (unresolved, level) => {
|
|
3482
|
-
if (unresolved.length === 0 || level >= levels) return {};
|
|
3483
|
-
const counts = countBy(unresolved, level, reserved);
|
|
3484
|
-
const isLastLevel = level >= levels - 1;
|
|
3485
|
-
const [resolved, colliding] = unresolved.reduce(
|
|
3486
|
-
([res, col], e) => {
|
|
3487
|
-
const name = e.candidates[level] ?? "";
|
|
3488
|
-
return (counts[name] ?? 0) > 1 && !isLastLevel ? [res, [...col, e]] : [{ ...res, [e.key]: name }, col];
|
|
3489
|
-
},
|
|
3490
|
-
[{}, []]
|
|
3491
|
-
);
|
|
3492
|
-
return { ...resolved, ...resolve6(colliding, level + 1) };
|
|
3493
|
-
};
|
|
3494
|
-
return resolve6(entries, 0);
|
|
3495
|
-
};
|
|
3496
|
-
var mkExtensionNameCandidates = (ext) => {
|
|
3497
|
-
return { candidates: extensionCandidates(ext.name, ext.path), recommended: "" };
|
|
3498
|
-
};
|
|
3499
|
-
var mkSliceNameCandidates = (fieldName, sliceName) => {
|
|
3500
|
-
return { candidates: sliceCandidates(fieldName, sliceName), recommended: "" };
|
|
3501
|
-
};
|
|
3502
|
-
var assignRecommendedBaseNames = (profile) => {
|
|
3503
|
-
const extensionEntries = (profile.extensions ?? []).filter((ext) => ext.url).map((ext) => ({
|
|
3504
|
-
key: `ext:${ext.url}:${ext.path}`,
|
|
3505
|
-
candidates: ext.nameCandidates.candidates
|
|
3506
|
-
}));
|
|
3507
|
-
const sliceEntries = Object.entries(profile.slicing ?? {}).flatMap(([fieldName, fieldSlicing]) => {
|
|
3508
|
-
if (!fieldSlicing.slices) return [];
|
|
3509
|
-
return Object.entries(fieldSlicing.slices).map(([sliceName, slice]) => ({
|
|
3510
|
-
key: `slice:${fieldName}:${sliceName}`,
|
|
3511
|
-
candidates: slice.nameCandidates.candidates
|
|
3512
|
-
}));
|
|
3513
|
-
});
|
|
3514
|
-
const reservedNames = new Set(Object.keys(profile.fields ?? {}).map(normalizeCamelName));
|
|
3515
|
-
const allEntries = [...extensionEntries, ...sliceEntries];
|
|
3516
|
-
if (allEntries.length === 0) return;
|
|
3517
|
-
const resolved = resolveNameCollisions(allEntries, reservedNames);
|
|
3518
|
-
for (const ext of profile.extensions ?? []) {
|
|
3519
|
-
if (!ext.url) continue;
|
|
3520
|
-
const key = `ext:${ext.url}:${ext.path}`;
|
|
3521
|
-
if (resolved[key]) ext.nameCandidates.recommended = resolved[key];
|
|
3522
|
-
}
|
|
3523
|
-
for (const [fieldName, fieldSlicing] of Object.entries(profile.slicing ?? {})) {
|
|
3524
|
-
if (!fieldSlicing.slices) continue;
|
|
3525
|
-
for (const [sliceName, slice] of Object.entries(fieldSlicing.slices)) {
|
|
3526
|
-
const key = `slice:${fieldName}:${sliceName}`;
|
|
3527
|
-
if (resolved[key]) slice.nameCandidates.recommended = resolved[key];
|
|
3528
|
-
}
|
|
3529
|
-
}
|
|
3530
|
-
};
|
|
3531
|
-
|
|
3532
|
-
// src/fhir-types/hl7-fhir-r4-core/CodeSystem.ts
|
|
3533
|
-
var isCodeSystem = (resource) => {
|
|
3534
|
-
return resource !== null && typeof resource === "object" && resource.resourceType === "CodeSystem";
|
|
3535
|
-
};
|
|
3536
|
-
|
|
3537
|
-
// src/fhir-types/hl7-fhir-r4-core/ValueSet.ts
|
|
3538
|
-
var isValueSet = (resource) => {
|
|
3539
|
-
return resource !== null && typeof resource === "object" && resource.resourceType === "ValueSet";
|
|
3540
|
-
};
|
|
3541
|
-
|
|
3542
|
-
// src/typeschema/register.ts
|
|
3543
|
-
var BARE_RESOURCE_NAME_RE = /^[a-zA-Z0-9]+$/;
|
|
3544
|
-
var readPackageDependencies = async (manager, packageMeta2) => {
|
|
3545
|
-
const packageJSON = await manager.packageJson(packageMeta2.name);
|
|
3546
|
-
if (!packageJSON) return [];
|
|
3547
|
-
const dependencies = packageJSON.dependencies;
|
|
3548
|
-
if (dependencies !== void 0) {
|
|
3549
|
-
return Object.entries(dependencies).map(([name, version]) => {
|
|
3550
|
-
return { name, version };
|
|
3551
|
-
});
|
|
3552
|
-
}
|
|
3553
|
-
return [];
|
|
3554
3442
|
};
|
|
3555
3443
|
var mkEmptyPkgIndex = (pkg) => {
|
|
3556
3444
|
return {
|
|
3557
3445
|
pkg,
|
|
3558
3446
|
canonicalResolution: {},
|
|
3559
3447
|
fhirSchemas: {},
|
|
3560
|
-
valueSets: {}
|
|
3448
|
+
valueSets: {},
|
|
3449
|
+
terminology: []
|
|
3561
3450
|
};
|
|
3562
3451
|
};
|
|
3563
3452
|
var mkPackageAwareResolver = async (manager, pkg, deep, acc, logger) => {
|
|
@@ -3567,6 +3456,8 @@ var mkPackageAwareResolver = async (manager, pkg, deep, acc, logger) => {
|
|
|
3567
3456
|
const index = mkEmptyPkgIndex(pkg);
|
|
3568
3457
|
acc[pkgId] = index;
|
|
3569
3458
|
for (const resource of await manager.search({ package: pkg })) {
|
|
3459
|
+
const terminologyResource = asTerminologyResource(resource, logger);
|
|
3460
|
+
if (terminologyResource) index.terminology.push(terminologyResource);
|
|
3570
3461
|
const rawUrl = resource.url;
|
|
3571
3462
|
if (!rawUrl) continue;
|
|
3572
3463
|
if (!(isStructureDefinition(resource) || isValueSet(resource) || isCodeSystem(resource))) continue;
|
|
@@ -3675,6 +3566,8 @@ var registerFromManager = async (manager, { logger, focusedPackages }) => {
|
|
|
3675
3566
|
while (fs6?.base) {
|
|
3676
3567
|
const pkg2 = fs6.package_meta;
|
|
3677
3568
|
const baseUrl = ensureSpecializationCanonicalUrl(fs6.base);
|
|
3569
|
+
if (fs6.kind === "logical" && fs6.derivation === "specialization" && isVirtualFhirBaseCanonical(fs6.base))
|
|
3570
|
+
break;
|
|
3678
3571
|
fs6 = resolveFs(pkg2, baseUrl);
|
|
3679
3572
|
if (fs6 === void 0)
|
|
3680
3573
|
throw new Error(
|
|
@@ -3739,6 +3632,9 @@ var registerFromManager = async (manager, { logger, focusedPackages }) => {
|
|
|
3739
3632
|
).filter((r) => isStructureDefinition(r)).sort((sd1, sd2) => sd1.url.localeCompare(sd2.url)),
|
|
3740
3633
|
allFs: () => Object.values(resolver).flatMap((pkgIndex) => Object.values(pkgIndex.fhirSchemas)),
|
|
3741
3634
|
allVs: () => Object.values(resolver).flatMap((pkgIndex) => Object.values(pkgIndex.valueSets)),
|
|
3635
|
+
allTerminology: () => Object.values(resolver).map(({ pkg, terminology }) => ({ packageMeta: pkg, resources: terminology })).sort(
|
|
3636
|
+
(left, right) => packageMetaToNpm(left.packageMeta).localeCompare(packageMetaToNpm(right.packageMeta))
|
|
3637
|
+
),
|
|
3742
3638
|
resolveVs,
|
|
3743
3639
|
resolveAny: (canonicalUrl) => packageAgnosticResolveCanonical(resolver, canonicalUrl),
|
|
3744
3640
|
resolveElementSnapshot,
|
|
@@ -3763,38 +3659,343 @@ var registerFromManager = async (manager, { logger, focusedPackages }) => {
|
|
|
3763
3659
|
}
|
|
3764
3660
|
};
|
|
3765
3661
|
};
|
|
3766
|
-
var registerFromPackageMetas = async (packageMetas, conf) => {
|
|
3767
|
-
const packageNames = packageMetas.map(packageMetaToNpm);
|
|
3768
|
-
conf?.logger?.info(`Loading FHIR packages: ${packageNames.join(", ")}`);
|
|
3769
|
-
const manager = CanonicalManager({
|
|
3770
|
-
packages: packageNames,
|
|
3771
|
-
workingDir: ".codegen-cache/canonical-manager-cache",
|
|
3772
|
-
registry: conf.registry || void 0
|
|
3773
|
-
});
|
|
3774
|
-
await manager.init();
|
|
3775
|
-
return await registerFromManager(manager, {
|
|
3776
|
-
...conf,
|
|
3777
|
-
focusedPackages: packageMetas
|
|
3778
|
-
});
|
|
3662
|
+
var registerFromPackageMetas = async (packageMetas, conf) => {
|
|
3663
|
+
const packageNames = packageMetas.map(packageMetaToNpm);
|
|
3664
|
+
conf?.logger?.info(`Loading FHIR packages: ${packageNames.join(", ")}`);
|
|
3665
|
+
const manager = CanonicalManager({
|
|
3666
|
+
packages: packageNames,
|
|
3667
|
+
workingDir: ".codegen-cache/canonical-manager-cache",
|
|
3668
|
+
registry: conf.registry || void 0
|
|
3669
|
+
});
|
|
3670
|
+
await manager.init();
|
|
3671
|
+
return await registerFromManager(manager, {
|
|
3672
|
+
...conf,
|
|
3673
|
+
focusedPackages: packageMetas
|
|
3674
|
+
});
|
|
3675
|
+
};
|
|
3676
|
+
var resolveFsElementGenealogy = (genealogy, path) => {
|
|
3677
|
+
const [top, ...rest] = path;
|
|
3678
|
+
if (top === void 0) return [];
|
|
3679
|
+
return genealogy.map((fs6) => {
|
|
3680
|
+
if (!fs6.elements) return void 0;
|
|
3681
|
+
let elem = fs6.elements?.[top];
|
|
3682
|
+
for (const k of rest) {
|
|
3683
|
+
elem = elem?.elements?.[k];
|
|
3684
|
+
}
|
|
3685
|
+
return elem;
|
|
3686
|
+
}).filter((elem) => elem !== void 0);
|
|
3687
|
+
};
|
|
3688
|
+
function mergeFsElementProps(genealogy) {
|
|
3689
|
+
const revGenealogy = genealogy.reverse();
|
|
3690
|
+
const snapshot = Object.assign({}, ...revGenealogy);
|
|
3691
|
+
snapshot.elements = void 0;
|
|
3692
|
+
return snapshot;
|
|
3693
|
+
}
|
|
3694
|
+
|
|
3695
|
+
// src/typeschema/core/identifier.ts
|
|
3696
|
+
function dropVersionFromUrl(url) {
|
|
3697
|
+
const baseUrl = url.split("|")[0];
|
|
3698
|
+
return baseUrl ? baseUrl : url;
|
|
3699
|
+
}
|
|
3700
|
+
function getVersionFromUrl(url) {
|
|
3701
|
+
const version = url.split("|")[1];
|
|
3702
|
+
return version;
|
|
3703
|
+
}
|
|
3704
|
+
var identifierBase = (fhirSchema) => ({
|
|
3705
|
+
package: fhirSchema.package_meta.name,
|
|
3706
|
+
version: fhirSchema.package_meta.version,
|
|
3707
|
+
name: fhirSchema.name,
|
|
3708
|
+
url: fhirSchema.url
|
|
3709
|
+
});
|
|
3710
|
+
function mkIdentifier(fhirSchema) {
|
|
3711
|
+
const fields = identifierBase(fhirSchema);
|
|
3712
|
+
if (fhirSchema.derivation === "constraint") return { kind: "profile", ...fields };
|
|
3713
|
+
if (fhirSchema.kind === "primitive-type") return { kind: "primitive-type", ...fields };
|
|
3714
|
+
if (fhirSchema.kind === "complex-type") return { kind: "complex-type", ...fields };
|
|
3715
|
+
if (fhirSchema.kind === "resource") return { kind: "resource", ...fields };
|
|
3716
|
+
if (fhirSchema.kind === "logical") return { kind: "logical", ...fields };
|
|
3717
|
+
return { kind: "resource", ...fields };
|
|
3718
|
+
}
|
|
3719
|
+
var VALUE_SET_NAME_SPLIT_RE = /[-_]/;
|
|
3720
|
+
var OPAQUE_VALUE_SET_ID_RE = /^[a-zA-Z0-9_-]{20,}$/;
|
|
3721
|
+
var getValueSetName = (url) => {
|
|
3722
|
+
const urlParts = url.split("/");
|
|
3723
|
+
const lastSegment = urlParts[urlParts.length - 1];
|
|
3724
|
+
if (lastSegment && lastSegment.length > 0) {
|
|
3725
|
+
return lastSegment.split(VALUE_SET_NAME_SPLIT_RE).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
|
|
3726
|
+
}
|
|
3727
|
+
return url;
|
|
3728
|
+
};
|
|
3729
|
+
function mkValueSetIdentifierByUrl(register, pkg, fullValueSetUrl) {
|
|
3730
|
+
const valueSetUrl = dropVersionFromUrl(fullValueSetUrl);
|
|
3731
|
+
const valueSetNameFallback = getValueSetName(valueSetUrl);
|
|
3732
|
+
const valuesSetFallback = {
|
|
3733
|
+
package_meta: {
|
|
3734
|
+
name: "missing_valuesets",
|
|
3735
|
+
version: getVersionFromUrl(valueSetUrl) || "0.0.0"
|
|
3736
|
+
},
|
|
3737
|
+
id: fullValueSetUrl};
|
|
3738
|
+
const valueSet = register.resolveVs(pkg, valueSetUrl) || valuesSetFallback;
|
|
3739
|
+
const valueSetName = valueSet?.id && !OPAQUE_VALUE_SET_ID_RE.test(valueSet.id) ? valueSet.id : valueSetNameFallback;
|
|
3740
|
+
return {
|
|
3741
|
+
kind: "value-set",
|
|
3742
|
+
package: valueSet.package_meta.name,
|
|
3743
|
+
version: valueSet.package_meta.version,
|
|
3744
|
+
name: valueSetName,
|
|
3745
|
+
url: valueSetUrl
|
|
3746
|
+
};
|
|
3747
|
+
}
|
|
3748
|
+
function mkBindingIdentifier(fhirSchema, path, element) {
|
|
3749
|
+
const bindingName = element.binding?.bindingName;
|
|
3750
|
+
const pathStr = path.join(".");
|
|
3751
|
+
const [pkg, name, url] = bindingName ? [{ name: "shared", version: "1.0.0" }, bindingName, `urn:fhir:binding:${bindingName}`] : [fhirSchema.package_meta, `${fhirSchema.name}.${pathStr}_binding`, `${fhirSchema.url}#${pathStr}_binding`];
|
|
3752
|
+
return {
|
|
3753
|
+
kind: "binding",
|
|
3754
|
+
package: pkg.name,
|
|
3755
|
+
version: pkg.version,
|
|
3756
|
+
name,
|
|
3757
|
+
url
|
|
3758
|
+
};
|
|
3759
|
+
}
|
|
3760
|
+
|
|
3761
|
+
// src/typeschema/core/binding.ts
|
|
3762
|
+
function extractValueSetConceptsByUrl(register, pkg, valueSetUrl, logger) {
|
|
3763
|
+
const cleanUrl = dropVersionFromUrl(valueSetUrl) || valueSetUrl;
|
|
3764
|
+
const valueSet = register.resolveVs(pkg, cleanUrl);
|
|
3765
|
+
if (!valueSet) return void 0;
|
|
3766
|
+
return extractValueSetConcepts(register, valueSet);
|
|
3767
|
+
}
|
|
3768
|
+
function extractValueSetConcepts(register, valueSet, _logger) {
|
|
3769
|
+
if (valueSet.expansion?.contains) {
|
|
3770
|
+
return valueSet.expansion.contains.filter((item) => item.code !== void 0).map((item) => {
|
|
3771
|
+
assert4(item.code);
|
|
3772
|
+
return {
|
|
3773
|
+
code: item.code,
|
|
3774
|
+
display: item.display,
|
|
3775
|
+
system: item.system
|
|
3776
|
+
};
|
|
3777
|
+
});
|
|
3778
|
+
}
|
|
3779
|
+
const concepts = [];
|
|
3780
|
+
if (valueSet.compose?.include) {
|
|
3781
|
+
for (const include of valueSet.compose.include) {
|
|
3782
|
+
if (include.concept) {
|
|
3783
|
+
for (const concept of include.concept) {
|
|
3784
|
+
concepts.push({
|
|
3785
|
+
system: include.system,
|
|
3786
|
+
code: concept.code,
|
|
3787
|
+
display: concept.display
|
|
3788
|
+
});
|
|
3789
|
+
}
|
|
3790
|
+
} else if (include.system && !include.filter) {
|
|
3791
|
+
try {
|
|
3792
|
+
const codeSystem = register.resolveAny(include.system);
|
|
3793
|
+
if (codeSystem?.concept) {
|
|
3794
|
+
const extractConcepts = (conceptList, system) => {
|
|
3795
|
+
for (const concept of conceptList) {
|
|
3796
|
+
concepts.push({
|
|
3797
|
+
system,
|
|
3798
|
+
code: concept.code,
|
|
3799
|
+
display: concept.display
|
|
3800
|
+
});
|
|
3801
|
+
if (concept.concept) {
|
|
3802
|
+
extractConcepts(concept.concept, system);
|
|
3803
|
+
}
|
|
3804
|
+
}
|
|
3805
|
+
};
|
|
3806
|
+
extractConcepts(codeSystem.concept, include.system);
|
|
3807
|
+
}
|
|
3808
|
+
} catch {
|
|
3809
|
+
}
|
|
3810
|
+
}
|
|
3811
|
+
}
|
|
3812
|
+
}
|
|
3813
|
+
return concepts.length > 0 ? concepts : void 0;
|
|
3814
|
+
}
|
|
3815
|
+
var MAX_ENUM_LENGTH = 100;
|
|
3816
|
+
var PLACEHOLDER_ONLY_ENUM_CODES = /* @__PURE__ */ new Set(["UNK"]);
|
|
3817
|
+
var BINDABLE_TYPES = /* @__PURE__ */ new Set([
|
|
3818
|
+
"code",
|
|
3819
|
+
"Coding",
|
|
3820
|
+
"CodeableConcept",
|
|
3821
|
+
"CodeableReference",
|
|
3822
|
+
"Quantity",
|
|
3823
|
+
"string",
|
|
3824
|
+
"uri",
|
|
3825
|
+
"Duration"
|
|
3826
|
+
]);
|
|
3827
|
+
function buildEnum(register, fhirSchema, element, logger) {
|
|
3828
|
+
if (!element.binding) return void 0;
|
|
3829
|
+
const strength = element.binding.strength;
|
|
3830
|
+
const valueSetUrl = element.binding.valueSet;
|
|
3831
|
+
if (!valueSetUrl) return void 0;
|
|
3832
|
+
if (!BINDABLE_TYPES.has(element.type ?? "")) {
|
|
3833
|
+
logger?.dryWarn(
|
|
3834
|
+
"#binding",
|
|
3835
|
+
`eld-11: Binding on non-bindable type '${element.type}' (valueSet: ${valueSetUrl})`
|
|
3836
|
+
);
|
|
3837
|
+
return void 0;
|
|
3838
|
+
}
|
|
3839
|
+
const shouldGenerateEnum = strength === "required" || strength === "extensible" || strength === "preferred";
|
|
3840
|
+
if (!shouldGenerateEnum) return void 0;
|
|
3841
|
+
const concepts = extractValueSetConceptsByUrl(register, fhirSchema.package_meta, valueSetUrl);
|
|
3842
|
+
if (!concepts || concepts.length === 0) return void 0;
|
|
3843
|
+
const codes = concepts.map((c) => c.code).filter((code) => code && typeof code === "string" && code.trim().length > 0);
|
|
3844
|
+
const onlyCode = codes.length === 1 ? codes[0] : void 0;
|
|
3845
|
+
if (onlyCode && PLACEHOLDER_ONLY_ENUM_CODES.has(onlyCode)) {
|
|
3846
|
+
logger?.dryWarn(
|
|
3847
|
+
"#placeholderValueSet",
|
|
3848
|
+
`Value set ${valueSetUrl} only expands to placeholder code '${onlyCode}'; skipping enum generation.`
|
|
3849
|
+
);
|
|
3850
|
+
return void 0;
|
|
3851
|
+
}
|
|
3852
|
+
if (codes.length > MAX_ENUM_LENGTH) {
|
|
3853
|
+
logger?.dryWarn(
|
|
3854
|
+
"#largeValueSet",
|
|
3855
|
+
`Value set ${valueSetUrl} has ${codes.length} which is more than ${MAX_ENUM_LENGTH} codes, which may cause issues with code generation.`
|
|
3856
|
+
);
|
|
3857
|
+
return void 0;
|
|
3858
|
+
}
|
|
3859
|
+
if (codes.length === 0) return void 0;
|
|
3860
|
+
return { isOpen: strength !== "required", values: codes };
|
|
3861
|
+
}
|
|
3862
|
+
function generateBindingSchema(register, fhirSchema, path, element, logger) {
|
|
3863
|
+
if (!element.binding?.valueSet) return void 0;
|
|
3864
|
+
const identifier = mkBindingIdentifier(fhirSchema, path, element);
|
|
3865
|
+
const valueSetIdentifier = mkValueSetIdentifierByUrl(
|
|
3866
|
+
register,
|
|
3867
|
+
fhirSchema.package_meta,
|
|
3868
|
+
element.binding.valueSet
|
|
3869
|
+
);
|
|
3870
|
+
const enumResult = buildEnum(register, fhirSchema, element, logger);
|
|
3871
|
+
return {
|
|
3872
|
+
identifier,
|
|
3873
|
+
valueset: valueSetIdentifier,
|
|
3874
|
+
strength: element.binding.strength,
|
|
3875
|
+
enum: enumResult,
|
|
3876
|
+
dependencies: [valueSetIdentifier]
|
|
3877
|
+
};
|
|
3878
|
+
}
|
|
3879
|
+
function collectBindingSchemas(register, fhirSchema, logger) {
|
|
3880
|
+
const processedPaths = /* @__PURE__ */ new Set();
|
|
3881
|
+
if (!fhirSchema.elements) return [];
|
|
3882
|
+
const bindings = [];
|
|
3883
|
+
function collectBindings(elements, parentPath) {
|
|
3884
|
+
for (const [key, element] of Object.entries(elements)) {
|
|
3885
|
+
const path = [...parentPath, key];
|
|
3886
|
+
const pathKey = path.join(".");
|
|
3887
|
+
const elemSnapshot = register.resolveElementSnapshot(fhirSchema, path);
|
|
3888
|
+
if (processedPaths.has(pathKey)) continue;
|
|
3889
|
+
processedPaths.add(pathKey);
|
|
3890
|
+
if (elemSnapshot.binding) {
|
|
3891
|
+
const binding = generateBindingSchema(register, fhirSchema, path, elemSnapshot, logger);
|
|
3892
|
+
if (binding) {
|
|
3893
|
+
bindings.push(binding);
|
|
3894
|
+
}
|
|
3895
|
+
}
|
|
3896
|
+
if (element.elements) {
|
|
3897
|
+
collectBindings(element.elements, path);
|
|
3898
|
+
}
|
|
3899
|
+
}
|
|
3900
|
+
}
|
|
3901
|
+
collectBindings(fhirSchema.elements, []);
|
|
3902
|
+
bindings.sort((a, b) => a.identifier.name.localeCompare(b.identifier.name));
|
|
3903
|
+
const uniqueBindings = [];
|
|
3904
|
+
const seenUrls = /* @__PURE__ */ new Set();
|
|
3905
|
+
for (const binding of bindings) {
|
|
3906
|
+
if (!seenUrls.has(binding.identifier.url)) {
|
|
3907
|
+
seenUrls.add(binding.identifier.url);
|
|
3908
|
+
uniqueBindings.push(binding);
|
|
3909
|
+
}
|
|
3910
|
+
}
|
|
3911
|
+
return uniqueBindings;
|
|
3912
|
+
}
|
|
3913
|
+
|
|
3914
|
+
// src/typeschema/core/name-candidates.ts
|
|
3915
|
+
var normalizeName = (s) => {
|
|
3916
|
+
const cleaned = s.replace(/\[x\]/g, "").replace(/[- :.]/g, "_");
|
|
3917
|
+
if (!cleaned) return "";
|
|
3918
|
+
return uppercaseFirstLetter(cleaned);
|
|
3919
|
+
};
|
|
3920
|
+
var normalizeCamelName = (s) => {
|
|
3921
|
+
const cleaned = s.replace(/\[x\]/g, "").replace(/:/g, "_");
|
|
3922
|
+
if (!cleaned) return "";
|
|
3923
|
+
return uppercaseFirstLetter(camelCase(cleaned));
|
|
3924
|
+
};
|
|
3925
|
+
var extensionCandidates = (name, path) => {
|
|
3926
|
+
const base = normalizeCamelName(name) || "Extension";
|
|
3927
|
+
const pathParts = path.split(".").filter((p) => p && p !== "extension").join("_");
|
|
3928
|
+
const pathPart = pathParts ? normalizeCamelName(pathParts) : "";
|
|
3929
|
+
const qualified = `${pathPart}${base}`;
|
|
3930
|
+
return [base, qualified, `${qualified}Extension`];
|
|
3931
|
+
};
|
|
3932
|
+
var sliceCandidates = (fieldName, sliceName) => {
|
|
3933
|
+
const base = normalizeName(sliceName) || "Slice";
|
|
3934
|
+
const fieldPart = normalizeCamelName(fieldName) || "Field";
|
|
3935
|
+
const qualified = `${fieldPart}${base}`;
|
|
3936
|
+
return [base, qualified, `${qualified}Slice`];
|
|
3937
|
+
};
|
|
3938
|
+
var countBy = (entries, level, reserved) => entries.reduce(
|
|
3939
|
+
(counts, e) => {
|
|
3940
|
+
const name = e.candidates[level] ?? "";
|
|
3941
|
+
counts[name] = (counts[name] ?? 0) + 1;
|
|
3942
|
+
if (reserved.has(name)) counts[name] = (counts[name] ?? 0) + 1;
|
|
3943
|
+
return counts;
|
|
3944
|
+
},
|
|
3945
|
+
{}
|
|
3946
|
+
);
|
|
3947
|
+
var resolveNameCollisions = (entries, reserved) => {
|
|
3948
|
+
const levels = entries[0]?.candidates.length ?? 0;
|
|
3949
|
+
const resolve5 = (unresolved, level) => {
|
|
3950
|
+
if (unresolved.length === 0 || level >= levels) return {};
|
|
3951
|
+
const counts = countBy(unresolved, level, reserved);
|
|
3952
|
+
const isLastLevel = level >= levels - 1;
|
|
3953
|
+
const [resolved, colliding] = unresolved.reduce(
|
|
3954
|
+
([res, col], e) => {
|
|
3955
|
+
const name = e.candidates[level] ?? "";
|
|
3956
|
+
return (counts[name] ?? 0) > 1 && !isLastLevel ? [res, [...col, e]] : [{ ...res, [e.key]: name }, col];
|
|
3957
|
+
},
|
|
3958
|
+
[{}, []]
|
|
3959
|
+
);
|
|
3960
|
+
return { ...resolved, ...resolve5(colliding, level + 1) };
|
|
3961
|
+
};
|
|
3962
|
+
return resolve5(entries, 0);
|
|
3963
|
+
};
|
|
3964
|
+
var mkExtensionNameCandidates = (ext) => {
|
|
3965
|
+
return { candidates: extensionCandidates(ext.name, ext.path), recommended: "" };
|
|
3779
3966
|
};
|
|
3780
|
-
var
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3967
|
+
var mkSliceNameCandidates = (fieldName, sliceName) => {
|
|
3968
|
+
return { candidates: sliceCandidates(fieldName, sliceName), recommended: "" };
|
|
3969
|
+
};
|
|
3970
|
+
var assignRecommendedBaseNames = (profile) => {
|
|
3971
|
+
const extensionEntries = (profile.extensions ?? []).filter((ext) => ext.url).map((ext) => ({
|
|
3972
|
+
key: `ext:${ext.url}:${ext.path}`,
|
|
3973
|
+
candidates: ext.nameCandidates.candidates
|
|
3974
|
+
}));
|
|
3975
|
+
const sliceEntries = Object.entries(profile.slicing ?? {}).flatMap(([fieldName, fieldSlicing]) => {
|
|
3976
|
+
if (!fieldSlicing.slices) return [];
|
|
3977
|
+
return Object.entries(fieldSlicing.slices).map(([sliceName, slice]) => ({
|
|
3978
|
+
key: `slice:${fieldName}:${sliceName}`,
|
|
3979
|
+
candidates: slice.nameCandidates.candidates
|
|
3980
|
+
}));
|
|
3981
|
+
});
|
|
3982
|
+
const reservedNames = new Set(Object.keys(profile.fields ?? {}).map(normalizeCamelName));
|
|
3983
|
+
const allEntries = [...extensionEntries, ...sliceEntries];
|
|
3984
|
+
if (allEntries.length === 0) return;
|
|
3985
|
+
const resolved = resolveNameCollisions(allEntries, reservedNames);
|
|
3986
|
+
for (const ext of profile.extensions ?? []) {
|
|
3987
|
+
if (!ext.url) continue;
|
|
3988
|
+
const key = `ext:${ext.url}:${ext.path}`;
|
|
3989
|
+
if (resolved[key]) ext.nameCandidates.recommended = resolved[key];
|
|
3990
|
+
}
|
|
3991
|
+
for (const [fieldName, fieldSlicing] of Object.entries(profile.slicing ?? {})) {
|
|
3992
|
+
if (!fieldSlicing.slices) continue;
|
|
3993
|
+
for (const [sliceName, slice] of Object.entries(fieldSlicing.slices)) {
|
|
3994
|
+
const key = `slice:${fieldName}:${sliceName}`;
|
|
3995
|
+
if (resolved[key]) slice.nameCandidates.recommended = resolved[key];
|
|
3788
3996
|
}
|
|
3789
|
-
|
|
3790
|
-
}).filter((elem) => elem !== void 0);
|
|
3997
|
+
}
|
|
3791
3998
|
};
|
|
3792
|
-
function mergeFsElementProps(genealogy) {
|
|
3793
|
-
const revGenealogy = genealogy.reverse();
|
|
3794
|
-
const snapshot = Object.assign({}, ...revGenealogy);
|
|
3795
|
-
snapshot.elements = void 0;
|
|
3796
|
-
return snapshot;
|
|
3797
|
-
}
|
|
3798
3999
|
|
|
3799
4000
|
// src/typeschema/core/nested-types.ts
|
|
3800
4001
|
var hasStructuralElements = (register, fhirSchema, path) => {
|
|
@@ -3965,7 +4166,7 @@ var fieldTypeResolutionHint = (register, pkg, type) => {
|
|
|
3965
4166
|
if (!dependsOnR4Core(register, pkg)) return "";
|
|
3966
4167
|
return `
|
|
3967
4168
|
hint: '${type}' is an R5+ type and is not available when generating against R4.
|
|
3968
|
-
Either
|
|
4169
|
+
Either exclude this canonical via a CanonicalManager patch (excludeCanonical; see src/api/builtin-patches.ts), or upgrade the target to R5.`;
|
|
3969
4170
|
};
|
|
3970
4171
|
function isRequired(register, fhirSchema, path) {
|
|
3971
4172
|
const fieldName = path[path.length - 1];
|
|
@@ -4223,18 +4424,13 @@ var mkField = (register, fhirSchema, path, element, logger, rawElement) => {
|
|
|
4223
4424
|
if (!valueConstraint && elemForCodingCheck.elements?.coding?.slicing?.slices) {
|
|
4224
4425
|
const codingSlices = elemForCodingCheck.elements.coding.slicing.slices;
|
|
4225
4426
|
const allSliceValues = Object.values(codingSlices);
|
|
4226
|
-
const
|
|
4227
|
-
(s) => s.min !== void 0 && s.min >= 1 && s.match && typeof s.match === "object" &&
|
|
4427
|
+
const allRequiredWithSystem = allSliceValues.length > 0 && allSliceValues.every(
|
|
4428
|
+
(s) => s.min !== void 0 && s.min >= 1 && s.match && typeof s.match === "object" && typeof s.match.system === "string"
|
|
4228
4429
|
);
|
|
4229
|
-
if (
|
|
4430
|
+
if (allRequiredWithSystem) {
|
|
4230
4431
|
const codingValues = allSliceValues.flatMap((s) => s.match ? [s.match] : []);
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
type: "CodeableConcept",
|
|
4234
|
-
value: {
|
|
4235
|
-
coding: codingValues
|
|
4236
|
-
}
|
|
4237
|
-
};
|
|
4432
|
+
const fullyFixed = allSliceValues.every((s) => typeof s.match.code === "string");
|
|
4433
|
+
valueConstraint = fullyFixed ? { kind: "fixed", type: "CodeableConcept", value: { coding: codingValues } } : { kind: "pattern", type: "CodeableConcept", value: { coding: codingValues }, validateOnly: true };
|
|
4238
4434
|
}
|
|
4239
4435
|
}
|
|
4240
4436
|
return {
|
|
@@ -4404,14 +4600,6 @@ function mkFields(register, fhirSchema, parentPath, elements, logger) {
|
|
|
4404
4600
|
for (const key of register.getAllElementKeys(elements)) {
|
|
4405
4601
|
const path = [...parentPath, key];
|
|
4406
4602
|
const elemSnapshot = register.resolveElementSnapshot(fhirSchema, path);
|
|
4407
|
-
const fcurl = elemSnapshot.type ? register.ensureSpecializationCanonicalUrl(elemSnapshot.type) : void 0;
|
|
4408
|
-
if (fcurl && shouldSkipCanonical(fhirSchema.package_meta, fcurl).shouldSkip) {
|
|
4409
|
-
logger?.warn(
|
|
4410
|
-
"#skipCanonical",
|
|
4411
|
-
`Skipping field ${path} for ${fcurl} due to skip hack ${shouldSkipCanonical(fhirSchema.package_meta, fcurl).reason}`
|
|
4412
|
-
);
|
|
4413
|
-
continue;
|
|
4414
|
-
}
|
|
4415
4603
|
if (isNestedElement(register, fhirSchema, path, elemSnapshot, elements[key])) {
|
|
4416
4604
|
fields[key] = mkNestedField(register, fhirSchema, path, elemSnapshot);
|
|
4417
4605
|
} else {
|
|
@@ -4469,17 +4657,18 @@ var extractProfileDependencies = (identifier, base, fields, nestedTypes) => {
|
|
|
4469
4657
|
function transformFhirSchema(register, fhirSchema, logger) {
|
|
4470
4658
|
let base;
|
|
4471
4659
|
if (fhirSchema.base) {
|
|
4472
|
-
const
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
)
|
|
4476
|
-
if (!baseFs)
|
|
4660
|
+
const baseUrl = register.ensureSpecializationCanonicalUrl(fhirSchema.base);
|
|
4661
|
+
const baseFs = register.resolveFs(fhirSchema.package_meta, baseUrl);
|
|
4662
|
+
const isVirtualLogicalBase = fhirSchema.kind === "logical" && fhirSchema.derivation === "specialization" && isVirtualFhirBaseCanonical(fhirSchema.base);
|
|
4663
|
+
if (!baseFs && !isVirtualLogicalBase)
|
|
4477
4664
|
throw new Error(
|
|
4478
4665
|
`Base resource not found '${fhirSchema.base}' for <${fhirSchema.url}> from ${packageMetaToFhir(fhirSchema.package_meta)}`
|
|
4479
4666
|
);
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
|
|
4667
|
+
if (baseFs) {
|
|
4668
|
+
const baseId = mkIdentifier(baseFs);
|
|
4669
|
+
assert4(!isNestedIdentifier(baseId), `Unexpected nested base for ${fhirSchema.url}`);
|
|
4670
|
+
base = baseId;
|
|
4671
|
+
}
|
|
4483
4672
|
}
|
|
4484
4673
|
const { fields, slicing } = mkFields(register, fhirSchema, [], fhirSchema.elements, logger);
|
|
4485
4674
|
const nested = mkNestedTypes(register, fhirSchema, logger);
|
|
@@ -4592,11 +4781,6 @@ var generateTypeSchemas = async (register, resolveCollisions, logger) => {
|
|
|
4592
4781
|
const schemasWithSources = [];
|
|
4593
4782
|
for (const fhirSchema of register.allFs()) {
|
|
4594
4783
|
const pkgId = packageMetaToFhir(fhirSchema.package_meta);
|
|
4595
|
-
const skipCheck = shouldSkipCanonical(fhirSchema.package_meta, fhirSchema.url);
|
|
4596
|
-
if (skipCheck.shouldSkip) {
|
|
4597
|
-
logger?.dryWarn("#skipCanonical", `Skip ${fhirSchema.url} from ${pkgId}. Reason: ${skipCheck.reason}`);
|
|
4598
|
-
continue;
|
|
4599
|
-
}
|
|
4600
4784
|
for (const schema of transformFhirSchema(register, fhirSchema, logger)) {
|
|
4601
4785
|
schemasWithSources.push({
|
|
4602
4786
|
schema,
|
|
@@ -5737,14 +5921,14 @@ var createGenerator = (templatePath, apiOpts) => {
|
|
|
5737
5921
|
return new MustacheGenerator(mustacheOptions);
|
|
5738
5922
|
};
|
|
5739
5923
|
function runCommand(cmd, args = [], options = {}) {
|
|
5740
|
-
return new Promise((
|
|
5924
|
+
return new Promise((resolve5, reject) => {
|
|
5741
5925
|
const child = spawn(cmd, args, {
|
|
5742
5926
|
stdio: "inherit",
|
|
5743
5927
|
...options
|
|
5744
5928
|
});
|
|
5745
5929
|
child.on("error", reject);
|
|
5746
5930
|
child.on("close", (code) => {
|
|
5747
|
-
if (code === 0)
|
|
5931
|
+
if (code === 0) resolve5(code);
|
|
5748
5932
|
else reject(new Error(`Prozess beendet mit Fehlercode ${code}`));
|
|
5749
5933
|
});
|
|
5750
5934
|
});
|
|
@@ -5775,13 +5959,19 @@ var MustacheGenerator = class extends FileSystemWriter {
|
|
|
5775
5959
|
tsIndex.collectComplexTypes().map((i) => i.identifier).sort((a, b) => a.url.localeCompare(b.url)).map((typeRef) => modelFactory.createComplexType(typeRef, cache)).forEach(this._renderComplexType.bind(this));
|
|
5776
5960
|
tsIndex.collectResources().map((i) => i.identifier).sort((a, b) => a.url.localeCompare(b.url)).map((typeRef) => modelFactory.createResource(typeRef, cache)).forEach(this._renderResource.bind(this));
|
|
5777
5961
|
this._renderUtility(modelFactory.createUtility());
|
|
5778
|
-
this.copyStaticFiles();
|
|
5779
5962
|
if (this.opts.shouldRunHooks) {
|
|
5780
5963
|
await this._runHooks(this.opts.hooks.afterGenerate);
|
|
5781
5964
|
}
|
|
5782
5965
|
return;
|
|
5783
5966
|
}
|
|
5967
|
+
/** Static files bypass the write buffer, so they are copied once generation is done and
|
|
5968
|
+
* `opts.inMemoryOnly` reflects the caller again rather than `generateAsync`'s buffering. */
|
|
5969
|
+
async generateAsync(tsIndex) {
|
|
5970
|
+
await super.generateAsync(tsIndex);
|
|
5971
|
+
this.copyStaticFiles();
|
|
5972
|
+
}
|
|
5784
5973
|
copyStaticFiles() {
|
|
5974
|
+
if (this.opts.inMemoryOnly) return;
|
|
5785
5975
|
const staticDir = Path5.resolve(this.opts.sources.staticSource);
|
|
5786
5976
|
if (!staticDir) {
|
|
5787
5977
|
throw new Error("staticDir must be set in subclass.");
|
|
@@ -5879,9 +6069,6 @@ var tsModuleName = (id) => {
|
|
|
5879
6069
|
var tsModuleFileName = (id) => {
|
|
5880
6070
|
return `${tsModuleName(id)}.ts`;
|
|
5881
6071
|
};
|
|
5882
|
-
var tsModulePath = (id) => {
|
|
5883
|
-
return `${tsPackageDir(id.package)}/${tsModuleName(id)}`;
|
|
5884
|
-
};
|
|
5885
6072
|
var tsNameFromCanonical = (canonical, dropFragment = true) => {
|
|
5886
6073
|
if (!canonical) return void 0;
|
|
5887
6074
|
const localName = extractNameFromCanonical(canonical, dropFragment);
|
|
@@ -5926,8 +6113,19 @@ var tsSliceFlatAllTypeName = (profileName, fieldName, sliceName) => {
|
|
|
5926
6113
|
var tsExtensionFlatTypeName = (profileName, extensionName) => {
|
|
5927
6114
|
return `${uppercaseFirstLetter(profileName)}_${uppercaseFirstLetter(normalizeTsName(extensionName))}Flat`;
|
|
5928
6115
|
};
|
|
6116
|
+
var tsExtensionExtractedTypeName = (profileName, extensionName) => {
|
|
6117
|
+
return `${uppercaseFirstLetter(profileName)}_${uppercaseFirstLetter(normalizeTsName(extensionName))}Extracted`;
|
|
6118
|
+
};
|
|
6119
|
+
var tsExtensionVFlatTypeName = (profileName, extensionName) => {
|
|
6120
|
+
return `${uppercaseFirstLetter(profileName)}_${uppercaseFirstLetter(normalizeTsName(extensionName))}VFlat`;
|
|
6121
|
+
};
|
|
5929
6122
|
var tsSliceStaticName = (name) => name.replace(/\[x\]/g, "").replace(/[^a-zA-Z0-9_$]/g, "_");
|
|
5930
6123
|
var tsValueFieldName = (id) => `value${uppercaseFirstLetter(id.name)}`;
|
|
6124
|
+
var TS_IDENTIFIER_RE = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
6125
|
+
var tsObjectKey = (key) => {
|
|
6126
|
+
if (key === "__proto__") return '["__proto__"]';
|
|
6127
|
+
return TS_IDENTIFIER_RE.test(key) ? key : JSON.stringify(key);
|
|
6128
|
+
};
|
|
5931
6129
|
|
|
5932
6130
|
// src/api/writer-generator/typescript/utils.ts
|
|
5933
6131
|
var primitiveType2tsType = {
|
|
@@ -6111,22 +6309,26 @@ var effectiveGetterDefault = (w, hasProfile) => {
|
|
|
6111
6309
|
if (configured === "profile" && !hasProfile) return "flat";
|
|
6112
6310
|
return configured;
|
|
6113
6311
|
};
|
|
6114
|
-
var returnTypeForMode = (mode, inputType, profileClassName) => {
|
|
6312
|
+
var returnTypeForMode = (mode, inputType, profileClassName, vFlatType) => {
|
|
6115
6313
|
if (mode === "profile" && profileClassName) return profileClassName;
|
|
6116
6314
|
if (mode === "raw") return "Extension";
|
|
6315
|
+
if (mode === "vflat" && vFlatType) return vFlatType;
|
|
6117
6316
|
return inputType;
|
|
6118
6317
|
};
|
|
6119
|
-
var generateExtensionGetterOverloads = (w, ext, targetPath, methodName, inputType, extProfileInfo, generateInputBody) => {
|
|
6318
|
+
var generateExtensionGetterOverloads = (w, ext, targetPath, methodName, inputType, extProfileInfo, generateInputBody, vFlatType) => {
|
|
6120
6319
|
const hasProfile = !!extProfileInfo;
|
|
6121
6320
|
const defaultMode = effectiveGetterDefault(w, hasProfile);
|
|
6122
6321
|
const modes = hasProfile ? ["flat", "profile", "raw"] : ["flat", "raw"];
|
|
6322
|
+
if (vFlatType) modes.splice(1, 0, "vflat");
|
|
6123
6323
|
for (const mode of modes) {
|
|
6124
|
-
const rt = returnTypeForMode(mode, inputType, extProfileInfo?.className);
|
|
6324
|
+
const rt = returnTypeForMode(mode, inputType, extProfileInfo?.className, vFlatType);
|
|
6125
6325
|
w.lineSM(`public ${methodName}(mode: '${mode}'): ${rt} | undefined`);
|
|
6126
6326
|
}
|
|
6127
|
-
const defaultReturn = returnTypeForMode(defaultMode, inputType, extProfileInfo?.className);
|
|
6327
|
+
const defaultReturn = returnTypeForMode(defaultMode, inputType, extProfileInfo?.className, vFlatType);
|
|
6128
6328
|
w.lineSM(`public ${methodName}(): ${defaultReturn} | undefined`);
|
|
6129
|
-
const allReturns = [
|
|
6329
|
+
const allReturns = [
|
|
6330
|
+
...new Set(modes.map((m) => returnTypeForMode(m, inputType, extProfileInfo?.className, vFlatType)))
|
|
6331
|
+
];
|
|
6130
6332
|
const modesUnion = modes.map((m) => `'${m}'`).join(" | ");
|
|
6131
6333
|
w.curlyBlock(
|
|
6132
6334
|
["public", methodName, `(mode: ${modesUnion} = '${defaultMode}'): ${allReturns.join(" | ")} | undefined`],
|
|
@@ -6204,21 +6406,50 @@ var generateComplexExtensionSetter2 = (w, info) => {
|
|
|
6204
6406
|
});
|
|
6205
6407
|
}
|
|
6206
6408
|
};
|
|
6409
|
+
var extractableMembers = (extProfileInfo) => {
|
|
6410
|
+
const slices = extProfileInfo ? collectSubExtensionSlices(extProfileInfo.snapshot) : [];
|
|
6411
|
+
if (slices.length === 0) return void 0;
|
|
6412
|
+
return slices.map((sub) => JSON.stringify(sub.name)).join(" | ");
|
|
6413
|
+
};
|
|
6414
|
+
var extensionExtractedTypes = (tsProfileName, ext, extProfileInfo) => {
|
|
6415
|
+
const inputTypeName = tsExtensionFlatTypeName(tsProfileName, ext.name);
|
|
6416
|
+
const members = extractableMembers(extProfileInfo);
|
|
6417
|
+
if (!members || !extProfileInfo) return { flat: `Partial<${inputTypeName}>`, vFlat: void 0 };
|
|
6418
|
+
const picked = `Pick<${extProfileInfo.className}Flat, ${members}>`;
|
|
6419
|
+
return { flat: `Partial<${picked}>`, vFlat: picked };
|
|
6420
|
+
};
|
|
6207
6421
|
var generateComplexExtensionGetter2 = (w, info) => {
|
|
6208
6422
|
const { ext, snapshot, getMethodName, targetPath, extProfileInfo } = info;
|
|
6209
6423
|
const tsProfileName = tsResourceName(snapshot.identifier);
|
|
6210
|
-
const
|
|
6211
|
-
const
|
|
6212
|
-
const
|
|
6213
|
-
|
|
6214
|
-
|
|
6215
|
-
|
|
6216
|
-
|
|
6217
|
-
|
|
6218
|
-
|
|
6219
|
-
|
|
6220
|
-
|
|
6221
|
-
|
|
6424
|
+
const baseName = ext.nameCandidates.recommended;
|
|
6425
|
+
const extractedType = tsExtensionExtractedTypeName(tsProfileName, baseName);
|
|
6426
|
+
const hasVFlat = extensionExtractedTypes(tsProfileName, ext, extProfileInfo).vFlat !== void 0;
|
|
6427
|
+
const vFlatType = hasVFlat ? tsExtensionVFlatTypeName(tsProfileName, baseName) : void 0;
|
|
6428
|
+
generateExtensionGetterOverloads(
|
|
6429
|
+
w,
|
|
6430
|
+
ext,
|
|
6431
|
+
targetPath,
|
|
6432
|
+
getMethodName,
|
|
6433
|
+
extractedType,
|
|
6434
|
+
extProfileInfo,
|
|
6435
|
+
() => {
|
|
6436
|
+
const configItems = (ext.subExtensions ?? []).map((sub) => {
|
|
6437
|
+
const valueField = sub.valueFieldType ? tsValueFieldName(sub.valueFieldType) : "value";
|
|
6438
|
+
const isArray = sub.max === "*";
|
|
6439
|
+
return `{ name: "${sub.url}", valueField: "${valueField}", isArray: ${isArray} }`;
|
|
6440
|
+
});
|
|
6441
|
+
w.line(`const config = [${configItems.join(", ")}]`);
|
|
6442
|
+
if (vFlatType && extProfileInfo) {
|
|
6443
|
+
w.curlyBlock(["if", "(mode === 'vflat')"], () => {
|
|
6444
|
+
w.line(`const { errors } = ${extProfileInfo.className}.apply(ext).validate()`);
|
|
6445
|
+
w.line('if (errors.length > 0) throw new Error(errors.join("; "))');
|
|
6446
|
+
w.line(`return extractComplexExtension<${vFlatType}>(ext, config)`);
|
|
6447
|
+
});
|
|
6448
|
+
}
|
|
6449
|
+
w.line(`return extractComplexExtension<${extractedType}>(ext, config)`);
|
|
6450
|
+
},
|
|
6451
|
+
vFlatType
|
|
6452
|
+
);
|
|
6222
6453
|
};
|
|
6223
6454
|
var generateSingleValueExtensionSetter2 = (w, tsIndex, info) => {
|
|
6224
6455
|
const { ext, setMethodName, targetPath, extProfileInfo } = info;
|
|
@@ -6367,6 +6598,11 @@ var collectTypesFromFlatInput = (tsIndex, snapshot, addType) => {
|
|
|
6367
6598
|
};
|
|
6368
6599
|
|
|
6369
6600
|
// src/api/writer-generator/typescript/profile-slices.ts
|
|
6601
|
+
var sliceAccessorBaseName = (candidates, recommended, fieldNames, reservedNames = /* @__PURE__ */ new Set()) => {
|
|
6602
|
+
const reserved = /* @__PURE__ */ new Set([...fieldNames.map((name) => uppercaseFirstLetter(name)), ...reservedNames]);
|
|
6603
|
+
if (!reserved.has(recommended)) return recommended;
|
|
6604
|
+
return candidates.find((candidate) => !reserved.has(candidate)) ?? recommended;
|
|
6605
|
+
};
|
|
6370
6606
|
var collectChoiceBaseNames = (tsIndex, typeId) => {
|
|
6371
6607
|
const names = /* @__PURE__ */ new Set();
|
|
6372
6608
|
const schema = tsIndex.resolveType(typeId);
|
|
@@ -6422,38 +6658,48 @@ var collectRequiredSliceNames2 = (field, fieldSlicing) => {
|
|
|
6422
6658
|
}).map(([name]) => name);
|
|
6423
6659
|
return names.length > 0 ? names : void 0;
|
|
6424
6660
|
};
|
|
6425
|
-
var collectSliceDefs2 = (tsIndex, snapshot) =>
|
|
6426
|
-
const
|
|
6427
|
-
|
|
6428
|
-
|
|
6429
|
-
|
|
6430
|
-
|
|
6431
|
-
|
|
6432
|
-
|
|
6433
|
-
const
|
|
6434
|
-
|
|
6435
|
-
|
|
6436
|
-
|
|
6437
|
-
|
|
6438
|
-
|
|
6439
|
-
|
|
6440
|
-
|
|
6441
|
-
|
|
6442
|
-
|
|
6443
|
-
|
|
6444
|
-
|
|
6445
|
-
|
|
6446
|
-
|
|
6447
|
-
|
|
6448
|
-
|
|
6449
|
-
|
|
6450
|
-
|
|
6451
|
-
|
|
6452
|
-
|
|
6453
|
-
|
|
6454
|
-
|
|
6661
|
+
var collectSliceDefs2 = (tsIndex, snapshot) => {
|
|
6662
|
+
const reservedBaseNames = /* @__PURE__ */ new Set();
|
|
6663
|
+
return Object.entries(snapshot.slicing ?? {}).flatMap(([fieldName, fieldSlicing]) => {
|
|
6664
|
+
const field = snapshot.fields[fieldName];
|
|
6665
|
+
if (!isNotChoiceDeclarationField(field) || !fieldSlicing.slices || !field.type) return [];
|
|
6666
|
+
const baseType = tsTypeFromIdentifier(field.type);
|
|
6667
|
+
const pkgName = snapshot.identifier.package;
|
|
6668
|
+
const choiceBaseNames = collectChoiceBaseNames(tsIndex, field.type);
|
|
6669
|
+
const isTypeDisc = isTypeDiscriminated(fieldSlicing);
|
|
6670
|
+
return Object.entries(fieldSlicing.slices).filter(([_, slice]) => Object.keys(slice.match ?? {}).length > 0).map(([sliceName, slice]) => {
|
|
6671
|
+
const matchFields = Object.keys(slice.match ?? {});
|
|
6672
|
+
const required = (slice.required ?? []).filter(
|
|
6673
|
+
(name) => !matchFields.includes(name) && !choiceBaseNames.has(name)
|
|
6674
|
+
);
|
|
6675
|
+
const cc = slice.elements ? tsIndex.constrainedChoice(pkgName, field.type, slice.elements) : void 0;
|
|
6676
|
+
const constrainedChoice = cc && !isPrimitiveIdentifier(cc.variantType) ? cc : void 0;
|
|
6677
|
+
const resourceType = isTypeDisc ? extractResourceTypeFromMatch(slice.match ?? {}) : void 0;
|
|
6678
|
+
const typedBaseType = resourceType ? `${baseType}<${resourceType}>` : baseType;
|
|
6679
|
+
const baseName = sliceAccessorBaseName(
|
|
6680
|
+
slice.nameCandidates.candidates,
|
|
6681
|
+
slice.nameCandidates.recommended,
|
|
6682
|
+
Object.keys(snapshot.fields),
|
|
6683
|
+
reservedBaseNames
|
|
6684
|
+
);
|
|
6685
|
+
reservedBaseNames.add(baseName);
|
|
6686
|
+
return {
|
|
6687
|
+
fieldName,
|
|
6688
|
+
baseType,
|
|
6689
|
+
typedBaseType,
|
|
6690
|
+
sliceName,
|
|
6691
|
+
baseName,
|
|
6692
|
+
match: slice.match ?? {},
|
|
6693
|
+
required,
|
|
6694
|
+
excluded: slice.excluded ?? [],
|
|
6695
|
+
array: Boolean(field.array),
|
|
6696
|
+
constrainedChoice,
|
|
6697
|
+
typeDiscriminator: isTypeDisc,
|
|
6698
|
+
max: slice.max ?? 0
|
|
6699
|
+
};
|
|
6700
|
+
});
|
|
6455
6701
|
});
|
|
6456
|
-
}
|
|
6702
|
+
};
|
|
6457
6703
|
var generateSliceSetters2 = (w, sliceDefs, snapshot) => {
|
|
6458
6704
|
const profileClassName = tsProfileClassName(snapshot);
|
|
6459
6705
|
const tsProfileName = tsResourceName(snapshot.identifier);
|
|
@@ -6603,26 +6849,54 @@ var generateSliceGetters2 = (w, sliceDefs, snapshot) => {
|
|
|
6603
6849
|
};
|
|
6604
6850
|
|
|
6605
6851
|
// src/api/writer-generator/typescript/profile-validation.ts
|
|
6606
|
-
var
|
|
6852
|
+
var collectSliceRequirements2 = (slice, match, field, tsIndex) => {
|
|
6853
|
+
const requiredFields = [];
|
|
6854
|
+
const choiceGroups = [];
|
|
6855
|
+
const matchKeys = new Set(Object.keys(match));
|
|
6856
|
+
const requiredNames = (slice.required ?? []).filter((rf) => !matchKeys.has(rf));
|
|
6857
|
+
const fieldType = field.type;
|
|
6858
|
+
for (const rf of requiredNames) {
|
|
6859
|
+
const variants = tsIndex && fieldType ? tsIndex.sliceChoiceVariants(fieldType.package, fieldType, slice.elements ?? [], rf) : void 0;
|
|
6860
|
+
if (variants && variants.length > 0) choiceGroups.push(variants);
|
|
6861
|
+
else requiredFields.push(rf);
|
|
6862
|
+
}
|
|
6863
|
+
if (tsIndex && fieldType && slice.elements) {
|
|
6864
|
+
const cc = tsIndex.constrainedChoice(fieldType.package, fieldType, slice.elements);
|
|
6865
|
+
if (cc && !requiredNames.includes(cc.choiceBase)) requiredFields.push(cc.variant);
|
|
6866
|
+
}
|
|
6867
|
+
return { requiredFields, choiceGroups };
|
|
6868
|
+
};
|
|
6869
|
+
var collectRegularFieldValidation2 = (errors, warnings, name, field, resolveRef, canonicalUrlExpr, tsIndex, fieldSlicing, enumExprs) => {
|
|
6607
6870
|
if (field.excluded) {
|
|
6608
6871
|
errors.push(`...validateExcluded(res, profileName, ${JSON.stringify(name)})`);
|
|
6609
6872
|
return;
|
|
6610
6873
|
}
|
|
6611
6874
|
if (field.required) errors.push(`...validateRequired(res, profileName, ${JSON.stringify(name)})`);
|
|
6612
6875
|
if (field.valueConstraint) {
|
|
6613
|
-
const
|
|
6614
|
-
|
|
6876
|
+
const constrainedValueExpr = canonicalUrlExpr && name === "url" && field.valueConstraint.value === canonicalUrlExpr.url ? canonicalUrlExpr.expr : JSON.stringify(field.valueConstraint.value);
|
|
6877
|
+
const fn = field.valueConstraint.validateOnly ? "validatePatternValue" : "validateFixedValue";
|
|
6878
|
+
const repeating = field.array ? ", true" : "";
|
|
6879
|
+
errors.push(`...${fn}(res, profileName, ${JSON.stringify(name)}, ${constrainedValueExpr}${repeating})`);
|
|
6615
6880
|
}
|
|
6616
6881
|
if (field.enum) {
|
|
6617
6882
|
const target = field.enum.isOpen ? warnings : errors;
|
|
6618
|
-
|
|
6883
|
+
const valuesExpr = enumExprs?.get(name) ?? JSON.stringify(field.enum.values);
|
|
6884
|
+
target.push(`...validateEnum(res, profileName, ${JSON.stringify(name)}, ${valuesExpr})`);
|
|
6619
6885
|
}
|
|
6620
6886
|
if (field.mustSupport && !field.required)
|
|
6621
6887
|
warnings.push(`...validateMustSupport(res, profileName, ${JSON.stringify(name)})`);
|
|
6622
|
-
if (field.reference && field.reference.resource.length > 0)
|
|
6888
|
+
if (field.reference && field.reference.resource.length > 0) {
|
|
6889
|
+
const allowed = field.reference.resource.flatMap((ref) => {
|
|
6890
|
+
const resolved = resolveRef(ref);
|
|
6891
|
+
const target = tsIndex?.resolveType(resolved);
|
|
6892
|
+
const family = target && "typeFamily" in target ? target.typeFamily?.resources ?? [] : [];
|
|
6893
|
+
if (family.length === 0) return [resolved.name];
|
|
6894
|
+
return family.filter((member) => !tsIndex?.isFamilyType(member)).map((member) => member.name).sort((a, b) => a.localeCompare(b));
|
|
6895
|
+
});
|
|
6623
6896
|
errors.push(
|
|
6624
|
-
`...validateReference(res, profileName, ${JSON.stringify(name)}, ${JSON.stringify(
|
|
6897
|
+
`...validateReference(res, profileName, ${JSON.stringify(name)}, ${JSON.stringify([...new Set(allowed)])})`
|
|
6625
6898
|
);
|
|
6899
|
+
}
|
|
6626
6900
|
if (fieldSlicing?.slices) {
|
|
6627
6901
|
for (const [sliceName, slice] of Object.entries(fieldSlicing.slices)) {
|
|
6628
6902
|
const match = slice.match ?? {};
|
|
@@ -6634,19 +6908,16 @@ var collectRegularFieldValidation2 = (errors, warnings, name, field, resolveRef,
|
|
|
6634
6908
|
`...validateSliceCardinality(res, profileName, ${JSON.stringify(name)}, ${JSON.stringify(match)}, ${JSON.stringify(sliceName)}, ${min}, ${max})`
|
|
6635
6909
|
);
|
|
6636
6910
|
}
|
|
6637
|
-
const
|
|
6638
|
-
|
|
6639
|
-
|
|
6640
|
-
|
|
6641
|
-
|
|
6642
|
-
|
|
6643
|
-
|
|
6644
|
-
|
|
6645
|
-
|
|
6646
|
-
|
|
6647
|
-
errors.push(
|
|
6648
|
-
`...validateSliceFields(res, profileName, ${JSON.stringify(name)}, ${JSON.stringify(match)}, ${JSON.stringify(sliceName)}, ${JSON.stringify(sliceRequiredFields)})`
|
|
6649
|
-
);
|
|
6911
|
+
const { requiredFields, choiceGroups } = collectSliceRequirements2(slice, match, field, tsIndex);
|
|
6912
|
+
if (requiredFields.length > 0 || choiceGroups.length > 0) {
|
|
6913
|
+
const args = [
|
|
6914
|
+
JSON.stringify(name),
|
|
6915
|
+
JSON.stringify(match),
|
|
6916
|
+
JSON.stringify(sliceName),
|
|
6917
|
+
JSON.stringify(requiredFields)
|
|
6918
|
+
];
|
|
6919
|
+
if (choiceGroups.length > 0) args.push(JSON.stringify(choiceGroups));
|
|
6920
|
+
errors.push(`...validateSliceFields(res, profileName, ${args.join(", ")})`);
|
|
6650
6921
|
}
|
|
6651
6922
|
}
|
|
6652
6923
|
}
|
|
@@ -6656,6 +6927,7 @@ var generateValidateMethod2 = (w, tsIndex, snapshot) => {
|
|
|
6656
6927
|
const profileName = snapshot.identifier.name;
|
|
6657
6928
|
const canonicalUrl = snapshot.identifier.url;
|
|
6658
6929
|
const canonicalUrlExpr = canonicalUrl ? { url: canonicalUrl, expr: `${tsProfileClassName(snapshot)}.canonicalUrl` } : void 0;
|
|
6930
|
+
const enumLinks = w.enumTerminologyLinks(tsIndex, snapshot);
|
|
6659
6931
|
w.curlyBlock(["validate(): { errors: string[]; warnings: string[] }"], () => {
|
|
6660
6932
|
w.line(`const profileName = "${profileName}"`);
|
|
6661
6933
|
w.line("const res = this.resource");
|
|
@@ -6678,7 +6950,8 @@ var generateValidateMethod2 = (w, tsIndex, snapshot) => {
|
|
|
6678
6950
|
tsIndex.findLastSpecializationByIdentifier,
|
|
6679
6951
|
canonicalUrlExpr,
|
|
6680
6952
|
tsIndex,
|
|
6681
|
-
snapshot.slicing?.[name]
|
|
6953
|
+
snapshot.slicing?.[name],
|
|
6954
|
+
enumLinks.exprs
|
|
6682
6955
|
);
|
|
6683
6956
|
}
|
|
6684
6957
|
for (const inheritedName of snapshot.inheritedRequiredFields ?? []) {
|
|
@@ -6736,11 +7009,6 @@ var tryPromoteChoice2 = (field, fields, params, promotedChoices, resolveRef, isF
|
|
|
6736
7009
|
params.push({ name: choiceName, tsType, typeId: choiceField.type });
|
|
6737
7010
|
promotedChoices.add(choiceName);
|
|
6738
7011
|
};
|
|
6739
|
-
var mkIsFamilyType = (tsIndex) => (ref) => {
|
|
6740
|
-
const schema = tsIndex.resolveType(ref);
|
|
6741
|
-
if (!schema || !("typeFamily" in schema)) return false;
|
|
6742
|
-
return (schema.typeFamily?.resources?.length ?? 0) > 0;
|
|
6743
|
-
};
|
|
6744
7012
|
var collectProfileFactoryInfo2 = (tsIndex, snapshot) => {
|
|
6745
7013
|
const autoFields = [];
|
|
6746
7014
|
const sliceAutoFields = [];
|
|
@@ -6750,7 +7018,7 @@ var collectProfileFactoryInfo2 = (tsIndex, snapshot) => {
|
|
|
6750
7018
|
const fields = snapshot.fields;
|
|
6751
7019
|
const promotedChoices = /* @__PURE__ */ new Set();
|
|
6752
7020
|
const resolveRef = tsIndex.findLastSpecializationByIdentifier;
|
|
6753
|
-
const isFamilyType =
|
|
7021
|
+
const isFamilyType = tsIndex.isFamilyType;
|
|
6754
7022
|
if (isResourceIdentifier(snapshot.base)) {
|
|
6755
7023
|
autoFields.push({ name: "resourceType", value: JSON.stringify(snapshot.base.name) });
|
|
6756
7024
|
}
|
|
@@ -6761,8 +7029,8 @@ var collectProfileFactoryInfo2 = (tsIndex, snapshot) => {
|
|
|
6761
7029
|
tryPromoteChoice2(field, fields, params, promotedChoices, resolveRef, isFamilyType);
|
|
6762
7030
|
continue;
|
|
6763
7031
|
}
|
|
6764
|
-
if (field.valueConstraint) {
|
|
6765
|
-
const value = JSON.stringify(field.valueConstraint.value);
|
|
7032
|
+
if (field.valueConstraint && !field.valueConstraint.validateOnly) {
|
|
7033
|
+
const value = field.valueConstraint.value === snapshot.identifier.url ? `${tsProfileClassName(snapshot)}.canonicalUrl` : JSON.stringify(field.valueConstraint.value);
|
|
6766
7034
|
autoFields.push({ name, value: field.array ? `[${value}]` : value });
|
|
6767
7035
|
fixedFields.add(name);
|
|
6768
7036
|
if (isNotChoiceDeclarationField(field) && field.type) {
|
|
@@ -6831,13 +7099,12 @@ var generateProfileIndexFile = (w, tsIndex, snapshots) => {
|
|
|
6831
7099
|
const exports$1 = /* @__PURE__ */ new Map();
|
|
6832
7100
|
for (const snapshot of snapshots) {
|
|
6833
7101
|
const className = tsProfileClassName(snapshot);
|
|
6834
|
-
const moduleName = tsProfileModuleName(tsIndex, snapshot);
|
|
6835
7102
|
if (!exports$1.has(className)) {
|
|
6836
|
-
exports$1.set(className,
|
|
7103
|
+
exports$1.set(className, tsProfileModuleName(tsIndex, snapshot));
|
|
6837
7104
|
}
|
|
6838
7105
|
}
|
|
6839
|
-
for (const
|
|
6840
|
-
w.
|
|
7106
|
+
for (const className of [...exports$1.keys()].sort()) {
|
|
7107
|
+
w.tsExport(`./${exports$1.get(className)}`, className);
|
|
6841
7108
|
}
|
|
6842
7109
|
});
|
|
6843
7110
|
});
|
|
@@ -6875,19 +7142,24 @@ var generateProfileHelpersImport = (w, tsIndex, snapshot, sliceDefs, factoryInfo
|
|
|
6875
7142
|
"validateChoiceProhibited",
|
|
6876
7143
|
"validateMustSupport"
|
|
6877
7144
|
);
|
|
7145
|
+
const hasPatternConstraint = Object.values(snapshot.fields).some(
|
|
7146
|
+
(field) => "valueConstraint" in field && field.valueConstraint?.validateOnly === true
|
|
7147
|
+
);
|
|
7148
|
+
if (hasPatternConstraint) imports.push("validatePatternValue");
|
|
6878
7149
|
if (imports.length > 0) {
|
|
6879
7150
|
w.tsImport("../../profile-helpers", ...imports);
|
|
6880
7151
|
w.line();
|
|
6881
7152
|
}
|
|
6882
7153
|
};
|
|
6883
7154
|
var generateProfileImports = (w, tsIndex, snapshot) => {
|
|
7155
|
+
const terminologyImports = w.enumTerminologyLinks(tsIndex, snapshot).imports;
|
|
6884
7156
|
const usedTypes = /* @__PURE__ */ new Map();
|
|
6885
7157
|
const getModulePath = (typeId) => {
|
|
6886
7158
|
if (isNestedIdentifier(typeId)) {
|
|
6887
7159
|
const path = tsNameFromCanonical(typeId.url, true);
|
|
6888
|
-
if (path) return `../../${
|
|
7160
|
+
if (path) return `../../${w.packageDir(typeId)}/${pascalCase(path)}`;
|
|
6889
7161
|
}
|
|
6890
|
-
return `../../${
|
|
7162
|
+
return `../../${w.modulePath(typeId)}`;
|
|
6891
7163
|
};
|
|
6892
7164
|
const addType = (typeId) => {
|
|
6893
7165
|
if (typeId.kind === "primitive-type") return;
|
|
@@ -6923,6 +7195,11 @@ var generateProfileImports = (w, tsIndex, snapshot) => {
|
|
|
6923
7195
|
w.tsImport(importPath, ...names.sort(), { typeOnly: true });
|
|
6924
7196
|
}
|
|
6925
7197
|
if (sortedModules.length > 0) w.line();
|
|
7198
|
+
const sortedTerminology = [...terminologyImports.entries()].sort(([left], [right]) => left.localeCompare(right));
|
|
7199
|
+
for (const [moduleDir, symbols] of sortedTerminology) {
|
|
7200
|
+
w.tsImport(`../../${moduleDir}/terminology`, ...[...symbols].sort());
|
|
7201
|
+
}
|
|
7202
|
+
if (sortedTerminology.length > 0) w.line();
|
|
6926
7203
|
const extProfileImports = /* @__PURE__ */ new Map();
|
|
6927
7204
|
for (const ext of snapshot.extensions ?? []) {
|
|
6928
7205
|
if (!ext.url) continue;
|
|
@@ -7031,6 +7308,7 @@ var generateFactoryMethods = (w, tsIndex, snapshot, factoryInfo) => {
|
|
|
7031
7308
|
w.line();
|
|
7032
7309
|
const subSlicesForInput = snapshot.base.name === "Extension" ? collectSubExtensionSlices(snapshot) : [];
|
|
7033
7310
|
const hasInputHelper = subSlicesForInput.length > 0;
|
|
7311
|
+
const requiresFactoryInput = hasParams || subSlicesForInput.some((sub) => sub.isRequired);
|
|
7034
7312
|
if (hasInputHelper) {
|
|
7035
7313
|
const rawInputTypeName = `${profileClassName}Raw`;
|
|
7036
7314
|
const inputTypeName = `${profileClassName}Flat`;
|
|
@@ -7069,9 +7347,24 @@ var generateFactoryMethods = (w, tsIndex, snapshot, factoryInfo) => {
|
|
|
7069
7347
|
}
|
|
7070
7348
|
);
|
|
7071
7349
|
w.line();
|
|
7072
|
-
const createResourceSig =
|
|
7350
|
+
const createResourceSig = requiresFactoryInput ? `args: ${rawInputTypeName} | ${inputTypeName}` : `args?: ${rawInputTypeName} | ${inputTypeName}`;
|
|
7073
7351
|
w.curlyBlock(["static", "createResource", `(${createResourceSig})`, `: ${tsBaseResourceName}`], () => {
|
|
7074
|
-
|
|
7352
|
+
const inputExpression = requiresFactoryInput ? "args" : "args ?? {}";
|
|
7353
|
+
w.lineSM(`const resolvedExtensions = ${profileClassName}.resolveInput(${inputExpression})`);
|
|
7354
|
+
for (const field of factoryInfo.sliceAutoFields) {
|
|
7355
|
+
if (field.name === "extension") continue;
|
|
7356
|
+
const matchRefs = field.sliceNames.map(
|
|
7357
|
+
(sliceName) => `${profileClassName}.${tsSliceStaticName(sliceName)}SliceMatch`
|
|
7358
|
+
);
|
|
7359
|
+
w.line(`const ${field.name}WithDefaults = ensureSliceDefaults(`);
|
|
7360
|
+
w.indentBlock(() => {
|
|
7361
|
+
w.line(`[...(args.${field.name} ?? [])],`);
|
|
7362
|
+
for (const ref of matchRefs) {
|
|
7363
|
+
w.line(`${ref},`);
|
|
7364
|
+
}
|
|
7365
|
+
});
|
|
7366
|
+
w.lineSM(")");
|
|
7367
|
+
}
|
|
7075
7368
|
const extSliceField = factoryInfo.sliceAutoFields.find((f) => f.name === "extension");
|
|
7076
7369
|
if (extSliceField) {
|
|
7077
7370
|
const matchRefs = extSliceField.sliceNames.map(
|
|
@@ -7109,7 +7402,7 @@ var generateFactoryMethods = (w, tsIndex, snapshot, factoryInfo) => {
|
|
|
7109
7402
|
w.lineSM("return resource");
|
|
7110
7403
|
});
|
|
7111
7404
|
w.line();
|
|
7112
|
-
const createSig =
|
|
7405
|
+
const createSig = requiresFactoryInput ? `args: ${rawInputTypeName} | ${inputTypeName}` : `args?: ${rawInputTypeName} | ${inputTypeName}`;
|
|
7113
7406
|
w.curlyBlock(["static", "create", `(${createSig})`, `: ${profileClassName}`], () => {
|
|
7114
7407
|
w.lineSM(`return ${profileClassName}.apply(${profileClassName}.createResource(args))`);
|
|
7115
7408
|
});
|
|
@@ -7220,6 +7513,21 @@ var generateInlineExtensionInputTypes = (w, tsIndex, snapshot) => {
|
|
|
7220
7513
|
w.line();
|
|
7221
7514
|
}
|
|
7222
7515
|
};
|
|
7516
|
+
var generateExtensionExtractedTypes = (w, tsIndex, snapshot) => {
|
|
7517
|
+
const tsProfileName = tsResourceName(snapshot.identifier);
|
|
7518
|
+
const complexExtensions = (snapshot.extensions ?? []).filter((ext) => ext.isComplex && ext.subExtensions);
|
|
7519
|
+
for (const ext of complexExtensions) {
|
|
7520
|
+
if (!ext.url) continue;
|
|
7521
|
+
const extProfileInfo = resolveExtensionProfile2(tsIndex, snapshot.identifier.package, ext.url);
|
|
7522
|
+
const { flat, vFlat } = extensionExtractedTypes(tsProfileName, ext, extProfileInfo);
|
|
7523
|
+
const baseName = ext.nameCandidates.recommended;
|
|
7524
|
+
w.lineSM(`export type ${tsExtensionExtractedTypeName(tsProfileName, baseName)} = ${flat}`);
|
|
7525
|
+
if (vFlat) {
|
|
7526
|
+
w.lineSM(`export type ${tsExtensionVFlatTypeName(tsProfileName, baseName)} = ${vFlat}`);
|
|
7527
|
+
}
|
|
7528
|
+
w.line();
|
|
7529
|
+
}
|
|
7530
|
+
};
|
|
7223
7531
|
var valueToTypeLiteral = (value) => {
|
|
7224
7532
|
if (value === null || value === void 0) return "undefined";
|
|
7225
7533
|
if (typeof value === "string") return JSON.stringify(value);
|
|
@@ -7295,15 +7603,29 @@ var generateRawType = (w, snapshot, factoryInfo) => {
|
|
|
7295
7603
|
});
|
|
7296
7604
|
w.line();
|
|
7297
7605
|
};
|
|
7298
|
-
var generateFlatInputType = (w, snapshot) => {
|
|
7606
|
+
var generateFlatInputType = (w, snapshot, factoryInfo) => {
|
|
7299
7607
|
const subSlices = snapshot.base.name === "Extension" ? collectSubExtensionSlices(snapshot) : [];
|
|
7300
7608
|
if (subSlices.length === 0) return;
|
|
7301
7609
|
const flatInputTypeName = `${tsProfileClassName(snapshot)}Flat`;
|
|
7610
|
+
const flatFields = [
|
|
7611
|
+
...factoryInfo.params.filter((param) => param.name !== "extension").map((param) => ({ name: param.name, optional: false, tsType: param.tsType })),
|
|
7612
|
+
...factoryInfo.sliceAutoFields.filter((field) => field.name !== "extension").map((field) => ({ name: field.name, optional: true, tsType: field.tsType })),
|
|
7613
|
+
...subSlices.map((sub) => ({
|
|
7614
|
+
name: sub.name,
|
|
7615
|
+
optional: !sub.isRequired,
|
|
7616
|
+
tsType: `${sub.tsType}${sub.isArray ? "[]" : ""}`
|
|
7617
|
+
}))
|
|
7618
|
+
];
|
|
7619
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7620
|
+
for (const field of flatFields) {
|
|
7621
|
+
if (seen.has(field.name)) {
|
|
7622
|
+
throw new Error(`Flat input field collision for ${flatInputTypeName}: ${field.name}`);
|
|
7623
|
+
}
|
|
7624
|
+
seen.add(field.name);
|
|
7625
|
+
}
|
|
7302
7626
|
w.curlyBlock(["export", "type", flatInputTypeName, "="], () => {
|
|
7303
|
-
for (const
|
|
7304
|
-
|
|
7305
|
-
const arr = sub.isArray ? "[]" : "";
|
|
7306
|
-
w.lineSM(`${sub.name}${opt}: ${sub.tsType}${arr}`);
|
|
7627
|
+
for (const field of flatFields) {
|
|
7628
|
+
w.lineSM(`${field.name}${field.optional ? "?" : ""}: ${field.tsType}`);
|
|
7307
7629
|
}
|
|
7308
7630
|
});
|
|
7309
7631
|
w.line();
|
|
@@ -7313,14 +7635,23 @@ var generateProfileClass = (w, tsIndex, snapshot) => {
|
|
|
7313
7635
|
const profileClassName = tsProfileClassName(snapshot);
|
|
7314
7636
|
const sliceDefs = collectSliceDefs2(tsIndex, snapshot);
|
|
7315
7637
|
const factoryInfo = collectProfileFactoryInfo2(tsIndex, snapshot);
|
|
7638
|
+
generateProfileHelpersImport(w, tsIndex, snapshot, sliceDefs, factoryInfo);
|
|
7316
7639
|
generateInlineExtensionInputTypes(w, tsIndex, snapshot);
|
|
7640
|
+
generateExtensionExtractedTypes(w, tsIndex, snapshot);
|
|
7317
7641
|
generateSliceInputTypes(w, snapshot, sliceDefs);
|
|
7318
|
-
generateProfileHelpersImport(w, tsIndex, snapshot, sliceDefs, factoryInfo);
|
|
7319
7642
|
generateRawType(w, snapshot, factoryInfo);
|
|
7320
|
-
generateFlatInputType(w, snapshot);
|
|
7643
|
+
generateFlatInputType(w, snapshot, factoryInfo);
|
|
7321
7644
|
const canonicalUrl = snapshot.identifier.url;
|
|
7322
7645
|
w.comment("CanonicalURL:", canonicalUrl, `(pkg: ${packageMetaToFhir(packageMeta(snapshot))})`);
|
|
7323
7646
|
w.curlyBlock(["export", "class", profileClassName], () => {
|
|
7647
|
+
const specializationBase = tsIndex.findLastSpecializationByIdentifier(snapshot.base);
|
|
7648
|
+
if (isResourceIdentifier(specializationBase)) {
|
|
7649
|
+
w.lineSM(`static readonly resourceType = ${JSON.stringify(specializationBase.name)}`);
|
|
7650
|
+
} else if (isProfileIdentifier(specializationBase) || isSnapshotProfileIdentifier(specializationBase)) {
|
|
7651
|
+
w.logger()?.error(
|
|
7652
|
+
`Cannot emit static resourceType for profile '${profileClassName}': base '${snapshot.base.url}' does not resolve to a specialization`
|
|
7653
|
+
);
|
|
7654
|
+
}
|
|
7324
7655
|
w.lineSM(`static readonly canonicalUrl = ${JSON.stringify(canonicalUrl)}`);
|
|
7325
7656
|
w.line();
|
|
7326
7657
|
generateStaticSliceFields2(w, sliceDefs);
|
|
@@ -7340,20 +7671,72 @@ var generateProfileClass = (w, tsIndex, snapshot) => {
|
|
|
7340
7671
|
};
|
|
7341
7672
|
|
|
7342
7673
|
// src/api/writer-generator/typescript/writer.ts
|
|
7343
|
-
var resolveTsAssets = (fn) =>
|
|
7344
|
-
const __dirname = Path5.dirname(fileURLToPath(import.meta.url));
|
|
7345
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
7346
|
-
if (__filename.endsWith("dist/index.js")) {
|
|
7347
|
-
return Path5.resolve(__dirname, "..", "assets", "api", "writer-generator", "typescript", fn);
|
|
7348
|
-
}
|
|
7349
|
-
return Path5.resolve(__dirname, "../../../..", "assets", "api", "writer-generator", "typescript", fn);
|
|
7350
|
-
};
|
|
7674
|
+
var resolveTsAssets = (fn) => resolveGeneratorAsset(import.meta.url, "typescript", fn);
|
|
7351
7675
|
var leafOf3 = (path) => path[path.length - 1] ?? "";
|
|
7352
7676
|
var TS_HARDCODED_GENERIC_NAMES = /* @__PURE__ */ new Set(["Reference", "Coding", "CodeableConcept"]);
|
|
7677
|
+
var CODE_SYSTEM_SUFFIX_RE = /CodeSystem$/;
|
|
7678
|
+
var CHOICE_SUFFIX_RE = /\[x\]/g;
|
|
7679
|
+
var INVALID_TS_IDENTIFIER_RUN_RE = /[^A-Za-z0-9_$]+/g;
|
|
7680
|
+
var PACKAGE_PATH_SEPARATOR_RE = /\\/g;
|
|
7681
|
+
var INVALID_PACKAGE_DIR_RUN_RE = /[^a-z0-9-]+/g;
|
|
7682
|
+
var PACKAGE_DIR_EDGE_RE = /^-+|-+$/g;
|
|
7683
|
+
var TS_IDENTIFIER_START_RE = /^[A-Za-z_$]/;
|
|
7684
|
+
var validTsIdentifier = (source) => {
|
|
7685
|
+
const normalized = source.replace(CHOICE_SUFFIX_RE, "_x_").replace(INVALID_TS_IDENTIFIER_RUN_RE, "_");
|
|
7686
|
+
return TS_IDENTIFIER_START_RE.test(normalized) ? normalized : `_${normalized}`;
|
|
7687
|
+
};
|
|
7688
|
+
var terminologySymbolName = (resource) => {
|
|
7689
|
+
const sourceName = resource.name ?? resource.id ?? tsNameFromCanonical(resource.url) ?? "Terminology";
|
|
7690
|
+
return validTsIdentifier(`${uppercaseFirstLetter(sourceName)}${resource.resourceType}`);
|
|
7691
|
+
};
|
|
7692
|
+
var terminologyResourceIdentity = (resource) => `${resource.id ?? ""}\0${resource.name ?? ""}`;
|
|
7693
|
+
var safePackageDir = (source) => {
|
|
7694
|
+
const normalized = tsPackageDir(source.replace(PACKAGE_PATH_SEPARATOR_RE, "_"));
|
|
7695
|
+
return normalized.replace(INVALID_PACKAGE_DIR_RUN_RE, "-").replace(PACKAGE_DIR_EDGE_RE, "") || "package";
|
|
7696
|
+
};
|
|
7697
|
+
var allocateTerminologySymbols = (resources) => {
|
|
7698
|
+
const baseNames = resources.map(terminologySymbolName);
|
|
7699
|
+
const counts = {};
|
|
7700
|
+
for (const name of baseNames) counts[name] = (counts[name] ?? 0) + 1;
|
|
7701
|
+
const used = /* @__PURE__ */ new Set();
|
|
7702
|
+
return resources.map((resource, index) => {
|
|
7703
|
+
const baseName = baseNames[index] ?? "Terminology";
|
|
7704
|
+
const localIdentity = tsNameFromCanonical(resource.url) ?? resource.id ?? "Resource";
|
|
7705
|
+
const desired = counts[baseName] === 1 ? baseName : validTsIdentifier(`${baseName}_${pascalCase(localIdentity)}`);
|
|
7706
|
+
let symbol = desired;
|
|
7707
|
+
let suffix = 2;
|
|
7708
|
+
while (used.has(symbol)) {
|
|
7709
|
+
symbol = `${desired}_${suffix}`;
|
|
7710
|
+
suffix += 1;
|
|
7711
|
+
}
|
|
7712
|
+
used.add(symbol);
|
|
7713
|
+
return { resource, symbol };
|
|
7714
|
+
});
|
|
7715
|
+
};
|
|
7353
7716
|
var TypeScript = class extends Writer {
|
|
7717
|
+
packageDirectories = /* @__PURE__ */ new Map();
|
|
7354
7718
|
constructor(options) {
|
|
7355
7719
|
super({ lineWidth: 120, ...options, resolveAssets: options.resolveAssets ?? resolveTsAssets });
|
|
7356
7720
|
}
|
|
7721
|
+
/** The package's physical output directory: consults the collision-suffix
|
|
7722
|
+
* map, so it can differ from the logical `tsPackageDir(name)` in name.ts
|
|
7723
|
+
* (e.g. `hl7-fhir-r4-core--2`). Unprefixed = stateful writer method. */
|
|
7724
|
+
packageDir(physical) {
|
|
7725
|
+
const pkg = "package" in physical ? { name: physical.package, version: physical.version } : physical;
|
|
7726
|
+
return this.packageDirectories.get(packageMetaToNpm(pkg)) ?? safePackageDir(pkg.name);
|
|
7727
|
+
}
|
|
7728
|
+
/** The module's physical position in the output tree: `packageDir/ModuleName`. */
|
|
7729
|
+
modulePath(identifier) {
|
|
7730
|
+
return `${this.packageDir(identifier)}/${tsModuleName(identifier)}`;
|
|
7731
|
+
}
|
|
7732
|
+
moduleSpecifier(specifier) {
|
|
7733
|
+
if (this.opts.moduleSpecifierStyle !== "node-esm" || !specifier.startsWith(".")) return specifier;
|
|
7734
|
+
return specifier.endsWith(".js") ? specifier : `${specifier}.js`;
|
|
7735
|
+
}
|
|
7736
|
+
directorySpecifier(specifier) {
|
|
7737
|
+
if (this.opts.moduleSpecifierStyle !== "node-esm" || !specifier.startsWith(".")) return specifier;
|
|
7738
|
+
return `${specifier}/index.js`;
|
|
7739
|
+
}
|
|
7357
7740
|
ifElseChain(branches, elseBody) {
|
|
7358
7741
|
branches.forEach((branch, i) => {
|
|
7359
7742
|
const prefix = i === 0 ? "if" : "} else if";
|
|
@@ -7375,7 +7758,8 @@ var TypeScript = class extends Writer {
|
|
|
7375
7758
|
const typeOnly = typeof last === "object" ? last.typeOnly : false;
|
|
7376
7759
|
const entities = typeof last === "object" ? rest.slice(0, -1) : rest;
|
|
7377
7760
|
const keyword = typeOnly ? "import type" : "import";
|
|
7378
|
-
const
|
|
7761
|
+
const specifier = this.moduleSpecifier(tsPackageName);
|
|
7762
|
+
const singleLine = `${keyword} { ${entities.join(", ")} } from "${specifier}"`;
|
|
7379
7763
|
if (singleLine.length <= (this.opts.lineWidth ?? 120)) {
|
|
7380
7764
|
this.lineSM(singleLine);
|
|
7381
7765
|
} else {
|
|
@@ -7383,14 +7767,27 @@ var TypeScript = class extends Writer {
|
|
|
7383
7767
|
for (const entity of entities) {
|
|
7384
7768
|
this.line(`${entity},`);
|
|
7385
7769
|
}
|
|
7386
|
-
}, [` from "${
|
|
7770
|
+
}, [` from "${specifier}";`]);
|
|
7387
7771
|
}
|
|
7388
7772
|
}
|
|
7389
|
-
|
|
7773
|
+
tsExport(from, ...rest) {
|
|
7774
|
+
const last = rest[rest.length - 1];
|
|
7775
|
+
const typeOnly = typeof last === "object" ? last.typeOnly : false;
|
|
7776
|
+
const entities = typeof last === "object" ? rest.slice(0, -1) : rest;
|
|
7777
|
+
const keyword = typeOnly ? "export type" : "export";
|
|
7778
|
+
this.lineSM(`${keyword} { ${entities.join(", ")} } from "${this.moduleSpecifier(from)}"`);
|
|
7779
|
+
}
|
|
7780
|
+
/** `export * from` a module, or a directory barrel when `barrel` is set. */
|
|
7781
|
+
tsExportAll(from, opts) {
|
|
7782
|
+
const specifier = opts?.barrel ? this.directorySpecifier(from) : this.moduleSpecifier(from);
|
|
7783
|
+
this.lineSM(`export * from "${specifier}"`);
|
|
7784
|
+
}
|
|
7785
|
+
generateFhirPackageIndexFile(schemas, hasTerminology = false) {
|
|
7390
7786
|
this.cat("index.ts", () => {
|
|
7787
|
+
if (hasTerminology) this.tsExportAll("./terminology");
|
|
7391
7788
|
const profiles = schemas.filter(isSnapshotProfileTypeSchema);
|
|
7392
7789
|
if (profiles.length > 0) {
|
|
7393
|
-
this.
|
|
7790
|
+
this.tsExportAll("./profiles", { barrel: true });
|
|
7394
7791
|
}
|
|
7395
7792
|
let exports$1 = schemas.flatMap((schema) => {
|
|
7396
7793
|
const resourceName = tsResourceName(schema.identifier);
|
|
@@ -7414,11 +7811,12 @@ var TypeScript = class extends Writer {
|
|
|
7414
7811
|
);
|
|
7415
7812
|
for (const exp of exports$1) {
|
|
7416
7813
|
this.debugComment(exp.identifier);
|
|
7814
|
+
const from = `./${exp.tsPackageName}`;
|
|
7417
7815
|
if (exp.typeExports.length > 0) {
|
|
7418
|
-
this.
|
|
7816
|
+
this.tsExport(from, ...exp.typeExports, { typeOnly: true });
|
|
7419
7817
|
}
|
|
7420
7818
|
if (exp.valueExports.length > 0) {
|
|
7421
|
-
this.
|
|
7819
|
+
this.tsExport(from, ...exp.valueExports);
|
|
7422
7820
|
}
|
|
7423
7821
|
}
|
|
7424
7822
|
});
|
|
@@ -7430,7 +7828,7 @@ var TypeScript = class extends Writer {
|
|
|
7430
7828
|
for (const dep of schema.dependencies) {
|
|
7431
7829
|
if (["complex-type", "resource", "logical"].includes(dep.kind)) {
|
|
7432
7830
|
imports.push({
|
|
7433
|
-
tsPackage: `${importPrefix}${
|
|
7831
|
+
tsPackage: `${importPrefix}${this.modulePath(dep)}`,
|
|
7434
7832
|
name: tsResourceName(dep),
|
|
7435
7833
|
dep
|
|
7436
7834
|
});
|
|
@@ -7451,7 +7849,7 @@ var TypeScript = class extends Writer {
|
|
|
7451
7849
|
const elementUrl = "http://hl7.org/fhir/StructureDefinition/Element";
|
|
7452
7850
|
const element = tsIndex.resolveByUrl(schema.identifier.package, elementUrl);
|
|
7453
7851
|
if (!element) throw new Error(`'${elementUrl}' not found for ${schema.identifier.package}.`);
|
|
7454
|
-
this.tsImport(`${importPrefix}${
|
|
7852
|
+
this.tsImport(`${importPrefix}${this.modulePath(element.identifier)}`, "Element", { typeOnly: true });
|
|
7455
7853
|
}
|
|
7456
7854
|
}
|
|
7457
7855
|
}
|
|
@@ -7460,7 +7858,7 @@ var TypeScript = class extends Writer {
|
|
|
7460
7858
|
if (complexTypeDeps && complexTypeDeps.length > 0) {
|
|
7461
7859
|
for (const dep of complexTypeDeps) {
|
|
7462
7860
|
this.debugComment(dep);
|
|
7463
|
-
this.
|
|
7861
|
+
this.tsExport(`../${this.modulePath(dep)}`, tsResourceName(dep), { typeOnly: true });
|
|
7464
7862
|
}
|
|
7465
7863
|
this.line();
|
|
7466
7864
|
}
|
|
@@ -7582,7 +7980,7 @@ var TypeScript = class extends Writer {
|
|
|
7582
7980
|
});
|
|
7583
7981
|
});
|
|
7584
7982
|
} else if (isSpecializationTypeSchema(schema)) {
|
|
7585
|
-
const isFamilyType =
|
|
7983
|
+
const isFamilyType = tsIndex.isFamilyType;
|
|
7586
7984
|
this.cat(`${tsModuleFileName(schema.identifier)}`, () => {
|
|
7587
7985
|
this.generateDisclaimer();
|
|
7588
7986
|
this.generateDependenciesImports(tsIndex, schema);
|
|
@@ -7600,6 +7998,204 @@ var TypeScript = class extends Writer {
|
|
|
7600
7998
|
throw new Error(`Profile generation not implemented for kind: ${schema.identifier.kind}`);
|
|
7601
7999
|
}
|
|
7602
8000
|
}
|
|
8001
|
+
/** Normalized terminology types: the FHIR vocabulary comes from the generated
|
|
8002
|
+
* CodeSystem type when the closure provides one; a package-free closure gets
|
|
8003
|
+
* a self-contained copy of the R4/R5 content-mode vocabulary instead. */
|
|
8004
|
+
generateTerminologyTypes(codeSystemImport) {
|
|
8005
|
+
this.cat("terminology-types.ts", () => {
|
|
8006
|
+
this.generateDisclaimer();
|
|
8007
|
+
if (codeSystemImport) this.tsImport(codeSystemImport, "CodeSystem", { typeOnly: true });
|
|
8008
|
+
this.line();
|
|
8009
|
+
const contentType = codeSystemImport ? `CodeSystem["content"]` : `("not-present" | "example" | "fragment" | "complete" | "supplement")`;
|
|
8010
|
+
this.lineSM(`export type TerminologyVerification = "registry-integrity" | "unverifiable" | (string & {})`);
|
|
8011
|
+
this.line();
|
|
8012
|
+
this.curlyBlock(["type", "TerminologyEntryBase", "="], () => {
|
|
8013
|
+
this.lineSM("canonicalUrl: string");
|
|
8014
|
+
this.lineSM("packageId: string");
|
|
8015
|
+
this.lineSM("packageVersion: string");
|
|
8016
|
+
this.lineSM("verification: TerminologyVerification");
|
|
8017
|
+
}, [";"]);
|
|
8018
|
+
this.line();
|
|
8019
|
+
this.line("/** `contentMode` is a CodeSystem concept; the other entry kinds have none. */");
|
|
8020
|
+
this.curlyBlock(["export", "type", "CodeSystemEntry", "=", "TerminologyEntryBase", "&"], () => {
|
|
8021
|
+
this.lineSM(`resourceType: "CodeSystem"`);
|
|
8022
|
+
this.lineSM(`contentMode?: ${contentType}`);
|
|
8023
|
+
}, [";"]);
|
|
8024
|
+
this.line();
|
|
8025
|
+
this.curlyBlock(["export", "type", "ValueSetEntry", "=", "TerminologyEntryBase", "&"], () => {
|
|
8026
|
+
this.lineSM(`resourceType: "ValueSet"`);
|
|
8027
|
+
}, [";"]);
|
|
8028
|
+
this.line();
|
|
8029
|
+
this.curlyBlock(["export", "type", "NamingSystemEntry", "=", "TerminologyEntryBase", "&"], () => {
|
|
8030
|
+
this.lineSM(`resourceType: "NamingSystem"`);
|
|
8031
|
+
}, [";"]);
|
|
8032
|
+
this.line();
|
|
8033
|
+
this.line("/** One normalized terminology resource, discriminated by `resourceType`. */");
|
|
8034
|
+
this.lineSM("export type TerminologyEntry = CodeSystemEntry | ValueSetEntry | NamingSystemEntry");
|
|
8035
|
+
this.line();
|
|
8036
|
+
this.line("/** A complete CodeSystem whose codes are embedded: the simplified runtime surface. */");
|
|
8037
|
+
this.curlyBlock([
|
|
8038
|
+
"export",
|
|
8039
|
+
"type",
|
|
8040
|
+
"CodedTerminologyEntry<Code extends string = string>",
|
|
8041
|
+
"=",
|
|
8042
|
+
"CodeSystemEntry",
|
|
8043
|
+
"&"
|
|
8044
|
+
], () => {
|
|
8045
|
+
this.lineSM(`contentMode: "complete"`);
|
|
8046
|
+
this.lineSM("codes: readonly Code[]");
|
|
8047
|
+
this.lineSM("displays: Readonly<Partial<Record<Code, string>>>");
|
|
8048
|
+
}, [";"]);
|
|
8049
|
+
});
|
|
8050
|
+
}
|
|
8051
|
+
/** Emitted terminology, resolved ahead of module generation so profile
|
|
8052
|
+
* emission can reference the allocated symbols. Keyed by package dir. */
|
|
8053
|
+
terminologyModules = /* @__PURE__ */ new Map();
|
|
8054
|
+
/** Coded systems across every emitted terminology module, by canonical URL. */
|
|
8055
|
+
terminologyCodeIndex = /* @__PURE__ */ new Map();
|
|
8056
|
+
prepareTerminology(generationUnits) {
|
|
8057
|
+
this.terminologyModules = /* @__PURE__ */ new Map();
|
|
8058
|
+
this.terminologyCodeIndex = /* @__PURE__ */ new Map();
|
|
8059
|
+
for (const [packageDir, unit] of [...generationUnits].sort(([left], [right]) => left.localeCompare(right))) {
|
|
8060
|
+
if (unit.terminology) this.prepareTerminologyModule(packageDir, unit.terminology);
|
|
8061
|
+
}
|
|
8062
|
+
}
|
|
8063
|
+
prepareTerminologyModule(packageDir, packageTerminology) {
|
|
8064
|
+
const { packageMeta: pkg } = packageTerminology;
|
|
8065
|
+
const verification = this.opts.terminology?.packageVerification?.[packageMetaToNpm(pkg)] ?? "not-recorded";
|
|
8066
|
+
const dedupedEntries = mkTerminologyEntries(packageTerminology, verification, this.logger());
|
|
8067
|
+
const sortedResources = dedupedEntries.slice().sort(({ resource: left }, { resource: right }) => {
|
|
8068
|
+
if (left.resourceType !== right.resourceType) return left.resourceType.localeCompare(right.resourceType);
|
|
8069
|
+
const symbolOrder = terminologySymbolName(left).localeCompare(terminologySymbolName(right));
|
|
8070
|
+
if (symbolOrder !== 0) return symbolOrder;
|
|
8071
|
+
const canonicalOrder = left.url.localeCompare(right.url);
|
|
8072
|
+
if (canonicalOrder !== 0) return canonicalOrder;
|
|
8073
|
+
return terminologyResourceIdentity(left).localeCompare(terminologyResourceIdentity(right));
|
|
8074
|
+
});
|
|
8075
|
+
const allocated = allocateTerminologySymbols(sortedResources.map(({ resource }) => resource));
|
|
8076
|
+
const allocatedEntries = sortedResources.map(({ entry }, index) => ({
|
|
8077
|
+
entry,
|
|
8078
|
+
symbol: allocated[index]?.symbol ?? "Terminology"
|
|
8079
|
+
}));
|
|
8080
|
+
this.terminologyModules.set(packageDir, allocatedEntries);
|
|
8081
|
+
for (const { entry, symbol } of allocatedEntries) {
|
|
8082
|
+
if ("codes" in entry && !this.terminologyCodeIndex.has(entry.canonicalUrl))
|
|
8083
|
+
this.terminologyCodeIndex.set(entry.canonicalUrl, {
|
|
8084
|
+
moduleDir: packageDir,
|
|
8085
|
+
symbol,
|
|
8086
|
+
codes: new Set(entry.codes)
|
|
8087
|
+
});
|
|
8088
|
+
}
|
|
8089
|
+
}
|
|
8090
|
+
/** Enum validations whose value lists are fully explained by emitted coded
|
|
8091
|
+
* systems reference those systems' `codes` instead of inlining literals.
|
|
8092
|
+
* Returns the replacement expression per field and the value imports the
|
|
8093
|
+
* profile module needs. Only whole-system matches convert; anything else
|
|
8094
|
+
* stays an inline literal, so unlinked output is unchanged. */
|
|
8095
|
+
linkFieldEnum(tsIndex, field) {
|
|
8096
|
+
if (isChoiceDeclarationField(field)) return void 0;
|
|
8097
|
+
if (!field.enum || field.enum.values.length === 0 || !field.binding) return void 0;
|
|
8098
|
+
const binding = tsIndex.resolveByUrl(field.binding.package, field.binding.url);
|
|
8099
|
+
if (!binding) return void 0;
|
|
8100
|
+
let concepts = "concept" in binding ? binding.concept : void 0;
|
|
8101
|
+
if (!concepts) {
|
|
8102
|
+
const dependencies = "dependencies" in binding ? binding.dependencies ?? [] : [];
|
|
8103
|
+
const valueSets = dependencies.filter((dep) => dep.kind === "value-set");
|
|
8104
|
+
const valueSetId = valueSets.length === 1 ? valueSets[0] : void 0;
|
|
8105
|
+
const valueSet = valueSetId ? tsIndex.resolveByUrl(valueSetId.package, valueSetId.url) : void 0;
|
|
8106
|
+
concepts = valueSet && "concept" in valueSet ? valueSet.concept : void 0;
|
|
8107
|
+
if (!concepts && valueSetId && tsIndex.register) {
|
|
8108
|
+
concepts = extractValueSetConceptsByUrl(
|
|
8109
|
+
tsIndex.register,
|
|
8110
|
+
{ name: valueSetId.package, version: valueSetId.version },
|
|
8111
|
+
valueSetId.url,
|
|
8112
|
+
this.logger()
|
|
8113
|
+
);
|
|
8114
|
+
}
|
|
8115
|
+
}
|
|
8116
|
+
if (!concepts || concepts.length === 0) return void 0;
|
|
8117
|
+
const conceptCodes = new Set(concepts.map(({ code }) => code));
|
|
8118
|
+
const values = field.enum.values;
|
|
8119
|
+
if (values.length !== conceptCodes.size || !values.every((value) => conceptCodes.has(value))) return void 0;
|
|
8120
|
+
const bySystem = /* @__PURE__ */ new Map();
|
|
8121
|
+
for (const concept of concepts) {
|
|
8122
|
+
if (!concept.system) continue;
|
|
8123
|
+
const codes = bySystem.get(concept.system) ?? /* @__PURE__ */ new Set();
|
|
8124
|
+
codes.add(concept.code);
|
|
8125
|
+
bySystem.set(concept.system, codes);
|
|
8126
|
+
}
|
|
8127
|
+
const spreads = [];
|
|
8128
|
+
const covered = /* @__PURE__ */ new Set();
|
|
8129
|
+
const imports = /* @__PURE__ */ new Map();
|
|
8130
|
+
for (const [system, codes] of bySystem) {
|
|
8131
|
+
const indexed = this.terminologyCodeIndex.get(system);
|
|
8132
|
+
if (!indexed) continue;
|
|
8133
|
+
if (codes.size !== indexed.codes.size || ![...codes].every((code) => indexed.codes.has(code))) continue;
|
|
8134
|
+
spreads.push(`...${indexed.symbol}.codes`);
|
|
8135
|
+
for (const code of codes) covered.add(code);
|
|
8136
|
+
const symbols = imports.get(indexed.moduleDir) ?? /* @__PURE__ */ new Set();
|
|
8137
|
+
symbols.add(indexed.symbol);
|
|
8138
|
+
imports.set(indexed.moduleDir, symbols);
|
|
8139
|
+
}
|
|
8140
|
+
if (spreads.length === 0) return void 0;
|
|
8141
|
+
const literals = values.filter((value) => !covered.has(value)).map((value) => JSON.stringify(value));
|
|
8142
|
+
return { expr: `[${[...spreads, ...literals].join(", ")}]`, imports };
|
|
8143
|
+
}
|
|
8144
|
+
enumTerminologyLinks(tsIndex, snapshot) {
|
|
8145
|
+
const exprs = /* @__PURE__ */ new Map();
|
|
8146
|
+
const imports = /* @__PURE__ */ new Map();
|
|
8147
|
+
if (this.terminologyCodeIndex.size === 0) return { exprs, imports };
|
|
8148
|
+
for (const [name, field] of Object.entries(snapshot.fields)) {
|
|
8149
|
+
const link = this.linkFieldEnum(tsIndex, field);
|
|
8150
|
+
if (!link) continue;
|
|
8151
|
+
exprs.set(name, link.expr);
|
|
8152
|
+
for (const [dir, symbols] of link.imports) {
|
|
8153
|
+
const merged = imports.get(dir) ?? /* @__PURE__ */ new Set();
|
|
8154
|
+
for (const symbol of symbols) merged.add(symbol);
|
|
8155
|
+
imports.set(dir, merged);
|
|
8156
|
+
}
|
|
8157
|
+
}
|
|
8158
|
+
return { exprs, imports };
|
|
8159
|
+
}
|
|
8160
|
+
generateTerminologyModule(packageDir) {
|
|
8161
|
+
const allocatedEntries = this.terminologyModules.get(packageDir) ?? [];
|
|
8162
|
+
this.cat("terminology.ts", () => {
|
|
8163
|
+
this.generateDisclaimer();
|
|
8164
|
+
const anyCoded = allocatedEntries.some(({ entry }) => "codes" in entry);
|
|
8165
|
+
const typeImports = anyCoded ? ["TerminologyEntry", "CodedTerminologyEntry"] : ["TerminologyEntry"];
|
|
8166
|
+
this.tsImport("../terminology-types", ...typeImports, { typeOnly: true });
|
|
8167
|
+
this.line();
|
|
8168
|
+
allocatedEntries.forEach(({ entry, symbol }, index) => {
|
|
8169
|
+
const coded = "codes" in entry;
|
|
8170
|
+
const codeName = symbol.endsWith("CodeSystem") ? symbol.replace(CODE_SYSTEM_SUFFIX_RE, "Code") : `${symbol}Code`;
|
|
8171
|
+
if (coded) {
|
|
8172
|
+
const union = entry.codes.map((code) => JSON.stringify(code)).join(" | ") || "never";
|
|
8173
|
+
this.lineSM(`export type ${codeName} = ${union}`);
|
|
8174
|
+
}
|
|
8175
|
+
const satisfiesClause = coded ? ` as const satisfies CodedTerminologyEntry<${codeName}>;` : " as const satisfies TerminologyEntry;";
|
|
8176
|
+
this.curlyBlock(["export", "const", symbol, "="], () => {
|
|
8177
|
+
this.line(`canonicalUrl: ${JSON.stringify(entry.canonicalUrl)},`);
|
|
8178
|
+
this.line(`packageId: ${JSON.stringify(entry.packageId)},`);
|
|
8179
|
+
this.line(`packageVersion: ${JSON.stringify(entry.packageVersion)},`);
|
|
8180
|
+
this.line(`verification: ${JSON.stringify(entry.verification)},`);
|
|
8181
|
+
this.line(`resourceType: ${JSON.stringify(entry.resourceType)},`);
|
|
8182
|
+
if ("contentMode" in entry && entry.contentMode !== void 0)
|
|
8183
|
+
this.line(`contentMode: ${JSON.stringify(entry.contentMode)},`);
|
|
8184
|
+
if (coded) {
|
|
8185
|
+
this.line(`codes: [${entry.codes.map((code) => JSON.stringify(code)).join(", ")}],`);
|
|
8186
|
+
this.curlyBlock(["displays:"], () => {
|
|
8187
|
+
for (const code of entry.codes) {
|
|
8188
|
+
const display = entry.displays[code];
|
|
8189
|
+
if (display === void 0) continue;
|
|
8190
|
+
this.line(`${tsObjectKey(code)}: ${JSON.stringify(display)},`);
|
|
8191
|
+
}
|
|
8192
|
+
}, [","]);
|
|
8193
|
+
}
|
|
8194
|
+
}, [satisfiesClause]);
|
|
8195
|
+
if (index < allocatedEntries.length - 1) this.line();
|
|
8196
|
+
});
|
|
8197
|
+
});
|
|
8198
|
+
}
|
|
7603
8199
|
async generate(tsIndex) {
|
|
7604
8200
|
const typesToGenerate = [
|
|
7605
8201
|
...tsIndex.collectComplexTypes(),
|
|
@@ -7607,20 +8203,103 @@ var TypeScript = class extends Writer {
|
|
|
7607
8203
|
...tsIndex.collectLogicalModels(),
|
|
7608
8204
|
...this.opts.generateProfile ? tsIndex.collectSnapshotProfiles() : []
|
|
7609
8205
|
];
|
|
7610
|
-
const
|
|
8206
|
+
const terminologyPackages = this.opts.terminology?.packages;
|
|
8207
|
+
const terminology = this.opts.terminology?.enabled ? (tsIndex.register?.allTerminology() ?? []).filter(
|
|
8208
|
+
({ packageMeta: pkg, resources }) => resources.length > 0 && (terminologyPackages === void 0 || terminologyPackages.includes(packageMetaToNpm(pkg)))
|
|
8209
|
+
) : [];
|
|
8210
|
+
const logicalUnits = /* @__PURE__ */ new Map();
|
|
8211
|
+
for (const schema of typesToGenerate) {
|
|
8212
|
+
const pkg = packageMeta(schema);
|
|
8213
|
+
const identity = packageMetaToNpm(pkg);
|
|
8214
|
+
const unit = logicalUnits.get(identity) ?? { packageMeta: pkg, packageSchemas: [] };
|
|
8215
|
+
unit.packageSchemas.push(schema);
|
|
8216
|
+
logicalUnits.set(identity, unit);
|
|
8217
|
+
}
|
|
8218
|
+
for (const packageTerminology of terminology) {
|
|
8219
|
+
const identity = packageMetaToNpm(packageTerminology.packageMeta);
|
|
8220
|
+
const unit = logicalUnits.get(identity) ?? {
|
|
8221
|
+
packageMeta: packageTerminology.packageMeta,
|
|
8222
|
+
packageSchemas: []
|
|
8223
|
+
};
|
|
8224
|
+
unit.terminology = packageTerminology;
|
|
8225
|
+
logicalUnits.set(identity, unit);
|
|
8226
|
+
}
|
|
8227
|
+
const identitiesByPackageName = /* @__PURE__ */ new Map();
|
|
8228
|
+
for (const [identity, { packageMeta: pkg }] of logicalUnits) {
|
|
8229
|
+
const identities = identitiesByPackageName.get(pkg.name) ?? [];
|
|
8230
|
+
identities.push(identity);
|
|
8231
|
+
identitiesByPackageName.set(pkg.name, identities);
|
|
8232
|
+
}
|
|
8233
|
+
const unitsByBaseDir = /* @__PURE__ */ new Map();
|
|
8234
|
+
for (const [identity, { packageMeta: pkg, packageSchemas, terminology: packageTerminology }] of logicalUnits) {
|
|
8235
|
+
const directorySource = (identitiesByPackageName.get(pkg.name)?.length ?? 0) > 1 ? packageMetaToNpm(pkg) : pkg.name;
|
|
8236
|
+
const baseDir = safePackageDir(directorySource);
|
|
8237
|
+
const units = unitsByBaseDir.get(baseDir) ?? [];
|
|
8238
|
+
const schemasByIdentity = new Map(
|
|
8239
|
+
packageSchemas.map((schema) => [JSON.stringify(schema.identifier), schema])
|
|
8240
|
+
);
|
|
8241
|
+
const sortedSchemas = [...schemasByIdentity.values()].sort(
|
|
8242
|
+
(left, right) => left.identifier.name.localeCompare(right.identifier.name)
|
|
8243
|
+
);
|
|
8244
|
+
units.push({ identity, packageSchemas: sortedSchemas, terminology: packageTerminology });
|
|
8245
|
+
unitsByBaseDir.set(baseDir, units);
|
|
8246
|
+
}
|
|
8247
|
+
const generationUnits = /* @__PURE__ */ new Map();
|
|
8248
|
+
const usedPackageDirs = /* @__PURE__ */ new Set();
|
|
8249
|
+
for (const [baseDir, units] of unitsByBaseDir) {
|
|
8250
|
+
if (units.length !== 1) continue;
|
|
8251
|
+
const unit = units[0];
|
|
8252
|
+
if (!unit) continue;
|
|
8253
|
+
generationUnits.set(baseDir, unit);
|
|
8254
|
+
usedPackageDirs.add(baseDir);
|
|
8255
|
+
}
|
|
8256
|
+
for (const [baseDir, units] of [...unitsByBaseDir].sort(([left], [right]) => left.localeCompare(right))) {
|
|
8257
|
+
if (units.length < 2) continue;
|
|
8258
|
+
let suffix = 1;
|
|
8259
|
+
for (const unit of units.sort((left, right) => left.identity.localeCompare(right.identity))) {
|
|
8260
|
+
let packageDir = `${baseDir}--${suffix}`;
|
|
8261
|
+
while (usedPackageDirs.has(packageDir)) {
|
|
8262
|
+
suffix += 1;
|
|
8263
|
+
packageDir = `${baseDir}--${suffix}`;
|
|
8264
|
+
}
|
|
8265
|
+
generationUnits.set(packageDir, unit);
|
|
8266
|
+
usedPackageDirs.add(packageDir);
|
|
8267
|
+
suffix += 1;
|
|
8268
|
+
}
|
|
8269
|
+
}
|
|
8270
|
+
this.packageDirectories = new Map(
|
|
8271
|
+
[...generationUnits].flatMap(([packageDir, unit]) => {
|
|
8272
|
+
if (unit.terminology) {
|
|
8273
|
+
return [[packageMetaToNpm(unit.terminology.packageMeta), packageDir]];
|
|
8274
|
+
}
|
|
8275
|
+
const schema = unit.packageSchemas[0];
|
|
8276
|
+
return schema ? [[packageMetaToNpm(packageMeta(schema)), packageDir]] : [];
|
|
8277
|
+
})
|
|
8278
|
+
);
|
|
7611
8279
|
const hasProfiles = this.opts.generateProfile && typesToGenerate.some(isSnapshotProfileTypeSchema);
|
|
8280
|
+
this.prepareTerminology(generationUnits);
|
|
7612
8281
|
this.cd("/", () => {
|
|
7613
8282
|
if (hasProfiles) {
|
|
7614
8283
|
this.cp("profile-helpers.ts", "profile-helpers.ts");
|
|
7615
8284
|
}
|
|
7616
|
-
|
|
7617
|
-
const
|
|
8285
|
+
if (terminology.length > 0) {
|
|
8286
|
+
const codeSystemSchema = typesToGenerate.find(
|
|
8287
|
+
(schema) => schema.identifier.url === "http://hl7.org/fhir/StructureDefinition/CodeSystem"
|
|
8288
|
+
);
|
|
8289
|
+
this.generateTerminologyTypes(
|
|
8290
|
+
codeSystemSchema ? `./${this.packageDir(codeSystemSchema.identifier)}/CodeSystem` : void 0
|
|
8291
|
+
);
|
|
8292
|
+
}
|
|
8293
|
+
for (const [packageDir, { packageSchemas, terminology: terminology2 }] of [...generationUnits].sort(
|
|
8294
|
+
([left], [right]) => left.localeCompare(right)
|
|
8295
|
+
)) {
|
|
7618
8296
|
this.cd(packageDir, () => {
|
|
7619
8297
|
for (const schema of packageSchemas) {
|
|
7620
8298
|
this.generateResourceModule(tsIndex, schema);
|
|
7621
8299
|
}
|
|
7622
8300
|
generateProfileIndexFile(this, tsIndex, packageSchemas.filter(isSnapshotProfileTypeSchema));
|
|
7623
|
-
this.
|
|
8301
|
+
if (terminology2) this.generateTerminologyModule(packageDir);
|
|
8302
|
+
this.generateFhirPackageIndexFile(packageSchemas, terminology2 !== void 0);
|
|
7624
8303
|
});
|
|
7625
8304
|
}
|
|
7626
8305
|
});
|
|
@@ -7638,11 +8317,27 @@ var formatLoc = (loc) => {
|
|
|
7638
8317
|
if (loc >= 1e3) return `${(loc / 1e3).toFixed(1)} kloc`;
|
|
7639
8318
|
return `${loc} loc`;
|
|
7640
8319
|
};
|
|
8320
|
+
var formatReportEntry = (entry) => {
|
|
8321
|
+
const pkg = (p) => `${p.name}@${p.version}`;
|
|
8322
|
+
switch (entry.kind) {
|
|
8323
|
+
case "exclusion":
|
|
8324
|
+
return `excluded ${entry.url} (${pkg(entry.package)}): ${entry.reason}`;
|
|
8325
|
+
case "index-recovery":
|
|
8326
|
+
return `recovered index for ${pkg(entry.package)}: ${entry.reason}, ${entry.recovered} resources`;
|
|
8327
|
+
case "deprecation":
|
|
8328
|
+
return `deprecation: ${entry.message}`;
|
|
8329
|
+
default:
|
|
8330
|
+
return JSON.stringify(entry);
|
|
8331
|
+
}
|
|
8332
|
+
};
|
|
7641
8333
|
var prettyReport = (report, options = {}) => {
|
|
7642
|
-
const { success, filesGenerated, errors, warnings, duration } = report;
|
|
8334
|
+
const { success, filesGenerated, errors, warnings, duration, inputReport } = report;
|
|
7643
8335
|
const fileLimit = options.fileLimit ?? 20;
|
|
7644
8336
|
const errorsStr = errors.length > 0 ? `Errors: ${errors.join(", ")}` : void 0;
|
|
7645
8337
|
const warningsStr = warnings.length > 0 ? `Warnings: ${warnings.join(", ")}` : void 0;
|
|
8338
|
+
const inputFixesStr = inputReport && inputReport.length > 0 ? [`Input fixes (${inputReport.length}):`, ...inputReport.map((e) => ` - ${formatReportEntry(e)}`)].join(
|
|
8339
|
+
"\n"
|
|
8340
|
+
) : void 0;
|
|
7646
8341
|
let totalFiles = 0;
|
|
7647
8342
|
let totalLoc = 0;
|
|
7648
8343
|
const aggregateByDir = (files) => {
|
|
@@ -7681,6 +8376,7 @@ ${fileLines}`;
|
|
|
7681
8376
|
return [
|
|
7682
8377
|
`Generated files (${totalFiles} files, ${formatLoc(totalLoc)}):`,
|
|
7683
8378
|
...groupStrs,
|
|
8379
|
+
inputFixesStr,
|
|
7684
8380
|
errorsStr,
|
|
7685
8381
|
warningsStr,
|
|
7686
8382
|
`Duration: ${Math.round(duration)}ms`,
|
|
@@ -7707,40 +8403,69 @@ var APIBuilder = class {
|
|
|
7707
8403
|
const defaultOpts = {
|
|
7708
8404
|
outputDir: "./generated",
|
|
7709
8405
|
cleanOutput: true,
|
|
7710
|
-
throwException: false
|
|
7711
|
-
registry: void 0,
|
|
7712
|
-
dropCanonicalManagerCache: false
|
|
8406
|
+
throwException: false
|
|
7713
8407
|
};
|
|
7714
8408
|
const apiBuilderKeys = [
|
|
7715
8409
|
"outputDir",
|
|
7716
8410
|
"cleanOutput",
|
|
7717
8411
|
"throwException",
|
|
7718
|
-
"typeSchema"
|
|
7719
|
-
"registry",
|
|
7720
|
-
"dropCanonicalManagerCache"
|
|
8412
|
+
"typeSchema"
|
|
7721
8413
|
];
|
|
7722
8414
|
const opts = {
|
|
7723
8415
|
...defaultOpts,
|
|
7724
8416
|
...Object.fromEntries(apiBuilderKeys.filter((k) => userOpts[k] !== void 0).map((k) => [k, userOpts[k]]))
|
|
7725
8417
|
};
|
|
7726
|
-
if (userOpts.manager && userOpts.register) {
|
|
7727
|
-
throw new Error("Cannot provide both 'manager' and 'register' options. Use one or the other.");
|
|
7728
|
-
}
|
|
7729
8418
|
this.managerInput = {
|
|
7730
8419
|
npmPackages: [],
|
|
7731
8420
|
localSDs: [],
|
|
7732
8421
|
localTgzPackages: []
|
|
7733
8422
|
};
|
|
7734
8423
|
this.prebuiltRegister = userOpts.register;
|
|
7735
|
-
this.
|
|
8424
|
+
this.logger = userOpts.logger ?? mkLogger({ prefix: "api" });
|
|
8425
|
+
const isManagerInstance = (value) => typeof value.init === "function";
|
|
8426
|
+
if (userOpts.manager)
|
|
8427
|
+
this.logger.warn("'manager' is deprecated; pass the instance via 'canonicalManager' instead.");
|
|
8428
|
+
const injectedManager = userOpts.manager ?? (userOpts.canonicalManager && isManagerInstance(userOpts.canonicalManager) ? userOpts.canonicalManager : void 0);
|
|
8429
|
+
if (injectedManager && userOpts.register) {
|
|
8430
|
+
throw new Error("Cannot provide both a CanonicalManager instance and 'register'. Use one or the other.");
|
|
8431
|
+
}
|
|
8432
|
+
const cmOptions = userOpts.canonicalManager && !isManagerInstance(userOpts.canonicalManager) ? userOpts.canonicalManager : void 0;
|
|
8433
|
+
const cm = { ...cmOptions };
|
|
8434
|
+
const deprecatedCmOptions = [
|
|
8435
|
+
["registry", "registry", userOpts.registry],
|
|
8436
|
+
["packageIndex", "packageIndex", userOpts.packageIndex],
|
|
8437
|
+
["dropCache", "dropCanonicalManagerCache", userOpts.dropCanonicalManagerCache],
|
|
8438
|
+
["patches", "patches", userOpts.patches]
|
|
8439
|
+
];
|
|
8440
|
+
for (const [key, oldName, value] of deprecatedCmOptions) {
|
|
8441
|
+
if (value === void 0) continue;
|
|
8442
|
+
if (cm[key] !== void 0)
|
|
8443
|
+
throw new Error(`Cannot set both 'canonicalManager.${key}' and the deprecated '${oldName}'.`);
|
|
8444
|
+
this.logger.warn(
|
|
8445
|
+
`'${oldName}' is deprecated; use 'canonicalManager: { ${key} }' \u2014 it configures the CanonicalManager package loader.`
|
|
8446
|
+
);
|
|
8447
|
+
cm[key] = value;
|
|
8448
|
+
}
|
|
8449
|
+
this.manager = injectedManager ?? CanonicalManager({
|
|
7736
8450
|
packages: [],
|
|
7737
|
-
workingDir: ".codegen-cache/canonical-manager-cache",
|
|
7738
|
-
registry:
|
|
7739
|
-
dropCache:
|
|
8451
|
+
workingDir: cm.workingDir ?? ".codegen-cache/canonical-manager-cache",
|
|
8452
|
+
registry: cm.registry,
|
|
8453
|
+
dropCache: cm.dropCache,
|
|
8454
|
+
patches: {
|
|
8455
|
+
packageJson: cm.patches?.packageJson ?? [],
|
|
8456
|
+
indexEntry: [
|
|
8457
|
+
...userOpts.builtinPatches ?? true ? builtinPatches.indexEntry ?? [] : [],
|
|
8458
|
+
...cm.patches?.indexEntry ?? []
|
|
8459
|
+
],
|
|
8460
|
+
fhirResource: cm.patches?.fhirResource ?? []
|
|
8461
|
+
},
|
|
7740
8462
|
preprocessPackage: userOpts.preprocessPackage,
|
|
8463
|
+
packageIndex: cm.packageIndex,
|
|
7741
8464
|
ignorePackageIndex: userOpts.ignorePackageIndex
|
|
7742
8465
|
});
|
|
7743
|
-
|
|
8466
|
+
if (cmOptions && (injectedManager || userOpts.register)) {
|
|
8467
|
+
this.logger.warn("loader configuration is ignored when a prebuilt manager/`register` is provided.");
|
|
8468
|
+
}
|
|
7744
8469
|
this.options = opts;
|
|
7745
8470
|
}
|
|
7746
8471
|
fromPackage(packageName, version) {
|
|
@@ -7785,7 +8510,7 @@ var APIBuilder = class {
|
|
|
7785
8510
|
typescript(userOpts) {
|
|
7786
8511
|
const defaultWriterOpts = {
|
|
7787
8512
|
logger: this.logger,
|
|
7788
|
-
outputDir:
|
|
8513
|
+
outputDir: this.generatorOutputDir("/types"),
|
|
7789
8514
|
tabSize: 4,
|
|
7790
8515
|
withDebugComment: false,
|
|
7791
8516
|
commentLinePrefix: "//",
|
|
@@ -7800,6 +8525,7 @@ var APIBuilder = class {
|
|
|
7800
8525
|
...defaultTsOpts,
|
|
7801
8526
|
...Object.fromEntries(Object.entries(userOpts).filter(([_, v]) => v !== void 0))
|
|
7802
8527
|
};
|
|
8528
|
+
if (opts.terminology?.enabled) this.wantsTerminologyTypes = true;
|
|
7803
8529
|
const generator = new TypeScript(opts);
|
|
7804
8530
|
this.generators.push({ name: "typescript", writer: generator });
|
|
7805
8531
|
this.logger.debug(`Configured TypeScript generator (${JSON.stringify(opts, void 0, 2)})`);
|
|
@@ -7852,7 +8578,7 @@ var APIBuilder = class {
|
|
|
7852
8578
|
csharp(userOptions) {
|
|
7853
8579
|
const defaultWriterOpts = {
|
|
7854
8580
|
logger: this.logger,
|
|
7855
|
-
outputDir:
|
|
8581
|
+
outputDir: this.generatorOutputDir("/types"),
|
|
7856
8582
|
tabSize: 4,
|
|
7857
8583
|
withDebugComment: false,
|
|
7858
8584
|
commentLinePrefix: "//"
|
|
@@ -7870,12 +8596,21 @@ var APIBuilder = class {
|
|
|
7870
8596
|
this.logger.debug(`Configured C# generator`);
|
|
7871
8597
|
return this;
|
|
7872
8598
|
}
|
|
8599
|
+
/** Set by `outputTo`, to tell an explicit output directory from the default one. */
|
|
8600
|
+
explicitOutputDir;
|
|
8601
|
+
/** Output directory for a generator being configured now. `subdir` applies only when
|
|
8602
|
+
* `outputTo` never named one. */
|
|
8603
|
+
generatorOutputDir(subdir) {
|
|
8604
|
+
if (this.explicitOutputDir !== void 0) return this.explicitOutputDir;
|
|
8605
|
+
return subdir === void 0 ? this.options.outputDir : Path5.join(this.options.outputDir, subdir);
|
|
8606
|
+
}
|
|
7873
8607
|
/**
|
|
7874
|
-
* Set the output directory for all generators
|
|
8608
|
+
* Set the output directory for all generators, whenever they are configured
|
|
7875
8609
|
*/
|
|
7876
8610
|
outputTo(directory) {
|
|
7877
8611
|
this.logger.debug(`Setting output directory: ${directory}`);
|
|
7878
8612
|
this.options.outputDir = directory;
|
|
8613
|
+
this.explicitOutputDir = directory;
|
|
7879
8614
|
for (const gen of this.generators) {
|
|
7880
8615
|
gen.writer.setOutputDir(directory);
|
|
7881
8616
|
}
|
|
@@ -7889,6 +8624,10 @@ var APIBuilder = class {
|
|
|
7889
8624
|
this.options.cleanOutput = enabled;
|
|
7890
8625
|
return this;
|
|
7891
8626
|
}
|
|
8627
|
+
/** Set when a TypeScript generator wants terminology modules: the emitted
|
|
8628
|
+
* terminology types are derived from the generated CodeSystem type, so it
|
|
8629
|
+
* must survive tree shaking even when the user's rules don't ask for it. */
|
|
8630
|
+
wantsTerminologyTypes = false;
|
|
7892
8631
|
typeSchema(cfg) {
|
|
7893
8632
|
this.options.typeSchema ??= {};
|
|
7894
8633
|
if (cfg.treeShake) {
|
|
@@ -7938,7 +8677,8 @@ var APIBuilder = class {
|
|
|
7938
8677
|
};
|
|
7939
8678
|
this.logger.debug(`Starting generation with ${this.generators.length} generators`);
|
|
7940
8679
|
try {
|
|
7941
|
-
|
|
8680
|
+
const writesToDisk = this.generators.some((gen) => !gen.writer.opts.inMemoryOnly);
|
|
8681
|
+
if (this.options.cleanOutput && writesToDisk) await cleanup(this.options, this.logger);
|
|
7942
8682
|
let register;
|
|
7943
8683
|
if (this.prebuiltRegister) {
|
|
7944
8684
|
this.logger.info("Using prebuilt register");
|
|
@@ -7960,6 +8700,7 @@ var APIBuilder = class {
|
|
|
7960
8700
|
logger: this.logger.fork("reg"),
|
|
7961
8701
|
focusedPackages: packageMetas
|
|
7962
8702
|
});
|
|
8703
|
+
result.inputReport = this.manager.report();
|
|
7963
8704
|
}
|
|
7964
8705
|
const tsLogger = this.logger.fork("ts");
|
|
7965
8706
|
const { schemas: typeSchemas, collisions } = await generateTypeSchemas(
|
|
@@ -7973,12 +8714,18 @@ var APIBuilder = class {
|
|
|
7973
8714
|
};
|
|
7974
8715
|
const tsIndexOpts = { register, irReport, logger: tsLogger };
|
|
7975
8716
|
let tsIndex = mkTypeSchemaIndex(typeSchemas, tsIndexOpts);
|
|
7976
|
-
if (this.options.typeSchema?.treeShake)
|
|
7977
|
-
|
|
7978
|
-
|
|
7979
|
-
|
|
7980
|
-
|
|
7981
|
-
|
|
8717
|
+
if (this.options.typeSchema?.treeShake) {
|
|
8718
|
+
let shake = this.options.typeSchema.treeShake;
|
|
8719
|
+
if (this.wantsTerminologyTypes) {
|
|
8720
|
+
const codeSystemCanonical = "http://hl7.org/fhir/StructureDefinition/CodeSystem";
|
|
8721
|
+
const provider = tsIndex.schemas.find((schema) => schema.identifier.url === codeSystemCanonical);
|
|
8722
|
+
if (provider) {
|
|
8723
|
+
const pkg = provider.identifier.package;
|
|
8724
|
+
shake = { ...shake, [pkg]: { ...shake[pkg] ?? {}, [codeSystemCanonical]: {} } };
|
|
8725
|
+
}
|
|
8726
|
+
}
|
|
8727
|
+
tsIndex = treeShake(tsIndex, shake, this.options.typeSchema.treeShakeDefaults);
|
|
8728
|
+
}
|
|
7982
8729
|
if (this.options.typeSchema?.promoteLogical)
|
|
7983
8730
|
tsIndex = promoteLogical(tsIndex, this.options.typeSchema.promoteLogical);
|
|
7984
8731
|
tsLogger.printTagSummary();
|