@atomic-ehr/codegen 0.0.1-canary.20251002114154.42f165c → 0.0.1-canary.20251003082149.3db28b4

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.
@@ -5,21 +5,12 @@
5
5
  */
6
6
  import type { FHIRSchemaElement } from "@atomic-ehr/fhirschema";
7
7
  import type { Register } from "@typeschema/register";
8
- import type { BindingTypeSchema, CanonicalUrl, PackageMeta, RichFHIRSchema } from "@typeschema/types";
9
- /**
10
- * Extract concepts from a ValueSet
11
- */
8
+ import type { BindingTypeSchema, CanonicalUrl, RichFHIRSchema } from "@typeschema/types";
12
9
  export declare function extractValueSetConcepts(valueSetUrl: CanonicalUrl, register: Register): {
13
10
  system: string;
14
11
  code: string;
15
12
  display?: string;
16
13
  }[] | undefined;
17
- /**
18
- * Build enum values from binding if applicable
19
- */
20
14
  export declare function buildEnum(element: FHIRSchemaElement, register: Register): string[] | undefined;
21
- export declare function generateBindingSchema(fhirSchema: RichFHIRSchema, path: string[], element: FHIRSchemaElement, register: Register, packageInfo?: PackageMeta): Promise<BindingTypeSchema | undefined>;
22
- /**
23
- * Collect all binding schemas from a FHIRSchema
24
- */
25
- export declare function collectBindingSchemas(fhirSchema: RichFHIRSchema, register: Register): Promise<BindingTypeSchema[]>;
15
+ export declare function generateBindingSchema(register: Register, fhirSchema: RichFHIRSchema, path: string[], element: FHIRSchemaElement): Promise<BindingTypeSchema | undefined>;
16
+ export declare function collectBindingSchemas(register: Register, fhirSchema: RichFHIRSchema): Promise<BindingTypeSchema[]>;
@@ -5,16 +5,12 @@
5
5
  */
6
6
  import { buildFieldType } from "./field-builder";
7
7
  import { dropVersionFromUrl, mkBindingIdentifier, mkValueSetIdentifier } from "./identifier";
