babelfhir-ts 1.0.25 → 1.0.27

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.
Files changed (72) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +28 -8
  3. package/out/generator/importManager.js +252 -0
  4. package/out/generator/index.js +12 -16
  5. package/out/generator/interfaceGenerator.js +81 -188
  6. package/out/generator/sdParser.js +14 -1
  7. package/out/generator/temp/1761673242558-4u3in1qziv2/generated/AdmissionAppointment.ts +94 -0
  8. package/out/generator/temp/1761673242558-4u3in1qziv2/generated/AdmissionAppointmentClass.ts +194 -0
  9. package/out/generator/temp/1761673242558-4u3in1qziv2/generated/AdmissionCheckIn.ts +29 -0
  10. package/out/generator/temp/1761673242558-4u3in1qziv2/generated/AdmissionCheckInClass.ts +194 -0
  11. package/out/generator/temp/1761673242558-4u3in1qziv2/generated/AdmissionCheckInNoCode.ts +29 -0
  12. package/out/generator/temp/1761673242558-4u3in1qziv2/generated/AdmissionCheckInNoCodeClass.ts +194 -0
  13. package/out/generator/temp/1761673242558-4u3in1qziv2/generated/AdmissionDataFlow.ts +51 -0
  14. package/out/generator/temp/1761673242558-4u3in1qziv2/generated/AdmissionDataFlowClass.ts +194 -0
  15. package/out/generator/temp/1761673242558-4u3in1qziv2/generated/AdmissionDevice.ts +67 -0
  16. package/out/generator/temp/1761673242558-4u3in1qziv2/generated/AdmissionDeviceClass.ts +199 -0
  17. package/out/generator/temp/1761673242558-4u3in1qziv2/generated/RandomSupport.ts +26 -0
  18. package/out/generator/temp/1761673242558-4u3in1qziv2/generated/valuesets/ValueSet-AdmissionFlowIds.ts +38 -0
  19. package/out/generator/temp/1761673242558-4u3in1qziv2/generated/valuesets/ValueSet-AdmissionReasonCodeGroup.ts +42 -0
  20. package/out/generator/temp/1761673242558-4u3in1qziv2/generated/valuesets/index.ts +18 -0
  21. package/out/generator/temp/1761673242558-4u3in1qziv2/package/.index.db +0 -0
  22. package/out/generator/temp/1761673242558-4u3in1qziv2/package/.index.json +234 -0
  23. package/out/generator/temp/1761673242558-4u3in1qziv2/package/CodeSystem-admission-flow-ids.json +1 -0
  24. package/out/generator/temp/1761673242558-4u3in1qziv2/package/CodeSystem-admission-reason-codes.json +1 -0
  25. package/out/generator/temp/1761673242558-4u3in1qziv2/package/ImplementationGuide-pink.admission.json +1 -0
  26. package/out/generator/temp/1761673242558-4u3in1qziv2/package/SearchParameter-SearchParameter-Appointment-AdmissionCheckIn.json +1 -0
  27. package/out/generator/temp/1761673242558-4u3in1qziv2/package/SearchParameter-SearchParameter-Appointment-AdmissionCheckInNoCode.json +1 -0
  28. package/out/generator/temp/1761673242558-4u3in1qziv2/package/SearchParameter-SearchParameter-Appointment-AdmissionUrl.json +1 -0
  29. package/out/generator/temp/1761673242558-4u3in1qziv2/package/SearchParameter-SearchParameter-Appointment-AdmissionUuid.json +1 -0
  30. package/out/generator/temp/1761673242558-4u3in1qziv2/package/StructureDefinition-AdmissionAppointment.json +1 -0
  31. package/out/generator/temp/1761673242558-4u3in1qziv2/package/StructureDefinition-AdmissionCheckIn.json +1 -0
  32. package/out/generator/temp/1761673242558-4u3in1qziv2/package/StructureDefinition-AdmissionCheckInNoCode.json +1 -0
  33. package/out/generator/temp/1761673242558-4u3in1qziv2/package/StructureDefinition-AdmissionDataFlow.json +1 -0
  34. package/out/generator/temp/1761673242558-4u3in1qziv2/package/StructureDefinition-AdmissionDevice.json +1 -0
  35. package/out/generator/temp/1761673242558-4u3in1qziv2/package/StructureDefinition-AdmissionDocumentMetadata.json +1 -0
  36. package/out/generator/temp/1761673242558-4u3in1qziv2/package/StructureDefinition-AdmissionFormData.json +1 -0
  37. package/out/generator/temp/1761673242558-4u3in1qziv2/package/StructureDefinition-AdmissionFormUrl.json +1 -0
  38. package/out/generator/temp/1761673242558-4u3in1qziv2/package/StructureDefinition-AdmissionFormUuid.json +1 -0
  39. package/out/generator/temp/1761673242558-4u3in1qziv2/package/StructureDefinition-AdmissionIntegrationPoints.json +1 -0
  40. package/out/generator/temp/1761673242558-4u3in1qziv2/package/StructureDefinition-AdmissionPatient.json +1 -0
  41. package/out/generator/temp/1761673242558-4u3in1qziv2/package/StructureDefinition-AdmissionPatientData.json +1 -0
  42. package/out/generator/temp/1761673242558-4u3in1qziv2/package/StructureDefinition-AdmissionRoutingRule.json +1 -0
  43. package/out/generator/temp/1761673242558-4u3in1qziv2/package/StructureDefinition-LocationTomlConfiguration.json +1 -0
  44. package/out/generator/temp/1761673242558-4u3in1qziv2/package/StructureDefinition-Pink-Location-With-Toml.json +1 -0
  45. package/out/generator/temp/1761673242558-4u3in1qziv2/package/StructureDefinition-PinkLocation.json +1 -0
  46. package/out/generator/temp/1761673242558-4u3in1qziv2/package/ValueSet-AdmissionFlowIdsValueSet.json +1 -0
  47. package/out/generator/temp/1761673242558-4u3in1qziv2/package/ValueSet-AdmissionReasonCodeGroup.json +1 -0
  48. package/out/generator/temp/1761673242558-4u3in1qziv2/package/example/AllergyIntolerance-Example-PenicillinAllergy.json +1 -0
  49. package/out/generator/temp/1761673242558-4u3in1qziv2/package/example/Appointment-Example-AdmissionAppointment.json +1 -0
  50. package/out/generator/temp/1761673242558-4u3in1qziv2/package/example/Condition-Example-DiabetesCondition.json +1 -0
  51. package/out/generator/temp/1761673242558-4u3in1qziv2/package/example/Device-Example-Surgery-Room.json +1 -0
  52. package/out/generator/temp/1761673242558-4u3in1qziv2/package/example/DocumentReference-Example-PreOperativeEvaluation.json +1 -0
  53. package/out/generator/temp/1761673242558-4u3in1qziv2/package/example/Location-Example-Location-With-Toml.json +1 -0
  54. package/out/generator/temp/1761673242558-4u3in1qziv2/package/example/Patient-Example-Patient.json +1 -0
  55. package/out/generator/temp/1761673242558-4u3in1qziv2/package/example/QuestionnaireResponse-Example-AdmissionFormResponse.json +1 -0
  56. package/out/generator/temp/1761673242558-4u3in1qziv2/package/other/spec.internals +313 -0
  57. package/out/generator/temp/1761673242558-4u3in1qziv2/package/other/validation-oo.json +1 -0
  58. package/out/generator/temp/1761673242558-4u3in1qziv2/package/other/validation-summary.json +1 -0
  59. package/out/generator/temp/1761673242558-4u3in1qziv2/package/package.json +23 -0
  60. package/out/generator/temp/1761673242558-4u3in1qziv2/package/xml/StructureDefinition-AdmissionAppointment.sch +22 -0
  61. package/out/generator/temp/1761673242558-4u3in1qziv2/package/xml/StructureDefinition-AdmissionCheckIn.sch +18 -0
  62. package/out/generator/temp/1761673242558-4u3in1qziv2/package/xml/StructureDefinition-AdmissionCheckInNoCode.sch +18 -0
  63. package/out/generator/temp/1761673242558-4u3in1qziv2/package/xml/StructureDefinition-AdmissionDevice.sch +12 -0
  64. package/out/generator/temp/1761673242558-4u3in1qziv2/package/xml/StructureDefinition-AdmissionFormUrl.sch +18 -0
  65. package/out/generator/temp/1761673242558-4u3in1qziv2/package/xml/StructureDefinition-AdmissionFormUuid.sch +18 -0
  66. package/out/generator/temp/1761673242558-4u3in1qziv2/package/xml/StructureDefinition-AdmissionPatient.sch +12 -0
  67. package/out/generator/temp/1761673242558-4u3in1qziv2/package/xml/StructureDefinition-Pink-Location-With-Toml.sch +26 -0
  68. package/out/generator/temp/1761673242558-4u3in1qziv2/package/xml/StructureDefinition-PinkLocation.sch +12 -0
  69. package/out/generator/utils.js +6 -2
  70. package/out/generator/validatorGenerator.js +41 -6
  71. package/out/generator/valueSetGenerator.js +1 -1
  72. package/package.json +22 -2
