@atomic-ehr/codegen 0.0.1-canary.20250830224431.6d211a5 → 0.0.1-canary.20250831211734.bb1536b

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.
@@ -1,632 +0,0 @@
1
- /**
2
- * Validation Generator
3
- *
4
- * Generates client-side validation for FHIR resources including validation types,
5
- * resource validators, and validation helpers for the REST client.
6
- */
7
- /**
8
- * Validation Generator class
9
- *
10
- * Generates client-side validation logic for FHIR resources including
11
- * validation types, validators, and integration helpers.
12
- */
13
- export class ValidationGenerator {
14
- resourceTypes = new Set();
15
- resourceSchemas = new Map();
16
- /**
17
- * Collect resource types and schemas for validation generation
18
- */
19
- collectResourceData(schemas) {
20
- this.resourceTypes.clear();
21
- this.resourceSchemas.clear();
22
- for (const schema of schemas) {
23
- if (schema.identifier.kind === "resource" &&
24
- schema.identifier.name !== "DomainResource" &&
25
- schema.identifier.name !== "Resource") {
26
- this.resourceTypes.add(schema.identifier.name);
27
- this.resourceSchemas.set(schema.identifier.name, schema);
28
- }
29
- }
30
- }
31
- /**
32
- * Generate validation types and interfaces
33
- */
34
- generateValidationTypes() {
35
- return `/**
36
- * FHIR Resource Validation Types
37
- *
38
- * Client-side validation types and interfaces for FHIR resources.
39
- * Generated automatically from FHIR schemas.
40
- */
41
-
42
- import type { ResourceTypes } from '../types';
43
-
44
- /**
45
- * Validation options for resource validation
46
- */
47
- export interface ValidationOptions {
48
- /** Validation profile to use (strict, lenient, etc.) */
49
- profile?: 'strict' | 'lenient' | 'minimal';
50
- /** Whether to throw on validation errors or return result */
51
- throwOnError?: boolean;
52
- /** Whether to validate required fields */
53
- validateRequired?: boolean;
54
- /** Whether to validate cardinality constraints */
55
- validateCardinality?: boolean;
56
- /** Whether to validate data types */
57
- validateTypes?: boolean;
58
- /** Whether to validate value constraints */
59
- validateConstraints?: boolean;
60
- /** Whether to collect performance metrics */
61
- collectMetrics?: boolean;
62
- }
63
-
64
- /**
65
- * Validation error details
66
- */
67
- export interface ValidationError {
68
- /** Error severity */
69
- severity: 'error' | 'warning' | 'information';
70
- /** Error code */
71
- code: string;
72
- /** Human-readable error message */
73
- message: string;
74
- /** Path to the invalid element */
75
- path: string;
76
- /** Current value that failed validation */
77
- value?: unknown;
78
- /** Expected value or constraint */
79
- expected?: string;
80
- /** Suggestion for fixing the error */
81
- suggestion?: string;
82
- }
83
-
84
- /**
85
- * Validation warning details
86
- */
87
- export interface ValidationWarning {
88
- /** Warning code */
89
- code: string;
90
- /** Human-readable warning message */
91
- message: string;
92
- /** Path to the element */
93
- path: string;
94
- /** Current value */
95
- value?: unknown;
96
- /** Suggestion for improvement */
97
- suggestion?: string;
98
- }
99
-
100
- /**
101
- * Validation result
102
- */
103
- export interface ValidationResult {
104
- /** Whether validation passed */
105
- valid: boolean;
106
- /** List of validation errors */
107
- errors: ValidationError[];
108
- /** List of validation warnings */
109
- warnings: ValidationWarning[];
110
- /** Validation performance metrics */
111
- metrics?: {
112
- /** Time taken for validation in milliseconds */
113
- duration: number;
114
- /** Number of elements validated */
115
- elementsValidated: number;
116
- /** Number of constraints checked */
117
- constraintsChecked: number;
118
- };
119
- }
120
-
121
- /**
122
- * Validation exception thrown when validation fails and throwOnError is true
123
- */
124
- export class ValidationException extends Error {
125
- public errors: ValidationError[];
126
- public warnings: ValidationWarning[];
127
- public result: ValidationResult;
128
-
129
- constructor(result: ValidationResult) {
130
- const errorCount = result.errors.length;
131
- const warningCount = result.warnings.length;
132
- super(\`Validation failed: \${errorCount} error(s), \${warningCount} warning(s)\`);
133
-
134
- this.name = 'ValidationException';
135
- this.errors = result.errors;
136
- this.warnings = result.warnings;
137
- this.result = result;
138
- }
139
- }`;
140
- }
141
- /**
142
- * Generate resource validators for all resource types
143
- */
144
- generateResourceValidators() {
145
- const resourceTypesArray = Array.from(this.resourceTypes).sort();
146
- return `/**
147
- * FHIR Resource Validators
148
- *
149
- * Client-side validation logic for FHIR resources.
150
- * Generated automatically from FHIR schemas.
151
- */
152
-
153
- import type { ResourceTypes, ResourceTypeMap, ${resourceTypesArray.join(", ")} } from './utility.js';
154
- import type { ValidationOptions, ValidationResult, ValidationError, ValidationWarning, ValidationException } from './validation-types.js';
155
-
156
- /**
157
- * Main Resource Validator class
158
- *
159
- * Provides validation methods for all FHIR resource types with configurable
160
- * validation profiles and detailed error reporting.
161
- */
162
- export class ResourceValidator {
163
- private static defaultOptions: Required<ValidationOptions> = {
164
- profile: 'strict',
165
- throwOnError: false,
166
- validateRequired: true,
167
- validateCardinality: true,
168
- validateTypes: true,
169
- validateConstraints: true,
170
- collectMetrics: false
171
- };
172
-
173
- /**
174
- * Validate any FHIR resource with type safety
175
- */
176
- static validate<T extends ResourceTypes>(
177
- resource: ResourceTypeMap[T],
178
- options: ValidationOptions = {}
179
- ): ValidationResult {
180
- const opts = { ...this.defaultOptions, ...options };
181
- const startTime = opts.collectMetrics ? performance.now() : 0;
182
-
183
- const result: ValidationResult = {
184
- valid: true,
185
- errors: [],
186
- warnings: []
187
- };
188
-
189
- try {
190
- // Basic resource type validation
191
- if (!resource || typeof resource !== 'object') {
192
- result.errors.push({
193
- severity: 'error',
194
- code: 'INVALID_RESOURCE',
195
- message: 'Resource must be a valid object',
196
- path: 'resource',
197
- value: resource,
198
- expected: 'object',
199
- suggestion: 'Provide a valid FHIR resource object'
200
- });
201
- result.valid = false;
202
- } else {
203
- // Validate resource type
204
- const resourceType = resource.resourceType as T;
205
- if (!resourceType) {
206
- result.errors.push({
207
- severity: 'error',
208
- code: 'MISSING_RESOURCE_TYPE',
209
- message: 'Resource must have a resourceType property',
210
- path: 'resourceType',
211
- value: undefined,
212
- expected: 'string',
213
- suggestion: 'Add a resourceType property to the resource'
214
- });
215
- result.valid = false;
216
- } else {
217
- // Call resource-specific validator
218
- this.validateResourceType(resourceType, resource, result, opts);
219
- }
220
- }
221
-
222
- // Add performance metrics if requested
223
- if (opts.collectMetrics) {
224
- const endTime = performance.now();
225
- result.metrics = {
226
- duration: endTime - startTime,
227
- elementsValidated: this.countElements(resource),
228
- constraintsChecked: result.errors.length + result.warnings.length
229
- };
230
- }
231
-
232
- // Throw exception if requested and validation failed
233
- if (opts.throwOnError && !result.valid) {
234
- const { ValidationException } = require('./validation-types');
235
- throw new ValidationException(result);
236
- }
237
-
238
- return result;
239
-
240
- } catch (error) {
241
- if (error instanceof Error && error.name === 'ValidationException') {
242
- throw error;
243
- }
244
-
245
- result.errors.push({
246
- severity: 'error',
247
- code: 'VALIDATION_ERROR',
248
- message: \`Validation failed: \${error instanceof Error ? error.message : String(error)}\`,
249
- path: 'resource',
250
- value: resource,
251
- suggestion: 'Check the resource structure and try again'
252
- });
253
- result.valid = false;
254
-
255
- if (opts.throwOnError) {
256
- const { ValidationException } = require('./validation-types');
257
- throw new ValidationException(result);
258
- }
259
-
260
- return result;
261
- }
262
- }
263
-
264
- ${this.generateResourceSpecificValidators()}
265
-
266
- /**
267
- * Validate resource type and dispatch to specific validator
268
- */
269
- private static validateResourceType<T extends ResourceTypes>(
270
- resourceType: T,
271
- resource: ResourceTypeMap[T],
272
- result: ValidationResult,
273
- options: Required<ValidationOptions>
274
- ): void {
275
- switch (resourceType) {
276
- ${resourceTypesArray
277
- .map((type) => ` case '${type}':
278
- this.validate${type}(resource as ${type}, result, options);
279
- break;`)
280
- .join("\n")}
281
- default:
282
- result.warnings.push({
283
- code: 'UNKNOWN_RESOURCE_TYPE',
284
- message: \`Unknown resource type: \${resourceType}\`,
285
- path: 'resourceType',
286
- value: resourceType,
287
- suggestion: 'Check if the resource type is supported'
288
- });
289
- }
290
- }
291
-
292
- /**
293
- * Count elements in resource for metrics
294
- */
295
- private static countElements(resource: any, count = 0): number {
296
- if (!resource || typeof resource !== 'object') return count;
297
-
298
- for (const value of Object.values(resource)) {
299
- count++;
300
- if (Array.isArray(value)) {
301
- for (const item of value) {
302
- count = this.countElements(item, count);
303
- }
304
- } else if (typeof value === 'object') {
305
- count = this.countElements(value, count);
306
- }
307
- }
308
-
309
- return count;
310
- }
311
-
312
- /**
313
- * Validate required fields
314
- */
315
- private static validateRequired(
316
- resource: any,
317
- requiredFields: string[],
318
- result: ValidationResult,
319
- basePath = ''
320
- ): void {
321
- for (const field of requiredFields) {
322
- const path = basePath ? \`\${basePath}.\${field}\` : field;
323
- if (resource[field] === undefined || resource[field] === null) {
324
- result.errors.push({
325
- severity: 'error',
326
- code: 'MISSING_REQUIRED_FIELD',
327
- message: \`Required field '\${field}' is missing\`,
328
- path,
329
- value: resource[field],
330
- expected: 'non-null value',
331
- suggestion: \`Add the required '\${field}' field to the resource\`
332
- });
333
- result.valid = false;
334
- }
335
- }
336
- }
337
-
338
- /**
339
- * Validate field type
340
- */
341
- private static validateFieldType(
342
- value: any,
343
- expectedType: string,
344
- fieldName: string,
345
- result: ValidationResult,
346
- basePath = ''
347
- ): void {
348
- const path = basePath ? \`\${basePath}.\${fieldName}\` : fieldName;
349
-
350
- if (value === undefined || value === null) return;
351
-
352
- let isValid = false;
353
- switch (expectedType) {
354
- case 'string':
355
- isValid = typeof value === 'string';
356
- break;
357
- case 'number':
358
- isValid = typeof value === 'number' && !isNaN(value);
359
- break;
360
- case 'boolean':
361
- isValid = typeof value === 'boolean';
362
- break;
363
- case 'array':
364
- isValid = Array.isArray(value);
365
- break;
366
- case 'object':
367
- isValid = typeof value === 'object' && !Array.isArray(value);
368
- break;
369
- default:
370
- isValid = true; // Skip unknown types
371
- }
372
-
373
- if (!isValid) {
374
- result.errors.push({
375
- severity: 'error',
376
- code: 'INVALID_FIELD_TYPE',
377
- message: \`Field '\${fieldName}' has invalid type\`,
378
- path,
379
- value,
380
- expected: expectedType,
381
- suggestion: \`Ensure '\${fieldName}' is of type \${expectedType}\`
382
- });
383
- result.valid = false;
384
- }
385
- }
386
- }`;
387
- }
388
- /**
389
- * Generate resource-specific validators
390
- */
391
- generateResourceSpecificValidators() {
392
- const validators = [];
393
- // Generate validators for key resource types
394
- const keyResourceTypes = [
395
- "Patient",
396
- "Observation",
397
- "Organization",
398
- "Practitioner",
399
- "Bundle",
400
- ];
401
- for (const resourceType of keyResourceTypes) {
402
- if (this.resourceTypes.has(resourceType)) {
403
- validators.push(this.generateResourceValidator(resourceType));
404
- }
405
- }
406
- // Generate generic validators for remaining resource types
407
- for (const resourceType of this.resourceTypes) {
408
- if (!keyResourceTypes.includes(resourceType)) {
409
- validators.push(this.generateGenericResourceValidator(resourceType));
410
- }
411
- }
412
- return validators.join("\n\n");
413
- }
414
- /**
415
- * Generate a specific validator for a key resource type
416
- */
417
- generateResourceValidator(resourceType) {
418
- const validationRules = this.getValidationRules(resourceType);
419
- return ` /**
420
- * Validate ${resourceType} resource
421
- */
422
- private static validate${resourceType}(
423
- resource: ${resourceType},
424
- result: ValidationResult,
425
- options: Required<ValidationOptions>
426
- ): void {
427
- // Validate required fields
428
- if (options.validateRequired) {
429
- this.validateRequired(resource, [${validationRules.required.map((f) => `'${f}'`).join(", ")}], result, '${resourceType.toLowerCase()}');
430
- }
431
-
432
- // Validate field types
433
- if (options.validateTypes) {
434
- ${validationRules.fields
435
- .map((field) => `if (resource.${field.name} !== undefined) {
436
- this.validateFieldType(resource.${field.name}, '${field.type}', '${field.name}', result, '${resourceType.toLowerCase()}');
437
- }`)
438
- .join("\n\t\t\t")}
439
- }
440
-
441
- // Validate specific constraints for ${resourceType}
442
- if (options.validateConstraints) {
443
- ${this.generateResourceSpecificConstraints(resourceType)}
444
- }
445
- }`;
446
- }
447
- /**
448
- * Generate a generic validator for less common resource types
449
- */
450
- generateGenericResourceValidator(resourceType) {
451
- return ` /**
452
- * Validate ${resourceType} resource (generic validation)
453
- */
454
- private static validate${resourceType}(
455
- resource: ${resourceType},
456
- result: ValidationResult,
457
- options: Required<ValidationOptions>
458
- ): void {
459
- // Basic validation for ${resourceType}
460
- if (options.validateRequired && resource.resourceType !== '${resourceType}') {
461
- result.errors.push({
462
- severity: 'error',
463
- code: 'INVALID_RESOURCE_TYPE',
464
- message: \`Expected resourceType '${resourceType}', got '\${resource.resourceType}'\`,
465
- path: 'resourceType',
466
- value: resource.resourceType,
467
- expected: '${resourceType}',
468
- suggestion: 'Ensure the resourceType matches the expected value'
469
- });
470
- result.valid = false;
471
- }
472
-
473
- // Generic field validation
474
- if (options.validateTypes) {
475
- // Validate common fields
476
- if (resource.id !== undefined) {
477
- this.validateFieldType(resource.id, 'string', 'id', result, '${resourceType.toLowerCase()}');
478
- }
479
- if ((resource as any).meta !== undefined) {
480
- this.validateFieldType((resource as any).meta, 'object', 'meta', result, '${resourceType.toLowerCase()}');
481
- }
482
- }
483
- }`;
484
- }
485
- /**
486
- * Get validation rules for a specific resource type
487
- */
488
- getValidationRules(resourceType) {
489
- switch (resourceType) {
490
- case "Patient":
491
- return {
492
- required: ["resourceType"],
493
- fields: [
494
- { name: "id", type: "string" },
495
- { name: "meta", type: "object" },
496
- { name: "identifier", type: "array" },
497
- { name: "active", type: "boolean" },
498
- { name: "name", type: "array" },
499
- { name: "telecom", type: "array" },
500
- { name: "gender", type: "string" },
501
- { name: "birthDate", type: "string" },
502
- { name: "address", type: "array" },
503
- ],
504
- };
505
- case "Observation":
506
- return {
507
- required: ["resourceType", "status", "code"],
508
- fields: [
509
- { name: "id", type: "string" },
510
- { name: "meta", type: "object" },
511
- { name: "identifier", type: "array" },
512
- { name: "status", type: "string" },
513
- { name: "category", type: "array" },
514
- { name: "code", type: "object" },
515
- { name: "subject", type: "object" },
516
- { name: "effectiveDateTime", type: "string" },
517
- { name: "valueQuantity", type: "object" },
518
- { name: "valueString", type: "string" },
519
- { name: "valueBoolean", type: "boolean" },
520
- ],
521
- };
522
- case "Organization":
523
- return {
524
- required: ["resourceType"],
525
- fields: [
526
- { name: "id", type: "string" },
527
- { name: "meta", type: "object" },
528
- { name: "identifier", type: "array" },
529
- { name: "active", type: "boolean" },
530
- { name: "type", type: "array" },
531
- { name: "name", type: "string" },
532
- { name: "telecom", type: "array" },
533
- { name: "address", type: "array" },
534
- ],
535
- };
536
- case "Practitioner":
537
- return {
538
- required: ["resourceType"],
539
- fields: [
540
- { name: "id", type: "string" },
541
- { name: "meta", type: "object" },
542
- { name: "identifier", type: "array" },
543
- { name: "active", type: "boolean" },
544
- { name: "name", type: "array" },
545
- { name: "telecom", type: "array" },
546
- { name: "address", type: "array" },
547
- { name: "gender", type: "string" },
548
- { name: "birthDate", type: "string" },
549
- ],
550
- };
551
- case "Bundle":
552
- return {
553
- required: ["resourceType", "type"],
554
- fields: [
555
- { name: "id", type: "string" },
556
- { name: "meta", type: "object" },
557
- { name: "identifier", type: "object" },
558
- { name: "type", type: "string" },
559
- { name: "timestamp", type: "string" },
560
- { name: "total", type: "number" },
561
- { name: "entry", type: "array" },
562
- ],
563
- };
564
- default:
565
- return {
566
- required: ["resourceType"],
567
- fields: [
568
- { name: "id", type: "string" },
569
- { name: "meta", type: "object" },
570
- ],
571
- };
572
- }
573
- }
574
- /**
575
- * Generate resource-specific constraint validation
576
- */
577
- generateResourceSpecificConstraints(resourceType) {
578
- switch (resourceType) {
579
- case "Patient":
580
- return `// Patient-specific constraints
581
- if (resource.gender && !['male', 'female', 'other', 'unknown'].includes(resource.gender)) {
582
- result.errors.push({
583
- severity: 'error',
584
- code: 'INVALID_GENDER_VALUE',
585
- message: 'Invalid gender value',
586
- path: 'patient.gender',
587
- value: resource.gender,
588
- expected: 'male, female, other, or unknown',
589
- suggestion: 'Use a valid gender code'
590
- });
591
- result.valid = false;
592
- }`;
593
- case "Observation":
594
- return `// Observation-specific constraints
595
- if (resource.status && !['registered', 'preliminary', 'final', 'amended', 'corrected', 'cancelled', 'entered-in-error', 'unknown'].includes(resource.status)) {
596
- result.errors.push({
597
- severity: 'error',
598
- code: 'INVALID_OBSERVATION_STATUS',
599
- message: 'Invalid observation status',
600
- path: 'observation.status',
601
- value: resource.status,
602
- expected: 'valid observation status code',
603
- suggestion: 'Use a valid observation status code'
604
- });
605
- result.valid = false;
606
- }`;
607
- case "Bundle":
608
- return `// Bundle-specific constraints
609
- if (resource.type && !['document', 'message', 'transaction', 'transaction-response', 'batch', 'batch-response', 'history', 'searchset', 'collection'].includes(resource.type)) {
610
- result.errors.push({
611
- severity: 'error',
612
- code: 'INVALID_BUNDLE_TYPE',
613
- message: 'Invalid bundle type',
614
- path: 'bundle.type',
615
- value: resource.type,
616
- expected: 'valid bundle type code',
617
- suggestion: 'Use a valid bundle type code'
618
- });
619
- result.valid = false;
620
- }`;
621
- default:
622
- return `// Generic resource constraints
623
- // No specific constraints for ${resourceType}`;
624
- }
625
- }
626
- /**
627
- * Get collected resource types
628
- */
629
- getResourceTypes() {
630
- return this.resourceTypes;
631
- }
632
- }