@atomic-ehr/codegen 0.0.5 → 0.0.6

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,14 +1,14 @@
1
1
  import pc from 'picocolors';
2
+ import assert3 from 'assert';
2
3
  import * as fs from 'fs';
3
- import fs__default, { existsSync } from 'fs';
4
- import * as afs2 from 'fs/promises';
5
- import { readFile } from 'fs/promises';
6
- import * as Path from 'path';
7
- import Path__default, { resolve } from 'path';
4
+ import fs__default from 'fs';
5
+ import * as Path5 from 'path';
6
+ import Path5__default from 'path';
8
7
  import { CanonicalManager } from '@atomic-ehr/fhir-canonical-manager';
9
8
  import { fileURLToPath } from 'url';
10
- import assert3 from 'assert';
9
+ import * as fsPromises from 'fs/promises';
11
10
  import * as YAML from 'yaml';
11
+ import YAML__default from 'yaml';
12
12
  import * as fhirschema from '@atomic-ehr/fhirschema';
13
13
  import { isStructureDefinition } from '@atomic-ehr/fhirschema';
14
14
  import { spawn } from 'child_process';
@@ -247,23 +247,24 @@ var FileSystemWriter = class {
247
247
  }
248
248
  cd(path, gen) {
249
249
  const prev = this.currentDir;
250
- this.currentDir = path.startsWith("/") ? Path.join(this.opts.outputDir, path) : Path.join(this.currentDir ?? this.opts.outputDir, path);
250
+ this.currentDir = path.startsWith("/") ? Path5.join(this.opts.outputDir, path) : Path5.join(this.currentDir ?? this.opts.outputDir, path);
251
251
  this.onDiskMkDir(this.currentDir);
252
252
  this.logger()?.debug(`cd '${this.currentDir}'`);
253
253
  gen();
254
254
  this.currentDir = prev;
255
255
  }
256
256
  cat(fn, gen) {
257
+ if (!this.currentDir) throw new Error("Should be in a directory (`cd`)");
257
258
  if (this.currentFile) throw new Error("Can't open file when another file is open");
258
259
  if (fn.includes("/")) throw new Error(`Change file path separatly: ${fn}`);
259
- const relPath = Path.normalize(`${this.currentDir}/${fn}`);
260
+ const relPath = Path5.normalize(`${this.currentDir}/${fn}`);
260
261
  try {
261
262
  const descriptor = this.onDiskOpenFile(relPath);
262
263
  this.logger()?.debug(`cat > '${relPath}'`);
263
264
  this.currentFile = { descriptor, relPath };
264
265
  this.writtenFilesBuffer[this.currentFile.relPath] = {
265
266
  relPath,
266
- absPath: Path.resolve(relPath),
267
+ absPath: Path5.resolve(relPath),
267
268
  tokens: []
268
269
  };
269
270
  gen();
@@ -279,14 +280,23 @@ var FileSystemWriter = class {
279
280
  if (!buf) throw new Error("No buffer found");
280
281
  buf.tokens.push(str);
281
282
  }
283
+ copyAssets(source, destination) {
284
+ const content = fs.readFileSync(source, "utf8");
285
+ this.writtenFilesBuffer[destination] = {
286
+ relPath: destination,
287
+ absPath: Path5.resolve(destination),
288
+ tokens: [content]
289
+ };
290
+ fs.cpSync(source, destination);
291
+ }
282
292
  cp(source, destination) {
283
293
  if (!this.opts.resolveAssets) throw new Error("resolveAssets is not defined");
284
- source = Path.resolve(this.opts.resolveAssets(source));
285
- destination = Path.normalize(`${this.currentDir ?? this.opts.outputDir}/${destination}`);
294
+ source = Path5.resolve(this.opts.resolveAssets(source));
295
+ destination = Path5.normalize(`${this.currentDir ?? this.opts.outputDir}/${destination}`);
286
296
  const content = fs.readFileSync(source, "utf8");
287
297
  this.writtenFilesBuffer[destination] = {
288
298
  relPath: destination,
289
- absPath: Path.resolve(destination),
299
+ absPath: Path5.resolve(destination),
290
300
  tokens: [content]
291
301
  };
292
302
  fs.cpSync(source, destination);
@@ -296,6 +306,25 @@ var FileSystemWriter = class {
296
306
  return { relPath, absPath, content: tokens.join("") };
297
307
  }).sort((a, b) => a.relPath.localeCompare(b.relPath));
298
308
  }
309
+ async flushAsync() {
310
+ const files = this.writtenFiles();
311
+ const dirs = /* @__PURE__ */ new Set();
312
+ for (const file of files) {
313
+ dirs.add(Path5.dirname(file.absPath));
314
+ }
315
+ await Promise.all(Array.from(dirs).map((dir) => fsPromises.mkdir(dir, { recursive: true })));
316
+ await Promise.all(files.map((file) => fsPromises.writeFile(file.absPath, file.content)));
317
+ }
318
+ async generateAsync(tsIndex) {
319
+ const originalInMemoryOnly = this.opts.inMemoryOnly;
320
+ this.opts.inMemoryOnly = true;
321
+ try {
322
+ await this.generate(tsIndex);
323
+ } finally {
324
+ this.opts.inMemoryOnly = originalInMemoryOnly;
325
+ }
326
+ await this.flushAsync();
327
+ }
299
328
  };
300
329
  var Writer = class extends FileSystemWriter {
301
330
  currentIndent = 0;
@@ -387,15 +416,21 @@ var extractNameFromCanonical = (canonical, dropFragment = true) => {
387
416
  }
388
417
  return localName;
389
418
  };
