babelfhir-ts 1.0.17 → 1.0.18
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/README.md +39 -3
- package/bin/babelfhir-ts.js +2 -10
- package/out/generator/classGenerator.js +2 -2
- package/out/generator/index.js +152 -12
- package/out/generator/interfaceGenerator.js +3 -3
- package/out/generator/temp/1761563744855-kptdikzwryj/generated/AdmissionAppointment.ts +94 -0
- package/out/generator/temp/1761563744855-kptdikzwryj/generated/AdmissionAppointmentClass.ts +194 -0
- package/out/generator/temp/1761563744855-kptdikzwryj/generated/AdmissionCheckIn.ts +29 -0
- package/out/generator/temp/1761563744855-kptdikzwryj/generated/AdmissionCheckInClass.ts +194 -0
- package/out/generator/temp/1761563744855-kptdikzwryj/generated/AdmissionCheckInNoCode.ts +29 -0
- package/out/generator/temp/1761563744855-kptdikzwryj/generated/AdmissionCheckInNoCodeClass.ts +194 -0
- package/out/generator/temp/1761563744855-kptdikzwryj/generated/RandomSupport.ts +26 -0
- package/out/generator/temp/1761563744855-kptdikzwryj/generated/valuesets/ValueSet-AdmissionFlowIds.ts +38 -0
- package/out/generator/temp/1761563744855-kptdikzwryj/generated/valuesets/ValueSet-AdmissionReasonCodeGroup.ts +42 -0
- package/out/generator/temp/1761563744855-kptdikzwryj/generated/valuesets/index.ts +18 -0
- package/out/generator/temp/1761563744855-kptdikzwryj/package/.index.db +0 -0
- package/out/generator/temp/1761563744855-kptdikzwryj/package/.index.json +234 -0
- package/out/generator/temp/1761563744855-kptdikzwryj/package/CodeSystem-admission-flow-ids.json +1 -0
- package/out/generator/temp/1761563744855-kptdikzwryj/package/CodeSystem-admission-reason-codes.json +1 -0
- package/out/generator/temp/1761563744855-kptdikzwryj/package/ImplementationGuide-pink.admission.json +1 -0
- package/out/generator/temp/1761563744855-kptdikzwryj/package/SearchParameter-SearchParameter-Appointment-AdmissionCheckIn.json +1 -0
- package/out/generator/temp/1761563744855-kptdikzwryj/package/SearchParameter-SearchParameter-Appointment-AdmissionCheckInNoCode.json +1 -0
- package/out/generator/temp/1761563744855-kptdikzwryj/package/SearchParameter-SearchParameter-Appointment-AdmissionUrl.json +1 -0
- package/out/generator/temp/1761563744855-kptdikzwryj/package/SearchParameter-SearchParameter-Appointment-AdmissionUuid.json +1 -0
- package/out/generator/temp/1761563744855-kptdikzwryj/package/StructureDefinition-AdmissionAppointment.json +1 -0
- package/out/generator/temp/1761563744855-kptdikzwryj/package/StructureDefinition-AdmissionCheckIn.json +1 -0
- package/out/generator/temp/1761563744855-kptdikzwryj/package/StructureDefinition-AdmissionCheckInNoCode.json +1 -0
- package/out/generator/temp/1761563744855-kptdikzwryj/package/StructureDefinition-AdmissionDataFlow.json +1 -0
- package/out/generator/temp/1761563744855-kptdikzwryj/package/StructureDefinition-AdmissionDevice.json +1 -0
- package/out/generator/temp/1761563744855-kptdikzwryj/package/StructureDefinition-AdmissionDocumentMetadata.json +1 -0
- package/out/generator/temp/1761563744855-kptdikzwryj/package/StructureDefinition-AdmissionFormData.json +1 -0
- package/out/generator/temp/1761563744855-kptdikzwryj/package/StructureDefinition-AdmissionFormUrl.json +1 -0
- package/out/generator/temp/1761563744855-kptdikzwryj/package/StructureDefinition-AdmissionFormUuid.json +1 -0
- package/out/generator/temp/1761563744855-kptdikzwryj/package/StructureDefinition-AdmissionIntegrationPoints.json +1 -0
- package/out/generator/temp/1761563744855-kptdikzwryj/package/StructureDefinition-AdmissionPatient.json +1 -0
- package/out/generator/temp/1761563744855-kptdikzwryj/package/StructureDefinition-AdmissionPatientData.json +1 -0
- package/out/generator/temp/1761563744855-kptdikzwryj/package/StructureDefinition-AdmissionRoutingRule.json +1 -0
- package/out/generator/temp/1761563744855-kptdikzwryj/package/StructureDefinition-LocationTomlConfiguration.json +1 -0
- package/out/generator/temp/1761563744855-kptdikzwryj/package/StructureDefinition-Pink-Location-With-Toml.json +1 -0
- package/out/generator/temp/1761563744855-kptdikzwryj/package/StructureDefinition-PinkLocation.json +1 -0
- package/out/generator/temp/1761563744855-kptdikzwryj/package/ValueSet-AdmissionFlowIdsValueSet.json +1 -0
- package/out/generator/temp/1761563744855-kptdikzwryj/package/ValueSet-AdmissionReasonCodeGroup.json +1 -0
- package/out/generator/temp/1761563744855-kptdikzwryj/package/example/AllergyIntolerance-Example-PenicillinAllergy.json +1 -0
- package/out/generator/temp/1761563744855-kptdikzwryj/package/example/Appointment-Example-AdmissionAppointment.json +1 -0
- package/out/generator/temp/1761563744855-kptdikzwryj/package/example/Condition-Example-DiabetesCondition.json +1 -0
- package/out/generator/temp/1761563744855-kptdikzwryj/package/example/Device-Example-Surgery-Room.json +1 -0
- package/out/generator/temp/1761563744855-kptdikzwryj/package/example/DocumentReference-Example-PreOperativeEvaluation.json +1 -0
- package/out/generator/temp/1761563744855-kptdikzwryj/package/example/Location-Example-Location-With-Toml.json +1 -0
- package/out/generator/temp/1761563744855-kptdikzwryj/package/example/Patient-Example-Patient.json +1 -0
- package/out/generator/temp/1761563744855-kptdikzwryj/package/example/QuestionnaireResponse-Example-AdmissionFormResponse.json +1 -0
- package/out/generator/temp/1761563744855-kptdikzwryj/package/other/spec.internals +313 -0
- package/out/generator/temp/1761563744855-kptdikzwryj/package/other/validation-oo.json +1 -0
- package/out/generator/temp/1761563744855-kptdikzwryj/package/other/validation-summary.json +1 -0
- package/out/generator/temp/1761563744855-kptdikzwryj/package/package.json +23 -0
- package/out/generator/temp/1761563744855-kptdikzwryj/package/xml/StructureDefinition-AdmissionAppointment.sch +22 -0
- package/out/generator/temp/1761563744855-kptdikzwryj/package/xml/StructureDefinition-AdmissionCheckIn.sch +18 -0
- package/out/generator/temp/1761563744855-kptdikzwryj/package/xml/StructureDefinition-AdmissionCheckInNoCode.sch +18 -0
- package/out/generator/temp/1761563744855-kptdikzwryj/package/xml/StructureDefinition-AdmissionDevice.sch +12 -0
- package/out/generator/temp/1761563744855-kptdikzwryj/package/xml/StructureDefinition-AdmissionFormUrl.sch +18 -0
- package/out/generator/temp/1761563744855-kptdikzwryj/package/xml/StructureDefinition-AdmissionFormUuid.sch +18 -0
- package/out/generator/temp/1761563744855-kptdikzwryj/package/xml/StructureDefinition-AdmissionPatient.sch +12 -0
- package/out/generator/temp/1761563744855-kptdikzwryj/package/xml/StructureDefinition-Pink-Location-With-Toml.sch +26 -0
- package/out/generator/temp/1761563744855-kptdikzwryj/package/xml/StructureDefinition-PinkLocation.sch +12 -0
- package/out/main.js +5 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,13 +10,14 @@
|
|
|
10
10
|
|
|
11
11
|
### What you get
|
|
12
12
|
|
|
13
|
-
- **Strongly typed interfaces** that merge profile constraints with base FHIR types (
|
|
13
|
+
- **Strongly typed interfaces** that merge profile constraints with base FHIR types (types come from `@types/fhir`)
|
|
14
|
+
- **Compiled output by default** — packages ship JavaScript (`.js`) plus TypeScript declarations (`.d.ts`)
|
|
14
15
|
- **Runtime validation** using FHIRPath expressions from the profile—no external validator required for basic checks
|
|
15
16
|
- **Type-safe extension handling** with proper slicing and nested extension support
|
|
16
17
|
- **Random data builders** for testing and development (when class generation is enabled)
|
|
17
18
|
- **Zero manual mapping**—consume any FHIR package or Implementation Guide directly from registries
|
|
18
|
-
- **Fast and lightweight**—
|
|
19
|
-
- **Install any FHIR profile as a node module**—use `babelfhir-ts install` to add Implementation Guides directly to your
|
|
19
|
+
- **Fast and lightweight**—minimal runtime deps; only `fhirpath` is required for validators
|
|
20
|
+
- **Install any FHIR profile as a node module**—use `babelfhir-ts install` to add Implementation Guides directly to your project
|
|
20
21
|
|
|
21
22
|
Use it to build FHIR-compliant APIs, validate incoming resources against profiles, or generate type-safe client SDKs from Implementation Guides.
|
|
22
23
|
|
|
@@ -71,6 +72,41 @@ const patient = USCorePatientClass.random();
|
|
|
71
72
|
const { errors, warnings } = await patient.validate();
|
|
72
73
|
```
|
|
73
74
|
|
|
75
|
+
## Using the generated code in your project
|
|
76
|
+
|
|
77
|
+
Generated profile packages installed via `babelfhir-ts install` are published as **compiled JavaScript with TypeScript declarations**:
|
|
78
|
+
|
|
79
|
+
- JavaScript for runtime: `index.js`, `*.js`
|
|
80
|
+
- Type declarations for IDE/TS: `index.d.ts`, `*.d.ts`
|
|
81
|
+
- Dependencies:
|
|
82
|
+
- `@types/fhir` is included as a dependency of the generated package (no extra setup in your app)
|
|
83
|
+
- `fhirpath` is a peer dependency (required only if you use the generated validators/classes)
|
|
84
|
+
|
|
85
|
+
Install `fhirpath` in your app if you plan to call `.validate()` or use the generated classes:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
npm install fhirpath
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Import and use:
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
// TypeScript or JavaScript
|
|
95
|
+
import { AdmissionAppointment, AdmissionAppointmentClass } from "pink.admission-generated";
|
|
96
|
+
|
|
97
|
+
// Types are available automatically via .d.ts
|
|
98
|
+
const appt: AdmissionAppointment = {
|
|
99
|
+
resourceType: "Appointment",
|
|
100
|
+
status: "booked",
|
|
101
|
+
start: "2025-10-23T14:00:00Z",
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
// Optional helper class + validation
|
|
105
|
+
const instance = AdmissionAppointmentClass.random();
|
|
106
|
+
const result = await new AdmissionAppointmentClass(appt).validate();
|
|
107
|
+
console.log(result.errors, result.warnings);
|
|
108
|
+
```
|
|
109
|
+
|
|
74
110
|
## CLI reference
|
|
75
111
|
|
|
76
112
|
```
|
package/bin/babelfhir-ts.js
CHANGED
|
@@ -9,24 +9,16 @@ import fs from 'fs';
|
|
|
9
9
|
const __filename = fileURLToPath(import.meta.url);
|
|
10
10
|
const __dirname = dirname(__filename);
|
|
11
11
|
|
|
12
|
-
// Use
|
|
13
|
-
const tsxBin = process.platform === 'win32' ? 'tsx.cmd' : 'tsx';
|
|
14
|
-
const tsxPath = join(__dirname, '..', 'node_modules', '.bin', tsxBin);
|
|
12
|
+
// Use node directly to run the compiled JavaScript
|
|
15
13
|
const mainJsPath = join(__dirname, '..', 'out', 'main.js');
|
|
16
14
|
|
|
17
|
-
// Check if tsx exists
|
|
18
|
-
if (!fs.existsSync(tsxPath)) {
|
|
19
|
-
console.error('tsx not found. Please install dependencies with: npm install');
|
|
20
|
-
process.exit(1);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
15
|
// Check if the compiled file exists
|
|
24
16
|
if (!fs.existsSync(mainJsPath)) {
|
|
25
17
|
console.error('Compiled main.js not found. Please ensure the package was built correctly.');
|
|
26
18
|
process.exit(1);
|
|
27
19
|
}
|
|
28
20
|
|
|
29
|
-
const child = spawn(
|
|
21
|
+
const child = spawn('node', [mainJsPath, ...process.argv.slice(2)], {
|
|
30
22
|
stdio: 'inherit',
|
|
31
23
|
cwd: process.cwd(), // Use the current working directory where the command was invoked
|
|
32
24
|
shell: process.platform === 'win32'
|
|
@@ -427,9 +427,9 @@ export function generateClass(className, interfaceName, baseResource, requiredFi
|
|
|
427
427
|
}
|
|
428
428
|
const helperImportList = Array.from(helperImports).join(', ');
|
|
429
429
|
return `
|
|
430
|
-
import { validate${interfaceName}, ${interfaceName} } from "./${interfaceName}";
|
|
430
|
+
import { validate${interfaceName}, ${interfaceName} } from "./${interfaceName}.js";
|
|
431
431
|
// Only import helpers actually referenced below (dynamic based on required fields)
|
|
432
|
-
import { ${helperImportList} } from "./RandomSupport";
|
|
432
|
+
import { ${helperImportList} } from "./RandomSupport.js";
|
|
433
433
|
|
|
434
434
|
// Helper emitted only when meta.profile enforcement is applicable (resource profiles)
|
|
435
435
|
${enableMetaProfile ? `type WithMeta = { meta?: { profile?: string[] } };
|
package/out/generator/index.js
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
2
|
import fs from 'fs';
|
|
3
|
-
import { generateClass } from './classGenerator';
|
|
4
|
-
import { generateInterfaces } from './interfaceGenerator';
|
|
5
|
-
import { extractPackage, readStructureDefinitionsFromDir, createPackageFromDir, readValueSetCodesFromDir, readValueSetsFromDir } from './packageParser';
|
|
6
|
-
import { fetchStructureDefinitions, parseStructureDefinition, fetchStructureDefinition, registerLocalStructureDefinitions, buildFhirChildTypeMap } from './sdParser';
|
|
7
|
-
import { writeInterfaceAndValidatorToFile, mergeFields, alignFields, ensureDirectoryExists, writeToFile, getBaseResource, downloadFile, sanitizeIdentifier, toPascalCase } from './utils';
|
|
8
|
-
import { generateValidateProfileFunction } from './validatorGenerator';
|
|
9
|
-
import { loadExamplesFromPackage, generateTestFile } from './testGenerator';
|
|
3
|
+
import { generateClass } from './classGenerator.js';
|
|
4
|
+
import { generateInterfaces } from './interfaceGenerator.js';
|
|
5
|
+
import { extractPackage, readStructureDefinitionsFromDir, createPackageFromDir, readValueSetCodesFromDir, readValueSetsFromDir } from './packageParser.js';
|
|
6
|
+
import { fetchStructureDefinitions, parseStructureDefinition, fetchStructureDefinition, registerLocalStructureDefinitions, buildFhirChildTypeMap } from './sdParser.js';
|
|
7
|
+
import { writeInterfaceAndValidatorToFile, mergeFields, alignFields, ensureDirectoryExists, writeToFile, getBaseResource, downloadFile, sanitizeIdentifier, toPascalCase } from './utils.js';
|
|
8
|
+
import { generateValidateProfileFunction } from './validatorGenerator.js';
|
|
9
|
+
import { loadExamplesFromPackage, generateTestFile } from './testGenerator.js';
|
|
10
10
|
import { generateValueSetTypeScript, generateValueSetRegistry } from './valueSetGenerator.js';
|
|
11
|
-
import { logger } from '../logger';
|
|
12
|
-
import fhirInterfaceNames from './fhirInterfaces.json';
|
|
11
|
+
import { logger } from '../logger.js';
|
|
12
|
+
import fhirInterfaceNames from './fhirInterfaces.json' with { type: 'json' };
|
|
13
|
+
import { spawn } from 'child_process';
|
|
13
14
|
/** Ensure RandomSupport.ts exists in a generation output directory */
|
|
14
15
|
function ensureRandomSupportFile(dir) {
|
|
15
16
|
const filePath = path.join(dir, 'RandomSupport.ts');
|
|
@@ -54,6 +55,118 @@ export function skeletonQuantity(): Quantity { return { value: randomInt(1,100),
|
|
|
54
55
|
fs.writeFileSync(filePath, content, 'utf-8');
|
|
55
56
|
}
|
|
56
57
|
}
|
|
58
|
+
/** Compile TypeScript files to JavaScript with declaration files */
|
|
59
|
+
async function compileTypeScriptToJS(dir) {
|
|
60
|
+
return new Promise((resolve, reject) => {
|
|
61
|
+
const tscCmd = process.platform === 'win32' ? 'tsc.cmd' : 'tsc';
|
|
62
|
+
// Create a temporary tsconfig.json for compilation
|
|
63
|
+
const tsconfig = {
|
|
64
|
+
compilerOptions: {
|
|
65
|
+
target: 'ES2020',
|
|
66
|
+
module: 'ES2020',
|
|
67
|
+
moduleResolution: 'node',
|
|
68
|
+
declaration: true,
|
|
69
|
+
outDir: dir,
|
|
70
|
+
rootDir: dir,
|
|
71
|
+
skipLibCheck: true,
|
|
72
|
+
esModuleInterop: true,
|
|
73
|
+
allowSyntheticDefaultImports: true,
|
|
74
|
+
strict: false,
|
|
75
|
+
resolveJsonModule: true,
|
|
76
|
+
types: ['node']
|
|
77
|
+
},
|
|
78
|
+
include: [`${dir}/**/*.ts`],
|
|
79
|
+
exclude: ['node_modules']
|
|
80
|
+
};
|
|
81
|
+
const tsconfigPath = path.join(dir, 'tsconfig.temp.json');
|
|
82
|
+
fs.writeFileSync(tsconfigPath, JSON.stringify(tsconfig, null, 2));
|
|
83
|
+
const child = spawn(tscCmd, ['-p', tsconfigPath], {
|
|
84
|
+
stdio: 'inherit',
|
|
85
|
+
shell: true
|
|
86
|
+
});
|
|
87
|
+
child.on('exit', (code) => {
|
|
88
|
+
// Clean up temp tsconfig
|
|
89
|
+
try {
|
|
90
|
+
fs.unlinkSync(tsconfigPath);
|
|
91
|
+
}
|
|
92
|
+
catch { /* ignore */ }
|
|
93
|
+
if (code === 0) {
|
|
94
|
+
// Remove .ts files after successful compilation
|
|
95
|
+
const files = fs.readdirSync(dir, { recursive: true, withFileTypes: true });
|
|
96
|
+
for (const file of files) {
|
|
97
|
+
if (file.isFile() && file.name.endsWith('.ts') && !file.name.endsWith('.d.ts')) {
|
|
98
|
+
const fullPath = path.join(file.path || dir, file.name);
|
|
99
|
+
fs.unlinkSync(fullPath);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
resolve();
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
reject(new Error(`TypeScript compilation failed with exit code ${code}`));
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
child.on('error', (err) => {
|
|
109
|
+
try {
|
|
110
|
+
fs.unlinkSync(tsconfigPath);
|
|
111
|
+
}
|
|
112
|
+
catch { /* ignore */ }
|
|
113
|
+
reject(new Error(`Failed to run tsc: ${err.message}`));
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
/** Generate index.ts that exports all generated interfaces and classes */
|
|
118
|
+
async function generateIndexFile(dir) {
|
|
119
|
+
const files = fs.readdirSync(dir, { withFileTypes: true });
|
|
120
|
+
const exports = [];
|
|
121
|
+
const exportedNames = new Set();
|
|
122
|
+
// First pass: collect all files and their exports
|
|
123
|
+
const fileExports = new Map();
|
|
124
|
+
for (const file of files) {
|
|
125
|
+
if (file.isFile() && file.name.endsWith('.ts') && file.name !== 'index.ts' && file.name !== 'RandomSupport.ts') {
|
|
126
|
+
const filePath = path.join(dir, file.name);
|
|
127
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
128
|
+
const names = new Set();
|
|
129
|
+
// Extract export names (interfaces, classes, functions, types)
|
|
130
|
+
const exportMatches = content.matchAll(/export\s+(?:interface|class|type|const|function)\s+(\w+)/g);
|
|
131
|
+
for (const match of exportMatches) {
|
|
132
|
+
names.add(match[1]);
|
|
133
|
+
}
|
|
134
|
+
fileExports.set(file.name, names);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// Second pass: only export from files that have unique exports or are primary files
|
|
138
|
+
for (const file of files) {
|
|
139
|
+
if (file.isFile() && file.name.endsWith('.ts') && file.name !== 'index.ts' && file.name !== 'RandomSupport.ts') {
|
|
140
|
+
const baseName = file.name.replace('.ts', '');
|
|
141
|
+
const fileNames = fileExports.get(file.name) || new Set();
|
|
142
|
+
// Check if this file's exports would conflict with already exported names
|
|
143
|
+
let hasConflict = false;
|
|
144
|
+
for (const name of fileNames) {
|
|
145
|
+
if (exportedNames.has(name)) {
|
|
146
|
+
hasConflict = true;
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
// Only export if no conflicts, or if it's a Class file (which are primary)
|
|
151
|
+
if (!hasConflict || file.name.endsWith('Class.ts')) {
|
|
152
|
+
exports.push(`export * from './${baseName}.js';`);
|
|
153
|
+
// Mark all names from this file as exported
|
|
154
|
+
for (const name of fileNames) {
|
|
155
|
+
exportedNames.add(name);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
// Also export from valuesets subfolder if it exists
|
|
161
|
+
const valueSetsDir = path.join(dir, 'valuesets');
|
|
162
|
+
if (fs.existsSync(valueSetsDir)) {
|
|
163
|
+
exports.push(`export * from './valuesets/index.js';`);
|
|
164
|
+
}
|
|
165
|
+
// Sort exports alphabetically for consistency
|
|
166
|
+
exports.sort();
|
|
167
|
+
const indexContent = `// Auto-generated index file - exports all generated FHIR profiles\n${exports.join('\n')}\n`;
|
|
168
|
+
fs.writeFileSync(path.join(dir, 'index.ts'), indexContent, 'utf-8');
|
|
169
|
+
}
|
|
57
170
|
// Reusable processor for a single StructureDefinition
|
|
58
171
|
async function processStructureDefinition(sd, ctx) {
|
|
59
172
|
const { outputDir, fhirSourceHint, valueSetCodesMap, valueSets, examplesMap, existingStructureDefinitions, profileIdToName, flags, fhirChildTypeMap } = ctx;
|
|
@@ -332,15 +445,42 @@ export async function generateIntoPackage(packageArchivePath, outArchivePath, fl
|
|
|
332
445
|
version: packageVersion,
|
|
333
446
|
description: `Generated TypeScript interfaces for ${packageName}`,
|
|
334
447
|
type: 'module',
|
|
335
|
-
main: 'index.
|
|
336
|
-
types: 'index.ts',
|
|
448
|
+
main: 'index.js',
|
|
449
|
+
types: 'index.d.ts',
|
|
337
450
|
scripts: {},
|
|
338
451
|
keywords: ['fhir', 'typescript', 'generated'],
|
|
339
|
-
license: 'MIT'
|
|
452
|
+
license: 'MIT',
|
|
453
|
+
// fhirpath is required at runtime by validators; keep as peer so host decides version
|
|
454
|
+
peerDependencies: {
|
|
455
|
+
'fhirpath': '^4.0.0'
|
|
456
|
+
},
|
|
457
|
+
// Type definitions for FHIR are needed for the .d.ts files
|
|
458
|
+
dependencies: {
|
|
459
|
+
'@types/fhir': '^0.0.41'
|
|
460
|
+
}
|
|
340
461
|
};
|
|
341
462
|
const generatedPackageJsonPath = path.join(outputDir, 'package.json');
|
|
342
463
|
fs.writeFileSync(generatedPackageJsonPath, JSON.stringify(generatedPackageJson, null, 2));
|
|
343
464
|
console.log(`Created package.json in generated folder: ${packageName}-generated@${packageVersion}`);
|
|
465
|
+
// Create a tsconfig.json for better IDE support with bundler moduleResolution
|
|
466
|
+
const generatedTsConfig = {
|
|
467
|
+
compilerOptions: {
|
|
468
|
+
moduleResolution: 'bundler',
|
|
469
|
+
module: 'ES2020',
|
|
470
|
+
target: 'ES2020',
|
|
471
|
+
strict: false,
|
|
472
|
+
skipLibCheck: true,
|
|
473
|
+
esModuleInterop: true
|
|
474
|
+
}
|
|
475
|
+
};
|
|
476
|
+
const generatedTsConfigPath = path.join(outputDir, 'tsconfig.json');
|
|
477
|
+
fs.writeFileSync(generatedTsConfigPath, JSON.stringify(generatedTsConfig, null, 2));
|
|
478
|
+
// Generate index.ts that exports all interfaces
|
|
479
|
+
console.log('Generating index.ts exports...');
|
|
480
|
+
await generateIndexFile(outputDir);
|
|
481
|
+
// Compile TypeScript to JavaScript
|
|
482
|
+
console.log('Compiling TypeScript to JavaScript...');
|
|
483
|
+
await compileTypeScriptToJS(outputDir);
|
|
344
484
|
const finalArchive = outArchivePath || deriveOutputArchiveName(packageArchivePath);
|
|
345
485
|
await createPackageFromDir(extractedRoot, finalArchive);
|
|
346
486
|
return finalArchive;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import fhirInterfaceNames from "./fhirInterfaces.json";
|
|
2
|
-
import { capitalize, sanitizeIdentifier, toPascalCase } from './utils';
|
|
1
|
+
import fhirInterfaceNames from "./fhirInterfaces.json" with { type: 'json' };
|
|
2
|
+
import { capitalize, sanitizeIdentifier, toPascalCase } from './utils.js';
|
|
3
3
|
import { generateCodeUnionType, getUniformSystem } from './vsParser.js';
|
|
4
|
-
import { logger } from '../logger';
|
|
4
|
+
import { logger } from '../logger.js';
|
|
5
5
|
export function generateInterfaces(interfaceName, newFields, baseResource, baseFields = [], valueSets, resourceType, existingProfiles, fhirChildTypeMap) {
|
|
6
6
|
const debug = (...args) => { if (process.env.DEBUG_FHIR_GEN === 'true')
|
|
7
7
|
console.log('[gen:interfaces]', ...args); };
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { Appointment, Extension } from "fhir/r4";
|
|
2
|
+
|
|
3
|
+
export interface AdmissionCheckInNoCode extends Extension { url: 'http://fhir.pink-protocol.io/StructureDefinition/AdmissionCheckInNoCode' }
|
|
4
|
+
|
|
5
|
+
export interface AdmissionCheckIn extends Extension { url: 'http://fhir.pink-protocol.io/StructureDefinition/AdmissionCheckIn' }
|
|
6
|
+
|
|
7
|
+
export interface AdmissionFormUrl extends Extension { url: 'http://fhir.pink-protocol.io/StructureDefinition/AdmissionFormUrl' }
|
|
8
|
+
|
|
9
|
+
export interface AdmissionFormUuid extends Extension { url: 'http://fhir.pink-protocol.io/StructureDefinition/AdmissionFormUuid' }
|
|
10
|
+
|
|
11
|
+
export interface AdmissionAppointment extends Appointment {
|
|
12
|
+
start: string;
|
|
13
|
+
|
|
14
|
+
extension?: (Extension | AdmissionFormUuid | AdmissionFormUrl | AdmissionCheckIn | AdmissionCheckInNoCode)[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
import fhirpath from "fhirpath";
|
|
19
|
+
|
|
20
|
+
export async function validateAdmissionAppointment(resource: AdmissionAppointment): Promise<{ errors: string[], warnings: string[] }> {
|
|
21
|
+
const errors: string[] = [];
|
|
22
|
+
const warnings: string[] = [];
|
|
23
|
+
|
|
24
|
+
const result0 = await Promise.resolve(fhirpath.evaluate(resource, "start.exists() = end.exists()", { resource }));
|
|
25
|
+
if (!result0.every(Boolean)) {
|
|
26
|
+
errors.push("Constraint violation: Either start and end are specified, or neither");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const result1 = await Promise.resolve(fhirpath.evaluate(resource, "(start.exists() and end.exists()) or (status in ('proposed' | 'cancelled' | 'waitlist'))", { resource }));
|
|
30
|
+
if (!result1.every(Boolean)) {
|
|
31
|
+
errors.push("Constraint violation: Only proposed or cancelled appointments can be missing start/end dates");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const result2 = await Promise.resolve(fhirpath.evaluate(resource, "Appointment.cancelationReason.exists() implies (Appointment.status='no-show' or Appointment.status='cancelled')", { resource }));
|
|
35
|
+
if (!result2.every(Boolean)) {
|
|
36
|
+
errors.push("Constraint violation: Cancelation reason is only used for appointments that have been cancelled, or no-show");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const result3 = await Promise.resolve(fhirpath.evaluate(resource, "contained.contained.empty()", { resource }));
|
|
40
|
+
if (!result3.every(Boolean)) {
|
|
41
|
+
errors.push("Constraint violation: If the resource is contained in another resource, it SHALL NOT contain nested Resources");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const result4 = await Promise.resolve(fhirpath.evaluate(resource, "contained.where((('#'+id in (%resource.descendants().reference | %resource.descendants().as(canonical) | %resource.descendants().as(uri) | %resource.descendants().as(url))) or descendants().where(reference = '#').exists() or descendants().where(as(canonical) = '#').exists() or descendants().where(as(canonical) = '#').exists()).not()).trace('unmatched', id).empty()", { resource }));
|
|
45
|
+
if (!result4.every(Boolean)) {
|
|
46
|
+
errors.push("Constraint violation: If the resource is contained in another resource, it SHALL be referred to from elsewhere in the resource or SHALL refer to the containing resource");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const result5 = await Promise.resolve(fhirpath.evaluate(resource, "contained.meta.versionId.empty() and contained.meta.lastUpdated.empty()", { resource }));
|
|
50
|
+
if (!result5.every(Boolean)) {
|
|
51
|
+
errors.push("Constraint violation: If a resource is contained in another resource, it SHALL NOT have a meta.versionId or a meta.lastUpdated");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const result6 = await Promise.resolve(fhirpath.evaluate(resource, "contained.meta.security.empty()", { resource }));
|
|
55
|
+
if (!result6.every(Boolean)) {
|
|
56
|
+
errors.push("Constraint violation: If a resource is contained in another resource, it SHALL NOT have a security label");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const result7 = await Promise.resolve(fhirpath.evaluate(resource, "text.`div`.exists()", { resource }));
|
|
60
|
+
if (!result7.every(Boolean)) {
|
|
61
|
+
warnings.push("Constraint violation: A resource should have narrative for robust management");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const result8 = await Promise.resolve(fhirpath.evaluate(resource, "hasValue() or (children().count() > id.count())", { resource }));
|
|
65
|
+
if (!result8.every(Boolean)) {
|
|
66
|
+
errors.push("Constraint violation: All FHIR elements must have a @value or children");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const result9 = await Promise.resolve(fhirpath.evaluate(resource, "type.exists() or actor.exists()", { resource }));
|
|
70
|
+
if (!result9.every(Boolean)) {
|
|
71
|
+
errors.push("Constraint violation: Either the type or actor on the participant SHALL be specified");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const result10 = await Promise.resolve(fhirpath.evaluate(resource, "status.exists()", { resource }));
|
|
75
|
+
if (!result10.every(Boolean)) {
|
|
76
|
+
errors.push("Constraint violation: status must be present");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const result11 = await Promise.resolve(fhirpath.evaluate(resource, "start.exists()", { resource }));
|
|
80
|
+
if (!result11.every(Boolean)) {
|
|
81
|
+
errors.push("Constraint violation: start must be present");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const result12 = await Promise.resolve(fhirpath.evaluate(resource, "participant.exists()", { resource }));
|
|
85
|
+
if (!result12.every(Boolean)) {
|
|
86
|
+
errors.push("Constraint violation: participant must be present");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const result13 = await Promise.resolve(fhirpath.evaluate(resource, "participant.status.exists()", { resource }));
|
|
90
|
+
if (!result13.every(Boolean)) {
|
|
91
|
+
errors.push("Constraint violation: participant.status must be present");
|
|
92
|
+
}
|
|
93
|
+
return { errors, warnings };
|
|
94
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
|
|
2
|
+
import { validateAdmissionAppointment, AdmissionAppointment } from "./AdmissionAppointment.js";
|
|
3
|
+
// Only import helpers actually referenced below (dynamic based on required fields)
|
|
4
|
+
import { randomId, setRandomSeed } from "./RandomSupport.js";
|
|
5
|
+
|
|
6
|
+
// Helper emitted only when meta.profile enforcement is applicable (resource profiles)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
// Internal helper (emitted per class file) to clone without resorting to 'any'.
|
|
10
|
+
function safeClone<T>(value: T): T {
|
|
11
|
+
// Use native structuredClone if present; otherwise fallback to JSON round-trip.
|
|
12
|
+
// We intentionally avoid casting through any to satisfy no-explicit-any lint rule.
|
|
13
|
+
const g: unknown = globalThis as unknown;
|
|
14
|
+
// Narrow globalThis to an object potentially containing structuredClone.
|
|
15
|
+
if (typeof (g as { structuredClone?: unknown }).structuredClone === 'function') {
|
|
16
|
+
return (g as { structuredClone: <U>(arg: U) => U }).structuredClone(value);
|
|
17
|
+
}
|
|
18
|
+
return JSON.parse(JSON.stringify(value)) as T; // best-effort deep clone
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class AdmissionAppointmentClass {
|
|
22
|
+
constructor(private resource: AdmissionAppointment) {
|
|
23
|
+
|
|
24
|
+
}
|
|
25
|
+
/** Validate current resource instance. */
|
|
26
|
+
async validate(): Promise<{ errors: string[], warnings: string[] }> {
|
|
27
|
+
return validateAdmissionAppointment(this.resource);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
getResource(): AdmissionAppointment {
|
|
31
|
+
return this.resource;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
setResource(resource: AdmissionAppointment): void {
|
|
35
|
+
this.resource = resource;
|
|
36
|
+
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Returns a plain JSON clone of the underlying resource ONLY if the last validation succeeded with zero errors.
|
|
41
|
+
* Call validate() first; otherwise this method throws to prevent accidental use of invalid / unchecked data.
|
|
42
|
+
*/
|
|
43
|
+
async toJSON(): Promise<AdmissionAppointment> {
|
|
44
|
+
return safeClone(this.resource);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Create an empty instance for parity testing: for true FHIR resources, includes only resourceType; for datatypes, returns an empty object.
|
|
49
|
+
* Note: meta.profile (when applicable) will be ensured by the constructor when wrapping this resource.
|
|
50
|
+
*/
|
|
51
|
+
static empty(overrides: Partial<AdmissionAppointment> = {}): AdmissionAppointment {
|
|
52
|
+
// For Resource profiles, include only resourceType. For Extension profiles, include url so the instance is minimally meaningful.
|
|
53
|
+
const base: Record<string, unknown> = {
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
};
|
|
57
|
+
return { ...(base as unknown as AdmissionAppointment), ...overrides };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Create a minimal randomized instance of AdmissionAppointment.
|
|
62
|
+
* Populates id, resourceType (if applicable) and primitive required (min>0) top-level fields with placeholders.
|
|
63
|
+
*/
|
|
64
|
+
static random(overrides: Partial<AdmissionAppointment> = {}, opts?: { seed?: number }): AdmissionAppointment {
|
|
65
|
+
if (opts?.seed !== undefined) {
|
|
66
|
+
// Direct call (no dynamic require) to ensure deterministic seeding works under ESM.
|
|
67
|
+
setRandomSeed(opts.seed);
|
|
68
|
+
}
|
|
69
|
+
// Start with a Partial to avoid unsafe casting errors; we'll assert at the end.
|
|
70
|
+
const base: Record<string, unknown> = { id: randomId(), };
|
|
71
|
+
// Always include meta.profile with this profile's canonical URL if available & only for resource profiles
|
|
72
|
+
// (no profileUrl provided or profile targets a datatype – meta.profile not applicable)
|
|
73
|
+
// Opportunistic enrichment: add some common fields if absent and the interface suggests a matching resource type name.
|
|
74
|
+
try {
|
|
75
|
+
const maybe: Record<string, unknown> = base as unknown as Record<string, unknown>;
|
|
76
|
+
const baseRes = 'Appointment';
|
|
77
|
+
// Identifier-like profile: ensure system & value
|
|
78
|
+
if (/Identifier/i.test(baseRes)) {
|
|
79
|
+
if (!('system' in maybe)) maybe.system = 'urn:ietf:rfc:3986';
|
|
80
|
+
if (!('value' in maybe)) maybe.value = 'urn:uuid:' + randomId();
|
|
81
|
+
if (!('type' in maybe)) maybe.type = { text: 'Primary Identifier' };
|
|
82
|
+
}
|
|
83
|
+
// Patient / Practitioner names
|
|
84
|
+
if (/Patient|Practitioner/i.test(baseRes)) {
|
|
85
|
+
if (!('identifier' in maybe)) maybe.identifier = [{ system: 'urn:ietf:rfc:3986', value: 'urn:uuid:' + randomId() }];
|
|
86
|
+
if (!('name' in maybe)) maybe.name = [{ family: randomId(), given: [randomId()] }];
|
|
87
|
+
if (!('gender' in maybe)) maybe.gender = 'unknown';
|
|
88
|
+
if (!('birthDate' in maybe)) maybe.birthDate = new Date(Date.now() - 10*365*24*60*60*1000).toISOString().substring(0,10);
|
|
89
|
+
if (!('address' in maybe)) maybe.address = [{ city: 'Example City', postalCode: '12345', country: 'DE' }];
|
|
90
|
+
}
|
|
91
|
+
if (/Condition/i.test(baseRes)) {
|
|
92
|
+
if (!('code' in maybe)) maybe.code = { coding: [{ system: 'http://snomed.info/sct', code: '64572001', display: 'Condition' }], text: 'Example condition' };
|
|
93
|
+
if (!('clinicalStatus' in maybe)) maybe.clinicalStatus = { coding: [{ system: 'http://terminology.hl7.org/CodeSystem/condition-clinical', code: 'active' }] };
|
|
94
|
+
if (!('verificationStatus' in maybe)) maybe.verificationStatus = { coding: [{ system: 'http://terminology.hl7.org/CodeSystem/condition-ver-status', code: 'confirmed' }] };
|
|
95
|
+
if (!('subject' in maybe)) maybe.subject = { reference: 'Patient/' + randomId() };
|
|
96
|
+
if (!('category' in maybe)) maybe.category = [{ coding: [{ system: 'http://terminology.hl7.org/CodeSystem/condition-category', code: 'encounter-diagnosis' }] }];
|
|
97
|
+
}
|
|
98
|
+
if (/Procedure/i.test(baseRes)) {
|
|
99
|
+
if (!('code' in maybe)) maybe.code = { coding: [{ system: 'http://snomed.info/sct', code: '71388002', display: 'Procedure' }], text: 'Example procedure' };
|
|
100
|
+
if (!('status' in maybe)) maybe.status = 'completed';
|
|
101
|
+
if (!('subject' in maybe)) maybe.subject = { reference: 'Patient/' + randomId() };
|
|
102
|
+
if (!('performedDateTime' in maybe)) maybe.performedDateTime = new Date().toISOString();
|
|
103
|
+
if (!('category' in maybe)) maybe.category = { coding: [{ system: 'http://snomed.info/sct', code: '387713003' }] };
|
|
104
|
+
}
|
|
105
|
+
if (/Encounter/i.test(baseRes)) {
|
|
106
|
+
if (!('status' in maybe)) maybe.status = 'finished';
|
|
107
|
+
if (!('class' in maybe)) maybe.class = { system: 'http://terminology.hl7.org/CodeSystem/v3-ActCode', code: 'IMP', display: 'inpatient encounter' };
|
|
108
|
+
if (!('subject' in maybe)) maybe.subject = { reference: 'Patient/' + randomId() };
|
|
109
|
+
if (!('period' in maybe)) maybe.period = { start: new Date(Date.now() - 7*24*60*60*1000).toISOString(), end: new Date().toISOString() };
|
|
110
|
+
if (!('type' in maybe)) maybe.type = [{ coding: [{ system: 'http://terminology.hl7.org/CodeSystem/v3-ActCode', code: 'IMP' }] }];
|
|
111
|
+
if (!('serviceProvider' in maybe)) maybe.serviceProvider = { reference: 'Organization/' + randomId() };
|
|
112
|
+
}
|
|
113
|
+
if (/Account/i.test(baseRes)) {
|
|
114
|
+
if (!('status' in maybe)) maybe.status = 'active';
|
|
115
|
+
if (!('type' in maybe)) maybe.type = { coding: [{ system: 'http://terminology.hl7.org/CodeSystem/v3-ActCode', code: 'PBILLACCT' }] };
|
|
116
|
+
if (!('identifier' in maybe)) maybe.identifier = [{ system: 'urn:ietf:rfc:3986', value: 'urn:uuid:' + randomId() }];
|
|
117
|
+
if (!('subject' in maybe)) maybe.subject = [{ reference: 'Patient/' + randomId() }];
|
|
118
|
+
}
|
|
119
|
+
if (/Bundle/i.test(baseRes)) {
|
|
120
|
+
if (!('type' in maybe)) maybe.type = 'document';
|
|
121
|
+
if (!('timestamp' in maybe)) maybe.timestamp = new Date().toISOString();
|
|
122
|
+
if (!('identifier' in maybe)) maybe.identifier = { system: 'urn:ietf:rfc:3986', value: 'urn:uuid:' + randomId() };
|
|
123
|
+
if (!('entry' in maybe)) maybe.entry = [{ fullUrl: 'urn:uuid:' + randomId(), resource: { resourceType: 'Composition', id: randomId(), status: 'final', type: { coding: [{ system: 'http://loinc.org', code: '60591-5' }] }, subject: { reference: 'Patient/' + randomId() }, date: new Date().toISOString(), author: [{ reference: 'Practitioner/' + randomId() }], title: 'Example Document', section: [] } }];
|
|
124
|
+
}
|
|
125
|
+
if (/Composition/i.test(baseRes)) {
|
|
126
|
+
if (!('status' in maybe)) maybe.status = 'final';
|
|
127
|
+
if (!('type' in maybe)) maybe.type = { coding: [{ system: 'http://loinc.org', code: '60591-5', display: 'Patient summary Document' }] };
|
|
128
|
+
if (!('subject' in maybe)) maybe.subject = { reference: 'Patient/' + randomId() };
|
|
129
|
+
if (!('date' in maybe)) maybe.date = new Date().toISOString();
|
|
130
|
+
if (!('author' in maybe)) maybe.author = [{ reference: 'Practitioner/' + randomId() }];
|
|
131
|
+
if (!('title' in maybe)) maybe.title = 'Patient Summary';
|
|
132
|
+
if (!('section' in maybe)) maybe.section = [];
|
|
133
|
+
}
|
|
134
|
+
if (/ValueSet/i.test(baseRes)) {
|
|
135
|
+
if (!('status' in maybe)) maybe.status = 'active';
|
|
136
|
+
if (!('url' in maybe)) maybe.url = 'urn:uuid:' + randomId();
|
|
137
|
+
if (!('name' in maybe)) maybe.name = 'ExampleValueSet';
|
|
138
|
+
if (!('version' in maybe)) maybe.version = '1.0.0';
|
|
139
|
+
if (!('compose' in maybe)) maybe.compose = { include: [{ system: 'http://loinc.org', concept: [{ code: '1234-5', display: 'Example LOINC' }] }] };
|
|
140
|
+
}
|
|
141
|
+
if (/CodeSystem/i.test(baseRes)) {
|
|
142
|
+
if (!('status' in maybe)) maybe.status = 'active';
|
|
143
|
+
if (!('content' in maybe)) maybe.content = 'complete';
|
|
144
|
+
if (!('url' in maybe)) maybe.url = 'urn:uuid:' + randomId();
|
|
145
|
+
if (!('name' in maybe)) maybe.name = 'ExampleCodeSystem';
|
|
146
|
+
if (!('version' in maybe)) maybe.version = '1.0.0';
|
|
147
|
+
if (!('concept' in maybe)) maybe.concept = [{ code: 'example', display: 'Example Code' }];
|
|
148
|
+
}
|
|
149
|
+
if (/RelatedPerson/i.test(baseRes)) {
|
|
150
|
+
if (!('patient' in maybe)) maybe.patient = { reference: 'Patient/' + randomId() };
|
|
151
|
+
if (!('identifier' in maybe)) maybe.identifier = [{ system: 'urn:ietf:rfc:3986', value: 'urn:uuid:' + randomId() }];
|
|
152
|
+
if (!('name' in maybe)) maybe.name = [{ family: randomId(), given: [randomId()] }];
|
|
153
|
+
if (!('relationship' in maybe)) maybe.relationship = [{ coding: [{ system: 'http://terminology.hl7.org/CodeSystem/v3-RoleCode', code: 'FTH', display: 'father' }] }];
|
|
154
|
+
if (!('address' in maybe)) maybe.address = [{ city: 'Example City', postalCode: '12345', country: 'DE' }];
|
|
155
|
+
}
|
|
156
|
+
if (/DocumentReference/i.test(baseRes)) {
|
|
157
|
+
if (!('status' in maybe)) maybe.status = 'current';
|
|
158
|
+
if (!('type' in maybe)) maybe.type = { coding: [{ system: 'http://loinc.org', code: '34133-9' }] };
|
|
159
|
+
if (!('category' in maybe)) maybe.category = [{ coding: [{ system: 'http://hl7.org/fhir/us/core/CodeSystem/us-core-documentreference-category', code: 'clinical-note' }] }];
|
|
160
|
+
if (!('subject' in maybe)) maybe.subject = { reference: 'Patient/' + randomId() };
|
|
161
|
+
if (!('content' in maybe)) maybe.content = [{ attachment: { contentType: 'application/pdf', data: 'ZXhhbXBsZQ==' } }]; // 'example'
|
|
162
|
+
}
|
|
163
|
+
if (/Binary/i.test(baseRes)) {
|
|
164
|
+
if (!('contentType' in maybe)) maybe.contentType = 'application/pdf';
|
|
165
|
+
if (!('data' in maybe)) maybe.data = 'SGVsbG8gV29ybGQ='; // "Hello World" in base64
|
|
166
|
+
}
|
|
167
|
+
if (/Coverage/i.test(baseRes)) {
|
|
168
|
+
if (!('status' in maybe)) maybe.status = 'active';
|
|
169
|
+
if (!('type' in maybe)) maybe.type = { coding: [{ system: 'http://terminology.hl7.org/CodeSystem/v3-ActCode', code: 'EHCPOL', display: 'extended healthcare' }] };
|
|
170
|
+
if (!('subscriberId' in maybe)) maybe.subscriberId = randomId();
|
|
171
|
+
if (!('beneficiary' in maybe)) maybe.beneficiary = { reference: 'Patient/' + randomId() };
|
|
172
|
+
if (!('payor' in maybe)) maybe.payor = [{ reference: 'Organization/' + randomId() }];
|
|
173
|
+
}
|
|
174
|
+
// Generic narrative for DomainResource derivatives
|
|
175
|
+
if (/DomainResource|Patient|Account|Bundle|Composition|Binary|CodeSystem|Condition|Encounter|Practitioner|Procedure|ValueSet|RelatedPerson|DocumentReference|Coverage/.test(baseRes)) {
|
|
176
|
+
if (!('text' in maybe)) maybe.text = { status: 'generated', div: '<div xmlns="http://www.w3.org/1999/xhtml">Generated</div>' };
|
|
177
|
+
}
|
|
178
|
+
// Extension value fallback: if profile base is Extension ensure a value[x]
|
|
179
|
+
if (/Extension/.test(baseRes)) {
|
|
180
|
+
const hasValue = Object.keys(maybe).some(k => k.startsWith('value'));
|
|
181
|
+
if (!hasValue) maybe['valueString'] = 'value';
|
|
182
|
+
// For Extension profiles, set url to the profile's canonical URL if available
|
|
183
|
+
if (!('url' in maybe)) maybe.url = 'http://fhir.pink-protocol.io/StructureDefinition/AdmissionAppointment';
|
|
184
|
+
}
|
|
185
|
+
} catch { /* enrichment is best-effort */ }
|
|
186
|
+
// Cast through unknown to acknowledge dynamic enrichment
|
|
187
|
+
return { ...(base as unknown as AdmissionAppointment), ...overrides };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** Convenience: returns a class instance wrapping a randomized resource. */
|
|
191
|
+
static randomClass(overrides: Partial<AdmissionAppointment> = {}, opts?: { seed?: number }): AdmissionAppointmentClass {
|
|
192
|
+
return new AdmissionAppointmentClass(this.random(overrides, opts));
|
|
193
|
+
}
|
|
194
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Extension } from "fhir/r4";
|
|
2
|
+
|
|
3
|
+
export interface AdmissionCheckIn extends Extension {
|
|
4
|
+
url: "http://fhir.pink-protocol.io/StructureDefinition/AdmissionCheckIn";
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
import fhirpath from "fhirpath";
|
|
9
|
+
|
|
10
|
+
export async function validateAdmissionCheckIn(resource: AdmissionCheckIn): Promise<{ errors: string[], warnings: string[] }> {
|
|
11
|
+
const errors: string[] = [];
|
|
12
|
+
const warnings: string[] = [];
|
|
13
|
+
|
|
14
|
+
const result0 = await Promise.resolve(fhirpath.evaluate(resource, "hasValue() or (children().count() > id.count())", { resource }));
|
|
15
|
+
if (!result0.every(Boolean)) {
|
|
16
|
+
errors.push("Constraint violation: All FHIR elements must have a @value or children");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const result1 = await Promise.resolve(fhirpath.evaluate(resource, "url.exists()", { resource }));
|
|
20
|
+
if (!result1.every(Boolean)) {
|
|
21
|
+
errors.push("Constraint violation: url must be present");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const result2 = await Promise.resolve(fhirpath.evaluate(resource, "value.exists()", { resource }));
|
|
25
|
+
if (!result2.every(Boolean)) {
|
|
26
|
+
errors.push("Constraint violation: value must be present");
|
|
27
|
+
}
|
|
28
|
+
return { errors, warnings };
|
|
29
|
+
}
|