8
- /**
9
- * Extract concepts from a ValueSet
10
- */
11
8
  export function extractValueSetConcepts(valueSetUrl, register) {
12
9
  try {
13
10
  const cleanUrl = dropVersionFromUrl(valueSetUrl) || valueSetUrl;
14
11
  const valueSet = register.resolveVs(cleanUrl);
15
12
  if (!valueSet)
16
13
  return undefined;
17
- // If expansion is available, use it
18
14
  if (valueSet.expansion?.contains) {
19
15
  return valueSet.expansion.contains.map((concept) => ({
20
16
  system: concept.system,
@@ -22,12 +18,10 @@ export function extractValueSetConcepts(valueSetUrl, register) {
22
18
  display: concept.display,
23
19
  }));
24
20
  }
25
- // Otherwise try to extract from compose
26
21
  const concepts = [];
27
22
  if (valueSet.compose?.include) {
28
23
  for (const include of valueSet.compose.include) {
29
24
  if (include.concept) {
30
- // Direct concept list
31
25
  for (const concept of include.concept) {
32
26
  concepts.push({
33
27
  system: include.system,
@@ -37,7 +31,6 @@ export function extractValueSetConcepts(valueSetUrl, register) {
37
31
  }
38
32
  }
39
33
  else if (include.system && !include.filter) {
40
- // Include all from CodeSystem
41
34
  try {
42
35
  const codeSystem = register.resolveAny(include.system);
43
36
  if (codeSystem?.concept) {
@@ -69,9 +62,7 @@ export function extractValueSetConcepts(valueSetUrl, register) {
69
62
  return undefined;
70
63
  }
71
64
  }
72
- /**
73
- * Build enum values from binding if applicable
74
- */
65
+ const MAX_ENUM_LENGTH = 100;
75
66
  export function buildEnum(element, register) {
76
67
  if (!element.binding)
77
68
  return undefined;
@@ -90,36 +81,30 @@ export function buildEnum(element, register) {
90
81
  if (!shouldGenerateEnum) {
91
82
  return undefined;
92
83
  }
93
- try {
94
- const concepts = extractValueSetConcepts(valueSet, register);
95
- if (!concepts || concepts.length === 0)
96
- return undefined;
97
- // Extract just the codes and filter out any empty/invalid ones
98
- const codes = concepts
99
- .map((c) => c.code)
100
- .filter((code) => code && typeof code === "string" && code.trim().length > 0);
101
- // Only return if we have valid codes and not too many (avoid huge enums)
102
- // Increased limit from 50 to 100 for better value set coverage
103
- return codes.length > 0 && codes.length <= 100 ? codes : undefined;
104
- }
105
- catch (error) {
106
- // Log the error for debugging but don't fail the generation
107
- console.debug(`Failed to extract enum values for ${valueSet}: ${error}`);
84
+ const concepts = extractValueSetConcepts(valueSet, register);
85
+ if (!concepts || concepts.length === 0)
86
+ return undefined;
87
+ const codes = concepts
88
+ .map((c) => c.code)
89
+ .filter((code) => code && typeof code === "string" && code.trim().length > 0);
90
+ if (codes.length > MAX_ENUM_LENGTH) {
91
+ console.warn(`Value set ${valueSet} has more than ${MAX_ENUM_LENGTH} codes, which may cause issues with code generation.`);
108
92
  return undefined;
109
93
  }
94
+ return codes.length > 0 ? codes : undefined;
110
95
  }
111
- export async function generateBindingSchema(fhirSchema, path, element, register, packageInfo) {
96
+ export async function generateBindingSchema(register, fhirSchema, path, element) {
112
97
  if (!element.binding?.valueSet)
113
98
  return undefined;
114
- const identifier = mkBindingIdentifier(fhirSchema, path, element.binding.bindingName, packageInfo);
115
- const fieldType = buildFieldType(fhirSchema, path, element, register, packageInfo);
116
- const valueSetIdentifier = mkValueSetIdentifier(element.binding.valueSet, undefined, packageInfo);
99
+ const identifier = mkBindingIdentifier(fhirSchema, path, element.binding.bindingName);
100
+ const fieldType = buildFieldType(register, fhirSchema, element);
101
+ const valueSetIdentifier = mkValueSetIdentifier(register, element.binding.valueSet);
117
102
  const dependencies = [];
118
103
  if (fieldType) {
119
104
  dependencies.push(fieldType);
120
105
  }
121
106
  dependencies.push(valueSetIdentifier);
122
- const enumValues = await buildEnum(element, register);
107
+ const enumValues = buildEnum(element, register);
123
108
  return {
124
109
  identifier,
125
110
  type: fieldType,
@@ -129,37 +114,30 @@ export async function generateBindingSchema(fhirSchema, path, element, register,
129
114
  dependencies,
130
115
  };
131
116
  }
132
- /**
133
- * Collect all binding schemas from a FHIRSchema
134
- */
135
- export async function collectBindingSchemas(fhirSchema, register) {
136
- const packageInfo = fhirSchema.package_meta;
137
- const bindings = [];
117
+ export async function collectBindingSchemas(register, fhirSchema) {
138
118
  const processedPaths = new Set();
139
- // Recursive function to process elements
140
- async function processElement(elements, parentPath) {
119
+ if (!fhirSchema.elements)
120
+ return [];
121
+ const bindings = [];
122
+ async function collectBindings(elements, parentPath) {
141
123
  for (const [key, element] of Object.entries(elements)) {
142
124
  const path = [...parentPath, key];
143
125
  const pathKey = path.join(".");
144
- // Skip if already processed
145
126
  if (processedPaths.has(pathKey))
146
127
  continue;
147
128
  processedPaths.add(pathKey);
148
- // Generate binding if present
149
129
  if (element.binding) {
150
- const binding = await generateBindingSchema(fhirSchema, path, element, register, packageInfo);
130
+ const binding = await generateBindingSchema(register, fhirSchema, path, element);
151
131
  if (binding) {
152
132
  bindings.push(binding);
153
133
  }
154
134
  }
155
135
  if (element.elements) {
156
- await processElement(element.elements, path);
136
+ await collectBindings(element.elements, path);
157
137
  }
158
138
  }
159
139
  }
160
- if (fhirSchema.elements) {
161
- await processElement(fhirSchema.elements, []);
162
- }
140
+ await collectBindings(fhirSchema.elements, []);
163
141
  bindings.sort((a, b) => a.identifier.name.localeCompare(b.identifier.name));
164
142
  const uniqueBindings = [];
165
143
  const seenUrls = new Set();
@@ -3,14 +3,13 @@
3
3
  *
4
4
  * Functions for transforming FHIRSchema elements into TypeSchema fields
5
5
  */
6
- import type { CanonicalManager } from "@atomic-ehr/fhir-canonical-manager";
7
6
  import type { FHIRSchemaElement } from "@atomic-ehr/fhirschema";
8
7
  import type { Register } from "@root/typeschema/register";
9
8
  import type { Field, Identifier, PackageMeta, RegularField, RichFHIRSchema } from "../types";
10
9
  export declare function isRequired(register: Register, fhirSchema: RichFHIRSchema, path: string[]): boolean;
11
10
  export declare function isExcluded(register: Register, fhirSchema: RichFHIRSchema, path: string[]): boolean;
12
11
  export declare const buildReferences: (element: FHIRSchemaElement, register: Register, _packageInfo?: PackageMeta) => Identifier[] | undefined;
13
- export declare function buildFieldType(fhirSchema: RichFHIRSchema, _path: string[], element: FHIRSchemaElement, _manager: ReturnType<typeof CanonicalManager>, packageInfo?: PackageMeta): Identifier | undefined;
12
+ export declare function buildFieldType(register: Register, fhirSchema: RichFHIRSchema, element: FHIRSchemaElement): Identifier | undefined;
14
13
  export declare const mkField: (register: Register, fhirSchema: RichFHIRSchema, path: string[], element: FHIRSchemaElement) => Field;
15
14
  export declare function isNestedElement(element: FHIRSchemaElement): boolean;
16
15
  export declare function mkNestedField(register: Register, fhirSchema: RichFHIRSchema, path: string[], element: FHIRSchemaElement): RegularField;
@@ -48,7 +48,7 @@ export const buildReferences = (element, register, _packageInfo) => {
48
48
  return mkIdentifier(fs);
49
49
  });
50
50
  };
51
- export function buildFieldType(fhirSchema, _path, element, _manager, packageInfo) {
51
+ export function buildFieldType(register, fhirSchema, element) {
52
52
  // Handle element reference (for slicing)
53
53
  if (element.elementReference) {
54
54
  const refPath = element.elementReference
@@ -60,20 +60,19 @@ export function buildFieldType(fhirSchema, _path, element, _manager, packageInfo
60
60
  return mkNestedIdentifier(fhirSchema, refPath);
61
61
  }
62
62
  }
63
- // Handle normal type
64
63
  if (element.type) {
65
- const typeUrl = element.type.includes("/")
66
- ? element.type
67
- : `http://hl7.org/fhir/StructureDefinition/${element.type}`;
68
- // Always create a type identifier, even if we can't resolve
69
64
  const kind = element.type.match(/^[a-z]/) ? "primitive-type" : "complex-type";
70
- const isStandardFhir = typeUrl.startsWith("http://hl7.org/fhir/");
65
+ const url = register.ensureCanonicalUrl(element.type);
66
+ const fieldFs = register.resolveFs(url);
67
+ if (!fieldFs) {
68
+ throw new Error(`Could not resolve field '${element.type}'`);
69
+ }
71
70
  return {
72
71
  kind: kind,
73
- package: isStandardFhir ? "hl7.fhir.r4.core" : packageInfo?.name || "undefined",
74
- version: isStandardFhir ? "4.0.1" : packageInfo?.version || "undefined",
72
+ package: fieldFs.package_meta.name,
73
+ version: fieldFs.package_meta.version,
75
74
  name: element.type,
76
- url: typeUrl,
75
+ url: url,
77
76
  };
78
77
  }
79
78
  return undefined;
@@ -82,13 +81,13 @@ export const mkField = (register, fhirSchema, path, element) => {
82
81
  let binding;
83
82
  let enumValues;
84
83
  if (element.binding) {
85
- binding = mkBindingIdentifier(fhirSchema, path, element.binding.bindingName, fhirSchema.package_meta);
84
+ binding = mkBindingIdentifier(fhirSchema, path, element.binding.bindingName);
86
85
  if (element.binding.strength === "required" && element.type === "code") {
87
86
  enumValues = buildEnum(element, register);
88
87
  }
89
88
  }
90
89
  return {
91
- type: buildFieldType(fhirSchema, path, element, register, fhirSchema.package_meta),
90
+ type: buildFieldType(register, fhirSchema, element),
92
91
  required: isRequired(register, fhirSchema, path),
93
92
  excluded: isExcluded(register, fhirSchema, path),
94
93
  reference: buildReferences(element, register, fhirSchema.package_meta),
@@ -4,9 +4,10 @@
4
4
  * Functions for creating TypeSchema identifiers from FHIRSchema entities
5
5
  */
6
6
  import type { FHIRSchema } from "@atomic-ehr/fhirschema";
7
- import type { BindingIdentifier, CanonicalUrl, Identifier, NestedIdentifier, PackageMeta, RichFHIRSchema, ValueSetTypeSchema } from "@typeschema/types";
8
- export declare function dropVersionFromUrl(url: CanonicalUrl | undefined): CanonicalUrl | undefined;
7
+ import type { BindingIdentifier, CanonicalUrl, Identifier, NestedIdentifier, RichFHIRSchema, ValueSetTypeSchema } from "@typeschema/types";
8
+ import type { Register } from "../register";
9
+ export declare function dropVersionFromUrl(url: CanonicalUrl): CanonicalUrl;
9
10
  export declare function mkIdentifier(fhirSchema: RichFHIRSchema): Identifier;
10
11
  export declare function mkNestedIdentifier(fhirSchema: RichFHIRSchema, path: string[]): NestedIdentifier;
11
- export declare function mkValueSetIdentifier(valueSetUrl: CanonicalUrl, valueSet: any, packageInfo?: PackageMeta): ValueSetTypeSchema["identifier"];
12
- export declare function mkBindingIdentifier(fhirSchema: FHIRSchema, path: string[], bindingName?: string, _packageInfo?: PackageMeta): BindingIdentifier;
12
+ export declare function mkValueSetIdentifier(register: Register, valueSetUrl: CanonicalUrl): ValueSetTypeSchema["identifier"];
13
+ export declare function mkBindingIdentifier(fhirSchema: FHIRSchema, path: string[], bindingName?: string): BindingIdentifier;
@@ -4,9 +4,8 @@
4
4
  * Functions for creating TypeSchema identifiers from FHIRSchema entities
5
5
  */
6
6
  export function dropVersionFromUrl(url) {
7
- if (!url)
8
- return undefined;
9
- return url.split("|")[0];
7
+ const baseUrl = url.split("|")[0];
8
+ return baseUrl ? baseUrl : url;
10
9
  }
11
10
  function determineKind(fhirSchema) {
12
11
  if (fhirSchema.derivation === "constraint")
@@ -38,12 +37,13 @@ export function mkNestedIdentifier(fhirSchema, path) {
38
37
  url: `${fhirSchema.url}#${nestedName}`,
39
38
  };
40
39
  }
41
- export function mkValueSetIdentifier(valueSetUrl, valueSet, packageInfo) {
42
- const cleanUrl = dropVersionFromUrl(valueSetUrl) || valueSetUrl;
40
+ export function mkValueSetIdentifier(register, valueSetUrl) {
41
+ valueSetUrl = dropVersionFromUrl(valueSetUrl);
42
+ const valueSet = register.resolveVs(valueSetUrl);
43
43
  // Generate a meaningful name from the URL instead of using potentially hash-like IDs
44
44
  let name = "unknown";
45
45
  // First try to get the last segment of the URL path
46
- const urlParts = cleanUrl.split("/");
46
+ const urlParts = valueSetUrl.split("/");
47
47
  const lastSegment = urlParts[urlParts.length - 1];
48
48
  if (lastSegment && lastSegment.length > 0) {
49
49
  // Convert kebab-case or snake_case to PascalCase for better readability
@@ -59,21 +59,24 @@ export function mkValueSetIdentifier(valueSetUrl, valueSet, packageInfo) {
59
59
  }
60
60
  return {
61
61
  kind: "value-set",
62
- package: packageInfo?.name || valueSet?.package_name || "undefined",
63
- version: packageInfo?.version || valueSet?.package_version || "undefined",
62
+ package: valueSet?.package_meta.name,
63
+ version: valueSet?.package_meta.version,
64
64
  name: name,
65
- url: cleanUrl,
65
+ url: valueSetUrl,
66
66
  };
67
67
  }
68
- export function mkBindingIdentifier(fhirSchema, path, bindingName, _packageInfo) {
68
+ export function mkBindingIdentifier(fhirSchema, path, bindingName) {
69
69
  const pathStr = path.join(".");
70
- const [name, url] = bindingName
71
- ? [bindingName, `urn:fhir:binding:${bindingName}`]
72
- : [`${fhirSchema.name}.${pathStr}_binding`, `${fhirSchema.url}#${pathStr}_binding`];
70
+ // NOTE: if SD specify `bindingName`, the definition should be shared between all
71
+ // packages. So we put it in the dedicated shared package.
72
+ // TODO: provide setting for `shared` package name.
73
+ const [pkg, name, url] = bindingName
74
+ ? [{ name: "shared", version: "1.0.0" }, bindingName, `urn:fhir:binding:${bindingName}`]
75
+ : [fhirSchema.package_meta, `${fhirSchema.name}.${pathStr}_binding`, `${fhirSchema.url}#${pathStr}_binding`];
73
76
  return {
74
77
  kind: "binding",
75
- package: fhirSchema.package_meta.name,
76
- version: fhirSchema.package_meta.version,
78
+ package: pkg.name,
79
+ version: pkg.version,
77
80
  name: name,
78
81
  url: url,
79
82
  };
@@ -236,7 +236,7 @@ async function transformResource(register, fhirSchema) {
236
236
  description: fhirSchema.description,
237
237
  dependencies,
238
238
  };
239
- const bindingSchemas = await collectBindingSchemas(fhirSchema, register);
239
+ const bindingSchemas = await collectBindingSchemas(register, fhirSchema);
240
240
  return [typeSchema, ...bindingSchemas];
241
241
  }
242
242
  export async function transformFHIRSchema(register, fhirSchema) {
@@ -247,7 +247,7 @@ export async function transformFHIRSchema(register, fhirSchema) {
247
247
  const profileSchema = await transformProfile(register, fhirSchema);
248
248
  results.push(profileSchema);
249
249
  // Collect binding schemas for profiles too
250
- const bindingSchemas = await collectBindingSchemas(fhirSchema, register);
250
+ const bindingSchemas = await collectBindingSchemas(register, fhirSchema);
251
251
  results.push(...bindingSchemas);
252
252
  return results;
253
253
  }
@@ -80,7 +80,7 @@ export class TypeSchemaGenerator {
80
80
  let valueSetFailedCount = 0;
81
81
  for (const vs of valueSets) {
82
82
  try {
83
- const valueSetSchema = await transformValueSet(vs, this.manager, packageInfo);
83
+ const valueSetSchema = await transformValueSet(vs, await registerFromManager(this.manager), packageInfo);
84
84
  if (valueSetSchema) {
85
85
  valueSetSchemas.push(valueSetSchema);
86
86
  valueSetConvertedCount++;
@@ -4,7 +4,7 @@ import type { CodegenLogger } from "@root/utils/codegen-logger";
4
4
  import type { CanonicalUrl, Name, PackageMeta, RichFHIRSchema } from "@typeschema/types";
5
5
  export type Register = {
6
6
  appendFs(fs: FHIRSchema): void;
7
- ensureCanonicalUrl(name: Name | CanonicalUrl): CanonicalUrl;
7
+ ensureCanonicalUrl(name: string | Name | CanonicalUrl): CanonicalUrl;
8
8
  resolveSd(canonicalUrl: CanonicalUrl): StructureDefinition | undefined;
9
9
  resolveFs(canonicalUrl: CanonicalUrl): RichFHIRSchema | undefined;
10
10
  resolveFsGenealogy(canonicalUrl: CanonicalUrl): RichFHIRSchema[];
@@ -1,6 +1,7 @@
1
1
  import { CanonicalManager } from "@atomic-ehr/fhir-canonical-manager";
2
2
  import * as fhirschema from "@atomic-ehr/fhirschema";
3
3
  import { enrichFHIRSchema } from "@typeschema/types";
4
+ // FIXME: working with multiple packages
4
5
  export const registerFromManager = async (manager, logger, packageInfo) => {
5
6
  const resources = await manager.search({});
6
7
  const any = {};
@@ -36,6 +37,9 @@ export const registerFromManager = async (manager, logger, packageInfo) => {
36
37
  const valueSets = {};
37
38
  for (const resource of resources) {
38
39
  if (resource.resourceType === "ValueSet") {
40
+ if (!resource.package_meta) {
41
+ resource.package_meta = packageInfo;
42
+ }
39
43
  valueSets[resource.url] = resource;
40
44
  }
41
45
  }
@@ -4,6 +4,7 @@
4
4
  * Functions for transforming FHIR ValueSets into TypeSchema format
5
5
  */
6
6
  import type { CanonicalManager } from "@atomic-ehr/fhir-canonical-manager";
7
+ import type { Register } from "../register";
7
8
  import type { PackageMeta, ValueSetTypeSchema } from "../types";
8
9
  /**
9
10
  * Extract all concepts from a ValueSet
@@ -16,4 +17,4 @@ export declare function extractValueSetConcepts(valueSet: any, manager: ReturnTy
16
17
  /**
17
18
  * Transform a FHIR ValueSet to TypeSchema format
18
19
  */
19
- export declare function transformValueSet(valueSet: any, manager: ReturnType<typeof CanonicalManager>, packageInfo?: PackageMeta): Promise<ValueSetTypeSchema>;
20
+ export declare function transformValueSet(valueSet: any, register: Register, packageInfo?: PackageMeta): Promise<ValueSetTypeSchema>;
@@ -116,8 +116,8 @@ export async function extractValueSetConcepts(valueSet, manager) {
116
116
  /**
117
117
  * Transform a FHIR ValueSet to TypeSchema format
118
118
  */
119
- export async function transformValueSet(valueSet, manager, packageInfo) {
120
- const identifier = mkValueSetIdentifier(valueSet.url, valueSet, packageInfo);
119
+ export async function transformValueSet(valueSet, register, packageInfo) {
120
+ const identifier = mkValueSetIdentifier(valueSet.url, valueSet);
121
121
  const typeSchemaValueSet = {
122
122
  identifier,
123
123
  };
@@ -126,7 +126,7 @@ export async function transformValueSet(valueSet, manager, packageInfo) {
126
126
  typeSchemaValueSet.description = valueSet.description;
127
127
  }
128
128
  // Try to extract concepts
129
- const concepts = await extractValueSetConcepts(valueSet, manager);
129
+ const concepts = await extractValueSetConcepts(valueSet, register);
130
130
  if (concepts && concepts.length > 0) {
131
131
  // If we can expand, include the concepts
132
132
  typeSchemaValueSet.concept = concepts;
@@ -152,7 +152,7 @@ export async function transformValueSet(valueSet, manager, packageInfo) {
152
152
  }
153
153
  if (item.valueSet) {
154
154
  for (const vsUrl of item.valueSet) {
155
- deps.push(mkValueSetIdentifier(vsUrl, undefined, packageInfo));
155
+ deps.push(mkValueSetIdentifier(register, vsUrl));
156
156
  }
157
157
  }
158
158
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atomic-ehr/codegen",
3
- "version": "0.0.1-canary.20251002114154.42f165c",
3
+ "version": "0.0.1-canary.20251003082149.3db28b4",
4
4
  "description": "Code generation tools for FHIR resources and TypeSchema definitions",
5
5
  "keywords": [
6
6
  "fhir",