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