@atomic-ehr/codegen 0.0.4-canary.20251217144707.ef99bf1 → 0.0.4-canary.20251218111944.d18e50d

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import { readFile } from 'fs/promises';
6
6
  import * as Path5 from 'path';
7
7
  import Path5__default, { resolve } from 'path';
8
8
  import { CanonicalManager } from '@atomic-ehr/fhir-canonical-manager';
9
- import assert2 from 'assert';
9
+ import assert3 from 'assert';
10
10
  import { fileURLToPath } from 'url';
11
11
  import * as YAML from 'yaml';
12
12
  import * as fhirschema from '@atomic-ehr/fhirschema';
@@ -410,6 +410,10 @@ var isChoiceDeclarationField = (field) => {
410
410
  if (!field) return false;
411
411
  return field.choices !== void 0;
412
412
  };
413
+ var isChoiceInstanceField = (field) => {
414
+ if (!field) return false;
415
+ return field.choiceOf !== void 0;
416
+ };
413
417
  var enrichValueSet = (vs, packageMeta) => {
414
418
  if (!vs.url) throw new Error("ValueSet must have a URL");
415
419
  if (!vs.name) throw new Error("ValueSet must have a name");
@@ -739,676 +743,110 @@ var CSharp = class extends Writer {
739
743
  fs__default.copyFileSync(sourceFile, destFile);
740
744
  }
741
745
  };
