@atomic-ehr/codegen 0.0.4-canary.20251217082407.6dae422 → 0.0.4-canary.20251217132509.d94026d
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/assets/api/writer-generator/python/requirements.txt +5 -0
- package/assets/api/writer-generator/python/resource_family_validator.py +92 -0
- package/dist/cli/index.js +13 -13
- package/dist/index.d.ts +21 -12
- package/dist/index.js +874 -269
- package/dist/index.js.map +1 -1
- package/package.json +6 -3
package/dist/index.js
CHANGED
|
@@ -3,13 +3,14 @@ import * as fs from 'fs';
|
|
|
3
3
|
import fs__default, { existsSync } from 'fs';
|
|
4
4
|
import * as afs2 from 'fs/promises';
|
|
5
5
|
import { readFile } from 'fs/promises';
|
|
6
|
-
import * as
|
|
7
|
-
import
|
|
6
|
+
import * as Path5 from 'path';
|
|
7
|
+
import Path5__default, { resolve } from 'path';
|
|
8
8
|
import { CanonicalManager } from '@atomic-ehr/fhir-canonical-manager';
|
|
9
|
+
import assert2 from 'assert';
|
|
10
|
+
import { fileURLToPath } from 'url';
|
|
11
|
+
import * as YAML from 'yaml';
|
|
9
12
|
import * as fhirschema from '@atomic-ehr/fhirschema';
|
|
10
13
|
import { isStructureDefinition } from '@atomic-ehr/fhirschema';
|
|
11
|
-
import * as YAML from 'yaml';
|
|
12
|
-
import assert from 'assert';
|
|
13
14
|
|
|
14
15
|
// src/utils/codegen-logger.ts
|
|
15
16
|
var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
|
|
@@ -178,6 +179,9 @@ var camelCase = (s) => {
|
|
|
178
179
|
var pascalCase = (s) => {
|
|
179
180
|
return words(s).map(capitalCase).join("");
|
|
180
181
|
};
|
|
182
|
+
var snakeCase = (s) => {
|
|
183
|
+
return words(s).map((s2) => s2.toLowerCase()).join("_");
|
|
184
|
+
};
|
|
181
185
|
var uppercaseFirstLetter = (str) => {
|
|
182
186
|
if (!str || str.length === 0) return str;
|
|
183
187
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
@@ -224,7 +228,7 @@ var FileSystemWriter = class {
|
|
|
224
228
|
}
|
|
225
229
|
cd(path, gen) {
|
|
226
230
|
const prev = this.currentDir;
|
|
227
|
-
this.currentDir = path.startsWith("/") ?
|
|
231
|
+
this.currentDir = path.startsWith("/") ? Path5.join(this.opts.outputDir, path) : Path5.join(this.currentDir ?? this.opts.outputDir, path);
|
|
228
232
|
this.onDiskMkDir(this.currentDir);
|
|
229
233
|
this.logger()?.debug(`cd '${this.currentDir}'`);
|
|
230
234
|
gen();
|
|
@@ -233,12 +237,12 @@ var FileSystemWriter = class {
|
|
|
233
237
|
cat(fn, gen) {
|
|
234
238
|
if (this.currentFile) throw new Error("Can't open file when another file is open");
|
|
235
239
|
if (fn.includes("/")) throw new Error(`Change file path separatly: ${fn}`);
|
|
236
|
-
const relPath =
|
|
240
|
+
const relPath = Path5.normalize(`${this.currentDir}/${fn}`);
|
|
237
241
|
try {
|
|
238
242
|
const descriptor = this.onDiskOpenFile(relPath);
|
|
239
243
|
this.logger()?.debug(`cat > '${relPath}'`);
|
|
240
244
|
this.currentFile = { descriptor, relPath };
|
|
241
|
-
this.writtenFilesBuffer[this.currentFile.relPath] = { relPath, absPath:
|
|
245
|
+
this.writtenFilesBuffer[this.currentFile.relPath] = { relPath, absPath: Path5.resolve(relPath), tokens: [] };
|
|
242
246
|
gen();
|
|
243
247
|
} finally {
|
|
244
248
|
if (this.currentFile) this.onDiskCloseFile(this.currentFile.descriptor);
|
|
@@ -726,212 +730,16 @@ var CSharp = class extends Writer {
|
|
|
726
730
|
}
|
|
727
731
|
copyStaticFiles() {
|
|
728
732
|
if (!this.opts.staticSourceDir) return;
|
|
729
|
-
const sourcePath =
|
|
733
|
+
const sourcePath = Path5__default.resolve(this.opts.staticSourceDir);
|
|
730
734
|
fs__default.cpSync(sourcePath, this.opts.outputDir, { recursive: true });
|
|
731
735
|
}
|
|
732
736
|
generateHelperFile() {
|
|
733
737
|
const sourceFile = "src/api/writer-generator/csharp/Helper.cs";
|
|
734
|
-
const destFile =
|
|
738
|
+
const destFile = Path5__default.join(this.opts.outputDir, "Helper.cs");
|
|
735
739
|
fs__default.copyFileSync(sourceFile, destFile);
|
|
736
740
|
}
|
|
737
741
|
};
|
|
738
742
|
|
|
739
|
-
// src/fhir-types/hl7-fhir-r4-core/CodeSystem.ts
|
|
740
|
-
var isCodeSystem = (resource) => {
|
|
741
|
-
return resource !== null && typeof resource === "object" && resource.resourceType === "CodeSystem";
|
|
742
|
-
};
|
|
743
|
-
|
|
744
|
-
// src/fhir-types/hl7-fhir-r4-core/ValueSet.ts
|
|
745
|
-
var isValueSet = (resource) => {
|
|
746
|
-
return resource !== null && typeof resource === "object" && resource.resourceType === "ValueSet";
|
|
747
|
-
};
|
|
748
|
-
|
|
749
|
-
// src/typeschema/register.ts
|
|
750
|
-
var readPackageDependencies = async (manager, packageMeta) => {
|
|
751
|
-
const packageJSON = await manager.packageJson(packageMeta.name);
|
|
752
|
-
const dependencies = packageJSON.dependencies;
|
|
753
|
-
if (dependencies !== void 0) {
|
|
754
|
-
return Object.entries(dependencies).map(([name, version]) => {
|
|
755
|
-
return { name, version };
|
|
756
|
-
});
|
|
757
|
-
}
|
|
758
|
-
return [];
|
|
759
|
-
};
|
|
760
|
-
var mkEmptyPkgIndex = (pkg) => {
|
|
761
|
-
return {
|
|
762
|
-
pkg,
|
|
763
|
-
canonicalResolution: {},
|
|
764
|
-
fhirSchemas: {},
|
|
765
|
-
valueSets: {}
|
|
766
|
-
};
|
|
767
|
-
};
|
|
768
|
-
var mkPackageAwareResolver = async (manager, pkg, deep, acc, logger) => {
|
|
769
|
-
const pkgId = packageMetaToFhir(pkg);
|
|
770
|
-
logger?.info(`${" ".repeat(deep * 2)}+ ${pkgId}`);
|
|
771
|
-
if (acc[pkgId]) return acc[pkgId];
|
|
772
|
-
const index = mkEmptyPkgIndex(pkg);
|
|
773
|
-
for (const resource of await manager.search({ package: pkg })) {
|
|
774
|
-
const rawUrl = resource.url;
|
|
775
|
-
if (!rawUrl) continue;
|
|
776
|
-
if (!(isStructureDefinition(resource) || isValueSet(resource) || isCodeSystem(resource))) continue;
|
|
777
|
-
const url = rawUrl;
|
|
778
|
-
if (index.canonicalResolution[url]) logger?.dry_warn(`Duplicate canonical URL: ${url} at ${pkgId}.`);
|
|
779
|
-
index.canonicalResolution[url] = [{ deep, pkg, pkgId, resource }];
|
|
780
|
-
}
|
|
781
|
-
const deps = await readPackageDependencies(manager, pkg);
|
|
782
|
-
for (const depPkg of deps) {
|
|
783
|
-
const { canonicalResolution } = await mkPackageAwareResolver(manager, depPkg, deep + 1, acc, logger);
|
|
784
|
-
for (const [surl, resolutions] of Object.entries(canonicalResolution)) {
|
|
785
|
-
const url = surl;
|
|
786
|
-
index.canonicalResolution[url] = [...index.canonicalResolution[url] || [], ...resolutions];
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
|
-
for (const resolutionOptions of Object.values(index.canonicalResolution)) {
|
|
790
|
-
resolutionOptions.sort((a, b) => a.deep - b.deep);
|
|
791
|
-
}
|
|
792
|
-
acc[pkgId] = index;
|
|
793
|
-
return index;
|
|
794
|
-
};
|
|
795
|
-
var packageAgnosticResolveCanonical = (resolver, url, _logger) => {
|
|
796
|
-
const options = Object.values(resolver).flatMap((pkg) => pkg.canonicalResolution[url]);
|
|
797
|
-
if (!options) throw new Error(`No canonical resolution found for ${url} in any package`);
|
|
798
|
-
return options[0]?.resource;
|
|
799
|
-
};
|
|
800
|
-
var registerFromManager = async (manager, { logger, fallbackPackageForNameResolution, focusedPackages }) => {
|
|
801
|
-
const packages = focusedPackages ?? await manager.packages();
|
|
802
|
-
const resolver = {};
|
|
803
|
-
for (const pkg of packages) {
|
|
804
|
-
await mkPackageAwareResolver(manager, pkg, 0, resolver, logger);
|
|
805
|
-
}
|
|
806
|
-
for (const { pkg, canonicalResolution } of Object.values(resolver)) {
|
|
807
|
-
const pkgId = packageMetaToFhir(pkg);
|
|
808
|
-
if (!resolver[pkgId]) throw new Error(`Package ${pkgId} not found`);
|
|
809
|
-
let counter = 0;
|
|
810
|
-
logger?.info(`FHIR Schema conversion for '${packageMetaToFhir(pkg)}' begins...`);
|
|
811
|
-
for (const [_url, options] of Object.entries(canonicalResolution)) {
|
|
812
|
-
const resolition = options[0];
|
|
813
|
-
if (!resolition) throw new Error(`Resource not found`);
|
|
814
|
-
const resource = resolition.resource;
|
|
815
|
-
const resourcePkg = resolition.pkg;
|
|
816
|
-
if (isStructureDefinition(resource)) {
|
|
817
|
-
const rfs = enrichFHIRSchema(
|
|
818
|
-
fhirschema.translate(resource),
|
|
819
|
-
resourcePkg
|
|
820
|
-
);
|
|
821
|
-
counter++;
|
|
822
|
-
resolver[pkgId].fhirSchemas[rfs.url] = rfs;
|
|
823
|
-
}
|
|
824
|
-
if (isValueSet(resource)) {
|
|
825
|
-
const rvs = enrichValueSet(resource, resourcePkg);
|
|
826
|
-
resolver[pkgId].valueSets[rvs.url] = rvs;
|
|
827
|
-
}
|
|
828
|
-
}
|
|
829
|
-
logger?.info(`FHIR Schema conversion for '${packageMetaToFhir(pkg)}' completed: ${counter} successful`);
|
|
830
|
-
}
|
|
831
|
-
const resolveFs = (pkg, canonicalUrl) => {
|
|
832
|
-
return resolver[packageMetaToFhir(pkg)]?.fhirSchemas[canonicalUrl] || fallbackPackageForNameResolution && resolver[packageMetaToFhir(fallbackPackageForNameResolution)]?.fhirSchemas[canonicalUrl];
|
|
833
|
-
};
|
|
834
|
-
const resolveVs = (pkg, canonicalUrl) => {
|
|
835
|
-
return resolver[packageMetaToFhir(pkg)]?.valueSets[canonicalUrl] || fallbackPackageForNameResolution && resolver[packageMetaToFhir(fallbackPackageForNameResolution)]?.valueSets[canonicalUrl];
|
|
836
|
-
};
|
|
837
|
-
const ensureSpecializationCanonicalUrl = (name) => name.match(/^[a-zA-Z0-9]+$/) && `http://hl7.org/fhir/StructureDefinition/${name}` || name;
|
|
838
|
-
const resolveFsGenealogy = (pkg, canonicalUrl) => {
|
|
839
|
-
let fs4 = resolveFs(pkg, canonicalUrl);
|
|
840
|
-
if (fs4 === void 0) throw new Error(`Failed to resolve FHIR Schema: '${canonicalUrl}'`);
|
|
841
|
-
const genealogy = [fs4];
|
|
842
|
-
while (fs4?.base) {
|
|
843
|
-
const pkg2 = fs4.package_meta;
|
|
844
|
-
const baseUrl = ensureSpecializationCanonicalUrl(fs4.base);
|
|
845
|
-
fs4 = resolveFs(pkg2, baseUrl);
|
|
846
|
-
if (fs4 === void 0)
|
|
847
|
-
throw new Error(
|
|
848
|
-
`Failed to resolve FHIR Schema base for '${canonicalUrl}'. Problem: '${baseUrl}' from '${packageMetaToFhir(pkg2)}'`
|
|
849
|
-
);
|
|
850
|
-
genealogy.push(fs4);
|
|
851
|
-
}
|
|
852
|
-
return genealogy;
|
|
853
|
-
};
|
|
854
|
-
const resolveFsSpecializations = (pkg, canonicalUrl) => {
|
|
855
|
-
return resolveFsGenealogy(pkg, canonicalUrl).filter((fs4) => fs4.derivation === "specialization");
|
|
856
|
-
};
|
|
857
|
-
const resolveElementSnapshot = (fhirSchema, path) => {
|
|
858
|
-
const geneology = resolveFsGenealogy(fhirSchema.package_meta, fhirSchema.url);
|
|
859
|
-
const elemGeneology = resolveFsElementGenealogy(geneology, path);
|
|
860
|
-
const elemSnapshot = fsElementSnapshot(elemGeneology);
|
|
861
|
-
return elemSnapshot;
|
|
862
|
-
};
|
|
863
|
-
const getAllElementKeys = (elems) => {
|
|
864
|
-
const keys = /* @__PURE__ */ new Set();
|
|
865
|
-
for (const [key, elem] of Object.entries(elems)) {
|
|
866
|
-
keys.add(key);
|
|
867
|
-
for (const choiceKey of elem?.choices || []) {
|
|
868
|
-
if (!elems[choiceKey]) {
|
|
869
|
-
keys.add(choiceKey);
|
|
870
|
-
}
|
|
871
|
-
}
|
|
872
|
-
}
|
|
873
|
-
return Array.from(keys);
|
|
874
|
-
};
|
|
875
|
-
return {
|
|
876
|
-
...manager,
|
|
877
|
-
testAppendFs(fs4) {
|
|
878
|
-
const rfs = enrichFHIRSchema(fs4);
|
|
879
|
-
const pkgId = packageMetaToFhir(rfs.package_meta);
|
|
880
|
-
if (!resolver[pkgId]) resolver[pkgId] = mkEmptyPkgIndex(rfs.package_meta);
|
|
881
|
-
resolver[pkgId].fhirSchemas[rfs.url] = rfs;
|
|
882
|
-
},
|
|
883
|
-
resolveFs,
|
|
884
|
-
resolveFsGenealogy,
|
|
885
|
-
resolveFsSpecializations,
|
|
886
|
-
ensureSpecializationCanonicalUrl,
|
|
887
|
-
resolveSd: (_pkg, canonicalUrl) => {
|
|
888
|
-
const res = packageAgnosticResolveCanonical(resolver, canonicalUrl);
|
|
889
|
-
if (isStructureDefinition(res)) return res;
|
|
890
|
-
return void 0;
|
|
891
|
-
},
|
|
892
|
-
allFs: () => Object.values(resolver).flatMap((pkgIndex) => Object.values(pkgIndex.fhirSchemas)),
|
|
893
|
-
allVs: () => Object.values(resolver).flatMap((pkgIndex) => Object.values(pkgIndex.valueSets)),
|
|
894
|
-
resolveVs,
|
|
895
|
-
resolveAny: (canonicalUrl) => packageAgnosticResolveCanonical(resolver, canonicalUrl),
|
|
896
|
-
resolveElementSnapshot,
|
|
897
|
-
getAllElementKeys,
|
|
898
|
-
resolver,
|
|
899
|
-
resolutionTree: () => {
|
|
900
|
-
const res = {};
|
|
901
|
-
for (const [_pkgId, pkgIndex] of Object.entries(resolver)) {
|
|
902
|
-
const pkgName = pkgIndex.pkg.name;
|
|
903
|
-
res[pkgName] = {};
|
|
904
|
-
for (const [surl, resolutions] of Object.entries(pkgIndex.canonicalResolution)) {
|
|
905
|
-
const url = surl;
|
|
906
|
-
res[pkgName][url] = [];
|
|
907
|
-
for (const resolution of resolutions) {
|
|
908
|
-
res[pkgName][url].push({ deep: resolution.deep, pkg: resolution.pkg });
|
|
909
|
-
}
|
|
910
|
-
}
|
|
911
|
-
}
|
|
912
|
-
return res;
|
|
913
|
-
}
|
|
914
|
-
};
|
|
915
|
-
};
|
|
916
|
-
var resolveFsElementGenealogy = (genealogy, path) => {
|
|
917
|
-
const [top, ...rest] = path;
|
|
918
|
-
if (top === void 0) return [];
|
|
919
|
-
return genealogy.map((fs4) => {
|
|
920
|
-
if (!fs4.elements) return void 0;
|
|
921
|
-
let elem = fs4.elements?.[top];
|
|
922
|
-
for (const k of rest) {
|
|
923
|
-
elem = elem?.elements?.[k];
|
|
924
|
-
}
|
|
925
|
-
return elem;
|
|
926
|
-
}).filter((elem) => elem !== void 0);
|
|
927
|
-
};
|
|
928
|
-
function fsElementSnapshot(genealogy) {
|
|
929
|
-
const revGenealogy = genealogy.reverse();
|
|
930
|
-
const snapshot = Object.assign({}, ...revGenealogy);
|
|
931
|
-
snapshot.elements = void 0;
|
|
932
|
-
return snapshot;
|
|
933
|
-
}
|
|
934
|
-
|
|
935
743
|
// src/typeschema/core/identifier.ts
|
|
936
744
|
function dropVersionFromUrl(url) {
|
|
937
745
|
const baseUrl = url.split("|")[0];
|
|
@@ -1002,7 +810,7 @@ function mkNestedIdentifier(register, fhirSchema, path, logger) {
|
|
|
1002
810
|
const nestedTypeOrigins = {};
|
|
1003
811
|
if (fhirSchema.derivation === "constraint") {
|
|
1004
812
|
const specializations = register.resolveFsSpecializations(fhirSchema.package_meta, fhirSchema.url);
|
|
1005
|
-
const nestedTypeGenealogy = specializations.map((
|
|
813
|
+
const nestedTypeGenealogy = specializations.map((fs5) => mkNestedTypes(register, fs5, logger)).filter((e) => e !== void 0).flat();
|
|
1006
814
|
for (const nt of nestedTypeGenealogy.reverse()) {
|
|
1007
815
|
nestedTypeOrigins[nt.identifier.name] = nt.identifier.url;
|
|
1008
816
|
}
|
|
@@ -1101,10 +909,10 @@ function isRequired(register, fhirSchema, path) {
|
|
|
1101
909
|
const fieldName = path[path.length - 1];
|
|
1102
910
|
if (!fieldName) throw new Error(`Internal error: fieldName is missing for path ${path.join("/")}`);
|
|
1103
911
|
const parentPath = path.slice(0, -1);
|
|
1104
|
-
const requires = register.resolveFsGenealogy(fhirSchema.package_meta, fhirSchema.url).flatMap((
|
|
1105
|
-
if (parentPath.length === 0) return
|
|
1106
|
-
if (!
|
|
1107
|
-
let elem =
|
|
912
|
+
const requires = register.resolveFsGenealogy(fhirSchema.package_meta, fhirSchema.url).flatMap((fs5) => {
|
|
913
|
+
if (parentPath.length === 0) return fs5.required || [];
|
|
914
|
+
if (!fs5.elements) return [];
|
|
915
|
+
let elem = fs5;
|
|
1108
916
|
for (const k of parentPath) {
|
|
1109
917
|
elem = elem?.elements?.[k];
|
|
1110
918
|
}
|
|
@@ -1116,10 +924,10 @@ function isExcluded(register, fhirSchema, path) {
|
|
|
1116
924
|
const fieldName = path[path.length - 1];
|
|
1117
925
|
if (!fieldName) throw new Error(`Internal error: fieldName is missing for path ${path.join("/")}`);
|
|
1118
926
|
const parentPath = path.slice(0, -1);
|
|
1119
|
-
const requires = register.resolveFsGenealogy(fhirSchema.package_meta, fhirSchema.url).flatMap((
|
|
1120
|
-
if (parentPath.length === 0) return
|
|
1121
|
-
if (!
|
|
1122
|
-
let elem =
|
|
927
|
+
const requires = register.resolveFsGenealogy(fhirSchema.package_meta, fhirSchema.url).flatMap((fs5) => {
|
|
928
|
+
if (parentPath.length === 0) return fs5.excluded || [];
|
|
929
|
+
if (!fs5.elements) return [];
|
|
930
|
+
let elem = fs5;
|
|
1123
931
|
for (const k of parentPath) {
|
|
1124
932
|
elem = elem?.elements?.[k];
|
|
1125
933
|
}
|
|
@@ -1131,9 +939,9 @@ var buildReferences = (register, fhirSchema, element) => {
|
|
|
1131
939
|
if (!element.refers) return void 0;
|
|
1132
940
|
return element.refers.map((ref) => {
|
|
1133
941
|
const curl = register.ensureSpecializationCanonicalUrl(ref);
|
|
1134
|
-
const
|
|
1135
|
-
if (!
|
|
1136
|
-
return mkIdentifier(
|
|
942
|
+
const fs5 = register.resolveFs(fhirSchema.package_meta, curl);
|
|
943
|
+
if (!fs5) throw new Error(`Failed to resolve fs for ${curl}`);
|
|
944
|
+
return mkIdentifier(fs5);
|
|
1137
945
|
});
|
|
1138
946
|
};
|
|
1139
947
|
function buildFieldType(register, fhirSchema, path, element, logger) {
|
|
@@ -1211,7 +1019,7 @@ function extractValueSetConceptsByUrl(register, pkg, valueSetUrl, logger) {
|
|
|
1211
1019
|
function extractValueSetConcepts(register, valueSet, _logger) {
|
|
1212
1020
|
if (valueSet.expansion?.contains) {
|
|
1213
1021
|
return valueSet.expansion.contains.filter((item) => item.code !== void 0).map((item) => {
|
|
1214
|
-
|
|
1022
|
+
assert2(item.code);
|
|
1215
1023
|
return {
|
|
1216
1024
|
code: item.code,
|
|
1217
1025
|
display: item.display,
|
|
@@ -1515,6 +1323,54 @@ var treeShake = (tsIndex, treeShake2, { resolutionTree, logger }) => {
|
|
|
1515
1323
|
const shaked = collectDeps(focusedSchemas, {});
|
|
1516
1324
|
return mkTypeSchemaIndex(shaked, { resolutionTree, logger });
|
|
1517
1325
|
};
|
|
1326
|
+
var buildDependencyGraph = (schemas) => {
|
|
1327
|
+
const nameToMap = {};
|
|
1328
|
+
for (const schema of schemas) {
|
|
1329
|
+
nameToMap[schema.identifier.name] = schema;
|
|
1330
|
+
}
|
|
1331
|
+
const graph = {};
|
|
1332
|
+
for (const schema of schemas) {
|
|
1333
|
+
const name = schema.identifier.name;
|
|
1334
|
+
const base = schema.base?.name;
|
|
1335
|
+
if (!graph[name]) {
|
|
1336
|
+
graph[name] = [];
|
|
1337
|
+
}
|
|
1338
|
+
if (base && nameToMap[base]) {
|
|
1339
|
+
graph[name].push(base);
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
return graph;
|
|
1343
|
+
};
|
|
1344
|
+
var topologicalSort = (graph) => {
|
|
1345
|
+
const sorted = [];
|
|
1346
|
+
const visited = {};
|
|
1347
|
+
const temp = {};
|
|
1348
|
+
const visit = (node) => {
|
|
1349
|
+
if (temp[node]) {
|
|
1350
|
+
throw new Error(`Graph has cycles ${node}`);
|
|
1351
|
+
}
|
|
1352
|
+
if (!visited[node]) {
|
|
1353
|
+
temp[node] = true;
|
|
1354
|
+
for (const neighbor of graph[node] ?? []) {
|
|
1355
|
+
visit(neighbor);
|
|
1356
|
+
}
|
|
1357
|
+
temp[node] = false;
|
|
1358
|
+
visited[node] = true;
|
|
1359
|
+
sorted.push(node);
|
|
1360
|
+
}
|
|
1361
|
+
};
|
|
1362
|
+
for (const node in graph) {
|
|
1363
|
+
if (!visited[node]) {
|
|
1364
|
+
visit(node);
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
return sorted;
|
|
1368
|
+
};
|
|
1369
|
+
var sortAsDeclarationSequence = (schemas) => {
|
|
1370
|
+
const graph = buildDependencyGraph(schemas);
|
|
1371
|
+
const sorted = topologicalSort(graph);
|
|
1372
|
+
return sorted.map((name) => schemas.find((schema) => schema.identifier.name === name)).filter(Boolean);
|
|
1373
|
+
};
|
|
1518
1374
|
var resourceRelatives = (schemas) => {
|
|
1519
1375
|
const regularSchemas = schemas.filter((e) => isResourceTypeSchema(e) || isLogicalTypeSchema(e));
|
|
1520
1376
|
const directPairs = [];
|
|
@@ -1560,7 +1416,7 @@ var mkTypeSchemaIndex = (schemas, { resolutionTree, logger }) => {
|
|
|
1560
1416
|
append(schema);
|
|
1561
1417
|
}
|
|
1562
1418
|
const relations = resourceRelatives(schemas);
|
|
1563
|
-
const
|
|
1419
|
+
const resolve5 = (id) => index[id.url]?.[id.package];
|
|
1564
1420
|
const resolveByUrl = (pkgName, url) => {
|
|
1565
1421
|
if (resolutionTree) {
|
|
1566
1422
|
const resolution = resolutionTree[pkgName]?.[url]?.[0];
|
|
@@ -1580,7 +1436,7 @@ var mkTypeSchemaIndex = (schemas, { resolutionTree, logger }) => {
|
|
|
1580
1436
|
res.push(cur);
|
|
1581
1437
|
const base = cur.base;
|
|
1582
1438
|
if (base === void 0) break;
|
|
1583
|
-
const resolved =
|
|
1439
|
+
const resolved = resolve5(base);
|
|
1584
1440
|
if (!resolved) {
|
|
1585
1441
|
logger?.warn(
|
|
1586
1442
|
`Failed to resolve base type: ${res.map((e) => `${e.identifier.url} (${e.identifier.kind})`).join(", ")}`
|
|
@@ -1606,7 +1462,7 @@ var mkTypeSchemaIndex = (schemas, { resolutionTree, logger }) => {
|
|
|
1606
1462
|
return nonConstraintSchema;
|
|
1607
1463
|
};
|
|
1608
1464
|
const findLastSpecializationByIdentifier = (id) => {
|
|
1609
|
-
const schema =
|
|
1465
|
+
const schema = resolve5(id);
|
|
1610
1466
|
if (!schema) return id;
|
|
1611
1467
|
return findLastSpecialization(schema).identifier;
|
|
1612
1468
|
};
|
|
@@ -1668,7 +1524,7 @@ var mkTypeSchemaIndex = (schemas, { resolutionTree, logger }) => {
|
|
|
1668
1524
|
}
|
|
1669
1525
|
}
|
|
1670
1526
|
const raw = filename.endsWith(".yaml") ? YAML.stringify(tree) : JSON.stringify(tree, void 0, 2);
|
|
1671
|
-
await afs2.mkdir(
|
|
1527
|
+
await afs2.mkdir(Path5.dirname(filename), { recursive: true });
|
|
1672
1528
|
await afs2.writeFile(filename, raw);
|
|
1673
1529
|
};
|
|
1674
1530
|
return {
|
|
@@ -1678,7 +1534,7 @@ var mkTypeSchemaIndex = (schemas, { resolutionTree, logger }) => {
|
|
|
1678
1534
|
collectResources: () => schemas.filter(isResourceTypeSchema),
|
|
1679
1535
|
collectLogicalModels: () => schemas.filter(isLogicalTypeSchema),
|
|
1680
1536
|
collectProfiles: () => schemas.filter(isProfileTypeSchema),
|
|
1681
|
-
resolve:
|
|
1537
|
+
resolve: resolve5,
|
|
1682
1538
|
resolveByUrl,
|
|
1683
1539
|
resourceChildren,
|
|
1684
1540
|
tryHierarchy,
|
|
@@ -1691,46 +1547,773 @@ var mkTypeSchemaIndex = (schemas, { resolutionTree, logger }) => {
|
|
|
1691
1547
|
};
|
|
1692
1548
|
};
|
|
1693
1549
|
|
|
1694
|
-
// src/
|
|
1695
|
-
var
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
"
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
"
|
|
1707
|
-
|
|
1550
|
+
// src/api/writer-generator/python.ts
|
|
1551
|
+
var PRIMITIVE_TYPE_MAP2 = {
|
|
1552
|
+
boolean: "bool",
|
|
1553
|
+
instant: "str",
|
|
1554
|
+
time: "str",
|
|
1555
|
+
date: "str",
|
|
1556
|
+
dateTime: "str",
|
|
1557
|
+
decimal: "float",
|
|
1558
|
+
integer: "int",
|
|
1559
|
+
unsignedInt: "int",
|
|
1560
|
+
positiveInt: "PositiveInt",
|
|
1561
|
+
integer64: "int",
|
|
1562
|
+
base64Binary: "str",
|
|
1563
|
+
uri: "str",
|
|
1564
|
+
url: "str",
|
|
1565
|
+
canonical: "str",
|
|
1566
|
+
oid: "str",
|
|
1567
|
+
uuid: "str",
|
|
1568
|
+
string: "str",
|
|
1569
|
+
code: "str",
|
|
1570
|
+
markdown: "str",
|
|
1571
|
+
id: "str",
|
|
1572
|
+
xhtml: "str"
|
|
1573
|
+
};
|
|
1574
|
+
var AVAILABLE_STRING_FORMATS = {
|
|
1575
|
+
snake_case: snakeCase,
|
|
1576
|
+
PascalCase: pascalCase,
|
|
1577
|
+
camelCase
|
|
1578
|
+
};
|
|
1579
|
+
var PYTHON_KEYWORDS = /* @__PURE__ */ new Set([
|
|
1580
|
+
"False",
|
|
1581
|
+
"None",
|
|
1582
|
+
"True",
|
|
1583
|
+
"and",
|
|
1584
|
+
"as",
|
|
1585
|
+
"assert",
|
|
1586
|
+
"async",
|
|
1587
|
+
"await",
|
|
1588
|
+
"break",
|
|
1589
|
+
"class",
|
|
1590
|
+
"continue",
|
|
1591
|
+
"def",
|
|
1592
|
+
"del",
|
|
1593
|
+
"elif",
|
|
1594
|
+
"else",
|
|
1595
|
+
"except",
|
|
1596
|
+
"finally",
|
|
1597
|
+
"for",
|
|
1598
|
+
"from",
|
|
1599
|
+
"global",
|
|
1600
|
+
"if",
|
|
1601
|
+
"import",
|
|
1602
|
+
"in",
|
|
1603
|
+
"is",
|
|
1604
|
+
"lambda",
|
|
1605
|
+
"nonlocal",
|
|
1606
|
+
"not",
|
|
1607
|
+
"or",
|
|
1608
|
+
"pass",
|
|
1609
|
+
"raise",
|
|
1610
|
+
"return",
|
|
1611
|
+
"try",
|
|
1612
|
+
"while",
|
|
1613
|
+
"with",
|
|
1614
|
+
"yield",
|
|
1615
|
+
"List"
|
|
1616
|
+
]);
|
|
1617
|
+
var MAX_IMPORT_LINE_LENGTH = 100;
|
|
1618
|
+
var fixReservedWords = (name) => {
|
|
1619
|
+
return PYTHON_KEYWORDS.has(name) ? `${name}_` : name;
|
|
1620
|
+
};
|
|
1621
|
+
var injectSuperClasses = (name) => {
|
|
1622
|
+
return name === "Resource" || name === "Element" ? ["BaseModel"] : [];
|
|
1623
|
+
};
|
|
1624
|
+
var canonicalToName2 = (canonical, dropFragment = true) => {
|
|
1625
|
+
if (!canonical) return void 0;
|
|
1626
|
+
let localName = canonical.split("/").pop();
|
|
1627
|
+
if (!localName) return void 0;
|
|
1628
|
+
if (dropFragment && localName.includes("#")) {
|
|
1629
|
+
localName = localName.split("#")[0];
|
|
1630
|
+
}
|
|
1631
|
+
if (!localName) return void 0;
|
|
1632
|
+
if (/^\d/.test(localName)) {
|
|
1633
|
+
localName = `number_${localName}`;
|
|
1708
1634
|
}
|
|
1635
|
+
return snakeCase(localName);
|
|
1709
1636
|
};
|
|
1710
|
-
var
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
const
|
|
1714
|
-
if (
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
fhirSchemas.push(...await transformFhirSchema(register, fhirSchema, logger));
|
|
1637
|
+
var deriveResourceName = (id) => {
|
|
1638
|
+
if (id.kind === "nested") {
|
|
1639
|
+
const url = id.url;
|
|
1640
|
+
const path = canonicalToName2(url, false);
|
|
1641
|
+
if (!path) return "";
|
|
1642
|
+
const [resourceName, fragment] = path.split("#");
|
|
1643
|
+
const name = uppercaseFirstLetterOfEach((fragment ?? "").split(".")).join("");
|
|
1644
|
+
return pascalCase([resourceName, name].join(""));
|
|
1719
1645
|
}
|
|
1720
|
-
|
|
1721
|
-
|
|
1646
|
+
return pascalCase(id.name);
|
|
1647
|
+
};
|
|
1648
|
+
var resolvePyAssets = (fn) => {
|
|
1649
|
+
const __dirname = Path5.dirname(fileURLToPath(import.meta.url));
|
|
1650
|
+
if (__filename.endsWith("dist/index.js")) {
|
|
1651
|
+
return Path5.resolve(__dirname, "..", "assets", "api", "writer-generator", "python", fn);
|
|
1652
|
+
} else {
|
|
1653
|
+
return Path5.resolve(__dirname, "../../..", "assets", "api", "writer-generator", "python", fn);
|
|
1722
1654
|
}
|
|
1723
|
-
return fhirSchemas;
|
|
1724
1655
|
};
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1656
|
+
var Python = class extends Writer {
|
|
1657
|
+
nameFormatFunction;
|
|
1658
|
+
tsIndex;
|
|
1659
|
+
constructor(options) {
|
|
1660
|
+
super(options);
|
|
1661
|
+
this.nameFormatFunction = this.getFieldFormatFunction(options.fieldFormat);
|
|
1662
|
+
}
|
|
1663
|
+
async generate(tsIndex) {
|
|
1664
|
+
this.tsIndex = tsIndex;
|
|
1665
|
+
const groups = {
|
|
1666
|
+
groupedComplexTypes: groupByPackages(tsIndex.collectComplexTypes()),
|
|
1667
|
+
groupedResources: groupByPackages(tsIndex.collectResources())
|
|
1668
|
+
};
|
|
1669
|
+
this.generateRootPackages(groups);
|
|
1670
|
+
this.generateSDKPackages(groups);
|
|
1671
|
+
}
|
|
1672
|
+
generateRootPackages(groups) {
|
|
1673
|
+
this.generateRootInitFile(groups);
|
|
1674
|
+
fs__default.cpSync(resolvePyAssets("requirements.txt"), Path5.resolve(this.opts.outputDir, "requirements.txt"));
|
|
1675
|
+
}
|
|
1676
|
+
generateSDKPackages(groups) {
|
|
1677
|
+
this.generateComplexTypesPackages(groups.groupedComplexTypes);
|
|
1678
|
+
this.generateResourcePackages(groups);
|
|
1679
|
+
}
|
|
1680
|
+
generateComplexTypesPackages(groupedComplexTypes) {
|
|
1681
|
+
for (const [packageName, packageComplexTypes] of Object.entries(groupedComplexTypes)) {
|
|
1682
|
+
this.cd(`/${snakeCase(packageName)}`, () => {
|
|
1683
|
+
this.generateBasePy(packageComplexTypes);
|
|
1684
|
+
});
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
generateResourcePackages(groups) {
|
|
1688
|
+
for (const [packageName, packageResources] of Object.entries(groups.groupedResources)) {
|
|
1689
|
+
this.cd(`/${snakeCase(packageName)}`, () => {
|
|
1690
|
+
this.generateResourcePackageContent(
|
|
1691
|
+
packageName,
|
|
1692
|
+
packageResources,
|
|
1693
|
+
groups.groupedComplexTypes[packageName] || []
|
|
1694
|
+
);
|
|
1695
|
+
});
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
generateResourcePackageContent(packageName, packageResources, packageComplexTypes) {
|
|
1699
|
+
const pyPackageName = this.pyFhirPackageByName(packageName);
|
|
1700
|
+
this.generateResourcePackageInit(pyPackageName, packageResources, packageComplexTypes);
|
|
1701
|
+
this.generateResourceFamilies(packageResources);
|
|
1702
|
+
for (const schema of packageResources) {
|
|
1703
|
+
this.generateResourceModule(schema);
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
generateRootInitFile(groups) {
|
|
1707
|
+
this.cd("/", () => {
|
|
1708
|
+
this.cat("__init__.py", () => {
|
|
1709
|
+
this.generateDisclaimer();
|
|
1710
|
+
const pydanticModels = this.collectAndImportAllModels(groups);
|
|
1711
|
+
this.generateModelRebuilds(pydanticModels);
|
|
1712
|
+
});
|
|
1713
|
+
});
|
|
1714
|
+
}
|
|
1715
|
+
collectAndImportAllModels(groups) {
|
|
1716
|
+
const models = [];
|
|
1717
|
+
for (const packageName of Object.keys(groups.groupedResources)) {
|
|
1718
|
+
const fullPyPackageName = this.pyFhirPackageByName(packageName);
|
|
1719
|
+
models.push(...this.importComplexTypes(fullPyPackageName, groups.groupedComplexTypes[packageName]));
|
|
1720
|
+
models.push(...this.importResources(fullPyPackageName, false, groups.groupedResources[packageName]));
|
|
1721
|
+
}
|
|
1722
|
+
this.line();
|
|
1723
|
+
return models;
|
|
1724
|
+
}
|
|
1725
|
+
generateModelRebuilds(models) {
|
|
1726
|
+
for (const modelName of models.sort()) {
|
|
1727
|
+
this.line(`${modelName}.model_rebuild()`);
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
generateBasePy(packageComplexTypes) {
|
|
1731
|
+
this.cat("base.py", () => {
|
|
1732
|
+
this.generateDisclaimer();
|
|
1733
|
+
this.generateDefaultImports();
|
|
1734
|
+
this.line();
|
|
1735
|
+
this.generateComplexTypes(packageComplexTypes);
|
|
1736
|
+
this.line();
|
|
1737
|
+
});
|
|
1738
|
+
}
|
|
1739
|
+
generateComplexTypes(complexTypes) {
|
|
1740
|
+
for (const schema of sortAsDeclarationSequence(complexTypes)) {
|
|
1741
|
+
this.generateNestedTypes(schema);
|
|
1742
|
+
this.line();
|
|
1743
|
+
this.generateType(schema);
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
generateResourcePackageInit(fullPyPackageName, packageResources, packageComplexTypes) {
|
|
1747
|
+
this.cat("__init__.py", () => {
|
|
1748
|
+
this.generateDisclaimer();
|
|
1749
|
+
this.importComplexTypes(fullPyPackageName, packageComplexTypes);
|
|
1750
|
+
const allResourceNames = this.importResources(fullPyPackageName, true, packageResources);
|
|
1751
|
+
this.line();
|
|
1752
|
+
this.generateExportsDeclaration(packageComplexTypes, allResourceNames);
|
|
1753
|
+
});
|
|
1754
|
+
}
|
|
1755
|
+
importComplexTypes(fullPyPackageName, packageComplexTypes) {
|
|
1756
|
+
if (!packageComplexTypes || packageComplexTypes.length === 0) return [];
|
|
1757
|
+
const baseTypes = packageComplexTypes.map((t) => t.identifier.name).sort();
|
|
1758
|
+
this.pyImportFrom(`${fullPyPackageName}.base`, ...baseTypes);
|
|
1759
|
+
this.line();
|
|
1760
|
+
return baseTypes;
|
|
1761
|
+
}
|
|
1762
|
+
buildImportLine(remaining, maxImportLineLength) {
|
|
1763
|
+
let line = "";
|
|
1764
|
+
while (remaining.length > 0 && line.length < maxImportLineLength) {
|
|
1765
|
+
const entity = remaining.shift();
|
|
1766
|
+
if (!entity) throw new Error("Unexpected empty entity");
|
|
1767
|
+
if (line.length > 0) {
|
|
1768
|
+
line += ", ";
|
|
1769
|
+
}
|
|
1770
|
+
line += entity;
|
|
1771
|
+
}
|
|
1772
|
+
if (remaining.length > 0) {
|
|
1773
|
+
line += ", \\";
|
|
1774
|
+
}
|
|
1775
|
+
return line;
|
|
1776
|
+
}
|
|
1777
|
+
importResources(fullPyPackageName, importEmptyResources, packageResources) {
|
|
1778
|
+
if (!packageResources || packageResources.length === 0) return [];
|
|
1779
|
+
const allResourceNames = [];
|
|
1780
|
+
for (const resource of packageResources) {
|
|
1781
|
+
const names = this.importOneResource(resource, fullPyPackageName);
|
|
1782
|
+
if (!importEmptyResources && !resource.fields) continue;
|
|
1783
|
+
allResourceNames.push(...names);
|
|
1784
|
+
}
|
|
1785
|
+
return allResourceNames;
|
|
1786
|
+
}
|
|
1787
|
+
importOneResource(resource, fullPyPackageName) {
|
|
1788
|
+
const moduleName = `${fullPyPackageName}.${snakeCase(resource.identifier.name)}`;
|
|
1789
|
+
const importNames = this.collectResourceImportNames(resource);
|
|
1790
|
+
this.pyImportFrom(moduleName, ...importNames);
|
|
1791
|
+
const names = [...importNames];
|
|
1792
|
+
if (this.shouldImportResourceFamily(resource)) {
|
|
1793
|
+
const familyName = `${resource.identifier.name}Family`;
|
|
1794
|
+
this.pyImportFrom(`${fullPyPackageName}.resource_families`, familyName);
|
|
1795
|
+
}
|
|
1796
|
+
return names;
|
|
1797
|
+
}
|
|
1798
|
+
collectResourceImportNames(resource) {
|
|
1799
|
+
const names = [deriveResourceName(resource.identifier)];
|
|
1800
|
+
for (const nested of resource.nested ?? []) {
|
|
1801
|
+
const nestedName = deriveResourceName(nested.identifier);
|
|
1802
|
+
names.push(nestedName);
|
|
1803
|
+
}
|
|
1804
|
+
return names;
|
|
1805
|
+
}
|
|
1806
|
+
shouldImportResourceFamily(resource) {
|
|
1807
|
+
assert2(this.tsIndex !== void 0);
|
|
1808
|
+
return resource.identifier.kind === "resource" && this.tsIndex.resourceChildren(resource.identifier).length > 0;
|
|
1809
|
+
}
|
|
1810
|
+
generateExportsDeclaration(packageComplexTypes, allResourceNames) {
|
|
1811
|
+
this.squareBlock(["__all__", "="], () => {
|
|
1812
|
+
const allExports = [
|
|
1813
|
+
...(packageComplexTypes || []).map((t) => t.identifier.name),
|
|
1814
|
+
...allResourceNames
|
|
1815
|
+
].sort();
|
|
1816
|
+
for (const schemaName of allExports) {
|
|
1817
|
+
this.line(`'${schemaName}',`);
|
|
1818
|
+
}
|
|
1819
|
+
});
|
|
1820
|
+
}
|
|
1821
|
+
generateResourceModule(schema) {
|
|
1822
|
+
this.cat(`${snakeCase(schema.identifier.name)}.py`, () => {
|
|
1823
|
+
this.generateDisclaimer();
|
|
1824
|
+
this.generateDefaultImports();
|
|
1825
|
+
this.line();
|
|
1826
|
+
this.generateDependenciesImports(schema);
|
|
1827
|
+
this.line();
|
|
1828
|
+
this.generateNestedTypes(schema);
|
|
1829
|
+
this.line();
|
|
1830
|
+
this.generateType(schema);
|
|
1831
|
+
});
|
|
1832
|
+
}
|
|
1833
|
+
generateType(schema) {
|
|
1834
|
+
const className = deriveResourceName(schema.identifier);
|
|
1835
|
+
const superClasses = this.getSuperClasses(schema);
|
|
1836
|
+
this.line(`class ${className}(${superClasses.join(", ")}):`);
|
|
1837
|
+
this.indentBlock(() => {
|
|
1838
|
+
this.generateClassBody(schema);
|
|
1839
|
+
});
|
|
1840
|
+
this.line();
|
|
1841
|
+
}
|
|
1842
|
+
getSuperClasses(schema) {
|
|
1843
|
+
return [...schema.base ? [schema.base.name] : [], ...injectSuperClasses(schema.identifier.name)];
|
|
1844
|
+
}
|
|
1845
|
+
generateClassBody(schema) {
|
|
1846
|
+
this.generateModelConfig();
|
|
1847
|
+
if (!schema.fields) {
|
|
1848
|
+
this.line("pass");
|
|
1849
|
+
return;
|
|
1850
|
+
}
|
|
1851
|
+
if (schema.identifier.kind === "resource") {
|
|
1852
|
+
this.generateResourceTypeField(schema);
|
|
1853
|
+
}
|
|
1854
|
+
this.generateFields(schema);
|
|
1855
|
+
if (schema.identifier.kind === "resource") {
|
|
1856
|
+
this.generateResourceMethods(schema);
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
generateModelConfig() {
|
|
1860
|
+
const extraMode = this.opts.allowExtraFields ? "allow" : "forbid";
|
|
1861
|
+
this.line(`model_config = ConfigDict(validate_by_name=True, serialize_by_alias=True, extra="${extraMode}")`);
|
|
1862
|
+
}
|
|
1863
|
+
generateResourceTypeField(schema) {
|
|
1864
|
+
this.line(`${this.nameFormatFunction("resourceType")}: str = Field(`);
|
|
1865
|
+
this.indentBlock(() => {
|
|
1866
|
+
this.line(`default='${schema.identifier.name}',`);
|
|
1867
|
+
this.line(`alias='resourceType',`);
|
|
1868
|
+
this.line(`serialization_alias='resourceType',`);
|
|
1869
|
+
this.line("frozen=True,");
|
|
1870
|
+
this.line(`pattern='${schema.identifier.name}'`);
|
|
1871
|
+
});
|
|
1872
|
+
this.line(")");
|
|
1873
|
+
}
|
|
1874
|
+
generateFields(schema) {
|
|
1875
|
+
const sortedFields = Object.entries(schema.fields ?? []).sort(([a], [b]) => a.localeCompare(b));
|
|
1876
|
+
for (const [fieldName, field] of sortedFields) {
|
|
1877
|
+
if ("choices" in field && field.choices) continue;
|
|
1878
|
+
const fieldInfo = this.buildFieldInfo(fieldName, field);
|
|
1879
|
+
this.line(`${fieldInfo.name}: ${fieldInfo.type}${fieldInfo.defaultValue}`);
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
buildFieldInfo(fieldName, field) {
|
|
1883
|
+
const pyFieldName = fixReservedWords(this.nameFormatFunction(fieldName));
|
|
1884
|
+
const fieldType = this.determineFieldType(field);
|
|
1885
|
+
const defaultValue = this.getFieldDefaultValue(field, fieldName);
|
|
1886
|
+
return {
|
|
1887
|
+
name: pyFieldName,
|
|
1888
|
+
type: fieldType,
|
|
1889
|
+
defaultValue
|
|
1890
|
+
};
|
|
1891
|
+
}
|
|
1892
|
+
determineFieldType(field) {
|
|
1893
|
+
let fieldType = field ? this.getBaseFieldType(field) : "";
|
|
1894
|
+
if ("enum" in field && field.enum) {
|
|
1895
|
+
const s = field.enum.map((e) => `"${e}"`).join(", ");
|
|
1896
|
+
fieldType = `Literal[${s}]`;
|
|
1897
|
+
}
|
|
1898
|
+
if (field.array) {
|
|
1899
|
+
fieldType = `PyList[${fieldType}]`;
|
|
1900
|
+
}
|
|
1901
|
+
if (!field.required) {
|
|
1902
|
+
fieldType = `${fieldType} | None`;
|
|
1903
|
+
}
|
|
1904
|
+
return fieldType;
|
|
1905
|
+
}
|
|
1906
|
+
getBaseFieldType(field) {
|
|
1907
|
+
if ("type" in field && field.type.kind === "resource") return `${field.type.name}Family`;
|
|
1908
|
+
if ("type" in field && field.type.kind === "nested") return deriveResourceName(field.type);
|
|
1909
|
+
if ("type" in field && field.type.kind === "primitive-type")
|
|
1910
|
+
return PRIMITIVE_TYPE_MAP2[field.type.name] ?? "str";
|
|
1911
|
+
return "type" in field ? field.type.name : "";
|
|
1912
|
+
}
|
|
1913
|
+
getFieldDefaultValue(field, fieldName) {
|
|
1914
|
+
const aliasSpec = `alias="${fieldName}", serialization_alias="${fieldName}"`;
|
|
1915
|
+
if (!field.required) {
|
|
1916
|
+
return ` = Field(None, ${aliasSpec})`;
|
|
1917
|
+
}
|
|
1918
|
+
return ` = Field(${aliasSpec})`;
|
|
1919
|
+
}
|
|
1920
|
+
generateResourceMethods(schema) {
|
|
1921
|
+
const className = schema.identifier.name.toString();
|
|
1922
|
+
this.line();
|
|
1923
|
+
this.line("def to_json(self, indent: int | None = None) -> str:");
|
|
1924
|
+
this.line(" return self.model_dump_json(exclude_unset=True, exclude_none=True, indent=indent)");
|
|
1925
|
+
this.line();
|
|
1926
|
+
this.line("@classmethod");
|
|
1927
|
+
this.line(`def from_json(cls, json: str) -> ${className}:`);
|
|
1928
|
+
this.line(" return cls.model_validate_json(json)");
|
|
1929
|
+
}
|
|
1930
|
+
generateNestedTypes(schema) {
|
|
1931
|
+
if (!schema.nested) return;
|
|
1932
|
+
this.line();
|
|
1933
|
+
for (const subtype of schema.nested) {
|
|
1934
|
+
this.generateType(subtype);
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1937
|
+
generateDefaultImports() {
|
|
1938
|
+
this.pyImportFrom("__future__", "annotations");
|
|
1939
|
+
this.pyImportFrom("pydantic", "BaseModel", "ConfigDict", "Field", "PositiveInt");
|
|
1940
|
+
this.pyImportFrom("typing", "List as PyList", "Literal");
|
|
1941
|
+
}
|
|
1942
|
+
generateDependenciesImports(schema) {
|
|
1943
|
+
if (!schema.dependencies || schema.dependencies.length === 0) return;
|
|
1944
|
+
this.importComplexTypeDependencies(schema.dependencies);
|
|
1945
|
+
this.importResourceDependencies(schema.dependencies);
|
|
1946
|
+
}
|
|
1947
|
+
importComplexTypeDependencies(dependencies) {
|
|
1948
|
+
const complexTypeDeps = dependencies.filter((dep) => dep.kind === "complex-type");
|
|
1949
|
+
const depsByPackage = this.groupDependenciesByPackage(complexTypeDeps);
|
|
1950
|
+
for (const [pyPackage, names] of Object.entries(depsByPackage)) {
|
|
1951
|
+
this.pyImportFrom(pyPackage, ...names.sort());
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
importResourceDependencies(dependencies) {
|
|
1955
|
+
const resourceDeps = dependencies.filter((dep) => dep.kind === "resource");
|
|
1956
|
+
for (const dep of resourceDeps) {
|
|
1957
|
+
this.pyImportType(dep);
|
|
1958
|
+
const familyName = `${pascalCase(dep.name)}Family`;
|
|
1959
|
+
const familyPackage = `${this.pyFhirPackage(dep)}.resource_families`;
|
|
1960
|
+
this.pyImportFrom(familyPackage, familyName);
|
|
1961
|
+
}
|
|
1962
|
+
}
|
|
1963
|
+
groupDependenciesByPackage(dependencies) {
|
|
1964
|
+
const grouped = {};
|
|
1965
|
+
for (const dep of dependencies) {
|
|
1966
|
+
const pyPackage = this.pyPackage(dep);
|
|
1967
|
+
if (!grouped[pyPackage]) {
|
|
1968
|
+
grouped[pyPackage] = [];
|
|
1969
|
+
}
|
|
1970
|
+
grouped[pyPackage].push(dep.name);
|
|
1971
|
+
}
|
|
1972
|
+
return grouped;
|
|
1973
|
+
}
|
|
1974
|
+
pyImportFrom(pyPackage, ...entities) {
|
|
1975
|
+
const oneLine = `from ${pyPackage} import ${entities.join(", ")}`;
|
|
1976
|
+
if (this.shouldUseSingleLineImport(oneLine, entities)) {
|
|
1977
|
+
this.line(oneLine);
|
|
1978
|
+
} else {
|
|
1979
|
+
this.writeMultiLineImport(pyPackage, entities);
|
|
1980
|
+
}
|
|
1981
|
+
}
|
|
1982
|
+
shouldUseSingleLineImport(oneLine, entities) {
|
|
1983
|
+
return oneLine.length <= MAX_IMPORT_LINE_LENGTH || entities.length === 1;
|
|
1984
|
+
}
|
|
1985
|
+
writeMultiLineImport(pyPackage, entities) {
|
|
1986
|
+
this.line(`from ${pyPackage} import (\\`);
|
|
1987
|
+
this.indentBlock(() => {
|
|
1988
|
+
const remaining = [...entities];
|
|
1989
|
+
while (remaining.length > 0) {
|
|
1990
|
+
const line = this.buildImportLine(remaining, MAX_IMPORT_LINE_LENGTH);
|
|
1991
|
+
this.line(line);
|
|
1992
|
+
}
|
|
1993
|
+
});
|
|
1994
|
+
this.line(")");
|
|
1995
|
+
}
|
|
1996
|
+
pyImportType(identifier) {
|
|
1997
|
+
this.pyImportFrom(this.pyPackage(identifier), pascalCase(identifier.name));
|
|
1998
|
+
}
|
|
1999
|
+
generateResourceFamilies(packageResources) {
|
|
2000
|
+
assert2(this.tsIndex !== void 0);
|
|
2001
|
+
const packages = (
|
|
2002
|
+
//this.helper.getPackages(packageResources, this.opts.rootPackageName);
|
|
2003
|
+
Object.keys(groupByPackages(packageResources)).map(
|
|
2004
|
+
(pkgName) => `${this.opts.rootPackageName}.${pkgName.replaceAll(".", "_")}`
|
|
2005
|
+
)
|
|
2006
|
+
);
|
|
2007
|
+
const families = {};
|
|
2008
|
+
for (const resource of this.tsIndex.collectResources()) {
|
|
2009
|
+
const children = this.tsIndex.resourceChildren(resource.identifier).map((c) => c.name);
|
|
2010
|
+
if (children.length > 0) {
|
|
2011
|
+
const familyName = `${resource.identifier.name}Family`;
|
|
2012
|
+
families[familyName] = children;
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
const exportList = Object.keys(families);
|
|
2016
|
+
if (exportList.length === 0) return;
|
|
2017
|
+
this.buildResourceFamiliesFile(packages, families, exportList);
|
|
2018
|
+
}
|
|
2019
|
+
buildResourceFamiliesFile(packages, families, exportList) {
|
|
2020
|
+
this.cat("resource_families.py", () => {
|
|
2021
|
+
this.generateDisclaimer();
|
|
2022
|
+
this.includeResourceFamilyValidator();
|
|
2023
|
+
this.line();
|
|
2024
|
+
this.generateFamilyDefinitions(packages, families);
|
|
2025
|
+
this.generateFamilyExports(exportList);
|
|
2026
|
+
});
|
|
2027
|
+
}
|
|
2028
|
+
includeResourceFamilyValidator() {
|
|
2029
|
+
const content = fs__default.readFileSync(resolvePyAssets("resource_family_validator.py"), "utf-8");
|
|
2030
|
+
this.line(content);
|
|
2031
|
+
}
|
|
2032
|
+
generateFamilyDefinitions(packages, families) {
|
|
2033
|
+
this.line(`packages = [${packages.map((p) => `'${p}'`).join(", ")}]`);
|
|
2034
|
+
this.line();
|
|
2035
|
+
for (const [familyName, resources] of Object.entries(families)) {
|
|
2036
|
+
this.generateFamilyDefinition(familyName, resources);
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
generateFamilyDefinition(familyName, resources) {
|
|
2040
|
+
const listName = `${familyName}_resources`;
|
|
2041
|
+
this.line(`${listName} = [${resources.map((r) => `'${r}'`).join(", ")}]`);
|
|
2042
|
+
this.line();
|
|
2043
|
+
this.line(`def validate_and_downcast_${familyName}(v: Any) -> Any:`);
|
|
2044
|
+
this.line(` return validate_and_downcast(v, packages, ${listName})`);
|
|
2045
|
+
this.line();
|
|
2046
|
+
this.line(`type ${familyName} = Annotated[Any, BeforeValidator(validate_and_downcast_${familyName})]`);
|
|
2047
|
+
this.line();
|
|
2048
|
+
}
|
|
2049
|
+
generateFamilyExports(exportList) {
|
|
2050
|
+
this.line(`__all__ = [${exportList.map((e) => `'${e}'`).join(", ")}]`);
|
|
2051
|
+
}
|
|
2052
|
+
buildPyPackageName(packageName) {
|
|
2053
|
+
const parts = packageName ? [snakeCase(packageName)] : [""];
|
|
2054
|
+
return parts.join(".");
|
|
2055
|
+
}
|
|
2056
|
+
pyFhirPackage(identifier) {
|
|
2057
|
+
return this.pyFhirPackageByName(identifier.package);
|
|
2058
|
+
}
|
|
2059
|
+
pyFhirPackageByName(name) {
|
|
2060
|
+
return [this.opts.rootPackageName, this.buildPyPackageName(name)].join(".");
|
|
2061
|
+
}
|
|
2062
|
+
pyPackage(identifier) {
|
|
2063
|
+
if (identifier.kind === "complex-type") {
|
|
2064
|
+
return `${this.pyFhirPackage(identifier)}.base`;
|
|
2065
|
+
}
|
|
2066
|
+
if (identifier.kind === "resource") {
|
|
2067
|
+
return [this.pyFhirPackage(identifier), snakeCase(identifier.name)].join(".");
|
|
2068
|
+
}
|
|
2069
|
+
return this.pyFhirPackage(identifier);
|
|
2070
|
+
}
|
|
2071
|
+
getFieldFormatFunction(format) {
|
|
2072
|
+
if (!AVAILABLE_STRING_FORMATS[format]) {
|
|
2073
|
+
this.logger()?.warn(`Unknown field format '${format}'. Defaulting to SnakeCase.`);
|
|
2074
|
+
this.logger()?.warn(`Supported formats: ${Object.keys(AVAILABLE_STRING_FORMATS).join(", ")}`);
|
|
2075
|
+
return snakeCase;
|
|
2076
|
+
}
|
|
2077
|
+
return AVAILABLE_STRING_FORMATS[format];
|
|
2078
|
+
}
|
|
2079
|
+
};
|
|
2080
|
+
|
|
2081
|
+
// src/fhir-types/hl7-fhir-r4-core/CodeSystem.ts
|
|
2082
|
+
var isCodeSystem = (resource) => {
|
|
2083
|
+
return resource !== null && typeof resource === "object" && resource.resourceType === "CodeSystem";
|
|
2084
|
+
};
|
|
2085
|
+
|
|
2086
|
+
// src/fhir-types/hl7-fhir-r4-core/ValueSet.ts
|
|
2087
|
+
var isValueSet = (resource) => {
|
|
2088
|
+
return resource !== null && typeof resource === "object" && resource.resourceType === "ValueSet";
|
|
2089
|
+
};
|
|
2090
|
+
|
|
2091
|
+
// src/typeschema/register.ts
|
|
2092
|
+
var readPackageDependencies = async (manager, packageMeta) => {
|
|
2093
|
+
const packageJSON = await manager.packageJson(packageMeta.name);
|
|
2094
|
+
const dependencies = packageJSON.dependencies;
|
|
2095
|
+
if (dependencies !== void 0) {
|
|
2096
|
+
return Object.entries(dependencies).map(([name, version]) => {
|
|
2097
|
+
return { name, version };
|
|
2098
|
+
});
|
|
2099
|
+
}
|
|
2100
|
+
return [];
|
|
2101
|
+
};
|
|
2102
|
+
var mkEmptyPkgIndex = (pkg) => {
|
|
2103
|
+
return {
|
|
2104
|
+
pkg,
|
|
2105
|
+
canonicalResolution: {},
|
|
2106
|
+
fhirSchemas: {},
|
|
2107
|
+
valueSets: {}
|
|
2108
|
+
};
|
|
2109
|
+
};
|
|
2110
|
+
var mkPackageAwareResolver = async (manager, pkg, deep, acc, logger) => {
|
|
2111
|
+
const pkgId = packageMetaToFhir(pkg);
|
|
2112
|
+
logger?.info(`${" ".repeat(deep * 2)}+ ${pkgId}`);
|
|
2113
|
+
if (acc[pkgId]) return acc[pkgId];
|
|
2114
|
+
const index = mkEmptyPkgIndex(pkg);
|
|
2115
|
+
for (const resource of await manager.search({ package: pkg })) {
|
|
2116
|
+
const rawUrl = resource.url;
|
|
2117
|
+
if (!rawUrl) continue;
|
|
2118
|
+
if (!(isStructureDefinition(resource) || isValueSet(resource) || isCodeSystem(resource))) continue;
|
|
2119
|
+
const url = rawUrl;
|
|
2120
|
+
if (index.canonicalResolution[url]) logger?.dry_warn(`Duplicate canonical URL: ${url} at ${pkgId}.`);
|
|
2121
|
+
index.canonicalResolution[url] = [{ deep, pkg, pkgId, resource }];
|
|
2122
|
+
}
|
|
2123
|
+
const deps = await readPackageDependencies(manager, pkg);
|
|
2124
|
+
for (const depPkg of deps) {
|
|
2125
|
+
const { canonicalResolution } = await mkPackageAwareResolver(manager, depPkg, deep + 1, acc, logger);
|
|
2126
|
+
for (const [surl, resolutions] of Object.entries(canonicalResolution)) {
|
|
2127
|
+
const url = surl;
|
|
2128
|
+
index.canonicalResolution[url] = [...index.canonicalResolution[url] || [], ...resolutions];
|
|
2129
|
+
}
|
|
2130
|
+
}
|
|
2131
|
+
for (const resolutionOptions of Object.values(index.canonicalResolution)) {
|
|
2132
|
+
resolutionOptions.sort((a, b) => a.deep - b.deep);
|
|
2133
|
+
}
|
|
2134
|
+
acc[pkgId] = index;
|
|
2135
|
+
return index;
|
|
2136
|
+
};
|
|
2137
|
+
var packageAgnosticResolveCanonical = (resolver, url, _logger) => {
|
|
2138
|
+
const options = Object.values(resolver).flatMap((pkg) => pkg.canonicalResolution[url]);
|
|
2139
|
+
if (!options) throw new Error(`No canonical resolution found for ${url} in any package`);
|
|
2140
|
+
return options[0]?.resource;
|
|
2141
|
+
};
|
|
2142
|
+
var registerFromManager = async (manager, { logger, fallbackPackageForNameResolution, focusedPackages }) => {
|
|
2143
|
+
const packages = focusedPackages ?? await manager.packages();
|
|
2144
|
+
const resolver = {};
|
|
2145
|
+
for (const pkg of packages) {
|
|
2146
|
+
await mkPackageAwareResolver(manager, pkg, 0, resolver, logger);
|
|
2147
|
+
}
|
|
2148
|
+
for (const { pkg, canonicalResolution } of Object.values(resolver)) {
|
|
2149
|
+
const pkgId = packageMetaToFhir(pkg);
|
|
2150
|
+
if (!resolver[pkgId]) throw new Error(`Package ${pkgId} not found`);
|
|
2151
|
+
let counter = 0;
|
|
2152
|
+
logger?.info(`FHIR Schema conversion for '${packageMetaToFhir(pkg)}' begins...`);
|
|
2153
|
+
for (const [_url, options] of Object.entries(canonicalResolution)) {
|
|
2154
|
+
const resolition = options[0];
|
|
2155
|
+
if (!resolition) throw new Error(`Resource not found`);
|
|
2156
|
+
const resource = resolition.resource;
|
|
2157
|
+
const resourcePkg = resolition.pkg;
|
|
2158
|
+
if (isStructureDefinition(resource)) {
|
|
2159
|
+
const rfs = enrichFHIRSchema(
|
|
2160
|
+
fhirschema.translate(resource),
|
|
2161
|
+
resourcePkg
|
|
2162
|
+
);
|
|
2163
|
+
counter++;
|
|
2164
|
+
resolver[pkgId].fhirSchemas[rfs.url] = rfs;
|
|
2165
|
+
}
|
|
2166
|
+
if (isValueSet(resource)) {
|
|
2167
|
+
const rvs = enrichValueSet(resource, resourcePkg);
|
|
2168
|
+
resolver[pkgId].valueSets[rvs.url] = rvs;
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
logger?.info(`FHIR Schema conversion for '${packageMetaToFhir(pkg)}' completed: ${counter} successful`);
|
|
2172
|
+
}
|
|
2173
|
+
const resolveFs = (pkg, canonicalUrl) => {
|
|
2174
|
+
return resolver[packageMetaToFhir(pkg)]?.fhirSchemas[canonicalUrl] || fallbackPackageForNameResolution && resolver[packageMetaToFhir(fallbackPackageForNameResolution)]?.fhirSchemas[canonicalUrl];
|
|
2175
|
+
};
|
|
2176
|
+
const resolveVs = (pkg, canonicalUrl) => {
|
|
2177
|
+
return resolver[packageMetaToFhir(pkg)]?.valueSets[canonicalUrl] || fallbackPackageForNameResolution && resolver[packageMetaToFhir(fallbackPackageForNameResolution)]?.valueSets[canonicalUrl];
|
|
2178
|
+
};
|
|
2179
|
+
const ensureSpecializationCanonicalUrl = (name) => name.match(/^[a-zA-Z0-9]+$/) && `http://hl7.org/fhir/StructureDefinition/${name}` || name;
|
|
2180
|
+
const resolveFsGenealogy = (pkg, canonicalUrl) => {
|
|
2181
|
+
let fs5 = resolveFs(pkg, canonicalUrl);
|
|
2182
|
+
if (fs5 === void 0) throw new Error(`Failed to resolve FHIR Schema: '${canonicalUrl}'`);
|
|
2183
|
+
const genealogy = [fs5];
|
|
2184
|
+
while (fs5?.base) {
|
|
2185
|
+
const pkg2 = fs5.package_meta;
|
|
2186
|
+
const baseUrl = ensureSpecializationCanonicalUrl(fs5.base);
|
|
2187
|
+
fs5 = resolveFs(pkg2, baseUrl);
|
|
2188
|
+
if (fs5 === void 0)
|
|
2189
|
+
throw new Error(
|
|
2190
|
+
`Failed to resolve FHIR Schema base for '${canonicalUrl}'. Problem: '${baseUrl}' from '${packageMetaToFhir(pkg2)}'`
|
|
2191
|
+
);
|
|
2192
|
+
genealogy.push(fs5);
|
|
2193
|
+
}
|
|
2194
|
+
return genealogy;
|
|
2195
|
+
};
|
|
2196
|
+
const resolveFsSpecializations = (pkg, canonicalUrl) => {
|
|
2197
|
+
return resolveFsGenealogy(pkg, canonicalUrl).filter((fs5) => fs5.derivation === "specialization");
|
|
2198
|
+
};
|
|
2199
|
+
const resolveElementSnapshot = (fhirSchema, path) => {
|
|
2200
|
+
const geneology = resolveFsGenealogy(fhirSchema.package_meta, fhirSchema.url);
|
|
2201
|
+
const elemGeneology = resolveFsElementGenealogy(geneology, path);
|
|
2202
|
+
const elemSnapshot = fsElementSnapshot(elemGeneology);
|
|
2203
|
+
return elemSnapshot;
|
|
2204
|
+
};
|
|
2205
|
+
const getAllElementKeys = (elems) => {
|
|
2206
|
+
const keys = /* @__PURE__ */ new Set();
|
|
2207
|
+
for (const [key, elem] of Object.entries(elems)) {
|
|
2208
|
+
keys.add(key);
|
|
2209
|
+
for (const choiceKey of elem?.choices || []) {
|
|
2210
|
+
if (!elems[choiceKey]) {
|
|
2211
|
+
keys.add(choiceKey);
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
return Array.from(keys);
|
|
2216
|
+
};
|
|
2217
|
+
return {
|
|
2218
|
+
...manager,
|
|
2219
|
+
testAppendFs(fs5) {
|
|
2220
|
+
const rfs = enrichFHIRSchema(fs5);
|
|
2221
|
+
const pkgId = packageMetaToFhir(rfs.package_meta);
|
|
2222
|
+
if (!resolver[pkgId]) resolver[pkgId] = mkEmptyPkgIndex(rfs.package_meta);
|
|
2223
|
+
resolver[pkgId].fhirSchemas[rfs.url] = rfs;
|
|
2224
|
+
},
|
|
2225
|
+
resolveFs,
|
|
2226
|
+
resolveFsGenealogy,
|
|
2227
|
+
resolveFsSpecializations,
|
|
2228
|
+
ensureSpecializationCanonicalUrl,
|
|
2229
|
+
resolveSd: (_pkg, canonicalUrl) => {
|
|
2230
|
+
const res = packageAgnosticResolveCanonical(resolver, canonicalUrl);
|
|
2231
|
+
if (isStructureDefinition(res)) return res;
|
|
2232
|
+
return void 0;
|
|
2233
|
+
},
|
|
2234
|
+
allFs: () => Object.values(resolver).flatMap((pkgIndex) => Object.values(pkgIndex.fhirSchemas)),
|
|
2235
|
+
allVs: () => Object.values(resolver).flatMap((pkgIndex) => Object.values(pkgIndex.valueSets)),
|
|
2236
|
+
resolveVs,
|
|
2237
|
+
resolveAny: (canonicalUrl) => packageAgnosticResolveCanonical(resolver, canonicalUrl),
|
|
2238
|
+
resolveElementSnapshot,
|
|
2239
|
+
getAllElementKeys,
|
|
2240
|
+
resolver,
|
|
2241
|
+
resolutionTree: () => {
|
|
2242
|
+
const res = {};
|
|
2243
|
+
for (const [_pkgId, pkgIndex] of Object.entries(resolver)) {
|
|
2244
|
+
const pkgName = pkgIndex.pkg.name;
|
|
2245
|
+
res[pkgName] = {};
|
|
2246
|
+
for (const [surl, resolutions] of Object.entries(pkgIndex.canonicalResolution)) {
|
|
2247
|
+
const url = surl;
|
|
2248
|
+
res[pkgName][url] = [];
|
|
2249
|
+
for (const resolution of resolutions) {
|
|
2250
|
+
res[pkgName][url].push({ deep: resolution.deep, pkg: resolution.pkg });
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2253
|
+
}
|
|
2254
|
+
return res;
|
|
2255
|
+
}
|
|
2256
|
+
};
|
|
2257
|
+
};
|
|
2258
|
+
var resolveFsElementGenealogy = (genealogy, path) => {
|
|
2259
|
+
const [top, ...rest] = path;
|
|
2260
|
+
if (top === void 0) return [];
|
|
2261
|
+
return genealogy.map((fs5) => {
|
|
2262
|
+
if (!fs5.elements) return void 0;
|
|
2263
|
+
let elem = fs5.elements?.[top];
|
|
2264
|
+
for (const k of rest) {
|
|
2265
|
+
elem = elem?.elements?.[k];
|
|
2266
|
+
}
|
|
2267
|
+
return elem;
|
|
2268
|
+
}).filter((elem) => elem !== void 0);
|
|
2269
|
+
};
|
|
2270
|
+
function fsElementSnapshot(genealogy) {
|
|
2271
|
+
const revGenealogy = genealogy.reverse();
|
|
2272
|
+
const snapshot = Object.assign({}, ...revGenealogy);
|
|
2273
|
+
snapshot.elements = void 0;
|
|
2274
|
+
return snapshot;
|
|
2275
|
+
}
|
|
2276
|
+
|
|
2277
|
+
// src/typeschema/index.ts
|
|
2278
|
+
var codeableReferenceInR4 = "Use CodeableReference which is not provided by FHIR R4.";
|
|
2279
|
+
var availabilityInR4 = "Use Availability which is not provided by FHIR R4.";
|
|
2280
|
+
var skipMe = {
|
|
2281
|
+
"hl7.fhir.uv.extensions.r4#1.0.0": {
|
|
2282
|
+
"http://hl7.org/fhir/StructureDefinition/extended-contact-availability": availabilityInR4,
|
|
2283
|
+
"http://hl7.org/fhir/StructureDefinition/immunization-procedure": codeableReferenceInR4,
|
|
2284
|
+
"http://hl7.org/fhir/StructureDefinition/specimen-additive": codeableReferenceInR4,
|
|
2285
|
+
"http://hl7.org/fhir/StructureDefinition/workflow-barrier": codeableReferenceInR4,
|
|
2286
|
+
"http://hl7.org/fhir/StructureDefinition/workflow-protectiveFactor": codeableReferenceInR4,
|
|
2287
|
+
"http://hl7.org/fhir/StructureDefinition/workflow-reason": codeableReferenceInR4
|
|
2288
|
+
},
|
|
2289
|
+
"hl7.fhir.r5.core#5.0.0": {
|
|
2290
|
+
"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."
|
|
2291
|
+
}
|
|
2292
|
+
};
|
|
2293
|
+
var generateTypeSchemas = async (register, logger) => {
|
|
2294
|
+
const fhirSchemas = [];
|
|
2295
|
+
for (const fhirSchema of register.allFs()) {
|
|
2296
|
+
const pkgId = packageMetaToFhir(fhirSchema.package_meta);
|
|
2297
|
+
if (skipMe[pkgId]?.[fhirSchema.url]) {
|
|
2298
|
+
logger?.dry_warn(`Skip ${fhirSchema.url} from ${pkgId}. Reason: ${skipMe[pkgId]?.[fhirSchema.url]}`);
|
|
2299
|
+
continue;
|
|
2300
|
+
}
|
|
2301
|
+
fhirSchemas.push(...await transformFhirSchema(register, fhirSchema, logger));
|
|
2302
|
+
}
|
|
2303
|
+
for (const vsSchema of register.allVs()) {
|
|
2304
|
+
fhirSchemas.push(await transformValueSet(register, vsSchema));
|
|
2305
|
+
}
|
|
2306
|
+
return fhirSchemas;
|
|
2307
|
+
};
|
|
2308
|
+
|
|
2309
|
+
// src/api/writer-generator/typescript.ts
|
|
2310
|
+
var primitiveType2tsType = {
|
|
2311
|
+
boolean: "boolean",
|
|
2312
|
+
instant: "string",
|
|
2313
|
+
time: "string",
|
|
2314
|
+
date: "string",
|
|
2315
|
+
dateTime: "string",
|
|
2316
|
+
decimal: "number",
|
|
1734
2317
|
integer: "number",
|
|
1735
2318
|
unsignedInt: "number",
|
|
1736
2319
|
positiveInt: "number",
|
|
@@ -1765,7 +2348,7 @@ var tsModuleFileName = (id) => {
|
|
|
1765
2348
|
var tsModulePath = (id) => {
|
|
1766
2349
|
return `${tsFhirPackageDir(id.package)}/${tsModuleName(id)}`;
|
|
1767
2350
|
};
|
|
1768
|
-
var
|
|
2351
|
+
var canonicalToName3 = (canonical, dropFragment = true) => {
|
|
1769
2352
|
if (!canonical) return void 0;
|
|
1770
2353
|
const localName = extractNameFromCanonical(canonical, dropFragment);
|
|
1771
2354
|
if (!localName) return void 0;
|
|
@@ -1774,7 +2357,7 @@ var canonicalToName2 = (canonical, dropFragment = true) => {
|
|
|
1774
2357
|
var tsResourceName = (id) => {
|
|
1775
2358
|
if (id.kind === "nested") {
|
|
1776
2359
|
const url = id.url;
|
|
1777
|
-
const path =
|
|
2360
|
+
const path = canonicalToName3(url, false);
|
|
1778
2361
|
if (!path) return "";
|
|
1779
2362
|
const [resourceName, fragment] = path.split("#");
|
|
1780
2363
|
const name = uppercaseFirstLetterOfEach((fragment ?? "").split(".")).join("");
|
|
@@ -1840,7 +2423,7 @@ var TypeScript = class extends Writer {
|
|
|
1840
2423
|
});
|
|
1841
2424
|
} else if (isNestedIdentifier(dep)) {
|
|
1842
2425
|
const ndep = { ...dep };
|
|
1843
|
-
ndep.name =
|
|
2426
|
+
ndep.name = canonicalToName3(dep.url);
|
|
1844
2427
|
imports.push({
|
|
1845
2428
|
tsPackage: `../${tsModulePath(ndep)}`,
|
|
1846
2429
|
name: tsResourceName(dep),
|
|
@@ -1893,7 +2476,7 @@ var TypeScript = class extends Writer {
|
|
|
1893
2476
|
name = tsResourceName(schema.identifier);
|
|
1894
2477
|
}
|
|
1895
2478
|
let extendsClause;
|
|
1896
|
-
if (schema.base) extendsClause = `extends ${
|
|
2479
|
+
if (schema.base) extendsClause = `extends ${canonicalToName3(schema.base.url)}`;
|
|
1897
2480
|
this.debugComment(schema.identifier);
|
|
1898
2481
|
if (!schema.fields && !extendsClause && !isResourceTypeSchema(schema)) {
|
|
1899
2482
|
this.lineSM(`export type ${name} = object`);
|
|
@@ -2200,7 +2783,7 @@ var writeTypeSchemasToSeparateFiles = async (typeSchemas, outputDir, logger) =>
|
|
|
2200
2783
|
const pkgPath = normalizeFileName(packageMetaToFhir(pkg));
|
|
2201
2784
|
const name = normalizeFileName(`${ts.identifier.name}(${extractNameFromCanonical(ts.identifier.url)})`);
|
|
2202
2785
|
const json = JSON.stringify(ts, null, 2);
|
|
2203
|
-
const baseName =
|
|
2786
|
+
const baseName = Path5.join(outputDir, pkgPath, name);
|
|
2204
2787
|
if (!files[baseName]) files[baseName] = [];
|
|
2205
2788
|
if (!files[baseName]?.some((e) => e === json)) {
|
|
2206
2789
|
files[baseName].push(json);
|
|
@@ -2215,7 +2798,7 @@ var writeTypeSchemasToSeparateFiles = async (typeSchemas, outputDir, logger) =>
|
|
|
2215
2798
|
} else {
|
|
2216
2799
|
fullName = `${baseName}-${index}.typeschema.json`;
|
|
2217
2800
|
}
|
|
2218
|
-
await afs2.mkdir(
|
|
2801
|
+
await afs2.mkdir(Path5.dirname(fullName), { recursive: true });
|
|
2219
2802
|
await afs2.writeFile(fullName, json);
|
|
2220
2803
|
})
|
|
2221
2804
|
);
|
|
@@ -2223,7 +2806,7 @@ var writeTypeSchemasToSeparateFiles = async (typeSchemas, outputDir, logger) =>
|
|
|
2223
2806
|
};
|
|
2224
2807
|
var writeTypeSchemasToSingleFile = async (typeSchemas, outputFile, logger) => {
|
|
2225
2808
|
logger.info(`Writing TypeSchema files to: ${outputFile}`);
|
|
2226
|
-
await afs2.mkdir(
|
|
2809
|
+
await afs2.mkdir(Path5.dirname(outputFile), { recursive: true });
|
|
2227
2810
|
logger.info(`Writing TypeSchemas to one file ${outputFile}...`);
|
|
2228
2811
|
for (const ts of typeSchemas) {
|
|
2229
2812
|
const json = JSON.stringify(ts, null, 2);
|
|
@@ -2234,7 +2817,7 @@ var writeTypeSchemasToSingleFile = async (typeSchemas, outputFile, logger) => {
|
|
|
2234
2817
|
var tryWriteTypeSchema = async (typeSchemas, opts, logger) => {
|
|
2235
2818
|
if (!opts.typeSchemaOutputDir) return;
|
|
2236
2819
|
try {
|
|
2237
|
-
if (
|
|
2820
|
+
if (Path5.extname(opts.typeSchemaOutputDir) === ".ndjson") {
|
|
2238
2821
|
await writeTypeSchemasToSingleFile(typeSchemas, opts.typeSchemaOutputDir, logger);
|
|
2239
2822
|
} else {
|
|
2240
2823
|
await writeTypeSchemasToSeparateFiles(typeSchemas, opts.typeSchemaOutputDir, logger);
|
|
@@ -2297,7 +2880,7 @@ var APIBuilder = class {
|
|
|
2297
2880
|
return this;
|
|
2298
2881
|
}
|
|
2299
2882
|
localTgzPackage(archivePath) {
|
|
2300
|
-
this.localTgzArchives.push(
|
|
2883
|
+
this.localTgzArchives.push(Path5.resolve(archivePath));
|
|
2301
2884
|
return this;
|
|
2302
2885
|
}
|
|
2303
2886
|
fromSchemas(schemas) {
|
|
@@ -2308,7 +2891,7 @@ var APIBuilder = class {
|
|
|
2308
2891
|
typescript(userOpts) {
|
|
2309
2892
|
const defaultWriterOpts = {
|
|
2310
2893
|
logger: this.logger,
|
|
2311
|
-
outputDir:
|
|
2894
|
+
outputDir: Path5.join(this.options.outputDir, "/types"),
|
|
2312
2895
|
tabSize: 4,
|
|
2313
2896
|
withDebugComment: false,
|
|
2314
2897
|
commentLinePrefix: "//",
|
|
@@ -2328,9 +2911,31 @@ var APIBuilder = class {
|
|
|
2328
2911
|
this.logger.debug(`Configured TypeScript generator (${JSON.stringify(opts, void 0, 2)})`);
|
|
2329
2912
|
return this;
|
|
2330
2913
|
}
|
|
2914
|
+
python(userOptions) {
|
|
2915
|
+
const defaultWriterOpts = {
|
|
2916
|
+
logger: this.logger,
|
|
2917
|
+
outputDir: this.options.outputDir,
|
|
2918
|
+
tabSize: 4,
|
|
2919
|
+
withDebugComment: false,
|
|
2920
|
+
commentLinePrefix: "#"
|
|
2921
|
+
};
|
|
2922
|
+
const defaultPyOpts = {
|
|
2923
|
+
...defaultWriterOpts,
|
|
2924
|
+
rootPackageName: "fhir_types",
|
|
2925
|
+
fieldFormat: "snake_case"
|
|
2926
|
+
};
|
|
2927
|
+
const opts = {
|
|
2928
|
+
...defaultPyOpts,
|
|
2929
|
+
...Object.fromEntries(Object.entries(userOptions).filter(([_, v]) => v !== void 0))
|
|
2930
|
+
};
|
|
2931
|
+
const generator = new Python(opts);
|
|
2932
|
+
this.generators.set("python", generator);
|
|
2933
|
+
this.logger.debug(`Configured python generator`);
|
|
2934
|
+
return this;
|
|
2935
|
+
}
|
|
2331
2936
|
csharp(namespace, staticSourceDir) {
|
|
2332
2937
|
const generator = new CSharp({
|
|
2333
|
-
outputDir:
|
|
2938
|
+
outputDir: Path5.join(this.options.outputDir, "/types"),
|
|
2334
2939
|
staticSourceDir: staticSourceDir ?? void 0,
|
|
2335
2940
|
targetNamespace: namespace,
|
|
2336
2941
|
logger: new CodegenLogger({
|