package/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2025 Maximilian Nussbaumer
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package/README.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # BabelFHIR-TS
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/babelfhir-ts.svg)](https://www.npmjs.com/package/babelfhir-ts)
4
+ [![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](https://opensource.org/licenses/ISC)
5
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.8-blue)](https://www.typescriptlang.org/)
6
+ [![Node.js](https://img.shields.io/badge/Node.js-18%2B-green)](https://nodejs.org/)
7
+
3
8
  **BabelFHIR-TS** transforms FHIR® StructureDefinitions into production-ready TypeScript code with full type safety and built-in validation. Unlike generic FHIR type definitions, BabelFHIR-TS generates **profile-aware** interfaces that understand your Implementation Guide's constraints, extensions, and slicing rules.
4
9
 
5
10
  ### What you get
@@ -49,7 +54,7 @@ Download and process a package directly from a registry (defaults to `https://pa
49
54
  babelfhir-ts --package hl7.fhir.us.core@8.0.0
50
55
  ```
51
56
 
52
- Install a processed package into your current project:
57
+ Download, pocess and install a processed package into your current project:
53
58
 
54
59
  ```bash
55
60
  babelfhir-ts install hl7.fhir.us.core@8.0.0
@@ -78,11 +83,11 @@ Install `fhirpath` in your app if you plan to call `.validate()` or use the gene
78
83
 
79
84
  ### Module resolution
80
85
 
81
- Generated packages use **`moduleResolution: "node16"`** in their `tsconfig.json` to ensure proper ESM compatibility. This works with all modern TypeScript setups including:
86
+ Generated packages work with all modern TypeScript setups:
82
87
 
83
88
  - **Node.js 18+** with `"type": "module"` in `package.json`
84
89
  - **Bundlers** (Vite, esbuild, Webpack) with ESM output
85
- - **TypeScript 5.0+** with `node16`, `nodenext`, or `bundler` module resolution
90
+ - **TypeScript 5.0+** with any module resolution (`node16`, `nodenext`, or `bundler`)
86
91
 
87
92
  #### FHIR type imports
88
93
 
@@ -92,13 +97,13 @@ Generated code imports base FHIR types from `"fhir/r4"`:
92
97
  import { Appointment, Extension, CodeableConcept } from "fhir/r4";
93
98
  ```
94
99
 
95
- Since `@types/fhir` doesn't provide a `package.json` `exports` field, BabelFHIR-TS includes an **ambient module declaration** (`fhir-r4.d.ts`) in every generated package. This declaration file:
100
+ Since `@types/fhir` doesn't provide a `package.json` `exports` field, BabelFHIR-TS includes an **ambient module declaration** (`fhir-r4.d.ts`) in every generated package. The generated `index.d.ts` references this file:
96
101
 
97
- - Maps `"fhir/r4"` imports to the actual `@types/fhir` type definitions
98
- - Enables strict `node16`/`nodenext` module resolution without path configuration
99
- - Is automatically picked up by TypeScript—no `tsconfig.json` changes needed in your project
102
+ ```ts
103
+ /// <reference path="./fhir-r4.d.ts" />
104
+ ```
100
105
 
101
- > **Note**: If you're using `moduleResolution: "bundler"` in your own project, the ambient module declaration is still compatible and won't cause any issues.
106
+ This ensures TypeScript can resolve `fhir/r4` imports automatically without any configuration in your project's `tsconfig.json`.
102
107
 
103
108
  ## CLI reference
104
109
 
@@ -188,3 +193,18 @@ We continuously improve the generator based on real-world IG usage, and your fee
188
193
  ## License
189
194
 
190
195
  ISC © Maximilian Nussbaumer
196
+
197
+ ## Contributing
198
+
199
+ Contributions are welcome! Please read [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines on how to contribute to this project.
200
+
201
+ ## Security
202
+
203
+ For security issues, please see [SECURITY.md](SECURITY.md) for our security policy and how to report vulnerabilities.
204
+
205
+ ## Links
206
+
207
+ - [npm package](https://www.npmjs.com/package/babelfhir-ts)
208
+ - [GitHub repository](https://github.com/quotentiroler/BabelFHIR-ts)
209
+ - [Issue tracker](https://github.com/quotentiroler/BabelFHIR-ts/issues)
210
+ - [Changelog](CHANGELOG.md)
@@ -0,0 +1,252 @@
1
+ import fhirInterfaceNames from "./fhirInterfaces.json" with { type: 'json' };
2
+ import { sanitizeIdentifier } from './utils.js';
3
+ /**
4
+ * Manages TypeScript import statements for generated FHIR interfaces.
5
+ * Handles three types of imports:
6
+ * 1. FHIR base types from '@types/fhir' (via 'fhir/r4')
7
+ * 2. Custom local types (other generated profiles in the same package)
8
+ * 3. External profiled types (to be fetched/generated separately)
9
+ * 4. Path-based imports (custom relative paths, e.g., ValueSets in subdirectories)
10
+ */
11
+ export class ImportManager {
12
+ fhirImports = new Set();
13
+ customImports = new Set();
14
+ pathBasedImports = new Map(); // typeName -> relativePath
15
+ localInterfaceNames;
16
+ existingProfiles;
17
+ externalProfiles = new Map();
18
+ constructor(localInterfaceNames, existingProfiles) {
19
+ this.localInterfaceNames = localInterfaceNames;
20
+ this.existingProfiles = existingProfiles;
21
+ }
22
+ /**
23
+ * Adds a type to the appropriate import category.
24
+ *
25
+ * @param typeName - The TypeScript type name to import
26
+ * @param profileUrl - Optional profile URL for external profiles
27
+ * @param baseType - Optional base type for external profiles
28
+ * @param isFhirBase - Whether the base type is a FHIR type
29
+ */
30
+ addType(typeName, profileUrl, baseType, isFhirBase = false) {
31
+ if (!typeName)
32
+ return;
33
+ // Check if it's a nested interface of the current profile (defined locally in this file)
34
+ if (this.localInterfaceNames.has(typeName)) {
35
+ // Don't add to imports - defined in the same file
36
+ return;
37
+ }
38
+ // Check if it's a known FHIR type from @types/fhir
39
+ if (this.isFhirType(typeName)) {
40
+ this.fhirImports.add(typeName);
41
+ return;
42
+ }
43
+ // Check if it exists in our generated profiles/types
44
+ if (this.existingProfiles?.has(typeName)) {
45
+ this.customImports.add(typeName);
46
+ return;
47
+ }
48
+ // Check if this is a primitive type that shouldn't be fetched
49
+ if (this.isPrimitiveLikeType(typeName)) {
50
+ return;
51
+ }
52
+ // Add to custom imports (will be generated or fetched)
53
+ this.customImports.add(typeName);
54
+ // Track as external profile if we have profile metadata
55
+ if (profileUrl || baseType) {
56
+ this.registerExternalProfile(typeName, profileUrl, baseType, isFhirBase);
57
+ }
58
+ }
59
+ /**
60
+ * Registers an external profile that needs to be fetched or generated.
61
+ */
62
+ registerExternalProfile(rawTypeName, profileUrl, baseType, isFhirBase = false) {
63
+ if (!rawTypeName)
64
+ return;
65
+ const sanitizedTypeName = sanitizeIdentifier(rawTypeName);
66
+ const resolvedProfileUrl = profileUrl && profileUrl.length > 0
67
+ ? profileUrl
68
+ : `http://hl7.org/fhir/StructureDefinition/${sanitizedTypeName}`;
69
+ let meta = this.externalProfiles.get(sanitizedTypeName);
70
+ if (!meta) {
71
+ meta = { profileUrl: resolvedProfileUrl, baseTypes: new Map() };
72
+ this.externalProfiles.set(sanitizedTypeName, meta);
73
+ }
74
+ else if (profileUrl && profileUrl.length > 0 &&
75
+ meta.profileUrl.startsWith('http://hl7.org/fhir/StructureDefinition/')) {
76
+ // Update with more specific profile URL
77
+ meta.profileUrl = profileUrl;
78
+ }
79
+ // Track base type relationship
80
+ if (baseType && baseType !== 'any') {
81
+ const sanitizedBase = sanitizeIdentifier(baseType);
82
+ const existing = meta.baseTypes.get(sanitizedBase);
83
+ if (!existing) {
84
+ meta.baseTypes.set(sanitizedBase, { isFhir: isFhirBase });
85
+ }
86
+ else if (isFhirBase && !existing.isFhir) {
87
+ existing.isFhir = true;
88
+ }
89
+ }
90
+ }
91
+ /**
92
+ * Gets all registered external profile references.
93
+ */
94
+ getExternalProfiles() {
95
+ return this.externalProfiles;
96
+ }
97
+ /**
98
+ * Generates import statement lines for FHIR and custom types.
99
+ */
100
+ generateImportStatements() {
101
+ const statements = [];
102
+ // FHIR imports from 'fhir/r4'
103
+ if (this.fhirImports.size > 0) {
104
+ const sortedFhirImports = Array.from(this.fhirImports).sort();
105
+ statements.push(`import { ${sortedFhirImports.join(', ')} } from "fhir/r4";`);
106
+ }
107
+ // Path-based imports (e.g., ValueSets from subdirectories)
108
+ const sortedPathBasedImports = Array.from(this.pathBasedImports.entries()).sort((a, b) => a[0].localeCompare(b[0]));
109
+ for (const [typeName, relativePath] of sortedPathBasedImports) {
110
+ statements.push(`import { ${typeName} } from "${relativePath}";`);
111
+ }
112
+ // Custom local imports
113
+ const sortedCustomImports = Array.from(this.customImports).sort();
114
+ for (const type of sortedCustomImports) {
115
+ statements.push(`import { ${type} } from "./${type}";`);
116
+ }
117
+ return statements.join('\n');
118
+ }
119
+ /**
120
+ * Prunes unused imports from generated code.
121
+ * Removes import statements for types that aren't actually used.
122
+ */
123
+ pruneUnusedImports(output) {
124
+ // Prune unused FHIR imports
125
+ output = output.replace(/import \{([^}]+)\} from "fhir\/r4";?/g, (full, inside) => {
126
+ const used = [];
127
+ const specifiers = inside.split(',').map((s) => s.trim()).filter(Boolean);
128
+ for (const name of specifiers) {
129
+ const re = new RegExp(`\\b${name}\\b`);
130
+ if (re.test(output.replace(full, ''))) {
131
+ used.push(name);
132
+ }
133
+ }
134
+ return used.length ? `import { ${used.join(', ')} } from "fhir/r4";` : '';
135
+ });
136
+ // Prune unused custom imports (both path-based and standard custom imports)
137
+ output = output.replace(/import \{([^}]+)\} from "\.\/(.+?)";?/g, (full, inside, path) => {
138
+ const used = [];
139
+ const specifiers = inside.split(',').map((s) => s.trim()).filter(Boolean);
140
+ for (const name of specifiers) {
141
+ const re = new RegExp(`\\b${name}\\b`);
142
+ if (re.test(output.replace(full, ''))) {
143
+ used.push(name);
144
+ }
145
+ }
146
+ return used.length ? `import { ${used.join(', ')} } from "./${path}";` : '';
147
+ });
148
+ // Remove now-empty import lines and collapse multiple blank lines
149
+ output = output.replace(/^\s*import \{\s*\}.*$/mg, '').replace(/\n{3,}/g, '\n\n');
150
+ return output;
151
+ }
152
+ /**
153
+ * Reorganizes imports to prefer local types over FHIR types where appropriate.
154
+ * Moves locally-generated types from fhir/r4 imports to local imports.
155
+ */
156
+ reorganizeImports(output) {
157
+ if (!this.existingProfiles || this.existingProfiles.size === 0) {
158
+ return output;
159
+ }
160
+ // Extract ALL types from fhir/r4 imports (there may be multiple import lines)
161
+ const fhirImportRegex = /import \{([^}]+)\} from "fhir\/r4";/g;
162
+ const allFhirTypes = [];
163
+ let match;
164
+ while ((match = fhirImportRegex.exec(output)) !== null) {
165
+ const types = match[1].split(',').map(t => t.trim()).filter(Boolean);
166
+ allFhirTypes.push(...types);
167
+ }
168
+ if (allFhirTypes.length === 0) {
169
+ return output;
170
+ }
171
+ const localTypes = [];
172
+ const remainingFhirTypes = [];
173
+ for (const typeName of allFhirTypes) {
174
+ if (this.existingProfiles.has(typeName)) {
175
+ // Never move FhirResource to local imports; it must stay from fhir/r4
176
+ if (typeName !== 'FhirResource') {
177
+ localTypes.push(typeName);
178
+ }
179
+ else {
180
+ remainingFhirTypes.push(typeName);
181
+ }
182
+ }
183
+ else {
184
+ remainingFhirTypes.push(typeName);
185
+ }
186
+ }
187
+ // Remove ALL old fhir/r4 import lines
188
+ output = output.replace(/import \{[^}]+\} from "fhir\/r4";\n?/g, '');
189
+ // Add back the imports at the top
190
+ let newImports = '';
191
+ if (remainingFhirTypes.length > 0) {
192
+ newImports += `import { ${remainingFhirTypes.join(', ')} } from "fhir/r4";\n`;
193
+ }
194
+ for (const localType of localTypes) {
195
+ newImports += `import { ${localType} } from "./${localType}";\n`;
196
+ }
197
+ // Insert new imports after any existing custom imports or at the top
198
+ if (newImports) {
199
+ const customImportMatch = output.match(/^(import \{[^}]+\} from "\.\/[^"]+";?\n)+/m);
200
+ if (customImportMatch) {
201
+ // Insert after existing custom imports
202
+ output = output.replace(customImportMatch[0], customImportMatch[0] + newImports);
203
+ }
204
+ else {
205
+ // Insert at the very top
206
+ output = newImports + output;
207
+ }
208
+ }
209
+ return output;
210
+ }
211
+ /**
212
+ * Checks if a type is a known FHIR type from @types/fhir.
213
+ */
214
+ isFhirType(type) {
215
+ return type === 'FhirResource' || fhirInterfaceNames.includes(type);
216
+ }
217
+ /**
218
+ * Checks if a type name looks like a primitive that shouldn't be imported.
219
+ */
220
+ isPrimitiveLikeType(typeName) {
221
+ const primitiveTypeLowerCase = typeName.toLowerCase();
222
+ return [
223
+ 'integer', 'unsignedint', 'positiveint', 'decimal', 'boolean', 'string',
224
+ 'base64binary', 'instant', 'date', 'datetime', 'time', 'code', 'oid',
225
+ 'id', 'markdown', 'uri', 'url', 'canonical', 'uuid'
226
+ ].includes(primitiveTypeLowerCase);
227
+ }
228
+ /**
229
+ * Adds a specific FHIR type to imports.
230
+ */
231
+ addFhirType(typeName) {
232
+ if (this.isFhirType(typeName)) {
233
+ this.fhirImports.add(typeName);
234
+ }
235
+ }
236
+ /**
237
+ * Adds a specific custom local type to imports.
238
+ */
239
+ addCustomType(typeName) {
240
+ if (!this.localInterfaceNames.has(typeName)) {
241
+ this.customImports.add(typeName);
242
+ }
243
+ }
244
+ /**
245
+ * Adds an import with an explicit relative path (e.g., for ValueSets in subdirectories).
246
+ * @param typeName - The type name to import
247
+ * @param relativePath - The relative path without extension (e.g., "./valuesets/ValueSet-Example")
248
+ */
249
+ addPathBasedImport(typeName, relativePath) {
250
+ this.pathBasedImports.set(typeName, relativePath);
251
+ }
252
+ }
@@ -91,6 +91,15 @@ async function compileTypeScriptToJS(dir) {
91
91
  }
92
92
  catch { /* ignore */ }
93
93
  if (code === 0) {
94
+ // Add triple-slash directive to index.d.ts after compilation
95
+ const indexDtsPath = path.join(dir, 'index.d.ts');
96
+ if (fs.existsSync(indexDtsPath)) {
97
+ const content = fs.readFileSync(indexDtsPath, 'utf-8');
98
+ if (!content.startsWith('/// <reference')) {
99
+ const newContent = `/// <reference path="./fhir-r4.d.ts" />\n${content}`;
100
+ fs.writeFileSync(indexDtsPath, newContent, 'utf-8');
101
+ }
102
+ }
94
103
  // Remove .ts files after successful compilation
95
104
  const files = fs.readdirSync(dir, { recursive: true, withFileTypes: true });
96
105
  for (const file of files) {
@@ -462,27 +471,13 @@ export async function generateIntoPackage(packageArchivePath, outArchivePath, fl
462
471
  const generatedPackageJsonPath = path.join(outputDir, 'package.json');
463
472
  fs.writeFileSync(generatedPackageJsonPath, JSON.stringify(generatedPackageJson, null, 2));
464
473
  console.log(`Created package.json in generated folder: ${packageName}-generated@${packageVersion}`);
465
- // Create a tsconfig.json for Node16/NodeNext module resolution
466
- // This ensures fhir/r4 imports are resolved via the ambient module declaration
467
- const generatedTsConfig = {
468
- compilerOptions: {
469
- moduleResolution: 'node16',
470
- module: 'Node16',
471
- target: 'ES2020',
472
- strict: false,
473
- skipLibCheck: true,
474
- esModuleInterop: true
475
- }
476
- };
477
- const generatedTsConfigPath = path.join(outputDir, 'tsconfig.json');
478
- fs.writeFileSync(generatedTsConfigPath, JSON.stringify(generatedTsConfig, null, 2));
479
474
  // Copy fhir-r4.d.ts ambient module declaration to support fhir/r4 imports
480
475
  // The file is at out/fhir-r4.d.ts (root of compiled output)
481
476
  const fhirR4DtsSource = path.join(path.dirname(path.dirname(fileURLToPath(import.meta.url))), 'fhir-r4.d.ts');
482
477
  const fhirR4DtsDest = path.join(outputDir, 'fhir-r4.d.ts');
483
478
  if (fs.existsSync(fhirR4DtsSource)) {
484
479
  fs.copyFileSync(fhirR4DtsSource, fhirR4DtsDest);
485
- console.log('Copied fhir-r4.d.ts ambient module declaration for Node16/NodeNext support');
480
+ console.log('Copied fhir-r4.d.ts ambient module declaration');
486
481
  }
487
482
  else {
488
483
  console.warn('Warning: fhir-r4.d.ts not found. Run: npm run generate:fhir-module');
@@ -523,6 +518,7 @@ export async function generateForDirectory(inputDir, outputDir, flags) {
523
518
  fs.mkdirSync(outputDir, { recursive: true });
524
519
  // Load ValueSets from the directory
525
520
  const valueSets = readValueSetsFromDir(inputDir);
521
+ const valueSetCodesMap = readValueSetCodesFromDir(inputDir);
526
522
  console.log(`Loaded ${valueSets.size} ValueSets from ${inputDir} (${Array.from(valueSets.values()).filter(vs => vs.isSmall).length} suitable for union types)`);
527
523
  // Generate TypeScript files for ValueSets
528
524
  const valueSetOutputDir = path.join(outputDir, 'valuesets');
@@ -622,7 +618,7 @@ export async function generateForDirectory(inputDir, outputDir, flags) {
622
618
  const full = path.join(inputDir, jf);
623
619
  try {
624
620
  const raw = JSON.parse(fs.readFileSync(full, 'utf-8'));
625
- await processStructureDefinition(raw, { outputDir, fhirSourceHint: full, valueSets, existingStructureDefinitions, profileIdToName, flags });
621
+ await processStructureDefinition(raw, { outputDir, fhirSourceHint: full, valueSetCodesMap, valueSets, existingStructureDefinitions, profileIdToName, flags });
626
622
  }
627
623
  catch (err) {
628
624
  console.error(`Failed processing JSON ${jf}:`, err);