@atomic-ehr/codegen 0.0.18 → 0.0.19-canary.20260921150939.55823d6

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