@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/README.md +30 -31
- package/assets/api/writer-generator/python/fhirpy_base_model.py +11 -13
- package/assets/api/writer-generator/python/fhirpy_base_model_camel_case.py +11 -1
- package/assets/api/writer-generator/python/requirements.txt +6 -4
- package/assets/api/writer-generator/typescript/profile-helpers.ts +412 -0
- package/dist/cli/index.js +5 -7
- package/dist/index.d.ts +49 -91
- package/dist/index.js +1321 -1288
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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/
|
|
20
|
-
var
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
level
|
|
51
|
-
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
1514
|
-
|
|
1515
|
-
fieldType =
|
|
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
|
-
|
|
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(
|
|
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])
|
|
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?.
|
|
2169
|
+
conf?.logger?.info(`Loading FHIR packages: ${packageNames.join(", ")}`);
|
|
2175
2170
|
const manager = CanonicalManager({
|
|
2176
2171
|
packages: packageNames,
|
|
2177
|
-
workingDir: "
|
|
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(
|
|
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
|
-
|
|
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}'
|
|
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((
|
|
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)
|
|
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
|
-
|
|
4085
|
+
const name = normalizeTsName(schema.identifier.name);
|
|
4086
|
+
return name.endsWith("Profile") ? name : `${name}Profile`;
|
|
4078
4087
|
};
|
|
4079
|
-
var
|
|
4080
|
-
return `${uppercaseFirstLetter(profileName)}_${uppercaseFirstLetter(normalizeTsName(fieldName))}_${uppercaseFirstLetter(normalizeTsName(sliceName))}
|
|
4088
|
+
var tsSliceFlatTypeName = (profileName, fieldName, sliceName) => {
|
|
4089
|
+
return `${uppercaseFirstLetter(profileName)}_${uppercaseFirstLetter(normalizeTsName(fieldName))}_${uppercaseFirstLetter(normalizeTsName(sliceName))}SliceFlat`;
|
|
4081
4090
|
};
|
|
4082
|
-
var
|
|
4083
|
-
return `${uppercaseFirstLetter(profileName)}_${uppercaseFirstLetter(normalizeTsName(extensionName))}
|
|
4091
|
+
var tsExtensionFlatTypeName = (profileName, extensionName) => {
|
|
4092
|
+
return `${uppercaseFirstLetter(profileName)}_${uppercaseFirstLetter(normalizeTsName(extensionName))}Flat`;
|
|
4084
4093
|
};
|
|
4085
|
-
var
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
var
|
|
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
|
-
|
|
4096
|
-
return `setExtension${pathPart}${uppercaseFirstLetter(normalized || "Extension")}`;
|
|
4100
|
+
return `${pathPart}${uppercaseFirstLetter(tsCamelCase(name) || "Extension")}`;
|
|
4097
4101
|
};
|
|
4098
|
-
var
|
|
4102
|
+
var tsQualifiedSliceMethodBaseName = (fieldName, sliceName) => {
|
|
4099
4103
|
const fieldPart = uppercaseFirstLetter(tsCamelCase(fieldName) || "Field");
|
|
4100
4104
|
const slicePart = uppercaseFirstLetter(normalizeTsName(sliceName) || "Slice");
|
|
4101
|
-
return
|
|
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
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
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
|
|
4228
|
+
return result;
|
|
4182
4229
|
};
|
|
4183
|
-
var
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
if (
|
|
4187
|
-
const
|
|
4188
|
-
|
|
4189
|
-
const
|
|
4190
|
-
|
|
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
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
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
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
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
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
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
|
-
|
|
4297
|
+
generateInputBody();
|
|
4223
4298
|
}
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
|
|
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
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
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
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
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
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
}
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
return
|
|
4308
|
-
})
|
|
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
|
-
|
|
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
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
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
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
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
|
|
4639
|
-
const
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
|
|
4643
|
-
|
|
4644
|
-
|
|
4645
|
-
|
|
4646
|
-
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
|
|
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
|
|
4670
|
-
for (const
|
|
4671
|
-
if (!
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
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
|
|
4710
|
-
|
|
4711
|
-
const
|
|
4712
|
-
const
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
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 (
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
|
|
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
|
-
|
|
4745
|
-
|
|
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
|
|
4755
|
-
for (const { importPath, tsName } of
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
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
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
const
|
|
4901
|
-
|
|
4902
|
-
|
|
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}
|
|
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
|
-
|
|
4923
|
-
w.
|
|
4924
|
-
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
|
|
4928
|
-
|
|
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
|
-
`
|
|
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
|
-
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
|
|
4967
|
-
|
|
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.
|
|
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
|
-
|
|
4976
|
-
|
|
5106
|
+
w.line(`extension: ${extensionVar},`);
|
|
5107
|
+
if (hasMeta) {
|
|
5108
|
+
w.line(`meta: { profile: [${profileClassName}.canonicalUrl] },`);
|
|
4977
5109
|
}
|
|
4978
|
-
}, [
|
|
4979
|
-
w.
|
|
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
|
-
|
|
4987
|
-
|
|
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
|
-
|
|
4990
|
-
|
|
4991
|
-
const
|
|
4992
|
-
|
|
4993
|
-
w.line(`
|
|
4994
|
-
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
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
|
-
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
});
|
|
5133
|
+
}
|
|
5134
|
+
if (isPrimitiveIdentifier(flatProfile.base)) {
|
|
5135
|
+
w.lineSM(`const resource = undefined as unknown as ${tsBaseResourceName}`);
|
|
5142
5136
|
} else {
|
|
5143
|
-
w.curlyBlock([
|
|
5144
|
-
|
|
5145
|
-
w.line(
|
|
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.
|
|
5157
|
-
}
|
|
5158
|
-
|
|
5159
|
-
|
|
5160
|
-
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
|
|
5165
|
-
|
|
5166
|
-
|
|
5167
|
-
|
|
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
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
|
|
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
|
-
|
|
5227
|
-
const
|
|
5228
|
-
|
|
5229
|
-
|
|
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
|
-
|
|
5233
|
-
|
|
5234
|
-
|
|
5235
|
-
|
|
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
|
-
|
|
5239
|
-
|
|
5240
|
-
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
|
|
5244
|
-
|
|
5245
|
-
|
|
5246
|
-
|
|
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
|
|
5252
|
-
const
|
|
5253
|
-
const
|
|
5254
|
-
|
|
5255
|
-
|
|
5256
|
-
|
|
5257
|
-
for (const
|
|
5258
|
-
|
|
5259
|
-
|
|
5260
|
-
|
|
5261
|
-
|
|
5262
|
-
|
|
5263
|
-
|
|
5264
|
-
|
|
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
|
|
5276
|
-
|
|
5277
|
-
|
|
5278
|
-
|
|
5279
|
-
|
|
5280
|
-
const
|
|
5281
|
-
|
|
5282
|
-
const
|
|
5283
|
-
w.
|
|
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 = (
|
|
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(
|
|
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,
|
|
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
|
-
|
|
5406
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
5579
|
-
|
|
5580
|
-
|
|
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
|
-
|
|
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 ??
|
|
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
|
|
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:
|
|
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(
|
|
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,
|
|
6010
|
+
export { APIBuilder, mkCodegenLogger, prettyReport, registerFromManager, registerFromPackageMetas };
|
|
5978
6011
|
//# sourceMappingURL=index.js.map
|
|
5979
6012
|
//# sourceMappingURL=index.js.map
|