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.
- package/LICENSE +15 -0
- package/README.md +28 -8
- package/out/generator/importManager.js +252 -0
- package/out/generator/index.js +12 -16
- package/out/generator/interfaceGenerator.js +81 -188
- package/out/generator/sdParser.js +14 -1
- package/out/generator/temp/1761673242558-4u3in1qziv2/generated/AdmissionAppointment.ts +94 -0
- package/out/generator/temp/1761673242558-4u3in1qziv2/generated/AdmissionAppointmentClass.ts +194 -0
- package/out/generator/temp/1761673242558-4u3in1qziv2/generated/AdmissionCheckIn.ts +29 -0
- package/out/generator/temp/1761673242558-4u3in1qziv2/generated/AdmissionCheckInClass.ts +194 -0
- package/out/generator/temp/1761673242558-4u3in1qziv2/generated/AdmissionCheckInNoCode.ts +29 -0
- package/out/generator/temp/1761673242558-4u3in1qziv2/generated/AdmissionCheckInNoCodeClass.ts +194 -0
- package/out/generator/temp/1761673242558-4u3in1qziv2/generated/AdmissionDataFlow.ts +51 -0
- package/out/generator/temp/1761673242558-4u3in1qziv2/generated/AdmissionDataFlowClass.ts +194 -0
- package/out/generator/temp/1761673242558-4u3in1qziv2/generated/AdmissionDevice.ts +67 -0
- package/out/generator/temp/1761673242558-4u3in1qziv2/generated/AdmissionDeviceClass.ts +199 -0
- package/out/generator/temp/1761673242558-4u3in1qziv2/generated/RandomSupport.ts +26 -0
- package/out/generator/temp/1761673242558-4u3in1qziv2/generated/valuesets/ValueSet-AdmissionFlowIds.ts +38 -0
- package/out/generator/temp/1761673242558-4u3in1qziv2/generated/valuesets/ValueSet-AdmissionReasonCodeGroup.ts +42 -0
- package/out/generator/temp/1761673242558-4u3in1qziv2/generated/valuesets/index.ts +18 -0
- package/out/generator/temp/1761673242558-4u3in1qziv2/package/.index.db +0 -0
- package/out/generator/temp/1761673242558-4u3in1qziv2/package/.index.json +234 -0
- package/out/generator/temp/1761673242558-4u3in1qziv2/package/CodeSystem-admission-flow-ids.json +1 -0
- package/out/generator/temp/1761673242558-4u3in1qziv2/package/CodeSystem-admission-reason-codes.json +1 -0
- package/out/generator/temp/1761673242558-4u3in1qziv2/package/ImplementationGuide-pink.admission.json +1 -0
- package/out/generator/temp/1761673242558-4u3in1qziv2/package/SearchParameter-SearchParameter-Appointment-AdmissionCheckIn.json +1 -0
- package/out/generator/temp/1761673242558-4u3in1qziv2/package/SearchParameter-SearchParameter-Appointment-AdmissionCheckInNoCode.json +1 -0
- package/out/generator/temp/1761673242558-4u3in1qziv2/package/SearchParameter-SearchParameter-Appointment-AdmissionUrl.json +1 -0
- package/out/generator/temp/1761673242558-4u3in1qziv2/package/SearchParameter-SearchParameter-Appointment-AdmissionUuid.json +1 -0
- package/out/generator/temp/1761673242558-4u3in1qziv2/package/StructureDefinition-AdmissionAppointment.json +1 -0
- package/out/generator/temp/1761673242558-4u3in1qziv2/package/StructureDefinition-AdmissionCheckIn.json +1 -0
- package/out/generator/temp/1761673242558-4u3in1qziv2/package/StructureDefinition-AdmissionCheckInNoCode.json +1 -0
- package/out/generator/temp/1761673242558-4u3in1qziv2/package/StructureDefinition-AdmissionDataFlow.json +1 -0
- package/out/generator/temp/1761673242558-4u3in1qziv2/package/StructureDefinition-AdmissionDevice.json +1 -0
- package/out/generator/temp/1761673242558-4u3in1qziv2/package/StructureDefinition-AdmissionDocumentMetadata.json +1 -0
- package/out/generator/temp/1761673242558-4u3in1qziv2/package/StructureDefinition-AdmissionFormData.json +1 -0
- package/out/generator/temp/1761673242558-4u3in1qziv2/package/StructureDefinition-AdmissionFormUrl.json +1 -0
- package/out/generator/temp/1761673242558-4u3in1qziv2/package/StructureDefinition-AdmissionFormUuid.json +1 -0
- package/out/generator/temp/1761673242558-4u3in1qziv2/package/StructureDefinition-AdmissionIntegrationPoints.json +1 -0
- package/out/generator/temp/1761673242558-4u3in1qziv2/package/StructureDefinition-AdmissionPatient.json +1 -0
- package/out/generator/temp/1761673242558-4u3in1qziv2/package/StructureDefinition-AdmissionPatientData.json +1 -0
- package/out/generator/temp/1761673242558-4u3in1qziv2/package/StructureDefinition-AdmissionRoutingRule.json +1 -0
- package/out/generator/temp/1761673242558-4u3in1qziv2/package/StructureDefinition-LocationTomlConfiguration.json +1 -0
- package/out/generator/temp/1761673242558-4u3in1qziv2/package/StructureDefinition-Pink-Location-With-Toml.json +1 -0
- package/out/generator/temp/1761673242558-4u3in1qziv2/package/StructureDefinition-PinkLocation.json +1 -0
- package/out/generator/temp/1761673242558-4u3in1qziv2/package/ValueSet-AdmissionFlowIdsValueSet.json +1 -0
- package/out/generator/temp/1761673242558-4u3in1qziv2/package/ValueSet-AdmissionReasonCodeGroup.json +1 -0
- package/out/generator/temp/1761673242558-4u3in1qziv2/package/example/AllergyIntolerance-Example-PenicillinAllergy.json +1 -0
- package/out/generator/temp/1761673242558-4u3in1qziv2/package/example/Appointment-Example-AdmissionAppointment.json +1 -0
- package/out/generator/temp/1761673242558-4u3in1qziv2/package/example/Condition-Example-DiabetesCondition.json +1 -0
- package/out/generator/temp/1761673242558-4u3in1qziv2/package/example/Device-Example-Surgery-Room.json +1 -0
- package/out/generator/temp/1761673242558-4u3in1qziv2/package/example/DocumentReference-Example-PreOperativeEvaluation.json +1 -0
- package/out/generator/temp/1761673242558-4u3in1qziv2/package/example/Location-Example-Location-With-Toml.json +1 -0
- package/out/generator/temp/1761673242558-4u3in1qziv2/package/example/Patient-Example-Patient.json +1 -0
- package/out/generator/temp/1761673242558-4u3in1qziv2/package/example/QuestionnaireResponse-Example-AdmissionFormResponse.json +1 -0
- package/out/generator/temp/1761673242558-4u3in1qziv2/package/other/spec.internals +313 -0
- package/out/generator/temp/1761673242558-4u3in1qziv2/package/other/validation-oo.json +1 -0
- package/out/generator/temp/1761673242558-4u3in1qziv2/package/other/validation-summary.json +1 -0
- package/out/generator/temp/1761673242558-4u3in1qziv2/package/package.json +23 -0
- package/out/generator/temp/1761673242558-4u3in1qziv2/package/xml/StructureDefinition-AdmissionAppointment.sch +22 -0
- package/out/generator/temp/1761673242558-4u3in1qziv2/package/xml/StructureDefinition-AdmissionCheckIn.sch +18 -0
- package/out/generator/temp/1761673242558-4u3in1qziv2/package/xml/StructureDefinition-AdmissionCheckInNoCode.sch +18 -0
- package/out/generator/temp/1761673242558-4u3in1qziv2/package/xml/StructureDefinition-AdmissionDevice.sch +12 -0
- package/out/generator/temp/1761673242558-4u3in1qziv2/package/xml/StructureDefinition-AdmissionFormUrl.sch +18 -0
- package/out/generator/temp/1761673242558-4u3in1qziv2/package/xml/StructureDefinition-AdmissionFormUuid.sch +18 -0
- package/out/generator/temp/1761673242558-4u3in1qziv2/package/xml/StructureDefinition-AdmissionPatient.sch +12 -0
- package/out/generator/temp/1761673242558-4u3in1qziv2/package/xml/StructureDefinition-Pink-Location-With-Toml.sch +26 -0
- package/out/generator/temp/1761673242558-4u3in1qziv2/package/xml/StructureDefinition-PinkLocation.sch +12 -0
- package/out/generator/utils.js +6 -2
- package/out/generator/validatorGenerator.js +41 -6
- package/out/generator/valueSetGenerator.js +1 -1
- 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
|
+
[](https://www.npmjs.com/package/babelfhir-ts)
|
|
4
|
+
[](https://opensource.org/licenses/ISC)
|
|
5
|
+
[](https://www.typescriptlang.org/)
|
|
6
|
+
[](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
|
-
|
|
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
|
|
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`
|
|
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.
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
102
|
+
```ts
|
|
103
|
+
/// <reference path="./fhir-r4.d.ts" />
|
|
104
|
+
```
|
|
100
105
|
|
|
101
|
-
|
|
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
|
+
}
|
package/out/generator/index.js
CHANGED
|
@@ -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
|
|
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);
|