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