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.
Files changed (65) hide show
  1. package/README.md +39 -3
  2. package/bin/babelfhir-ts.js +2 -10
  3. package/out/generator/classGenerator.js +2 -2
  4. package/out/generator/index.js +152 -12
  5. package/out/generator/interfaceGenerator.js +3 -3
  6. package/out/generator/temp/1761563744855-kptdikzwryj/generated/AdmissionAppointment.ts +94 -0
  7. package/out/generator/temp/1761563744855-kptdikzwryj/generated/AdmissionAppointmentClass.ts +194 -0
  8. package/out/generator/temp/1761563744855-kptdikzwryj/generated/AdmissionCheckIn.ts +29 -0
  9. package/out/generator/temp/1761563744855-kptdikzwryj/generated/AdmissionCheckInClass.ts +194 -0
  10. package/out/generator/temp/1761563744855-kptdikzwryj/generated/AdmissionCheckInNoCode.ts +29 -0
  11. package/out/generator/temp/1761563744855-kptdikzwryj/generated/AdmissionCheckInNoCodeClass.ts +194 -0
  12. package/out/generator/temp/1761563744855-kptdikzwryj/generated/RandomSupport.ts +26 -0
  13. package/out/generator/temp/1761563744855-kptdikzwryj/generated/valuesets/ValueSet-AdmissionFlowIds.ts +38 -0
  14. package/out/generator/temp/1761563744855-kptdikzwryj/generated/valuesets/ValueSet-AdmissionReasonCodeGroup.ts +42 -0
  15. package/out/generator/temp/1761563744855-kptdikzwryj/generated/valuesets/index.ts +18 -0
  16. package/out/generator/temp/1761563744855-kptdikzwryj/package/.index.db +0 -0
  17. package/out/generator/temp/1761563744855-kptdikzwryj/package/.index.json +234 -0
  18. package/out/generator/temp/1761563744855-kptdikzwryj/package/CodeSystem-admission-flow-ids.json +1 -0
  19. package/out/generator/temp/1761563744855-kptdikzwryj/package/CodeSystem-admission-reason-codes.json +1 -0
  20. package/out/generator/temp/1761563744855-kptdikzwryj/package/ImplementationGuide-pink.admission.json +1 -0
  21. package/out/generator/temp/1761563744855-kptdikzwryj/package/SearchParameter-SearchParameter-Appointment-AdmissionCheckIn.json +1 -0
  22. package/out/generator/temp/1761563744855-kptdikzwryj/package/SearchParameter-SearchParameter-Appointment-AdmissionCheckInNoCode.json +1 -0
  23. package/out/generator/temp/1761563744855-kptdikzwryj/package/SearchParameter-SearchParameter-Appointment-AdmissionUrl.json +1 -0
  24. package/out/generator/temp/1761563744855-kptdikzwryj/package/SearchParameter-SearchParameter-Appointment-AdmissionUuid.json +1 -0
  25. package/out/generator/temp/1761563744855-kptdikzwryj/package/StructureDefinition-AdmissionAppointment.json +1 -0
  26. package/out/generator/temp/1761563744855-kptdikzwryj/package/StructureDefinition-AdmissionCheckIn.json +1 -0
  27. package/out/generator/temp/1761563744855-kptdikzwryj/package/StructureDefinition-AdmissionCheckInNoCode.json +1 -0
  28. package/out/generator/temp/1761563744855-kptdikzwryj/package/StructureDefinition-AdmissionDataFlow.json +1 -0
  29. package/out/generator/temp/1761563744855-kptdikzwryj/package/StructureDefinition-AdmissionDevice.json +1 -0
  30. package/out/generator/temp/1761563744855-kptdikzwryj/package/StructureDefinition-AdmissionDocumentMetadata.json +1 -0
  31. package/out/generator/temp/1761563744855-kptdikzwryj/package/StructureDefinition-AdmissionFormData.json +1 -0
  32. package/out/generator/temp/1761563744855-kptdikzwryj/package/StructureDefinition-AdmissionFormUrl.json +1 -0
  33. package/out/generator/temp/1761563744855-kptdikzwryj/package/StructureDefinition-AdmissionFormUuid.json +1 -0
  34. package/out/generator/temp/1761563744855-kptdikzwryj/package/StructureDefinition-AdmissionIntegrationPoints.json +1 -0
  35. package/out/generator/temp/1761563744855-kptdikzwryj/package/StructureDefinition-AdmissionPatient.json +1 -0
  36. package/out/generator/temp/1761563744855-kptdikzwryj/package/StructureDefinition-AdmissionPatientData.json +1 -0
  37. package/out/generator/temp/1761563744855-kptdikzwryj/package/StructureDefinition-AdmissionRoutingRule.json +1 -0
  38. package/out/generator/temp/1761563744855-kptdikzwryj/package/StructureDefinition-LocationTomlConfiguration.json +1 -0
  39. package/out/generator/temp/1761563744855-kptdikzwryj/package/StructureDefinition-Pink-Location-With-Toml.json +1 -0
  40. package/out/generator/temp/1761563744855-kptdikzwryj/package/StructureDefinition-PinkLocation.json +1 -0
  41. package/out/generator/temp/1761563744855-kptdikzwryj/package/ValueSet-AdmissionFlowIdsValueSet.json +1 -0
  42. package/out/generator/temp/1761563744855-kptdikzwryj/package/ValueSet-AdmissionReasonCodeGroup.json +1 -0
  43. package/out/generator/temp/1761563744855-kptdikzwryj/package/example/AllergyIntolerance-Example-PenicillinAllergy.json +1 -0
  44. package/out/generator/temp/1761563744855-kptdikzwryj/package/example/Appointment-Example-AdmissionAppointment.json +1 -0
  45. package/out/generator/temp/1761563744855-kptdikzwryj/package/example/Condition-Example-DiabetesCondition.json +1 -0
  46. package/out/generator/temp/1761563744855-kptdikzwryj/package/example/Device-Example-Surgery-Room.json +1 -0
  47. package/out/generator/temp/1761563744855-kptdikzwryj/package/example/DocumentReference-Example-PreOperativeEvaluation.json +1 -0
  48. package/out/generator/temp/1761563744855-kptdikzwryj/package/example/Location-Example-Location-With-Toml.json +1 -0
  49. package/out/generator/temp/1761563744855-kptdikzwryj/package/example/Patient-Example-Patient.json +1 -0
  50. package/out/generator/temp/1761563744855-kptdikzwryj/package/example/QuestionnaireResponse-Example-AdmissionFormResponse.json +1 -0
  51. package/out/generator/temp/1761563744855-kptdikzwryj/package/other/spec.internals +313 -0
  52. package/out/generator/temp/1761563744855-kptdikzwryj/package/other/validation-oo.json +1 -0
  53. package/out/generator/temp/1761563744855-kptdikzwryj/package/other/validation-summary.json +1 -0
  54. package/out/generator/temp/1761563744855-kptdikzwryj/package/package.json +23 -0
  55. package/out/generator/temp/1761563744855-kptdikzwryj/package/xml/StructureDefinition-AdmissionAppointment.sch +22 -0
  56. package/out/generator/temp/1761563744855-kptdikzwryj/package/xml/StructureDefinition-AdmissionCheckIn.sch +18 -0
  57. package/out/generator/temp/1761563744855-kptdikzwryj/package/xml/StructureDefinition-AdmissionCheckInNoCode.sch +18 -0
  58. package/out/generator/temp/1761563744855-kptdikzwryj/package/xml/StructureDefinition-AdmissionDevice.sch +12 -0
  59. package/out/generator/temp/1761563744855-kptdikzwryj/package/xml/StructureDefinition-AdmissionFormUrl.sch +18 -0
  60. package/out/generator/temp/1761563744855-kptdikzwryj/package/xml/StructureDefinition-AdmissionFormUuid.sch +18 -0
  61. package/out/generator/temp/1761563744855-kptdikzwryj/package/xml/StructureDefinition-AdmissionPatient.sch +12 -0
  62. package/out/generator/temp/1761563744855-kptdikzwryj/package/xml/StructureDefinition-Pink-Location-With-Toml.sch +26 -0
  63. package/out/generator/temp/1761563744855-kptdikzwryj/package/xml/StructureDefinition-PinkLocation.sch +12 -0
  64. package/out/main.js +5 -7
  65. 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 (built on `@types/fhir`)
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**—pure TypeScript code generation with no runtime dependencies except `fhirpath` for validation
19
- - **Install any FHIR profile as a node module**—use `babelfhir-ts install` to add Implementation Guides directly to your TypeScript project
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
  ```
@@ -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 tsx to run the compiled JavaScript (tsx handles ES module resolution better)
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(tsxPath, [mainJsPath, ...process.argv.slice(2)], {
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[] } };
@@ -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.ts',
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
+ }