@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/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 { fileURLToPath } from 'url';
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 (/^\d/.test(localName)) {
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 resolve6 = ((id) => {
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 = resolve6(base);
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: resolve6,
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 (/^\d/.test(out)) out = `_${out}`;
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
- helpers.add("validate_fixed_value");
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 sliceRequiredFields = [];
2262
- const matchKeys = new Set(Object.keys(match));
2263
- for (const rf of slice.required ?? []) {
2264
- if (!matchKeys.has(rf)) sliceRequiredFields.push(pyFieldName(rf, formatName2));
2265
- }
2266
- if (field.type && slice.elements) {
2267
- const cc = tsIndex.constrainedChoice(field.type.package, field.type, slice.elements);
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 && s.typeDiscriminatorResource).map((s) => s.typeDiscriminatorResource)
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/typeschema/skip-hack.ts
3196
- var codeableReferenceInR4 = "Use CodeableReference which is not provided by FHIR R4.";
3197
- var availabilityInR4 = "Use Availability which is not provided by FHIR R4.";
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/typeschema/core/identifier.ts
3228
- function dropVersionFromUrl(url) {
3229
- const baseUrl = url.split("|")[0];
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/core/binding.ts
3294
- function extractValueSetConceptsByUrl(register, pkg, valueSetUrl, logger) {
3295
- const cleanUrl = dropVersionFromUrl(valueSetUrl) || valueSetUrl;
3296
- const valueSet = register.resolveVs(pkg, cleanUrl);
3297
- if (!valueSet) return void 0;
3298
- return extractValueSetConcepts(register, valueSet);
3299
- }
3300
- function extractValueSetConcepts(register, valueSet, _logger) {
3301
- if (valueSet.expansion?.contains) {
3302
- return valueSet.expansion.contains.filter((item) => item.code !== void 0).map((item) => {
3303
- assert4(item.code);
3304
- return {
3305
- code: item.code,
3306
- display: item.display,
3307
- system: item.system
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
- const concepts = [];
3312
- if (valueSet.compose?.include) {
3313
- for (const include of valueSet.compose.include) {
3314
- if (include.concept) {
3315
- for (const concept of include.concept) {
3316
- concepts.push({
3317
- system: include.system,
3318
- code: concept.code,
3319
- display: concept.display
3320
- });
3321
- }
3322
- } else if (include.system && !include.filter) {
3323
- try {
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 concepts.length > 0 ? concepts : void 0;
3346
- }
3347
- var MAX_ENUM_LENGTH = 100;
3348
- var PLACEHOLDER_ONLY_ENUM_CODES = /* @__PURE__ */ new Set(["UNK"]);
3349
- var BINDABLE_TYPES = /* @__PURE__ */ new Set([
3350
- "code",
3351
- "Coding",
3352
- "CodeableConcept",
3353
- "CodeableReference",
3354
- "Quantity",
3355
- "string",
3356
- "uri",
3357
- "Duration"
3358
- ]);
3359
- function buildEnum(register, fhirSchema, element, logger) {
3360
- if (!element.binding) return void 0;
3361
- const strength = element.binding.strength;
3362
- const valueSetUrl = element.binding.valueSet;
3363
- if (!valueSetUrl) return void 0;
3364
- if (!BINDABLE_TYPES.has(element.type ?? "")) {
3365
- logger?.dryWarn(
3366
- "#binding",
3367
- `eld-11: Binding on non-bindable type '${element.type}' (valueSet: ${valueSetUrl})`
3368
- );
3369
- return void 0;
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
- if (codes.length > MAX_ENUM_LENGTH) {
3385
- logger?.dryWarn(
3386
- "#largeValueSet",
3387
- `Value set ${valueSetUrl} has ${codes.length} which is more than ${MAX_ENUM_LENGTH} codes, which may cause issues with code generation.`
3388
- );
3389
- return void 0;
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 (codes.length === 0) return void 0;
3392
- return { isOpen: strength !== "required", values: codes };
3393
- }
3394
- function generateBindingSchema(register, fhirSchema, path, element, logger) {
3395
- if (!element.binding?.valueSet) return void 0;
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
- identifier,
3405
- valueset: valueSetIdentifier,
3406
- strength: element.binding.strength,
3407
- enum: enumResult,
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 resolveFsElementGenealogy = (genealogy, path) => {
3781
- const [top, ...rest] = path;
3782
- if (top === void 0) return [];
3783
- return genealogy.map((fs6) => {
3784
- if (!fs6.elements) return void 0;
3785
- let elem = fs6.elements?.[top];
3786
- for (const k of rest) {
3787
- elem = elem?.elements?.[k];
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
- return elem;
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 skip this canonical via skip-hack.ts, or upgrade the target to R5.`;
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 allRequired = allSliceValues.length > 0 && allSliceValues.every(
4227
- (s) => s.min !== void 0 && s.min >= 1 && s.match && typeof s.match === "object" && Object.keys(s.match).length > 0
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 (allRequired) {
4427
+ if (allRequiredWithSystem) {
4230
4428
  const codingValues = allSliceValues.flatMap((s) => s.match ? [s.match] : []);
4231
- valueConstraint = {
4232
- kind: "fixed",
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 baseFs = register.resolveFs(
4473
- fhirSchema.package_meta,
4474
- register.ensureSpecializationCanonicalUrl(fhirSchema.base)
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
- const baseId = mkIdentifier(baseFs);
4481
- assert4(!isNestedIdentifier(baseId), `Unexpected nested base for ${fhirSchema.url}`);
4482
- base = baseId;
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((resolve6, reject) => {
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) resolve6(code);
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 = [...new Set(modes.map((m) => returnTypeForMode(m, inputType, extProfileInfo?.className)))];
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 inputTypeName = tsExtensionFlatTypeName(tsProfileName, ext.name);
6211
- const extProfileHasFlatInput = extProfileInfo ? collectSubExtensionSlices(extProfileInfo.snapshot).length > 0 : false;
6212
- const inputType = extProfileHasFlatInput && extProfileInfo ? `${extProfileInfo.className}Flat` : inputTypeName;
6213
- generateExtensionGetterOverloads(w, ext, targetPath, getMethodName, inputType, extProfileInfo, () => {
6214
- const configItems = (ext.subExtensions ?? []).map((sub) => {
6215
- const valueField = sub.valueFieldType ? tsValueFieldName(sub.valueFieldType) : "value";
6216
- const isArray = sub.max === "*";
6217
- return `{ name: "${sub.url}", valueField: "${valueField}", isArray: ${isArray} }`;
6218
- });
6219
- w.line(`const config = [${configItems.join(", ")}]`);
6220
- w.line(`return extractComplexExtension<${inputType}>(ext, config)`);
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) => Object.entries(snapshot.slicing ?? {}).flatMap(([fieldName, fieldSlicing]) => {
6426
- const field = snapshot.fields[fieldName];
6427
- if (!isNotChoiceDeclarationField(field) || !fieldSlicing.slices || !field.type) return [];
6428
- const baseType = tsTypeFromIdentifier(field.type);
6429
- const pkgName = snapshot.identifier.package;
6430
- const choiceBaseNames = collectChoiceBaseNames(tsIndex, field.type);
6431
- const isTypeDisc = isTypeDiscriminated(fieldSlicing);
6432
- return Object.entries(fieldSlicing.slices).filter(([_, slice]) => Object.keys(slice.match ?? {}).length > 0).map(([sliceName, slice]) => {
6433
- const matchFields = Object.keys(slice.match ?? {});
6434
- const required = (slice.required ?? []).filter(
6435
- (name) => !matchFields.includes(name) && !choiceBaseNames.has(name)
6436
- );
6437
- const cc = slice.elements ? tsIndex.constrainedChoice(pkgName, field.type, slice.elements) : void 0;
6438
- const constrainedChoice = cc && !isPrimitiveIdentifier(cc.variantType) ? cc : void 0;
6439
- const resourceType = isTypeDisc ? extractResourceTypeFromMatch(slice.match ?? {}) : void 0;
6440
- const typedBaseType = resourceType ? `${baseType}<${resourceType}>` : baseType;
6441
- return {
6442
- fieldName,
6443
- baseType,
6444
- typedBaseType,
6445
- sliceName,
6446
- baseName: slice.nameCandidates.recommended,
6447
- match: slice.match ?? {},
6448
- required,
6449
- excluded: slice.excluded ?? [],
6450
- array: Boolean(field.array),
6451
- constrainedChoice,
6452
- typeDiscriminator: isTypeDisc,
6453
- max: slice.max ?? 0
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 collectRegularFieldValidation2 = (errors, warnings, name, field, resolveRef, canonicalUrlExpr, tsIndex, fieldSlicing) => {
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 valueExpr = canonicalUrlExpr && name === "url" && field.valueConstraint.value === canonicalUrlExpr.url ? canonicalUrlExpr.expr : JSON.stringify(field.valueConstraint.value);
6614
- errors.push(`...validateFixedValue(res, profileName, ${JSON.stringify(name)}, ${valueExpr})`);
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
- target.push(`...validateEnum(res, profileName, ${JSON.stringify(name)}, ${JSON.stringify(field.enum.values)})`);
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(field.reference.resource.map((ref) => resolveRef(ref).name))})`
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 sliceRequiredFields = [];
6638
- const matchKeys = new Set(Object.keys(match));
6639
- for (const rf of slice.required ?? []) {
6640
- if (!matchKeys.has(rf)) sliceRequiredFields.push(rf);
6641
- }
6642
- if (tsIndex && field.type && slice.elements) {
6643
- const cc = tsIndex.constrainedChoice(field.type.package, field.type, slice.elements);
6644
- if (cc) sliceRequiredFields.push(cc.variant);
6645
- }
6646
- if (sliceRequiredFields.length > 0) {
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 = mkIsFamilyType(tsIndex);
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, `export { ${className} } from "./${moduleName}"`);
7100
+ exports$1.set(className, tsProfileModuleName(tsIndex, snapshot));
6837
7101
  }
6838
7102
  }
6839
- for (const exp of [...exports$1.values()].sort()) {
6840
- w.lineSM(exp);
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 `../../${tsPackageDir(typeId.package)}/${pascalCase(path)}`;
7157
+ if (path) return `../../${w.packageDir(typeId)}/${pascalCase(path)}`;
6889
7158
  }
6890
- return `../../${tsModulePath(typeId)}`;
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 = hasParams ? `args: ${rawInputTypeName} | ${inputTypeName}` : `args?: ${rawInputTypeName} | ${inputTypeName}`;
7347
+ const createResourceSig = requiresFactoryInput ? `args: ${rawInputTypeName} | ${inputTypeName}` : `args?: ${rawInputTypeName} | ${inputTypeName}`;
7073
7348
  w.curlyBlock(["static", "createResource", `(${createResourceSig})`, `: ${tsBaseResourceName}`], () => {
7074
- w.lineSM(`const resolvedExtensions = ${profileClassName}.resolveInput(args ?? {})`);
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 = hasParams ? `args: ${rawInputTypeName} | ${inputTypeName}` : `args?: ${rawInputTypeName} | ${inputTypeName}`;
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 sub of subSlices) {
7304
- const opt = sub.isRequired ? "" : "?";
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 singleLine = `${keyword} { ${entities.join(", ")} } from "${tsPackageName}"`;
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 "${tsPackageName}";`]);
7767
+ }, [` from "${specifier}";`]);
7387
7768
  }
7388
7769
  }
7389
- generateFhirPackageIndexFile(schemas) {
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.lineSM(`export * from "./profiles"`);
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.lineSM(`export type { ${exp.typeExports.join(", ")} } from "./${exp.tsPackageName}"`);
7813
+ this.tsExport(from, ...exp.typeExports, { typeOnly: true });
7419
7814
  }
7420
7815
  if (exp.valueExports.length > 0) {
7421
- this.lineSM(`export { ${exp.valueExports.join(", ")} } from "./${exp.tsPackageName}"`);
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}${tsModulePath(dep)}`,
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}${tsModulePath(element.identifier)}`, "Element", { typeOnly: true });
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.lineSM(`export type { ${tsResourceName(dep)} } from "${`../${tsModulePath(dep)}`}"`);
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 = mkIsFamilyType(tsIndex);
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 grouped = groupByPackages(typesToGenerate);
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
- for (const [packageName, packageSchemas] of Object.entries(grouped)) {
7617
- const packageDir = tsPackageDir(packageName);
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.generateFhirPackageIndexFile(packageSchemas);
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.manager = userOpts.manager ?? CanonicalManager({
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: userOpts.registry,
7739
- dropCache: userOpts.dropCanonicalManagerCache,
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
- this.logger = userOpts.logger ?? mkLogger({ prefix: "api" });
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: Path5.join(this.options.outputDir, "/types"),
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: Path5.join(this.options.outputDir, "/types"),
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
- if (this.options.cleanOutput) await cleanup(this.options, this.logger);
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
- tsIndex = treeShake(
7978
- tsIndex,
7979
- this.options.typeSchema.treeShake,
7980
- this.options.typeSchema.treeShakeDefaults
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();