742
-
743
- // src/typeschema/core/identifier.ts
744
- function dropVersionFromUrl(url) {
745
- const baseUrl = url.split("|")[0];
746
- return baseUrl ? baseUrl : url;
747
- }
748
- function getVersionFromUrl(url) {
749
- const version = url.split("|")[1];
750
- return version;
751
- }
752
- function determineKind(fhirSchema) {
753
- if (fhirSchema.derivation === "constraint") return "profile";
754
- if (fhirSchema.kind === "primitive-type") return "primitive-type";
755
- if (fhirSchema.kind === "complex-type") return "complex-type";
756
- if (fhirSchema.kind === "resource") return "resource";
757
- if (fhirSchema.kind === "logical") return "logical";
758
- return "resource";
759
- }
760
- function mkIdentifier(fhirSchema) {
761
- return {
762
- kind: determineKind(fhirSchema),
763
- package: fhirSchema.package_meta.name,
764
- version: fhirSchema.package_meta.version,
765
- name: fhirSchema.name,
766
- url: fhirSchema.url
767
- };
768
- }
769
- var getValueSetName = (url) => {
770
- const urlParts = url.split("/");
771
- const lastSegment = urlParts[urlParts.length - 1];
772
- if (lastSegment && lastSegment.length > 0) {
773
- return lastSegment.split(/[-_]/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
746
+ var groupByPackages = (typeSchemas) => {
747
+ const grouped = {};
748
+ for (const ts of typeSchemas) {
749
+ const pkgName = ts.identifier.package;
750
+ if (!grouped[pkgName]) grouped[pkgName] = [];
751
+ grouped[pkgName].push(ts);
774
752
  }
775
- return url;
776
- };
777
- function mkValueSetIdentifierByUrl(register, pkg, fullValueSetUrl) {
778
- const valueSetUrl = dropVersionFromUrl(fullValueSetUrl);
779
- const valueSetNameFallback = getValueSetName(valueSetUrl);
780
- const valuesSetFallback = {
781
- package_meta: {
782
- name: "missing_valuesets",
783
- version: getVersionFromUrl(valueSetUrl) || "0.0.0"
784
- },
785
- id: fullValueSetUrl};
786
- const valueSet = register.resolveVs(pkg, valueSetUrl) || valuesSetFallback;
787
- const valueSetName = valueSet?.id && !/^[a-zA-Z0-9_-]{20,}$/.test(valueSet.id) ? valueSet.id : valueSetNameFallback;
788
- return {
789
- kind: "value-set",
790
- package: valueSet.package_meta.name,
791
- version: valueSet.package_meta.version,
792
- name: valueSetName,
793
- url: valueSetUrl
794
- };
795
- }
796
- function mkBindingIdentifier(fhirSchema, path, bindingName) {
797
- const pathStr = path.join(".");
798
- const [pkg, name, url] = bindingName ? [{ name: "shared", version: "1.0.0" }, bindingName, `urn:fhir:binding:${bindingName}`] : [fhirSchema.package_meta, `${fhirSchema.name}.${pathStr}_binding`, `${fhirSchema.url}#${pathStr}_binding`];
799
- return {
800
- kind: "binding",
801
- package: pkg.name,
802
- version: pkg.version,
803
- name,
804
- url
805
- };
806
- }
807
-
808
- // src/typeschema/core/nested-types.ts
809
- function mkNestedIdentifier(register, fhirSchema, path, logger) {
810
- const nestedTypeOrigins = {};
811
- if (fhirSchema.derivation === "constraint") {
812
- const specializations = register.resolveFsSpecializations(fhirSchema.package_meta, fhirSchema.url);
813
- const nestedTypeGenealogy = specializations.map((fs5) => mkNestedTypes(register, fs5, logger)).filter((e) => e !== void 0).flat();
814
- for (const nt of nestedTypeGenealogy.reverse()) {
815
- nestedTypeOrigins[nt.identifier.name] = nt.identifier.url;
753
+ for (const [packageName, typeSchemas2] of Object.entries(grouped)) {
754
+ const dict = {};
755
+ for (const ts of typeSchemas2) {
756
+ dict[JSON.stringify(ts.identifier)] = ts;
816
757
  }
758
+ const tmp = Object.values(dict);
759
+ tmp.sort((a, b) => a.identifier.name.localeCompare(b.identifier.name));
760
+ grouped[packageName] = tmp;
817
761
  }
818
- const nestedName = path.join(".");
819
- const url = nestedTypeOrigins[nestedName] ?? `${fhirSchema.url}#${nestedName}`;
820
- return {
821
- kind: "nested",
822
- package: fhirSchema.package_meta.name,
823
- version: fhirSchema.package_meta.version,
824
- name: nestedName,
825
- url
826
- };
827
- }
828
- function collectNestedElements(fhirSchema, parentPath, elements) {
829
- const nested = [];
830
- for (const [key, element] of Object.entries(elements)) {
831
- const path = [...parentPath, key];
832
- if (isNestedElement(element)) {
833
- nested.push([path, element]);
762
+ return grouped;
763
+ };
764
+ var buildDependencyGraph = (schemas) => {
765
+ const nameToMap = {};
766
+ for (const schema of schemas) {
767
+ nameToMap[schema.identifier.name] = schema;
768
+ }
769
+ const graph = {};
770
+ for (const schema of schemas) {
771
+ const name = schema.identifier.name;
772
+ const base = schema.base?.name;
773
+ if (!graph[name]) {
774
+ graph[name] = [];
834
775
  }
835
- if (element.elements) {
836
- nested.push(...collectNestedElements(fhirSchema, path, element.elements));
776
+ if (base && nameToMap[base]) {
777
+ graph[name].push(base);
837
778
  }
838
779
  }
839
- return nested;
840
- }
841
- function transformNestedElements(register, fhirSchema, parentPath, elements, logger) {
842
- const fields = {};
843
- for (const [key, _element] of Object.entries(elements)) {
844
- const path = [...parentPath, key];
845
- const elemSnapshot = register.resolveElementSnapshot(fhirSchema, path);
846
- if (isNestedElement(elemSnapshot)) {
847
- fields[key] = mkNestedField(register, fhirSchema, path, elemSnapshot, logger);
848
- } else {
849
- fields[key] = mkField(register, fhirSchema, path, elemSnapshot, logger);
780
+ return graph;
781
+ };
782
+ var topologicalSort = (graph) => {
783
+ const sorted = [];
784
+ const visited = {};
785
+ const temp = {};
786
+ const visit = (node) => {
787
+ if (temp[node]) {
788
+ throw new Error(`Graph has cycles ${node}`);
789
+ }
790
+ if (!visited[node]) {
791
+ temp[node] = true;
792
+ for (const neighbor of graph[node] ?? []) {
793
+ visit(neighbor);
794
+ }
795
+ temp[node] = false;
796
+ visited[node] = true;
797
+ sorted.push(node);
798
+ }
799
+ };
800
+ for (const node in graph) {
801
+ if (!visited[node]) {
802
+ visit(node);
850
803
  }
851
804
  }
852
- return fields;
853
- }
854
- function mkNestedTypes(register, fhirSchema, logger) {
855
- if (!fhirSchema.elements) return void 0;
856
- const nested = collectNestedElements(fhirSchema, [], fhirSchema.elements).filter(
857
- ([_, element]) => element.elements && Object.keys(element.elements).length > 0
858
- );
859
- const nestedTypes = [];
860
- for (const [path, element] of nested) {
861
- const identifier = mkNestedIdentifier(register, fhirSchema, path, logger);
862
- let baseName;
863
- if (element.type === "BackboneElement" || !element.type) {
864
- baseName = "BackboneElement";
865
- } else {
866
- baseName = element.type;
805
+ return sorted;
806
+ };
807
+ var sortAsDeclarationSequence = (schemas) => {
808
+ const graph = buildDependencyGraph(schemas);
809
+ const sorted = topologicalSort(graph);
810
+ return sorted.map((name) => schemas.find((schema) => schema.identifier.name === name)).filter(Boolean);
811
+ };
812
+ var resourceRelatives = (schemas) => {
813
+ const regularSchemas = schemas.filter((e) => isResourceTypeSchema(e) || isLogicalTypeSchema(e));
814
+ const directPairs = [];
815
+ for (const schema of regularSchemas) {
816
+ if (schema.base) {
817
+ directPairs.push({ parent: schema.base, child: schema.identifier });
867
818
  }
868
- const baseUrl = register.ensureSpecializationCanonicalUrl(baseName);
869
- const baseFs = register.resolveFs(fhirSchema.package_meta, baseUrl);
870
- if (!baseFs) throw new Error(`Could not resolve base type ${baseName}`);
871
- const base = {
872
- kind: "complex-type",
873
- package: baseFs.package_meta.name,
874
- version: baseFs.package_meta.version,
875
- name: baseName,
876
- url: baseUrl
877
- };
878
- const fields = transformNestedElements(register, fhirSchema, path, element.elements ?? {}, logger);
879
- const nestedType = {
880
- identifier,
881
- base,
882
- fields
883
- };
884
- nestedTypes.push(nestedType);
885
819
  }
886
- nestedTypes.sort((a, b) => a.identifier.url.localeCompare(b.identifier.url));
887
- return nestedTypes.length === 0 ? void 0 : nestedTypes;
888
- }
889
- function extractNestedDependencies(nestedTypes) {
890
- const deps = [];
891
- for (const nested of nestedTypes) {
892
- if (nested.base) {
893
- deps.push(nested.base);
820
+ const allPairs = [...directPairs];
821
+ const findTransitiveRelatives = (parentRef) => {
822
+ const directChildren = directPairs.filter((pair) => pair.parent.name === parentRef.name).map((pair) => pair.child);
823
+ const transitiveChildren = [];
824
+ for (const child of directChildren) {
825
+ transitiveChildren.push(...findTransitiveRelatives(child));
894
826
  }
895
- for (const field of Object.values(nested.fields || {})) {
896
- if ("type" in field && field.type) {
897
- deps.push(field.type);
898
- }
899
- if ("binding" in field && field.binding) {
900
- deps.push(field.binding);
827
+ return [...directChildren, ...transitiveChildren];
828
+ };
829
+ for (const pair of directPairs) {
830
+ const transitiveChildren = findTransitiveRelatives(pair.child);
831
+ for (const transitiveChild of transitiveChildren) {
832
+ if (!directPairs.some((dp) => dp.parent.name === pair.parent.name && dp.child.name === transitiveChild.name)) {
833
+ allPairs.push({ parent: pair.parent, child: transitiveChild });
901
834
  }
902
835
  }
903
836
  }
904
- return deps;
905
- }
906
-
907
- // src/typeschema/core/field-builder.ts
908
- function isRequired(register, fhirSchema, path) {
909
- const fieldName = path[path.length - 1];
910
- if (!fieldName) throw new Error(`Internal error: fieldName is missing for path ${path.join("/")}`);
911
- const parentPath = path.slice(0, -1);
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;
916
- for (const k of parentPath) {
917
- elem = elem?.elements?.[k];
918
- }
919
- return elem?.required || [];
920
- });
921
- return new Set(requires).has(fieldName);
922
- }
923
- function isExcluded(register, fhirSchema, path) {
924
- const fieldName = path[path.length - 1];
925
- if (!fieldName) throw new Error(`Internal error: fieldName is missing for path ${path.join("/")}`);
926
- const parentPath = path.slice(0, -1);
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;
931
- for (const k of parentPath) {
932
- elem = elem?.elements?.[k];
933
- }
934
- return elem?.excluded || [];
935
- });
936
- return new Set(requires).has(fieldName);
937
- }
938
- var buildReferences = (register, fhirSchema, element) => {
939
- if (!element.refers) return void 0;
940
- return element.refers.map((ref) => {
941
- const curl = register.ensureSpecializationCanonicalUrl(ref);
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);
945
- });
946
- };
947
- function buildFieldType(register, fhirSchema, path, element, logger) {
948
- if (element.elementReference) {
949
- const refPath = element.elementReference.slice(1).filter((_, i) => i % 2 === 1);
950
- return mkNestedIdentifier(register, fhirSchema, refPath, logger);
951
- } else if (element.type) {
952
- const url = register.ensureSpecializationCanonicalUrl(element.type);
953
- const fieldFs = register.resolveFs(fhirSchema.package_meta, url);
954
- if (!fieldFs)
955
- throw new Error(
956
- `Could not resolve field type: '${element.type}' (from '${fhirSchema.url}' in '${packageMetaToFhir(fhirSchema.package_meta)}')`
957
- );
958
- return mkIdentifier(fieldFs);
959
- } else if (element.choices) {
960
- return void 0;
961
- } else if (fhirSchema.derivation === "constraint") {
962
- return void 0;
963
- } else {
964
- logger?.error(
965
- `Can't recognize element type '${fhirSchema.url}' (${fhirSchema.derivation}) at '${path.join(".")}': ${JSON.stringify(element, void 0, 2)}`
966
- );
967
- throw new Error(`Unrecognized element type`);
968
- }
969
- }
970
- var mkField = (register, fhirSchema, path, element, logger) => {
971
- let binding;
972
- let enumValues;
973
- if (element.binding) {
974
- binding = mkBindingIdentifier(fhirSchema, path, element.binding.bindingName);
975
- if (element.binding.strength === "required" && element.type === "code") {
976
- enumValues = buildEnum(register, fhirSchema, element, logger);
977
- }
978
- }
979
- const fieldType = buildFieldType(register, fhirSchema, path, element, logger);
980
- if (!fieldType)
981
- logger?.warn(`Field type not found for '${fhirSchema.url}#${path.join(".")}' (${fhirSchema.derivation})`);
982
- return {
983
- type: fieldType,
984
- required: isRequired(register, fhirSchema, path),
985
- excluded: isExcluded(register, fhirSchema, path),
986
- reference: buildReferences(register, fhirSchema, element),
987
- array: element.array || false,
988
- min: element.min,
989
- max: element.max,
990
- choices: element.choices,
991
- choiceOf: element.choiceOf,
992
- binding,
993
- enum: enumValues
994
- };
995
- };
996
- function isNestedElement(element) {
997
- const isBackbone = element.type === "BackboneElement";
998
- const isElement = element.type === "Element" && element.elements !== void 0 && Object.keys(element.elements).length > 0;
999
- const elementsWithoutType = element.type === void 0 && element.choiceOf === void 0 && element.elements !== void 0 && Object.keys(element.elements).length > 0;
1000
- return isBackbone || isElement || elementsWithoutType;
1001
- }
1002
- function mkNestedField(register, fhirSchema, path, element, logger) {
1003
- const nestedIdentifier = mkNestedIdentifier(register, fhirSchema, path, logger);
1004
- return {
1005
- type: nestedIdentifier,
1006
- array: element.array || false,
1007
- required: isRequired(register, fhirSchema, path),
1008
- excluded: isExcluded(register, fhirSchema, path)
1009
- };
1010
- }
1011
-
1012
- // src/typeschema/core/binding.ts
1013
- function extractValueSetConceptsByUrl(register, pkg, valueSetUrl, logger) {
1014
- const cleanUrl = dropVersionFromUrl(valueSetUrl) || valueSetUrl;
1015
- const valueSet = register.resolveVs(pkg, cleanUrl);
1016
- if (!valueSet) return void 0;
1017
- return extractValueSetConcepts(register, valueSet);
1018
- }
1019
- function extractValueSetConcepts(register, valueSet, _logger) {
1020
- if (valueSet.expansion?.contains) {
1021
- return valueSet.expansion.contains.filter((item) => item.code !== void 0).map((item) => {
1022
- assert2(item.code);
1023
- return {
1024
- code: item.code,
1025
- display: item.display,
1026
- system: item.system
1027
- };
1028
- });
1029
- }
1030
- const concepts = [];
1031
- if (valueSet.compose?.include) {
1032
- for (const include of valueSet.compose.include) {
1033
- if (include.concept) {
1034
- for (const concept of include.concept) {
1035
- concepts.push({
1036
- system: include.system,
1037
- code: concept.code,
1038
- display: concept.display
1039
- });
1040
- }
1041
- } else if (include.system && !include.filter) {
1042
- try {
1043
- const codeSystem = register.resolveAny(include.system);
1044
- if (codeSystem?.concept) {
1045
- const extractConcepts = (conceptList, system) => {
1046
- for (const concept of conceptList) {
1047
- concepts.push({
1048
- system,
1049
- code: concept.code,
1050
- display: concept.display
1051
- });
1052
- if (concept.concept) {
1053
- extractConcepts(concept.concept, system);
1054
- }
1055
- }
1056
- };
1057
- extractConcepts(codeSystem.concept, include.system);
1058
- }
1059
- } catch {
1060
- }
1061
- }
1062
- }
1063
- }
1064
- return concepts.length > 0 ? concepts : void 0;
1065
- }
1066
- var MAX_ENUM_LENGTH = 100;
1067
- function buildEnum(register, fhirSchema, element, logger) {
1068
- if (!element.binding) return void 0;
1069
- const strength = element.binding.strength;
1070
- const valueSetUrl = element.binding.valueSet;
1071
- if (!valueSetUrl) return void 0;
1072
- const shouldGenerateEnum = strength === "required" || strength === "extensible" && (element.type === "code" || element.type === "Coding") || strength === "preferred" && (element.type === "code" || element.type === "Coding");
1073
- if (!shouldGenerateEnum) return void 0;
1074
- const concepts = extractValueSetConceptsByUrl(register, fhirSchema.package_meta, valueSetUrl);
1075
- if (!concepts || concepts.length === 0) return void 0;
1076
- const codes = concepts.map((c) => c.code).filter((code) => code && typeof code === "string" && code.trim().length > 0);
1077
- if (codes.length > MAX_ENUM_LENGTH) {
1078
- logger?.dry_warn(
1079
- `Value set ${valueSetUrl} has ${codes.length} which is more than ${MAX_ENUM_LENGTH} codes, which may cause issues with code generation.`
1080
- );
1081
- return void 0;
1082
- }
1083
- return codes.length > 0 ? codes : void 0;
1084
- }
1085
- function generateBindingSchema(register, fhirSchema, path, element, logger) {
1086
- if (!element.binding?.valueSet) return void 0;
1087
- const identifier = mkBindingIdentifier(fhirSchema, path, element.binding.bindingName);
1088
- const fieldType = buildFieldType(register, fhirSchema, path, element, logger);
1089
- const valueSetIdentifier = mkValueSetIdentifierByUrl(
1090
- register,
1091
- fhirSchema.package_meta,
1092
- element.binding.valueSet
1093
- );
1094
- const dependencies = [];
1095
- if (fieldType) {
1096
- dependencies.push(fieldType);
1097
- }
1098
- dependencies.push(valueSetIdentifier);
1099
- const enumValues = buildEnum(register, fhirSchema, element, logger);
1100
- return {
1101
- identifier,
1102
- type: fieldType,
1103
- valueset: valueSetIdentifier,
1104
- strength: element.binding.strength,
1105
- enum: enumValues,
1106
- dependencies
1107
- };
1108
- }
1109
- function collectBindingSchemas(register, fhirSchema, logger) {
1110
- const processedPaths = /* @__PURE__ */ new Set();
1111
- if (!fhirSchema.elements) return [];
1112
- const bindings = [];
1113
- function collectBindings(elements, parentPath) {
1114
- for (const [key, element] of Object.entries(elements)) {
1115
- const path = [...parentPath, key];
1116
- const pathKey = path.join(".");
1117
- if (processedPaths.has(pathKey)) continue;
1118
- processedPaths.add(pathKey);
1119
- if (element.binding) {
1120
- const binding = generateBindingSchema(register, fhirSchema, path, element, logger);
1121
- if (binding) {
1122
- bindings.push(binding);
1123
- }
1124
- }
1125
- if (element.elements) {
1126
- collectBindings(element.elements, path);
1127
- }
1128
- }
1129
- }
1130
- collectBindings(fhirSchema.elements, []);
1131
- bindings.sort((a, b) => a.identifier.name.localeCompare(b.identifier.name));
1132
- const uniqueBindings = [];
1133
- const seenUrls = /* @__PURE__ */ new Set();
1134
- for (const binding of bindings) {
1135
- if (!seenUrls.has(binding.identifier.url)) {
1136
- seenUrls.add(binding.identifier.url);
1137
- uniqueBindings.push(binding);
1138
- }
1139
- }
1140
- return uniqueBindings;
1141
- }
1142
-
1143
- // src/typeschema/core/transformer.ts
1144
- function mkFields(register, fhirSchema, parentPath, elements, logger) {
1145
- if (!elements) return void 0;
1146
- const fields = {};
1147
- for (const key of register.getAllElementKeys(elements)) {
1148
- const path = [...parentPath, key];
1149
- const elemSnapshot = register.resolveElementSnapshot(fhirSchema, path);
1150
- if (isNestedElement(elemSnapshot)) {
1151
- fields[key] = mkNestedField(register, fhirSchema, path, elemSnapshot, logger);
1152
- } else {
1153
- fields[key] = mkField(register, fhirSchema, path, elemSnapshot, logger);
1154
- }
1155
- }
1156
- return fields;
1157
- }
1158
- function extractFieldDependencies(fields) {
1159
- const deps = [];
1160
- for (const field of Object.values(fields)) {
1161
- if ("type" in field && field.type) {
1162
- deps.push(field.type);
1163
- }
1164
- if ("binding" in field && field.binding) {
1165
- deps.push(field.binding);
1166
- }
1167
- }
1168
- return deps;
1169
- }
1170
- function isExtensionSchema(fhirSchema, _identifier) {
1171
- if (fhirSchema.base === "Extension" || fhirSchema.base === "http://hl7.org/fhir/StructureDefinition/Extension") {
1172
- return true;
1173
- }
1174
- if (fhirSchema.url?.includes("/extension/") || fhirSchema.url?.includes("-extension")) {
1175
- return true;
1176
- }
1177
- if (fhirSchema.name?.toLowerCase().includes("extension")) {
1178
- return true;
1179
- }
1180
- if (fhirSchema.type === "Extension") {
1181
- return true;
1182
- }
1183
- return false;
1184
- }
1185
- async function transformValueSet(register, valueSet, logger) {
1186
- if (!valueSet.url) throw new Error("ValueSet URL is required");
1187
- const identifier = mkValueSetIdentifierByUrl(register, valueSet.package_meta, valueSet.url);
1188
- const concept = extractValueSetConceptsByUrl(register, valueSet.package_meta, valueSet.url);
1189
- return {
1190
- identifier,
1191
- description: valueSet.description,
1192
- concept,
1193
- compose: !concept ? valueSet.compose : void 0
1194
- };
1195
- }
1196
- function extractDependencies(identifier, base, fields, nestedTypes) {
1197
- const deps = [];
1198
- if (base) deps.push(base);
1199
- if (fields) deps.push(...extractFieldDependencies(fields));
1200
- if (nestedTypes) deps.push(...extractNestedDependencies(nestedTypes));
1201
- const uniqDeps = {};
1202
- for (const dep of deps) {
1203
- if (dep.url === identifier.url) continue;
1204
- uniqDeps[dep.url] = dep;
1205
- }
1206
- const localNestedTypeUrls = new Set(nestedTypes?.map((nt) => nt.identifier.url));
1207
- const result = Object.values(uniqDeps).filter((e) => {
1208
- if (isProfileIdentifier(identifier)) return true;
1209
- if (!isNestedIdentifier(e)) return true;
1210
- return !localNestedTypeUrls.has(e.url);
1211
- }).sort((a, b) => a.url.localeCompare(b.url));
1212
- return result.length > 0 ? result : void 0;
1213
- }
1214
- function transformFhirSchemaResource(register, fhirSchema, logger) {
1215
- const identifier = mkIdentifier(fhirSchema);
1216
- let base;
1217
- if (fhirSchema.base && fhirSchema.type !== "Element") {
1218
- const baseFs = register.resolveFs(
1219
- fhirSchema.package_meta,
1220
- register.ensureSpecializationCanonicalUrl(fhirSchema.base)
1221
- );
1222
- if (!baseFs) {
1223
- throw new Error(
1224
- `Base resource not found '${fhirSchema.base}' for <${fhirSchema.url}> from ${packageMetaToFhir(fhirSchema.package_meta)}`
1225
- );
1226
- }
1227
- base = mkIdentifier(baseFs);
1228
- }
1229
- const fields = mkFields(register, fhirSchema, [], fhirSchema.elements, logger);
1230
- const nested = mkNestedTypes(register, fhirSchema, logger);
1231
- const dependencies = extractDependencies(identifier, base, fields, nested);
1232
- const typeSchema = {
1233
- identifier,
1234
- base,
1235
- fields,
1236
- nested,
1237
- description: fhirSchema.description,
1238
- dependencies
1239
- };
1240
- const bindingSchemas = collectBindingSchemas(register, fhirSchema, logger);
1241
- return [typeSchema, ...bindingSchemas];
1242
- }
1243
- async function transformFhirSchema(register, fhirSchema, logger) {
1244
- const schemas = transformFhirSchemaResource(register, fhirSchema, logger);
1245
- if (isExtensionSchema(fhirSchema, mkIdentifier(fhirSchema))) {
1246
- const schema = schemas[0];
1247
- if (!schema) throw new Error(`Expected schema to be defined`);
1248
- schema.metadata = {
1249
- isExtension: true
1250
- // Mark as extension for file organization
1251
- };
1252
- }
1253
- return schemas;
1254
- }
1255
-
1256
- // src/typeschema/utils.ts
1257
- var groupByPackages = (typeSchemas) => {
1258
- const grouped = {};
1259
- for (const ts of typeSchemas) {
1260
- const pkgName = ts.identifier.package;
1261
- if (!grouped[pkgName]) grouped[pkgName] = [];
1262
- grouped[pkgName].push(ts);
1263
- }
1264
- for (const [packageName, typeSchemas2] of Object.entries(grouped)) {
1265
- const dict = {};
1266
- for (const ts of typeSchemas2) {
1267
- dict[JSON.stringify(ts.identifier)] = ts;
1268
- }
1269
- const tmp = Object.values(dict);
1270
- tmp.sort((a, b) => a.identifier.name.localeCompare(b.identifier.name));
1271
- grouped[packageName] = tmp;
1272
- }
1273
- return grouped;
1274
- };
1275
- var treeShakeTypeSchema = (schema, rule, _logger) => {
1276
- schema = structuredClone(schema);
1277
- if (isPrimitiveTypeSchema(schema) || isValueSetTypeSchema(schema) || isBindingSchema(schema)) return schema;
1278
- for (const fieldName of rule.ignoreFields ?? []) {
1279
- if (schema.fields && !schema.fields[fieldName]) throw new Error(`Field ${fieldName} not found`);
1280
- if (schema.fields) {
1281
- delete schema.fields[fieldName];
1282
- }
1283
- }
1284
- schema.dependencies = extractDependencies(schema.identifier, schema.base, schema.fields, schema.nested);
1285
- return schema;
1286
- };
1287
- var treeShake = (tsIndex, treeShake2, { resolutionTree, logger }) => {
1288
- const focusedSchemas = [];
1289
- for (const [pkgId, requires] of Object.entries(treeShake2)) {
1290
- for (const [url, rule] of Object.entries(requires)) {
1291
- const schema = tsIndex.resolveByUrl(pkgId, url);
1292
- if (!schema) throw new Error(`Schema not found for ${pkgId} ${url}`);
1293
- const shaked2 = treeShakeTypeSchema(schema, rule);
1294
- focusedSchemas.push(shaked2);
1295
- }
1296
- }
1297
- const collectDeps = (schemas, acc) => {
1298
- if (schemas.length === 0) return Object.values(acc);
1299
- for (const schema of schemas) {
1300
- acc[JSON.stringify(schema.identifier)] = schema;
1301
- }
1302
- const newSchemas = [];
1303
- for (const schema of schemas) {
1304
- if (isSpecializationTypeSchema(schema)) {
1305
- if (!schema.dependencies) continue;
1306
- schema.dependencies.forEach((dep) => {
1307
- const depSchema = tsIndex.resolve(dep);
1308
- if (!depSchema) throw new Error(`Schema not found for ${dep}`);
1309
- const id = JSON.stringify(depSchema.identifier);
1310
- if (!acc[id]) newSchemas.push(depSchema);
1311
- });
1312
- if (schema.nested) {
1313
- for (const nest of schema.nested) {
1314
- if (isNestedIdentifier(nest.identifier)) continue;
1315
- const id = JSON.stringify(nest.identifier);
1316
- if (!acc[id]) newSchemas.push(nest);
1317
- }
1318
- }
1319
- }
1320
- }
1321
- return collectDeps(newSchemas, acc);
1322
- };
1323
- const shaked = collectDeps(focusedSchemas, {});
1324
- return mkTypeSchemaIndex(shaked, { resolutionTree, logger });
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
- };
1374
- var resourceRelatives = (schemas) => {
1375
- const regularSchemas = schemas.filter((e) => isResourceTypeSchema(e) || isLogicalTypeSchema(e));
1376
- const directPairs = [];
1377
- for (const schema of regularSchemas) {
1378
- if (schema.base) {
1379
- directPairs.push({ parent: schema.base, child: schema.identifier });
1380
- }
1381
- }
1382
- const allPairs = [...directPairs];
1383
- const findTransitiveRelatives = (parentRef) => {
1384
- const directChildren = directPairs.filter((pair) => pair.parent.name === parentRef.name).map((pair) => pair.child);
1385
- const transitiveChildren = [];
1386
- for (const child of directChildren) {
1387
- transitiveChildren.push(...findTransitiveRelatives(child));
1388
- }
1389
- return [...directChildren, ...transitiveChildren];
1390
- };
1391
- for (const pair of directPairs) {
1392
- const transitiveChildren = findTransitiveRelatives(pair.child);
1393
- for (const transitiveChild of transitiveChildren) {
1394
- if (!directPairs.some((dp) => dp.parent.name === pair.parent.name && dp.child.name === transitiveChild.name)) {
1395
- allPairs.push({ parent: pair.parent, child: transitiveChild });
1396
- }
1397
- }
1398
- }
1399
- return allPairs;
1400
- };
1401
- var mkTypeSchemaIndex = (schemas, { resolutionTree, logger }) => {
1402
- const index = {};
1403
- const append = (schema) => {
1404
- const url = schema.identifier.url;
1405
- const pkg = schema.identifier.package;
1406
- if (!index[url]) index[url] = {};
1407
- if (index[url][schema.identifier.package] && pkg !== "shared") {
1408
- const r1 = JSON.stringify(schema.identifier, void 0, 2);
1409
- const r2 = JSON.stringify(index[url][pkg]?.identifier, void 0, 2);
1410
- if (r1 !== r2) throw new Error(`Duplicate schema: ${r1} and ${r2}`);
1411
- return;
837
+ return allPairs;
838
+ };
839
+ var mkTypeSchemaIndex = (schemas, { resolutionTree, logger }) => {
840
+ const index = {};
841
+ const append = (schema) => {
842
+ const url = schema.identifier.url;
843
+ const pkg = schema.identifier.package;
844
+ if (!index[url]) index[url] = {};
845
+ if (index[url][schema.identifier.package] && pkg !== "shared") {
846
+ const r1 = JSON.stringify(schema.identifier, void 0, 2);
847
+ const r2 = JSON.stringify(index[url][pkg]?.identifier, void 0, 2);
848
+ if (r1 !== r2) throw new Error(`Duplicate schema: ${r1} and ${r2}`);
849
+ return;
1412
850
  }
1413
851
  index[url][pkg] = schema;
1414
852
  };
@@ -1804,7 +1242,7 @@ var Python = class extends Writer {
1804
1242
  return names;
1805
1243
  }
1806
1244
  shouldImportResourceFamily(resource) {
1807
- assert2(this.tsIndex !== void 0);
1245
+ assert3(this.tsIndex !== void 0);
1808
1246
  return resource.identifier.kind === "resource" && this.tsIndex.resourceChildren(resource.identifier).length > 0;
1809
1247
  }
1810
1248
  generateExportsDeclaration(packageComplexTypes, allResourceNames) {
@@ -1839,244 +1277,757 @@ var Python = class extends Writer {
1839
1277
  });
1840
1278
  this.line();
1841
1279
  }
1842
- getSuperClasses(schema) {
1843
- return [...schema.base ? [schema.base.name] : [], ...injectSuperClasses(schema.identifier.name)];
1280
+ getSuperClasses(schema) {
1281
+ return [...schema.base ? [schema.base.name] : [], ...injectSuperClasses(schema.identifier.name)];
1282
+ }
1283
+ generateClassBody(schema) {
1284
+ this.generateModelConfig();
1285
+ if (!schema.fields) {
1286
+ this.line("pass");
1287
+ return;
1288
+ }
1289
+ if (schema.identifier.kind === "resource") {
1290
+ this.generateResourceTypeField(schema);
1291
+ }
1292
+ this.generateFields(schema);
1293
+ if (schema.identifier.kind === "resource") {
1294
+ this.generateResourceMethods(schema);
1295
+ }
1296
+ }
1297
+ generateModelConfig() {
1298
+ const extraMode = this.opts.allowExtraFields ? "allow" : "forbid";
1299
+ this.line(`model_config = ConfigDict(validate_by_name=True, serialize_by_alias=True, extra="${extraMode}")`);
1300
+ }
1301
+ generateResourceTypeField(schema) {
1302
+ this.line(`${this.nameFormatFunction("resourceType")}: str = Field(`);
1303
+ this.indentBlock(() => {
1304
+ this.line(`default='${schema.identifier.name}',`);
1305
+ this.line(`alias='resourceType',`);
1306
+ this.line(`serialization_alias='resourceType',`);
1307
+ this.line("frozen=True,");
1308
+ this.line(`pattern='${schema.identifier.name}'`);
1309
+ });
1310
+ this.line(")");
1311
+ }
1312
+ generateFields(schema) {
1313
+ const sortedFields = Object.entries(schema.fields ?? []).sort(([a], [b]) => a.localeCompare(b));
1314
+ for (const [fieldName, field] of sortedFields) {
1315
+ if ("choices" in field && field.choices) continue;
1316
+ const fieldInfo = this.buildFieldInfo(fieldName, field);
1317
+ this.line(`${fieldInfo.name}: ${fieldInfo.type}${fieldInfo.defaultValue}`);
1318
+ }
1319
+ }
1320
+ buildFieldInfo(fieldName, field) {
1321
+ const pyFieldName = fixReservedWords(this.nameFormatFunction(fieldName));
1322
+ const fieldType = this.determineFieldType(field);
1323
+ const defaultValue = this.getFieldDefaultValue(field, fieldName);
1324
+ return {
1325
+ name: pyFieldName,
1326
+ type: fieldType,
1327
+ defaultValue
1328
+ };
1329
+ }
1330
+ determineFieldType(field) {
1331
+ let fieldType = field ? this.getBaseFieldType(field) : "";
1332
+ if ("enum" in field && field.enum) {
1333
+ const s = field.enum.map((e) => `"${e}"`).join(", ");
1334
+ fieldType = `Literal[${s}]`;
1335
+ }
1336
+ if (field.array) {
1337
+ fieldType = `PyList[${fieldType}]`;
1338
+ }
1339
+ if (!field.required) {
1340
+ fieldType = `${fieldType} | None`;
1341
+ }
1342
+ return fieldType;
1343
+ }
1344
+ getBaseFieldType(field) {
1345
+ if ("type" in field && field.type.kind === "resource") return `${field.type.name}Family`;
1346
+ if ("type" in field && field.type.kind === "nested") return deriveResourceName(field.type);
1347
+ if ("type" in field && field.type.kind === "primitive-type")
1348
+ return PRIMITIVE_TYPE_MAP2[field.type.name] ?? "str";
1349
+ return "type" in field ? field.type.name : "";
1350
+ }
1351
+ getFieldDefaultValue(field, fieldName) {
1352
+ const aliasSpec = `alias="${fieldName}", serialization_alias="${fieldName}"`;
1353
+ if (!field.required) {
1354
+ return ` = Field(None, ${aliasSpec})`;
1355
+ }
1356
+ return ` = Field(${aliasSpec})`;
1357
+ }
1358
+ generateResourceMethods(schema) {
1359
+ const className = schema.identifier.name.toString();
1360
+ this.line();
1361
+ this.line("def to_json(self, indent: int | None = None) -> str:");
1362
+ this.line(" return self.model_dump_json(exclude_unset=True, exclude_none=True, indent=indent)");
1363
+ this.line();
1364
+ this.line("@classmethod");
1365
+ this.line(`def from_json(cls, json: str) -> ${className}:`);
1366
+ this.line(" return cls.model_validate_json(json)");
1367
+ }
1368
+ generateNestedTypes(schema) {
1369
+ if (!schema.nested) return;
1370
+ this.line();
1371
+ for (const subtype of schema.nested) {
1372
+ this.generateType(subtype);
1373
+ }
1374
+ }
1375
+ generateDefaultImports() {
1376
+ this.pyImportFrom("__future__", "annotations");
1377
+ this.pyImportFrom("pydantic", "BaseModel", "ConfigDict", "Field", "PositiveInt");
1378
+ this.pyImportFrom("typing", "List as PyList", "Literal");
1379
+ }
1380
+ generateDependenciesImports(schema) {
1381
+ if (!schema.dependencies || schema.dependencies.length === 0) return;
1382
+ this.importComplexTypeDependencies(schema.dependencies);
1383
+ this.importResourceDependencies(schema.dependencies);
1384
+ }
1385
+ importComplexTypeDependencies(dependencies) {
1386
+ const complexTypeDeps = dependencies.filter((dep) => dep.kind === "complex-type");
1387
+ const depsByPackage = this.groupDependenciesByPackage(complexTypeDeps);
1388
+ for (const [pyPackage, names] of Object.entries(depsByPackage)) {
1389
+ this.pyImportFrom(pyPackage, ...names.sort());
1390
+ }
1391
+ }
1392
+ importResourceDependencies(dependencies) {
1393
+ const resourceDeps = dependencies.filter((dep) => dep.kind === "resource");
1394
+ for (const dep of resourceDeps) {
1395
+ this.pyImportType(dep);
1396
+ const familyName = `${pascalCase(dep.name)}Family`;
1397
+ const familyPackage = `${this.pyFhirPackage(dep)}.resource_families`;
1398
+ this.pyImportFrom(familyPackage, familyName);
1399
+ }
1400
+ }
1401
+ groupDependenciesByPackage(dependencies) {
1402
+ const grouped = {};
1403
+ for (const dep of dependencies) {
1404
+ const pyPackage = this.pyPackage(dep);
1405
+ if (!grouped[pyPackage]) {
1406
+ grouped[pyPackage] = [];
1407
+ }
1408
+ grouped[pyPackage].push(dep.name);
1409
+ }
1410
+ return grouped;
1411
+ }
1412
+ pyImportFrom(pyPackage, ...entities) {
1413
+ const oneLine = `from ${pyPackage} import ${entities.join(", ")}`;
1414
+ if (this.shouldUseSingleLineImport(oneLine, entities)) {
1415
+ this.line(oneLine);
1416
+ } else {
1417
+ this.writeMultiLineImport(pyPackage, entities);
1418
+ }
1419
+ }
1420
+ shouldUseSingleLineImport(oneLine, entities) {
1421
+ return oneLine.length <= MAX_IMPORT_LINE_LENGTH || entities.length === 1;
1422
+ }
1423
+ writeMultiLineImport(pyPackage, entities) {
1424
+ this.line(`from ${pyPackage} import (\\`);
1425
+ this.indentBlock(() => {
1426
+ const remaining = [...entities];
1427
+ while (remaining.length > 0) {
1428
+ const line = this.buildImportLine(remaining, MAX_IMPORT_LINE_LENGTH);
1429
+ this.line(line);
1430
+ }
1431
+ });
1432
+ this.line(")");
1433
+ }
1434
+ pyImportType(identifier) {
1435
+ this.pyImportFrom(this.pyPackage(identifier), pascalCase(identifier.name));
1436
+ }
1437
+ generateResourceFamilies(packageResources) {
1438
+ assert3(this.tsIndex !== void 0);
1439
+ const packages = (
1440
+ //this.helper.getPackages(packageResources, this.opts.rootPackageName);
1441
+ Object.keys(groupByPackages(packageResources)).map(
1442
+ (pkgName) => `${this.opts.rootPackageName}.${pkgName.replaceAll(".", "_")}`
1443
+ )
1444
+ );
1445
+ const families = {};
1446
+ for (const resource of this.tsIndex.collectResources()) {
1447
+ const children = this.tsIndex.resourceChildren(resource.identifier).map((c) => c.name);
1448
+ if (children.length > 0) {
1449
+ const familyName = `${resource.identifier.name}Family`;
1450
+ families[familyName] = children;
1451
+ }
1452
+ }
1453
+ const exportList = Object.keys(families);
1454
+ if (exportList.length === 0) return;
1455
+ this.buildResourceFamiliesFile(packages, families, exportList);
1456
+ }
1457
+ buildResourceFamiliesFile(packages, families, exportList) {
1458
+ this.cat("resource_families.py", () => {
1459
+ this.generateDisclaimer();
1460
+ this.includeResourceFamilyValidator();
1461
+ this.line();
1462
+ this.generateFamilyDefinitions(packages, families);
1463
+ this.generateFamilyExports(exportList);
1464
+ });
1465
+ }
1466
+ includeResourceFamilyValidator() {
1467
+ const content = fs__default.readFileSync(resolvePyAssets("resource_family_validator.py"), "utf-8");
1468
+ this.line(content);
1469
+ }
1470
+ generateFamilyDefinitions(packages, families) {
1471
+ this.line(`packages = [${packages.map((p) => `'${p}'`).join(", ")}]`);
1472
+ this.line();
1473
+ for (const [familyName, resources] of Object.entries(families)) {
1474
+ this.generateFamilyDefinition(familyName, resources);
1475
+ }
1476
+ }
1477
+ generateFamilyDefinition(familyName, resources) {
1478
+ const listName = `${familyName}_resources`;
1479
+ this.line(`${listName} = [${resources.map((r) => `'${r}'`).join(", ")}]`);
1480
+ this.line();
1481
+ this.line(`def validate_and_downcast_${familyName}(v: Any) -> Any:`);
1482
+ this.line(` return validate_and_downcast(v, packages, ${listName})`);
1483
+ this.line();
1484
+ this.line(`type ${familyName} = Annotated[Any, BeforeValidator(validate_and_downcast_${familyName})]`);
1485
+ this.line();
1486
+ }
1487
+ generateFamilyExports(exportList) {
1488
+ this.line(`__all__ = [${exportList.map((e) => `'${e}'`).join(", ")}]`);
1844
1489
  }
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
- }
1490
+ buildPyPackageName(packageName) {
1491
+ const parts = packageName ? [snakeCase(packageName)] : [""];
1492
+ return parts.join(".");
1858
1493
  }
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}")`);
1494
+ pyFhirPackage(identifier) {
1495
+ return this.pyFhirPackageByName(identifier.package);
1862
1496
  }
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(")");
1497
+ pyFhirPackageByName(name) {
1498
+ return [this.opts.rootPackageName, this.buildPyPackageName(name)].join(".");
1873
1499
  }
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}`);
1500
+ pyPackage(identifier) {
1501
+ if (identifier.kind === "complex-type") {
1502
+ return `${this.pyFhirPackage(identifier)}.base`;
1880
1503
  }
1504
+ if (identifier.kind === "resource") {
1505
+ return [this.pyFhirPackage(identifier), snakeCase(identifier.name)].join(".");
1506
+ }
1507
+ return this.pyFhirPackage(identifier);
1881
1508
  }
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
- };
1509
+ getFieldFormatFunction(format) {
1510
+ if (!AVAILABLE_STRING_FORMATS[format]) {
1511
+ this.logger()?.warn(`Unknown field format '${format}'. Defaulting to SnakeCase.`);
1512
+ this.logger()?.warn(`Supported formats: ${Object.keys(AVAILABLE_STRING_FORMATS).join(", ")}`);
1513
+ return snakeCase;
1514
+ }
1515
+ return AVAILABLE_STRING_FORMATS[format];
1891
1516
  }
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}]`;
1517
+ };
1518
+
1519
+ // src/typeschema/core/identifier.ts
1520
+ function dropVersionFromUrl(url) {
1521
+ const baseUrl = url.split("|")[0];
1522
+ return baseUrl ? baseUrl : url;
1523
+ }
1524
+ function getVersionFromUrl(url) {
1525
+ const version = url.split("|")[1];
1526
+ return version;
1527
+ }
1528
+ function determineKind(fhirSchema) {
1529
+ if (fhirSchema.derivation === "constraint") return "profile";
1530
+ if (fhirSchema.kind === "primitive-type") return "primitive-type";
1531
+ if (fhirSchema.kind === "complex-type") return "complex-type";
1532
+ if (fhirSchema.kind === "resource") return "resource";
1533
+ if (fhirSchema.kind === "logical") return "logical";
1534
+ return "resource";
1535
+ }
1536
+ function mkIdentifier(fhirSchema) {
1537
+ return {
1538
+ kind: determineKind(fhirSchema),
1539
+ package: fhirSchema.package_meta.name,
1540
+ version: fhirSchema.package_meta.version,
1541
+ name: fhirSchema.name,
1542
+ url: fhirSchema.url
1543
+ };
1544
+ }
1545
+ var getValueSetName = (url) => {
1546
+ const urlParts = url.split("/");
1547
+ const lastSegment = urlParts[urlParts.length - 1];
1548
+ if (lastSegment && lastSegment.length > 0) {
1549
+ return lastSegment.split(/[-_]/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
1550
+ }
1551
+ return url;
1552
+ };
1553
+ function mkValueSetIdentifierByUrl(register, pkg, fullValueSetUrl) {
1554
+ const valueSetUrl = dropVersionFromUrl(fullValueSetUrl);
1555
+ const valueSetNameFallback = getValueSetName(valueSetUrl);
1556
+ const valuesSetFallback = {
1557
+ package_meta: {
1558
+ name: "missing_valuesets",
1559
+ version: getVersionFromUrl(valueSetUrl) || "0.0.0"
1560
+ },
1561
+ id: fullValueSetUrl};
1562
+ const valueSet = register.resolveVs(pkg, valueSetUrl) || valuesSetFallback;
1563
+ const valueSetName = valueSet?.id && !/^[a-zA-Z0-9_-]{20,}$/.test(valueSet.id) ? valueSet.id : valueSetNameFallback;
1564
+ return {
1565
+ kind: "value-set",
1566
+ package: valueSet.package_meta.name,
1567
+ version: valueSet.package_meta.version,
1568
+ name: valueSetName,
1569
+ url: valueSetUrl
1570
+ };
1571
+ }
1572
+ function mkBindingIdentifier(fhirSchema, path, bindingName) {
1573
+ const pathStr = path.join(".");
1574
+ const [pkg, name, url] = bindingName ? [{ name: "shared", version: "1.0.0" }, bindingName, `urn:fhir:binding:${bindingName}`] : [fhirSchema.package_meta, `${fhirSchema.name}.${pathStr}_binding`, `${fhirSchema.url}#${pathStr}_binding`];
1575
+ return {
1576
+ kind: "binding",
1577
+ package: pkg.name,
1578
+ version: pkg.version,
1579
+ name,
1580
+ url
1581
+ };
1582
+ }
1583
+
1584
+ // src/typeschema/core/nested-types.ts
1585
+ function mkNestedIdentifier(register, fhirSchema, path, logger) {
1586
+ const nestedTypeOrigins = {};
1587
+ if (fhirSchema.derivation === "constraint") {
1588
+ const specializations = register.resolveFsSpecializations(fhirSchema.package_meta, fhirSchema.url);
1589
+ const nestedTypeGenealogy = specializations.map((fs5) => mkNestedTypes(register, fs5, logger)).filter((e) => e !== void 0).flat();
1590
+ for (const nt of nestedTypeGenealogy.reverse()) {
1591
+ nestedTypeOrigins[nt.identifier.name] = nt.identifier.url;
1897
1592
  }
1898
- if (field.array) {
1899
- fieldType = `PyList[${fieldType}]`;
1593
+ }
1594
+ const nestedName = path.join(".");
1595
+ const url = nestedTypeOrigins[nestedName] ?? `${fhirSchema.url}#${nestedName}`;
1596
+ return {
1597
+ kind: "nested",
1598
+ package: fhirSchema.package_meta.name,
1599
+ version: fhirSchema.package_meta.version,
1600
+ name: nestedName,
1601
+ url
1602
+ };
1603
+ }
1604
+ function collectNestedElements(fhirSchema, parentPath, elements) {
1605
+ const nested = [];
1606
+ for (const [key, element] of Object.entries(elements)) {
1607
+ const path = [...parentPath, key];
1608
+ if (isNestedElement(element)) {
1609
+ nested.push([path, element]);
1900
1610
  }
1901
- if (!field.required) {
1902
- fieldType = `${fieldType} | None`;
1611
+ if (element.elements) {
1612
+ nested.push(...collectNestedElements(fhirSchema, path, element.elements));
1903
1613
  }
1904
- return fieldType;
1905
1614
  }
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})`;
1615
+ return nested;
1616
+ }
1617
+ function transformNestedElements(register, fhirSchema, parentPath, elements, logger) {
1618
+ const fields = {};
1619
+ for (const [key, _element] of Object.entries(elements)) {
1620
+ const path = [...parentPath, key];
1621
+ const elemSnapshot = register.resolveElementSnapshot(fhirSchema, path);
1622
+ if (isNestedElement(elemSnapshot)) {
1623
+ fields[key] = mkNestedField(register, fhirSchema, path, elemSnapshot, logger);
1624
+ } else {
1625
+ fields[key] = mkField(register, fhirSchema, path, elemSnapshot, logger);
1917
1626
  }
1918
- return ` = Field(${aliasSpec})`;
1919
1627
  }
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)");
1628
+ return fields;
1629
+ }
1630
+ function mkNestedTypes(register, fhirSchema, logger) {
1631
+ if (!fhirSchema.elements) return void 0;
1632
+ const nested = collectNestedElements(fhirSchema, [], fhirSchema.elements).filter(
1633
+ ([_, element]) => element.elements && Object.keys(element.elements).length > 0
1634
+ );
1635
+ const nestedTypes = [];
1636
+ for (const [path, element] of nested) {
1637
+ const identifier = mkNestedIdentifier(register, fhirSchema, path, logger);
1638
+ let baseName;
1639
+ if (element.type === "BackboneElement" || !element.type) {
1640
+ baseName = "BackboneElement";
1641
+ } else {
1642
+ baseName = element.type;
1643
+ }
1644
+ const baseUrl = register.ensureSpecializationCanonicalUrl(baseName);
1645
+ const baseFs = register.resolveFs(fhirSchema.package_meta, baseUrl);
1646
+ if (!baseFs) throw new Error(`Could not resolve base type ${baseName}`);
1647
+ const base = {
1648
+ kind: "complex-type",
1649
+ package: baseFs.package_meta.name,
1650
+ version: baseFs.package_meta.version,
1651
+ name: baseName,
1652
+ url: baseUrl
1653
+ };
1654
+ const fields = transformNestedElements(register, fhirSchema, path, element.elements ?? {}, logger);
1655
+ const nestedType = {
1656
+ identifier,
1657
+ base,
1658
+ fields
1659
+ };
1660
+ nestedTypes.push(nestedType);
1929
1661
  }
1930
- generateNestedTypes(schema) {
1931
- if (!schema.nested) return;
1932
- this.line();
1933
- for (const subtype of schema.nested) {
1934
- this.generateType(subtype);
1662
+ nestedTypes.sort((a, b) => a.identifier.url.localeCompare(b.identifier.url));
1663
+ return nestedTypes.length === 0 ? void 0 : nestedTypes;
1664
+ }
1665
+ function extractNestedDependencies(nestedTypes) {
1666
+ const deps = [];
1667
+ for (const nested of nestedTypes) {
1668
+ if (nested.base) {
1669
+ deps.push(nested.base);
1670
+ }
1671
+ for (const field of Object.values(nested.fields || {})) {
1672
+ if ("type" in field && field.type) {
1673
+ deps.push(field.type);
1674
+ }
1675
+ if ("binding" in field && field.binding) {
1676
+ deps.push(field.binding);
1677
+ }
1935
1678
  }
1936
1679
  }
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());
1680
+ return deps;
1681
+ }
1682
+
1683
+ // src/typeschema/core/field-builder.ts
1684
+ function isRequired(register, fhirSchema, path) {
1685
+ const fieldName = path[path.length - 1];
1686
+ if (!fieldName) throw new Error(`Internal error: fieldName is missing for path ${path.join("/")}`);
1687
+ const parentPath = path.slice(0, -1);
1688
+ const requires = register.resolveFsGenealogy(fhirSchema.package_meta, fhirSchema.url).flatMap((fs5) => {
1689
+ if (parentPath.length === 0) return fs5.required || [];
1690
+ if (!fs5.elements) return [];
1691
+ let elem = fs5;
1692
+ for (const k of parentPath) {
1693
+ elem = elem?.elements?.[k];
1694
+ }
1695
+ return elem?.required || [];
1696
+ });
1697
+ return new Set(requires).has(fieldName);
1698
+ }
1699
+ function isExcluded(register, fhirSchema, path) {
1700
+ const fieldName = path[path.length - 1];
1701
+ if (!fieldName) throw new Error(`Internal error: fieldName is missing for path ${path.join("/")}`);
1702
+ const parentPath = path.slice(0, -1);
1703
+ const requires = register.resolveFsGenealogy(fhirSchema.package_meta, fhirSchema.url).flatMap((fs5) => {
1704
+ if (parentPath.length === 0) return fs5.excluded || [];
1705
+ if (!fs5.elements) return [];
1706
+ let elem = fs5;
1707
+ for (const k of parentPath) {
1708
+ elem = elem?.elements?.[k];
1952
1709
  }
1710
+ return elem?.excluded || [];
1711
+ });
1712
+ return new Set(requires).has(fieldName);
1713
+ }
1714
+ var buildReferences = (register, fhirSchema, element) => {
1715
+ if (!element.refers) return void 0;
1716
+ return element.refers.map((ref) => {
1717
+ const curl = register.ensureSpecializationCanonicalUrl(ref);
1718
+ const fs5 = register.resolveFs(fhirSchema.package_meta, curl);
1719
+ if (!fs5) throw new Error(`Failed to resolve fs for ${curl}`);
1720
+ return mkIdentifier(fs5);
1721
+ });
1722
+ };
1723
+ function buildFieldType(register, fhirSchema, path, element, logger) {
1724
+ if (element.elementReference) {
1725
+ const refPath = element.elementReference.slice(1).filter((_, i) => i % 2 === 1);
1726
+ return mkNestedIdentifier(register, fhirSchema, refPath, logger);
1727
+ } else if (element.type) {
1728
+ const url = register.ensureSpecializationCanonicalUrl(element.type);
1729
+ const fieldFs = register.resolveFs(fhirSchema.package_meta, url);
1730
+ if (!fieldFs)
1731
+ throw new Error(
1732
+ `Could not resolve field type: '${element.type}' (from '${fhirSchema.url}' in '${packageMetaToFhir(fhirSchema.package_meta)}')`
1733
+ );
1734
+ return mkIdentifier(fieldFs);
1735
+ } else if (element.choices) {
1736
+ return void 0;
1737
+ } else if (fhirSchema.derivation === "constraint") {
1738
+ return void 0;
1739
+ } else {
1740
+ logger?.error(
1741
+ `Can't recognize element type '${fhirSchema.url}' (${fhirSchema.derivation}) at '${path.join(".")}': ${JSON.stringify(element, void 0, 2)}`
1742
+ );
1743
+ throw new Error(`Unrecognized element type`);
1953
1744
  }
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);
1745
+ }
1746
+ var mkField = (register, fhirSchema, path, element, logger) => {
1747
+ let binding;
1748
+ let enumValues;
1749
+ if (element.binding) {
1750
+ binding = mkBindingIdentifier(fhirSchema, path, element.binding.bindingName);
1751
+ if (element.binding.strength === "required" && element.type === "code") {
1752
+ enumValues = buildEnum(register, fhirSchema, element, logger);
1961
1753
  }
1962
1754
  }
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] = [];
1755
+ const fieldType = buildFieldType(register, fhirSchema, path, element, logger);
1756
+ if (!fieldType)
1757
+ logger?.warn(`Field type not found for '${fhirSchema.url}#${path.join(".")}' (${fhirSchema.derivation})`);
1758
+ return {
1759
+ type: fieldType,
1760
+ required: isRequired(register, fhirSchema, path),
1761
+ excluded: isExcluded(register, fhirSchema, path),
1762
+ reference: buildReferences(register, fhirSchema, element),
1763
+ array: element.array || false,
1764
+ min: element.min,
1765
+ max: element.max,
1766
+ choices: element.choices,
1767
+ choiceOf: element.choiceOf,
1768
+ binding,
1769
+ enum: enumValues
1770
+ };
1771
+ };
1772
+ function isNestedElement(element) {
1773
+ const isBackbone = element.type === "BackboneElement";
1774
+ const isElement = element.type === "Element" && element.elements !== void 0 && Object.keys(element.elements).length > 0;
1775
+ const elementsWithoutType = element.type === void 0 && element.choiceOf === void 0 && element.elements !== void 0 && Object.keys(element.elements).length > 0;
1776
+ return isBackbone || isElement || elementsWithoutType;
1777
+ }
1778
+ function mkNestedField(register, fhirSchema, path, element, logger) {
1779
+ const nestedIdentifier = mkNestedIdentifier(register, fhirSchema, path, logger);
1780
+ return {
1781
+ type: nestedIdentifier,
1782
+ array: element.array || false,
1783
+ required: isRequired(register, fhirSchema, path),
1784
+ excluded: isExcluded(register, fhirSchema, path)
1785
+ };
1786
+ }
1787
+
1788
+ // src/typeschema/core/binding.ts
1789
+ function extractValueSetConceptsByUrl(register, pkg, valueSetUrl, logger) {
1790
+ const cleanUrl = dropVersionFromUrl(valueSetUrl) || valueSetUrl;
1791
+ const valueSet = register.resolveVs(pkg, cleanUrl);
1792
+ if (!valueSet) return void 0;
1793
+ return extractValueSetConcepts(register, valueSet);
1794
+ }
1795
+ function extractValueSetConcepts(register, valueSet, _logger) {
1796
+ if (valueSet.expansion?.contains) {
1797
+ return valueSet.expansion.contains.filter((item) => item.code !== void 0).map((item) => {
1798
+ assert3(item.code);
1799
+ return {
1800
+ code: item.code,
1801
+ display: item.display,
1802
+ system: item.system
1803
+ };
1804
+ });
1805
+ }
1806
+ const concepts = [];
1807
+ if (valueSet.compose?.include) {
1808
+ for (const include of valueSet.compose.include) {
1809
+ if (include.concept) {
1810
+ for (const concept of include.concept) {
1811
+ concepts.push({
1812
+ system: include.system,
1813
+ code: concept.code,
1814
+ display: concept.display
1815
+ });
1816
+ }
1817
+ } else if (include.system && !include.filter) {
1818
+ try {
1819
+ const codeSystem = register.resolveAny(include.system);
1820
+ if (codeSystem?.concept) {
1821
+ const extractConcepts = (conceptList, system) => {
1822
+ for (const concept of conceptList) {
1823
+ concepts.push({
1824
+ system,
1825
+ code: concept.code,
1826
+ display: concept.display
1827
+ });
1828
+ if (concept.concept) {
1829
+ extractConcepts(concept.concept, system);
1830
+ }
1831
+ }
1832
+ };
1833
+ extractConcepts(codeSystem.concept, include.system);
1834
+ }
1835
+ } catch {
1836
+ }
1969
1837
  }
1970
- grouped[pyPackage].push(dep.name);
1971
1838
  }
1972
- return grouped;
1973
1839
  }
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
- }
1840
+ return concepts.length > 0 ? concepts : void 0;
1841
+ }
1842
+ var MAX_ENUM_LENGTH = 100;
1843
+ function buildEnum(register, fhirSchema, element, logger) {
1844
+ if (!element.binding) return void 0;
1845
+ const strength = element.binding.strength;
1846
+ const valueSetUrl = element.binding.valueSet;
1847
+ if (!valueSetUrl) return void 0;
1848
+ const shouldGenerateEnum = strength === "required" || strength === "extensible" && (element.type === "code" || element.type === "Coding") || strength === "preferred" && (element.type === "code" || element.type === "Coding");
1849
+ if (!shouldGenerateEnum) return void 0;
1850
+ const concepts = extractValueSetConceptsByUrl(register, fhirSchema.package_meta, valueSetUrl);
1851
+ if (!concepts || concepts.length === 0) return void 0;
1852
+ const codes = concepts.map((c) => c.code).filter((code) => code && typeof code === "string" && code.trim().length > 0);
1853
+ if (codes.length > MAX_ENUM_LENGTH) {
1854
+ logger?.dry_warn(
1855
+ `Value set ${valueSetUrl} has ${codes.length} which is more than ${MAX_ENUM_LENGTH} codes, which may cause issues with code generation.`
1856
+ );
1857
+ return void 0;
1981
1858
  }
1982
- shouldUseSingleLineImport(oneLine, entities) {
1983
- return oneLine.length <= MAX_IMPORT_LINE_LENGTH || entities.length === 1;
1859
+ return codes.length > 0 ? codes : void 0;
1860
+ }
1861
+ function generateBindingSchema(register, fhirSchema, path, element, logger) {
1862
+ if (!element.binding?.valueSet) return void 0;
1863
+ const identifier = mkBindingIdentifier(fhirSchema, path, element.binding.bindingName);
1864
+ const fieldType = buildFieldType(register, fhirSchema, path, element, logger);
1865
+ const valueSetIdentifier = mkValueSetIdentifierByUrl(
1866
+ register,
1867
+ fhirSchema.package_meta,
1868
+ element.binding.valueSet
1869
+ );
1870
+ const dependencies = [];
1871
+ if (fieldType) {
1872
+ dependencies.push(fieldType);
1984
1873
  }
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);
1874
+ dependencies.push(valueSetIdentifier);
1875
+ const enumValues = buildEnum(register, fhirSchema, element, logger);
1876
+ return {
1877
+ identifier,
1878
+ type: fieldType,
1879
+ valueset: valueSetIdentifier,
1880
+ strength: element.binding.strength,
1881
+ enum: enumValues,
1882
+ dependencies
1883
+ };
1884
+ }
1885
+ function collectBindingSchemas(register, fhirSchema, logger) {
1886
+ const processedPaths = /* @__PURE__ */ new Set();
1887
+ if (!fhirSchema.elements) return [];
1888
+ const bindings = [];
1889
+ function collectBindings(elements, parentPath) {
1890
+ for (const [key, element] of Object.entries(elements)) {
1891
+ const path = [...parentPath, key];
1892
+ const pathKey = path.join(".");
1893
+ if (processedPaths.has(pathKey)) continue;
1894
+ processedPaths.add(pathKey);
1895
+ if (element.binding) {
1896
+ const binding = generateBindingSchema(register, fhirSchema, path, element, logger);
1897
+ if (binding) {
1898
+ bindings.push(binding);
1899
+ }
1992
1900
  }
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;
1901
+ if (element.elements) {
1902
+ collectBindings(element.elements, path);
2013
1903
  }
2014
1904
  }
2015
- const exportList = Object.keys(families);
2016
- if (exportList.length === 0) return;
2017
- this.buildResourceFamiliesFile(packages, families, exportList);
2018
1905
  }
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
- });
1906
+ collectBindings(fhirSchema.elements, []);
1907
+ bindings.sort((a, b) => a.identifier.name.localeCompare(b.identifier.name));
1908
+ const uniqueBindings = [];
1909
+ const seenUrls = /* @__PURE__ */ new Set();
1910
+ for (const binding of bindings) {
1911
+ if (!seenUrls.has(binding.identifier.url)) {
1912
+ seenUrls.add(binding.identifier.url);
1913
+ uniqueBindings.push(binding);
1914
+ }
2027
1915
  }
2028
- includeResourceFamilyValidator() {
2029
- const content = fs__default.readFileSync(resolvePyAssets("resource_family_validator.py"), "utf-8");
2030
- this.line(content);
1916
+ return uniqueBindings;
1917
+ }
1918
+
1919
+ // src/typeschema/core/transformer.ts
1920
+ function mkFields(register, fhirSchema, parentPath, elements, logger) {
1921
+ if (!elements) return void 0;
1922
+ const fields = {};
1923
+ for (const key of register.getAllElementKeys(elements)) {
1924
+ const path = [...parentPath, key];
1925
+ const elemSnapshot = register.resolveElementSnapshot(fhirSchema, path);
1926
+ if (isNestedElement(elemSnapshot)) {
1927
+ fields[key] = mkNestedField(register, fhirSchema, path, elemSnapshot, logger);
1928
+ } else {
1929
+ fields[key] = mkField(register, fhirSchema, path, elemSnapshot, logger);
1930
+ }
2031
1931
  }
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);
1932
+ return fields;
1933
+ }
1934
+ function extractFieldDependencies(fields) {
1935
+ const deps = [];
1936
+ for (const field of Object.values(fields)) {
1937
+ if ("type" in field && field.type) {
1938
+ deps.push(field.type);
1939
+ }
1940
+ if ("binding" in field && field.binding) {
1941
+ deps.push(field.binding);
2037
1942
  }
2038
1943
  }
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();
1944
+ return deps;
1945
+ }
1946
+ function isExtensionSchema(fhirSchema, _identifier) {
1947
+ if (fhirSchema.base === "Extension" || fhirSchema.base === "http://hl7.org/fhir/StructureDefinition/Extension") {
1948
+ return true;
2048
1949
  }
2049
- generateFamilyExports(exportList) {
2050
- this.line(`__all__ = [${exportList.map((e) => `'${e}'`).join(", ")}]`);
1950
+ if (fhirSchema.url?.includes("/extension/") || fhirSchema.url?.includes("-extension")) {
1951
+ return true;
2051
1952
  }
2052
- buildPyPackageName(packageName) {
2053
- const parts = packageName ? [snakeCase(packageName)] : [""];
2054
- return parts.join(".");
1953
+ if (fhirSchema.name?.toLowerCase().includes("extension")) {
1954
+ return true;
2055
1955
  }
2056
- pyFhirPackage(identifier) {
2057
- return this.pyFhirPackageByName(identifier.package);
1956
+ if (fhirSchema.type === "Extension") {
1957
+ return true;
2058
1958
  }
2059
- pyFhirPackageByName(name) {
2060
- return [this.opts.rootPackageName, this.buildPyPackageName(name)].join(".");
1959
+ return false;
1960
+ }
1961
+ async function transformValueSet(register, valueSet, logger) {
1962
+ if (!valueSet.url) throw new Error("ValueSet URL is required");
1963
+ const identifier = mkValueSetIdentifierByUrl(register, valueSet.package_meta, valueSet.url);
1964
+ const concept = extractValueSetConceptsByUrl(register, valueSet.package_meta, valueSet.url);
1965
+ return {
1966
+ identifier,
1967
+ description: valueSet.description,
1968
+ concept,
1969
+ compose: !concept ? valueSet.compose : void 0
1970
+ };
1971
+ }
1972
+ function extractDependencies(identifier, base, fields, nestedTypes) {
1973
+ const deps = [];
1974
+ if (base) deps.push(base);
1975
+ if (fields) deps.push(...extractFieldDependencies(fields));
1976
+ if (nestedTypes) deps.push(...extractNestedDependencies(nestedTypes));
1977
+ const uniqDeps = {};
1978
+ for (const dep of deps) {
1979
+ if (dep.url === identifier.url) continue;
1980
+ uniqDeps[dep.url] = dep;
2061
1981
  }
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(".");
1982
+ const localNestedTypeUrls = new Set(nestedTypes?.map((nt) => nt.identifier.url));
1983
+ const result = Object.values(uniqDeps).filter((e) => {
1984
+ if (isProfileIdentifier(identifier)) return true;
1985
+ if (!isNestedIdentifier(e)) return true;
1986
+ return !localNestedTypeUrls.has(e.url);
1987
+ }).sort((a, b) => a.url.localeCompare(b.url));
1988
+ return result.length > 0 ? result : void 0;
1989
+ }
1990
+ function transformFhirSchemaResource(register, fhirSchema, logger) {
1991
+ const identifier = mkIdentifier(fhirSchema);
1992
+ let base;
1993
+ if (fhirSchema.base && fhirSchema.type !== "Element") {
1994
+ const baseFs = register.resolveFs(
1995
+ fhirSchema.package_meta,
1996
+ register.ensureSpecializationCanonicalUrl(fhirSchema.base)
1997
+ );
1998
+ if (!baseFs) {
1999
+ throw new Error(
2000
+ `Base resource not found '${fhirSchema.base}' for <${fhirSchema.url}> from ${packageMetaToFhir(fhirSchema.package_meta)}`
2001
+ );
2068
2002
  }
2069
- return this.pyFhirPackage(identifier);
2003
+ base = mkIdentifier(baseFs);
2070
2004
  }
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];
2005
+ const fields = mkFields(register, fhirSchema, [], fhirSchema.elements, logger);
2006
+ const nested = mkNestedTypes(register, fhirSchema, logger);
2007
+ const dependencies = extractDependencies(identifier, base, fields, nested);
2008
+ const typeSchema = {
2009
+ identifier,
2010
+ base,
2011
+ fields,
2012
+ nested,
2013
+ description: fhirSchema.description,
2014
+ dependencies
2015
+ };
2016
+ const bindingSchemas = collectBindingSchemas(register, fhirSchema, logger);
2017
+ return [typeSchema, ...bindingSchemas];
2018
+ }
2019
+ async function transformFhirSchema(register, fhirSchema, logger) {
2020
+ const schemas = transformFhirSchemaResource(register, fhirSchema, logger);
2021
+ if (isExtensionSchema(fhirSchema, mkIdentifier(fhirSchema))) {
2022
+ const schema = schemas[0];
2023
+ if (!schema) throw new Error(`Expected schema to be defined`);
2024
+ schema.metadata = {
2025
+ isExtension: true
2026
+ // Mark as extension for file organization
2027
+ };
2078
2028
  }
2079
- };
2029
+ return schemas;
2030
+ }
2080
2031
 
2081
2032
  // src/fhir-types/hl7-fhir-r4-core/CodeSystem.ts
2082
2033
  var isCodeSystem = (resource) => {
@@ -2305,6 +2256,131 @@ var generateTypeSchemas = async (register, logger) => {
2305
2256
  }
2306
2257
  return fhirSchemas;
2307
2258
  };
2259
+ var mutableSelectFields = (schema, selectFields) => {
2260
+ const selectedFields = {};
2261
+ const selectPolimorphic = {};
2262
+ for (const fieldName of selectFields) {
2263
+ const field = schema.fields?.[fieldName];
2264
+ if (!schema.fields || !field) throw new Error(`Field ${fieldName} not found`);
2265
+ if (isChoiceDeclarationField(field)) {
2266
+ if (!selectPolimorphic[fieldName]) selectPolimorphic[fieldName] = {};
2267
+ selectPolimorphic[fieldName].declaration = field.choices;
2268
+ } else if (isChoiceInstanceField(field)) {
2269
+ const choiceName = field.choiceOf;
2270
+ if (!selectPolimorphic[choiceName]) selectPolimorphic[choiceName] = {};
2271
+ selectPolimorphic[choiceName].instances = [...selectPolimorphic[choiceName].instances ?? [], fieldName];
2272
+ } else {
2273
+ selectedFields[fieldName] = field;
2274
+ }
2275
+ }
2276
+ for (const [choiceName, { declaration, instances }] of Object.entries(selectPolimorphic)) {
2277
+ const choices = instances ?? declaration;
2278
+ assert3(choices);
2279
+ for (const choiceInstanceName of choices) {
2280
+ const field = schema.fields?.[choiceInstanceName];
2281
+ assert3(field);
2282
+ selectedFields[choiceInstanceName] = field;
2283
+ }
2284
+ const decl = schema.fields?.[choiceName];
2285
+ assert3(decl);
2286
+ selectedFields[choiceName] = { ...decl, choices };
2287
+ }
2288
+ schema.fields = selectedFields;
2289
+ };
2290
+ var mutableIgnoreFields = (schema, ignoreFields) => {
2291
+ for (const fieldName of ignoreFields) {
2292
+ const field = schema.fields?.[fieldName];
2293
+ if (!schema.fields || !field) throw new Error(`Field ${fieldName} not found`);
2294
+ if (schema.fields) {
2295
+ if (isChoiceDeclarationField(field)) {
2296
+ for (const choiceName of field.choices) {
2297
+ delete schema.fields[choiceName];
2298
+ }
2299
+ }
2300
+ if (isChoiceInstanceField(field)) {
2301
+ const choiceDeclaration = schema.fields[field.choiceOf];
2302
+ assert3(isChoiceDeclarationField(choiceDeclaration));
2303
+ choiceDeclaration.choices = choiceDeclaration.choices.filter((c) => c !== fieldName);
2304
+ if (choiceDeclaration.choices.length === 0) {
2305
+ delete schema.fields[field.choiceOf];
2306
+ }
2307
+ }
2308
+ delete schema.fields[fieldName];
2309
+ }
2310
+ }
2311
+ };
2312
+ var treeShakeTypeSchema = (schema, rule, _logger) => {
2313
+ schema = structuredClone(schema);
2314
+ if (isPrimitiveTypeSchema(schema) || isValueSetTypeSchema(schema) || isBindingSchema(schema)) return schema;
2315
+ if (rule.selectFields) {
2316
+ if (rule.ignoreFields) throw new Error("Cannot use both ignoreFields and selectFields in the same rule");
2317
+ mutableSelectFields(schema, rule.selectFields);
2318
+ }
2319
+ if (rule.ignoreFields) {
2320
+ if (rule.selectFields) throw new Error("Cannot use both ignoreFields and selectFields in the same rule");
2321
+ mutableIgnoreFields(schema, rule.ignoreFields);
2322
+ }
2323
+ if (schema.nested) {
2324
+ const usedTypes = /* @__PURE__ */ new Set();
2325
+ const collectUsedNestedTypes = (s) => {
2326
+ Object.values(s.fields ?? {}).filter(isNotChoiceDeclarationField).filter((f) => isNestedIdentifier(f.type)).forEach((f) => {
2327
+ const url = f.type.url;
2328
+ if (!usedTypes.has(url)) {
2329
+ usedTypes.add(url);
2330
+ const nestedTypeDef = schema.nested?.find((f2) => f2.identifier.url === url);
2331
+ assert3(nestedTypeDef);
2332
+ collectUsedNestedTypes(nestedTypeDef);
2333
+ }
2334
+ });
2335
+ };
2336
+ collectUsedNestedTypes(schema);
2337
+ schema.nested = schema.nested.filter((n) => usedTypes.has(n.identifier.url));
2338
+ }
2339
+ schema.dependencies = extractDependencies(schema.identifier, schema.base, schema.fields, schema.nested);
2340
+ return schema;
2341
+ };
2342
+ var treeShake = (tsIndex, treeShake2, { resolutionTree, logger }) => {
2343
+ const focusedSchemas = [];
2344
+ for (const [pkgId, requires] of Object.entries(treeShake2)) {
2345
+ for (const [url, rule] of Object.entries(requires)) {
2346
+ const schema = tsIndex.resolveByUrl(pkgId, url);
2347
+ if (!schema) throw new Error(`Schema not found for ${pkgId} ${url}`);
2348
+ const shaked2 = treeShakeTypeSchema(schema, rule);
2349
+ focusedSchemas.push(shaked2);
2350
+ }
2351
+ }
2352
+ const collectDeps = (schemas, acc) => {
2353
+ if (schemas.length === 0) return Object.values(acc);
2354
+ for (const schema of schemas) {
2355
+ acc[JSON.stringify(schema.identifier)] = schema;
2356
+ }
2357
+ const newSchemas = [];
2358
+ for (const schema of schemas) {
2359
+ if (isSpecializationTypeSchema(schema)) {
2360
+ if (!schema.dependencies) continue;
2361
+ schema.dependencies.forEach((dep) => {
2362
+ const depSchema = tsIndex.resolve(dep);
2363
+ if (!depSchema)
2364
+ throw new Error(
2365
+ `Dependent schema ${JSON.stringify(dep)} not found for ${JSON.stringify(schema.identifier)}`
2366
+ );
2367
+ const id = JSON.stringify(depSchema.identifier);
2368
+ if (!acc[id]) newSchemas.push(depSchema);
2369
+ });
2370
+ if (schema.nested) {
2371
+ for (const nest of schema.nested) {
2372
+ if (isNestedIdentifier(nest.identifier)) continue;
2373
+ const id = JSON.stringify(nest.identifier);
2374
+ if (!acc[id]) newSchemas.push(nest);
2375
+ }
2376
+ }
2377
+ }
2378
+ }
2379
+ return collectDeps(newSchemas, acc);
2380
+ };
2381
+ const shaked = collectDeps(focusedSchemas, {});
2382
+ return mkTypeSchemaIndex(shaked, { resolutionTree, logger });
2383
+ };
2308
2384
 
2309
2385
  // src/api/writer-generator/typescript.ts
2310
2386
  var primitiveType2tsType = {