@atomic-ehr/codegen 0.0.8 → 0.0.9-canary.20260312182458.1c26a02

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
@@ -16,170 +16,99 @@ import { spawn } from 'child_process';
16
16
  import * as util from 'util';
17
17
  import Mustache from 'mustache';
18
18
 
19
- // src/utils/codegen-logger.ts
20
- var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
21
- LogLevel2[LogLevel2["DEBUG"] = 0] = "DEBUG";
22
- LogLevel2[LogLevel2["INFO"] = 1] = "INFO";
23
- LogLevel2[LogLevel2["WARN"] = 2] = "WARN";
24
- LogLevel2[LogLevel2["ERROR"] = 3] = "ERROR";
25
- LogLevel2[LogLevel2["SILENT"] = 4] = "SILENT";
26
- return LogLevel2;
27
- })(LogLevel || {});
28
- var parseLogLevel = (level) => {
29
- switch (level.toUpperCase()) {
30
- case "DEBUG":
31
- return 0 /* DEBUG */;
32
- case "INFO":
33
- return 1 /* INFO */;
34
- case "WARN":
35
- return 2 /* WARN */;
36
- case "ERROR":
37
- return 3 /* ERROR */;
38
- case "SILENT":
39
- return 4 /* SILENT */;
40
- default:
41
- throw new Error(`Invalid log level: ${level}`);
42
- }
43
- };
44
- var CodegenLogger = class _CodegenLogger {
45
- options;
46
- dryWarnSet = /* @__PURE__ */ new Set();
47
- constructor(options = {}) {
48
- this.options = {
49
- timestamp: false,
50
- level: 1 /* INFO */,
51
- ...options
19
+ // src/utils/common-log.ts
20
+ var LEVEL_PRIORITY = { DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, SILENT: 4 };
21
+ function mkLogger(opts = {}) {
22
+ const prefix = opts.prefix ?? "";
23
+ const suppressedSet = new Set(opts.suppressTags ?? []);
24
+ const tagCounts = {};
25
+ const entries = [];
26
+ const drySet = /* @__PURE__ */ new Set();
27
+ const currentLevel = opts.level ?? "INFO";
28
+ const shouldLog = (level) => LEVEL_PRIORITY[level] >= LEVEL_PRIORITY[currentLevel];
29
+ const colorize = {
30
+ DEBUG: (s) => s,
31
+ INFO: (s) => s,
32
+ WARN: pc.yellow,
33
+ ERROR: pc.red,
34
+ SILENT: (s) => s
35
+ };
36
+ const fmt = (level, icon, msg, tag) => {
37
+ const pfx = prefix ? `${prefix}: ` : "";
38
+ const tagSuffix = tag ? ` ${pc.dim(`(${tag})`)}` : "";
39
+ return colorize[level](`${icon} ${pfx}${msg}`) + tagSuffix;
40
+ };
41
+ const pushEntry = (level, msg, tag, suppressed = false) => {
42
+ entries.push({ level, tag, message: msg, suppressed, prefix, timestamp: Date.now() });
43
+ };
44
+ const mkLogFn = (level, icon, consoleFn, dedupe = false) => {
45
+ return (...args) => {
46
+ const tag = args.length === 2 ? args[0] : void 0;
47
+ const msg = args.length === 2 ? args[1] : args[0];
48
+ if (tag) tagCounts[tag] = (tagCounts[tag] ?? 0) + 1;
49
+ const isSuppressed = tag !== void 0 && suppressedSet.has(tag);
50
+ pushEntry(level, msg, tag, isSuppressed);
51
+ if (isSuppressed) return;
52
+ if (!shouldLog(level)) return;
53
+ if (dedupe) {
54
+ const key = `${level}::${tag ?? ""}::${msg}`;
55
+ if (drySet.has(key)) return;
56
+ drySet.add(key);
57
+ }
58
+ consoleFn(fmt(level, icon, msg, tag));
52
59
  };
53
- }
54
- /**
55
- * Check if a message at the given level should be logged
56
- */
57
- shouldLog(messageLevel) {
58
- const currentLevel = this.options.level ?? 1 /* INFO */;
59
- return messageLevel >= currentLevel;
60
- }
61
- static consoleLevelsMap = {
62
- [1 /* INFO */]: console.log,
63
- [2 /* WARN */]: console.warn,
64
- [3 /* ERROR */]: console.error,
65
- [0 /* DEBUG */]: console.log,
66
- [4 /* SILENT */]: () => {
67
- }
68
60
  };
69
- formatMessage(level, message, color) {
70
- const timestamp = this.options.timestamp ? `${pc.gray((/* @__PURE__ */ new Date()).toLocaleTimeString())} ` : "";
71
- const prefix = this.options.prefix ? `${pc.cyan(`[${this.options.prefix}]`)} ` : "";
72
- return `${timestamp}${color(level)} ${prefix}${message}`;
73
- }
74
- isSuppressed(level) {
75
- return this.options.suppressLoggingLevel === "all" || this.options.suppressLoggingLevel?.includes(level) || false;
76
- }
77
- tryWriteToConsole(level, formattedMessage) {
78
- if (this.isSuppressed(level)) return;
79
- if (!this.shouldLog(level)) return;
80
- const logFn = _CodegenLogger.consoleLevelsMap[level] || console.log;
81
- logFn(formattedMessage);
82
- }
83
- /**
84
- * Success message with checkmark
85
- */
86
- success(message) {
87
- this.tryWriteToConsole(1 /* INFO */, this.formatMessage("", message, pc.green));
88
- }
89
- /**
90
- * Error message with X mark
91
- */
92
- error(message, error) {
93
- if (this.isSuppressed(3 /* ERROR */)) return;
94
- if (!this.shouldLog(3 /* ERROR */)) return;
95
- console.error(this.formatMessage("X", message, pc.red));
96
- const showDetails = this.options.level === 0 /* DEBUG */;
97
- if (error && showDetails) {
98
- console.error(pc.red(` ${error.message}`));
99
- if (error.stack) {
100
- console.error(pc.gray(error.stack));
61
+ const logger = {
62
+ warn: mkLogFn("WARN", "!", console.warn),
63
+ dryWarn: mkLogFn("WARN", "!", console.warn, true),
64
+ info: mkLogFn("INFO", "i", console.log),
65
+ error: mkLogFn("ERROR", "X", console.error),
66
+ debug: mkLogFn("DEBUG", "D", console.log),
67
+ fork(childPrefix, childOpts) {
68
+ const fullPrefix = prefix ? `${prefix}/${childPrefix}` : childPrefix;
69
+ const merged = [...suppressedSet, ...childOpts?.suppressTags ?? []];
70
+ return mkLogger({
71
+ prefix: fullPrefix,
72
+ suppressTags: merged,
73
+ level: childOpts?.level ?? currentLevel
74
+ });
75
+ },
76
+ as() {
77
+ return logger;
78
+ },
79
+ tagCounts() {
80
+ return tagCounts;
81
+ },
82
+ printTagSummary() {
83
+ const allTags = Object.entries(tagCounts);
84
+ if (allTags.length === 0) return;
85
+ const pfx = prefix ? `${prefix}: ` : "";
86
+ const emitted = allTags.filter(([tag]) => !suppressedSet.has(tag));
87
+ const suppressed = allTags.filter(([tag]) => suppressedSet.has(tag));
88
+ if (emitted.length > 0) {
89
+ const total = emitted.reduce((sum, [, c]) => sum + c, 0);
90
+ const detail = emitted.map(([tag, c]) => `${tag}: ${c}`).join(", ");
91
+ console.warn(pc.yellow(`! ${pfx}${total} warnings (${detail})`));
101
92
  }
93
+ if (suppressed.length > 0) {
94
+ const total = suppressed.reduce((sum, [, c]) => sum + c, 0);
95
+ const detail = suppressed.map(([tag, c]) => `${tag}: ${c}`).join(", ");
96
+ console.log(pc.dim(`i ${pfx}${total} suppressed (${detail})`));
97
+ }
98
+ },
99
+ buffer() {
100
+ return entries;
101
+ },
102
+ bufferClear() {
103
+ entries.length = 0;
102
104
  }
103
- }
104
- /**
105
- * Warning message with warning sign
106
- */
107
- warn(message) {
108
- this.tryWriteToConsole(2 /* WARN */, this.formatMessage("!", message, pc.yellow));
109
- }
110
- dryWarn(message) {
111
- if (!this.dryWarnSet.has(message)) {
112
- this.warn(message);
113
- this.dryWarnSet.add(message);
114
- }
115
- }
116
- /**
117
- * Info message with info icon
118
- */
119
- info(message) {
120
- this.tryWriteToConsole(1 /* INFO */, this.formatMessage("i", message, pc.blue));
121
- }
122
- /**
123
- * Debug message (only shows when log level is DEBUG or verbose is true)
124
- */
125
- debug(message) {
126
- if (this.shouldLog(0 /* DEBUG */)) {
127
- this.tryWriteToConsole(0 /* DEBUG */, this.formatMessage("\u{1F41B}", message, pc.magenta));
128
- }
129
- }
130
- /**
131
- * Step message with rocket
132
- */
133
- step(message) {
134
- this.tryWriteToConsole(1 /* INFO */, this.formatMessage("\u{1F680}", message, pc.cyan));
135
- }
136
- /**
137
- * Progress message with clock
138
- */
139
- progress(message) {
140
- this.tryWriteToConsole(1 /* INFO */, this.formatMessage("\u23F3", message, pc.blue));
141
- }
142
- /**
143
- * Plain message (no icon, just colored text)
144
- */
145
- plain(message, color = (s) => s) {
146
- const timestamp = this.options.timestamp ? `${pc.gray((/* @__PURE__ */ new Date()).toLocaleTimeString())} ` : "";
147
- const prefix = this.options.prefix ? `${pc.cyan(`[${this.options.prefix}]`)} ` : "";
148
- this.tryWriteToConsole(1 /* INFO */, `${timestamp}${prefix}${color(message)}`);
149
- }
150
- /**
151
- * Dimmed/gray text for less important info
152
- */
153
- dim(message) {
154
- this.plain(message, pc.gray);
155
- }
156
- /**
157
- * Create a child logger with a prefix
158
- */
159
- child(prefix) {
160
- return new _CodegenLogger({
161
- ...this.options,
162
- prefix: this.options.prefix ? `${this.options.prefix}:${prefix}` : prefix
163
- });
164
- }
165
- /**
166
- * Update options
167
- */
168
- configure(options) {
169
- this.options = { ...this.options, ...options };
170
- }
171
- getLevel() {
172
- return this.options.level ?? 1 /* INFO */;
173
- }
174
- setLevel(level) {
175
- this.options.level = level;
176
- }
177
- };
178
- new CodegenLogger();
179
- function createLogger(options = {}) {
180
- return new CodegenLogger(options);
105
+ };
106
+ return logger;
181
107
  }
182
108
 
109
+ // src/utils/log.ts
110
+ var mkCodegenLogger = (opts = {}) => mkLogger(opts);
111
+
183
112
  // src/api/writer-generator/utils.ts
184
113
  var words = (s) => {
185
114
  return s.split(/(?<=[a-z])(?=[A-Z])|[-_.\s]/).filter(Boolean);
@@ -982,7 +911,7 @@ var mkTypeSchemaIndex = (schemas, {
982
911
  append(schema);
983
912
  }
984
913
  const relations = resourceRelatives(schemas);
985
- const resolve5 = (id) => {
914
+ const resolve6 = (id) => {
986
915
  if (id.kind === "nested") return nestedIndex[id.url]?.[id.package];
987
916
  return index[id.url]?.[id.package];
988
917
  };
@@ -1015,9 +944,10 @@ var mkTypeSchemaIndex = (schemas, {
1015
944
  res.push(cur);
1016
945
  const base = cur.base;
1017
946
  if (base === void 0) break;
1018
- const resolved = resolve5(base);
947
+ const resolved = resolve6(base);
1019
948
  if (!resolved) {
1020
949
  logger?.warn(
950
+ "#resolveBase",
1021
951
  `Failed to resolve base type: ${res.map((e) => `${e.identifier.url} (${e.identifier.kind})`).join(", ")}`
1022
952
  );
1023
953
  return void 0;
@@ -1041,7 +971,7 @@ var mkTypeSchemaIndex = (schemas, {
1041
971
  return nonConstraintSchema;
1042
972
  };
1043
973
  const findLastSpecializationByIdentifier = (id) => {
1044
- const schema = resolve5(id);
974
+ const schema = resolve6(id);
1045
975
  if (!schema) return id;
1046
976
  return findLastSpecialization(schema).identifier;
1047
977
  };
@@ -1086,6 +1016,25 @@ var mkTypeSchemaIndex = (schemas, {
1086
1016
  extensions: mergedExtensions.length > 0 ? mergedExtensions : void 0
1087
1017
  };
1088
1018
  };
1019
+ const constrainedChoice = (pkgName, baseTypeId, sliceElements) => {
1020
+ const baseSchema = resolveByUrl(pkgName, baseTypeId.url);
1021
+ if (!baseSchema || !("fields" in baseSchema) || !baseSchema.fields) return void 0;
1022
+ for (const [fieldName, field] of Object.entries(baseSchema.fields)) {
1023
+ if (!isChoiceDeclarationField(field)) continue;
1024
+ const matchingVariants = field.choices.filter((c) => sliceElements.includes(c));
1025
+ if (matchingVariants.length !== 1) continue;
1026
+ const variantName = matchingVariants[0];
1027
+ const variantField = baseSchema.fields[variantName];
1028
+ if (!variantField || !isChoiceInstanceField(variantField)) continue;
1029
+ return {
1030
+ choiceBase: fieldName,
1031
+ variant: variantName,
1032
+ variantType: variantField.type,
1033
+ allChoiceNames: field.choices
1034
+ };
1035
+ }
1036
+ return void 0;
1037
+ };
1089
1038
  const isWithMetaField = (profile) => {
1090
1039
  const genealogy = tryHierarchy(profile);
1091
1040
  if (!genealogy) return false;
@@ -1128,7 +1077,7 @@ var mkTypeSchemaIndex = (schemas, {
1128
1077
  collectResources: () => schemas.filter(isResourceTypeSchema),
1129
1078
  collectLogicalModels: () => schemas.filter(isLogicalTypeSchema),
1130
1079
  collectProfiles: () => schemas.filter(isProfileTypeSchema),
1131
- resolve: resolve5,
1080
+ resolve: resolve6,
1132
1081
  resolveByUrl,
1133
1082
  resourceChildren,
1134
1083
  tryHierarchy,
@@ -1136,6 +1085,7 @@ var mkTypeSchemaIndex = (schemas, {
1136
1085
  findLastSpecialization,
1137
1086
  findLastSpecializationByIdentifier,
1138
1087
  flatProfile,
1088
+ constrainedChoice,
1139
1089
  isWithMetaField,
1140
1090
  entityTree,
1141
1091
  exportTree,
@@ -1212,6 +1162,14 @@ var PYTHON_KEYWORDS = /* @__PURE__ */ new Set([
1212
1162
  "List"
1213
1163
  ]);
1214
1164
  var MAX_IMPORT_LINE_LENGTH = 100;
1165
+ var GENERIC_FIELD_REWRITES = {
1166
+ Coding: { code: "T" },
1167
+ CodeableConcept: { coding: "Coding[T]" }
1168
+ };
1169
+ var pyEnumType = (enumDef) => {
1170
+ const values = enumDef.values.map((e) => `"${e}"`).join(", ");
1171
+ return enumDef.isOpen ? `Literal[${values}] | str` : `Literal[${values}]`;
1172
+ };
1215
1173
  var fixReservedWords = (name) => {
1216
1174
  return PYTHON_KEYWORDS.has(name) ? `${name}_` : name;
1217
1175
  };
@@ -1334,9 +1292,14 @@ var Python = class extends Writer {
1334
1292
  }
1335
1293
  }
1336
1294
  generateBasePy(packageComplexTypes) {
1295
+ const hasGenericTypes = packageComplexTypes.some((s) => s.identifier.name in GENERIC_FIELD_REWRITES);
1337
1296
  this.cat("base.py", () => {
1338
1297
  this.generateDisclaimer();
1339
- this.generateDefaultImports();
1298
+ this.generateDefaultImports(hasGenericTypes);
1299
+ if (hasGenericTypes) {
1300
+ this.line();
1301
+ this.line("T = TypeVar('T', bound=str, default=str)");
1302
+ }
1340
1303
  this.line();
1341
1304
  this.generateComplexTypes(packageComplexTypes);
1342
1305
  this.line();
@@ -1427,7 +1390,7 @@ var Python = class extends Writer {
1427
1390
  generateResourceModule(schema) {
1428
1391
  this.cat(`${snakeCase(schema.identifier.name)}.py`, () => {
1429
1392
  this.generateDisclaimer();
1430
- this.generateDefaultImports();
1393
+ this.generateDefaultImports(false);
1431
1394
  this.generateFhirBaseModelImport();
1432
1395
  this.line();
1433
1396
  this.generateDependenciesImports(schema);
@@ -1451,7 +1414,11 @@ var Python = class extends Writer {
1451
1414
  this.line();
1452
1415
  }
1453
1416
  getSuperClasses(schema) {
1454
- return [...schema.base ? [schema.base.name] : [], ...this.injectSuperClasses(schema.identifier.url)];
1417
+ const bases = [];
1418
+ if (schema.base) bases.push(schema.base.name);
1419
+ bases.push(...this.injectSuperClasses(schema.identifier.url));
1420
+ if (schema.identifier.name in GENERIC_FIELD_REWRITES) bases.push("Generic[T]");
1421
+ return bases;
1455
1422
  }
1456
1423
  generateClassBody(schema) {
1457
1424
  this.generateModelConfig();
@@ -1462,7 +1429,7 @@ var Python = class extends Writer {
1462
1429
  if (schema.identifier.kind === "resource") {
1463
1430
  this.generateResourceTypeField(schema);
1464
1431
  }
1465
- this.generateFields(schema);
1432
+ this.generateFields(schema, schema.identifier.name);
1466
1433
  if (schema.identifier.kind === "resource") {
1467
1434
  this.generateResourceMethods(schema);
1468
1435
  }
@@ -1490,17 +1457,17 @@ var Python = class extends Writer {
1490
1457
  });
1491
1458
  this.line(")");
1492
1459
  }
1493
- generateFields(schema) {
1460
+ generateFields(schema, schemaName) {
1494
1461
  const sortedFields = Object.entries(schema.fields ?? []).sort(([a], [b]) => a.localeCompare(b));
1495
1462
  for (const [fieldName, field] of sortedFields) {
1496
1463
  if ("choices" in field && field.choices) continue;
1497
- const fieldInfo = this.buildFieldInfo(fieldName, field);
1464
+ const fieldInfo = this.buildFieldInfo(fieldName, field, schemaName);
1498
1465
  this.line(`${fieldInfo.name}: ${fieldInfo.type}${fieldInfo.defaultValue}`);
1499
1466
  }
1500
1467
  }
1501
- buildFieldInfo(fieldName, field) {
1468
+ buildFieldInfo(fieldName, field, schemaName) {
1502
1469
  const pyFieldName = fixReservedWords(this.nameFormatFunction(fieldName));
1503
- const fieldType = this.determineFieldType(field);
1470
+ const fieldType = this.determineFieldType(field, fieldName, schemaName);
1504
1471
  const defaultValue = this.getFieldDefaultValue(field, fieldName);
1505
1472
  return {
1506
1473
  name: pyFieldName,
@@ -1508,11 +1475,23 @@ var Python = class extends Writer {
1508
1475
  defaultValue
1509
1476
  };
1510
1477
  }
1511
- determineFieldType(field) {
1478
+ determineFieldType(field, fieldName, schemaName) {
1512
1479
  let fieldType = field ? this.getBaseFieldType(field) : "";
1513
- if ("enum" in field && field.enum && !field.enum.isOpen) {
1514
- const s = field.enum.values.map((e) => `"${e}"`).join(", ");
1515
- fieldType = `Literal[${s}]`;
1480
+ const rewrite = GENERIC_FIELD_REWRITES[schemaName]?.[fieldName];
1481
+ if (rewrite) {
1482
+ fieldType = rewrite;
1483
+ if (field.array) fieldType = `PyList[${fieldType}]`;
1484
+ if (!field.required) fieldType = `${fieldType} | None`;
1485
+ return fieldType;
1486
+ }
1487
+ if ("enum" in field && field.enum) {
1488
+ const baseTypeName = "type" in field ? field.type.name : "";
1489
+ if (baseTypeName in GENERIC_FIELD_REWRITES) {
1490
+ fieldType = `${fieldType}[${pyEnumType(field.enum)}]`;
1491
+ } else if (!field.enum.isOpen) {
1492
+ const s = field.enum.values.map((e) => `"${e}"`).join(", ");
1493
+ fieldType = `Literal[${s}]`;
1494
+ }
1516
1495
  }
1517
1496
  if (field.array) {
1518
1497
  fieldType = `PyList[${fieldType}]`;
@@ -1553,10 +1532,17 @@ var Python = class extends Writer {
1553
1532
  this.generateType(subtype);
1554
1533
  }
1555
1534
  }
1556
- generateDefaultImports() {
1535
+ generateDefaultImports(includeGenericImports) {
1557
1536
  this.pyImportFrom("__future__", "annotations");
1558
1537
  this.pyImportFrom("pydantic", "BaseModel", "ConfigDict", "Field", "PositiveInt");
1559
- this.pyImportFrom("typing", "List as PyList", "Literal");
1538
+ const typingImports = ["List as PyList", "Literal"];
1539
+ if (includeGenericImports) {
1540
+ typingImports.push("Generic");
1541
+ }
1542
+ this.pyImportFrom("typing", ...typingImports.sort());
1543
+ if (includeGenericImports) {
1544
+ this.pyImportFrom("typing_extensions", "TypeVar");
1545
+ }
1560
1546
  }
1561
1547
  generateDependenciesImports(schema) {
1562
1548
  if (!schema.dependencies || schema.dependencies.length === 0) return;
@@ -1872,7 +1858,10 @@ function buildEnum(register, fhirSchema, element, logger) {
1872
1858
  const valueSetUrl = element.binding.valueSet;
1873
1859
  if (!valueSetUrl) return void 0;
1874
1860
  if (!BINDABLE_TYPES.has(element.type ?? "")) {
1875
- logger?.dryWarn(`eld-11: Binding on non-bindable type '${element.type}' (valueSet: ${valueSetUrl})`);
1861
+ logger?.dryWarn(
1862
+ "#binding",
1863
+ `eld-11: Binding on non-bindable type '${element.type}' (valueSet: ${valueSetUrl})`
1864
+ );
1876
1865
  return void 0;
1877
1866
  }
1878
1867
  const shouldGenerateEnum = strength === "required" || strength === "extensible" || strength === "preferred";
@@ -1882,6 +1871,7 @@ function buildEnum(register, fhirSchema, element, logger) {
1882
1871
  const codes = concepts.map((c) => c.code).filter((code) => code && typeof code === "string" && code.trim().length > 0);
1883
1872
  if (codes.length > MAX_ENUM_LENGTH) {
1884
1873
  logger?.dryWarn(
1874
+ "#largeValueSet",
1885
1875
  `Value set ${valueSetUrl} has ${codes.length} which is more than ${MAX_ENUM_LENGTH} codes, which may cause issues with code generation.`
1886
1876
  );
1887
1877
  return void 0;
@@ -1981,7 +1971,8 @@ var mkPackageAwareResolver = async (manager, pkg, deep, acc, logger) => {
1981
1971
  if (!rawUrl) continue;
1982
1972
  if (!(isStructureDefinition(resource) || isValueSet(resource) || isCodeSystem(resource))) continue;
1983
1973
  const url = rawUrl;
1984
- if (index.canonicalResolution[url]) logger?.dryWarn(`Duplicate canonical URL: ${url} at ${pkgId}.`);
1974
+ if (index.canonicalResolution[url])
1975
+ logger?.dryWarn("#duplicateCanonical", `Duplicate canonical URL: ${url} at ${pkgId}.`);
1985
1976
  index.canonicalResolution[url] = [{ deep, pkg, pkgId, resource }];
1986
1977
  }
1987
1978
  const deps = await readPackageDependencies(manager, pkg);
@@ -2002,6 +1993,8 @@ var enrichResolver = (resolver, logger) => {
2002
1993
  for (const { pkg, canonicalResolution } of Object.values(resolver)) {
2003
1994
  const pkgId = packageMetaToFhir(pkg);
2004
1995
  if (!resolver[pkgId]) throw new Error(`Package ${pkgId} not found`);
1996
+ let counter = 0;
1997
+ logger?.info(`FHIR Schema conversion for '${packageMetaToFhir(pkg)}' begins...`);
2005
1998
  for (const [_url, options] of Object.entries(canonicalResolution)) {
2006
1999
  const resolition = options[0];
2007
2000
  if (!resolition) throw new Error(`Resource not found`);
@@ -2010,6 +2003,7 @@ var enrichResolver = (resolver, logger) => {
2010
2003
  if (isStructureDefinition(resource)) {
2011
2004
  const fs7 = fhirschema.translate(resource);
2012
2005
  const rfs = enrichFHIRSchema(fs7, resourcePkg);
2006
+ counter++;
2013
2007
  resolver[pkgId].fhirSchemas[rfs.url] = rfs;
2014
2008
  }
2015
2009
  if (isValueSet(resource)) {
@@ -2017,6 +2011,7 @@ var enrichResolver = (resolver, logger) => {
2017
2011
  resolver[pkgId].valueSets[rvs.url] = rvs;
2018
2012
  }
2019
2013
  }
2014
+ logger?.info(`FHIR Schema conversion for '${packageMetaToFhir(pkg)}' completed: ${counter} successful`);
2020
2015
  }
2021
2016
  };
2022
2017
  var packageAgnosticResolveCanonical = (resolver, url, _logger) => {
@@ -2030,7 +2025,7 @@ var registerFromManager = async (manager, { logger, focusedPackages }) => {
2030
2025
  for (const pkg of packages) {
2031
2026
  await mkPackageAwareResolver(manager, pkg, 0, resolver, logger);
2032
2027
  }
2033
- enrichResolver(resolver);
2028
+ enrichResolver(resolver, logger);
2034
2029
  const resolveFs = (pkg, canonicalUrl) => {
2035
2030
  const pkgIndex = resolver[packageMetaToFhir(pkg)];
2036
2031
  if (pkgIndex) {
@@ -2171,10 +2166,10 @@ var registerFromManager = async (manager, { logger, focusedPackages }) => {
2171
2166
  };
2172
2167
  var registerFromPackageMetas = async (packageMetas, conf) => {
2173
2168
  const packageNames = packageMetas.map(packageMetaToNpm);
2174
- conf?.logger?.step(`Loading FHIR packages: ${packageNames.join(", ")}`);
2169
+ conf?.logger?.info(`Loading FHIR packages: ${packageNames.join(", ")}`);
2175
2170
  const manager = CanonicalManager({
2176
2171
  packages: packageNames,
2177
- workingDir: "tmp/fhir",
2172
+ workingDir: ".codegen-cache/canonical-manager-cache",
2178
2173
  registry: conf.registry || void 0
2179
2174
  });
2180
2175
  await manager.init();
@@ -2510,6 +2505,7 @@ function buildFieldType(register, fhirSchema, path, element, logger) {
2510
2505
  return void 0;
2511
2506
  } else {
2512
2507
  logger?.dryWarn(
2508
+ "#fieldTypeNotFound",
2513
2509
  `Can't recognize element type: <${fhirSchema.url}>.${path.join(".")} (pkg: '${packageMetaToFhir(fhirSchema.package_meta)}'): missing type info`
2514
2510
  );
2515
2511
  return void 0;
@@ -2526,7 +2522,10 @@ var mkField = (register, fhirSchema, path, element, logger, rawElement) => {
2526
2522
  }
2527
2523
  const fieldType = buildFieldType(register, fhirSchema, path, element, logger);
2528
2524
  if (!fieldType)
2529
- logger?.dryWarn(`Field type not found for '${fhirSchema.url}#${path.join(".")}' (${fhirSchema.derivation})`);
2525
+ logger?.dryWarn(
2526
+ "#fieldTypeNotFound",
2527
+ `Field type not found for '${fhirSchema.url}#${path.join(".")}' (${fhirSchema.derivation})`
2528
+ );
2530
2529
  let valueConstraint;
2531
2530
  if (element.pattern) {
2532
2531
  valueConstraint = { kind: "pattern", type: element.pattern.type, value: element.pattern.value };
@@ -2564,7 +2563,8 @@ var mkField = (register, fhirSchema, path, element, logger, rawElement) => {
2564
2563
  choiceOf: element.choiceOf,
2565
2564
  binding,
2566
2565
  enum: enumResult,
2567
- valueConstraint
2566
+ valueConstraint,
2567
+ mustSupport: element.mustSupport
2568
2568
  };
2569
2569
  };
2570
2570
  function mkNestedField(register, fhirSchema, path, element) {
@@ -2709,7 +2709,14 @@ var extractProfileExtensions = (register, fhirSchema, logger) => {
2709
2709
  }
2710
2710
  };
2711
2711
  walkElement([], fhirSchema);
2712
- return extensions.length === 0 ? void 0 : extensions;
2712
+ const seen = /* @__PURE__ */ new Set();
2713
+ const deduped = extensions.filter((ext) => {
2714
+ const key = `${ext.url}:${ext.path}`;
2715
+ if (seen.has(key)) return false;
2716
+ seen.add(key);
2717
+ return true;
2718
+ });
2719
+ return deduped.length === 0 ? void 0 : deduped;
2713
2720
  };
2714
2721
 
2715
2722
  // src/typeschema/core/transformer.ts
@@ -2722,6 +2729,7 @@ function mkFields(register, fhirSchema, parentPath, elements, logger) {
2722
2729
  const fcurl = elemSnapshot.type ? register.ensureSpecializationCanonicalUrl(elemSnapshot.type) : void 0;
2723
2730
  if (fcurl && shouldSkipCanonical(fhirSchema.package_meta, fcurl).shouldSkip) {
2724
2731
  logger?.warn(
2732
+ "#skipCanonical",
2725
2733
  `Skipping field ${path} for ${fcurl} due to skip hack ${shouldSkipCanonical(fhirSchema.package_meta, fcurl).reason}`
2726
2734
  );
2727
2735
  continue;
@@ -2826,7 +2834,7 @@ var deduplicateSchemas = (schemasWithSources, logger) => {
2826
2834
  if (sorted.length > 1) {
2827
2835
  const pkg = best.typeSchema.identifier.package;
2828
2836
  const url = best.typeSchema.identifier.url;
2829
- logger?.dryWarn(`'${url}' from '${pkg}'' has ${sorted.length} versions`);
2837
+ logger?.dryWarn("#duplicateSchema", `'${url}' from '${pkg}' has ${sorted.length} versions`);
2830
2838
  collisions[pkg] ??= {};
2831
2839
  collisions[pkg][url] = sorted.flatMap(
2832
2840
  (v) => v.sources.map((s) => ({
@@ -2845,7 +2853,7 @@ var generateTypeSchemas = async (register, logger) => {
2845
2853
  const pkgId = packageMetaToFhir(fhirSchema.package_meta);
2846
2854
  const skipCheck = shouldSkipCanonical(fhirSchema.package_meta, fhirSchema.url);
2847
2855
  if (skipCheck.shouldSkip) {
2848
- logger?.dryWarn(`Skip ${fhirSchema.url} from ${pkgId}. Reason: ${skipCheck.reason}`);
2856
+ logger?.dryWarn("#skipCanonical", `Skip ${fhirSchema.url} from ${pkgId}. Reason: ${skipCheck.reason}`);
2849
2857
  continue;
2850
2858
  }
2851
2859
  for (const schema of await transformFhirSchema(register, fhirSchema, logger)) {
@@ -3897,14 +3905,14 @@ var createGenerator = (templatePath, apiOpts) => {
3897
3905
  return new MustacheGenerator(mustacheOptions);
3898
3906
  };
3899
3907
  function runCommand(cmd, args = [], options = {}) {
3900
- return new Promise((resolve5, reject) => {
3908
+ return new Promise((resolve6, reject) => {
3901
3909
  const child = spawn(cmd, args, {
3902
3910
  stdio: "inherit",
3903
3911
  ...options
3904
3912
  });
3905
3913
  child.on("error", reject);
3906
3914
  child.on("close", (code) => {
3907
- if (code === 0) resolve5(code);
3915
+ if (code === 0) resolve6(code);
3908
3916
  else reject(new Error(`Prozess beendet mit Fehlercode ${code}`));
3909
3917
  });
3910
3918
  });
@@ -4074,32 +4082,31 @@ var tsProfileModuleFileName = (tsIndex, schema) => {
4074
4082
  return `${tsProfileModuleName(tsIndex, schema)}.ts`;
4075
4083
  };
4076
4084
  var tsProfileClassName = (schema) => {
4077
- return `${normalizeTsName(schema.identifier.name)}Profile`;
4085
+ const name = normalizeTsName(schema.identifier.name);
4086
+ return name.endsWith("Profile") ? name : `${name}Profile`;
4078
4087
  };
4079
- var tsSliceInputTypeName = (profileName, fieldName, sliceName) => {
4080
- return `${uppercaseFirstLetter(profileName)}_${uppercaseFirstLetter(normalizeTsName(fieldName))}_${uppercaseFirstLetter(normalizeTsName(sliceName))}SliceInput`;
4088
+ var tsSliceFlatTypeName = (profileName, fieldName, sliceName) => {
4089
+ return `${uppercaseFirstLetter(profileName)}_${uppercaseFirstLetter(normalizeTsName(fieldName))}_${uppercaseFirstLetter(normalizeTsName(sliceName))}SliceFlat`;
4081
4090
  };
4082
- var tsExtensionInputTypeName = (profileName, extensionName) => {
4083
- return `${uppercaseFirstLetter(profileName)}_${uppercaseFirstLetter(normalizeTsName(extensionName))}Input`;
4091
+ var tsExtensionFlatTypeName = (profileName, extensionName) => {
4092
+ return `${uppercaseFirstLetter(profileName)}_${uppercaseFirstLetter(normalizeTsName(extensionName))}Flat`;
4084
4093
  };
4085
- var tsSliceMethodName = (sliceName) => {
4086
- return `set${uppercaseFirstLetter(normalizeTsName(sliceName) || "Slice")}`;
4087
- };
4088
- var tsExtensionMethodName = (name) => {
4089
- const normalized = tsCamelCase(name);
4090
- return `set${uppercaseFirstLetter(normalized || "Extension")}`;
4091
- };
4092
- var tsQualifiedExtensionMethodName = (name, path) => {
4094
+ var tsSliceStaticName = (name) => name.replace(/\[x\]/g, "").replace(/[^a-zA-Z0-9_$]/g, "_");
4095
+ var tsSliceMethodBaseName = (sliceName) => uppercaseFirstLetter(normalizeTsName(sliceName) || "Slice");
4096
+ var tsExtensionMethodBaseName = (name) => uppercaseFirstLetter(tsCamelCase(name) || "Extension");
4097
+ var tsQualifiedExtensionMethodBaseName = (name, path) => {
4093
4098
  const rawPath = path?.split(".").filter((p) => p && p !== "extension").join("_") ?? "";
4094
4099
  const pathPart = rawPath ? uppercaseFirstLetter(tsCamelCase(rawPath)) : "";
4095
- const normalized = tsCamelCase(name);
4096
- return `setExtension${pathPart}${uppercaseFirstLetter(normalized || "Extension")}`;
4100
+ return `${pathPart}${uppercaseFirstLetter(tsCamelCase(name) || "Extension")}`;
4097
4101
  };
4098
- var tsQualifiedSliceMethodName = (fieldName, sliceName) => {
4102
+ var tsQualifiedSliceMethodBaseName = (fieldName, sliceName) => {
4099
4103
  const fieldPart = uppercaseFirstLetter(tsCamelCase(fieldName) || "Field");
4100
4104
  const slicePart = uppercaseFirstLetter(normalizeTsName(sliceName) || "Slice");
4101
- return `setSlice${fieldPart}${slicePart}`;
4105
+ return `${fieldPart}${slicePart}`;
4102
4106
  };
4107
+ var tsResolvedExtensionBaseName = (extensionBaseNames, url, path, fallbackName) => extensionBaseNames[`${url}:${path}`] ?? fallbackName;
4108
+ var tsResolvedSliceBaseName = (sliceBaseNames, fieldName, sliceName) => sliceBaseNames[`${fieldName}:${sliceName}`] ?? sliceName;
4109
+ var tsValueFieldName = (id) => `value${uppercaseFirstLetter(id.name)}`;
4103
4110
 
4104
4111
  // src/api/writer-generator/typescript/utils.ts
4105
4112
  var primitiveType2tsType = {
@@ -4144,7 +4151,7 @@ var rewriteFieldTypeDefs = {
4144
4151
  Reference: { reference: () => "`${T}/${string}`" },
4145
4152
  CodeableConcept: { coding: () => "Coding<T>" }
4146
4153
  };
4147
- var resolveFieldTsType = (schemaName, tsName, field) => {
4154
+ var resolveFieldTsType = (schemaName, tsName, field, resolveRef) => {
4148
4155
  const rewriteFieldType = rewriteFieldTypeDefs[schemaName]?.[tsName];
4149
4156
  if (rewriteFieldType) return rewriteFieldType();
4150
4157
  if (field.enum) {
@@ -4153,13 +4160,14 @@ var resolveFieldTsType = (schemaName, tsName, field) => {
4153
4160
  return tsEnumType(field.enum);
4154
4161
  }
4155
4162
  if (field.reference && field.reference.length > 0) {
4156
- const references = field.reference.map((ref) => `"${ref.name}"`).join(" | ");
4163
+ const references = field.reference.map((ref) => resolveRef ? resolveRef(ref) : ref).map((ref) => `"${ref.name}"`).join(" | ");
4157
4164
  return `Reference<${references}>`;
4158
4165
  }
4159
4166
  if (isPrimitiveIdentifier(field.type)) return resolvePrimitiveType(field.type.name);
4160
4167
  if (isNestedIdentifier(field.type)) return tsResourceName(field.type);
4161
4168
  return field.type.name;
4162
4169
  };
4170
+ var fieldTsType = (field, resolveRef) => resolveFieldTsType("", "", field, resolveRef) + (field.array ? "[]" : "");
4163
4171
  var tsTypeFromIdentifier = (id) => {
4164
4172
  if (isNestedIdentifier(id)) return tsResourceName(id);
4165
4173
  if (isPrimitiveIdentifier(id)) return resolvePrimitiveType(id.name);
@@ -4168,514 +4176,302 @@ var tsTypeFromIdentifier = (id) => {
4168
4176
  return id.name;
4169
4177
  };
4170
4178
 
4171
- // src/api/writer-generator/typescript/profile.ts
4172
- var collectChoiceAccessors = (flatProfile, promotedChoices) => {
4173
- const accessors = [];
4174
- for (const [name, field] of Object.entries(flatProfile.fields ?? {})) {
4175
- if (field.excluded) continue;
4176
- if (!isChoiceInstanceField(field)) continue;
4177
- if (promotedChoices.has(name)) continue;
4178
- const tsType = tsTypeFromIdentifier(field.type) + (field.array ? "[]" : "");
4179
- accessors.push({ name, tsType, typeId: field.type });
4179
+ // src/api/writer-generator/typescript/profile-extensions.ts
4180
+ var extractValueField = (elements) => {
4181
+ if (!elements) return void 0;
4182
+ return elements.find((e) => e.startsWith("value") && e !== "value");
4183
+ };
4184
+ var valueFieldToTsType = (valueField) => {
4185
+ const fhirName = valueField.replace(/^value/, "");
4186
+ const primitives = {
4187
+ String: "string",
4188
+ Boolean: "boolean",
4189
+ Integer: "number",
4190
+ Decimal: "number",
4191
+ Date: "string",
4192
+ DateTime: "string",
4193
+ Time: "string",
4194
+ Instant: "string",
4195
+ Uri: "string",
4196
+ Url: "string",
4197
+ Canonical: "string",
4198
+ Code: "string",
4199
+ Oid: "string",
4200
+ Id: "string",
4201
+ Markdown: "string",
4202
+ UnsignedInt: "number",
4203
+ PositiveInt: "number",
4204
+ Uuid: "string",
4205
+ Base64Binary: "string"
4206
+ };
4207
+ return primitives[fhirName] ?? fhirName;
4208
+ };
4209
+ var collectSubExtensionSlices = (extProfile) => {
4210
+ const extensionField = extProfile.fields?.extension;
4211
+ if (!extensionField || isChoiceDeclarationField(extensionField) || !extensionField.slicing?.slices) return [];
4212
+ const result = [];
4213
+ for (const [sliceName, slice] of Object.entries(extensionField.slicing.slices)) {
4214
+ const valueField = extractValueField(slice.elements);
4215
+ if (!valueField) continue;
4216
+ const tsType = valueFieldToTsType(valueField);
4217
+ const isArray = slice.max === void 0;
4218
+ const isRequired2 = slice.min !== void 0 && slice.min >= 1;
4219
+ result.push({
4220
+ name: tsCamelCase(sliceName) || sliceName,
4221
+ url: sliceName,
4222
+ valueField,
4223
+ tsType,
4224
+ isArray,
4225
+ isRequired: isRequired2
4226
+ });
4180
4227
  }
4181
- return accessors;
4228
+ return result;
4182
4229
  };
4183
- var tryPromoteChoice = (field, fields, params, promotedChoices) => {
4184
- if (!isChoiceDeclarationField(field) || !field.required || field.choices.length !== 1) return;
4185
- const choiceName = field.choices[0];
4186
- if (!choiceName) return;
4187
- const choiceField = fields[choiceName];
4188
- if (!choiceField || !isChoiceInstanceField(choiceField)) return;
4189
- const tsType = tsTypeFromIdentifier(choiceField.type) + (choiceField.array ? "[]" : "");
4190
- params.push({ name: choiceName, tsType, typeId: choiceField.type });
4191
- promotedChoices.add(choiceName);
4230
+ var resolveExtensionProfile = (tsIndex, pkgName, url) => {
4231
+ const schema = tsIndex.resolveByUrl(pkgName, url);
4232
+ if (!schema || !isProfileTypeSchema(schema)) return void 0;
4233
+ if (schema.identifier.package !== pkgName) return void 0;
4234
+ const className = tsProfileClassName(schema);
4235
+ const modulePath = `./${tsProfileModuleName(tsIndex, schema)}`;
4236
+ const flatProfile = tsIndex.flatProfile(schema);
4237
+ return { className, modulePath, flatProfile };
4192
4238
  };
4193
- var collectRequiredSliceMatches = (field) => {
4194
- if (!field.array || !field.slicing?.slices) return void 0;
4195
- const matches = Object.values(field.slicing.slices).filter((s) => s.min !== void 0 && s.min >= 1 && s.match && Object.keys(s.match).length > 0).map((s) => s.match);
4196
- return matches.length > 0 ? matches : void 0;
4239
+ var generateRawExtensionBody = (w, ext, targetPath, paramName = "input") => {
4240
+ w.line(
4241
+ `if (${paramName}.url !== ${JSON.stringify(ext.url)}) throw new Error(\`Expected extension url '${ext.url}', got '\${${paramName}.url}'\`)`
4242
+ );
4243
+ generateExtensionPush(w, targetPath, paramName);
4197
4244
  };
4198
- var collectProfileFactoryInfo = (flatProfile) => {
4199
- const autoFields = [];
4200
- const sliceAutoFields = [];
4201
- const params = [];
4202
- const autoAccessors = [];
4203
- const fields = flatProfile.fields ?? {};
4204
- const promotedChoices = /* @__PURE__ */ new Set();
4205
- if (isResourceIdentifier(flatProfile.base)) {
4206
- autoFields.push({ name: "resourceType", value: JSON.stringify(flatProfile.base.name) });
4245
+ var generateExtensionPush = (w, targetPath, extExpr) => {
4246
+ if (targetPath.length === 0) {
4247
+ w.line(`pushExtension(this.resource, ${extExpr})`);
4248
+ } else {
4249
+ w.line(
4250
+ `const target = ensurePath(this.resource as unknown as Record<string, unknown>, ${JSON.stringify(targetPath)})`
4251
+ );
4252
+ w.line("if (!Array.isArray(target.extension)) target.extension = [] as Extension[]");
4253
+ w.line(`pushExtension(target as unknown as { extension?: Extension[] }, ${extExpr})`);
4207
4254
  }
4208
- for (const [name, field] of Object.entries(fields)) {
4209
- if (field.excluded) continue;
4210
- if (isChoiceInstanceField(field)) continue;
4211
- if (isChoiceDeclarationField(field)) {
4212
- tryPromoteChoice(field, fields, params, promotedChoices);
4213
- continue;
4214
- }
4215
- if (field.valueConstraint) {
4216
- const value = JSON.stringify(field.valueConstraint.value);
4217
- autoFields.push({ name, value: field.array ? `[${value}]` : value });
4218
- if (isNotChoiceDeclarationField(field) && field.type) {
4219
- const tsType = resolveFieldTsType("", "", field) + (field.array ? "[]" : "");
4220
- autoAccessors.push({ name, tsType, typeId: field.type });
4255
+ };
4256
+ var generateExtLookup = (w, ext, targetPath) => {
4257
+ if (targetPath.length === 0) {
4258
+ w.line(`const ext = this.resource.extension?.find(e => e.url === "${ext.url}")`);
4259
+ } else {
4260
+ w.line(
4261
+ `const target = ensurePath(this.resource as unknown as Record<string, unknown>, ${JSON.stringify(targetPath)})`
4262
+ );
4263
+ w.line(`const ext = (target.extension as Extension[] | undefined)?.find(e => e.url === "${ext.url}")`);
4264
+ }
4265
+ };
4266
+ var effectiveGetterDefault = (w, hasProfile) => {
4267
+ const configured = w.opts.extensionGetterDefault ?? "flat";
4268
+ if (configured === "profile" && !hasProfile) return "flat";
4269
+ return configured;
4270
+ };
4271
+ var returnTypeForMode = (mode, inputType, profileClassName) => {
4272
+ if (mode === "profile" && profileClassName) return profileClassName;
4273
+ if (mode === "raw") return "Extension";
4274
+ return inputType;
4275
+ };
4276
+ var generateExtensionGetterOverloads = (w, ext, targetPath, methodName, inputType, extProfileInfo, generateInputBody) => {
4277
+ const hasProfile = !!extProfileInfo;
4278
+ const defaultMode = effectiveGetterDefault(w, hasProfile);
4279
+ const modes = hasProfile ? ["flat", "profile", "raw"] : ["flat", "raw"];
4280
+ for (const mode of modes) {
4281
+ const rt = returnTypeForMode(mode, inputType, extProfileInfo?.className);
4282
+ w.lineSM(`public ${methodName}(mode: '${mode}'): ${rt} | undefined`);
4283
+ }
4284
+ const defaultReturn = returnTypeForMode(defaultMode, inputType, extProfileInfo?.className);
4285
+ w.lineSM(`public ${methodName}(): ${defaultReturn} | undefined`);
4286
+ const allReturns = [...new Set(modes.map((m) => returnTypeForMode(m, inputType, extProfileInfo?.className)))];
4287
+ const modesUnion = modes.map((m) => `'${m}'`).join(" | ");
4288
+ w.curlyBlock(
4289
+ ["public", methodName, `(mode: ${modesUnion} = '${defaultMode}'): ${allReturns.join(" | ")} | undefined`],
4290
+ () => {
4291
+ generateExtLookup(w, ext, targetPath);
4292
+ w.line("if (!ext) return undefined");
4293
+ w.line("if (mode === 'raw') return ext");
4294
+ if (hasProfile) {
4295
+ w.line(`if (mode === 'profile') return ${extProfileInfo?.className}.apply(ext)`);
4221
4296
  }
4222
- continue;
4297
+ generateInputBody();
4223
4298
  }
4224
- if (isNotChoiceDeclarationField(field)) {
4225
- const requiredMatches = collectRequiredSliceMatches(field);
4226
- if (requiredMatches) {
4227
- const defaultValue = `[${requiredMatches.map((m) => JSON.stringify(m)).join(",")}]`;
4228
- if (field.type) {
4229
- const tsType = resolveFieldTsType("", "", field) + (field.array ? "[]" : "");
4230
- sliceAutoFields.push({
4231
- name,
4232
- tsType,
4233
- typeId: field.type,
4234
- defaultValue,
4235
- matches: requiredMatches.map((m) => JSON.stringify(m))
4299
+ );
4300
+ };
4301
+ var generateComplexExtensionSetter = (w, info) => {
4302
+ const { ext, flatProfile, setMethodName, targetPath, extProfileInfo } = info;
4303
+ const tsProfileName = tsResourceName(flatProfile.identifier);
4304
+ const inputTypeName = tsExtensionFlatTypeName(tsProfileName, ext.name);
4305
+ const extProfileHasFlatInput = extProfileInfo ? collectSubExtensionSlices(extProfileInfo.flatProfile).length > 0 : false;
4306
+ if (extProfileInfo && extProfileHasFlatInput) {
4307
+ const paramType = `${extProfileInfo.className}Flat | ${extProfileInfo.className} | Extension`;
4308
+ w.curlyBlock(["public", setMethodName, `(input: ${paramType}): this`], () => {
4309
+ w.ifElseChain(
4310
+ [
4311
+ {
4312
+ cond: `input instanceof ${extProfileInfo.className}`,
4313
+ body: () => generateExtensionPush(w, targetPath, "input.toResource()")
4314
+ },
4315
+ {
4316
+ cond: "isExtension<Extension>(input)",
4317
+ body: () => generateRawExtensionBody(w, ext, targetPath)
4318
+ }
4319
+ ],
4320
+ () => generateExtensionPush(w, targetPath, `${extProfileInfo.className}.createResource(input)`)
4321
+ );
4322
+ w.line("return this");
4323
+ });
4324
+ } else {
4325
+ w.curlyBlock(["public", setMethodName, `(input: ${inputTypeName}): this`], () => {
4326
+ w.line("const subExtensions: Extension[] = []");
4327
+ for (const sub of ext.subExtensions ?? []) {
4328
+ const valueField = sub.valueType ? tsValueFieldName(sub.valueType) : "value";
4329
+ if (sub.max === "*") {
4330
+ w.curlyBlock(["if", `(input.${sub.name})`], () => {
4331
+ w.curlyBlock(["for", `(const item of input.${sub.name})`], () => {
4332
+ w.line(`subExtensions.push({ url: "${sub.url}", ${valueField}: item } as Extension)`);
4333
+ });
4334
+ });
4335
+ } else {
4336
+ w.curlyBlock(["if", `(input.${sub.name} !== undefined)`], () => {
4337
+ w.line(
4338
+ `subExtensions.push({ url: "${sub.url}", ${valueField}: input.${sub.name} } as Extension)`
4339
+ );
4236
4340
  });
4237
- autoAccessors.push({ name, tsType, typeId: field.type });
4238
4341
  }
4239
- continue;
4240
4342
  }
4241
- }
4242
- if (field.required) {
4243
- const tsType = resolveFieldTsType("", "", field) + (field.array ? "[]" : "");
4244
- params.push({ name, tsType, typeId: field.type });
4245
- }
4343
+ if (targetPath.length === 0) {
4344
+ w.line("const list = (this.resource.extension ??= [])");
4345
+ w.line(`list.push({ url: "${ext.url}", extension: subExtensions })`);
4346
+ } else {
4347
+ w.line(
4348
+ `const target = ensurePath(this.resource as unknown as Record<string, unknown>, ${JSON.stringify(targetPath)})`
4349
+ );
4350
+ w.line("if (!Array.isArray(target.extension)) target.extension = [] as Extension[]");
4351
+ w.line(`(target.extension as Extension[]).push({ url: "${ext.url}", extension: subExtensions })`);
4352
+ }
4353
+ w.line("return this");
4354
+ });
4246
4355
  }
4247
- const accessors = [...autoAccessors, ...collectChoiceAccessors(flatProfile, promotedChoices)];
4248
- return { autoFields, sliceAutoFields, params, accessors };
4249
4356
  };
4250
- var generateProfileIndexFile = (w, tsIndex, initialProfiles) => {
4251
- if (initialProfiles.length === 0) return;
4252
- w.cd("profiles", () => {
4253
- w.cat("index.ts", () => {
4254
- const profiles = initialProfiles.map((profile) => {
4255
- const className = tsProfileClassName(profile);
4256
- const resourceName = tsResourceName(profile.identifier);
4257
- const overrides = detectFieldOverrides(w, tsIndex, profile);
4258
- let typeExport;
4259
- if (overrides.size > 0) typeExport = resourceName;
4260
- return [profile, className, typeExport];
4261
- });
4262
- if (profiles.length === 0) return;
4263
- const classExports = /* @__PURE__ */ new Map();
4264
- const typeExports = /* @__PURE__ */ new Map();
4265
- for (const [profile, className, typeName] of profiles) {
4266
- const moduleName = tsProfileModuleName(tsIndex, profile);
4267
- if (!classExports.has(className)) {
4268
- classExports.set(className, `export { ${className} } from "./${moduleName}"`);
4269
- }
4270
- if (typeName && !typeExports.has(typeName)) {
4271
- typeExports.set(typeName, `export type { ${typeName} } from "./${moduleName}"`);
4272
- }
4273
- }
4274
- const allExports = [...classExports.values(), ...typeExports.values()].sort();
4275
- for (const exp of allExports) {
4276
- w.lineSM(exp);
4277
- }
4357
+ var generateComplexExtensionGetter = (w, info) => {
4358
+ const { ext, flatProfile, getMethodName, targetPath, extProfileInfo } = info;
4359
+ const tsProfileName = tsResourceName(flatProfile.identifier);
4360
+ const inputTypeName = tsExtensionFlatTypeName(tsProfileName, ext.name);
4361
+ const extProfileHasFlatInput = extProfileInfo ? collectSubExtensionSlices(extProfileInfo.flatProfile).length > 0 : false;
4362
+ const inputType = extProfileHasFlatInput && extProfileInfo ? `${extProfileInfo.className}Flat` : inputTypeName;
4363
+ generateExtensionGetterOverloads(w, ext, targetPath, getMethodName, inputType, extProfileInfo, () => {
4364
+ const configItems = (ext.subExtensions ?? []).map((sub) => {
4365
+ const valueField = sub.valueType ? tsValueFieldName(sub.valueType) : "value";
4366
+ const isArray = sub.max === "*";
4367
+ return `{ name: "${sub.url}", valueField: "${valueField}", isArray: ${isArray} }`;
4278
4368
  });
4369
+ w.line(`const config = [${configItems.join(", ")}]`);
4370
+ w.line(`return extractComplexExtension<${inputType}>(ext, config)`);
4279
4371
  });
4280
4372
  };
4281
- var tsTypeForProfileField = (_w, tsIndex, flatProfile, fieldName, field) => {
4282
- if (!isNotChoiceDeclarationField(field)) {
4283
- throw new Error(`Choice declaration fields not supported for '${fieldName}'`);
4284
- }
4285
- let tsType;
4286
- if (field.enum) {
4287
- if (field.type?.name === "Coding") {
4288
- tsType = `Coding<${tsEnumType(field.enum)}>`;
4289
- } else if (field.type?.name === "CodeableConcept") {
4290
- tsType = `CodeableConcept<${tsEnumType(field.enum)}>`;
4291
- } else {
4292
- tsType = tsEnumType(field.enum);
4293
- }
4294
- } else if (field.reference && field.reference.length > 0) {
4295
- const specialization = tsIndex.findLastSpecialization(flatProfile);
4296
- if (!isSpecializationTypeSchema(specialization))
4297
- throw new Error(`Invalid specialization for ${flatProfile.identifier}`);
4298
- const sField = specialization.fields?.[fieldName];
4299
- if (sField === void 0 || isChoiceDeclarationField(sField) || sField.reference === void 0)
4300
- throw new Error(`Invalid field declaration for ${fieldName}`);
4301
- const sRefs = sField.reference.map((e) => e.name);
4302
- const references = field.reference.map((ref) => {
4303
- const resRef = tsIndex.findLastSpecializationByIdentifier(ref);
4304
- if (resRef.name !== ref.name) {
4305
- return `"${resRef.name}" /*${ref.name}*/`;
4306
- }
4307
- return `'${ref.name}'`;
4308
- }).join(" | ");
4309
- if (sRefs.length === 1 && sRefs[0] === "Resource" && references !== '"Resource"') {
4310
- const cleanRefs = references.replace(/\/\*[^*]*\*\//g, "").trim();
4311
- tsType = `Reference<"Resource" /* ${cleanRefs} */ >`;
4312
- } else {
4313
- tsType = `Reference<${references}>`;
4314
- }
4315
- } else if (isPrimitiveIdentifier(field.type)) {
4316
- tsType = resolvePrimitiveType(field.type.name);
4317
- } else if (isNestedIdentifier(field.type)) {
4318
- tsType = tsResourceName(field.type);
4319
- } else if (field.type === void 0) {
4320
- throw new Error(`Undefined type for '${fieldName}' field at ${typeSchemaInfo(flatProfile)}`);
4373
+ var generateSingleValueExtensionSetter = (w, tsIndex, info) => {
4374
+ const { ext, setMethodName, targetPath, extProfileInfo } = info;
4375
+ const firstValueType = ext.valueTypes?.[0];
4376
+ if (!firstValueType) return;
4377
+ const valueType = tsTypeFromIdentifier(firstValueType);
4378
+ const valueField = tsValueFieldName(firstValueType);
4379
+ if (extProfileInfo) {
4380
+ const paramType = `${extProfileInfo.className} | Extension | ${valueType}`;
4381
+ const extHasValueParam = collectProfileFactoryInfo(tsIndex, extProfileInfo.flatProfile).params.some(
4382
+ (p) => p.name === valueField
4383
+ );
4384
+ const elseExpr = extHasValueParam ? `${extProfileInfo.className}.createResource({ ${valueField}: value as ${valueType} })` : `{ url: "${ext.url}", ${valueField}: value as ${valueType} } as Extension`;
4385
+ w.curlyBlock(["public", setMethodName, `(value: ${paramType}): this`], () => {
4386
+ w.ifElseChain(
4387
+ [
4388
+ {
4389
+ cond: `value instanceof ${extProfileInfo.className}`,
4390
+ body: () => generateExtensionPush(w, targetPath, "value.toResource()")
4391
+ },
4392
+ {
4393
+ cond: "isExtension(value)",
4394
+ body: () => generateRawExtensionBody(w, ext, targetPath, "value")
4395
+ }
4396
+ ],
4397
+ () => generateExtensionPush(w, targetPath, elseExpr)
4398
+ );
4399
+ w.line("return this");
4400
+ });
4321
4401
  } else {
4322
- tsType = field.type.name;
4402
+ w.curlyBlock(["public", setMethodName, `(value: ${valueType}): this`], () => {
4403
+ const extLiteral = `{ url: "${ext.url}", ${valueField}: value } as Extension`;
4404
+ generateExtensionPush(w, targetPath, extLiteral);
4405
+ w.line("return this");
4406
+ });
4323
4407
  }
4324
- return tsType;
4325
4408
  };
4326
- var generateProfileHelpersModule = (w) => {
4327
- w.cat("profile-helpers.ts", () => {
4328
- w.generateDisclaimer();
4329
- w.curlyBlock(
4330
- ["export const", "isRecord", "=", "(value: unknown): value is Record<string, unknown>", "=>"],
4331
- () => {
4332
- w.lineSM('return value !== null && typeof value === "object" && !Array.isArray(value)');
4333
- }
4334
- );
4335
- w.line();
4336
- w.curlyBlock(
4409
+ var generateSingleValueExtensionGetter = (w, info) => {
4410
+ const { ext, getMethodName, targetPath, extProfileInfo } = info;
4411
+ const firstValueType = ext.valueTypes?.[0];
4412
+ if (!firstValueType) return;
4413
+ const valueType = tsTypeFromIdentifier(firstValueType);
4414
+ const valueField = tsValueFieldName(firstValueType);
4415
+ generateExtensionGetterOverloads(w, ext, targetPath, getMethodName, valueType, extProfileInfo, () => {
4416
+ w.line(`return getExtensionValue<${valueType}>(ext, "${valueField}")`);
4417
+ });
4418
+ };
4419
+ var generateGenericExtensionSetter = (w, info) => {
4420
+ const { ext, setMethodName, targetPath } = info;
4421
+ w.curlyBlock(["public", setMethodName, `(value: Omit<Extension, "url"> | Extension): this`], () => {
4422
+ w.ifElseChain(
4337
4423
  [
4338
- "export const",
4339
- "getOrCreateObjectAtPath",
4340
- "=",
4341
- "(root: Record<string, unknown>, path: string[]): Record<string, unknown>",
4342
- "=>"
4424
+ {
4425
+ cond: "isExtension(value)",
4426
+ body: () => generateRawExtensionBody(w, ext, targetPath, "value")
4427
+ }
4343
4428
  ],
4344
- () => {
4345
- w.lineSM("let current: Record<string, unknown> = root");
4346
- w.curlyBlock(["for (const", "segment", "of", "path)"], () => {
4347
- w.curlyBlock(["if", "(Array.isArray(current[segment]))"], () => {
4348
- w.lineSM("const list = current[segment] as unknown[]");
4349
- w.curlyBlock(["if", "(list.length === 0)"], () => {
4350
- w.lineSM("list.push({})");
4351
- });
4352
- w.lineSM("current = list[0] as Record<string, unknown>");
4353
- });
4354
- w.curlyBlock(["else"], () => {
4355
- w.curlyBlock(["if", "(!isRecord(current[segment]))"], () => {
4356
- w.lineSM("current[segment] = {}");
4357
- });
4358
- w.lineSM("current = current[segment] as Record<string, unknown>");
4359
- });
4360
- });
4361
- w.lineSM("return current");
4362
- }
4363
- );
4364
- w.line();
4365
- w.curlyBlock(
4366
- [
4367
- "export const",
4368
- "mergeMatch",
4369
- "=",
4370
- "(target: Record<string, unknown>, match: Record<string, unknown>): void",
4371
- "=>"
4372
- ],
4373
- () => {
4374
- w.curlyBlock(["for (const", "[key, matchValue]", "of", "Object.entries(match))"], () => {
4375
- w.curlyBlock(
4376
- ["if", '(key === "__proto__" || key === "constructor" || key === "prototype")'],
4377
- () => {
4378
- w.lineSM("continue");
4379
- }
4380
- );
4381
- w.curlyBlock(["if", "(isRecord(matchValue))"], () => {
4382
- w.curlyBlock(["if", "(isRecord(target[key]))"], () => {
4383
- w.lineSM("mergeMatch(target[key] as Record<string, unknown>, matchValue)");
4384
- });
4385
- w.curlyBlock(["else"], () => {
4386
- w.lineSM("target[key] = { ...matchValue }");
4387
- });
4388
- });
4389
- w.curlyBlock(["else"], () => {
4390
- w.lineSM("target[key] = matchValue");
4391
- });
4392
- });
4393
- }
4394
- );
4395
- w.line();
4396
- w.curlyBlock(
4397
- [
4398
- "export const",
4399
- "applySliceMatch",
4400
- "=",
4401
- "<T extends Record<string, unknown>>(input: T, match: Record<string, unknown>): T",
4402
- "=>"
4403
- ],
4404
- () => {
4405
- w.lineSM("const result = { ...input } as Record<string, unknown>");
4406
- w.lineSM("mergeMatch(result, match)");
4407
- w.lineSM("return result as T");
4408
- }
4409
- );
4410
- w.line();
4411
- w.curlyBlock(["export const", "matchesValue", "=", "(value: unknown, match: unknown): boolean", "=>"], () => {
4412
- w.curlyBlock(["if", "(Array.isArray(match))"], () => {
4413
- w.curlyBlock(["if", "(!Array.isArray(value))"], () => w.lineSM("return false"));
4414
- w.lineSM("return match.every((matchItem) => value.some((item) => matchesValue(item, matchItem)))");
4415
- });
4416
- w.curlyBlock(["if", "(isRecord(match))"], () => {
4417
- w.curlyBlock(["if", "(!isRecord(value))"], () => w.lineSM("return false"));
4418
- w.curlyBlock(["for (const", "[key, matchValue]", "of", "Object.entries(match))"], () => {
4419
- w.curlyBlock(["if", "(!matchesValue((value as Record<string, unknown>)[key], matchValue))"], () => {
4420
- w.lineSM("return false");
4421
- });
4422
- });
4423
- w.lineSM("return true");
4424
- });
4425
- w.lineSM("return value === match");
4426
- });
4427
- w.line();
4428
- w.curlyBlock(
4429
- ["export const", "matchesSlice", "=", "(value: unknown, match: Record<string, unknown>): boolean", "=>"],
4430
- () => {
4431
- w.lineSM("return matchesValue(value, match)");
4432
- }
4433
- );
4434
- w.line();
4435
- w.curlyBlock(
4436
- [
4437
- "export const",
4438
- "extractComplexExtension",
4439
- "=",
4440
- "(extension: { extension?: Array<{ url?: string; [key: string]: unknown }> } | undefined, config: Array<{ name: string; valueField: string; isArray: boolean }>): Record<string, unknown> | undefined",
4441
- "=>"
4442
- ],
4443
- () => {
4444
- w.lineSM("if (!extension?.extension) return undefined");
4445
- w.lineSM("const result: Record<string, unknown> = {}");
4446
- w.curlyBlock(["for (const", "{ name, valueField, isArray }", "of", "config)"], () => {
4447
- w.lineSM("const subExts = extension.extension.filter(e => e.url === name)");
4448
- w.curlyBlock(["if", "(isArray)"], () => {
4449
- w.lineSM("result[name] = subExts.map(e => (e as Record<string, unknown>)[valueField])");
4450
- });
4451
- w.curlyBlock(["else if", "(subExts[0])"], () => {
4452
- w.lineSM("result[name] = (subExts[0] as Record<string, unknown>)[valueField]");
4453
- });
4454
- });
4455
- w.lineSM("return result");
4456
- }
4457
- );
4458
- w.line();
4459
- w.curlyBlock(
4460
- [
4461
- "export const",
4462
- "extractSliceSimplified",
4463
- "=",
4464
- "<T extends Record<string, unknown>>(slice: T, matchKeys: string[]): Partial<T>",
4465
- "=>"
4466
- ],
4467
- () => {
4468
- w.lineSM("const result = { ...slice } as Record<string, unknown>");
4469
- w.curlyBlock(["for (const", "key", "of", "matchKeys)"], () => {
4470
- w.lineSM("delete result[key]");
4471
- });
4472
- w.lineSM("return result as Partial<T>");
4473
- }
4474
- );
4475
- w.line();
4476
- w.curlyBlock(
4477
- [
4478
- "export const",
4479
- "wrapSliceChoice",
4480
- "=",
4481
- "(input: Record<string, unknown>, choiceVariant: string): Record<string, unknown>",
4482
- "=>"
4483
- ],
4484
- () => {
4485
- w.lineSM("if (Object.keys(input).length === 0) return input");
4486
- w.lineSM("return { [choiceVariant]: input }");
4487
- }
4488
- );
4489
- w.line();
4490
- w.curlyBlock(
4491
- [
4492
- "export const",
4493
- "flattenSliceChoice",
4494
- "=",
4495
- "(slice: Record<string, unknown>, matchKeys: string[], choiceVariant: string): Record<string, unknown>",
4496
- "=>"
4497
- ],
4498
- () => {
4499
- w.lineSM("const result = { ...slice } as Record<string, unknown>");
4500
- w.curlyBlock(["for (const", "key", "of", "matchKeys)"], () => {
4501
- w.lineSM("delete result[key]");
4502
- });
4503
- w.lineSM("const variantValue = result[choiceVariant]");
4504
- w.lineSM("delete result[choiceVariant]");
4505
- w.curlyBlock(["if", "(isRecord(variantValue))"], () => {
4506
- w.lineSM("Object.assign(result, variantValue)");
4507
- });
4508
- w.lineSM("return result");
4509
- }
4510
- );
4511
- w.line();
4512
- w.curlyBlock(
4513
- [
4514
- "export const",
4515
- "validateRequired",
4516
- "=",
4517
- "(r: Record<string, unknown>, field: string, path: string): string | undefined",
4518
- "=>"
4519
- ],
4520
- () => {
4521
- w.lineSM(
4522
- "return r[field] === undefined || r[field] === null ? `${path}: required field '${field}' is missing` : undefined"
4523
- );
4524
- }
4525
- );
4526
- w.line();
4527
- w.curlyBlock(
4528
- [
4529
- "export const",
4530
- "validateExcluded",
4531
- "=",
4532
- "(r: Record<string, unknown>, field: string, path: string): string | undefined",
4533
- "=>"
4534
- ],
4535
- () => {
4536
- w.lineSM("return r[field] !== undefined ? `${path}: field '${field}' must not be present` : undefined");
4537
- }
4538
- );
4539
- w.line();
4540
- w.curlyBlock(
4541
- [
4542
- "export const",
4543
- "validateFixedValue",
4544
- "=",
4545
- "(r: Record<string, unknown>, field: string, expected: unknown, path: string): string | undefined",
4546
- "=>"
4547
- ],
4548
- () => {
4549
- w.lineSM(
4550
- "return matchesValue(r[field], expected) ? undefined : `${path}: field '${field}' does not match expected fixed value`"
4551
- );
4552
- }
4553
- );
4554
- w.line();
4555
- w.curlyBlock(
4556
- [
4557
- "export const",
4558
- "validateSliceCardinality",
4559
- "=",
4560
- "(items: unknown[] | undefined, match: Record<string, unknown>, sliceName: string, min: number, max: number, path: string): string[]",
4561
- "=>"
4562
- ],
4563
- () => {
4564
- w.lineSM("const count = (items ?? []).filter(item => matchesSlice(item, match)).length");
4565
- w.lineSM("const errors: string[] = []");
4566
- w.curlyBlock(["if", "(count < min)"], () => {
4567
- w.lineSM(
4568
- "errors.push(`${path}: slice '${sliceName}' requires at least ${min} item(s), found ${count}`)"
4569
- );
4570
- });
4571
- w.curlyBlock(["if", "(max > 0 && count > max)"], () => {
4572
- w.lineSM(
4573
- "errors.push(`${path}: slice '${sliceName}' allows at most ${max} item(s), found ${count}`)"
4574
- );
4575
- });
4576
- w.lineSM("return errors");
4577
- }
4578
- );
4579
- w.line();
4580
- w.curlyBlock(
4581
- [
4582
- "export const",
4583
- "validateEnum",
4584
- "=",
4585
- "(value: unknown, allowed: string[], field: string, path: string): string | undefined",
4586
- "=>"
4587
- ],
4588
- () => {
4589
- w.lineSM("if (value === undefined || value === null) return undefined");
4590
- w.curlyBlock(["if", "(typeof value === 'string')"], () => {
4591
- w.lineSM(
4592
- "return allowed.includes(value) ? undefined : `${path}: field '${field}' value '${value}' is not in allowed values`"
4593
- );
4594
- });
4595
- w.lineSM("const rec = value as Record<string, unknown>");
4596
- w.comment("Coding");
4597
- w.curlyBlock(["if", "(typeof rec.code === 'string' && rec.system !== undefined)"], () => {
4598
- w.lineSM(
4599
- "return allowed.includes(rec.code) ? undefined : `${path}: field '${field}' code '${rec.code}' is not in allowed values`"
4600
- );
4601
- });
4602
- w.comment("CodeableConcept");
4603
- w.curlyBlock(["if", "(Array.isArray(rec.coding))"], () => {
4604
- w.lineSM(
4605
- "const codes = (rec.coding as Array<Record<string, unknown>>).map(c => c.code as string).filter(Boolean)"
4606
- );
4607
- w.lineSM("const hasValid = codes.some(c => allowed.includes(c))");
4608
- w.lineSM(
4609
- "return hasValid ? undefined : `${path}: field '${field}' has no coding with an allowed code`"
4610
- );
4611
- });
4612
- w.lineSM("return undefined");
4613
- }
4614
- );
4615
- w.line();
4616
- w.curlyBlock(
4617
- [
4618
- "export const",
4619
- "validateReference",
4620
- "=",
4621
- "(value: unknown, allowed: string[], field: string, path: string): string | undefined",
4622
- "=>"
4623
- ],
4624
- () => {
4625
- w.lineSM("if (value === undefined || value === null) return undefined");
4626
- w.lineSM("const ref = (value as Record<string, unknown>).reference as string | undefined");
4627
- w.lineSM("if (!ref) return undefined");
4628
- w.lineSM("const slashIdx = ref.indexOf('/')");
4629
- w.lineSM("if (slashIdx === -1) return undefined");
4630
- w.lineSM("const refType = ref.slice(0, slashIdx)");
4631
- w.lineSM(
4632
- "return allowed.includes(refType) ? undefined : `${path}: field '${field}' references '${refType}' but only ${allowed.join(', ')} are allowed`"
4633
- );
4634
- }
4429
+ () => generateExtensionPush(w, targetPath, `{ url: "${ext.url}", ...value } as Extension`)
4635
4430
  );
4431
+ w.line("return this");
4636
4432
  });
4637
4433
  };
4638
- var generateProfileHelpersImport = (w, options) => {
4639
- const imports = [];
4640
- if (options.needsSliceHelpers) {
4641
- imports.push("applySliceMatch", "matchesSlice");
4642
- }
4643
- if (options.needsGetOrCreateObjectAtPath) {
4644
- imports.push("getOrCreateObjectAtPath");
4645
- }
4646
- if (options.needsExtensionExtraction) {
4647
- imports.push("extractComplexExtension");
4648
- }
4649
- if (options.needsSliceExtraction) {
4650
- imports.push("extractSliceSimplified");
4651
- }
4652
- if (options.needsSliceChoiceHelpers) {
4653
- imports.push("wrapSliceChoice", "flattenSliceChoice");
4654
- }
4655
- if (options.needsValidation) {
4656
- imports.push(
4657
- "validateRequired",
4658
- "validateExcluded",
4659
- "validateFixedValue",
4660
- "validateSliceCardinality",
4661
- "validateEnum",
4662
- "validateReference"
4663
- );
4664
- }
4665
- if (imports.length > 0) {
4666
- w.lineSM(`import { ${imports.join(", ")} } from "../../profile-helpers"`);
4667
- }
4434
+ var generateGenericExtensionGetter = (w, info) => {
4435
+ const { ext, getMethodName, targetPath } = info;
4436
+ w.curlyBlock(["public", getMethodName, "(): Extension | undefined"], () => {
4437
+ if (targetPath.length === 0) {
4438
+ w.line(`return this.resource.extension?.find(e => e.url === "${ext.url}")`);
4439
+ } else {
4440
+ w.line(
4441
+ `const target = ensurePath(this.resource as unknown as Record<string, unknown>, ${JSON.stringify(targetPath)})`
4442
+ );
4443
+ w.line(`return (target.extension as Extension[] | undefined)?.find(e => e.url === "${ext.url}")`);
4444
+ }
4445
+ });
4668
4446
  };
4669
- var collectTypesFromSlices = (tsIndex, flatProfile, addType) => {
4670
- for (const field of Object.values(flatProfile.fields ?? {})) {
4671
- if (!isNotChoiceDeclarationField(field) || !field.slicing?.slices || !field.type) continue;
4672
- for (const slice of Object.values(field.slicing.slices)) {
4673
- if (Object.keys(slice.match ?? {}).length > 0) {
4674
- addType(field.type);
4675
- const cc = detectConstrainedChoice(tsIndex, flatProfile, field.type, slice.elements);
4676
- if (cc) addType(cc.variantTypeId);
4677
- }
4447
+ var generateExtensionMethods = (w, tsIndex, flatProfile, extensionBaseNames) => {
4448
+ for (const ext of flatProfile.extensions ?? []) {
4449
+ if (!ext.url) continue;
4450
+ const baseName = tsResolvedExtensionBaseName(extensionBaseNames, ext.url, ext.path, ext.name);
4451
+ const targetPath = ext.path.split(".").filter((segment) => segment !== "extension");
4452
+ const extProfileInfo = resolveExtensionProfile(tsIndex, flatProfile.identifier.package, ext.url);
4453
+ const info = {
4454
+ ext,
4455
+ flatProfile,
4456
+ setMethodName: `set${baseName}`,
4457
+ getMethodName: `get${baseName}`,
4458
+ targetPath,
4459
+ extProfileInfo
4460
+ };
4461
+ if (ext.isComplex && ext.subExtensions) {
4462
+ generateComplexExtensionSetter(w, info);
4463
+ w.line();
4464
+ generateComplexExtensionGetter(w, info);
4465
+ } else if (ext.valueTypes?.length === 1 && ext.valueTypes[0]) {
4466
+ generateSingleValueExtensionSetter(w, tsIndex, info);
4467
+ w.line();
4468
+ generateSingleValueExtensionGetter(w, info);
4469
+ } else {
4470
+ generateGenericExtensionSetter(w, info);
4471
+ w.line();
4472
+ generateGenericExtensionGetter(w, info);
4678
4473
  }
4474
+ w.line();
4679
4475
  }
4680
4476
  };
4681
4477
  var collectTypesFromExtensions = (tsIndex, flatProfile, addType) => {
@@ -4706,23 +4502,438 @@ var collectTypesFromExtensions = (tsIndex, flatProfile, addType) => {
4706
4502
  }
4707
4503
  return needsExtensionType;
4708
4504
  };
4709
- var collectTypesFromFieldOverrides = (tsIndex, flatProfile, addType) => {
4710
- const referenceUrl = "http://hl7.org/fhir/StructureDefinition/Reference";
4711
- const referenceSchema = tsIndex.resolveByUrl(flatProfile.identifier.package, referenceUrl);
4712
- const specialization = tsIndex.findLastSpecialization(flatProfile);
4713
- if (!isSpecializationTypeSchema(specialization)) return;
4714
- for (const [fieldName, pField] of Object.entries(flatProfile.fields ?? {})) {
4715
- if (!isNotChoiceDeclarationField(pField)) continue;
4505
+ var collectTypesFromFlatInput = (tsIndex, flatProfile, addType) => {
4506
+ if (flatProfile.base.name !== "Extension") return;
4507
+ const subSlices = collectSubExtensionSlices(flatProfile);
4508
+ for (const sub of subSlices) {
4509
+ const tsType = sub.tsType;
4510
+ if (["string", "boolean", "number"].includes(tsType)) continue;
4511
+ const fhirUrl = `http://hl7.org/fhir/StructureDefinition/${tsType}`;
4512
+ const schema = tsIndex.resolveByUrl(flatProfile.identifier.package, fhirUrl);
4513
+ if (schema) addType(schema.identifier);
4514
+ }
4515
+ };
4516
+
4517
+ // src/api/writer-generator/typescript/profile-slices.ts
4518
+ var collectChoiceBaseNames = (tsIndex, typeId) => {
4519
+ const names = /* @__PURE__ */ new Set();
4520
+ const schema = tsIndex.resolve(typeId);
4521
+ if (schema && "fields" in schema && schema.fields) {
4522
+ for (const [name, f] of Object.entries(schema.fields)) {
4523
+ if (isChoiceDeclarationField(f)) names.add(name);
4524
+ }
4525
+ }
4526
+ return names;
4527
+ };
4528
+ var collectTypesFromSlices = (tsIndex, flatProfile, addType) => {
4529
+ const pkgName = flatProfile.identifier.package;
4530
+ for (const field of Object.values(flatProfile.fields ?? {})) {
4531
+ if (!isNotChoiceDeclarationField(field) || !field.slicing?.slices || !field.type) continue;
4532
+ for (const slice of Object.values(field.slicing.slices)) {
4533
+ if (Object.keys(slice.match ?? {}).length > 0) {
4534
+ addType(field.type);
4535
+ const cc = slice.elements ? tsIndex.constrainedChoice(pkgName, field.type, slice.elements) : void 0;
4536
+ if (cc) addType(cc.variantType);
4537
+ }
4538
+ }
4539
+ }
4540
+ };
4541
+ var collectRequiredSliceNames = (field) => {
4542
+ if (!field.array || !field.slicing?.slices) return void 0;
4543
+ const names = Object.entries(field.slicing.slices).filter(([_, s]) => s.min !== void 0 && s.min >= 1 && s.match && Object.keys(s.match).length > 0).map(([name]) => name);
4544
+ return names.length > 0 ? names : void 0;
4545
+ };
4546
+ var collectSliceDefs = (tsIndex, flatProfile) => Object.entries(flatProfile.fields ?? {}).filter(([_, field]) => isNotChoiceDeclarationField(field) && field.slicing?.slices).flatMap(([fieldName, field]) => {
4547
+ if (!isNotChoiceDeclarationField(field) || !field.slicing?.slices || !field.type) return [];
4548
+ const baseType = tsTypeFromIdentifier(field.type);
4549
+ const pkgName = flatProfile.identifier.package;
4550
+ const choiceBaseNames = collectChoiceBaseNames(tsIndex, field.type);
4551
+ return Object.entries(field.slicing.slices).filter(([_, slice]) => Object.keys(slice.match ?? {}).length > 0).map(([sliceName, slice]) => {
4552
+ const matchFields = Object.keys(slice.match ?? {});
4553
+ const required = (slice.required ?? []).filter(
4554
+ (name) => !matchFields.includes(name) && !choiceBaseNames.has(name)
4555
+ );
4556
+ const cc = slice.elements ? tsIndex.constrainedChoice(pkgName, field.type, slice.elements) : void 0;
4557
+ const constrainedChoice = cc && !isPrimitiveIdentifier(cc.variantType) ? cc : void 0;
4558
+ return {
4559
+ fieldName,
4560
+ baseType,
4561
+ sliceName,
4562
+ match: slice.match ?? {},
4563
+ required,
4564
+ excluded: slice.excluded ?? [],
4565
+ array: Boolean(field.array),
4566
+ constrainedChoice
4567
+ };
4568
+ });
4569
+ });
4570
+ var generateSliceSetters = (w, sliceDefs, flatProfile, sliceBaseNames) => {
4571
+ const profileClassName = tsProfileClassName(flatProfile);
4572
+ const tsProfileName = tsResourceName(flatProfile.identifier);
4573
+ for (const sliceDef of sliceDefs) {
4574
+ const baseName = tsResolvedSliceBaseName(sliceBaseNames, sliceDef.fieldName, sliceDef.sliceName);
4575
+ const methodName = `set${baseName}`;
4576
+ const typeName = tsSliceFlatTypeName(tsProfileName, sliceDef.fieldName, sliceDef.sliceName);
4577
+ const matchRef = `${profileClassName}.${tsSliceStaticName(sliceDef.sliceName)}SliceMatch`;
4578
+ const tsField = tsFieldName(sliceDef.fieldName);
4579
+ const fieldAccess = tsGet("this.resource", tsField);
4580
+ const baseType = sliceDef.baseType;
4581
+ const inputOptional = sliceDef.required.length === 0;
4582
+ const unionType = `${typeName} | ${baseType}`;
4583
+ const paramSignature = inputOptional ? `(input?: ${unionType}): this` : `(input: ${unionType}): this`;
4584
+ w.curlyBlock(["public", methodName, paramSignature], () => {
4585
+ w.line(`const match = ${matchRef}`);
4586
+ w.curlyBlock(["if", "(input && matchesValue(input, match))"], () => {
4587
+ if (sliceDef.array) {
4588
+ w.line(`setArraySlice(${fieldAccess} ??= [], match, input as ${baseType})`);
4589
+ } else {
4590
+ w.line(`${fieldAccess} = input as ${baseType}`);
4591
+ }
4592
+ w.line("return this");
4593
+ });
4594
+ const inputExpr = inputOptional ? "input ?? {}" : "input";
4595
+ if (sliceDef.constrainedChoice) {
4596
+ const cc = sliceDef.constrainedChoice;
4597
+ w.line(`const wrapped = wrapSliceChoice<${baseType}>(${inputExpr}, ${JSON.stringify(cc.variant)})`);
4598
+ w.line(`const value = applySliceMatch<${baseType}>(wrapped, match)`);
4599
+ } else {
4600
+ w.line(`const value = applySliceMatch<${baseType}>(${inputExpr}, match)`);
4601
+ }
4602
+ if (sliceDef.array) {
4603
+ w.line(`setArraySlice(${fieldAccess} ??= [], match, value)`);
4604
+ } else {
4605
+ w.line(`${fieldAccess} = value`);
4606
+ }
4607
+ w.line("return this");
4608
+ });
4609
+ w.line();
4610
+ }
4611
+ };
4612
+ var generateSliceGetters = (w, sliceDefs, flatProfile, sliceBaseNames) => {
4613
+ const profileClassName = tsProfileClassName(flatProfile);
4614
+ const tsProfileName = tsResourceName(flatProfile.identifier);
4615
+ const defaultMode = w.opts.sliceGetterDefault ?? "flat";
4616
+ for (const sliceDef of sliceDefs) {
4617
+ const baseName = tsResolvedSliceBaseName(sliceBaseNames, sliceDef.fieldName, sliceDef.sliceName);
4618
+ const getMethodName = `get${baseName}`;
4619
+ const typeName = tsSliceFlatTypeName(tsProfileName, sliceDef.fieldName, sliceDef.sliceName);
4620
+ const matchRef = `${profileClassName}.${tsSliceStaticName(sliceDef.sliceName)}SliceMatch`;
4621
+ const matchKeys = JSON.stringify(Object.keys(sliceDef.match));
4622
+ const tsField = tsFieldName(sliceDef.fieldName);
4623
+ const fieldAccess = tsGet("this.resource", tsField);
4624
+ const baseType = sliceDef.baseType;
4625
+ const defaultReturn = defaultMode === "raw" ? baseType : typeName;
4626
+ w.lineSM(`public ${getMethodName}(mode: 'flat'): ${typeName} | undefined`);
4627
+ w.lineSM(`public ${getMethodName}(mode: 'raw'): ${baseType} | undefined`);
4628
+ w.lineSM(`public ${getMethodName}(): ${defaultReturn} | undefined`);
4629
+ w.curlyBlock(
4630
+ [
4631
+ "public",
4632
+ getMethodName,
4633
+ `(mode: 'flat' | 'raw' = '${defaultMode}'): ${typeName} | ${baseType} | undefined`
4634
+ ],
4635
+ () => {
4636
+ w.line(`const match = ${matchRef}`);
4637
+ if (sliceDef.array) {
4638
+ w.line(`const item = getArraySlice(${fieldAccess}, match)`);
4639
+ w.line("if (!item) return undefined");
4640
+ } else {
4641
+ w.line(`const item = ${fieldAccess}`);
4642
+ w.line("if (!item || !matchesValue(item, match)) return undefined");
4643
+ }
4644
+ w.line("if (mode === 'raw') return item");
4645
+ if (sliceDef.constrainedChoice) {
4646
+ const cc = sliceDef.constrainedChoice;
4647
+ w.line(`return unwrapSliceChoice<${typeName}>(item, ${matchKeys}, ${JSON.stringify(cc.variant)})`);
4648
+ } else {
4649
+ w.line(`return stripMatchKeys<${typeName}>(item, ${matchKeys})`);
4650
+ }
4651
+ }
4652
+ );
4653
+ w.line();
4654
+ }
4655
+ };
4656
+
4657
+ // src/api/writer-generator/typescript/profile-validation.ts
4658
+ var collectRegularFieldValidation = (errors, warnings, name, field, resolveRef, canonicalUrlExpr) => {
4659
+ if (field.excluded) {
4660
+ errors.push(`...validateExcluded(res, profileName, ${JSON.stringify(name)})`);
4661
+ return;
4662
+ }
4663
+ if (field.required) errors.push(`...validateRequired(res, profileName, ${JSON.stringify(name)})`);
4664
+ if (field.valueConstraint) {
4665
+ const valueExpr = canonicalUrlExpr && name === "url" && field.valueConstraint.value === canonicalUrlExpr.url ? canonicalUrlExpr.expr : JSON.stringify(field.valueConstraint.value);
4666
+ errors.push(`...validateFixedValue(res, profileName, ${JSON.stringify(name)}, ${valueExpr})`);
4667
+ }
4668
+ if (field.enum) {
4669
+ const target = field.enum.isOpen ? warnings : errors;
4670
+ target.push(`...validateEnum(res, profileName, ${JSON.stringify(name)}, ${JSON.stringify(field.enum.values)})`);
4671
+ }
4672
+ if (field.mustSupport && !field.required)
4673
+ warnings.push(`...validateMustSupport(res, profileName, ${JSON.stringify(name)})`);
4674
+ if (field.reference && field.reference.length > 0)
4675
+ errors.push(
4676
+ `...validateReference(res, profileName, ${JSON.stringify(name)}, ${JSON.stringify(field.reference.map((ref) => resolveRef(ref).name))})`
4677
+ );
4678
+ if (field.slicing?.slices) {
4679
+ for (const [sliceName, slice] of Object.entries(field.slicing.slices)) {
4680
+ if (slice.min === void 0 && slice.max === void 0) continue;
4681
+ const match = slice.match ?? {};
4682
+ if (Object.keys(match).length === 0) continue;
4683
+ const min = slice.min ?? 0;
4684
+ const max = slice.max ?? 0;
4685
+ errors.push(
4686
+ `...validateSliceCardinality(res, profileName, ${JSON.stringify(name)}, ${JSON.stringify(match)}, ${JSON.stringify(sliceName)}, ${min}, ${max})`
4687
+ );
4688
+ }
4689
+ }
4690
+ };
4691
+ var generateValidateMethod = (w, tsIndex, flatProfile) => {
4692
+ const fields = flatProfile.fields ?? {};
4693
+ const profileName = flatProfile.identifier.name;
4694
+ const canonicalUrl = flatProfile.identifier.url;
4695
+ const canonicalUrlExpr = canonicalUrl ? { url: canonicalUrl, expr: `${tsProfileClassName(flatProfile)}.canonicalUrl` } : void 0;
4696
+ w.curlyBlock(["validate(): { errors: string[]; warnings: string[] }"], () => {
4697
+ w.line(`const profileName = "${profileName}"`);
4698
+ w.line("const res = this.resource");
4699
+ const errors = [];
4700
+ const warnings = [];
4701
+ for (const [name, field] of Object.entries(fields)) {
4702
+ if (isChoiceInstanceField(field)) continue;
4703
+ if (isChoiceDeclarationField(field)) {
4704
+ if (field.required)
4705
+ errors.push(`...validateChoiceRequired(res, profileName, ${JSON.stringify(field.choices)})`);
4706
+ continue;
4707
+ }
4708
+ collectRegularFieldValidation(
4709
+ errors,
4710
+ warnings,
4711
+ name,
4712
+ field,
4713
+ tsIndex.findLastSpecializationByIdentifier,
4714
+ canonicalUrlExpr
4715
+ );
4716
+ }
4717
+ const emitArray = (label, exprs) => {
4718
+ if (exprs.length === 0) {
4719
+ w.line(`${label}: [],`);
4720
+ } else {
4721
+ w.squareBlock([`${label}:`], () => {
4722
+ for (const expr of exprs) w.line(`${expr},`);
4723
+ }, [","]);
4724
+ }
4725
+ };
4726
+ w.curlyBlock(["return"], () => {
4727
+ emitArray("errors", errors);
4728
+ emitArray("warnings", warnings);
4729
+ });
4730
+ });
4731
+ w.line();
4732
+ };
4733
+
4734
+ // src/api/writer-generator/typescript/profile.ts
4735
+ var collectChoiceAccessors = (flatProfile, promotedChoices) => {
4736
+ const accessors = [];
4737
+ for (const [name, field] of Object.entries(flatProfile.fields ?? {})) {
4738
+ if (field.excluded) continue;
4739
+ if (!isChoiceInstanceField(field)) continue;
4740
+ if (promotedChoices.has(name)) continue;
4741
+ const tsType = tsTypeFromIdentifier(field.type) + (field.array ? "[]" : "");
4742
+ accessors.push({ name, tsType, typeId: field.type });
4743
+ }
4744
+ return accessors;
4745
+ };
4746
+ var tryPromoteChoice = (field, fields, params, promotedChoices) => {
4747
+ if (!isChoiceDeclarationField(field) || !field.required || field.choices.length !== 1) return;
4748
+ const choiceName = field.choices[0];
4749
+ if (!choiceName) return;
4750
+ const choiceField = fields[choiceName];
4751
+ if (!choiceField || !isChoiceInstanceField(choiceField)) return;
4752
+ const tsType = tsTypeFromIdentifier(choiceField.type) + (choiceField.array ? "[]" : "");
4753
+ params.push({ name: choiceName, tsType, typeId: choiceField.type });
4754
+ promotedChoices.add(choiceName);
4755
+ };
4756
+ var collectProfileFactoryInfo = (tsIndex, flatProfile) => {
4757
+ const autoFields = [];
4758
+ const sliceAutoFields = [];
4759
+ const params = [];
4760
+ const autoAccessors = [];
4761
+ const fields = flatProfile.fields ?? {};
4762
+ const promotedChoices = /* @__PURE__ */ new Set();
4763
+ const resolveRef = tsIndex.findLastSpecializationByIdentifier;
4764
+ if (isResourceIdentifier(flatProfile.base)) {
4765
+ autoFields.push({ name: "resourceType", value: JSON.stringify(flatProfile.base.name) });
4766
+ }
4767
+ for (const [name, field] of Object.entries(fields)) {
4768
+ if (field.excluded) continue;
4769
+ if (isChoiceInstanceField(field)) continue;
4770
+ if (isChoiceDeclarationField(field)) {
4771
+ tryPromoteChoice(field, fields, params, promotedChoices);
4772
+ continue;
4773
+ }
4774
+ if (field.valueConstraint) {
4775
+ const value = JSON.stringify(field.valueConstraint.value);
4776
+ autoFields.push({ name, value: field.array ? `[${value}]` : value });
4777
+ if (isNotChoiceDeclarationField(field) && field.type) {
4778
+ const tsType = fieldTsType(field, resolveRef);
4779
+ autoAccessors.push({ name, tsType, typeId: field.type });
4780
+ }
4781
+ continue;
4782
+ }
4783
+ if (isNotChoiceDeclarationField(field)) {
4784
+ const sliceNames = collectRequiredSliceNames(field);
4785
+ if (sliceNames) {
4786
+ if (field.type) {
4787
+ const tsType = fieldTsType(field, resolveRef);
4788
+ sliceAutoFields.push({
4789
+ name,
4790
+ tsType,
4791
+ typeId: field.type,
4792
+ sliceNames
4793
+ });
4794
+ autoAccessors.push({ name, tsType, typeId: field.type });
4795
+ }
4796
+ continue;
4797
+ }
4798
+ }
4799
+ if (field.required) {
4800
+ const tsType = fieldTsType(field, resolveRef);
4801
+ params.push({ name, tsType, typeId: field.type });
4802
+ }
4803
+ }
4804
+ collectBaseRequiredParams(tsIndex, flatProfile, resolveRef, params, [
4805
+ ...autoFields.map((f) => f.name),
4806
+ ...sliceAutoFields.map((f) => f.name),
4807
+ ...params.map((f) => f.name),
4808
+ ...promotedChoices
4809
+ ]);
4810
+ const accessors = [...autoAccessors, ...collectChoiceAccessors(flatProfile, promotedChoices)];
4811
+ return { autoFields, sliceAutoFields, params, accessors };
4812
+ };
4813
+ var collectBaseRequiredParams = (tsIndex, flatProfile, resolveRef, params, coveredNames) => {
4814
+ const covered = new Set(coveredNames);
4815
+ const baseSchema = tsIndex.resolve(flatProfile.base);
4816
+ if (!baseSchema || !("fields" in baseSchema) || !baseSchema.fields) return;
4817
+ for (const [name, field] of Object.entries(baseSchema.fields)) {
4818
+ if (covered.has(name)) continue;
4819
+ if (!field.required) continue;
4820
+ if (isChoiceInstanceField(field)) continue;
4821
+ if (isChoiceDeclarationField(field)) continue;
4822
+ if (isNotChoiceDeclarationField(field) && field.type) {
4823
+ const tsType = fieldTsType(field, resolveRef);
4824
+ params.push({ name, tsType, typeId: field.type });
4825
+ }
4826
+ }
4827
+ };
4828
+ var generateProfileIndexFile = (w, tsIndex, initialProfiles) => {
4829
+ if (initialProfiles.length === 0) return;
4830
+ w.cd("profiles", () => {
4831
+ w.cat("index.ts", () => {
4832
+ const profiles = initialProfiles.map((profile) => {
4833
+ const className = tsProfileClassName(profile);
4834
+ const resourceName = tsResourceName(profile.identifier);
4835
+ const overrides = detectFieldOverrides(tsIndex, profile);
4836
+ let typeExport;
4837
+ if (overrides.size > 0) typeExport = resourceName;
4838
+ return [profile, className, typeExport];
4839
+ });
4840
+ if (profiles.length === 0) return;
4841
+ const classExports = /* @__PURE__ */ new Map();
4842
+ const typeExports = /* @__PURE__ */ new Map();
4843
+ for (const [profile, className, typeName] of profiles) {
4844
+ const moduleName = tsProfileModuleName(tsIndex, profile);
4845
+ if (!classExports.has(className)) {
4846
+ classExports.set(className, `export { ${className} } from "./${moduleName}"`);
4847
+ }
4848
+ if (typeName && !typeExports.has(typeName) && !classExports.has(typeName)) {
4849
+ typeExports.set(typeName, `export type { ${typeName} } from "./${moduleName}"`);
4850
+ }
4851
+ }
4852
+ const allExports = [...classExports.values(), ...typeExports.values()].sort();
4853
+ for (const exp of allExports) {
4854
+ w.lineSM(exp);
4855
+ }
4856
+ });
4857
+ });
4858
+ };
4859
+ var tsTypeForProfileField = (tsIndex, flatProfile, fieldName, field) => {
4860
+ if (!isNotChoiceDeclarationField(field)) {
4861
+ throw new Error(`Choice declaration fields not supported for '${fieldName}'`);
4862
+ }
4863
+ let tsType;
4864
+ if (field.enum) {
4865
+ if (field.type?.name === "Coding") {
4866
+ tsType = `Coding<${tsEnumType(field.enum)}>`;
4867
+ } else if (field.type?.name === "CodeableConcept") {
4868
+ tsType = `CodeableConcept<${tsEnumType(field.enum)}>`;
4869
+ } else {
4870
+ tsType = tsEnumType(field.enum);
4871
+ }
4872
+ } else if (field.reference && field.reference.length > 0) {
4873
+ const specialization = tsIndex.findLastSpecialization(flatProfile);
4874
+ if (!isSpecializationTypeSchema(specialization))
4875
+ throw new Error(`Invalid specialization for ${flatProfile.identifier}`);
4716
4876
  const sField = specialization.fields?.[fieldName];
4717
- if (!sField || isChoiceDeclarationField(sField)) continue;
4718
- if (pField.reference && sField.reference && pField.reference.length < sField.reference.length) {
4719
- if (referenceSchema) addType(referenceSchema.identifier);
4720
- } else if (pField.required && !sField.required && pField.type) {
4721
- addType(pField.type);
4877
+ if (sField === void 0 || isChoiceDeclarationField(sField) || sField.reference === void 0)
4878
+ throw new Error(`Invalid field declaration for ${fieldName}`);
4879
+ const sRefs = sField.reference.map((e) => e.name);
4880
+ const references = field.reference.map((ref) => {
4881
+ const resRef = tsIndex.findLastSpecializationByIdentifier(ref);
4882
+ if (resRef.name !== ref.name) {
4883
+ return `"${resRef.name}" /*${ref.name}*/`;
4884
+ }
4885
+ return `'${ref.name}'`;
4886
+ }).join(" | ");
4887
+ if (sRefs.length === 1 && sRefs[0] === "Resource" && references !== '"Resource"') {
4888
+ const cleanRefs = references.replace(/\/\*[^*]*\*\//g, "").trim();
4889
+ tsType = `Reference<"Resource" /* ${cleanRefs} */ >`;
4890
+ } else {
4891
+ tsType = `Reference<${references}>`;
4722
4892
  }
4893
+ } else if (isPrimitiveIdentifier(field.type)) {
4894
+ tsType = resolvePrimitiveType(field.type.name);
4895
+ } else if (isNestedIdentifier(field.type)) {
4896
+ tsType = tsResourceName(field.type);
4897
+ } else if (field.type === void 0) {
4898
+ throw new Error(`Undefined type for '${fieldName}' field at ${typeSchemaInfo(flatProfile)}`);
4899
+ } else {
4900
+ tsType = field.type.name;
4901
+ }
4902
+ return tsType;
4903
+ };
4904
+ var generateProfileHelpersImport = (w, tsIndex, flatProfile, sliceDefs, factoryInfo) => {
4905
+ const extensions = flatProfile.extensions ?? [];
4906
+ const hasMeta = tsIndex.isWithMetaField(flatProfile);
4907
+ const canonicalUrl = flatProfile.identifier.url;
4908
+ const imports = [];
4909
+ if (!isPrimitiveIdentifier(flatProfile.base)) imports.push("buildResource");
4910
+ if (flatProfile.base.name === "Extension" && !!canonicalUrl && collectSubExtensionSlices(flatProfile).length > 0)
4911
+ imports.push("isRawExtensionInput");
4912
+ if (canonicalUrl && hasMeta) imports.push("ensureProfile");
4913
+ if (sliceDefs.length > 0 || factoryInfo.sliceAutoFields.length > 0)
4914
+ imports.push("applySliceMatch", "matchesValue", "setArraySlice", "getArraySlice", "ensureSliceDefaults");
4915
+ if (extensions.some((ext) => ext.path.split(".").some((s) => s !== "extension"))) imports.push("ensurePath");
4916
+ if (extensions.some((ext) => ext.isComplex && ext.subExtensions)) imports.push("extractComplexExtension");
4917
+ if (sliceDefs.length > 0) imports.push("stripMatchKeys");
4918
+ if (sliceDefs.some((s) => s.constrainedChoice)) imports.push("wrapSliceChoice", "unwrapSliceChoice");
4919
+ if (extensions.some((ext) => ext.url)) imports.push("isExtension", "getExtensionValue", "pushExtension");
4920
+ if (Object.keys(flatProfile.fields ?? {}).length > 0)
4921
+ imports.push(
4922
+ "validateRequired",
4923
+ "validateExcluded",
4924
+ "validateFixedValue",
4925
+ "validateSliceCardinality",
4926
+ "validateEnum",
4927
+ "validateReference",
4928
+ "validateChoiceRequired",
4929
+ "validateMustSupport"
4930
+ );
4931
+ if (imports.length > 0) {
4932
+ w.tsImport("../../profile-helpers", ...imports);
4933
+ w.line();
4723
4934
  }
4724
4935
  };
4725
- var generateProfileImports = (w, tsIndex, flatProfile) => {
4936
+ var generateProfileImports = (w, tsIndex, flatProfile, overrides) => {
4726
4937
  const usedTypes = /* @__PURE__ */ new Map();
4727
4938
  const getModulePath = (typeId) => {
4728
4939
  if (isNestedIdentifier(typeId)) {
@@ -4741,8 +4952,9 @@ var generateProfileImports = (w, tsIndex, flatProfile) => {
4741
4952
  addType(flatProfile.base);
4742
4953
  collectTypesFromSlices(tsIndex, flatProfile, addType);
4743
4954
  const needsExtensionType = collectTypesFromExtensions(tsIndex, flatProfile, addType);
4744
- collectTypesFromFieldOverrides(tsIndex, flatProfile, addType);
4745
- const factoryInfo = collectProfileFactoryInfo(flatProfile);
4955
+ for (const { typeId } of overrides.values()) addType(typeId);
4956
+ collectTypesFromFlatInput(tsIndex, flatProfile, addType);
4957
+ const factoryInfo = collectProfileFactoryInfo(tsIndex, flatProfile);
4746
4958
  for (const param of factoryInfo.params) addType(param.typeId);
4747
4959
  for (const f of factoryInfo.sliceAutoFields) addType(f.typeId);
4748
4960
  for (const accessor of factoryInfo.accessors) addType(accessor.typeId);
@@ -4751,616 +4963,398 @@ var generateProfileImports = (w, tsIndex, flatProfile) => {
4751
4963
  const extensionSchema = tsIndex.resolveByUrl(flatProfile.identifier.package, extensionUrl);
4752
4964
  if (extensionSchema) addType(extensionSchema.identifier);
4753
4965
  }
4754
- const sortedImports = Array.from(usedTypes.values()).sort((a, b) => a.tsName.localeCompare(b.tsName));
4755
- for (const { importPath, tsName } of sortedImports) {
4756
- w.tsImportType(importPath, tsName);
4757
- }
4758
- if (sortedImports.length > 0) w.line();
4759
- };
4760
- var collectExtSliceMethodSuffixes = (extensions, extensionMethodNames) => {
4761
- const suffixes = /* @__PURE__ */ new Set();
4762
- for (const name of extensionMethodNames.values()) {
4763
- suffixes.add(name.replace(/^set/, ""));
4764
- }
4765
- for (const ext of extensions) {
4766
- if (ext.url) suffixes.add(uppercaseFirstLetter(tsCamelCase(ext.name)));
4767
- }
4768
- return suffixes;
4769
- };
4770
- var detectConstrainedChoice = (tsIndex, flatProfile, sliceBaseTypeId, sliceElements) => {
4771
- if (!sliceElements) return void 0;
4772
- const baseSchema = tsIndex.resolveByUrl(flatProfile.identifier.package, sliceBaseTypeId.url);
4773
- if (!baseSchema || !("fields" in baseSchema) || !baseSchema.fields) return void 0;
4774
- for (const [fieldName, field] of Object.entries(baseSchema.fields)) {
4775
- if (!isChoiceDeclarationField(field)) continue;
4776
- const matchingVariants = field.choices.filter((c) => sliceElements.includes(c));
4777
- if (matchingVariants.length !== 1) continue;
4778
- const variantName = matchingVariants[0];
4779
- const variantField = baseSchema.fields[variantName];
4780
- if (!variantField || !isChoiceInstanceField(variantField)) continue;
4781
- return {
4782
- choiceBase: fieldName,
4783
- variant: variantName,
4784
- variantType: tsTypeFromIdentifier(variantField.type),
4785
- variantTypeId: variantField.type,
4786
- allChoiceNames: field.choices
4787
- };
4788
- }
4789
- return void 0;
4790
- };
4791
- var generateProfileClass = (w, tsIndex, flatProfile, schema) => {
4792
- const tsBaseResourceName = tsTypeFromIdentifier(flatProfile.base);
4793
- const tsProfileName = tsResourceName(flatProfile.identifier);
4794
- const profileClassName = tsProfileClassName(flatProfile);
4795
- const polymorphicBaseNames = /* @__PURE__ */ new Set([
4796
- "value",
4797
- "effective",
4798
- "onset",
4799
- "abatement",
4800
- "occurrence",
4801
- "timing",
4802
- "deceased",
4803
- "born",
4804
- "age",
4805
- "medication",
4806
- "performed",
4807
- "serviced",
4808
- "collected",
4809
- "item",
4810
- "subject",
4811
- "bounds",
4812
- "amount",
4813
- "content",
4814
- "product",
4815
- "rate",
4816
- "dose",
4817
- "asNeeded"
4818
- ]);
4819
- const sliceDefs = Object.entries(flatProfile.fields ?? {}).filter(([_fieldName, field]) => isNotChoiceDeclarationField(field) && field.slicing?.slices).flatMap(([fieldName, field]) => {
4820
- if (!isNotChoiceDeclarationField(field) || !field.slicing?.slices || !field.type) return [];
4821
- const baseType = tsTypeFromIdentifier(field.type);
4822
- return Object.entries(field.slicing.slices).filter(([_sliceName, slice]) => {
4823
- const match = slice.match ?? {};
4824
- return Object.keys(match).length > 0;
4825
- }).map(([sliceName, slice]) => {
4826
- const matchFields = Object.keys(slice.match ?? {});
4827
- const required = slice.required ?? [];
4828
- const filteredRequired = required.filter(
4829
- (name) => !matchFields.includes(name) && !polymorphicBaseNames.has(name)
4830
- );
4831
- const constrainedChoice = detectConstrainedChoice(tsIndex, flatProfile, field.type, slice.elements);
4832
- return {
4833
- fieldName,
4834
- baseType,
4835
- sliceName,
4836
- match: slice.match ?? {},
4837
- required,
4838
- excluded: slice.excluded ?? [],
4839
- array: Boolean(field.array),
4840
- // Input is optional when there are no required fields after filtering
4841
- inputOptional: filteredRequired.length === 0,
4842
- constrainedChoice
4843
- };
4844
- });
4845
- });
4846
- const extensions = flatProfile.extensions ?? [];
4847
- const complexExtensions = extensions.filter((ext) => ext.isComplex && ext.subExtensions);
4848
- for (const ext of complexExtensions) {
4849
- const typeName = tsExtensionInputTypeName(tsProfileName, ext.name);
4850
- w.curlyBlock(["export", "type", typeName, "="], () => {
4851
- for (const sub of ext.subExtensions ?? []) {
4852
- const tsType = sub.valueType ? tsTypeFromIdentifier(sub.valueType) : "unknown";
4853
- const isArray = sub.max === "*";
4854
- const isRequired2 = sub.min !== void 0 && sub.min > 0;
4855
- w.lineSM(`${sub.name}${isRequired2 ? "" : "?"}: ${tsType}${isArray ? "[]" : ""}`);
4856
- }
4857
- });
4858
- w.line();
4859
- }
4860
- if (sliceDefs.length > 0) {
4861
- for (const sliceDef of sliceDefs) {
4862
- const typeName = tsSliceInputTypeName(tsProfileName, sliceDef.fieldName, sliceDef.sliceName);
4863
- const matchFields = Object.keys(sliceDef.match);
4864
- const allExcluded = [.../* @__PURE__ */ new Set([...sliceDef.excluded, ...matchFields])];
4865
- if (sliceDef.constrainedChoice) {
4866
- const cc = sliceDef.constrainedChoice;
4867
- allExcluded.push(cc.choiceBase);
4868
- for (const name of cc.allChoiceNames) {
4869
- if (!allExcluded.includes(name)) allExcluded.push(name);
4870
- }
4871
- }
4872
- const excludedNames = allExcluded.map((name) => JSON.stringify(name));
4873
- const filteredRequired = sliceDef.required.filter(
4874
- (name) => !matchFields.includes(name) && !polymorphicBaseNames.has(name)
4875
- );
4876
- const requiredNames = filteredRequired.map((name) => JSON.stringify(name));
4877
- let typeExpr = sliceDef.baseType;
4878
- if (excludedNames.length > 0) {
4879
- typeExpr = `Omit<${typeExpr}, ${excludedNames.join(" | ")}>`;
4880
- }
4881
- if (requiredNames.length > 0) {
4882
- typeExpr = `${typeExpr} & Required<Pick<${sliceDef.baseType}, ${requiredNames.join(" | ")}>>`;
4883
- }
4884
- if (sliceDef.constrainedChoice) {
4885
- typeExpr = `${typeExpr} & ${sliceDef.constrainedChoice.variantType}`;
4886
- }
4887
- w.lineSM(`export type ${typeName} = ${typeExpr}`);
4966
+ const grouped = /* @__PURE__ */ new Map();
4967
+ for (const { importPath, tsName } of usedTypes.values()) {
4968
+ let names = grouped.get(importPath);
4969
+ if (!names) {
4970
+ names = [];
4971
+ grouped.set(importPath, names);
4972
+ }
4973
+ names.push(tsName);
4974
+ }
4975
+ const sortedModules = [...grouped.entries()].sort(([a], [b]) => a.localeCompare(b));
4976
+ for (const [importPath, names] of sortedModules) {
4977
+ w.tsImport(importPath, ...names.sort(), { typeOnly: true });
4978
+ }
4979
+ if (sortedModules.length > 0) w.line();
4980
+ const extProfileImports = /* @__PURE__ */ new Map();
4981
+ for (const ext of flatProfile.extensions ?? []) {
4982
+ if (!ext.url) continue;
4983
+ const info = resolveExtensionProfile(tsIndex, flatProfile.identifier.package, ext.url);
4984
+ if (!info) continue;
4985
+ if (!extProfileImports.has(info.className)) {
4986
+ const hasFlatInput = collectSubExtensionSlices(info.flatProfile).length > 0;
4987
+ extProfileImports.set(info.className, { modulePath: info.modulePath, hasFlatInput });
4888
4988
  }
4889
- w.line();
4890
4989
  }
4891
- const hasOverrideInterface = detectFieldOverrides(w, tsIndex, flatProfile).size > 0;
4892
- const factoryInfo = collectProfileFactoryInfo(flatProfile);
4893
- const needsSliceHelpers = sliceDefs.length > 0 || factoryInfo.sliceAutoFields.length > 0;
4894
- const extensionsWithNestedPath = extensions.filter((ext) => {
4895
- const targetPath = ext.path.split(".").filter((segment) => segment !== "extension");
4896
- return targetPath.length > 0;
4897
- });
4898
- const needsGetOrCreateObjectAtPath = extensionsWithNestedPath.length > 0;
4899
- const needsExtensionExtraction = complexExtensions.length > 0;
4900
- const needsSliceExtraction = sliceDefs.length > 0;
4901
- const needsSliceChoiceHelpers = sliceDefs.some((s) => s.constrainedChoice);
4902
- const needsValidation = Object.keys(flatProfile.fields ?? {}).length > 0;
4903
- if (needsSliceHelpers || needsGetOrCreateObjectAtPath || needsExtensionExtraction || needsSliceExtraction || needsSliceChoiceHelpers || needsValidation) {
4904
- generateProfileHelpersImport(w, {
4905
- needsGetOrCreateObjectAtPath,
4906
- needsSliceHelpers,
4907
- needsExtensionExtraction,
4908
- needsSliceExtraction,
4909
- needsSliceChoiceHelpers,
4910
- needsValidation
4911
- });
4912
- w.line();
4990
+ for (const [className, { modulePath, hasFlatInput }] of [...extProfileImports.entries()].sort(
4991
+ ([a], [b]) => a.localeCompare(b)
4992
+ )) {
4993
+ const imports = [className, ...hasFlatInput ? [`type ${className}Flat`] : []];
4994
+ w.tsImport(modulePath, ...imports);
4995
+ }
4996
+ if (extProfileImports.size > 0) w.line();
4997
+ };
4998
+ var generateStaticSliceFields = (w, sliceDefs) => {
4999
+ for (const sliceDef of sliceDefs) {
5000
+ const staticName = `${tsSliceStaticName(sliceDef.sliceName)}SliceMatch`;
5001
+ w.lineSM(`private static readonly ${staticName}: Record<string, unknown> = ${JSON.stringify(sliceDef.match)}`);
4913
5002
  }
5003
+ if (sliceDefs.length > 0) w.line();
5004
+ };
5005
+ var generateFactoryMethods = (w, tsIndex, flatProfile, factoryInfo) => {
5006
+ const profileClassName = tsProfileClassName(flatProfile);
5007
+ const tsBaseResourceName = tsTypeFromIdentifier(flatProfile.base);
5008
+ const hasMeta = tsIndex.isWithMetaField(flatProfile);
4914
5009
  const hasParams = factoryInfo.params.length > 0 || factoryInfo.sliceAutoFields.length > 0;
4915
- const createArgsTypeName = `${profileClassName}Params`;
5010
+ const createArgsTypeName = `${profileClassName}Raw`;
4916
5011
  const paramSignature = hasParams ? `args: ${createArgsTypeName}` : "";
4917
5012
  const allFields = [
4918
5013
  ...factoryInfo.autoFields.map((f) => ({ name: f.name, value: f.value })),
4919
5014
  ...factoryInfo.sliceAutoFields.map((f) => ({ name: f.name, value: `${f.name}WithDefaults` })),
4920
5015
  ...factoryInfo.params.map((p) => ({ name: p.name, value: `args.${p.name}` }))
4921
5016
  ];
4922
- if (hasParams) {
4923
- w.curlyBlock(["export", "type", createArgsTypeName, "="], () => {
4924
- for (const p of factoryInfo.params) {
4925
- w.lineSM(`${p.name}: ${p.tsType}`);
4926
- }
4927
- for (const f of factoryInfo.sliceAutoFields) {
4928
- w.lineSM(`${f.name}?: ${f.tsType}`);
4929
- }
4930
- });
4931
- w.line();
4932
- }
4933
- const canonicalUrl = schema?.identifier.url;
4934
- if (schema) {
4935
- w.comment("CanonicalURL:", schema.identifier.url, `(pkg: ${packageMetaToFhir(packageMeta(schema))})`);
4936
- }
4937
- w.curlyBlock(["export", "class", profileClassName], () => {
4938
- if (canonicalUrl) {
4939
- w.line(`static readonly canonicalUrl = ${JSON.stringify(canonicalUrl)}`);
4940
- w.line();
4941
- }
4942
- w.line(`private resource: ${tsBaseResourceName}`);
4943
- w.line();
4944
- w.curlyBlock(["constructor", `(resource: ${tsBaseResourceName})`], () => {
4945
- w.line("this.resource = resource");
4946
- if (canonicalUrl && isResourceIdentifier(flatProfile.base)) {
4947
- w.line(`const r = resource as unknown as Record<string, unknown>`);
4948
- w.line(`const meta = (r.meta ??= {}) as Record<string, unknown>`);
4949
- w.line(`const profiles = (meta.profile ??= []) as string[]`);
5017
+ w.curlyBlock(["constructor", `(resource: ${tsBaseResourceName})`], () => {
5018
+ w.lineSM("this.resource = resource");
5019
+ });
5020
+ w.line();
5021
+ w.curlyBlock(["static", "from", `(resource: ${tsBaseResourceName})`, `: ${profileClassName}`], () => {
5022
+ if (hasMeta) {
5023
+ w.curlyBlock(["if", `(!resource.meta?.profile?.includes(${profileClassName}.canonicalUrl))`], () => {
4950
5024
  w.line(
4951
- `if (!profiles.includes(${JSON.stringify(canonicalUrl)})) profiles.push(${JSON.stringify(canonicalUrl)})`
5025
+ `throw new Error(\`${profileClassName}: meta.profile must include \${${profileClassName}.canonicalUrl}\`)`
5026
+ );
5027
+ });
5028
+ }
5029
+ w.lineSM(`const profile = new ${profileClassName}(resource)`);
5030
+ w.lineSM("const { errors } = profile.validate()");
5031
+ w.line(`if (errors.length > 0) throw new Error(errors.join("; "))`);
5032
+ w.lineSM("return profile");
5033
+ });
5034
+ w.line();
5035
+ w.curlyBlock(["static", "apply", `(resource: ${tsBaseResourceName})`, `: ${profileClassName}`], () => {
5036
+ if (hasMeta) {
5037
+ w.lineSM(`ensureProfile(resource, ${profileClassName}.canonicalUrl)`);
5038
+ }
5039
+ w.lineSM(`return new ${profileClassName}(resource)`);
5040
+ });
5041
+ w.line();
5042
+ const subSlicesForInput = flatProfile.base.name === "Extension" ? collectSubExtensionSlices(flatProfile) : [];
5043
+ const hasInputHelper = subSlicesForInput.length > 0;
5044
+ if (hasInputHelper) {
5045
+ const rawInputTypeName = `${profileClassName}Raw`;
5046
+ const inputTypeName = `${profileClassName}Flat`;
5047
+ w.curlyBlock(
5048
+ ["private static", "resolveInput", `(args: ${rawInputTypeName} | ${inputTypeName})`, ": Extension[]"],
5049
+ () => {
5050
+ w.ifElseChain(
5051
+ [
5052
+ {
5053
+ cond: `isRawExtensionInput<${rawInputTypeName}>(args)`,
5054
+ body: () => w.lineSM("return args.extension ?? []")
5055
+ }
5056
+ ],
5057
+ () => {
5058
+ w.lineSM("const result: Extension[] = []");
5059
+ for (const sub of subSlicesForInput) {
5060
+ if (sub.isArray) {
5061
+ w.curlyBlock(["if", `(args.${sub.name})`], () => {
5062
+ w.curlyBlock(["for", `(const item of args.${sub.name})`], () => {
5063
+ w.lineSM(
5064
+ `result.push({ url: "${sub.url}", ${sub.valueField}: item } as Extension)`
5065
+ );
5066
+ });
5067
+ });
5068
+ } else {
5069
+ w.curlyBlock(["if", `(args.${sub.name} !== undefined)`], () => {
5070
+ w.lineSM(
5071
+ `result.push({ url: "${sub.url}", ${sub.valueField}: args.${sub.name} } as Extension)`
5072
+ );
5073
+ });
5074
+ }
5075
+ }
5076
+ w.lineSM("return result");
5077
+ }
4952
5078
  );
4953
5079
  }
4954
- });
4955
- w.line();
4956
- w.curlyBlock(["static", "from", `(resource: ${tsBaseResourceName})`, `: ${profileClassName}`], () => {
4957
- w.line(`return new ${profileClassName}(resource)`);
4958
- });
5080
+ );
4959
5081
  w.line();
4960
- w.curlyBlock(["static", "createResource", `(${paramSignature})`, `: ${tsBaseResourceName}`], () => {
4961
- for (const f of factoryInfo.sliceAutoFields) {
4962
- const matchExprs = f.matches.map((m) => `${m} as Record<string, unknown>`);
4963
- w.line(`const ${f.name}Defaults = ${f.defaultValue} as unknown[]`);
4964
- w.line(`const ${f.name}WithDefaults = [...(args.${f.name} ?? [])] as unknown[]`);
4965
- for (let i = 0; i < matchExprs.length; i++) {
4966
- w.line(
4967
- `if (!${f.name}WithDefaults.some(item => matchesSlice(item, ${matchExprs[i]}))) ${f.name}WithDefaults.push(${f.name}Defaults[${i}]!)`
4968
- );
4969
- }
5082
+ const createResourceSig = hasParams ? `args: ${rawInputTypeName} | ${inputTypeName}` : `args?: ${rawInputTypeName} | ${inputTypeName}`;
5083
+ w.curlyBlock(["static", "createResource", `(${createResourceSig})`, `: ${tsBaseResourceName}`], () => {
5084
+ w.lineSM(`const resolvedExtensions = ${profileClassName}.resolveInput(args ?? {})`);
5085
+ const extSliceField = factoryInfo.sliceAutoFields.find((f) => f.name === "extension");
5086
+ if (extSliceField) {
5087
+ const matchRefs = extSliceField.sliceNames.map(
5088
+ (s) => `${profileClassName}.${tsSliceStaticName(s)}SliceMatch`
5089
+ );
5090
+ w.line("const extensionWithDefaults = ensureSliceDefaults(");
5091
+ w.indentBlock(() => {
5092
+ w.line("resolvedExtensions,");
5093
+ for (const ref of matchRefs) {
5094
+ w.line(`${ref},`);
5095
+ }
5096
+ });
5097
+ w.lineSM(")");
4970
5098
  }
4971
- w.curlyBlock([`const resource: ${tsBaseResourceName} =`], () => {
5099
+ w.line();
5100
+ const extensionVar = extSliceField ? "extensionWithDefaults" : "resolvedExtensions";
5101
+ w.curlyBlock([`const resource = buildResource<${tsBaseResourceName}>(`], () => {
4972
5102
  for (const f of allFields) {
5103
+ if (f.name === "extension") continue;
4973
5104
  w.line(`${f.name}: ${f.value},`);
4974
5105
  }
4975
- if (canonicalUrl && isResourceIdentifier(flatProfile.base)) {
4976
- w.line(`meta: { profile: [${JSON.stringify(canonicalUrl)}] },`);
5106
+ w.line(`extension: ${extensionVar},`);
5107
+ if (hasMeta) {
5108
+ w.line(`meta: { profile: [${profileClassName}.canonicalUrl] },`);
4977
5109
  }
4978
- }, [` as unknown as ${tsBaseResourceName}`]);
4979
- w.line("return resource");
4980
- });
4981
- w.line();
4982
- w.curlyBlock(["static", "create", `(${paramSignature})`, `: ${profileClassName}`], () => {
4983
- w.line(`return ${profileClassName}.from(${profileClassName}.createResource(${hasParams ? "args" : ""}))`);
5110
+ }, [")"]);
5111
+ w.lineSM("return resource");
4984
5112
  });
4985
5113
  w.line();
4986
- w.curlyBlock(["toResource", "()", `: ${tsBaseResourceName}`], () => {
4987
- w.line("return this.resource");
5114
+ const createSig = hasParams ? `args: ${rawInputTypeName} | ${inputTypeName}` : `args?: ${rawInputTypeName} | ${inputTypeName}`;
5115
+ w.curlyBlock(["static", "create", `(${createSig})`, `: ${profileClassName}`], () => {
5116
+ w.lineSM(`return ${profileClassName}.apply(${profileClassName}.createResource(args))`);
4988
5117
  });
4989
- w.line();
4990
- for (const p of factoryInfo.params) {
4991
- const methodSuffix = uppercaseFirstLetter(p.name);
4992
- w.curlyBlock([`get${methodSuffix}`, "()", `: ${p.tsType} | undefined`], () => {
4993
- w.line(`return this.resource.${p.name} as ${p.tsType} | undefined`);
4994
- });
4995
- w.line();
4996
- w.curlyBlock([`set${methodSuffix}`, `(value: ${p.tsType})`, ": this"], () => {
4997
- w.line(`Object.assign(this.resource, { ${p.name}: value })`);
4998
- w.line("return this");
4999
- });
5000
- w.line();
5001
- }
5002
- const extensionMethods = extensions.filter((ext) => ext.url).map((ext) => ({
5003
- ext,
5004
- baseName: tsExtensionMethodName(ext.name),
5005
- fallbackName: tsQualifiedExtensionMethodName(ext.name, ext.path)
5006
- }));
5007
- const sliceMethodBases = sliceDefs.map((slice) => tsSliceMethodName(slice.sliceName));
5008
- const methodCounts = /* @__PURE__ */ new Map();
5009
- for (const name of [...sliceMethodBases, ...extensionMethods.map((m) => m.baseName)]) {
5010
- methodCounts.set(name, (methodCounts.get(name) ?? 0) + 1);
5011
- }
5012
- const extensionMethodNames = new Map(
5013
- extensionMethods.map((entry) => [
5014
- entry.ext,
5015
- (methodCounts.get(entry.baseName) ?? 0) > 1 ? entry.fallbackName : entry.baseName
5016
- ])
5017
- );
5018
- const sliceMethodNames = new Map(
5019
- sliceDefs.map((slice) => {
5020
- const baseName = tsSliceMethodName(slice.sliceName);
5021
- const needsFallback = (methodCounts.get(baseName) ?? 0) > 1;
5022
- const fallback = tsQualifiedSliceMethodName(slice.fieldName, slice.sliceName);
5023
- return [slice, needsFallback ? fallback : baseName];
5024
- })
5025
- );
5026
- const extSliceMethodSuffixes = collectExtSliceMethodSuffixes(extensions, extensionMethodNames);
5027
- for (const a of factoryInfo.accessors) {
5028
- const methodSuffix = uppercaseFirstLetter(tsCamelCase(a.name));
5029
- if (extSliceMethodSuffixes.has(methodSuffix)) continue;
5030
- const fieldAccess = tsFieldName(a.name);
5031
- w.curlyBlock([`get${methodSuffix}`, "()", `: ${a.tsType} | undefined`], () => {
5032
- w.line(`return ${tsGet("this.resource", fieldAccess)} as ${a.tsType} | undefined`);
5033
- });
5034
- w.line();
5035
- w.curlyBlock([`set${methodSuffix}`, `(value: ${a.tsType})`, ": this"], () => {
5036
- w.line(`Object.assign(this.resource, { ${fieldAccess}: value })`);
5037
- w.line("return this");
5038
- });
5039
- w.line();
5040
- }
5041
- if (hasOverrideInterface) {
5042
- w.curlyBlock(["toProfile", "()", `: ${tsProfileName}`], () => {
5043
- w.line(`return this.resource as ${tsProfileName}`);
5044
- });
5045
- w.line();
5046
- }
5047
- generateExtensionSetterMethods(w, extensions, extensionMethodNames, tsProfileName);
5048
- for (const sliceDef of sliceDefs) {
5049
- const methodName = sliceMethodNames.get(sliceDef) ?? tsQualifiedSliceMethodName(sliceDef.fieldName, sliceDef.sliceName);
5050
- const typeName = tsSliceInputTypeName(tsProfileName, sliceDef.fieldName, sliceDef.sliceName);
5051
- const matchLiteral = JSON.stringify(sliceDef.match);
5052
- const tsField = tsFieldName(sliceDef.fieldName);
5053
- const fieldAccess = tsGet("this.resource", tsField);
5054
- const paramSignature2 = sliceDef.inputOptional ? `(input?: ${typeName}): this` : `(input: ${typeName}): this`;
5055
- w.curlyBlock(["public", methodName, paramSignature2], () => {
5056
- w.line(`const match = ${matchLiteral} as Record<string, unknown>`);
5057
- const inputExpr = sliceDef.inputOptional ? "(input ?? {}) as Record<string, unknown>" : "input as Record<string, unknown>";
5058
- if (sliceDef.constrainedChoice) {
5059
- const cc = sliceDef.constrainedChoice;
5060
- w.line(
5061
- `const value = applySliceMatch(wrapSliceChoice(${inputExpr}, ${JSON.stringify(cc.variant)}), match) as unknown as ${sliceDef.baseType}`
5062
- );
5063
- } else {
5064
- w.line(`const value = applySliceMatch(${inputExpr}, match) as unknown as ${sliceDef.baseType}`);
5065
- }
5066
- if (sliceDef.array) {
5067
- w.line(`const list = (${fieldAccess} ??= [])`);
5068
- w.line("const index = list.findIndex((item) => matchesSlice(item, match))");
5069
- w.line("if (index === -1) {");
5070
- w.indentBlock(() => {
5071
- w.line("list.push(value)");
5072
- });
5073
- w.line("} else {");
5074
- w.indentBlock(() => {
5075
- w.line("list[index] = value");
5076
- });
5077
- w.line("}");
5078
- } else {
5079
- w.line(`${fieldAccess} = value`);
5080
- }
5081
- w.line("return this");
5082
- });
5083
- w.line();
5084
- }
5085
- const generatedGetMethods = /* @__PURE__ */ new Set();
5086
- for (const ext of extensions) {
5087
- if (!ext.url) continue;
5088
- const baseName = uppercaseFirstLetter(tsCamelCase(ext.name));
5089
- const getMethodName = `get${baseName}`;
5090
- const getExtensionMethodName = `get${baseName}Extension`;
5091
- if (generatedGetMethods.has(getMethodName)) continue;
5092
- generatedGetMethods.add(getMethodName);
5093
- const valueTypes = ext.valueTypes ?? [];
5094
- const targetPath = ext.path.split(".").filter((segment) => segment !== "extension");
5095
- const generateExtLookup = () => {
5096
- if (targetPath.length === 0) {
5097
- w.line(`const ext = this.resource.extension?.find(e => e.url === "${ext.url}")`);
5098
- } else {
5099
- w.line(
5100
- `const target = getOrCreateObjectAtPath(this.resource as unknown as Record<string, unknown>, ${JSON.stringify(targetPath)})`
5101
- );
5102
- w.line(
5103
- `const ext = (target.extension as Extension[] | undefined)?.find(e => e.url === "${ext.url}")`
5104
- );
5105
- }
5106
- };
5107
- if (ext.isComplex && ext.subExtensions) {
5108
- const inputTypeName = tsExtensionInputTypeName(tsProfileName, ext.name);
5109
- w.curlyBlock(["public", getMethodName, `(): ${inputTypeName} | undefined`], () => {
5110
- generateExtLookup();
5111
- w.line("if (!ext) return undefined");
5112
- const configItems = (ext.subExtensions ?? []).map((sub) => {
5113
- const valueField = sub.valueType ? `value${uppercaseFirstLetter(sub.valueType.name)}` : "value";
5114
- const isArray = sub.max === "*";
5115
- return `{ name: "${sub.url}", valueField: "${valueField}", isArray: ${isArray} }`;
5116
- });
5117
- w.line(`const config = [${configItems.join(", ")}]`);
5118
- w.line(
5119
- `return extractComplexExtension(ext as unknown as { extension?: Array<{ url?: string; [key: string]: unknown }> }, config) as ${inputTypeName}`
5120
- );
5121
- });
5122
- w.line();
5123
- w.curlyBlock(["public", getExtensionMethodName, "(): Extension | undefined"], () => {
5124
- generateExtLookup();
5125
- w.line("return ext");
5126
- });
5127
- } else if (valueTypes.length === 1 && valueTypes[0]) {
5128
- const firstValueType = valueTypes[0];
5129
- const valueType = tsTypeFromIdentifier(firstValueType);
5130
- const valueField = `value${uppercaseFirstLetter(firstValueType.name)}`;
5131
- w.curlyBlock(["public", getMethodName, `(): ${valueType} | undefined`], () => {
5132
- generateExtLookup();
5133
- w.line(
5134
- `return (ext as Record<string, unknown> | undefined)?.${valueField} as ${valueType} | undefined`
5135
- );
5118
+ } else {
5119
+ w.curlyBlock(["static", "createResource", `(${paramSignature})`, `: ${tsBaseResourceName}`], () => {
5120
+ for (const f of factoryInfo.sliceAutoFields) {
5121
+ const matchRefs = f.sliceNames.map((s) => `${profileClassName}.${tsSliceStaticName(s)}SliceMatch`);
5122
+ w.line(`const ${f.name}WithDefaults = ensureSliceDefaults(`);
5123
+ w.indentBlock(() => {
5124
+ w.line(`[...(args.${f.name} ?? [])],`);
5125
+ for (const ref of matchRefs) {
5126
+ w.line(`${ref},`);
5127
+ }
5136
5128
  });
5129
+ w.lineSM(")");
5130
+ }
5131
+ if (factoryInfo.sliceAutoFields.length > 0) {
5137
5132
  w.line();
5138
- w.curlyBlock(["public", getExtensionMethodName, "(): Extension | undefined"], () => {
5139
- generateExtLookup();
5140
- w.line("return ext");
5141
- });
5133
+ }
5134
+ if (isPrimitiveIdentifier(flatProfile.base)) {
5135
+ w.lineSM(`const resource = undefined as unknown as ${tsBaseResourceName}`);
5142
5136
  } else {
5143
- w.curlyBlock(["public", getMethodName, "(): Extension | undefined"], () => {
5144
- if (targetPath.length === 0) {
5145
- w.line(`return this.resource.extension?.find(e => e.url === "${ext.url}")`);
5146
- } else {
5147
- w.line(
5148
- `const target = getOrCreateObjectAtPath(this.resource as unknown as Record<string, unknown>, ${JSON.stringify(targetPath)})`
5149
- );
5150
- w.line(
5151
- `return (target.extension as Extension[] | undefined)?.find(e => e.url === "${ext.url}")`
5152
- );
5137
+ w.curlyBlock([`const resource = buildResource<${tsBaseResourceName}>(`], () => {
5138
+ for (const f of allFields) {
5139
+ w.line(`${f.name}: ${f.value},`);
5153
5140
  }
5154
- });
5141
+ if (hasMeta) {
5142
+ w.line(`meta: { profile: [${profileClassName}.canonicalUrl] },`);
5143
+ }
5144
+ }, [")"]);
5155
5145
  }
5156
- w.line();
5157
- }
5158
- for (const sliceDef of sliceDefs) {
5159
- const baseName = uppercaseFirstLetter(normalizeTsName(sliceDef.sliceName));
5160
- const getMethodName = `get${baseName}`;
5161
- const getRawMethodName = `get${baseName}Raw`;
5162
- if (generatedGetMethods.has(getMethodName)) continue;
5163
- generatedGetMethods.add(getMethodName);
5164
- const typeName = tsSliceInputTypeName(tsProfileName, sliceDef.fieldName, sliceDef.sliceName);
5165
- const matchLiteral = JSON.stringify(sliceDef.match);
5166
- const matchKeys = JSON.stringify(Object.keys(sliceDef.match));
5167
- const tsField = tsFieldName(sliceDef.fieldName);
5168
- const fieldAccess = tsGet("this.resource", tsField);
5169
- const baseType = sliceDef.baseType;
5170
- const generateSliceLookup = () => {
5171
- w.line(`const match = ${matchLiteral} as Record<string, unknown>`);
5172
- if (sliceDef.array) {
5173
- w.line(`const list = ${fieldAccess}`);
5174
- w.line("if (!list) return undefined");
5175
- w.line("const item = list.find((item) => matchesSlice(item, match))");
5176
- } else {
5177
- w.line(`const item = ${fieldAccess}`);
5178
- w.line("if (!item || !matchesSlice(item, match)) return undefined");
5179
- }
5180
- };
5181
- w.curlyBlock(["public", getMethodName, `(): ${typeName} | undefined`], () => {
5182
- generateSliceLookup();
5183
- if (sliceDef.array) {
5184
- w.line("if (!item) return undefined");
5185
- }
5186
- if (sliceDef.constrainedChoice) {
5187
- const cc = sliceDef.constrainedChoice;
5188
- w.line(
5189
- `return flattenSliceChoice(item as unknown as Record<string, unknown>, ${matchKeys}, ${JSON.stringify(cc.variant)}) as ${typeName}`
5190
- );
5191
- } else {
5192
- w.line(
5193
- `return extractSliceSimplified(item as unknown as Record<string, unknown>, ${matchKeys}) as ${typeName}`
5194
- );
5195
- }
5196
- });
5197
- w.line();
5198
- w.curlyBlock(["public", getRawMethodName, `(): ${baseType} | undefined`], () => {
5199
- generateSliceLookup();
5200
- if (sliceDef.array) {
5201
- w.line("return item");
5202
- } else {
5203
- w.line("return item");
5204
- }
5205
- });
5206
- w.line();
5207
- }
5208
- generateValidateMethod(w, flatProfile);
5146
+ w.lineSM("return resource");
5147
+ });
5148
+ w.line();
5149
+ w.curlyBlock(["static", "create", `(${paramSignature})`, `: ${profileClassName}`], () => {
5150
+ w.lineSM(
5151
+ `return ${profileClassName}.apply(${profileClassName}.createResource(${hasParams ? "args" : ""}))`
5152
+ );
5153
+ });
5154
+ }
5155
+ w.line();
5156
+ w.curlyBlock(["toResource", "()", `: ${tsBaseResourceName}`], () => {
5157
+ w.lineSM("return this.resource");
5209
5158
  });
5210
5159
  w.line();
5211
5160
  };
5212
- var emitRegularFieldValidation = (w, name, field, profileName) => {
5213
- if (field.excluded) {
5214
- w.line(`{ const e = validateExcluded(r, ${JSON.stringify(name)}, "${profileName}"); if (e) errors.push(e) }`);
5215
- return;
5216
- }
5217
- if (field.required) {
5218
- w.line(`{ const e = validateRequired(r, ${JSON.stringify(name)}, "${profileName}"); if (e) errors.push(e) }`);
5219
- }
5220
- if (field.valueConstraint) {
5221
- const expected = JSON.stringify(field.valueConstraint.value);
5222
- w.line(
5223
- `{ const e = validateFixedValue(r, ${JSON.stringify(name)}, ${expected}, "${profileName}"); if (e) errors.push(e) }`
5224
- );
5161
+ var generateFieldAccessors = (w, factoryInfo, extSliceMethodBaseNames) => {
5162
+ w.line("// Field accessors");
5163
+ for (const p of factoryInfo.params) {
5164
+ const methodBaseName = uppercaseFirstLetter(p.name);
5165
+ w.curlyBlock([`get${methodBaseName}`, "()", `: ${p.tsType} | undefined`], () => {
5166
+ w.lineSM(`return this.resource.${p.name} as ${p.tsType} | undefined`);
5167
+ });
5168
+ w.line();
5169
+ w.curlyBlock([`set${methodBaseName}`, `(value: ${p.tsType})`, ": this"], () => {
5170
+ w.lineSM(`Object.assign(this.resource, { ${p.name}: value })`);
5171
+ w.lineSM("return this");
5172
+ });
5173
+ w.line();
5225
5174
  }
5226
- if (field.enum && !field.enum.isOpen) {
5227
- const values = JSON.stringify(field.enum.values);
5228
- w.line(
5229
- `{ const e = validateEnum(r[${JSON.stringify(name)}], ${values}, ${JSON.stringify(name)}, "${profileName}"); if (e) errors.push(e) }`
5230
- );
5175
+ for (const a of factoryInfo.accessors) {
5176
+ const methodBaseName = uppercaseFirstLetter(tsCamelCase(a.name));
5177
+ if (extSliceMethodBaseNames.has(methodBaseName)) continue;
5178
+ const fieldAccess = tsFieldName(a.name);
5179
+ w.curlyBlock([`get${methodBaseName}`, "()", `: ${a.tsType} | undefined`], () => {
5180
+ w.lineSM(`return ${tsGet("this.resource", fieldAccess)} as ${a.tsType} | undefined`);
5181
+ });
5182
+ w.line();
5183
+ w.curlyBlock([`set${methodBaseName}`, `(value: ${a.tsType})`, ": this"], () => {
5184
+ w.lineSM(`Object.assign(this.resource, { ${fieldAccess}: value })`);
5185
+ w.lineSM("return this");
5186
+ });
5187
+ w.line();
5231
5188
  }
5232
- if (field.reference && field.reference.length > 0) {
5233
- const refNames = JSON.stringify(field.reference.map((ref) => ref.name));
5234
- w.line(
5235
- `{ const e = validateReference(r[${JSON.stringify(name)}], ${refNames}, ${JSON.stringify(name)}, "${profileName}"); if (e) errors.push(e) }`
5236
- );
5189
+ };
5190
+ var generateInlineExtensionInputTypes = (w, tsIndex, flatProfile) => {
5191
+ const tsProfileName = tsResourceName(flatProfile.identifier);
5192
+ const complexExtensions = (flatProfile.extensions ?? []).filter((ext) => ext.isComplex && ext.subExtensions);
5193
+ for (const ext of complexExtensions) {
5194
+ if (!ext.url) continue;
5195
+ const extProfileInfo = resolveExtensionProfile(tsIndex, flatProfile.identifier.package, ext.url);
5196
+ const hasFlatInput = extProfileInfo ? collectSubExtensionSlices(extProfileInfo.flatProfile).length > 0 : false;
5197
+ if (hasFlatInput) continue;
5198
+ const typeName = tsExtensionFlatTypeName(tsProfileName, ext.name);
5199
+ w.curlyBlock(["export", "type", typeName, "="], () => {
5200
+ for (const sub of ext.subExtensions ?? []) {
5201
+ const tsType = sub.valueType ? tsTypeFromIdentifier(sub.valueType) : "unknown";
5202
+ const isArray = sub.max === "*";
5203
+ const isRequired2 = sub.min !== void 0 && sub.min > 0;
5204
+ w.lineSM(`${sub.name}${isRequired2 ? "" : "?"}: ${tsType}${isArray ? "[]" : ""}`);
5205
+ }
5206
+ });
5207
+ w.line();
5237
5208
  }
5238
- if (field.slicing?.slices) {
5239
- for (const [sliceName, slice] of Object.entries(field.slicing.slices)) {
5240
- if (slice.min === void 0 && slice.max === void 0) continue;
5241
- const match = slice.match ?? {};
5242
- if (Object.keys(match).length === 0) continue;
5243
- const min = slice.min ?? 0;
5244
- const max = slice.max ?? 0;
5245
- w.line(
5246
- `errors.push(...validateSliceCardinality(r[${JSON.stringify(name)}] as unknown[] | undefined, ${JSON.stringify(match)}, ${JSON.stringify(sliceName)}, ${min}, ${max}, "${profileName}.${name}"))`
5247
- );
5209
+ };
5210
+ var generateSliceInputTypes = (w, flatProfile, sliceDefs) => {
5211
+ if (sliceDefs.length === 0) return;
5212
+ const tsProfileName = tsResourceName(flatProfile.identifier);
5213
+ for (const sliceDef of sliceDefs) {
5214
+ const typeName = tsSliceFlatTypeName(tsProfileName, sliceDef.fieldName, sliceDef.sliceName);
5215
+ const matchFields = Object.keys(sliceDef.match);
5216
+ const allExcluded = [.../* @__PURE__ */ new Set([...sliceDef.excluded, ...matchFields])];
5217
+ if (sliceDef.constrainedChoice) {
5218
+ const cc = sliceDef.constrainedChoice;
5219
+ allExcluded.push(cc.choiceBase);
5220
+ for (const name of cc.allChoiceNames) {
5221
+ if (!allExcluded.includes(name)) allExcluded.push(name);
5222
+ }
5223
+ }
5224
+ const excludedNames = allExcluded.map((name) => JSON.stringify(name));
5225
+ const requiredNames = sliceDef.required.map((name) => JSON.stringify(name));
5226
+ let typeExpr = sliceDef.baseType;
5227
+ if (excludedNames.length > 0) {
5228
+ typeExpr = `Omit<${typeExpr}, ${excludedNames.join(" | ")}>`;
5248
5229
  }
5230
+ if (requiredNames.length > 0) {
5231
+ typeExpr = `${typeExpr} & Required<Pick<${sliceDef.baseType}, ${requiredNames.join(" | ")}>>`;
5232
+ }
5233
+ if (sliceDef.constrainedChoice) {
5234
+ typeExpr = `${typeExpr} & ${tsTypeFromIdentifier(sliceDef.constrainedChoice.variantType)}`;
5235
+ }
5236
+ w.lineSM(`export type ${typeName} = ${typeExpr}`);
5249
5237
  }
5238
+ w.line();
5250
5239
  };
5251
- var generateValidateMethod = (w, flatProfile) => {
5252
- const fields = flatProfile.fields ?? {};
5253
- const profileName = flatProfile.identifier.name;
5254
- w.curlyBlock(["validate", "()", ": string[]"], () => {
5255
- w.line("const errors: string[] = []");
5256
- w.line("const r = this.resource as unknown as Record<string, unknown>");
5257
- for (const [name, field] of Object.entries(fields)) {
5258
- if (isChoiceInstanceField(field)) continue;
5259
- if (isChoiceDeclarationField(field)) {
5260
- if (field.required) {
5261
- const choiceNames = field.choices;
5262
- const checks = choiceNames.map((c) => `r[${JSON.stringify(c)}] !== undefined`).join(" || ");
5263
- w.curlyBlock(["if", `(!(${checks}))`], () => {
5264
- w.line(`errors.push("${name}: at least one of ${choiceNames.join(", ")} is required")`);
5265
- });
5266
- }
5267
- continue;
5268
- }
5269
- emitRegularFieldValidation(w, name, field, profileName);
5240
+ var generateRawType = (w, flatProfile, factoryInfo) => {
5241
+ const hasParams = factoryInfo.params.length > 0 || factoryInfo.sliceAutoFields.length > 0;
5242
+ const subSlices = flatProfile.base.name === "Extension" ? collectSubExtensionSlices(flatProfile) : [];
5243
+ if (!hasParams && subSlices.length === 0) return;
5244
+ const createArgsTypeName = `${tsProfileClassName(flatProfile)}Raw`;
5245
+ w.curlyBlock(["export", "type", createArgsTypeName, "="], () => {
5246
+ for (const p of factoryInfo.params) {
5247
+ w.lineSM(`${p.name}: ${p.tsType}`);
5248
+ }
5249
+ for (const f of factoryInfo.sliceAutoFields) {
5250
+ w.lineSM(`${f.name}?: ${f.tsType}`);
5251
+ }
5252
+ const extensionCovered = factoryInfo.params.some((p) => p.name === "extension") || factoryInfo.sliceAutoFields.some((f) => f.name === "extension");
5253
+ if (subSlices.length > 0 && !extensionCovered) {
5254
+ w.lineSM("extension?: Extension[]");
5270
5255
  }
5271
- w.line("return errors");
5272
5256
  });
5273
5257
  w.line();
5274
5258
  };
5275
- var generateExtensionSetterMethods = (w, extensions, extensionMethodNames, tsProfileName) => {
5276
- for (const ext of extensions) {
5277
- if (!ext.url) continue;
5278
- const methodName = extensionMethodNames.get(ext) ?? tsQualifiedExtensionMethodName(ext.name, ext.path);
5279
- const valueTypes = ext.valueTypes ?? [];
5280
- const targetPath = ext.path.split(".").filter((segment) => segment !== "extension");
5281
- if (ext.isComplex && ext.subExtensions) {
5282
- const inputTypeName = tsExtensionInputTypeName(tsProfileName, ext.name);
5283
- w.curlyBlock(["public", methodName, `(input: ${inputTypeName}): this`], () => {
5284
- w.line("const subExtensions: Extension[] = []");
5285
- for (const sub of ext.subExtensions ?? []) {
5286
- const valueField = sub.valueType ? `value${uppercaseFirstLetter(sub.valueType.name)}` : "value";
5287
- const needsCast = !sub.valueType;
5288
- const pushSuffix = needsCast ? " as Extension" : "";
5289
- if (sub.max === "*") {
5290
- w.curlyBlock(["if", `(input.${sub.name})`], () => {
5291
- w.curlyBlock(["for", `(const item of input.${sub.name})`], () => {
5292
- w.line(`subExtensions.push({ url: "${sub.url}", ${valueField}: item }${pushSuffix})`);
5293
- });
5294
- });
5295
- } else {
5296
- w.curlyBlock(["if", `(input.${sub.name} !== undefined)`], () => {
5297
- w.line(
5298
- `subExtensions.push({ url: "${sub.url}", ${valueField}: input.${sub.name} }${pushSuffix})`
5299
- );
5300
- });
5301
- }
5302
- }
5303
- if (targetPath.length === 0) {
5304
- w.line("const list = (this.resource.extension ??= [])");
5305
- w.line(`list.push({ url: "${ext.url}", extension: subExtensions })`);
5306
- } else {
5307
- w.line(
5308
- `const target = getOrCreateObjectAtPath(this.resource as unknown as Record<string, unknown>, ${JSON.stringify(targetPath)})`
5309
- );
5310
- w.line("if (!Array.isArray(target.extension)) target.extension = [] as Extension[]");
5311
- w.line(`(target.extension as Extension[]).push({ url: "${ext.url}", extension: subExtensions })`);
5312
- }
5313
- w.line("return this");
5314
- });
5315
- } else if (valueTypes.length === 1 && valueTypes[0]) {
5316
- const firstValueType = valueTypes[0];
5317
- const valueType = tsTypeFromIdentifier(firstValueType);
5318
- const valueField = `value${uppercaseFirstLetter(firstValueType.name)}`;
5319
- w.curlyBlock(["public", methodName, `(value: ${valueType}): this`], () => {
5320
- const extLiteral = `{ url: "${ext.url}", ${valueField}: value } as Extension`;
5321
- if (targetPath.length === 0) {
5322
- w.line("const list = (this.resource.extension ??= [])");
5323
- w.line(`list.push(${extLiteral})`);
5324
- } else {
5325
- w.line(
5326
- `const target = getOrCreateObjectAtPath(this.resource as unknown as Record<string, unknown>, ${JSON.stringify(
5327
- targetPath
5328
- )})`
5329
- );
5330
- w.line("if (!Array.isArray(target.extension)) target.extension = [] as Extension[]");
5331
- w.line(`(target.extension as Extension[]).push(${extLiteral})`);
5332
- }
5333
- w.line("return this");
5334
- });
5335
- } else {
5336
- w.curlyBlock(["public", methodName, `(value: Omit<Extension, "url">): this`], () => {
5337
- if (targetPath.length === 0) {
5338
- w.line("const list = (this.resource.extension ??= [])");
5339
- w.line(`list.push({ url: "${ext.url}", ...value })`);
5340
- } else {
5341
- w.line(
5342
- `const target = getOrCreateObjectAtPath(this.resource as unknown as Record<string, unknown>, ${JSON.stringify(
5343
- targetPath
5344
- )})`
5345
- );
5346
- w.line("if (!Array.isArray(target.extension)) target.extension = [] as Extension[]");
5347
- w.line(`(target.extension as Extension[]).push({ url: "${ext.url}", ...value })`);
5348
- }
5349
- w.line("return this");
5350
- });
5259
+ var generateFlatInputType = (w, flatProfile) => {
5260
+ const subSlices = flatProfile.base.name === "Extension" ? collectSubExtensionSlices(flatProfile) : [];
5261
+ if (subSlices.length === 0) return;
5262
+ const flatInputTypeName = `${tsProfileClassName(flatProfile)}Flat`;
5263
+ w.curlyBlock(["export", "type", flatInputTypeName, "="], () => {
5264
+ for (const sub of subSlices) {
5265
+ const opt = sub.isRequired ? "" : "?";
5266
+ const arr = sub.isArray ? "[]" : "";
5267
+ w.lineSM(`${sub.name}${opt}: ${sub.tsType}${arr}`);
5351
5268
  }
5269
+ });
5270
+ w.line();
5271
+ };
5272
+ var countBy = (entries, level) => entries.reduce(
5273
+ (counts, e) => {
5274
+ const name = e.candidates[level] ?? "";
5275
+ counts[name] = (counts[name] ?? 0) + 1;
5276
+ return counts;
5277
+ },
5278
+ {}
5279
+ );
5280
+ var resolveNameCollisions = (entries) => {
5281
+ const levels = entries[0]?.candidates.length ?? 0;
5282
+ const resolve6 = (unresolved, level) => {
5283
+ if (unresolved.length === 0 || level >= levels) return {};
5284
+ const counts = countBy(unresolved, level);
5285
+ const isLastLevel = level >= levels - 1;
5286
+ const [resolved, colliding] = unresolved.reduce(
5287
+ ([res, col], e) => {
5288
+ const name = e.candidates[level] ?? "";
5289
+ return (counts[name] ?? 0) > 1 && !isLastLevel ? [res, [...col, e]] : [{ ...res, [e.key]: name }, col];
5290
+ },
5291
+ [{}, []]
5292
+ );
5293
+ return { ...resolved, ...resolve6(colliding, level + 1) };
5294
+ };
5295
+ return resolve6(entries, 0);
5296
+ };
5297
+ var toRecord = (entries, resolved) => Object.fromEntries(entries.map((e) => [e.key, resolved[e.key] ?? e.candidates[0] ?? ""]));
5298
+ var resolveProfileMethodBaseNames = (extensions, sliceDefs) => {
5299
+ const extensionEntries = extensions.filter((ext) => ext.url).map((ext) => {
5300
+ const base = tsExtensionMethodBaseName(ext.name);
5301
+ const qualified = tsQualifiedExtensionMethodBaseName(ext.name, ext.path);
5302
+ return { key: `${ext.url}:${ext.path}`, candidates: [base, qualified, `${qualified}Extension`] };
5303
+ });
5304
+ const sliceEntries = sliceDefs.map((slice) => {
5305
+ const base = tsSliceMethodBaseName(slice.sliceName);
5306
+ const qualified = tsQualifiedSliceMethodBaseName(slice.fieldName, slice.sliceName);
5307
+ return { key: `${slice.fieldName}:${slice.sliceName}`, candidates: [base, qualified, `${qualified}Slice`] };
5308
+ });
5309
+ const resolved = resolveNameCollisions([...extensionEntries, ...sliceEntries]);
5310
+ const extensionsRecords = toRecord(extensionEntries, resolved);
5311
+ const slicesRecords = toRecord(sliceEntries, resolved);
5312
+ const allBaseNames = /* @__PURE__ */ new Set([...Object.values(extensionsRecords), ...Object.values(slicesRecords)]);
5313
+ return { extensions: extensionsRecords, slices: slicesRecords, allBaseNames };
5314
+ };
5315
+ var generateProfileClass = (w, tsIndex, flatProfile) => {
5316
+ const tsBaseResourceName = tsTypeFromIdentifier(flatProfile.base);
5317
+ const profileClassName = tsProfileClassName(flatProfile);
5318
+ const sliceDefs = collectSliceDefs(tsIndex, flatProfile);
5319
+ const factoryInfo = collectProfileFactoryInfo(tsIndex, flatProfile);
5320
+ generateInlineExtensionInputTypes(w, tsIndex, flatProfile);
5321
+ generateSliceInputTypes(w, flatProfile, sliceDefs);
5322
+ generateProfileHelpersImport(w, tsIndex, flatProfile, sliceDefs, factoryInfo);
5323
+ generateRawType(w, flatProfile, factoryInfo);
5324
+ generateFlatInputType(w, flatProfile);
5325
+ const canonicalUrl = flatProfile.identifier.url;
5326
+ w.comment("CanonicalURL:", canonicalUrl, `(pkg: ${packageMetaToFhir(packageMeta(flatProfile))})`);
5327
+ const resolvedMethodNames = resolveProfileMethodBaseNames(flatProfile.extensions ?? [], sliceDefs);
5328
+ w.curlyBlock(["export", "class", profileClassName], () => {
5329
+ w.lineSM(`static readonly canonicalUrl = ${JSON.stringify(canonicalUrl)}`);
5352
5330
  w.line();
5353
- }
5331
+ generateStaticSliceFields(w, sliceDefs);
5332
+ w.lineSM(`private resource: ${tsBaseResourceName}`);
5333
+ w.line();
5334
+ generateFactoryMethods(w, tsIndex, flatProfile, factoryInfo);
5335
+ generateFieldAccessors(w, factoryInfo, resolvedMethodNames.allBaseNames);
5336
+ w.line("// Extensions");
5337
+ generateExtensionMethods(w, tsIndex, flatProfile, resolvedMethodNames.extensions);
5338
+ w.line("// Slices");
5339
+ generateSliceSetters(w, sliceDefs, flatProfile, resolvedMethodNames.slices);
5340
+ generateSliceGetters(w, sliceDefs, flatProfile, resolvedMethodNames.slices);
5341
+ w.line("// Validation");
5342
+ generateValidateMethod(w, tsIndex, flatProfile);
5343
+ });
5344
+ w.line();
5354
5345
  };
5355
- var detectFieldOverrides = (w, tsIndex, flatProfile) => {
5346
+ var detectFieldOverrides = (tsIndex, flatProfile) => {
5356
5347
  const overrides = /* @__PURE__ */ new Map();
5357
5348
  const specialization = tsIndex.findLastSpecialization(flatProfile);
5358
5349
  if (!isSpecializationTypeSchema(specialization)) return overrides;
5350
+ const referenceUrl = "http://hl7.org/fhir/StructureDefinition/Reference";
5351
+ const referenceSchema = tsIndex.resolveByUrl(flatProfile.identifier.package, referenceUrl);
5359
5352
  for (const [fieldName, pField] of Object.entries(flatProfile.fields ?? {})) {
5360
5353
  if (!isNotChoiceDeclarationField(pField)) continue;
5361
5354
  const sField = specialization.fields?.[fieldName];
5362
5355
  if (!sField || isChoiceDeclarationField(sField)) continue;
5363
5356
  if (pField.reference && sField.reference && pField.reference.length < sField.reference.length) {
5357
+ if (!referenceSchema) continue;
5364
5358
  const references = pField.reference.map((ref) => {
5365
5359
  const resRef = tsIndex.findLastSpecializationByIdentifier(ref);
5366
5360
  if (resRef.name !== ref.name) {
@@ -5371,21 +5365,22 @@ var detectFieldOverrides = (w, tsIndex, flatProfile) => {
5371
5365
  overrides.set(fieldName, {
5372
5366
  profileType: `Reference<${references}>`,
5373
5367
  required: pField.required ?? false,
5374
- array: pField.array ?? false
5368
+ array: pField.array ?? false,
5369
+ typeId: referenceSchema.identifier
5375
5370
  });
5376
5371
  } else if (pField.required && !sField.required) {
5377
- const tsType = tsTypeForProfileField(w, tsIndex, flatProfile, fieldName, pField);
5372
+ const tsType = tsTypeForProfileField(tsIndex, flatProfile, fieldName, pField);
5378
5373
  overrides.set(fieldName, {
5379
5374
  profileType: tsType,
5380
5375
  required: true,
5381
- array: pField.array ?? false
5376
+ array: pField.array ?? false,
5377
+ typeId: pField.type
5382
5378
  });
5383
5379
  }
5384
5380
  }
5385
5381
  return overrides;
5386
5382
  };
5387
- var generateProfileOverrideInterface = (w, tsIndex, flatProfile) => {
5388
- const overrides = detectFieldOverrides(w, tsIndex, flatProfile);
5383
+ var generateProfileOverrideInterface = (w, flatProfile, overrides) => {
5389
5384
  if (overrides.size === 0) return;
5390
5385
  const tsProfileName = tsResourceName(flatProfile.identifier);
5391
5386
  const tsBaseResourceName = tsResourceName(flatProfile.base);
@@ -5401,9 +5396,49 @@ var generateProfileOverrideInterface = (w, tsIndex, flatProfile) => {
5401
5396
  };
5402
5397
 
5403
5398
  // src/api/writer-generator/typescript/writer.ts
5399
+ var resolveTsAssets = (fn) => {
5400
+ const __dirname = Path5.dirname(fileURLToPath(import.meta.url));
5401
+ const __filename = fileURLToPath(import.meta.url);
5402
+ if (__filename.endsWith("dist/index.js")) {
5403
+ return Path5.resolve(__dirname, "..", "assets", "api", "writer-generator", "typescript", fn);
5404
+ }
5405
+ return Path5.resolve(__dirname, "../../../..", "assets", "api", "writer-generator", "typescript", fn);
5406
+ };
5404
5407
  var TypeScript = class extends Writer {
5405
- tsImportType(tsPackageName, ...entities) {
5406
- this.lineSM(`import type { ${entities.join(", ")} } from "${tsPackageName}"`);
5408
+ constructor(options) {
5409
+ super({ lineWidth: 120, ...options, resolveAssets: options.resolveAssets ?? resolveTsAssets });
5410
+ }
5411
+ ifElseChain(branches, elseBody) {
5412
+ branches.forEach((branch, i) => {
5413
+ const prefix = i === 0 ? "if" : "} else if";
5414
+ this.line(`${prefix} (${branch.cond}) {`);
5415
+ this.indent();
5416
+ branch.body();
5417
+ this.deindent();
5418
+ });
5419
+ if (elseBody) {
5420
+ this.line("} else {");
5421
+ this.indent();
5422
+ elseBody();
5423
+ this.deindent();
5424
+ }
5425
+ this.line("}");
5426
+ }
5427
+ tsImport(tsPackageName, ...rest) {
5428
+ const last = rest[rest.length - 1];
5429
+ const typeOnly = typeof last === "object" ? last.typeOnly : false;
5430
+ const entities = typeof last === "object" ? rest.slice(0, -1) : rest;
5431
+ const keyword = typeOnly ? "import type" : "import";
5432
+ const singleLine = `${keyword} { ${entities.join(", ")} } from "${tsPackageName}"`;
5433
+ if (singleLine.length <= (this.opts.lineWidth ?? 120)) {
5434
+ this.lineSM(singleLine);
5435
+ } else {
5436
+ this.curlyBlock([keyword], () => {
5437
+ for (const entity of entities) {
5438
+ this.line(`${entity},`);
5439
+ }
5440
+ }, [` from "${tsPackageName}";`]);
5441
+ }
5407
5442
  }
5408
5443
  generateFhirPackageIndexFile(schemas) {
5409
5444
  this.cat("index.ts", () => {
@@ -5468,7 +5503,7 @@ var TypeScript = class extends Writer {
5468
5503
  imports.sort((a, b) => a.name.localeCompare(b.name));
5469
5504
  for (const dep of imports) {
5470
5505
  this.debugComment(dep.dep);
5471
- this.tsImportType(dep.tsPackage, dep.name);
5506
+ this.tsImport(dep.tsPackage, dep.name, { typeOnly: true });
5472
5507
  }
5473
5508
  for (const dep of skipped) {
5474
5509
  this.debugComment("skip:", dep);
@@ -5478,7 +5513,7 @@ var TypeScript = class extends Writer {
5478
5513
  const elementUrl = "http://hl7.org/fhir/StructureDefinition/Element";
5479
5514
  const element = tsIndex.resolveByUrl(schema.identifier.package, elementUrl);
5480
5515
  if (!element) throw new Error(`'${elementUrl}' not found for ${schema.identifier.package}.`);
5481
- this.tsImportType(`${importPrefix}${tsModulePath(element.identifier)}`, "Element");
5516
+ this.tsImport(`${importPrefix}${tsModulePath(element.identifier)}`, "Element", { typeOnly: true });
5482
5517
  }
5483
5518
  }
5484
5519
  }
@@ -5575,9 +5610,10 @@ var TypeScript = class extends Writer {
5575
5610
  this.cat(`${tsProfileModuleFileName(tsIndex, schema)}`, () => {
5576
5611
  this.generateDisclaimer();
5577
5612
  const flatProfile = tsIndex.flatProfile(schema);
5578
- generateProfileImports(this, tsIndex, flatProfile);
5579
- generateProfileOverrideInterface(this, tsIndex, flatProfile);
5580
- generateProfileClass(this, tsIndex, flatProfile, schema);
5613
+ const overrides = detectFieldOverrides(tsIndex, flatProfile);
5614
+ generateProfileImports(this, tsIndex, flatProfile, overrides);
5615
+ generateProfileOverrideInterface(this, flatProfile, overrides);
5616
+ generateProfileClass(this, tsIndex, flatProfile);
5581
5617
  });
5582
5618
  });
5583
5619
  } else if (["complex-type", "resource", "logical"].includes(schema.identifier.kind)) {
@@ -5609,7 +5645,7 @@ var TypeScript = class extends Writer {
5609
5645
  const hasProfiles = this.opts.generateProfile && typesToGenerate.some(isProfileTypeSchema);
5610
5646
  this.cd("/", () => {
5611
5647
  if (hasProfiles) {
5612
- generateProfileHelpersModule(this);
5648
+ this.cp("profile-helpers.ts", "profile-helpers.ts");
5613
5649
  }
5614
5650
  for (const [packageName, packageSchemas] of Object.entries(grouped)) {
5615
5651
  const packageDir = tsPackageDir(packageName);
@@ -5674,7 +5710,6 @@ var APIBuilder = class {
5674
5710
  treeShake: void 0,
5675
5711
  promoteLogical: void 0,
5676
5712
  registry: void 0,
5677
- logLevel: parseLogLevel("INFO"),
5678
5713
  dropCanonicalManagerCache: false
5679
5714
  };
5680
5715
  const opts = {
@@ -5701,7 +5736,7 @@ var APIBuilder = class {
5701
5736
  dropCache: userOpts.dropCanonicalManagerCache,
5702
5737
  preprocessPackage: userOpts.preprocessPackage
5703
5738
  });
5704
- this.logger = userOpts.logger ?? createLogger({ prefix: "API", level: opts.logLevel });
5739
+ this.logger = userOpts.logger ?? mkLogger({ prefix: "api" });
5705
5740
  this.options = opts;
5706
5741
  }
5707
5742
  fromPackage(packageName, version) {
@@ -5841,10 +5876,6 @@ var APIBuilder = class {
5841
5876
  }
5842
5877
  return this;
5843
5878
  }
5844
- setLogLevel(level) {
5845
- this.logger?.setLevel(typeof level === "string" ? parseLogLevel(level) : level);
5846
- return this;
5847
- }
5848
5879
  throwException(enabled = true) {
5849
5880
  this.options.throwException = enabled;
5850
5881
  return this;
@@ -5912,26 +5943,28 @@ var APIBuilder = class {
5912
5943
  const ref2meta = await this.manager.init();
5913
5944
  const packageMetas = Object.values(ref2meta);
5914
5945
  register = await registerFromManager(this.manager, {
5915
- logger: this.logger,
5946
+ logger: this.logger.fork("reg"),
5916
5947
  focusedPackages: packageMetas
5917
5948
  });
5918
5949
  }
5919
- const { schemas: typeSchemas, collisions } = await generateTypeSchemas(register, this.logger);
5950
+ const tsLogger = this.logger.fork("ts");
5951
+ const { schemas: typeSchemas, collisions } = await generateTypeSchemas(register, tsLogger);
5920
5952
  const tsIndexOpts = {
5921
5953
  register,
5922
- logger: this.logger,
5954
+ logger: tsLogger,
5923
5955
  irReport: Object.keys(collisions).length > 0 ? { collisions } : {}
5924
5956
  };
5925
5957
  let tsIndex = mkTypeSchemaIndex(typeSchemas, tsIndexOpts);
5926
5958
  if (this.options.treeShake) tsIndex = treeShake(tsIndex, this.options.treeShake);
5927
5959
  if (this.options.promoteLogical) tsIndex = promoteLogical(tsIndex, this.options.promoteLogical);
5960
+ tsLogger.printTagSummary();
5928
5961
  this.logger.debug(`Executing ${this.generators.length} generators`);
5929
5962
  await this.executeGenerators(result, tsIndex);
5930
5963
  this.logger.info("Generation completed successfully");
5931
5964
  result.success = result.errors.length === 0;
5932
5965
  this.logger.debug(`Generation completed: ${result.filesGenerated.length} files`);
5933
5966
  } catch (error) {
5934
- this.logger.error("Code generation failed", error instanceof Error ? error : new Error(String(error)));
5967
+ this.logger.error(`Code generation failed: ${error instanceof Error ? error.message : String(error)}`);
5935
5968
  result.errors.push(error instanceof Error ? error.message : String(error));
5936
5969
  if (this.options.throwException) throw error;
5937
5970
  }
@@ -5974,6 +6007,6 @@ var APIBuilder = class {
5974
6007
  }
5975
6008
  };
5976
6009
 
5977
- export { APIBuilder, LogLevel, prettyReport, registerFromPackageMetas };
6010
+ export { APIBuilder, mkCodegenLogger, prettyReport, registerFromManager, registerFromPackageMetas };
5978
6011
  //# sourceMappingURL=index.js.map
5979
6012
  //# sourceMappingURL=index.js.map