390
- var packageMetaToFhir = (packageMeta) => `${packageMeta.name}#${packageMeta.version}`;
391
- var packageMetaToNpm = (packageMeta) => `${packageMeta.name}@${packageMeta.version}`;
392
- var enrichFHIRSchema = (schema, packageMeta) => {
393
- if (!packageMeta) {
394
- packageMeta = { name: "undefined", version: "undefined" };
419
+ var packageMeta = (schema) => {
420
+ return {
421
+ name: schema.identifier.package,
422
+ version: schema.identifier.version
423
+ };
424
+ };
425
+ var packageMetaToFhir = (packageMeta2) => `${packageMeta2.name}#${packageMeta2.version}`;
426
+ var packageMetaToNpm = (packageMeta2) => `${packageMeta2.name}@${packageMeta2.version}`;
427
+ var enrichFHIRSchema = (schema, packageMeta2) => {
428
+ if (!packageMeta2) {
429
+ packageMeta2 = { name: "undefined", version: "undefined" };
395
430
  }
396
431
  return {
397
432
  ...schema,
398
- package_meta: schema.package_meta || packageMeta,
433
+ package_meta: schema.package_meta || packageMeta2,
399
434
  name: schema.name,
400
435
  url: schema.url,
401
436
  base: schema.base
@@ -452,12 +487,12 @@ var isChoiceInstanceField = (field) => {
452
487
  if (!field) return false;
453
488
  return field.choiceOf !== void 0;
454
489
  };
455
- var enrichValueSet = (vs, packageMeta) => {
490
+ var enrichValueSet = (vs, packageMeta2) => {
456
491
  if (!vs.url) throw new Error("ValueSet must have a URL");
457
492
  if (!vs.name) throw new Error("ValueSet must have a name");
458
493
  return {
459
494
  ...vs,
460
- package_meta: vs.package_meta || packageMeta,
495
+ package_meta: vs.package_meta || packageMeta2,
461
496
  name: vs.name,
462
497
  url: vs.url
463
498
  };
@@ -511,12 +546,12 @@ function formatName(input) {
511
546
 
512
547
  // src/api/writer-generator/csharp/csharp.ts
513
548
  var resolveCSharpAssets = (fn) => {
514
- const __filename2 = fileURLToPath(import.meta.url);
515
- const __dirname = Path__default.dirname(__filename2);
516
- if (__filename2.endsWith("dist/index.js")) {
517
- return Path__default.resolve(__dirname, "..", "assets", "api", "writer-generator", "csharp", fn);
549
+ const __filename = fileURLToPath(import.meta.url);
550
+ const __dirname = Path5__default.dirname(__filename);
551
+ if (__filename.endsWith("dist/index.js")) {
552
+ return Path5__default.resolve(__dirname, "..", "assets", "api", "writer-generator", "csharp", fn);
518
553
  } else {
519
- return Path__default.resolve(__dirname, "../../../..", "assets", "api", "writer-generator", "csharp", fn);
554
+ return Path5__default.resolve(__dirname, "../../../..", "assets", "api", "writer-generator", "csharp", fn);
520
555
  }
521
556
  };
522
557
  var PRIMITIVE_TYPE_MAP = {
@@ -787,7 +822,7 @@ var CSharp = class extends Writer {
787
822
  generateHelperFile() {
788
823
  if (this.opts.inMemoryOnly) return;
789
824
  const sourceFile = resolveCSharpAssets("Helper.cs");
790
- const destFile = Path__default.join(this.opts.outputDir, "Helper.cs");
825
+ const destFile = Path5__default.join(this.opts.outputDir, "Helper.cs");
791
826
  fs__default.copyFileSync(sourceFile, destFile);
792
827
  }
793
828
  };
@@ -862,51 +897,87 @@ var resourceRelatives = (schemas) => {
862
897
  (e) => isResourceTypeSchema(e) || isLogicalTypeSchema(e) || isComplexTypeTypeSchema(e)
863
898
  );
864
899
  const directPairs = [];
900
+ const childrenByParent = /* @__PURE__ */ new Map();
865
901
  for (const schema of regularSchemas) {
866
902
  if (schema.base) {
867
903
  directPairs.push({ parent: schema.base, child: schema.identifier });
904
+ const parentName = schema.base.name;
905
+ let children = childrenByParent.get(parentName);
906
+ if (!children) {
907
+ children = [];
908
+ childrenByParent.set(parentName, children);
909
+ }
910
+ children.push(schema.identifier);
868
911
  }
869
912
  }
870
- const allPairs = [...directPairs];
871
- const findTransitiveRelatives = (parentRef) => {
872
- const directChildren = directPairs.filter((pair) => pair.parent.name === parentRef.name).map((pair) => pair.child);
873
- const transitiveChildren = [];
913
+ const transitiveCache = /* @__PURE__ */ new Map();
914
+ const getTransitiveChildren = (parentName) => {
915
+ const cached = transitiveCache.get(parentName);
916
+ if (cached) return cached;
917
+ const directChildren = childrenByParent.get(parentName) ?? [];
918
+ const result = [...directChildren];
874
919
  for (const child of directChildren) {
875
- transitiveChildren.push(...findTransitiveRelatives(child));
920
+ result.push(...getTransitiveChildren(child.name));
876
921
  }
877
- return [...directChildren, ...transitiveChildren];
922
+ transitiveCache.set(parentName, result);
923
+ return result;
878
924
  };
925
+ const seen = /* @__PURE__ */ new Set();
926
+ const allPairs = [];
879
927
  for (const pair of directPairs) {
880
- const transitiveChildren = findTransitiveRelatives(pair.child);
881
- for (const transitiveChild of transitiveChildren) {
882
- if (!directPairs.some((dp) => dp.parent.name === pair.parent.name && dp.child.name === transitiveChild.name)) {
928
+ const key = `${pair.parent.name}|${pair.child.name}`;
929
+ seen.add(key);
930
+ allPairs.push(pair);
931
+ for (const transitiveChild of getTransitiveChildren(pair.child.name)) {
932
+ const transitiveKey = `${pair.parent.name}|${transitiveChild.name}`;
933
+ if (!seen.has(transitiveKey)) {
934
+ seen.add(transitiveKey);
883
935
  allPairs.push({ parent: pair.parent, child: transitiveChild });
884
936
  }
885
937
  }
886
938
  }
887
939
  return allPairs;
888
940
  };
889
- var mkTypeSchemaIndex = (schemas, { resolutionTree, logger }) => {
941
+ var mkTypeSchemaIndex = (schemas, {
942
+ register,
943
+ logger,
944
+ irReport = {}
945
+ }) => {
890
946
  const index = {};
947
+ const nestedIndex = {};
891
948
  const append = (schema) => {
892
949
  const url = schema.identifier.url;
893
950
  const pkg = schema.identifier.package;
894
951
  if (!index[url]) index[url] = {};
895
- if (index[url][schema.identifier.package] && pkg !== "shared") {
952
+ if (index[url][pkg] && pkg !== "shared") {
896
953
  const r1 = JSON.stringify(schema.identifier, void 0, 2);
897
954
  const r2 = JSON.stringify(index[url][pkg]?.identifier, void 0, 2);
898
955
  if (r1 !== r2) throw new Error(`Duplicate schema: ${r1} and ${r2}`);
899
956
  return;
900
957
  }
901
958
  index[url][pkg] = schema;
959
+ if (isSpecializationTypeSchema(schema) || isProfileTypeSchema(schema)) {
960
+ if (schema.nested) {
961
+ schema.nested.forEach((nschema) => {
962
+ const nurl = nschema.identifier.url;
963
+ const npkg = nschema.identifier.package;
964
+ nestedIndex[nurl] ??= {};
965
+ nestedIndex[nurl][npkg] = nschema;
966
+ });
967
+ }
968
+ }
902
969
  };
903
970
  for (const schema of schemas) {
904
971
  append(schema);
905
972
  }
906
973
  const relations = resourceRelatives(schemas);
907
- const resolve6 = (id) => index[id.url]?.[id.package];
974
+ const resolve5 = (id) => {
975
+ if (id.kind === "nested") return nestedIndex[id.url]?.[id.package];
976
+ return index[id.url]?.[id.package];
977
+ };
908
978
  const resolveByUrl = (pkgName, url) => {
909
- if (resolutionTree) {
979
+ if (register) {
980
+ const resolutionTree = register.resolutionTree();
910
981
  const resolution = resolutionTree[pkgName]?.[url]?.[0];
911
982
  if (resolution) {
912
983
  return index[url]?.[resolution.pkg.name];
@@ -924,7 +995,7 @@ var mkTypeSchemaIndex = (schemas, { resolutionTree, logger }) => {
924
995
  res.push(cur);
925
996
  const base = cur.base;
926
997
  if (base === void 0) break;
927
- const resolved = resolve6(base);
998
+ const resolved = resolve5(base);
928
999
  if (!resolved) {
929
1000
  logger?.warn(
930
1001
  `Failed to resolve base type: ${res.map((e) => `${e.identifier.url} (${e.identifier.kind})`).join(", ")}`
@@ -950,7 +1021,7 @@ var mkTypeSchemaIndex = (schemas, { resolutionTree, logger }) => {
950
1021
  return nonConstraintSchema;
951
1022
  };
952
1023
  const findLastSpecializationByIdentifier = (id) => {
953
- const schema = resolve6(id);
1024
+ const schema = resolve5(id);
954
1025
  if (!schema) return id;
955
1026
  return findLastSpecialization(schema).identifier;
956
1027
  };
@@ -980,11 +1051,21 @@ var mkTypeSchemaIndex = (schemas, { resolutionTree, logger }) => {
980
1051
  deps[e.url] = e;
981
1052
  }
982
1053
  const dependencies = Object.values(deps);
1054
+ const extensionMap = /* @__PURE__ */ new Map();
1055
+ for (const anySchema of constraintSchemas.slice().reverse()) {
1056
+ const extensions = anySchema.extensions ?? [];
1057
+ for (const ext of extensions) {
1058
+ const key = `${ext.path}|${ext.name}|${ext.url ?? ""}`;
1059
+ extensionMap.set(key, ext);
1060
+ }
1061
+ }
1062
+ const mergedExtensions = Array.from(extensionMap.values());
983
1063
  return {
984
1064
  ...schema,
985
1065
  base: nonConstraintSchema.identifier,
986
1066
  fields: mergedFields,
987
- dependencies
1067
+ dependencies,
1068
+ extensions: mergedExtensions.length > 0 ? mergedExtensions : void 0
988
1069
  };
989
1070
  };
990
1071
  const isWithMetaField = (profile) => {
@@ -994,7 +1075,7 @@ var mkTypeSchemaIndex = (schemas, { resolutionTree, logger }) => {
994
1075
  return schema.fields?.meta !== void 0;
995
1076
  });
996
1077
  };
997
- const exportTree = async (filename) => {
1078
+ const entityTree = () => {
998
1079
  const tree = {};
999
1080
  for (const [pkgId, shemas] of Object.entries(groupByPackages(schemas))) {
1000
1081
  tree[pkgId] = {
@@ -1011,18 +1092,25 @@ var mkTypeSchemaIndex = (schemas, { resolutionTree, logger }) => {
1011
1092
  tree[pkgId][schema.identifier.kind][schema.identifier.url] = {};
1012
1093
  }
1013
1094
  }
1095
+ return tree;
1096
+ };
1097
+ const exportTree = async (filename) => {
1098
+ const tree = entityTree();
1014
1099
  const raw = filename.endsWith(".yaml") ? YAML.stringify(tree) : JSON.stringify(tree, void 0, 2);
1015
- await afs2.mkdir(Path.dirname(filename), { recursive: true });
1016
- await afs2.writeFile(filename, raw);
1100
+ await fsPromises.mkdir(Path5.dirname(filename), { recursive: true });
1101
+ await fsPromises.writeFile(filename, raw);
1017
1102
  };
1018
1103
  return {
1019
1104
  _schemaIndex: index,
1020
1105
  _relations: relations,
1106
+ schemas,
1107
+ schemasByPackage: groupByPackages(schemas),
1108
+ register,
1021
1109
  collectComplexTypes: () => schemas.filter(isComplexTypeTypeSchema),
1022
1110
  collectResources: () => schemas.filter(isResourceTypeSchema),
1023
1111
  collectLogicalModels: () => schemas.filter(isLogicalTypeSchema),
1024
1112
  collectProfiles: () => schemas.filter(isProfileTypeSchema),
1025
- resolve: resolve6,
1113
+ resolve: resolve5,
1026
1114
  resolveByUrl,
1027
1115
  resourceChildren,
1028
1116
  tryHierarchy,
@@ -1031,7 +1119,10 @@ var mkTypeSchemaIndex = (schemas, { resolutionTree, logger }) => {
1031
1119
  findLastSpecializationByIdentifier,
1032
1120
  flatProfile,
1033
1121
  isWithMetaField,
1034
- exportTree
1122
+ entityTree,
1123
+ exportTree,
1124
+ irReport: () => irReport,
1125
+ replaceSchemas: (newSchemas) => mkTypeSchemaIndex(newSchemas, { register, logger, irReport: { ...irReport } })
1035
1126
  };
1036
1127
  };
1037
1128
 
@@ -1106,9 +1197,6 @@ var MAX_IMPORT_LINE_LENGTH = 100;
1106
1197
  var fixReservedWords = (name) => {
1107
1198
  return PYTHON_KEYWORDS.has(name) ? `${name}_` : name;
1108
1199
  };
1109
- var injectSuperClasses = (name) => {
1110
- return name === "Resource" || name === "Element" ? ["BaseModel"] : [];
1111
- };
1112
1200
  var canonicalToName2 = (canonical, dropFragment = true) => {
1113
1201
  if (!canonical) return void 0;
1114
1202
  let localName = canonical.split("/").pop();
@@ -1134,19 +1222,22 @@ var deriveResourceName = (id) => {
1134
1222
  return pascalCase(id.name);
1135
1223
  };
1136
1224
  var resolvePyAssets = (fn) => {
1137
- const __dirname = Path.dirname(fileURLToPath(import.meta.url));
1225
+ const __dirname = Path5.dirname(fileURLToPath(import.meta.url));
1226
+ const __filename = fileURLToPath(import.meta.url);
1138
1227
  if (__filename.endsWith("dist/index.js")) {
1139
- return Path.resolve(__dirname, "..", "assets", "api", "writer-generator", "python", fn);
1228
+ return Path5.resolve(__dirname, "..", "assets", "api", "writer-generator", "python", fn);
1140
1229
  } else {
1141
- return Path.resolve(__dirname, "../../..", "assets", "api", "writer-generator", "python", fn);
1230
+ return Path5.resolve(__dirname, "../../..", "assets", "api", "writer-generator", "python", fn);
1142
1231
  }
1143
1232
  };
1144
1233
  var Python = class extends Writer {
1145
1234
  nameFormatFunction;
1146
1235
  tsIndex;
1236
+ forFhirpyClient;
1147
1237
  constructor(options) {
1148
1238
  super({ ...options, resolveAssets: options.resolveAssets ?? resolvePyAssets });
1149
1239
  this.nameFormatFunction = this.getFieldFormatFunction(options.fieldFormat);
1240
+ this.forFhirpyClient = options.fhirpyClient ?? false;
1150
1241
  }
1151
1242
  async generate(tsIndex) {
1152
1243
  this.tsIndex = tsIndex;
@@ -1159,7 +1250,12 @@ var Python = class extends Writer {
1159
1250
  }
1160
1251
  generateRootPackages(groups) {
1161
1252
  this.generateRootInitFile(groups);
1162
- this.cp(resolvePyAssets("requirements.txt"), "requirements.txt");
1253
+ if (this.forFhirpyClient)
1254
+ this.copyAssets(
1255
+ resolvePyAssets("fhirpy_base_model.py"),
1256
+ Path5.resolve(this.opts.outputDir, "fhirpy_base_model.py")
1257
+ );
1258
+ this.copyAssets(resolvePyAssets("requirements.txt"), Path5.resolve(this.opts.outputDir, "requirements.txt"));
1163
1259
  }
1164
1260
  generateSDKPackages(groups) {
1165
1261
  this.generateComplexTypesPackages(groups.groupedComplexTypes);
@@ -1310,6 +1406,7 @@ var Python = class extends Writer {
1310
1406
  this.cat(`${snakeCase(schema.identifier.name)}.py`, () => {
1311
1407
  this.generateDisclaimer();
1312
1408
  this.generateDefaultImports();
1409
+ this.generateFhirBaseModelImport();
1313
1410
  this.line();
1314
1411
  this.generateDependenciesImports(schema);
1315
1412
  this.line();
@@ -1318,6 +1415,10 @@ var Python = class extends Writer {
1318
1415
  this.generateType(schema);
1319
1416
  });
1320
1417
  }
1418
+ generateFhirBaseModelImport() {
1419
+ if (this.forFhirpyClient)
1420
+ this.pyImportFrom(`${this.opts.rootPackageName}.fhirpy_base_model`, "FhirpyBaseModel");
1421
+ }
1321
1422
  generateType(schema) {
1322
1423
  const className = deriveResourceName(schema.identifier);
1323
1424
  const superClasses = this.getSuperClasses(schema);
@@ -1328,7 +1429,7 @@ var Python = class extends Writer {
1328
1429
  this.line();
1329
1430
  }
1330
1431
  getSuperClasses(schema) {
1331
- return [...schema.base ? [schema.base.name] : [], ...injectSuperClasses(schema.identifier.name)];
1432
+ return [...schema.base ? [schema.base.name] : [], ...this.injectSuperClasses(schema.identifier.url)];
1332
1433
  }
1333
1434
  generateClassBody(schema) {
1334
1435
  this.generateModelConfig();
@@ -1526,7 +1627,9 @@ var Python = class extends Writer {
1526
1627
  }
1527
1628
  generateFamilyDefinition(familyName, resources) {
1528
1629
  const listName = `${familyName}_resources`;
1529
- this.line(`${listName} = [${resources.map((r) => `'${r}'`).join(", ")}]`);
1630
+ this.line(
1631
+ `${listName} = [${resources.map((r) => `'${r}'`).sort().join(", ")}]`
1632
+ );
1530
1633
  this.line();
1531
1634
  this.line(`def validate_and_downcast_${familyName}(v: Any) -> Any:`);
1532
1635
  this.line(` return validate_and_downcast(v, packages, ${listName})`);
@@ -1564,7 +1667,43 @@ var Python = class extends Writer {
1564
1667
  }
1565
1668
  return AVAILABLE_STRING_FORMATS[format2];
1566
1669
  }
1670
+ injectSuperClasses(url) {
1671
+ const name = canonicalToName2(url);
1672
+ if (name === "resource") return this.forFhirpyClient ? ["FhirpyBaseModel"] : ["BaseModel"];
1673
+ if (name === "element") return ["BaseModel"];
1674
+ return [];
1675
+ }
1676
+ };
1677
+
1678
+ // src/typeschema/skip-hack.ts
1679
+ var codeableReferenceInR4 = "Use CodeableReference which is not provided by FHIR R4.";
1680
+ var availabilityInR4 = "Use Availability which is not provided by FHIR R4.";
1681
+ var skipList = {
1682
+ "hl7.fhir.uv.extensions.r4": {
1683
+ "http://hl7.org/fhir/StructureDefinition/extended-contact-availability": availabilityInR4,
1684
+ "http://hl7.org/fhir/StructureDefinition/immunization-procedure": codeableReferenceInR4,
1685
+ "http://hl7.org/fhir/StructureDefinition/specimen-additive": codeableReferenceInR4,
1686
+ "http://hl7.org/fhir/StructureDefinition/workflow-barrier": codeableReferenceInR4,
1687
+ "http://hl7.org/fhir/StructureDefinition/workflow-protectiveFactor": codeableReferenceInR4,
1688
+ "http://hl7.org/fhir/StructureDefinition/workflow-reason": codeableReferenceInR4
1689
+ },
1690
+ "hl7.fhir.r5.core#5.0.0": {
1691
+ "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.",
1692
+ "http://hl7.org/fhir/StructureDefinition/publishablecodesystem": "Uses R5-only base types not available in R4 generation."
1693
+ }
1567
1694
  };
1695
+ function shouldSkipCanonical(packageMeta2, canonicalUrl) {
1696
+ const pkgId = `${packageMeta2.name}#${packageMeta2.version}`;
1697
+ const reasonByPkgId = skipList[pkgId]?.[canonicalUrl];
1698
+ if (reasonByPkgId) {
1699
+ return { shouldSkip: true, reason: reasonByPkgId };
1700
+ }
1701
+ const reasonByName = skipList[packageMeta2.name]?.[canonicalUrl];
1702
+ if (reasonByName) {
1703
+ return { shouldSkip: true, reason: reasonByName };
1704
+ }
1705
+ return { shouldSkip: false };
1706
+ }
1568
1707
 
1569
1708
  // src/typeschema/core/identifier.ts
1570
1709
  function dropVersionFromUrl(url) {
@@ -1643,10 +1782,13 @@ function mkNestedIdentifier(register, fhirSchema, path, logger) {
1643
1782
  }
1644
1783
  const nestedName = path.join(".");
1645
1784
  const url = nestedTypeOrigins[nestedName] ?? `${fhirSchema.url}#${nestedName}`;
1785
+ const baseUrl = url.split("#")[0];
1786
+ const baseFs = register.resolveFs(fhirSchema.package_meta, baseUrl);
1787
+ const packageMeta2 = baseFs?.package_meta ?? fhirSchema.package_meta;
1646
1788
  return {
1647
1789
  kind: "nested",
1648
- package: fhirSchema.package_meta.name,
1649
- version: fhirSchema.package_meta.version,
1790
+ package: packageMeta2.name,
1791
+ version: packageMeta2.version,
1650
1792
  name: nestedName,
1651
1793
  url
1652
1794
  };
@@ -1770,6 +1912,49 @@ var buildReferences = (register, fhirSchema, element) => {
1770
1912
  return mkIdentifier(fs7);
1771
1913
  });
1772
1914
  };
1915
+ var extractSliceFieldNames = (schema) => {
1916
+ const required = /* @__PURE__ */ new Set();
1917
+ const excluded = /* @__PURE__ */ new Set();
1918
+ if (schema.required) {
1919
+ for (const name of schema.required) required.add(name);
1920
+ }
1921
+ if (schema.excluded) {
1922
+ for (const name of schema.excluded) excluded.add(name);
1923
+ }
1924
+ if (schema.elements) {
1925
+ for (const [name, element] of Object.entries(schema.elements)) {
1926
+ if (element.min !== void 0 && element.min > 0) {
1927
+ required.add(name);
1928
+ }
1929
+ }
1930
+ }
1931
+ return {
1932
+ required: required.size > 0 ? Array.from(required) : void 0,
1933
+ excluded: excluded.size > 0 ? Array.from(excluded) : void 0
1934
+ };
1935
+ };
1936
+ var buildSlicing = (element) => {
1937
+ const slicing = element.slicing;
1938
+ if (!slicing) return void 0;
1939
+ const slices = {};
1940
+ for (const [name, slice] of Object.entries(slicing.slices ?? {})) {
1941
+ if (!slice) continue;
1942
+ const { required, excluded } = slice.schema ? extractSliceFieldNames(slice.schema) : {};
1943
+ slices[name] = {
1944
+ min: slice.min,
1945
+ max: slice.max,
1946
+ match: slice.match,
1947
+ required,
1948
+ excluded
1949
+ };
1950
+ }
1951
+ return {
1952
+ discriminator: slicing.discriminator,
1953
+ rules: slicing.rules,
1954
+ ordered: slicing.ordered,
1955
+ slices: Object.keys(slices).length > 0 ? slices : void 0
1956
+ };
1957
+ };
1773
1958
  function buildFieldType(register, fhirSchema, path, element, logger) {
1774
1959
  if (element.elementReference) {
1775
1960
  const refPath = element.elementReference.slice(1).filter((_, i) => i % 2 === 1);
@@ -1779,7 +1964,7 @@ function buildFieldType(register, fhirSchema, path, element, logger) {
1779
1964
  const fieldFs = register.resolveFs(fhirSchema.package_meta, url);
1780
1965
  if (!fieldFs)
1781
1966
  throw new Error(
1782
- `Could not resolve field type: '${element.type}' (from '${fhirSchema.url}' in '${packageMetaToFhir(fhirSchema.package_meta)}')`
1967
+ `Could not resolve field type: <${fhirSchema.url}>.${path.join(".")}: <${element.type}> (pkg: '${packageMetaToFhir(fhirSchema.package_meta)}'))`
1783
1968
  );
1784
1969
  return mkIdentifier(fieldFs);
1785
1970
  } else if (element.choices) {
@@ -1788,7 +1973,7 @@ function buildFieldType(register, fhirSchema, path, element, logger) {
1788
1973
  return void 0;
1789
1974
  } else {
1790
1975
  logger?.error(
1791
- `Can't recognize element type '${fhirSchema.url}' (${fhirSchema.derivation}) at '${path.join(".")}': ${JSON.stringify(element, void 0, 2)}`
1976
+ `Can't recognize element type: <${fhirSchema.url}>.${path.join(".")} (pkg: '${packageMetaToFhir(fhirSchema.package_meta)}'): ${JSON.stringify(element, void 0, 2)}`
1792
1977
  );
1793
1978
  throw new Error(`Unrecognized element type`);
1794
1979
  }
@@ -1804,7 +1989,7 @@ var mkField = (register, fhirSchema, path, element, logger) => {
1804
1989
  }
1805
1990
  const fieldType = buildFieldType(register, fhirSchema, path, element, logger);
1806
1991
  if (!fieldType)
1807
- logger?.warn(`Field type not found for '${fhirSchema.url}#${path.join(".")}' (${fhirSchema.derivation})`);
1992
+ logger?.dry_warn(`Field type not found for '${fhirSchema.url}#${path.join(".")}' (${fhirSchema.derivation})`);
1808
1993
  return {
1809
1994
  type: fieldType,
1810
1995
  required: isRequired(register, fhirSchema, path),
@@ -1813,6 +1998,7 @@ var mkField = (register, fhirSchema, path, element, logger) => {
1813
1998
  array: element.array || false,
1814
1999
  min: element.min,
1815
2000
  max: element.max,
2001
+ slicing: buildSlicing(element),
1816
2002
  choices: element.choices,
1817
2003
  choiceOf: element.choiceOf,
1818
2004
  binding,
@@ -1831,7 +2017,8 @@ function mkNestedField(register, fhirSchema, path, element, logger) {
1831
2017
  type: nestedIdentifier,
1832
2018
  array: element.array || false,
1833
2019
  required: isRequired(register, fhirSchema, path),
1834
- excluded: isExcluded(register, fhirSchema, path)
2020
+ excluded: isExcluded(register, fhirSchema, path),
2021
+ slicing: buildSlicing(element)
1835
2022
  };
1836
2023
  }
1837
2024
 
@@ -1940,10 +2127,11 @@ function collectBindingSchemas(register, fhirSchema, logger) {
1940
2127
  for (const [key, element] of Object.entries(elements)) {
1941
2128
  const path = [...parentPath, key];
1942
2129
  const pathKey = path.join(".");
2130
+ const elemSnapshot = register.resolveElementSnapshot(fhirSchema, path);
1943
2131
  if (processedPaths.has(pathKey)) continue;
1944
2132
  processedPaths.add(pathKey);
1945
- if (element.binding) {
1946
- const binding = generateBindingSchema(register, fhirSchema, path, element, logger);
2133
+ if (elemSnapshot.binding) {
2134
+ const binding = generateBindingSchema(register, fhirSchema, path, elemSnapshot, logger);
1947
2135
  if (binding) {
1948
2136
  bindings.push(binding);
1949
2137
  }
@@ -1973,6 +2161,13 @@ function mkFields(register, fhirSchema, parentPath, elements, logger) {
1973
2161
  for (const key of register.getAllElementKeys(elements)) {
1974
2162
  const path = [...parentPath, key];
1975
2163
  const elemSnapshot = register.resolveElementSnapshot(fhirSchema, path);
2164
+ const fcurl = elemSnapshot.type ? register.ensureSpecializationCanonicalUrl(elemSnapshot.type) : void 0;
2165
+ if (fcurl && shouldSkipCanonical(fhirSchema.package_meta, fcurl).shouldSkip) {
2166
+ logger?.warn(
2167
+ `Skipping field ${path} for ${fcurl} due to skip hack ${shouldSkipCanonical(fhirSchema.package_meta, fcurl).reason}`
2168
+ );
2169
+ continue;
2170
+ }
1976
2171
  if (isNestedElement(elemSnapshot)) {
1977
2172
  fields[key] = mkNestedField(register, fhirSchema, path, elemSnapshot, logger);
1978
2173
  } else {
@@ -2054,18 +2249,143 @@ function transformFhirSchemaResource(register, fhirSchema, logger) {
2054
2249
  }
2055
2250
  const fields = mkFields(register, fhirSchema, [], fhirSchema.elements, logger);
2056
2251
  const nested = mkNestedTypes(register, fhirSchema, logger);
2252
+ const extensions = fhirSchema.derivation === "constraint" ? extractProfileExtensions(register, fhirSchema, logger) : void 0;
2057
2253
  const dependencies = extractDependencies(identifier, base, fields, nested);
2254
+ const extensionDeps = extensions?.flatMap((ext) => ext.valueTypes ?? []) ?? [];
2255
+ const mergedDeps = (() => {
2256
+ if (!dependencies && extensionDeps.length === 0) return dependencies;
2257
+ const depMap = {};
2258
+ for (const dep of dependencies ?? []) depMap[dep.url] = dep;
2259
+ for (const dep of extensionDeps) depMap[dep.url] = dep;
2260
+ return Object.values(depMap);
2261
+ })();
2058
2262
  const typeSchema = {
2059
2263
  identifier,
2060
2264
  base,
2061
2265
  fields,
2062
2266
  nested,
2063
2267
  description: fhirSchema.description,
2064
- dependencies
2268
+ dependencies: mergedDeps,
2269
+ ...extensions && extensions.length > 0 ? { extensions } : {}
2065
2270
  };
2066
2271
  const bindingSchemas = collectBindingSchemas(register, fhirSchema, logger);
2067
2272
  return [typeSchema, ...bindingSchemas];
2068
2273
  }
2274
+ function extractExtensionValueTypes(register, fhirSchema, extensionUrl, logger) {
2275
+ const extensionSchema = register.resolveFs(fhirSchema.package_meta, extensionUrl);
2276
+ if (!extensionSchema?.elements) return void 0;
2277
+ const valueTypes = [];
2278
+ for (const [key, element] of Object.entries(extensionSchema.elements)) {
2279
+ if (element.choiceOf !== "value" && !key.startsWith("value")) continue;
2280
+ const fieldType = buildFieldType(register, extensionSchema, [key], element, logger);
2281
+ if (fieldType) valueTypes.push(fieldType);
2282
+ }
2283
+ if (valueTypes.length === 0) return void 0;
2284
+ const uniq = new Map(valueTypes.map((type) => [type.url, type]));
2285
+ return Array.from(uniq.values());
2286
+ }
2287
+ function extractSubExtensions(register, fhirSchema, extensionUrl, logger) {
2288
+ const extensionSchema = register.resolveFs(fhirSchema.package_meta, extensionUrl);
2289
+ if (!extensionSchema?.elements) return void 0;
2290
+ const subExtensions = [];
2291
+ for (const [key, element] of Object.entries(extensionSchema.elements)) {
2292
+ if (!key.startsWith("extension:")) continue;
2293
+ const sliceName = key.split(":")[1];
2294
+ if (!sliceName) continue;
2295
+ const sliceUrl = element.url ?? sliceName;
2296
+ let valueType;
2297
+ for (const [elemKey, elemValue] of Object.entries(element.elements ?? {})) {
2298
+ if (elemValue.choiceOf !== "value" && !elemKey.startsWith("value")) continue;
2299
+ valueType = buildFieldType(register, extensionSchema, [key, elemKey], elemValue, logger);
2300
+ if (valueType) break;
2301
+ }
2302
+ subExtensions.push({
2303
+ name: sliceName,
2304
+ url: sliceUrl,
2305
+ valueType,
2306
+ min: element.min,
2307
+ max: element.max !== void 0 ? String(element.max) : void 0
2308
+ });
2309
+ }
2310
+ const extensionElement = extensionSchema.elements.extension;
2311
+ const slices = extensionElement?.slicing?.slices;
2312
+ if (slices && typeof slices === "object") {
2313
+ for (const [sliceName, sliceData] of Object.entries(slices)) {
2314
+ const slice = sliceData;
2315
+ const schema = slice.schema;
2316
+ if (!schema) continue;
2317
+ const sliceUrl = slice.match?.url ?? sliceName;
2318
+ let valueType;
2319
+ const schemaElements = schema.elements ?? {};
2320
+ for (const [elemKey, elemValue] of Object.entries(schemaElements)) {
2321
+ const elem = elemValue;
2322
+ if (elem.choiceOf !== "value" && !elemKey.startsWith("value")) continue;
2323
+ if (elem.type) {
2324
+ valueType = {
2325
+ kind: "complex-type",
2326
+ package: extensionSchema.package_meta.name,
2327
+ version: extensionSchema.package_meta.version,
2328
+ name: elem.type,
2329
+ url: `http://hl7.org/fhir/StructureDefinition/${elem.type}`
2330
+ };
2331
+ break;
2332
+ }
2333
+ }
2334
+ subExtensions.push({
2335
+ name: sliceName,
2336
+ url: sliceUrl,
2337
+ valueType,
2338
+ min: schema._required ? 1 : schema.min ?? 0,
2339
+ // biome-ignore lint/style/noNestedTernary : okay here
2340
+ max: schema.max !== void 0 ? String(schema.max) : schema.array ? "*" : "1"
2341
+ });
2342
+ }
2343
+ }
2344
+ return subExtensions.length > 0 ? subExtensions : void 0;
2345
+ }
2346
+ function extractProfileExtensions(register, fhirSchema, logger) {
2347
+ const extensions = [];
2348
+ const addExtensionEntry = (path, name, schema) => {
2349
+ const url = schema.url;
2350
+ const valueTypes = url ? extractExtensionValueTypes(register, fhirSchema, url, logger) : void 0;
2351
+ const subExtensions = url ? extractSubExtensions(register, fhirSchema, url, logger) : void 0;
2352
+ const isComplex = subExtensions !== void 0 && subExtensions.length > 0;
2353
+ extensions.push({
2354
+ name,
2355
+ path: [...path, "extension"].join("."),
2356
+ url,
2357
+ min: schema.min,
2358
+ max: schema.max !== void 0 ? String(schema.max) : void 0,
2359
+ mustSupport: schema.mustSupport,
2360
+ valueTypes,
2361
+ subExtensions,
2362
+ isComplex
2363
+ });
2364
+ };
2365
+ const walkElement = (path, element) => {
2366
+ if (element.extensions) {
2367
+ for (const [name, schema] of Object.entries(element.extensions)) {
2368
+ addExtensionEntry(path, name, schema);
2369
+ }
2370
+ }
2371
+ if (element.elements) {
2372
+ for (const [key, child] of Object.entries(element.elements)) {
2373
+ walkElement([...path, key], child);
2374
+ }
2375
+ }
2376
+ };
2377
+ if (fhirSchema.extensions) {
2378
+ for (const [name, schema] of Object.entries(fhirSchema.extensions)) {
2379
+ addExtensionEntry([], name, schema);
2380
+ }
2381
+ }
2382
+ if (fhirSchema.elements) {
2383
+ for (const [key, element] of Object.entries(fhirSchema.elements)) {
2384
+ walkElement([key], element);
2385
+ }
2386
+ }
2387
+ return extensions;
2388
+ }
2069
2389
  async function transformFhirSchema(register, fhirSchema, logger) {
2070
2390
  const schemas = transformFhirSchemaResource(register, fhirSchema, logger);
2071
2391
  if (isExtensionSchema(fhirSchema, mkIdentifier(fhirSchema))) {
@@ -2090,8 +2410,8 @@ var isValueSet = (resource) => {
2090
2410
  };
2091
2411
 
2092
2412
  // src/typeschema/register.ts
2093
- var readPackageDependencies = async (manager, packageMeta) => {
2094
- const packageJSON = await manager.packageJson(packageMeta.name);
2413
+ var readPackageDependencies = async (manager, packageMeta2) => {
2414
+ const packageJSON = await manager.packageJson(packageMeta2.name);
2095
2415
  const dependencies = packageJSON.dependencies;
2096
2416
  if (dependencies !== void 0) {
2097
2417
  return Object.entries(dependencies).map(([name, version]) => {
@@ -2135,33 +2455,18 @@ var mkPackageAwareResolver = async (manager, pkg, deep, acc, logger) => {
2135
2455
  acc[pkgId] = index;
2136
2456
  return index;
2137
2457
  };
2138
- var packageAgnosticResolveCanonical = (resolver, url, _logger) => {
2139
- const options = Object.values(resolver).flatMap((pkg) => pkg.canonicalResolution[url]);
2140
- if (!options) throw new Error(`No canonical resolution found for ${url} in any package`);
2141
- return options[0]?.resource;
2142
- };
2143
- var registerFromManager = async (manager, { logger, fallbackPackageForNameResolution, focusedPackages }) => {
2144
- const packages = focusedPackages ?? await manager.packages();
2145
- const resolver = {};
2146
- for (const pkg of packages) {
2147
- await mkPackageAwareResolver(manager, pkg, 0, resolver, logger);
2148
- }
2458
+ var enrichResolver = (resolver, logger) => {
2149
2459
  for (const { pkg, canonicalResolution } of Object.values(resolver)) {
2150
2460
  const pkgId = packageMetaToFhir(pkg);
2151
2461
  if (!resolver[pkgId]) throw new Error(`Package ${pkgId} not found`);
2152
- let counter = 0;
2153
- logger?.info(`FHIR Schema conversion for '${packageMetaToFhir(pkg)}' begins...`);
2154
2462
  for (const [_url, options] of Object.entries(canonicalResolution)) {
2155
2463
  const resolition = options[0];
2156
2464
  if (!resolition) throw new Error(`Resource not found`);
2157
2465
  const resource = resolition.resource;
2158
2466
  const resourcePkg = resolition.pkg;
2159
2467
  if (isStructureDefinition(resource)) {
2160
- const rfs = enrichFHIRSchema(
2161
- fhirschema.translate(resource),
2162
- resourcePkg
2163
- );
2164
- counter++;
2468
+ const fs7 = fhirschema.translate(resource);
2469
+ const rfs = enrichFHIRSchema(fs7, resourcePkg);
2165
2470
  resolver[pkgId].fhirSchemas[rfs.url] = rfs;
2166
2471
  }
2167
2472
  if (isValueSet(resource)) {
@@ -2169,15 +2474,33 @@ var registerFromManager = async (manager, { logger, fallbackPackageForNameResolu
2169
2474
  resolver[pkgId].valueSets[rvs.url] = rvs;
2170
2475
  }
2171
2476
  }
2172
- logger?.info(`FHIR Schema conversion for '${packageMetaToFhir(pkg)}' completed: ${counter} successful`);
2173
2477
  }
2478
+ };
2479
+ var packageAgnosticResolveCanonical = (resolver, url, _logger) => {
2480
+ const options = Object.values(resolver).flatMap((pkg) => pkg.canonicalResolution[url]);
2481
+ if (!options) throw new Error(`No canonical resolution found for ${url} in any package`);
2482
+ return options[0]?.resource;
2483
+ };
2484
+ var registerFromManager = async (manager, { logger, fallbackPackageForNameResolution, focusedPackages }) => {
2485
+ const packages = focusedPackages ?? await manager.packages();
2486
+ const resolver = {};
2487
+ for (const pkg of packages) {
2488
+ await mkPackageAwareResolver(manager, pkg, 0, resolver, logger);
2489
+ }
2490
+ enrichResolver(resolver);
2174
2491
  const resolveFs = (pkg, canonicalUrl) => {
2175
2492
  return resolver[packageMetaToFhir(pkg)]?.fhirSchemas[canonicalUrl] || fallbackPackageForNameResolution && resolver[packageMetaToFhir(fallbackPackageForNameResolution)]?.fhirSchemas[canonicalUrl];
2176
2493
  };
2177
2494
  const resolveVs = (pkg, canonicalUrl) => {
2178
2495
  return resolver[packageMetaToFhir(pkg)]?.valueSets[canonicalUrl] || fallbackPackageForNameResolution && resolver[packageMetaToFhir(fallbackPackageForNameResolution)]?.valueSets[canonicalUrl];
2179
2496
  };
2180
- const ensureSpecializationCanonicalUrl = (name) => name.match(/^[a-zA-Z0-9]+$/) && `http://hl7.org/fhir/StructureDefinition/${name}` || name;
2497
+ const ensureSpecializationCanonicalUrl = (name) => {
2498
+ if (name.includes("|")) name = name.split("|")[0];
2499
+ if (name.match(/^[a-zA-Z0-9]+$/)) {
2500
+ return `http://hl7.org/fhir/StructureDefinition/${name}`;
2501
+ }
2502
+ return name;
2503
+ };
2181
2504
  const resolveFsGenealogy = (pkg, canonicalUrl) => {
2182
2505
  let fs7 = resolveFs(pkg, canonicalUrl);
2183
2506
  if (fs7 === void 0) throw new Error(`Failed to resolve FHIR Schema: '${canonicalUrl}'`);
@@ -2215,23 +2538,56 @@ var registerFromManager = async (manager, { logger, fallbackPackageForNameResolu
2215
2538
  }
2216
2539
  return Array.from(keys);
2217
2540
  };
2541
+ let cachedResolutionTree;
2218
2542
  return {
2219
- ...manager,
2220
2543
  testAppendFs(fs7) {
2221
2544
  const rfs = enrichFHIRSchema(fs7);
2222
2545
  const pkgId = packageMetaToFhir(rfs.package_meta);
2223
2546
  if (!resolver[pkgId]) resolver[pkgId] = mkEmptyPkgIndex(rfs.package_meta);
2224
2547
  resolver[pkgId].fhirSchemas[rfs.url] = rfs;
2548
+ cachedResolutionTree = void 0;
2225
2549
  },
2226
2550
  resolveFs,
2227
2551
  resolveFsGenealogy,
2228
2552
  resolveFsSpecializations,
2229
2553
  ensureSpecializationCanonicalUrl,
2230
- resolveSd: (_pkg, canonicalUrl) => {
2231
- const res = packageAgnosticResolveCanonical(resolver, canonicalUrl);
2554
+ resolveSd: (pkg, canonicalUrl) => {
2555
+ const res = resolver[packageMetaToFhir(pkg)]?.canonicalResolution[canonicalUrl]?.[0]?.resource;
2232
2556
  if (isStructureDefinition(res)) return res;
2233
2557
  return void 0;
2234
2558
  },
2559
+ allSd: () => Object.values(resolver).flatMap(
2560
+ (pkgIndex) => Object.values(pkgIndex.canonicalResolution).flatMap(
2561
+ (resolutions) => resolutions.map((r) => {
2562
+ const sd = r.resource;
2563
+ if (!sd.package_name) {
2564
+ return {
2565
+ ...sd,
2566
+ package_name: pkgIndex.pkg.name,
2567
+ package_version: pkgIndex.pkg.version
2568
+ };
2569
+ }
2570
+ return sd;
2571
+ })
2572
+ )
2573
+ ).filter((r) => isStructureDefinition(r)).sort((sd1, sd2) => sd1.url.localeCompare(sd2.url)),
2574
+ patchSd: (fn) => {
2575
+ Object.values(resolver).flatMap(
2576
+ (pkgIndex) => Object.values(pkgIndex.canonicalResolution).forEach((resolutions) => {
2577
+ resolutions.forEach((e) => {
2578
+ if (isStructureDefinition(e.resource)) {
2579
+ const sd = e.resource;
2580
+ const newSd = fn(pkgIndex.pkg, sd);
2581
+ if (sd.url !== newSd.url)
2582
+ throw new Error(`Patch update StructureDefinition URL: ${sd.url} !== ${newSd.url}`);
2583
+ e.resource = newSd;
2584
+ }
2585
+ });
2586
+ })
2587
+ );
2588
+ enrichResolver(resolver);
2589
+ cachedResolutionTree = void 0;
2590
+ },
2235
2591
  allFs: () => Object.values(resolver).flatMap((pkgIndex) => Object.values(pkgIndex.fhirSchemas)),
2236
2592
  allVs: () => Object.values(resolver).flatMap((pkgIndex) => Object.values(pkgIndex.valueSets)),
2237
2593
  resolveVs,
@@ -2240,6 +2596,7 @@ var registerFromManager = async (manager, { logger, fallbackPackageForNameResolu
2240
2596
  getAllElementKeys,
2241
2597
  resolver,
2242
2598
  resolutionTree: () => {
2599
+ if (cachedResolutionTree) return cachedResolutionTree;
2243
2600
  const res = {};
2244
2601
  for (const [_pkgId, pkgIndex] of Object.entries(resolver)) {
2245
2602
  const pkgName = pkgIndex.pkg.name;
@@ -2252,6 +2609,7 @@ var registerFromManager = async (manager, { logger, fallbackPackageForNameResolu
2252
2609
  }
2253
2610
  }
2254
2611
  }
2612
+ cachedResolutionTree = res;
2255
2613
  return res;
2256
2614
  }
2257
2615
  };
@@ -2276,27 +2634,13 @@ function fsElementSnapshot(genealogy) {
2276
2634
  }
2277
2635
 
2278
2636
  // src/typeschema/index.ts
2279
- var codeableReferenceInR4 = "Use CodeableReference which is not provided by FHIR R4.";
2280
- var availabilityInR4 = "Use Availability which is not provided by FHIR R4.";
2281
- var skipMe = {
2282
- "hl7.fhir.uv.extensions.r4#1.0.0": {
2283
- "http://hl7.org/fhir/StructureDefinition/extended-contact-availability": availabilityInR4,
2284
- "http://hl7.org/fhir/StructureDefinition/immunization-procedure": codeableReferenceInR4,
2285
- "http://hl7.org/fhir/StructureDefinition/specimen-additive": codeableReferenceInR4,
2286
- "http://hl7.org/fhir/StructureDefinition/workflow-barrier": codeableReferenceInR4,
2287
- "http://hl7.org/fhir/StructureDefinition/workflow-protectiveFactor": codeableReferenceInR4,
2288
- "http://hl7.org/fhir/StructureDefinition/workflow-reason": codeableReferenceInR4
2289
- },
2290
- "hl7.fhir.r5.core#5.0.0": {
2291
- "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."
2292
- }
2293
- };
2294
2637
  var generateTypeSchemas = async (register, logger) => {
2295
2638
  const fhirSchemas = [];
2296
2639
  for (const fhirSchema of register.allFs()) {
2297
2640
  const pkgId = packageMetaToFhir(fhirSchema.package_meta);
2298
- if (skipMe[pkgId]?.[fhirSchema.url]) {
2299
- logger?.dry_warn(`Skip ${fhirSchema.url} from ${pkgId}. Reason: ${skipMe[pkgId]?.[fhirSchema.url]}`);
2641
+ const skipCheck = shouldSkipCanonical(fhirSchema.package_meta, fhirSchema.url);
2642
+ if (skipCheck.shouldSkip) {
2643
+ logger?.dry_warn(`Skip ${fhirSchema.url} from ${pkgId}. Reason: ${skipCheck.reason}`);
2300
2644
  continue;
2301
2645
  }
2302
2646
  fhirSchemas.push(...await transformFhirSchema(register, fhirSchema, logger));
@@ -2306,6 +2650,57 @@ var generateTypeSchemas = async (register, logger) => {
2306
2650
  }
2307
2651
  return fhirSchemas;
2308
2652
  };
2653
+
2654
+ // src/typeschema/ir/logic-promotion.ts
2655
+ var promoteLogical = (tsIndex, promotes) => {
2656
+ const promoteSets = Object.fromEntries(
2657
+ Object.entries(promotes).map(([pkg, urls]) => [pkg, new Set(urls)])
2658
+ );
2659
+ const identifierToString = (i) => `${i.package}-${i.version}-${i.kind}-${i.url}`;
2660
+ const renames = Object.fromEntries(
2661
+ tsIndex.schemas.map((schema) => {
2662
+ const promo = promoteSets[schema.identifier.package]?.has(schema.identifier.url);
2663
+ if (!promo) return void 0;
2664
+ if (schema.identifier.kind !== "logical")
2665
+ throw new Error(`Unexpected schema kind: ${JSON.stringify(schema.identifier)}`);
2666
+ return [identifierToString(schema.identifier), { ...schema.identifier, kind: "resource" }];
2667
+ }).filter((e) => e !== void 0)
2668
+ );
2669
+ const replace = (i) => renames[identifierToString(i)] || i;
2670
+ const replaceInFields = (fields) => {
2671
+ if (!fields) return void 0;
2672
+ return Object.fromEntries(
2673
+ Object.entries(fields).map(([k, f]) => {
2674
+ if (isChoiceDeclarationField(f)) return [k, f];
2675
+ return [k, { ...f, type: f.type ? replace(f.type) : void 0 }];
2676
+ })
2677
+ );
2678
+ };
2679
+ const schemas = tsIndex.schemas.map((schema) => {
2680
+ if (isPrimitiveTypeSchema(schema) || isValueSetTypeSchema(schema)) return schema;
2681
+ const cloned = JSON.parse(JSON.stringify(schema));
2682
+ cloned.identifier = replace(cloned.identifier);
2683
+ cloned.dependencies = cloned.dependencies?.map(replace);
2684
+ if (isSpecializationTypeSchema(cloned) || isProfileTypeSchema(cloned)) {
2685
+ cloned.fields = replaceInFields(cloned.fields);
2686
+ cloned.nested = cloned.nested?.map((n) => {
2687
+ return {
2688
+ ...n,
2689
+ base: replace(n.base),
2690
+ nested: replaceInFields(n.fields)
2691
+ };
2692
+ });
2693
+ }
2694
+ return cloned;
2695
+ });
2696
+ const promotedIndex = tsIndex.replaceSchemas(schemas);
2697
+ promotedIndex.irReport().logicalPromotion = {
2698
+ packages: Object.fromEntries(
2699
+ Object.entries(promotes).map(([pkgName, urls]) => [pkgName, { promotedCanonicals: [...urls].sort() }])
2700
+ )
2701
+ };
2702
+ return promotedIndex;
2703
+ };
2309
2704
  var mutableSelectFields = (schema, selectFields) => {
2310
2705
  const selectedFields = {};
2311
2706
  const selectPolimorphic = {};
@@ -2359,8 +2754,38 @@ var mutableIgnoreFields = (schema, ignoreFields) => {
2359
2754
  }
2360
2755
  }
2361
2756
  };
2757
+ var mutableFillReport = (report, tsIndex, shakedIndex) => {
2758
+ const packages = Object.keys(tsIndex.schemasByPackage);
2759
+ const shakedPackages = Object.keys(shakedIndex.schemasByPackage);
2760
+ const skippedPackages = packages.filter((pkg) => !shakedPackages.includes(pkg));
2761
+ report.skippedPackages = skippedPackages;
2762
+ for (const [pkgName, shakedSchemas] of Object.entries(shakedIndex.schemasByPackage)) {
2763
+ if (skippedPackages.includes(pkgName)) continue;
2764
+ const tsSchemas = tsIndex.schemasByPackage[pkgName];
2765
+ assert3(tsSchemas);
2766
+ report.packages[pkgName] = {
2767
+ skippedCanonicals: tsSchemas.filter((schema) => !shakedSchemas.includes(schema)).map((schema) => schema.identifier.url).sort(),
2768
+ canonicals: Object.fromEntries(
2769
+ shakedSchemas.map((shakedSchema) => {
2770
+ const schema = tsIndex.resolve(shakedSchema.identifier);
2771
+ assert3(schema);
2772
+ if (!isSpecializationTypeSchema(schema)) return void 0;
2773
+ assert3(isSpecializationTypeSchema(shakedSchema));
2774
+ if (!schema.fields) return void 0;
2775
+ if (!shakedSchema.fields) {
2776
+ return [shakedSchema.identifier.url, Object.keys(schema.fields)];
2777
+ }
2778
+ const shakedFieldNames = Object.keys(shakedSchema.fields);
2779
+ const skippedFields = Object.keys(schema.fields).filter((field) => !shakedFieldNames.includes(field)).sort();
2780
+ if (skippedFields.length === 0) return void 0;
2781
+ return [shakedSchema.identifier.url, { skippedFields }];
2782
+ }).filter((e) => e !== void 0)
2783
+ )
2784
+ };
2785
+ }
2786
+ };
2362
2787
  var treeShakeTypeSchema = (schema, rule, _logger) => {
2363
- schema = structuredClone(schema);
2788
+ schema = JSON.parse(JSON.stringify(schema));
2364
2789
  if (isPrimitiveTypeSchema(schema) || isValueSetTypeSchema(schema) || isBindingSchema(schema)) return schema;
2365
2790
  if (rule.selectFields) {
2366
2791
  if (rule.ignoreFields) throw new Error("Cannot use both ignoreFields and selectFields in the same rule");
@@ -2389,7 +2814,7 @@ var treeShakeTypeSchema = (schema, rule, _logger) => {
2389
2814
  schema.dependencies = extractDependencies(schema.identifier, schema.base, schema.fields, schema.nested);
2390
2815
  return schema;
2391
2816
  };
2392
- var treeShake = (tsIndex, treeShake2, { resolutionTree, logger }) => {
2817
+ var treeShake = (tsIndex, treeShake2) => {
2393
2818
  const focusedSchemas = [];
2394
2819
  for (const [pkgId, requires] of Object.entries(treeShake2)) {
2395
2820
  for (const [url, rule] of Object.entries(requires)) {
@@ -2406,7 +2831,7 @@ var treeShake = (tsIndex, treeShake2, { resolutionTree, logger }) => {
2406
2831
  }
2407
2832
  const newSchemas = [];
2408
2833
  for (const schema of schemas) {
2409
- if (isSpecializationTypeSchema(schema)) {
2834
+ if (isSpecializationTypeSchema(schema) || isProfileTypeSchema(schema)) {
2410
2835
  if (!schema.dependencies) continue;
2411
2836
  schema.dependencies.forEach((dep) => {
2412
2837
  const depSchema = tsIndex.resolve(dep);
@@ -2429,7 +2854,202 @@ var treeShake = (tsIndex, treeShake2, { resolutionTree, logger }) => {
2429
2854
  return collectDeps(newSchemas, acc);
2430
2855
  };
2431
2856
  const shaked = collectDeps(focusedSchemas, {});
2432
- return mkTypeSchemaIndex(shaked, { resolutionTree, logger });
2857
+ const shakedIndex = tsIndex.replaceSchemas(shaked);
2858
+ const treeShakeReport = { skippedPackages: [], packages: {} };
2859
+ const irReport = shakedIndex.irReport();
2860
+ irReport.treeShake = treeShakeReport;
2861
+ mutableFillReport(treeShakeReport, tsIndex, shakedIndex);
2862
+ return shakedIndex;
2863
+ };
2864
+ var normalizeFileName = (str) => {
2865
+ const res = str.replace(/[^a-zA-Z0-9\-_.@#()]/g, "");
2866
+ if (res.length === 0) return "unknown";
2867
+ return res;
2868
+ };
2869
+ var typeSchemaToJson = (ts, pretty) => {
2870
+ const pkgPath = normalizeFileName(ts.identifier.package);
2871
+ const name = normalizeFileName(`${ts.identifier.name}(${extractNameFromCanonical(ts.identifier.url)})`);
2872
+ const baseName = Path5.join(pkgPath, name);
2873
+ return {
2874
+ filename: baseName,
2875
+ genContent: () => JSON.stringify(ts, null, pretty ? 2 : void 0)
2876
+ };
2877
+ };
2878
+ var fhirSchemaToJson = (fs7, pretty) => {
2879
+ const pkgPath = normalizeFileName(fs7.package_meta.name);
2880
+ const name = normalizeFileName(`${fs7.name}(${extractNameFromCanonical(fs7.url)})`);
2881
+ const baseName = Path5.join(pkgPath, name);
2882
+ return {
2883
+ filename: baseName,
2884
+ genContent: () => JSON.stringify(fs7, null, pretty ? 2 : void 0)
2885
+ };
2886
+ };
2887
+ var structureDefinitionToJson = (sd, pretty) => {
2888
+ const pkgPath = normalizeFileName(sd.package_name ?? "unknown");
2889
+ const name = normalizeFileName(`${sd.name}(${extractNameFromCanonical(sd.url)})`);
2890
+ const baseName = Path5.join(pkgPath, name);
2891
+ return {
2892
+ filename: baseName,
2893
+ // HACK: for some reason ID may change between CI and local install
2894
+ genContent: () => JSON.stringify({ ...sd, id: void 0 }, null, pretty ? 2 : void 0)
2895
+ };
2896
+ };
2897
+ var IntrospectionWriter = class extends FileSystemWriter {
2898
+ async generate(tsIndex) {
2899
+ this.logger()?.info(`IntrospectionWriter: Begin`);
2900
+ if (this.opts.typeTree) {
2901
+ await this.writeTypeTree(tsIndex);
2902
+ this.logger()?.info(`IntrospectionWriter: Type tree written to ${this.opts.typeTree}`);
2903
+ }
2904
+ if (this.opts.typeSchemas) {
2905
+ const outputPath = this.opts.typeSchemas;
2906
+ const typeSchemas = tsIndex.schemas;
2907
+ if (Path5.extname(outputPath) === ".ndjson") {
2908
+ this.writeNdjson(typeSchemas, outputPath, typeSchemaToJson);
2909
+ } else {
2910
+ this.writeJsonFiles(
2911
+ typeSchemas.map((ts) => typeSchemaToJson(ts, true)),
2912
+ outputPath
2913
+ );
2914
+ }
2915
+ this.logger()?.info(
2916
+ `IntrospectionWriter: ${typeSchemas.length} TypeSchema written to ${this.opts.typeSchemas}`
2917
+ );
2918
+ }
2919
+ if (this.opts.fhirSchemas && tsIndex.register) {
2920
+ const outputPath = this.opts.fhirSchemas;
2921
+ const fhirSchemas = tsIndex.register.allFs();
2922
+ if (Path5.extname(outputPath) === ".ndjson") {
2923
+ this.writeNdjson(fhirSchemas, outputPath, fhirSchemaToJson);
2924
+ } else {
2925
+ this.writeJsonFiles(
2926
+ fhirSchemas.map((fs7) => fhirSchemaToJson(fs7, true)),
2927
+ outputPath
2928
+ );
2929
+ }
2930
+ this.logger()?.info(`IntrospectionWriter: ${fhirSchemas.length} FHIR schema written to ${outputPath}`);
2931
+ }
2932
+ if (this.opts.structureDefinitions && tsIndex.register) {
2933
+ const outputPath = this.opts.structureDefinitions;
2934
+ const structureDefinitions = tsIndex.register.allSd();
2935
+ if (Path5.extname(outputPath) === ".ndjson") {
2936
+ this.writeNdjson(structureDefinitions, outputPath, structureDefinitionToJson);
2937
+ } else {
2938
+ this.writeJsonFiles(
2939
+ structureDefinitions.map((sd) => structureDefinitionToJson(sd, true)),
2940
+ outputPath
2941
+ );
2942
+ }
2943
+ this.logger()?.info(
2944
+ `IntrospectionWriter: ${structureDefinitions.length} StructureDefinitions written to ${outputPath}`
2945
+ );
2946
+ }
2947
+ }
2948
+ async writeNdjson(items, outputFile, toJson) {
2949
+ this.cd(Path5.dirname(outputFile), () => {
2950
+ this.cat(Path5.basename(outputFile), () => {
2951
+ for (const item of items) {
2952
+ const { genContent } = toJson(item, false);
2953
+ this.write(`${genContent()}
2954
+ `);
2955
+ }
2956
+ });
2957
+ });
2958
+ }
2959
+ async writeJsonFiles(items, outputDir) {
2960
+ this.cd(outputDir, () => {
2961
+ for (const { filename, genContent } of items) {
2962
+ const fileName = `${filename}.json`;
2963
+ this.cd(Path5.dirname(fileName), () => {
2964
+ this.cat(Path5.basename(fileName), () => {
2965
+ this.write(genContent());
2966
+ });
2967
+ });
2968
+ }
2969
+ });
2970
+ }
2971
+ async writeTypeTree(tsIndex) {
2972
+ const filename = this.opts.typeTree;
2973
+ if (!filename) return;
2974
+ const tree = tsIndex.entityTree();
2975
+ const raw = filename.endsWith(".yaml") ? YAML__default.stringify(tree) : JSON.stringify(tree, void 0, 2);
2976
+ const dir = Path5.dirname(filename);
2977
+ const file = Path5.basename(filename);
2978
+ this.cd(dir, () => {
2979
+ this.cat(file, () => {
2980
+ this.write(raw);
2981
+ });
2982
+ });
2983
+ }
2984
+ };
2985
+
2986
+ // src/typeschema/ir/report.ts
2987
+ var generateIrReportReadme = (report) => {
2988
+ const lines = ["# IR Report", ""];
2989
+ const allPackages = /* @__PURE__ */ new Set([
2990
+ ...Object.keys(report.treeShake?.packages ?? {}),
2991
+ ...Object.keys(report.logicalPromotion?.packages ?? {})
2992
+ ]);
2993
+ if (allPackages.size === 0) {
2994
+ lines.push("No IR modifications applied.");
2995
+ return lines.join("\n");
2996
+ }
2997
+ if (report.treeShake?.skippedPackages.length) {
2998
+ lines.push("## Skipped Packages", "");
2999
+ for (const pkg of report.treeShake.skippedPackages) {
3000
+ lines.push(`- ${pkg}`);
3001
+ }
3002
+ lines.push("");
3003
+ }
3004
+ for (const pkgName of [...allPackages].sort()) {
3005
+ lines.push(`## Package: \`${pkgName}\``, "");
3006
+ const treeShakePkg = report.treeShake?.packages[pkgName];
3007
+ const logicalPromotionPkg = report.logicalPromotion?.packages[pkgName];
3008
+ if (logicalPromotionPkg?.promotedCanonicals.length) {
3009
+ lines.push("### Promoted Logical Models", "");
3010
+ for (const canonical of logicalPromotionPkg.promotedCanonicals) {
3011
+ lines.push(`- \`${canonical}\``);
3012
+ }
3013
+ lines.push("");
3014
+ }
3015
+ if (treeShakePkg) {
3016
+ const canonicalsWithChanges = Object.entries(treeShakePkg.canonicals).filter(
3017
+ ([_, data]) => data.skippedFields.length > 0
3018
+ );
3019
+ if (canonicalsWithChanges.length > 0) {
3020
+ lines.push("### Modified Canonicals", "");
3021
+ for (const [canonical, data] of canonicalsWithChanges) {
3022
+ lines.push(`#### \`${canonical}\``, "");
3023
+ lines.push("Skipped fields:", "");
3024
+ for (const field of data.skippedFields) {
3025
+ lines.push(`- \`${field}\``);
3026
+ }
3027
+ lines.push("");
3028
+ }
3029
+ }
3030
+ if (treeShakePkg.skippedCanonicals.length > 0) {
3031
+ lines.push("### Skipped Canonicals", "");
3032
+ for (const canonical of treeShakePkg.skippedCanonicals) {
3033
+ lines.push(`- \`${canonical}\``);
3034
+ }
3035
+ lines.push("");
3036
+ }
3037
+ }
3038
+ }
3039
+ return lines.join("\n");
3040
+ };
3041
+
3042
+ // src/api/writer-generator/ir-report.ts
3043
+ var IrReportWriterWriter = class extends FileSystemWriter {
3044
+ async generate(tsIndex) {
3045
+ const report = tsIndex.irReport();
3046
+ const md = generateIrReportReadme(report);
3047
+ this.cd("/", () => {
3048
+ this.cat(this.opts.rootReadmeFileName, () => {
3049
+ this.write(md);
3050
+ });
3051
+ });
3052
+ }
2433
3053
  };
2434
3054
 
2435
3055
  // src/api/mustache/generator/DebugMixinProvider.ts
@@ -2570,7 +3190,7 @@ var TemplateFileCache = class {
2570
3190
  templateBaseDir;
2571
3191
  templateCache = {};
2572
3192
  constructor(templateBaseDir) {
2573
- this.templateBaseDir = Path__default.resolve(templateBaseDir);
3193
+ this.templateBaseDir = Path5__default.resolve(templateBaseDir);
2574
3194
  }
2575
3195
  _normalizeName(name) {
2576
3196
  if (name.endsWith(".mustache")) {
@@ -2585,7 +3205,7 @@ var TemplateFileCache = class {
2585
3205
  const normalizedName = this._normalizeName(name);
2586
3206
  if (!this.templateCache[normalizedName]) {
2587
3207
  this.templateCache[normalizedName] = fs__default.readFileSync(
2588
- Path__default.join(this.templateBaseDir, normalizedName),
3208
+ Path5__default.join(this.templateBaseDir, normalizedName),
2589
3209
  "utf-8"
2590
3210
  );
2591
3211
  }
@@ -2912,7 +3532,7 @@ var ViewModelFactory = class {
2912
3532
  }
2913
3533
  };
2914
3534
  function loadMustacheGeneratorConfig(templatePath, logger) {
2915
- const filePath = Path.resolve(templatePath, "config.json");
3535
+ const filePath = Path5.resolve(templatePath, "config.json");
2916
3536
  try {
2917
3537
  const raw = fs.readFileSync(filePath, "utf-8");
2918
3538
  const parsed = JSON.parse(raw);
@@ -2950,21 +3570,21 @@ var createGenerator = (templatePath, apiOpts) => {
2950
3570
  ...apiOpts,
2951
3571
  ...actualFileOpts,
2952
3572
  sources: {
2953
- staticSource: Path.resolve(templatePath, "static"),
2954
- templateSource: Path.resolve(templatePath, "templates")
3573
+ staticSource: Path5.resolve(templatePath, "static"),
3574
+ templateSource: Path5.resolve(templatePath, "templates")
2955
3575
  }
2956
3576
  };
2957
3577
  return new MustacheGenerator(mustacheOptions);
2958
3578
  };
2959
3579
  function runCommand(cmd, args = [], options = {}) {
2960
- return new Promise((resolve6, reject) => {
3580
+ return new Promise((resolve5, reject) => {
2961
3581
  const child = spawn(cmd, args, {
2962
3582
  stdio: "inherit",
2963
3583
  ...options
2964
3584
  });
2965
3585
  child.on("error", reject);
2966
3586
  child.on("close", (code) => {
2967
- if (code === 0) resolve6(code);
3587
+ if (code === 0) resolve5(code);
2968
3588
  else reject(new Error(`Prozess beendet mit Fehlercode ${code}`));
2969
3589
  });
2970
3590
  });
@@ -3002,7 +3622,7 @@ var MustacheGenerator = class extends FileSystemWriter {
3002
3622
  return;
3003
3623
  }
3004
3624
  copyStaticFiles() {
3005
- const staticDir = Path.resolve(this.opts.sources.staticSource);
3625
+ const staticDir = Path5.resolve(this.opts.sources.staticSource);
3006
3626
  if (!staticDir) {
3007
3627
  throw new Error("staticDir must be set in subclass.");
3008
3628
  }
@@ -3112,13 +3732,15 @@ var tsFhirPackageDir = (name) => {
3112
3732
  return kebabCase(name);
3113
3733
  };
3114
3734
  var tsModuleName = (id) => {
3115
- if (id.kind === "profile") return `${tsResourceName(id)}_profile`;
3116
- return pascalCase(id.name);
3735
+ return uppercaseFirstLetter(normalizeTsName(id.name));
3117
3736
  };
3118
3737
  var tsModuleFileName = (id) => {
3119
3738
  return `${tsModuleName(id)}.ts`;
3120
3739
  };
3121
3740
  var tsModulePath = (id) => {
3741
+ if (isProfileIdentifier(id)) {
3742
+ return `${tsFhirPackageDir(id.package)}/profiles/${tsModuleName(id)}`;
3743
+ }
3122
3744
  return `${tsFhirPackageDir(id.package)}/${tsModuleName(id)}`;
3123
3745
  };
3124
3746
  var canonicalToName3 = (canonical, dropFragment = true) => {
@@ -3146,7 +3768,7 @@ var tsFieldName = (n) => {
3146
3768
  };
3147
3769
  var normalizeTsName = (n) => {
3148
3770
  if (tsKeywords.has(n)) n = `${n}_`;
3149
- return n.replace(/[- ]/g, "_");
3771
+ return n.replace(/\[x\]/g, "_x_").replace(/[- :]/g, "_");
3150
3772
  };
3151
3773
  var tsGet = (object, tsFieldName2) => {
3152
3774
  if (tsFieldName2.startsWith('"')) return `${object}[${tsFieldName2}]`;
@@ -3155,42 +3777,114 @@ var tsGet = (object, tsFieldName2) => {
3155
3777
  var tsEnumType = (enumValues) => {
3156
3778
  return `(${enumValues.map((e) => `"${e}"`).join(" | ")})`;
3157
3779
  };
3780
+ var tsTypeFromIdentifier = (id) => {
3781
+ if (isNestedIdentifier(id)) return tsResourceName(id);
3782
+ if (isPrimitiveIdentifier(id)) return resolvePrimitiveType(id.name);
3783
+ const primitiveType = primitiveType2tsType[id.name];
3784
+ if (primitiveType !== void 0) return primitiveType;
3785
+ return id.name;
3786
+ };
3787
+ var tsProfileClassName = (id) => {
3788
+ return `${uppercaseFirstLetter(normalizeTsName(id.name))}Profile`;
3789
+ };
3790
+ var tsSliceInputTypeName = (profileName, fieldName, sliceName) => {
3791
+ return `${uppercaseFirstLetter(profileName)}_${uppercaseFirstLetter(normalizeTsName(fieldName))}_${uppercaseFirstLetter(normalizeTsName(sliceName))}SliceInput`;
3792
+ };
3793
+ var tsExtensionInputTypeName = (profileName, extensionName) => {
3794
+ return `${uppercaseFirstLetter(profileName)}_${uppercaseFirstLetter(normalizeTsName(extensionName))}Input`;
3795
+ };
3796
+ var safeCamelCase = (name) => {
3797
+ if (!name) return "";
3798
+ const normalized = name.replace(/\[x\]/g, "").replace(/:/g, "_");
3799
+ return camelCase(normalized);
3800
+ };
3801
+ var tsSliceMethodName = (sliceName) => {
3802
+ const normalized = safeCamelCase(sliceName);
3803
+ return `set${uppercaseFirstLetter(normalized || "Slice")}`;
3804
+ };
3805
+ var tsExtensionMethodName = (name) => {
3806
+ const normalized = safeCamelCase(name);
3807
+ return `set${uppercaseFirstLetter(normalized || "Extension")}`;
3808
+ };
3809
+ var tsExtensionMethodFallback = (name, path) => {
3810
+ const rawPath = path?.split(".").filter((p) => p && p !== "extension").join("_") ?? "";
3811
+ const pathPart = rawPath ? uppercaseFirstLetter(safeCamelCase(rawPath)) : "";
3812
+ const normalized = safeCamelCase(name);
3813
+ return `setExtension${pathPart}${uppercaseFirstLetter(normalized || "Extension")}`;
3814
+ };
3815
+ var tsSliceMethodFallback = (fieldName, sliceName) => {
3816
+ const fieldPart = uppercaseFirstLetter(safeCamelCase(fieldName) || "Field");
3817
+ const slicePart = uppercaseFirstLetter(safeCamelCase(sliceName) || "Slice");
3818
+ return `setSlice${fieldPart}${slicePart}`;
3819
+ };
3158
3820
  var TypeScript = class extends Writer {
3159
3821
  tsImportType(tsPackageName, ...entities) {
3160
3822
  this.lineSM(`import type { ${entities.join(", ")} } from "${tsPackageName}"`);
3161
3823
  }
3824
+ generateProfileIndexFile(profiles) {
3825
+ if (profiles.length === 0) return;
3826
+ this.cd("profiles", () => {
3827
+ this.cat("index.ts", () => {
3828
+ const seen = /* @__PURE__ */ new Set();
3829
+ const uniqueProfiles = profiles.filter((profile) => {
3830
+ const className = tsProfileClassName(profile.identifier);
3831
+ if (seen.has(className)) return false;
3832
+ seen.add(className);
3833
+ return true;
3834
+ });
3835
+ if (uniqueProfiles.length === 0) return;
3836
+ for (const profile of uniqueProfiles) {
3837
+ const className = tsProfileClassName(profile.identifier);
3838
+ this.lineSM(`export { ${className} } from "./${tsModuleName(profile.identifier)}"`);
3839
+ }
3840
+ });
3841
+ });
3842
+ }
3162
3843
  generateFhirPackageIndexFile(schemas) {
3163
3844
  this.cat("index.ts", () => {
3164
- let exports = schemas.flatMap((schema) => [
3165
- {
3166
- identifier: schema.identifier,
3167
- tsPackageName: tsModuleName(schema.identifier),
3168
- resourceName: tsResourceName(schema.identifier),
3169
- nestedTypes: isResourceTypeSchema(schema) && schema.nested || isLogicalTypeSchema(schema) && schema.nested ? schema.nested.map((n) => tsResourceName(n.identifier)) : [],
3170
- helpers: isResourceTypeSchema(schema) || isLogicalTypeSchema(schema) ? [`is${tsResourceName(schema.identifier)}`] : []
3171
- }
3172
- ]).sort((a, b) => a.resourceName.localeCompare(b.resourceName));
3845
+ const profiles = schemas.filter(isProfileTypeSchema);
3846
+ if (profiles.length > 0) {
3847
+ this.lineSM(`export * from "./profiles"`);
3848
+ }
3849
+ let exports = schemas.flatMap((schema) => {
3850
+ const resourceName = tsResourceName(schema.identifier);
3851
+ const typeExports = isProfileTypeSchema(schema) ? [] : [
3852
+ resourceName,
3853
+ ...isResourceTypeSchema(schema) && schema.nested || isLogicalTypeSchema(schema) && schema.nested ? schema.nested.map((n) => tsResourceName(n.identifier)) : []
3854
+ ];
3855
+ const valueExports = isResourceTypeSchema(schema) ? [`is${resourceName}`] : [];
3856
+ return [
3857
+ {
3858
+ identifier: schema.identifier,
3859
+ tsPackageName: tsModuleName(schema.identifier),
3860
+ resourceName,
3861
+ typeExports,
3862
+ valueExports
3863
+ }
3864
+ ];
3865
+ }).sort((a, b) => a.resourceName.localeCompare(b.resourceName));
3173
3866
  exports = Array.from(new Map(exports.map((exp) => [exp.resourceName.toLowerCase(), exp])).values()).sort(
3174
3867
  (a, b) => a.resourceName.localeCompare(b.resourceName)
3175
3868
  );
3176
3869
  for (const exp of exports) {
3177
3870
  this.debugComment(exp.identifier);
3178
- this.lineSM(
3179
- `export type { ${[exp.resourceName, ...exp.nestedTypes].join(", ")} } from "./${exp.tsPackageName}"`
3180
- );
3181
- if (exp.helpers.length > 0)
3182
- this.lineSM(`export { ${exp.helpers.join(", ")} } from "./${exp.tsPackageName}"`);
3871
+ if (exp.typeExports.length > 0) {
3872
+ this.lineSM(`export type { ${exp.typeExports.join(", ")} } from "./${exp.tsPackageName}"`);
3873
+ }
3874
+ if (exp.valueExports.length > 0) {
3875
+ this.lineSM(`export { ${exp.valueExports.join(", ")} } from "./${exp.tsPackageName}"`);
3876
+ }
3183
3877
  }
3184
3878
  });
3185
3879
  }
3186
- generateDependenciesImports(tsIndex, schema) {
3880
+ generateDependenciesImports(tsIndex, schema, importPrefix = "../") {
3187
3881
  if (schema.dependencies) {
3188
3882
  const imports = [];
3189
3883
  const skipped = [];
3190
3884
  for (const dep of schema.dependencies) {
3191
3885
  if (["complex-type", "resource", "logical"].includes(dep.kind)) {
3192
3886
  imports.push({
3193
- tsPackage: `../${tsModulePath(dep)}`,
3887
+ tsPackage: `${importPrefix}${tsModulePath(dep)}`,
3194
3888
  name: uppercaseFirstLetter(dep.name),
3195
3889
  dep
3196
3890
  });
@@ -3198,7 +3892,7 @@ var TypeScript = class extends Writer {
3198
3892
  const ndep = { ...dep };
3199
3893
  ndep.name = canonicalToName3(dep.url);
3200
3894
  imports.push({
3201
- tsPackage: `../${tsModulePath(ndep)}`,
3895
+ tsPackage: `${importPrefix}${tsModulePath(ndep)}`,
3202
3896
  name: tsResourceName(dep),
3203
3897
  dep
3204
3898
  });
@@ -3219,7 +3913,7 @@ var TypeScript = class extends Writer {
3219
3913
  const elementUrl = "http://hl7.org/fhir/StructureDefinition/Element";
3220
3914
  const element = tsIndex.resolveByUrl(schema.identifier.package, elementUrl);
3221
3915
  if (!element) throw new Error(`'${elementUrl}' not found for ${schema.identifier.package}.`);
3222
- this.tsImportType(`../${tsModulePath(element.identifier)}`, "Element");
3916
+ this.tsImportType(`${importPrefix}${tsModulePath(element.identifier)}`, "Element");
3223
3917
  }
3224
3918
  }
3225
3919
  }
@@ -3256,7 +3950,7 @@ var TypeScript = class extends Writer {
3256
3950
  return;
3257
3951
  }
3258
3952
  this.curlyBlock(["export", "interface", name, extendsClause], () => {
3259
- if (isResourceTypeSchema(schema) || isLogicalTypeSchema(schema)) {
3953
+ if (isResourceTypeSchema(schema)) {
3260
3954
  const possibleResourceTypes = [schema.identifier];
3261
3955
  possibleResourceTypes.push(...tsIndex.resourceChildren(schema.identifier));
3262
3956
  const openSetSuffix = this.opts.openResourceTypeSet && possibleResourceTypes.length > 1 ? " | string" : "";
@@ -3307,7 +4001,7 @@ var TypeScript = class extends Writer {
3307
4001
  return false;
3308
4002
  }
3309
4003
  generateResourceTypePredicate(schema) {
3310
- if (!isResourceTypeSchema(schema) && !isLogicalTypeSchema(schema)) return;
4004
+ if (!isResourceTypeSchema(schema)) return;
3311
4005
  const name = tsResourceName(schema.identifier);
3312
4006
  this.curlyBlock(["export", "const", `is${name}`, "=", `(resource: unknown): resource is ${name}`, "=>"], () => {
3313
4007
  this.lineSM(
@@ -3323,6 +4017,45 @@ var TypeScript = class extends Writer {
3323
4017
  }
3324
4018
  }
3325
4019
  }
4020
+ tsTypeForProfileField(tsIndex, flatProfile, fieldName, field) {
4021
+ if (!isNotChoiceDeclarationField(field)) {
4022
+ throw new Error(`Choice declaration fields not supported for '${fieldName}'`);
4023
+ }
4024
+ if (field.enum) {
4025
+ return tsEnumType(field.enum);
4026
+ }
4027
+ if (field.reference && field.reference.length > 0) {
4028
+ const specialization = tsIndex.findLastSpecialization(flatProfile);
4029
+ if (!isSpecializationTypeSchema(specialization))
4030
+ throw new Error(`Invalid specialization for ${flatProfile.identifier}`);
4031
+ const sField = specialization.fields?.[fieldName];
4032
+ if (sField === void 0 || isChoiceDeclarationField(sField) || sField.reference === void 0)
4033
+ throw new Error(`Invalid field declaration for ${fieldName}`);
4034
+ const sRefs = sField.reference.map((e) => e.name);
4035
+ const references = field.reference.map((ref) => {
4036
+ const resRef = tsIndex.findLastSpecializationByIdentifier(ref);
4037
+ if (resRef.name !== ref.name) {
4038
+ return `"${resRef.name}" /*${ref.name}*/`;
4039
+ }
4040
+ return `'${ref.name}'`;
4041
+ }).join(" | ");
4042
+ if (sRefs.length === 1 && sRefs[0] === "Resource" && references !== '"Resource"') {
4043
+ const cleanRefs = references.replace(/\/\*[^*]*\*\//g, "").trim();
4044
+ return `Reference<"Resource" /* ${cleanRefs} */ >`;
4045
+ }
4046
+ return `Reference<${references}>`;
4047
+ }
4048
+ if (isNestedIdentifier(field.type)) {
4049
+ return tsResourceName(field.type);
4050
+ }
4051
+ if (isPrimitiveIdentifier(field.type)) {
4052
+ return resolvePrimitiveType(field.type.name);
4053
+ }
4054
+ if (field.type === void 0) {
4055
+ throw new Error(`Undefined type for '${fieldName}' field at ${typeSchemaInfo(flatProfile)}`);
4056
+ }
4057
+ return field.type.name;
4058
+ }
3326
4059
  generateProfileType(tsIndex, flatProfile) {
3327
4060
  this.debugComment("flatProfile", flatProfile);
3328
4061
  const tsName = tsResourceName(flatProfile.identifier);
@@ -3335,38 +4068,7 @@ var TypeScript = class extends Writer {
3335
4068
  if (isChoiceDeclarationField(field)) continue;
3336
4069
  this.debugComment(fieldName, field);
3337
4070
  const tsName2 = tsFieldName(fieldName);
3338
- let tsType;
3339
- if (field.enum) {
3340
- tsType = tsEnumType(field.enum);
3341
- } else if (field.reference && field.reference.length > 0) {
3342
- const specialization = tsIndex.findLastSpecialization(flatProfile);
3343
- if (!isSpecializationTypeSchema(specialization))
3344
- throw new Error(`Invalid specialization for ${flatProfile.identifier}`);
3345
- const sField = specialization.fields?.[fieldName];
3346
- if (sField === void 0 || isChoiceDeclarationField(sField) || sField.reference === void 0)
3347
- throw new Error(`Invalid field declaration for ${fieldName}`);
3348
- const sRefs = sField.reference.map((e) => e.name);
3349
- const references = field.reference.map((ref) => {
3350
- const resRef = tsIndex.findLastSpecializationByIdentifier(ref);
3351
- if (resRef.name !== ref.name) {
3352
- return `"${resRef.name}" /*${ref.name}*/`;
3353
- }
3354
- return `'${ref.name}'`;
3355
- }).join(" | ");
3356
- if (sRefs.length === 1 && sRefs[0] === "Resource" && references !== '"Resource"') {
3357
- tsType = `Reference<"Resource" /* ${references} */ >`;
3358
- } else {
3359
- tsType = `Reference<${references}>`;
3360
- }
3361
- } else if (isNestedIdentifier(field.type)) {
3362
- tsType = tsResourceName(field.type);
3363
- } else if (isPrimitiveIdentifier(field.type)) {
3364
- tsType = resolvePrimitiveType(field.type.name);
3365
- } else if (field.type === void 0) {
3366
- throw new Error(`Undefined type for '${fieldName}' field at ${typeSchemaInfo(flatProfile)}`);
3367
- } else {
3368
- tsType = field.type.name;
3369
- }
4071
+ const tsType = this.tsTypeForProfileField(tsIndex, flatProfile, fieldName, field);
3370
4072
  this.lineSM(`${tsName2}${!field.required ? "?" : ""}: ${tsType}${field.array ? "[]" : ""}`);
3371
4073
  }
3372
4074
  });
@@ -3473,41 +4175,769 @@ var TypeScript = class extends Writer {
3473
4175
  }
3474
4176
  );
3475
4177
  }
3476
- generateResourceModule(tsIndex, schema) {
3477
- this.cat(`${tsModuleFileName(schema.identifier)}`, () => {
4178
+ generateProfileHelpersModule() {
4179
+ this.cat("profile-helpers.ts", () => {
3478
4180
  this.generateDisclaimer();
3479
- if (["complex-type", "resource", "logical"].includes(schema.identifier.kind)) {
4181
+ this.curlyBlock(
4182
+ ["export const", "isRecord", "=", "(value: unknown): value is Record<string, unknown>", "=>"],
4183
+ () => {
4184
+ this.lineSM('return value !== null && typeof value === "object" && !Array.isArray(value)');
4185
+ }
4186
+ );
4187
+ this.line();
4188
+ this.curlyBlock(
4189
+ [
4190
+ "export const",
4191
+ "getOrCreateObjectAtPath",
4192
+ "=",
4193
+ "(root: Record<string, unknown>, path: string[]): Record<string, unknown>",
4194
+ "=>"
4195
+ ],
4196
+ () => {
4197
+ this.lineSM("let current: Record<string, unknown> = root");
4198
+ this.curlyBlock(["for (const", "segment", "of", "path)"], () => {
4199
+ this.curlyBlock(["if", "(Array.isArray(current[segment]))"], () => {
4200
+ this.lineSM("const list = current[segment] as unknown[]");
4201
+ this.curlyBlock(["if", "(list.length === 0)"], () => {
4202
+ this.lineSM("list.push({})");
4203
+ });
4204
+ this.lineSM("current = list[0] as Record<string, unknown>");
4205
+ });
4206
+ this.curlyBlock(["else"], () => {
4207
+ this.curlyBlock(["if", "(!isRecord(current[segment]))"], () => {
4208
+ this.lineSM("current[segment] = {}");
4209
+ });
4210
+ this.lineSM("current = current[segment] as Record<string, unknown>");
4211
+ });
4212
+ });
4213
+ this.lineSM("return current");
4214
+ }
4215
+ );
4216
+ this.line();
4217
+ this.curlyBlock(
4218
+ [
4219
+ "export const",
4220
+ "mergeMatch",
4221
+ "=",
4222
+ "(target: Record<string, unknown>, match: Record<string, unknown>): void",
4223
+ "=>"
4224
+ ],
4225
+ () => {
4226
+ this.curlyBlock(["for (const", "[key, matchValue]", "of", "Object.entries(match))"], () => {
4227
+ this.curlyBlock(
4228
+ ["if", '(key === "__proto__" || key === "constructor" || key === "prototype")'],
4229
+ () => {
4230
+ this.lineSM("continue");
4231
+ }
4232
+ );
4233
+ this.curlyBlock(["if", "(isRecord(matchValue))"], () => {
4234
+ this.curlyBlock(["if", "(isRecord(target[key]))"], () => {
4235
+ this.lineSM("mergeMatch(target[key] as Record<string, unknown>, matchValue)");
4236
+ });
4237
+ this.curlyBlock(["else"], () => {
4238
+ this.lineSM("target[key] = { ...matchValue }");
4239
+ });
4240
+ });
4241
+ this.curlyBlock(["else"], () => {
4242
+ this.lineSM("target[key] = matchValue");
4243
+ });
4244
+ });
4245
+ }
4246
+ );
4247
+ this.line();
4248
+ this.curlyBlock(
4249
+ [
4250
+ "export const",
4251
+ "applySliceMatch",
4252
+ "=",
4253
+ "<T extends Record<string, unknown>>(input: T, match: Record<string, unknown>): T",
4254
+ "=>"
4255
+ ],
4256
+ () => {
4257
+ this.lineSM("const result = { ...input } as Record<string, unknown>");
4258
+ this.lineSM("mergeMatch(result, match)");
4259
+ this.lineSM("return result as T");
4260
+ }
4261
+ );
4262
+ this.line();
4263
+ this.curlyBlock(
4264
+ ["export const", "matchesValue", "=", "(value: unknown, match: unknown): boolean", "=>"],
4265
+ () => {
4266
+ this.curlyBlock(["if", "(Array.isArray(match))"], () => {
4267
+ this.curlyBlock(["if", "(!Array.isArray(value))"], () => this.lineSM("return false"));
4268
+ this.lineSM(
4269
+ "return match.every((matchItem) => value.some((item) => matchesValue(item, matchItem)))"
4270
+ );
4271
+ });
4272
+ this.curlyBlock(["if", "(isRecord(match))"], () => {
4273
+ this.curlyBlock(["if", "(!isRecord(value))"], () => this.lineSM("return false"));
4274
+ this.curlyBlock(["for (const", "[key, matchValue]", "of", "Object.entries(match))"], () => {
4275
+ this.curlyBlock(
4276
+ ["if", "(!matchesValue((value as Record<string, unknown>)[key], matchValue))"],
4277
+ () => {
4278
+ this.lineSM("return false");
4279
+ }
4280
+ );
4281
+ });
4282
+ this.lineSM("return true");
4283
+ });
4284
+ this.lineSM("return value === match");
4285
+ }
4286
+ );
4287
+ this.line();
4288
+ this.curlyBlock(
4289
+ [
4290
+ "export const",
4291
+ "matchesSlice",
4292
+ "=",
4293
+ "(value: unknown, match: Record<string, unknown>): boolean",
4294
+ "=>"
4295
+ ],
4296
+ () => {
4297
+ this.lineSM("return matchesValue(value, match)");
4298
+ }
4299
+ );
4300
+ this.line();
4301
+ this.curlyBlock(
4302
+ [
4303
+ "export const",
4304
+ "extractComplexExtension",
4305
+ "=",
4306
+ "(extension: { extension?: Array<{ url?: string; [key: string]: unknown }> } | undefined, config: Array<{ name: string; valueField: string; isArray: boolean }>): Record<string, unknown> | undefined",
4307
+ "=>"
4308
+ ],
4309
+ () => {
4310
+ this.lineSM("if (!extension?.extension) return undefined");
4311
+ this.lineSM("const result: Record<string, unknown> = {}");
4312
+ this.curlyBlock(["for (const", "{ name, valueField, isArray }", "of", "config)"], () => {
4313
+ this.lineSM("const subExts = extension.extension.filter(e => e.url === name)");
4314
+ this.curlyBlock(["if", "(isArray)"], () => {
4315
+ this.lineSM("result[name] = subExts.map(e => (e as Record<string, unknown>)[valueField])");
4316
+ });
4317
+ this.curlyBlock(["else if", "(subExts[0])"], () => {
4318
+ this.lineSM("result[name] = (subExts[0] as Record<string, unknown>)[valueField]");
4319
+ });
4320
+ });
4321
+ this.lineSM("return result");
4322
+ }
4323
+ );
4324
+ this.line();
4325
+ this.curlyBlock(
4326
+ [
4327
+ "export const",
4328
+ "extractSliceSimplified",
4329
+ "=",
4330
+ "<T extends Record<string, unknown>>(slice: T, matchKeys: string[]): Partial<T>",
4331
+ "=>"
4332
+ ],
4333
+ () => {
4334
+ this.lineSM("const result = { ...slice } as Record<string, unknown>");
4335
+ this.curlyBlock(["for (const", "key", "of", "matchKeys)"], () => {
4336
+ this.lineSM("delete result[key]");
4337
+ });
4338
+ this.lineSM("return result as Partial<T>");
4339
+ }
4340
+ );
4341
+ });
4342
+ }
4343
+ generateProfileHelpersImport(options) {
4344
+ const imports = [];
4345
+ if (options.needsSliceHelpers) {
4346
+ imports.push("applySliceMatch", "matchesSlice");
4347
+ }
4348
+ if (options.needsGetOrCreateObjectAtPath) {
4349
+ imports.push("getOrCreateObjectAtPath");
4350
+ }
4351
+ if (options.needsExtensionExtraction) {
4352
+ imports.push("extractComplexExtension");
4353
+ }
4354
+ if (options.needsSliceExtraction) {
4355
+ imports.push("extractSliceSimplified");
4356
+ }
4357
+ if (imports.length > 0) {
4358
+ this.lineSM(`import { ${imports.join(", ")} } from "../../profile-helpers"`);
4359
+ }
4360
+ }
4361
+ generateProfileImports(tsIndex, flatProfile) {
4362
+ const usedTypes = /* @__PURE__ */ new Map();
4363
+ const getModulePath = (typeId) => {
4364
+ if (isNestedIdentifier(typeId)) {
4365
+ const url = typeId.url;
4366
+ const path = canonicalToName3(url, true);
4367
+ if (path) {
4368
+ return `../../${tsFhirPackageDir(typeId.package)}/${pascalCase(path)}`;
4369
+ }
4370
+ }
4371
+ return `../../${tsModulePath(typeId)}`;
4372
+ };
4373
+ const addType = (typeId) => {
4374
+ if (typeId.kind === "primitive-type") return;
4375
+ const tsName = tsResourceName(typeId);
4376
+ if (!usedTypes.has(tsName)) {
4377
+ usedTypes.set(tsName, {
4378
+ importPath: getModulePath(typeId),
4379
+ tsName
4380
+ });
4381
+ }
4382
+ };
4383
+ addType(flatProfile.base);
4384
+ const fields = flatProfile.fields ?? {};
4385
+ for (const [_fieldName, field] of Object.entries(fields)) {
4386
+ if (!isNotChoiceDeclarationField(field) || !field.slicing?.slices || !field.type) continue;
4387
+ for (const [_sliceName, slice] of Object.entries(field.slicing.slices)) {
4388
+ const match = slice.match ?? {};
4389
+ if (Object.keys(match).length === 0) continue;
4390
+ addType(field.type);
4391
+ }
4392
+ }
4393
+ const extensions = flatProfile.extensions ?? [];
4394
+ let needsExtensionType = false;
4395
+ for (const ext of extensions) {
4396
+ if (ext.isComplex && ext.subExtensions) {
4397
+ needsExtensionType = true;
4398
+ for (const sub of ext.subExtensions) {
4399
+ if (sub.valueType) {
4400
+ const resolvedType = tsIndex.resolveByUrl(
4401
+ flatProfile.identifier.package,
4402
+ sub.valueType.url
4403
+ );
4404
+ if (resolvedType) {
4405
+ addType(resolvedType.identifier);
4406
+ } else {
4407
+ addType(sub.valueType);
4408
+ }
4409
+ }
4410
+ }
4411
+ } else if (ext.valueTypes && ext.valueTypes.length === 1) {
4412
+ needsExtensionType = true;
4413
+ const valueType = ext.valueTypes[0];
4414
+ if (valueType) {
4415
+ addType(valueType);
4416
+ }
4417
+ } else {
4418
+ needsExtensionType = true;
4419
+ }
4420
+ }
4421
+ if (needsExtensionType) {
4422
+ const extensionUrl = "http://hl7.org/fhir/StructureDefinition/Extension";
4423
+ const extensionSchema = tsIndex.resolveByUrl(flatProfile.identifier.package, extensionUrl);
4424
+ if (extensionSchema) {
4425
+ addType(extensionSchema.identifier);
4426
+ }
4427
+ }
4428
+ const referenceUrl = "http://hl7.org/fhir/StructureDefinition/Reference";
4429
+ const referenceSchema = tsIndex.resolveByUrl(flatProfile.identifier.package, referenceUrl);
4430
+ const specialization = tsIndex.findLastSpecialization(flatProfile);
4431
+ if (isSpecializationTypeSchema(specialization)) {
4432
+ for (const [fieldName, pField] of Object.entries(flatProfile.fields ?? {})) {
4433
+ if (!isNotChoiceDeclarationField(pField)) continue;
4434
+ const sField = specialization.fields?.[fieldName];
4435
+ if (!sField || isChoiceDeclarationField(sField)) continue;
4436
+ if (pField.reference && sField.reference && pField.reference.length < sField.reference.length) {
4437
+ if (referenceSchema) {
4438
+ addType(referenceSchema.identifier);
4439
+ }
4440
+ } else if (pField.required && !sField.required) {
4441
+ if (pField.type) {
4442
+ addType(pField.type);
4443
+ }
4444
+ }
4445
+ }
4446
+ }
4447
+ const sortedImports = Array.from(usedTypes.values()).sort((a, b) => a.tsName.localeCompare(b.tsName));
4448
+ for (const { importPath, tsName } of sortedImports) {
4449
+ this.tsImportType(importPath, tsName);
4450
+ }
4451
+ if (sortedImports.length > 0) {
4452
+ this.line();
4453
+ }
4454
+ }
4455
+ generateProfileClass(tsIndex, flatProfile) {
4456
+ const tsBaseResourceName = tsTypeFromIdentifier(flatProfile.base);
4457
+ const tsProfileName = tsResourceName(flatProfile.identifier);
4458
+ const profileClassName = tsProfileClassName(flatProfile.identifier);
4459
+ const polymorphicBaseNames = /* @__PURE__ */ new Set([
4460
+ "value",
4461
+ "effective",
4462
+ "onset",
4463
+ "abatement",
4464
+ "occurrence",
4465
+ "timing",
4466
+ "deceased",
4467
+ "born",
4468
+ "age",
4469
+ "medication",
4470
+ "performed",
4471
+ "serviced",
4472
+ "collected",
4473
+ "item",
4474
+ "subject",
4475
+ "bounds",
4476
+ "amount",
4477
+ "content",
4478
+ "product",
4479
+ "rate",
4480
+ "dose",
4481
+ "asNeeded"
4482
+ ]);
4483
+ const sliceDefs = Object.entries(flatProfile.fields ?? {}).filter(([_fieldName, field]) => isNotChoiceDeclarationField(field) && field.slicing?.slices).flatMap(([fieldName, field]) => {
4484
+ if (!isNotChoiceDeclarationField(field) || !field.slicing?.slices || !field.type) return [];
4485
+ const baseType = tsTypeFromIdentifier(field.type);
4486
+ return Object.entries(field.slicing.slices).filter(([_sliceName, slice]) => {
4487
+ const match = slice.match ?? {};
4488
+ return Object.keys(match).length > 0;
4489
+ }).map(([sliceName, slice]) => {
4490
+ const matchFields = Object.keys(slice.match ?? {});
4491
+ const required = slice.required ?? [];
4492
+ const filteredRequired = required.filter(
4493
+ (name) => !matchFields.includes(name) && !polymorphicBaseNames.has(name)
4494
+ );
4495
+ return {
4496
+ fieldName,
4497
+ baseType,
4498
+ sliceName,
4499
+ match: slice.match ?? {},
4500
+ required,
4501
+ excluded: slice.excluded ?? [],
4502
+ array: Boolean(field.array),
4503
+ // Input is optional when there are no required fields after filtering
4504
+ inputOptional: filteredRequired.length === 0
4505
+ };
4506
+ });
4507
+ });
4508
+ const extensions = flatProfile.extensions ?? [];
4509
+ const complexExtensions = extensions.filter((ext) => ext.isComplex && ext.subExtensions);
4510
+ for (const ext of complexExtensions) {
4511
+ const typeName = tsExtensionInputTypeName(tsProfileName, ext.name);
4512
+ this.curlyBlock(["export", "type", typeName, "="], () => {
4513
+ for (const sub of ext.subExtensions ?? []) {
4514
+ const tsType = sub.valueType ? tsTypeFromIdentifier(sub.valueType) : "unknown";
4515
+ const isArray = sub.max === "*";
4516
+ const isRequired2 = sub.min !== void 0 && sub.min > 0;
4517
+ this.lineSM(`${sub.name}${isRequired2 ? "" : "?"}: ${tsType}${isArray ? "[]" : ""}`);
4518
+ }
4519
+ });
4520
+ this.line();
4521
+ }
4522
+ if (sliceDefs.length > 0) {
4523
+ for (const sliceDef of sliceDefs) {
4524
+ const typeName = tsSliceInputTypeName(tsProfileName, sliceDef.fieldName, sliceDef.sliceName);
4525
+ const matchFields = Object.keys(sliceDef.match);
4526
+ const allExcluded = [.../* @__PURE__ */ new Set([...sliceDef.excluded, ...matchFields])];
4527
+ const excludedNames = allExcluded.map((name) => JSON.stringify(name));
4528
+ const filteredRequired = sliceDef.required.filter(
4529
+ (name) => !matchFields.includes(name) && !polymorphicBaseNames.has(name)
4530
+ );
4531
+ const requiredNames = filteredRequired.map((name) => JSON.stringify(name));
4532
+ let typeExpr = sliceDef.baseType;
4533
+ if (excludedNames.length > 0) {
4534
+ typeExpr = `Omit<${typeExpr}, ${excludedNames.join(" | ")}>`;
4535
+ }
4536
+ if (requiredNames.length > 0) {
4537
+ typeExpr = `${typeExpr} & Required<Pick<${sliceDef.baseType}, ${requiredNames.join(" | ")}>>`;
4538
+ }
4539
+ this.lineSM(`export type ${typeName} = ${typeExpr}`);
4540
+ }
4541
+ this.line();
4542
+ }
4543
+ const needsSliceHelpers = sliceDefs.length > 0;
4544
+ const extensionsWithNestedPath = extensions.filter((ext) => {
4545
+ const targetPath = ext.path.split(".").filter((segment) => segment !== "extension");
4546
+ return targetPath.length > 0;
4547
+ });
4548
+ const needsGetOrCreateObjectAtPath = extensionsWithNestedPath.length > 0;
4549
+ const needsExtensionExtraction = complexExtensions.length > 0;
4550
+ const needsSliceExtraction = sliceDefs.length > 0;
4551
+ if (needsSliceHelpers || needsGetOrCreateObjectAtPath || needsExtensionExtraction || needsSliceExtraction) {
4552
+ this.generateProfileHelpersImport({
4553
+ needsGetOrCreateObjectAtPath,
4554
+ needsSliceHelpers,
4555
+ needsExtensionExtraction,
4556
+ needsSliceExtraction
4557
+ });
4558
+ this.line();
4559
+ }
4560
+ const hasOverrideInterface = this.detectFieldOverrides(tsIndex, flatProfile).size > 0;
4561
+ this.curlyBlock(["export", "class", profileClassName], () => {
4562
+ this.line(`private resource: ${tsBaseResourceName}`);
4563
+ this.line();
4564
+ this.curlyBlock(["constructor", `(resource: ${tsBaseResourceName})`], () => {
4565
+ this.line("this.resource = resource");
4566
+ });
4567
+ this.line();
4568
+ this.curlyBlock(["toResource", "()", `: ${tsBaseResourceName}`], () => {
4569
+ this.line("return this.resource");
4570
+ });
4571
+ this.line();
4572
+ if (hasOverrideInterface) {
4573
+ this.curlyBlock(["toProfile", "()", `: ${tsProfileName}`], () => {
4574
+ this.line(`return this.resource as ${tsProfileName}`);
4575
+ });
4576
+ this.line();
4577
+ }
4578
+ const extensionMethods = extensions.filter((ext) => ext.url).map((ext) => ({
4579
+ ext,
4580
+ baseName: tsExtensionMethodName(ext.name),
4581
+ fallbackName: tsExtensionMethodFallback(ext.name, ext.path)
4582
+ }));
4583
+ const sliceMethodBases = sliceDefs.map((slice) => tsSliceMethodName(slice.sliceName));
4584
+ const methodCounts = /* @__PURE__ */ new Map();
4585
+ for (const name of [...sliceMethodBases, ...extensionMethods.map((m) => m.baseName)]) {
4586
+ methodCounts.set(name, (methodCounts.get(name) ?? 0) + 1);
4587
+ }
4588
+ const extensionMethodNames = new Map(
4589
+ extensionMethods.map((entry) => [
4590
+ entry.ext,
4591
+ (methodCounts.get(entry.baseName) ?? 0) > 1 ? entry.fallbackName : entry.baseName
4592
+ ])
4593
+ );
4594
+ const sliceMethodNames = new Map(
4595
+ sliceDefs.map((slice) => {
4596
+ const baseName = tsSliceMethodName(slice.sliceName);
4597
+ const needsFallback = (methodCounts.get(baseName) ?? 0) > 1;
4598
+ const fallback = tsSliceMethodFallback(slice.fieldName, slice.sliceName);
4599
+ return [slice, needsFallback ? fallback : baseName];
4600
+ })
4601
+ );
4602
+ for (const ext of extensions) {
4603
+ if (!ext.url) continue;
4604
+ const methodName = extensionMethodNames.get(ext) ?? tsExtensionMethodFallback(ext.name, ext.path);
4605
+ const valueTypes = ext.valueTypes ?? [];
4606
+ const targetPath = ext.path.split(".").filter((segment) => segment !== "extension");
4607
+ if (ext.isComplex && ext.subExtensions) {
4608
+ const inputTypeName = tsExtensionInputTypeName(tsProfileName, ext.name);
4609
+ this.curlyBlock(["public", methodName, `(input: ${inputTypeName}): this`], () => {
4610
+ this.line("const subExtensions: Extension[] = []");
4611
+ for (const sub of ext.subExtensions ?? []) {
4612
+ const valueField = sub.valueType ? `value${uppercaseFirstLetter(sub.valueType.name)}` : "value";
4613
+ const needsCast = !sub.valueType;
4614
+ const pushSuffix = needsCast ? " as Extension" : "";
4615
+ if (sub.max === "*") {
4616
+ this.curlyBlock(["if", `(input.${sub.name})`], () => {
4617
+ this.curlyBlock(["for", `(const item of input.${sub.name})`], () => {
4618
+ this.line(
4619
+ `subExtensions.push({ url: "${sub.url}", ${valueField}: item }${pushSuffix})`
4620
+ );
4621
+ });
4622
+ });
4623
+ } else {
4624
+ this.curlyBlock(["if", `(input.${sub.name} !== undefined)`], () => {
4625
+ this.line(
4626
+ `subExtensions.push({ url: "${sub.url}", ${valueField}: input.${sub.name} }${pushSuffix})`
4627
+ );
4628
+ });
4629
+ }
4630
+ }
4631
+ if (targetPath.length === 0) {
4632
+ this.line("const list = (this.resource.extension ??= [])");
4633
+ this.line(`list.push({ url: "${ext.url}", extension: subExtensions })`);
4634
+ } else {
4635
+ this.line(
4636
+ `const target = getOrCreateObjectAtPath(this.resource as unknown as Record<string, unknown>, ${JSON.stringify(targetPath)})`
4637
+ );
4638
+ this.line("if (!Array.isArray(target.extension)) target.extension = [] as Extension[]");
4639
+ this.line(
4640
+ `(target.extension as Extension[]).push({ url: "${ext.url}", extension: subExtensions })`
4641
+ );
4642
+ }
4643
+ this.line("return this");
4644
+ });
4645
+ } else if (valueTypes.length === 1 && valueTypes[0]) {
4646
+ const firstValueType = valueTypes[0];
4647
+ const valueType = tsTypeFromIdentifier(firstValueType);
4648
+ const valueField = `value${uppercaseFirstLetter(firstValueType.name)}`;
4649
+ this.curlyBlock(["public", methodName, `(value: ${valueType}): this`], () => {
4650
+ if (targetPath.length === 0) {
4651
+ this.line("const list = (this.resource.extension ??= [])");
4652
+ this.line(`list.push({ url: "${ext.url}", ${valueField}: value })`);
4653
+ } else {
4654
+ this.line(
4655
+ `const target = getOrCreateObjectAtPath(this.resource as unknown as Record<string, unknown>, ${JSON.stringify(
4656
+ targetPath
4657
+ )})`
4658
+ );
4659
+ this.line("if (!Array.isArray(target.extension)) target.extension = [] as Extension[]");
4660
+ this.line(
4661
+ `(target.extension as Extension[]).push({ url: "${ext.url}", ${valueField}: value })`
4662
+ );
4663
+ }
4664
+ this.line("return this");
4665
+ });
4666
+ } else {
4667
+ this.curlyBlock(["public", methodName, `(value: Omit<Extension, "url">): this`], () => {
4668
+ if (targetPath.length === 0) {
4669
+ this.line("const list = (this.resource.extension ??= [])");
4670
+ this.line(`list.push({ url: "${ext.url}", ...value })`);
4671
+ } else {
4672
+ this.line(
4673
+ `const target = getOrCreateObjectAtPath(this.resource as unknown as Record<string, unknown>, ${JSON.stringify(
4674
+ targetPath
4675
+ )})`
4676
+ );
4677
+ this.line("if (!Array.isArray(target.extension)) target.extension = [] as Extension[]");
4678
+ this.line(`(target.extension as Extension[]).push({ url: "${ext.url}", ...value })`);
4679
+ }
4680
+ this.line("return this");
4681
+ });
4682
+ }
4683
+ this.line();
4684
+ }
4685
+ for (const sliceDef of sliceDefs) {
4686
+ const methodName = sliceMethodNames.get(sliceDef) ?? tsSliceMethodFallback(sliceDef.fieldName, sliceDef.sliceName);
4687
+ const typeName = tsSliceInputTypeName(tsProfileName, sliceDef.fieldName, sliceDef.sliceName);
4688
+ const matchLiteral = JSON.stringify(sliceDef.match);
4689
+ const tsField = tsFieldName(sliceDef.fieldName);
4690
+ const fieldAccess = tsGet("this.resource", tsField);
4691
+ const paramSignature = sliceDef.inputOptional ? `(input?: ${typeName}): this` : `(input: ${typeName}): this`;
4692
+ this.curlyBlock(["public", methodName, paramSignature], () => {
4693
+ this.line(`const match = ${matchLiteral} as Record<string, unknown>`);
4694
+ const inputExpr = sliceDef.inputOptional ? "(input ?? {}) as Record<string, unknown>" : "input as Record<string, unknown>";
4695
+ this.line(`const value = applySliceMatch(${inputExpr}, match) as unknown as ${sliceDef.baseType}`);
4696
+ if (sliceDef.array) {
4697
+ this.line(`const list = (${fieldAccess} ??= [])`);
4698
+ this.line("const index = list.findIndex((item) => matchesSlice(item, match))");
4699
+ this.line("if (index === -1) {");
4700
+ this.indentBlock(() => {
4701
+ this.line("list.push(value)");
4702
+ });
4703
+ this.line("} else {");
4704
+ this.indentBlock(() => {
4705
+ this.line("list[index] = value");
4706
+ });
4707
+ this.line("}");
4708
+ } else {
4709
+ this.line(`${fieldAccess} = value`);
4710
+ }
4711
+ this.line("return this");
4712
+ });
4713
+ this.line();
4714
+ }
4715
+ const generatedGetMethods = /* @__PURE__ */ new Set();
4716
+ for (const ext of extensions) {
4717
+ if (!ext.url) continue;
4718
+ const baseName = uppercaseFirstLetter(safeCamelCase(ext.name));
4719
+ const getMethodName = `get${baseName}`;
4720
+ const getExtensionMethodName = `get${baseName}Extension`;
4721
+ if (generatedGetMethods.has(getMethodName)) continue;
4722
+ generatedGetMethods.add(getMethodName);
4723
+ const valueTypes = ext.valueTypes ?? [];
4724
+ const targetPath = ext.path.split(".").filter((segment) => segment !== "extension");
4725
+ const generateExtLookup = () => {
4726
+ if (targetPath.length === 0) {
4727
+ this.line(`const ext = this.resource.extension?.find(e => e.url === "${ext.url}")`);
4728
+ } else {
4729
+ this.line(
4730
+ `const target = getOrCreateObjectAtPath(this.resource as unknown as Record<string, unknown>, ${JSON.stringify(targetPath)})`
4731
+ );
4732
+ this.line(
4733
+ `const ext = (target.extension as Extension[] | undefined)?.find(e => e.url === "${ext.url}")`
4734
+ );
4735
+ }
4736
+ };
4737
+ if (ext.isComplex && ext.subExtensions) {
4738
+ const inputTypeName = tsExtensionInputTypeName(tsProfileName, ext.name);
4739
+ this.curlyBlock(["public", getMethodName, `(): ${inputTypeName} | undefined`], () => {
4740
+ generateExtLookup();
4741
+ this.line("if (!ext) return undefined");
4742
+ const configItems = (ext.subExtensions ?? []).map((sub) => {
4743
+ const valueField = sub.valueType ? `value${uppercaseFirstLetter(sub.valueType.name)}` : "value";
4744
+ const isArray = sub.max === "*";
4745
+ return `{ name: "${sub.url}", valueField: "${valueField}", isArray: ${isArray} }`;
4746
+ });
4747
+ this.line(`const config = [${configItems.join(", ")}]`);
4748
+ this.line(
4749
+ `return extractComplexExtension(ext as unknown as { extension?: Array<{ url?: string; [key: string]: unknown }> }, config) as ${inputTypeName}`
4750
+ );
4751
+ });
4752
+ this.line();
4753
+ this.curlyBlock(["public", getExtensionMethodName, "(): Extension | undefined"], () => {
4754
+ generateExtLookup();
4755
+ this.line("return ext");
4756
+ });
4757
+ } else if (valueTypes.length === 1 && valueTypes[0]) {
4758
+ const firstValueType = valueTypes[0];
4759
+ const valueType = tsTypeFromIdentifier(firstValueType);
4760
+ const valueField = `value${uppercaseFirstLetter(firstValueType.name)}`;
4761
+ this.curlyBlock(["public", getMethodName, `(): ${valueType} | undefined`], () => {
4762
+ generateExtLookup();
4763
+ this.line(`return ext?.${valueField}`);
4764
+ });
4765
+ this.line();
4766
+ this.curlyBlock(["public", getExtensionMethodName, "(): Extension | undefined"], () => {
4767
+ generateExtLookup();
4768
+ this.line("return ext");
4769
+ });
4770
+ } else {
4771
+ this.curlyBlock(["public", getMethodName, "(): Extension | undefined"], () => {
4772
+ if (targetPath.length === 0) {
4773
+ this.line(`return this.resource.extension?.find(e => e.url === "${ext.url}")`);
4774
+ } else {
4775
+ this.line(
4776
+ `const target = getOrCreateObjectAtPath(this.resource as unknown as Record<string, unknown>, ${JSON.stringify(targetPath)})`
4777
+ );
4778
+ this.line(
4779
+ `return (target.extension as Extension[] | undefined)?.find(e => e.url === "${ext.url}")`
4780
+ );
4781
+ }
4782
+ });
4783
+ }
4784
+ this.line();
4785
+ }
4786
+ for (const sliceDef of sliceDefs) {
4787
+ const baseName = uppercaseFirstLetter(safeCamelCase(sliceDef.sliceName));
4788
+ const getMethodName = `get${baseName}`;
4789
+ const getRawMethodName = `get${baseName}Raw`;
4790
+ if (generatedGetMethods.has(getMethodName)) continue;
4791
+ generatedGetMethods.add(getMethodName);
4792
+ const typeName = tsSliceInputTypeName(tsProfileName, sliceDef.fieldName, sliceDef.sliceName);
4793
+ const matchLiteral = JSON.stringify(sliceDef.match);
4794
+ const matchKeys = JSON.stringify(Object.keys(sliceDef.match));
4795
+ const tsField = tsFieldName(sliceDef.fieldName);
4796
+ const fieldAccess = tsGet("this.resource", tsField);
4797
+ const baseType = sliceDef.baseType;
4798
+ const generateSliceLookup = () => {
4799
+ this.line(`const match = ${matchLiteral} as Record<string, unknown>`);
4800
+ if (sliceDef.array) {
4801
+ this.line(`const list = ${fieldAccess}`);
4802
+ this.line("if (!list) return undefined");
4803
+ this.line("const item = list.find((item) => matchesSlice(item, match))");
4804
+ } else {
4805
+ this.line(`const item = ${fieldAccess}`);
4806
+ this.line("if (!item || !matchesSlice(item, match)) return undefined");
4807
+ }
4808
+ };
4809
+ this.curlyBlock(["public", getMethodName, `(): ${typeName} | undefined`], () => {
4810
+ generateSliceLookup();
4811
+ if (sliceDef.array) {
4812
+ this.line("if (!item) return undefined");
4813
+ }
4814
+ this.line(
4815
+ `return extractSliceSimplified(item as unknown as Record<string, unknown>, ${matchKeys}) as ${typeName}`
4816
+ );
4817
+ });
4818
+ this.line();
4819
+ this.curlyBlock(["public", getRawMethodName, `(): ${baseType} | undefined`], () => {
4820
+ generateSliceLookup();
4821
+ if (sliceDef.array) {
4822
+ this.line("return item");
4823
+ } else {
4824
+ this.line("return item");
4825
+ }
4826
+ });
4827
+ this.line();
4828
+ }
4829
+ });
4830
+ this.line();
4831
+ }
4832
+ /**
4833
+ * Detects fields where the profile changes cardinality or narrows Reference types
4834
+ * compared to the base resource type.
4835
+ */
4836
+ detectFieldOverrides(tsIndex, flatProfile) {
4837
+ const overrides = /* @__PURE__ */ new Map();
4838
+ const specialization = tsIndex.findLastSpecialization(flatProfile);
4839
+ if (!isSpecializationTypeSchema(specialization)) return overrides;
4840
+ for (const [fieldName, pField] of Object.entries(flatProfile.fields ?? {})) {
4841
+ if (!isNotChoiceDeclarationField(pField)) continue;
4842
+ const sField = specialization.fields?.[fieldName];
4843
+ if (!sField || isChoiceDeclarationField(sField)) continue;
4844
+ if (pField.reference && sField.reference && pField.reference.length < sField.reference.length) {
4845
+ const references = pField.reference.map((ref) => {
4846
+ const resRef = tsIndex.findLastSpecializationByIdentifier(ref);
4847
+ if (resRef.name !== ref.name) {
4848
+ return `"${resRef.name}"`;
4849
+ }
4850
+ return `"${ref.name}"`;
4851
+ }).join(" | ");
4852
+ overrides.set(fieldName, {
4853
+ profileType: `Reference<${references}>`,
4854
+ required: pField.required ?? false,
4855
+ array: pField.array ?? false
4856
+ });
4857
+ } else if (pField.required && !sField.required) {
4858
+ const tsType = this.tsTypeForProfileField(tsIndex, flatProfile, fieldName, pField);
4859
+ overrides.set(fieldName, {
4860
+ profileType: tsType,
4861
+ required: true,
4862
+ array: pField.array ?? false
4863
+ });
4864
+ }
4865
+ }
4866
+ return overrides;
4867
+ }
4868
+ /**
4869
+ * Generates an override interface for profiles that narrow cardinality or Reference types.
4870
+ * Example: export interface USCorePatient extends Patient { subject: Reference<"Patient"> }
4871
+ */
4872
+ generateProfileOverrideInterface(tsIndex, flatProfile) {
4873
+ const overrides = this.detectFieldOverrides(tsIndex, flatProfile);
4874
+ if (overrides.size === 0) return;
4875
+ const tsProfileName = tsResourceName(flatProfile.identifier);
4876
+ const tsBaseResourceName = tsResourceName(flatProfile.base);
4877
+ this.curlyBlock(["export", "interface", tsProfileName, "extends", tsBaseResourceName], () => {
4878
+ for (const [fieldName, override] of overrides) {
4879
+ const tsField = tsFieldName(fieldName);
4880
+ const optionalSymbol = override.required ? "" : "?";
4881
+ const arraySymbol = override.array ? "[]" : "";
4882
+ this.lineSM(`${tsField}${optionalSymbol}: ${override.profileType}${arraySymbol}`);
4883
+ }
4884
+ });
4885
+ this.line();
4886
+ }
4887
+ generateResourceModule(tsIndex, schema) {
4888
+ if (isProfileTypeSchema(schema)) {
4889
+ this.cd("profiles", () => {
4890
+ this.cat(`${tsModuleFileName(schema.identifier)}`, () => {
4891
+ this.generateDisclaimer();
4892
+ const flatProfile = tsIndex.flatProfile(schema);
4893
+ this.generateProfileImports(tsIndex, flatProfile);
4894
+ this.comment(
4895
+ "CanonicalURL:",
4896
+ schema.identifier.url,
4897
+ `(pkg: ${packageMetaToFhir(packageMeta(schema))})`
4898
+ );
4899
+ this.generateProfileOverrideInterface(tsIndex, flatProfile);
4900
+ this.generateProfileClass(tsIndex, flatProfile);
4901
+ });
4902
+ });
4903
+ } else if (["complex-type", "resource", "logical"].includes(schema.identifier.kind)) {
4904
+ this.cat(`${tsModuleFileName(schema.identifier)}`, () => {
4905
+ this.generateDisclaimer();
3480
4906
  this.generateDependenciesImports(tsIndex, schema);
3481
4907
  this.generateComplexTypeReexports(schema);
3482
4908
  this.generateNestedTypes(tsIndex, schema);
3483
- this.comment("CanonicalURL:", schema.identifier.url);
4909
+ this.comment(
4910
+ "CanonicalURL:",
4911
+ schema.identifier.url,
4912
+ `(pkg: ${packageMetaToFhir(packageMeta(schema))})`
4913
+ );
3484
4914
  this.generateType(tsIndex, schema);
3485
4915
  this.generateResourceTypePredicate(schema);
3486
- } else if (isProfileTypeSchema(schema)) {
3487
- const flatProfile = tsIndex.flatProfile(schema);
3488
- this.generateDependenciesImports(tsIndex, flatProfile);
3489
- this.comment("CanonicalURL:", schema.identifier.url);
3490
- this.generateProfileType(tsIndex, flatProfile);
3491
- this.generateAttachProfile(flatProfile);
3492
- this.generateExtractProfile(tsIndex, flatProfile);
3493
- } else throw new Error(`Profile generation not implemented for kind: ${schema.identifier.kind}`);
3494
- });
4916
+ });
4917
+ } else {
4918
+ throw new Error(`Profile generation not implemented for kind: ${schema.identifier.kind}`);
4919
+ }
3495
4920
  }
3496
4921
  async generate(tsIndex) {
3497
4922
  const typesToGenerate = [
3498
4923
  ...tsIndex.collectComplexTypes(),
3499
4924
  ...tsIndex.collectResources(),
3500
4925
  ...tsIndex.collectLogicalModels(),
3501
- ...this.opts.generateProfile ? tsIndex.collectProfiles().filter((p) => tsIndex.isWithMetaField(p)) : []
4926
+ ...this.opts.generateProfile ? tsIndex.collectProfiles() : []
3502
4927
  ];
3503
4928
  const grouped = groupByPackages(typesToGenerate);
4929
+ const hasProfiles = this.opts.generateProfile && typesToGenerate.some(isProfileTypeSchema);
3504
4930
  this.cd("/", () => {
4931
+ if (hasProfiles) {
4932
+ this.generateProfileHelpersModule();
4933
+ }
3505
4934
  for (const [packageName, packageSchemas] of Object.entries(grouped)) {
3506
4935
  const tsPackageDir = tsFhirPackageDir(packageName);
3507
4936
  this.cd(tsPackageDir, () => {
3508
4937
  for (const schema of packageSchemas) {
3509
4938
  this.generateResourceModule(tsIndex, schema);
3510
4939
  }
4940
+ this.generateProfileIndexFile(packageSchemas.filter(isProfileTypeSchema));
3511
4941
  this.generateFhirPackageIndexFile(packageSchemas);
3512
4942
  });
3513
4943
  }
@@ -3516,13 +4946,18 @@ var TypeScript = class extends Writer {
3516
4946
  };
3517
4947
 
3518
4948
  // src/api/builder.ts
4949
+ function countLinesByMatches(text) {
4950
+ if (text === "") return 0;
4951
+ const m = text.match(/\n/g);
4952
+ return m ? m.length + 1 : 1;
4953
+ }
3519
4954
  var prettyReport = (report) => {
3520
4955
  const { success, filesGenerated, errors, warnings, duration } = report;
3521
4956
  const errorsStr = errors.length > 0 ? `Errors: ${errors.join(", ")}` : void 0;
3522
4957
  const warningsStr = warnings.length > 0 ? `Warnings: ${warnings.join(", ")}` : void 0;
3523
4958
  let allLoc = 0;
3524
4959
  const files = Object.entries(filesGenerated).map(([path, content]) => {
3525
- const loc = content.split("\n").length;
4960
+ const loc = countLinesByMatches(content);
3526
4961
  allLoc += loc;
3527
4962
  return ` - ${path} (${loc} loc)`;
3528
4963
  }).join("\n");
@@ -3535,128 +4970,64 @@ var prettyReport = (report) => {
3535
4970
  `Status: ${success ? "\u{1F7E9} Success" : "\u{1F7E5} Failure"}`
3536
4971
  ].filter((e) => e).join("\n");
3537
4972
  };
3538
- var normalizeFileName = (str) => {
3539
- const res = str.replace(/[^a-zA-Z0-9\-_.@#()]/g, "");
3540
- if (res.length === 0) return "unknown";
3541
- return res;
3542
- };
3543
4973
  var cleanup = async (opts, logger) => {
3544
4974
  logger.info(`Cleaning outputs...`);
3545
4975
  try {
3546
4976
  logger.info(`Clean ${opts.outputDir}`);
3547
4977
  fs.rmSync(opts.outputDir, { recursive: true, force: true });
3548
- if (opts.typeSchemaOutputDir) {
3549
- logger.info(`Clean ${opts.typeSchemaOutputDir}`);
3550
- fs.rmSync(opts.typeSchemaOutputDir, {
3551
- recursive: true,
3552
- force: true
3553
- });
3554
- }
3555
- if (opts.exportTypeTree) {
3556
- logger.info(`Clean ${opts.exportTypeTree}`);
3557
- fs.rmSync(opts.exportTypeTree, {
3558
- recursive: true,
3559
- force: true
3560
- });
3561
- }
3562
4978
  } catch (error) {
3563
4979
  logger.warn(`Error cleaning output directory: ${error instanceof Error ? error.message : String(error)}`);
3564
4980
  }
3565
4981
  };
3566
- var writeTypeSchemasToSeparateFiles = async (typeSchemas, outputDir, logger) => {
3567
- await afs2.mkdir(outputDir, { recursive: true });
3568
- logger.info(`Writing TypeSchema files to ${outputDir}/...`);
3569
- const files = {};
3570
- for (const ts of typeSchemas) {
3571
- const pkg = {
3572
- name: ts.identifier.package,
3573
- version: ts.identifier.version
3574
- };
3575
- const pkgPath = normalizeFileName(packageMetaToFhir(pkg));
3576
- const name = normalizeFileName(`${ts.identifier.name}(${extractNameFromCanonical(ts.identifier.url)})`);
3577
- const json = JSON.stringify(ts, null, 2);
3578
- const baseName = Path.join(outputDir, pkgPath, name);
3579
- if (!files[baseName]) files[baseName] = [];
3580
- if (!files[baseName]?.some((e) => e === json)) {
3581
- files[baseName].push(json);
3582
- }
3583
- }
3584
- for (const [baseName, jsons] of Object.entries(files)) {
3585
- await Promise.all(
3586
- jsons.map(async (json, index) => {
3587
- let fullName;
3588
- if (index === 0) {
3589
- fullName = `${baseName}.typeschema.json`;
3590
- } else {
3591
- fullName = `${baseName}-${index}.typeschema.json`;
3592
- }
3593
- await afs2.mkdir(Path.dirname(fullName), { recursive: true });
3594
- await afs2.writeFile(fullName, json);
3595
- })
3596
- );
3597
- }
3598
- };
3599
- var writeTypeSchemasToSingleFile = async (typeSchemas, outputFile, logger) => {
3600
- logger.info(`Writing TypeSchema files to: ${outputFile}`);
3601
- await afs2.mkdir(Path.dirname(outputFile), { recursive: true });
3602
- logger.info(`Writing TypeSchemas to one file ${outputFile}...`);
3603
- for (const ts of typeSchemas) {
3604
- const json = JSON.stringify(ts, null, 2);
3605
- await afs2.appendFile(outputFile, `${json}
3606
- `);
3607
- }
3608
- };
3609
- var tryWriteTypeSchema = async (typeSchemas, opts, logger) => {
3610
- if (!opts.typeSchemaOutputDir) return;
3611
- try {
3612
- if (Path.extname(opts.typeSchemaOutputDir) === ".ndjson") {
3613
- await writeTypeSchemasToSingleFile(typeSchemas, opts.typeSchemaOutputDir, logger);
3614
- } else {
3615
- await writeTypeSchemasToSeparateFiles(typeSchemas, opts.typeSchemaOutputDir, logger);
3616
- }
3617
- logger.info(`Writing TypeSchema - DONE`);
3618
- } catch (error) {
3619
- logger.error("Failed to write TypeSchema output", error instanceof Error ? error : new Error(String(error)));
3620
- if (opts.throwException) throw error;
3621
- }
3622
- };
3623
4982
  var APIBuilder = class {
3624
- schemas = [];
3625
4983
  options;
3626
- generators = /* @__PURE__ */ new Map();
4984
+ manager;
4985
+ prebuiltRegister;
4986
+ managerInput;
3627
4987
  logger;
3628
- packages = [];
3629
- localStructurePackages = [];
3630
- localTgzArchives = [];
3631
- progressCallback;
3632
- typeSchemaConfig;
3633
- constructor(options = {}) {
3634
- this.options = {
3635
- outputDir: options.outputDir || "./generated",
3636
- overwrite: options.overwrite ?? true,
3637
- cache: options.cache ?? true,
3638
- cleanOutput: options.cleanOutput ?? true,
3639
- typeSchemaConfig: options.typeSchemaConfig,
3640
- manager: options.manager || null,
3641
- throwException: options.throwException || false,
3642
- typeSchemaOutputDir: options.typeSchemaOutputDir,
3643
- exportTypeTree: options.exportTypeTree,
3644
- treeShake: options.treeShake,
3645
- registry: options.registry
4988
+ generators = [];
4989
+ constructor(userOpts = {}) {
4990
+ const defaultOpts = {
4991
+ outputDir: "./generated",
4992
+ cleanOutput: true,
4993
+ throwException: false,
4994
+ treeShake: void 0,
4995
+ promoteLogical: void 0,
4996
+ registry: void 0,
4997
+ logLevel: parseLogLevel("INFO")
3646
4998
  };
3647
- this.typeSchemaConfig = options.typeSchemaConfig;
3648
- this.logger = options.logger || createLogger({
3649
- prefix: "API",
3650
- level: options.logLevel
4999
+ const opts = {
5000
+ ...defaultOpts,
5001
+ ...Object.fromEntries(
5002
+ Object.entries(userOpts).filter(
5003
+ ([k, v]) => v !== void 0 && k !== "manager" && k !== "register" && k !== "logger"
5004
+ )
5005
+ )
5006
+ };
5007
+ if (userOpts.manager && userOpts.register) {
5008
+ throw new Error("Cannot provide both 'manager' and 'register' options. Use one or the other.");
5009
+ }
5010
+ this.managerInput = {
5011
+ npmPackages: [],
5012
+ localSDs: [],
5013
+ localTgzPackages: []
5014
+ };
5015
+ this.prebuiltRegister = userOpts.register;
5016
+ this.manager = userOpts.manager ?? CanonicalManager({
5017
+ packages: [],
5018
+ workingDir: ".codegen-cache/canonical-manager-cache",
5019
+ registry: userOpts.registry
3651
5020
  });
5021
+ this.logger = userOpts.logger ?? createLogger({ prefix: "API", level: opts.logLevel });
5022
+ this.options = opts;
3652
5023
  }
3653
5024
  fromPackage(packageName, version) {
3654
5025
  const pkg = packageMetaToNpm({ name: packageName, version: version || "latest" });
3655
- this.packages.push(pkg);
5026
+ this.managerInput.npmPackages.push(pkg);
3656
5027
  return this;
3657
5028
  }
3658
5029
  fromPackageRef(packageRef) {
3659
- this.packages.push(packageRef);
5030
+ this.managerInput.npmPackages.push(packageRef);
3660
5031
  return this;
3661
5032
  }
3662
5033
  /**
@@ -3668,22 +5039,39 @@ var APIBuilder = class {
3668
5039
  return this;
3669
5040
  }
3670
5041
  localStructureDefinitions(config) {
3671
- this.localStructurePackages.push(config);
5042
+ this.logger.info(`Registering local StructureDefinitions for ${config.package.name}@${config.package.version}`);
5043
+ this.managerInput.localSDs.push({
5044
+ name: config.package.name,
5045
+ version: config.package.version,
5046
+ path: config.path,
5047
+ dependencies: config.dependencies?.map((dep) => packageMetaToNpm(dep))
5048
+ });
3672
5049
  return this;
3673
5050
  }
3674
5051
  localTgzPackage(archivePath) {
3675
- this.localTgzArchives.push(Path.resolve(archivePath));
5052
+ this.logger.info(`Registering local tgz package: ${archivePath}`);
5053
+ this.managerInput.localTgzPackages.push({ archivePath: Path5.resolve(archivePath) });
3676
5054
  return this;
3677
5055
  }
3678
- fromSchemas(schemas) {
3679
- this.logger.debug(`Adding ${schemas.length} TypeSchemas to generation`);
3680
- this.schemas = [...this.schemas, ...schemas];
5056
+ introspection(userOpts) {
5057
+ const defaultWriterOpts = {
5058
+ logger: this.logger,
5059
+ outputDir: this.options.outputDir,
5060
+ inMemoryOnly: false
5061
+ };
5062
+ const opts = {
5063
+ ...defaultWriterOpts,
5064
+ ...Object.fromEntries(Object.entries(userOpts ?? {}).filter(([_, v]) => v !== void 0))
5065
+ };
5066
+ const writer = new IntrospectionWriter(opts);
5067
+ this.generators.push({ name: "introspection", writer });
5068
+ this.logger.debug(`Configured introspection generator (${JSON.stringify(opts, void 0, 2)})`);
3681
5069
  return this;
3682
5070
  }
3683
5071
  typescript(userOpts) {
3684
5072
  const defaultWriterOpts = {
3685
5073
  logger: this.logger,
3686
- outputDir: Path.join(this.options.outputDir, "/types"),
5074
+ outputDir: Path5.join(this.options.outputDir, "/types"),
3687
5075
  tabSize: 4,
3688
5076
  withDebugComment: false,
3689
5077
  commentLinePrefix: "//",
@@ -3699,7 +5087,7 @@ var APIBuilder = class {
3699
5087
  ...Object.fromEntries(Object.entries(userOpts).filter(([_, v]) => v !== void 0))
3700
5088
  };
3701
5089
  const generator = new TypeScript(opts);
3702
- this.generators.set("typescript", generator);
5090
+ this.generators.push({ name: "typescript", writer: generator });
3703
5091
  this.logger.debug(`Configured TypeScript generator (${JSON.stringify(opts, void 0, 2)})`);
3704
5092
  return this;
3705
5093
  }
@@ -3721,7 +5109,7 @@ var APIBuilder = class {
3721
5109
  ...Object.fromEntries(Object.entries(userOptions).filter(([_, v]) => v !== void 0))
3722
5110
  };
3723
5111
  const generator = new Python(opts);
3724
- this.generators.set("python", generator);
5112
+ this.generators.push({ name: "python", writer: generator });
3725
5113
  this.logger.debug(`Configured python generator`);
3726
5114
  return this;
3727
5115
  }
@@ -3742,14 +5130,14 @@ var APIBuilder = class {
3742
5130
  ...userOpts
3743
5131
  };
3744
5132
  const generator = createGenerator(templatePath, opts);
3745
- this.generators.set(`mustache[${templatePath}]`, generator);
5133
+ this.generators.push({ name: `mustache[${templatePath}]`, writer: generator });
3746
5134
  this.logger.debug(`Configured TypeScript generator (${JSON.stringify(opts, void 0, 2)})`);
3747
5135
  return this;
3748
5136
  }
3749
5137
  csharp(userOptions) {
3750
5138
  const defaultWriterOpts = {
3751
5139
  logger: this.logger,
3752
- outputDir: Path.join(this.options.outputDir, "/types"),
5140
+ outputDir: Path5.join(this.options.outputDir, "/types"),
3753
5141
  tabSize: 4,
3754
5142
  withDebugComment: false,
3755
5143
  commentLinePrefix: "//"
@@ -3763,25 +5151,18 @@ var APIBuilder = class {
3763
5151
  ...Object.fromEntries(Object.entries(userOptions).filter(([_, v]) => v !== void 0))
3764
5152
  };
3765
5153
  const generator = new CSharp(opts);
3766
- this.generators.set("csharp", generator);
5154
+ this.generators.push({ name: "csharp", writer: generator });
3767
5155
  this.logger.debug(`Configured C# generator`);
3768
5156
  return this;
3769
5157
  }
3770
- /**
3771
- * Set a progress callback for monitoring generation
3772
- */
3773
- onProgress(callback) {
3774
- this.progressCallback = callback;
3775
- return this;
3776
- }
3777
5158
  /**
3778
5159
  * Set the output directory for all generators
3779
5160
  */
3780
5161
  outputTo(directory) {
3781
5162
  this.logger.debug(`Setting output directory: ${directory}`);
3782
5163
  this.options.outputDir = directory;
3783
- for (const generator of this.generators.values()) {
3784
- generator.setOutputDir(directory);
5164
+ for (const gen of this.generators) {
5165
+ gen.writer.setOutputDir(directory);
3785
5166
  }
3786
5167
  return this;
3787
5168
  }
@@ -3797,16 +5178,32 @@ var APIBuilder = class {
3797
5178
  this.options.cleanOutput = enabled;
3798
5179
  return this;
3799
5180
  }
3800
- writeTypeTree(filename) {
3801
- this.options.exportTypeTree = filename;
3802
- return this;
3803
- }
3804
- treeShake(tree) {
3805
- this.options.treeShake = tree;
5181
+ typeSchema(cfg) {
5182
+ if (cfg.treeShake) {
5183
+ assert3(this.options.treeShake === void 0, "treeShake option is already set");
5184
+ this.options.treeShake = cfg.treeShake;
5185
+ }
5186
+ if (cfg.promoteLogical) {
5187
+ assert3(this.options.promoteLogical === void 0, "promoteLogical option is already set");
5188
+ this.options.promoteLogical = cfg.promoteLogical;
5189
+ }
5190
+ this.irReport({});
3806
5191
  return this;
3807
5192
  }
3808
- writeTypeSchemas(target) {
3809
- this.options.typeSchemaOutputDir = target;
5193
+ irReport(userOpts) {
5194
+ const defaultWriterOpts = {
5195
+ logger: this.logger,
5196
+ outputDir: this.options.outputDir,
5197
+ inMemoryOnly: false
5198
+ };
5199
+ const opts = {
5200
+ ...defaultWriterOpts,
5201
+ rootReadmeFileName: "README.md",
5202
+ ...Object.fromEntries(Object.entries(userOpts ?? {}).filter(([_, v]) => v !== void 0))
5203
+ };
5204
+ const writer = new IrReportWriterWriter(opts);
5205
+ this.generators.push({ name: "ir-report", writer });
5206
+ this.logger.debug(`Configured ir-report generator (${JSON.stringify(opts, void 0, 2)})`);
3810
5207
  return this;
3811
5208
  }
3812
5209
  async generate() {
@@ -3819,48 +5216,40 @@ var APIBuilder = class {
3819
5216
  warnings: [],
3820
5217
  duration: 0
3821
5218
  };
3822
- this.logger.debug(`Starting generation with ${this.generators.size} generators`);
5219
+ this.logger.debug(`Starting generation with ${this.generators.length} generators`);
3823
5220
  try {
3824
5221
  if (this.options.cleanOutput) cleanup(this.options, this.logger);
3825
- this.logger.info("Initialize Canonical Manager");
3826
- const manager = this.options.manager || CanonicalManager({
3827
- packages: this.packages,
3828
- workingDir: ".codegen-cache/canonical-manager-cache",
3829
- registry: this.options.registry || void 0
3830
- });
3831
- if (this.localStructurePackages.length > 0) {
3832
- for (const config of this.localStructurePackages) {
3833
- this.logger.info(
3834
- `Registering local StructureDefinitions for ${config.package.name}@${config.package.version}`
3835
- );
3836
- await manager.addLocalPackage({
3837
- name: config.package.name,
3838
- version: config.package.version,
3839
- path: config.path,
3840
- dependencies: config.dependencies?.map((dep) => packageMetaToNpm(dep))
3841
- });
5222
+ let register;
5223
+ if (this.prebuiltRegister) {
5224
+ this.logger.info("Using prebuilt register");
5225
+ register = this.prebuiltRegister;
5226
+ } else {
5227
+ this.logger.info("Initialize Canonical Manager");
5228
+ if (this.managerInput.npmPackages.length > 0) {
5229
+ await this.manager.addPackages(...this.managerInput.npmPackages.sort());
3842
5230
  }
5231
+ for (const config of this.managerInput.localSDs) {
5232
+ await this.manager.addLocalPackage(config);
5233
+ }
5234
+ for (const tgzArchive of this.managerInput.localTgzPackages) {
5235
+ await this.manager.addTgzPackage(tgzArchive);
5236
+ }
5237
+ const ref2meta = await this.manager.init();
5238
+ const packageMetas = Object.values(ref2meta);
5239
+ register = await registerFromManager(this.manager, {
5240
+ logger: this.logger,
5241
+ focusedPackages: packageMetas
5242
+ });
3843
5243
  }
3844
- for (const archivePath of this.localTgzArchives) {
3845
- this.logger.info(`Registering local tgz package: ${archivePath}`);
3846
- await manager.addTgzPackage({ archivePath });
3847
- }
3848
- const ref2meta = await manager.init();
3849
- const packageMetas = Object.values(ref2meta);
3850
- const register = await registerFromManager(manager, {
3851
- logger: this.logger,
3852
- focusedPackages: packageMetas
3853
- });
3854
5244
  const typeSchemas = await generateTypeSchemas(register, this.logger);
3855
- await tryWriteTypeSchema(typeSchemas, this.options, this.logger);
3856
5245
  const tsIndexOpts = {
3857
- resolutionTree: register.resolutionTree(),
5246
+ register,
3858
5247
  logger: this.logger
3859
5248
  };
3860
5249
  let tsIndex = mkTypeSchemaIndex(typeSchemas, tsIndexOpts);
3861
- if (this.options.treeShake) tsIndex = treeShake(tsIndex, this.options.treeShake, tsIndexOpts);
3862
- if (this.options.exportTypeTree) await tsIndex.exportTree(this.options.exportTypeTree);
3863
- this.logger.debug(`Executing ${this.generators.size} generators`);
5250
+ if (this.options.treeShake) tsIndex = treeShake(tsIndex, this.options.treeShake);
5251
+ if (this.options.promoteLogical) tsIndex = promoteLogical(tsIndex, this.options.promoteLogical);
5252
+ this.logger.debug(`Executing ${this.generators.length} generators`);
3864
5253
  await this.executeGenerators(result, tsIndex);
3865
5254
  this.logger.info("Generation completed successfully");
3866
5255
  result.success = result.errors.length === 0;
@@ -3880,559 +5269,35 @@ var APIBuilder = class {
3880
5269
  * Clear all configuration and start fresh
3881
5270
  */
3882
5271
  reset() {
3883
- this.schemas = [];
3884
- this.generators.clear();
3885
- this.progressCallback = void 0;
3886
- this.packages = [];
3887
- this.localStructurePackages = [];
3888
- this.localTgzArchives = [];
5272
+ this.generators = [];
3889
5273
  return this;
3890
5274
  }
3891
- /**
3892
- * Get loaded schemas (for inspection)
3893
- */
3894
- getSchemas() {
3895
- return [...this.schemas];
3896
- }
3897
5275
  /**
3898
5276
  * Get configured generators (for inspection)
3899
5277
  */
3900
5278
  getGenerators() {
3901
- return Array.from(this.generators.keys());
5279
+ return this.generators.map((g) => g.name);
3902
5280
  }
3903
5281
  async executeGenerators(result, tsIndex) {
3904
- for (const [type, generator] of this.generators.entries()) {
3905
- this.logger.info(`Generating ${type}...`);
5282
+ for (const gen of this.generators) {
5283
+ this.logger.info(`Generating ${gen.name}...`);
3906
5284
  try {
3907
- await generator.generate(tsIndex);
3908
- const fileBuffer = generator.writtenFiles();
5285
+ await gen.writer.generateAsync(tsIndex);
5286
+ const fileBuffer = gen.writer.writtenFiles();
3909
5287
  fileBuffer.forEach((buf) => {
3910
5288
  result.filesGenerated[buf.relPath] = buf.content;
3911
5289
  });
3912
- this.logger.info(`Generating ${type} finished successfully`);
5290
+ this.logger.info(`Generating ${gen.name} finished successfully`);
3913
5291
  } catch (error) {
3914
5292
  result.errors.push(
3915
- `${type} generator failed: ${error instanceof Error ? error.message : String(error)}`
5293
+ `${gen.name} generator failed: ${error instanceof Error ? error.message : String(error)}`
3916
5294
  );
3917
5295
  if (this.options.throwException) throw error;
3918
5296
  }
3919
5297
  }
3920
5298
  }
3921
5299
  };
3922
- var DEFAULT_CONFIG = {
3923
- outputDir: "./generated",
3924
- verbose: false,
3925
- overwrite: true,
3926
- validate: true,
3927
- cache: true,
3928
- cleanOutput: true,
3929
- registry: "",
3930
- typescript: {
3931
- moduleFormat: "esm",
3932
- generateIndex: true,
3933
- includeDocuments: false,
3934
- namingConvention: "PascalCase",
3935
- strictMode: true,
3936
- includeProfiles: true,
3937
- includeExtensions: false,
3938
- includeCodeSystems: false,
3939
- includeOperations: false,
3940
- generateValueSets: false,
3941
- valueSetDirectory: "valuesets",
3942
- valueSetMode: "required-only",
3943
- valueSetStrengths: ["required"],
3944
- includeValueSetHelpers: false,
3945
- fhirVersion: "R4",
3946
- resourceTypes: [],
3947
- maxDepth: 10,
3948
- // Profile generation defaults
3949
- profileOptions: {
3950
- generateKind: "interface",
3951
- includeConstraints: true,
3952
- includeDocumentation: true,
3953
- strictMode: false,
3954
- subfolder: "profiles"
3955
- },
3956
- // Builder generation defaults
3957
- generateBuilders: false,
3958
- builderOptions: {
3959
- includeValidation: true,
3960
- includeFactoryMethods: true,
3961
- includeInterfaces: true,
3962
- generateNestedBuilders: true,
3963
- includeHelperMethods: true,
3964
- supportPartialBuild: true,
3965
- includeJSDoc: true,
3966
- generateFactories: true,
3967
- includeTypeGuards: true,
3968
- handleChoiceTypes: true,
3969
- generateArrayHelpers: true
3970
- },
3971
- // Validator generation defaults
3972
- validatorOptions: {
3973
- includeCardinality: true,
3974
- includeTypes: true,
3975
- includeConstraints: true,
3976
- includeInvariants: false,
3977
- validateRequired: true,
3978
- allowAdditional: false,
3979
- strictValidation: false,
3980
- collectMetrics: false,
3981
- generateAssertions: true,
3982
- generatePartialValidators: true,
3983
- optimizePerformance: true,
3984
- includeJSDoc: true,
3985
- generateCompositeValidators: true
3986
- },
3987
- // Type guard generation defaults
3988
- guardOptions: {
3989
- includeRuntimeValidation: true,
3990
- includeErrorMessages: true,
3991
- treeShakeable: true,
3992
- targetTSVersion: "5.0",
3993
- strictGuards: false,
3994
- includeNullChecks: true,
3995
- verbose: false
3996
- }
3997
- },
3998
- typeSchema: {
3999
- enablePersistence: true,
4000
- cacheDir: ".typeschema-cache",
4001
- maxAge: 24 * 60 * 60 * 1e3,
4002
- // 24 hours in milliseconds
4003
- validateCached: true,
4004
- forceRegenerate: false,
4005
- shareCache: true,
4006
- cacheKeyPrefix: "",
4007
- treeshake: [],
4008
- singleFile: false,
4009
- profiles: {
4010
- autoDetect: true
4011
- }
4012
- },
4013
- packages: [],
4014
- files: [],
4015
- $schema: ""
4016
- };
4017
- var CONFIG_FILE_NAMES = [
4018
- "atomic-codegen.config.ts",
4019
- "atomic-codegen.config",
4020
- "atomic-codegen.config.json",
4021
- ".atomic-codegenrc",
4022
- "atomic-codegen.json",
4023
- ".atomic-codegen.json",
4024
- "codegen.config.json",
4025
- "codegen.json"
4026
- ];
4027
- var ConfigValidator = class {
4028
- /**
4029
- * Validate a configuration object
4030
- */
4031
- validate(config) {
4032
- const result = {
4033
- valid: true,
4034
- errors: [],
4035
- warnings: []
4036
- };
4037
- if (!config || typeof config !== "object") {
4038
- result.valid = false;
4039
- result.errors.push({
4040
- path: "root",
4041
- message: "Configuration must be an object",
4042
- value: config
4043
- });
4044
- return result;
4045
- }
4046
- const cfg = config;
4047
- if (cfg.outputDir !== void 0 && typeof cfg.outputDir !== "string") {
4048
- result.errors.push({
4049
- path: "outputDir",
4050
- message: "outputDir must be a string",
4051
- value: cfg.outputDir
4052
- });
4053
- }
4054
- const booleanFields = ["verbose", "overwrite", "validate", "cache"];
4055
- for (const field of booleanFields) {
4056
- if (cfg[field] !== void 0 && typeof cfg[field] !== "boolean") {
4057
- result.errors.push({
4058
- path: field,
4059
- message: `${field} must be a boolean`,
4060
- value: cfg[field]
4061
- });
4062
- }
4063
- }
4064
- if (cfg.typescript !== void 0) {
4065
- const tsErrors = this.validateTypeScriptConfig(cfg.typescript);
4066
- result.errors.push(...tsErrors);
4067
- }
4068
- if (cfg.typeSchema !== void 0) {
4069
- const tsErrors = this.validateTypeSchemaConfig(cfg.typeSchema);
4070
- result.errors.push(...tsErrors);
4071
- }
4072
- if (cfg.packages !== void 0) {
4073
- if (!Array.isArray(cfg.packages)) {
4074
- result.errors.push({
4075
- path: "packages",
4076
- message: "packages must be an array",
4077
- value: cfg.packages
4078
- });
4079
- } else {
4080
- cfg.packages.forEach((pkg, index) => {
4081
- if (typeof pkg !== "string") {
4082
- result.errors.push({
4083
- path: `packages[${index}]`,
4084
- message: "package name must be a string",
4085
- value: pkg
4086
- });
4087
- }
4088
- });
4089
- }
4090
- }
4091
- if (cfg.files !== void 0) {
4092
- if (!Array.isArray(cfg.files)) {
4093
- result.errors.push({
4094
- path: "files",
4095
- message: "files must be an array",
4096
- value: cfg.files
4097
- });
4098
- } else {
4099
- cfg.files.forEach((file, index) => {
4100
- if (typeof file !== "string") {
4101
- result.errors.push({
4102
- path: `files[${index}]`,
4103
- message: "file path must be a string",
4104
- value: file
4105
- });
4106
- }
4107
- });
4108
- }
4109
- }
4110
- result.valid = result.errors.length === 0;
4111
- if (result.valid) {
4112
- result.config = cfg;
4113
- }
4114
- return result;
4115
- }
4116
- validateTypeScriptConfig(config) {
4117
- const errors = [];
4118
- if (typeof config !== "object" || config === null) {
4119
- errors.push({
4120
- path: "typescript",
4121
- message: "typescript config must be an object",
4122
- value: config
4123
- });
4124
- return errors;
4125
- }
4126
- const cfg = config;
4127
- if (cfg.moduleFormat !== void 0) {
4128
- if (!["esm", "cjs"].includes(cfg.moduleFormat)) {
4129
- errors.push({
4130
- path: "typescript.moduleFormat",
4131
- message: 'moduleFormat must be "esm" or "cjs"',
4132
- value: cfg.moduleFormat
4133
- });
4134
- }
4135
- }
4136
- if (cfg.namingConvention !== void 0) {
4137
- if (!["PascalCase", "camelCase"].includes(cfg.namingConvention)) {
4138
- errors.push({
4139
- path: "typescript.namingConvention",
4140
- message: 'namingConvention must be "PascalCase" or "camelCase"',
4141
- value: cfg.namingConvention
4142
- });
4143
- }
4144
- }
4145
- const booleanFields = [
4146
- "generateIndex",
4147
- "includeDocuments",
4148
- "strictMode",
4149
- "includeProfiles",
4150
- "includeExtensions",
4151
- "includeCodeSystems",
4152
- "includeOperations",
4153
- "generateValueSets",
4154
- "includeValueSetHelpers"
4155
- ];
4156
- for (const field of booleanFields) {
4157
- if (cfg[field] !== void 0 && typeof cfg[field] !== "boolean") {
4158
- errors.push({
4159
- path: `typescript.${field}`,
4160
- message: `${field} must be a boolean`,
4161
- value: cfg[field]
4162
- });
4163
- }
4164
- }
4165
- if (cfg.validatorOptions !== void 0) {
4166
- const validatorErrors = this.validateValidatorOptions(cfg.validatorOptions);
4167
- errors.push(...validatorErrors);
4168
- }
4169
- if (cfg.guardOptions !== void 0) {
4170
- const guardErrors = this.validateGuardOptions(cfg.guardOptions);
4171
- errors.push(...guardErrors);
4172
- }
4173
- if (cfg.profileOptions !== void 0) {
4174
- const profileErrors = this.validateProfileOptions(cfg.profileOptions);
4175
- errors.push(...profileErrors);
4176
- }
4177
- return errors;
4178
- }
4179
- validateValidatorOptions(config) {
4180
- const errors = [];
4181
- if (typeof config !== "object" || config === null) {
4182
- errors.push({
4183
- path: "typescript.validatorOptions",
4184
- message: "validatorOptions must be an object",
4185
- value: config
4186
- });
4187
- return errors;
4188
- }
4189
- const cfg = config;
4190
- const booleanFields = [
4191
- "includeCardinality",
4192
- "includeTypes",
4193
- "includeConstraints",
4194
- "includeInvariants",
4195
- "validateRequired",
4196
- "allowAdditional",
4197
- "strictValidation",
4198
- "collectMetrics",
4199
- "generateAssertions",
4200
- "generatePartialValidators",
4201
- "optimizePerformance",
4202
- "includeJSDoc",
4203
- "generateCompositeValidators"
4204
- ];
4205
- for (const field of booleanFields) {
4206
- if (cfg[field] !== void 0 && typeof cfg[field] !== "boolean") {
4207
- errors.push({
4208
- path: `typescript.validatorOptions.${field}`,
4209
- message: `${field} must be a boolean`,
4210
- value: cfg[field]
4211
- });
4212
- }
4213
- }
4214
- return errors;
4215
- }
4216
- validateGuardOptions(config) {
4217
- const errors = [];
4218
- if (typeof config !== "object" || config === null) {
4219
- errors.push({
4220
- path: "typescript.guardOptions",
4221
- message: "guardOptions must be an object",
4222
- value: config
4223
- });
4224
- return errors;
4225
- }
4226
- const cfg = config;
4227
- if (cfg.targetTSVersion !== void 0) {
4228
- if (!["3.8", "4.0", "4.5", "5.0"].includes(cfg.targetTSVersion)) {
4229
- errors.push({
4230
- path: "typescript.guardOptions.targetTSVersion",
4231
- message: 'targetTSVersion must be one of: "3.8", "4.0", "4.5", "5.0"',
4232
- value: cfg.targetTSVersion
4233
- });
4234
- }
4235
- }
4236
- const booleanFields = [
4237
- "includeRuntimeValidation",
4238
- "includeErrorMessages",
4239
- "treeShakeable",
4240
- "strictGuards",
4241
- "includeNullChecks",
4242
- "verbose"
4243
- ];
4244
- for (const field of booleanFields) {
4245
- if (cfg[field] !== void 0 && typeof cfg[field] !== "boolean") {
4246
- errors.push({
4247
- path: `typescript.guardOptions.${field}`,
4248
- message: `${field} must be a boolean`,
4249
- value: cfg[field]
4250
- });
4251
- }
4252
- }
4253
- return errors;
4254
- }
4255
- validateProfileOptions(config) {
4256
- const errors = [];
4257
- if (typeof config !== "object" || config === null) {
4258
- errors.push({
4259
- path: "typescript.profileOptions",
4260
- message: "profileOptions must be an object",
4261
- value: config
4262
- });
4263
- return errors;
4264
- }
4265
- const cfg = config;
4266
- if (cfg.generateKind !== void 0) {
4267
- if (!["interface", "type", "both"].includes(cfg.generateKind)) {
4268
- errors.push({
4269
- path: "typescript.profileOptions.generateKind",
4270
- message: 'generateKind must be "interface", "type", or "both"',
4271
- value: cfg.generateKind
4272
- });
4273
- }
4274
- }
4275
- if (cfg.subfolder !== void 0 && typeof cfg.subfolder !== "string") {
4276
- errors.push({
4277
- path: "typescript.profileOptions.subfolder",
4278
- message: "subfolder must be a string",
4279
- value: cfg.subfolder
4280
- });
4281
- }
4282
- const booleanFields = ["includeConstraints", "includeDocumentation", "strictMode"];
4283
- for (const field of booleanFields) {
4284
- if (cfg[field] !== void 0 && typeof cfg[field] !== "boolean") {
4285
- errors.push({
4286
- path: `typescript.profileOptions.${field}`,
4287
- message: `${field} must be a boolean`,
4288
- value: cfg[field]
4289
- });
4290
- }
4291
- }
4292
- return errors;
4293
- }
4294
- validateTypeSchemaConfig(config) {
4295
- const errors = [];
4296
- if (typeof config !== "object" || config === null) {
4297
- errors.push({
4298
- path: "typeSchema",
4299
- message: "typeSchema config must be an object",
4300
- value: config
4301
- });
4302
- return errors;
4303
- }
4304
- const cfg = config;
4305
- const booleanFields = ["enablePersistence", "validateCached", "forceRegenerate", "shareCache"];
4306
- for (const field of booleanFields) {
4307
- if (cfg[field] !== void 0 && typeof cfg[field] !== "boolean") {
4308
- errors.push({
4309
- path: `typeSchema.${field}`,
4310
- message: `${field} must be a boolean`,
4311
- value: cfg[field]
4312
- });
4313
- }
4314
- }
4315
- const stringFields = ["cacheDir", "cacheKeyPrefix"];
4316
- for (const field of stringFields) {
4317
- if (cfg[field] !== void 0 && typeof cfg[field] !== "string") {
4318
- errors.push({
4319
- path: `typeSchema.${field}`,
4320
- message: `${field} must be a string`,
4321
- value: cfg[field]
4322
- });
4323
- }
4324
- }
4325
- if (cfg.maxAge !== void 0) {
4326
- if (typeof cfg.maxAge !== "number" || cfg.maxAge <= 0) {
4327
- errors.push({
4328
- path: "typeSchema.maxAge",
4329
- message: "maxAge must be a positive number",
4330
- value: cfg.maxAge
4331
- });
4332
- }
4333
- }
4334
- if (cfg.profiles !== void 0) {
4335
- if (typeof cfg.profiles !== "object" || cfg.profiles === null) {
4336
- errors.push({
4337
- path: "typeSchema.profiles",
4338
- message: "profiles must be an object",
4339
- value: cfg.profiles
4340
- });
4341
- } else {
4342
- const profiles = cfg.profiles;
4343
- if (profiles.autoDetect !== void 0 && typeof profiles.autoDetect !== "boolean") {
4344
- errors.push({
4345
- path: "typeSchema.profiles.autoDetect",
4346
- message: "autoDetect must be a boolean",
4347
- value: profiles.autoDetect
4348
- });
4349
- }
4350
- }
4351
- }
4352
- return errors;
4353
- }
4354
- };
4355
- var ConfigLoader = class {
4356
- validator = new ConfigValidator();
4357
- /**
4358
- * Auto-load configuration from the current working directory
4359
- */
4360
- async autoload(workingDir = process.cwd()) {
4361
- const configPath = await this.findConfigFile(workingDir);
4362
- if (configPath) {
4363
- return this.loadFromFile(configPath);
4364
- }
4365
- return { ...DEFAULT_CONFIG };
4366
- }
4367
- /**
4368
- * Load configuration from a specific file
4369
- */
4370
- async loadFromFile(filePath) {
4371
- try {
4372
- let config;
4373
- if (filePath.endsWith(".ts") || filePath.endsWith("")) {
4374
- const absolutePath = resolve(filePath);
4375
- const importResult = await import(absolutePath);
4376
- config = importResult.default || importResult;
4377
- } else {
4378
- const content = await readFile(filePath, "utf-8");
4379
- config = JSON.parse(content);
4380
- }
4381
- const validation = this.validator.validate(config);
4382
- if (!validation.valid) {
4383
- const errorMessages = validation.errors.map((e) => `${e.path}: ${e.message}`).join("\n");
4384
- throw new Error(`Configuration validation failed:
4385
- ${errorMessages}`);
4386
- }
4387
- if (!validation.config) throw new Error("Invalid configuration");
4388
- return this.mergeWithDefaults(validation.config);
4389
- } catch (error) {
4390
- if (error instanceof Error) {
4391
- throw new Error(`Failed to load config from ${filePath}: ${error.message}`);
4392
- }
4393
- throw error;
4394
- }
4395
- }
4396
- /**
4397
- * Find configuration file in the given directory
4398
- */
4399
- async findConfigFile(startDir) {
4400
- for (const fileName of CONFIG_FILE_NAMES) {
4401
- const configPath = resolve(startDir, fileName);
4402
- if (existsSync(configPath)) {
4403
- return configPath;
4404
- }
4405
- }
4406
- return null;
4407
- }
4408
- /**
4409
- * Merge user config with defaults
4410
- */
4411
- mergeWithDefaults(userConfig) {
4412
- const merged = {
4413
- ...DEFAULT_CONFIG,
4414
- ...userConfig,
4415
- typescript: {
4416
- ...DEFAULT_CONFIG.typescript,
4417
- ...userConfig.typescript
4418
- }
4419
- };
4420
- return merged;
4421
- }
4422
- };
4423
- var configLoader = new ConfigLoader();
4424
- async function loadConfig(workingDir) {
4425
- return configLoader.autoload(workingDir);
4426
- }
4427
- function isConfig(obj) {
4428
- const validator = new ConfigValidator();
4429
- const result = validator.validate(obj);
4430
- return result.valid;
4431
- }
4432
- function defineConfig(config) {
4433
- return config;
4434
- }
4435
5300
 
4436
- export { APIBuilder, CONFIG_FILE_NAMES, ConfigLoader, ConfigValidator, DEFAULT_CONFIG, LogLevel, configLoader, defineConfig, isConfig, loadConfig, prettyReport };
5301
+ export { APIBuilder, LogLevel, prettyReport };
4437
5302
  //# sourceMappingURL=index.js.map
4438
5303
  //# sourceMappingURL=index.js.map