fhir-resource-diff 0.2.0

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.
@@ -0,0 +1,2318 @@
1
+ // src/core/fhir-version.ts
2
+ var SUPPORTED_FHIR_VERSIONS = ["R4", "R4B", "R5"];
3
+ var DEFAULT_FHIR_VERSION = "R4";
4
+ var VERSION_STRING_MAP = /* @__PURE__ */ new Map([
5
+ ["4.0.0", "R4"],
6
+ ["4.0.1", "R4"],
7
+ ["4.3.0", "R4B"],
8
+ ["4.3.0-snapshot1", "R4B"],
9
+ ["5.0.0", "R5"],
10
+ ["5.0.0-snapshot1", "R5"],
11
+ ["5.0.0-ballot", "R5"]
12
+ ]);
13
+ function detectFhirVersion(resource) {
14
+ const versionStr = resource.meta?.fhirVersion;
15
+ if (typeof versionStr !== "string") return void 0;
16
+ return VERSION_STRING_MAP.get(versionStr);
17
+ }
18
+ function resolveFhirVersion(explicit, resource) {
19
+ if (explicit !== void 0) return explicit;
20
+ return detectFhirVersion(resource) ?? DEFAULT_FHIR_VERSION;
21
+ }
22
+ function fhirVersionLabel(version) {
23
+ const labels = {
24
+ R4: "FHIR R4 (v4.0.1)",
25
+ R4B: "FHIR R4B (v4.3.0)",
26
+ R5: "FHIR R5 (v5.0.0)"
27
+ };
28
+ return labels[version];
29
+ }
30
+ function fhirBaseUrl(version) {
31
+ return `https://hl7.org/fhir/${version}`;
32
+ }
33
+ function isSupportedFhirVersion(value) {
34
+ return SUPPORTED_FHIR_VERSIONS.includes(value);
35
+ }
36
+
37
+ // src/core/resource-registry.ts
38
+ var RESOURCE_REGISTRY = [
39
+ // foundation
40
+ {
41
+ resourceType: "Bundle",
42
+ category: "foundation",
43
+ versions: ["R4", "R4B", "R5"],
44
+ description: "Container for a collection of resources",
45
+ maturityLevel: "N",
46
+ useCases: [
47
+ "Transaction: atomic write of multiple resources to a FHIR server",
48
+ "Search result set: server response to a search query",
49
+ "Document: a persistent clinical document (IPS, CCD)",
50
+ "Message: event notification"
51
+ ],
52
+ keyFields: [
53
+ {
54
+ name: "type",
55
+ required: true,
56
+ note: "document|message|transaction|transaction-response|batch|batch-response|history|searchset|collection|subscription-notification"
57
+ },
58
+ {
59
+ name: "entry[]",
60
+ required: false,
61
+ note: "the resources; shape of entry depends on Bundle.type"
62
+ },
63
+ {
64
+ name: "entry[].resource",
65
+ required: false,
66
+ note: "the actual resource"
67
+ },
68
+ {
69
+ name: "entry[].request",
70
+ required: false,
71
+ note: "for transaction/batch: method + url"
72
+ },
73
+ {
74
+ name: "entry[].response",
75
+ required: false,
76
+ note: "for transaction-response/batch-response: status"
77
+ },
78
+ {
79
+ name: "total",
80
+ required: false,
81
+ note: "only meaningful for searchset \u2014 total matching count"
82
+ }
83
+ ],
84
+ versionNotes: {
85
+ "R4B\u2192R5": "subscription-notification added as a new Bundle type; issues field added for server-reported problems with individual entries"
86
+ }
87
+ },
88
+ {
89
+ resourceType: "OperationOutcome",
90
+ category: "foundation",
91
+ versions: ["R4", "R4B", "R5"],
92
+ description: "Collection of processing messages (errors, warnings, information)",
93
+ maturityLevel: "N"
94
+ },
95
+ {
96
+ resourceType: "Parameters",
97
+ category: "foundation",
98
+ versions: ["R4", "R4B", "R5"],
99
+ description: "Operation request/response parameters",
100
+ maturityLevel: "N"
101
+ },
102
+ {
103
+ resourceType: "Binary",
104
+ category: "foundation",
105
+ versions: ["R4", "R4B", "R5"],
106
+ description: "Raw binary content",
107
+ maturityLevel: 3
108
+ },
109
+ // base
110
+ {
111
+ resourceType: "Patient",
112
+ category: "base",
113
+ versions: ["R4", "R4B", "R5"],
114
+ description: "Demographics and administrative information about an individual",
115
+ maturityLevel: "N",
116
+ useCases: [
117
+ "Demographics and identity for individuals receiving healthcare",
118
+ "Linking clinical data (observations, conditions, encounters) to a person",
119
+ "Cross-system patient matching and identity resolution"
120
+ ],
121
+ keyFields: [
122
+ {
123
+ name: "identifier",
124
+ required: false,
125
+ note: "MRN, SSN, passport \u2014 systems commonly use multiple"
126
+ },
127
+ {
128
+ name: "name",
129
+ required: false,
130
+ note: "HumanName array; use[official] for the primary name"
131
+ },
132
+ {
133
+ name: "birthDate",
134
+ required: false,
135
+ note: "FHIR date: YYYY, YYYY-MM, or YYYY-MM-DD"
136
+ },
137
+ {
138
+ name: "gender",
139
+ required: false,
140
+ note: "administrative gender: male | female | other | unknown"
141
+ },
142
+ {
143
+ name: "address",
144
+ required: false,
145
+ note: "array \u2014 patients may have multiple (home, work, old)"
146
+ },
147
+ {
148
+ name: "telecom",
149
+ required: false,
150
+ note: "phone/email array; rank=1 is preferred contact"
151
+ },
152
+ {
153
+ name: "deceased[x]",
154
+ required: false,
155
+ note: "boolean or dateTime \u2014 affects active status logic"
156
+ }
157
+ ],
158
+ versionNotes: {
159
+ "R4B\u2192R5": "link field added for cross-version Patient links; contact.relationship binding relaxed"
160
+ }
161
+ },
162
+ {
163
+ resourceType: "Practitioner",
164
+ category: "base",
165
+ versions: ["R4", "R4B", "R5"],
166
+ description: "A person with a formal responsibility in healthcare",
167
+ maturityLevel: 3,
168
+ useCases: [
169
+ "Identifying the author of a clinical note",
170
+ "Linking prescriptions and orders to a licensed provider",
171
+ "Directory listings for healthcare professionals"
172
+ ],
173
+ keyFields: [
174
+ {
175
+ name: "identifier",
176
+ required: false,
177
+ note: "NPI (US), provider number \u2014 systems usually require at least one"
178
+ },
179
+ {
180
+ name: "name",
181
+ required: false,
182
+ note: "HumanName array"
183
+ },
184
+ {
185
+ name: "qualification[]",
186
+ required: false,
187
+ note: "licenses and certifications with issuer and period"
188
+ }
189
+ ],
190
+ versionNotes: {
191
+ "R4B\u2192R5": "communication now includes proficiency level"
192
+ }
193
+ },
194
+ {
195
+ resourceType: "PractitionerRole",
196
+ category: "base",
197
+ versions: ["R4", "R4B", "R5"],
198
+ description: "Roles and specialties a practitioner is authorized to perform",
199
+ maturityLevel: 2
200
+ },
201
+ {
202
+ resourceType: "Organization",
203
+ category: "base",
204
+ versions: ["R4", "R4B", "R5"],
205
+ description: "A formally recognized grouping of people or organizations",
206
+ maturityLevel: 3
207
+ },
208
+ {
209
+ resourceType: "Location",
210
+ category: "base",
211
+ versions: ["R4", "R4B", "R5"],
212
+ description: "Physical place where services are provided",
213
+ maturityLevel: 3
214
+ },
215
+ {
216
+ resourceType: "Endpoint",
217
+ category: "base",
218
+ versions: ["R4", "R4B", "R5"],
219
+ description: "Technical connectivity details for a system",
220
+ maturityLevel: 2
221
+ },
222
+ {
223
+ resourceType: "RelatedPerson",
224
+ category: "base",
225
+ versions: ["R4", "R4B", "R5"],
226
+ description: "Person related to the patient",
227
+ maturityLevel: 2
228
+ },
229
+ {
230
+ resourceType: "HealthcareService",
231
+ category: "base",
232
+ versions: ["R4", "R4B", "R5"],
233
+ description: "Details of a healthcare service available at a location",
234
+ maturityLevel: 2
235
+ },
236
+ // clinical
237
+ {
238
+ resourceType: "Observation",
239
+ category: "clinical",
240
+ versions: ["R4", "R4B", "R5"],
241
+ description: "Measurements and assertions about a patient or subject",
242
+ maturityLevel: "N",
243
+ useCases: [
244
+ "Lab results: glucose, HbA1c, CBC panels, lipid panels",
245
+ "Vital signs: blood pressure, heart rate, BMI, oxygen saturation",
246
+ "Clinical scores, assessment findings, and survey answers"
247
+ ],
248
+ keyFields: [
249
+ {
250
+ name: "status",
251
+ required: true,
252
+ note: "registered|preliminary|final|amended|corrected|cancelled|entered-in-error|unknown"
253
+ },
254
+ {
255
+ name: "code",
256
+ required: true,
257
+ note: "what was observed \u2014 LOINC (preferred), SNOMED, local codes"
258
+ },
259
+ {
260
+ name: "subject",
261
+ required: true,
262
+ note: "usually Patient; can be Group, Device, or Location"
263
+ },
264
+ {
265
+ name: "effective[x]",
266
+ required: false,
267
+ note: "when the observation was made \u2014 dateTime or Period"
268
+ },
269
+ {
270
+ name: "value[x]",
271
+ required: false,
272
+ note: "the result \u2014 Quantity, CodeableConcept, string, boolean, integer, Range, Ratio, SampledData, time, dateTime, Period"
273
+ },
274
+ {
275
+ name: "component[]",
276
+ required: false,
277
+ note: "for panel observations; each component has its own code + value[x] (e.g. systolic + diastolic for BP)"
278
+ },
279
+ {
280
+ name: "referenceRange",
281
+ required: false,
282
+ note: "low/high bounds for interpreting Quantity values"
283
+ }
284
+ ],
285
+ versionNotes: {
286
+ "R4B\u2192R5": "bodyStructure field added (replaces bodySite extension); subject broadens to support more reference targets; triggeredBy added for derived observations"
287
+ }
288
+ },
289
+ {
290
+ resourceType: "Condition",
291
+ category: "clinical",
292
+ versions: ["R4", "R4B", "R5"],
293
+ description: "Detailed information about a clinical condition or diagnosis",
294
+ maturityLevel: 3,
295
+ useCases: [
296
+ "Problem list entries: active chronic conditions",
297
+ "Encounter diagnoses: the reason care was provided",
298
+ "Past medical history and resolved conditions"
299
+ ],
300
+ keyFields: [
301
+ {
302
+ name: "clinicalStatus",
303
+ required: false,
304
+ note: "CodeableConcept \u2014 active|recurrence|relapse|inactive|remission|resolved"
305
+ },
306
+ {
307
+ name: "verificationStatus",
308
+ required: false,
309
+ note: "CodeableConcept \u2014 unconfirmed|provisional|differential|confirmed|refuted|entered-in-error"
310
+ },
311
+ {
312
+ name: "code",
313
+ required: false,
314
+ note: "the diagnosis (SNOMED, ICD-10, local) \u2014 surprisingly not required in base spec"
315
+ },
316
+ {
317
+ name: "subject",
318
+ required: true,
319
+ note: "the patient"
320
+ },
321
+ {
322
+ name: "onset[x]",
323
+ required: false,
324
+ note: "when the condition started \u2014 dateTime, Age, Period, Range, string"
325
+ },
326
+ {
327
+ name: "abatement[x]",
328
+ required: false,
329
+ note: "when it resolved \u2014 same type choices as onset[x]"
330
+ }
331
+ ],
332
+ versionNotes: {
333
+ "R4B\u2192R5": "participant added (who was involved); stage moved to backbone element; encounter reference added directly to resource"
334
+ }
335
+ },
336
+ {
337
+ resourceType: "Encounter",
338
+ category: "clinical",
339
+ versions: ["R4", "R4B", "R5"],
340
+ description: "An interaction during which services are provided",
341
+ maturityLevel: 2,
342
+ useCases: [
343
+ "Inpatient admission and discharge records",
344
+ "Outpatient visits and office appointments",
345
+ "Emergency department presentations"
346
+ ],
347
+ keyFields: [
348
+ {
349
+ name: "status",
350
+ required: true,
351
+ note: "R4/R4B: planned|arrived|triaged|in-progress|onleave|finished|cancelled|... R5: planned|in-progress|on-hold|discharged|completed|cancelled|..."
352
+ },
353
+ {
354
+ name: "class",
355
+ required: true,
356
+ note: "R4/R4B: Coding (AMB=ambulatory, IMP=inpatient, EMER=emergency) R5: renamed to class and made CodeableConcept"
357
+ },
358
+ {
359
+ name: "subject",
360
+ required: true,
361
+ note: "the patient"
362
+ },
363
+ {
364
+ name: "period",
365
+ required: false,
366
+ note: "start and end datetime of the encounter"
367
+ },
368
+ {
369
+ name: "participant[]",
370
+ required: false,
371
+ note: "practitioners involved (type + individual reference)"
372
+ },
373
+ {
374
+ name: "diagnosis[]",
375
+ required: false,
376
+ note: "conditions and procedures linked to this encounter"
377
+ }
378
+ ],
379
+ versionNotes: {
380
+ "R4B\u2192R5": "class changed from Coding to CodeableConcept (CodeableReference); status values reworked; hospitalization renamed to admission; reason now a CodeableReference instead of CodeableConcept"
381
+ }
382
+ },
383
+ {
384
+ resourceType: "Procedure",
385
+ category: "clinical",
386
+ versions: ["R4", "R4B", "R5"],
387
+ description: "An action performed on or for a patient",
388
+ maturityLevel: 3,
389
+ useCases: [
390
+ "Surgical procedures and interventions",
391
+ "Therapeutic procedures (dialysis, chemotherapy, physical therapy)",
392
+ "Diagnostic procedures (biopsy, colonoscopy)"
393
+ ],
394
+ keyFields: [
395
+ {
396
+ name: "status",
397
+ required: true,
398
+ note: "preparation|in-progress|not-done|on-hold|stopped|completed|entered-in-error|unknown"
399
+ },
400
+ {
401
+ name: "code",
402
+ required: false,
403
+ note: "what was performed (SNOMED, CPT, local)"
404
+ },
405
+ {
406
+ name: "subject",
407
+ required: true,
408
+ note: "the patient"
409
+ },
410
+ {
411
+ name: "performed[x]",
412
+ required: false,
413
+ note: "when \u2014 dateTime, Period, string, Age, Range"
414
+ },
415
+ {
416
+ name: "report[]",
417
+ required: false,
418
+ note: "linked DiagnosticReport results"
419
+ }
420
+ ],
421
+ versionNotes: {
422
+ "R4B\u2192R5": "recorded field added; report made CodeableReference; performer uses new Actor type"
423
+ }
424
+ },
425
+ {
426
+ resourceType: "AllergyIntolerance",
427
+ category: "clinical",
428
+ versions: ["R4", "R4B", "R5"],
429
+ description: "Allergy or intolerance and its clinical consequences",
430
+ maturityLevel: 3,
431
+ useCases: [
432
+ "Medication allergies for prescribing safety checks",
433
+ "Food and environmental allergen records",
434
+ "Adverse reaction history"
435
+ ],
436
+ keyFields: [
437
+ {
438
+ name: "clinicalStatus",
439
+ required: false,
440
+ note: "CodeableConcept \u2014 active|inactive|resolved"
441
+ },
442
+ {
443
+ name: "verificationStatus",
444
+ required: false,
445
+ note: "CodeableConcept \u2014 unconfirmed|confirmed|refuted|entered-in-error"
446
+ },
447
+ {
448
+ name: "type",
449
+ required: false,
450
+ note: "allergy | intolerance"
451
+ },
452
+ {
453
+ name: "code",
454
+ required: false,
455
+ note: "the allergen (RxNorm for drugs, SNOMED for substances)"
456
+ },
457
+ {
458
+ name: "patient",
459
+ required: true,
460
+ note: "required (R4/R4B) renamed to subject in R5"
461
+ },
462
+ {
463
+ name: "reaction[]",
464
+ required: false,
465
+ note: "manifestation descriptions and severity"
466
+ }
467
+ ],
468
+ versionNotes: {
469
+ "R4B\u2192R5": "patient renamed to subject (breaking rename); participant added"
470
+ }
471
+ },
472
+ {
473
+ resourceType: "MedicationRequest",
474
+ category: "clinical",
475
+ versions: ["R4", "R4B", "R5"],
476
+ description: "Order or request for a medication",
477
+ maturityLevel: 3,
478
+ useCases: [
479
+ "Prescriptions from a prescriber to a pharmacy",
480
+ "Medication orders within inpatient care",
481
+ "Medication plans in chronic disease management"
482
+ ],
483
+ keyFields: [
484
+ {
485
+ name: "status",
486
+ required: true,
487
+ note: "active|on-hold|cancelled|completed|entered-in-error|stopped|draft|unknown"
488
+ },
489
+ {
490
+ name: "intent",
491
+ required: true,
492
+ note: "proposal|plan|order|original-order|reflex-order|filler-order|instance-order|option"
493
+ },
494
+ {
495
+ name: "medication[x]",
496
+ required: true,
497
+ note: "reference to Medication resource or CodeableConcept \u2014 R5 uses CodeableReference"
498
+ },
499
+ {
500
+ name: "subject",
501
+ required: true,
502
+ note: "the patient"
503
+ },
504
+ {
505
+ name: "authoredOn",
506
+ required: false,
507
+ note: "when the prescription was written"
508
+ },
509
+ {
510
+ name: "dosageInstruction[]",
511
+ required: false,
512
+ note: "timing, route, dose \u2014 highly structured but verbose"
513
+ }
514
+ ],
515
+ versionNotes: {
516
+ "R4B\u2192R5": "medication[x] changed to CodeableReference; statusChanged added; informationSource added; renderedDosageInstruction added (human-readable summary)"
517
+ }
518
+ },
519
+ {
520
+ resourceType: "MedicationStatement",
521
+ category: "clinical",
522
+ versions: ["R4", "R4B", "R5"],
523
+ description: "Record of medication use",
524
+ maturityLevel: 3
525
+ },
526
+ {
527
+ resourceType: "DiagnosticReport",
528
+ category: "clinical",
529
+ versions: ["R4", "R4B", "R5"],
530
+ description: "Findings and interpretation of diagnostic investigations",
531
+ maturityLevel: 3,
532
+ useCases: [
533
+ "Lab report: a set of Observation results with interpretation",
534
+ "Radiology report: imaging study findings",
535
+ "Pathology report: tissue examination results"
536
+ ],
537
+ keyFields: [
538
+ {
539
+ name: "status",
540
+ required: true,
541
+ note: "registered|partial|preliminary|final|amended|corrected|appended|cancelled|entered-in-error"
542
+ },
543
+ {
544
+ name: "code",
545
+ required: true,
546
+ note: "the type of report (LOINC panel code, local code)"
547
+ },
548
+ {
549
+ name: "subject",
550
+ required: false,
551
+ note: "the patient (technically optional in base spec)"
552
+ },
553
+ {
554
+ name: "result[]",
555
+ required: false,
556
+ note: "references to Observation resources in the report"
557
+ },
558
+ {
559
+ name: "conclusion",
560
+ required: false,
561
+ note: "free-text clinical interpretation"
562
+ }
563
+ ],
564
+ versionNotes: {
565
+ "R4B\u2192R5": "study replaces imagingStudy; note added; composition added for structured reports"
566
+ }
567
+ },
568
+ {
569
+ resourceType: "Immunization",
570
+ category: "clinical",
571
+ versions: ["R4", "R4B", "R5"],
572
+ description: "Immunization event record",
573
+ maturityLevel: 3,
574
+ useCases: [
575
+ "Vaccination records for immunization history",
576
+ "Reporting immunizations to public health registries",
577
+ "Immunization forecasting input"
578
+ ],
579
+ keyFields: [
580
+ {
581
+ name: "status",
582
+ required: true,
583
+ note: "completed | entered-in-error | not-done"
584
+ },
585
+ {
586
+ name: "vaccineCode",
587
+ required: true,
588
+ note: "the vaccine administered (CVX codes in US, SNOMED)"
589
+ },
590
+ {
591
+ name: "patient",
592
+ required: true,
593
+ note: "the patient"
594
+ },
595
+ {
596
+ name: "occurrence[x]",
597
+ required: true,
598
+ note: "when administered \u2014 dateTime or string"
599
+ },
600
+ {
601
+ name: "lotNumber",
602
+ required: false,
603
+ note: "manufacturer lot for traceability"
604
+ }
605
+ ],
606
+ versionNotes: {
607
+ "R4B\u2192R5": "informationSource added; basedOn added for linking to immunization recommendations"
608
+ }
609
+ },
610
+ {
611
+ resourceType: "CarePlan",
612
+ category: "clinical",
613
+ versions: ["R4", "R4B", "R5"],
614
+ description: "Plan of care for a patient",
615
+ maturityLevel: 2
616
+ },
617
+ {
618
+ resourceType: "CareTeam",
619
+ category: "clinical",
620
+ versions: ["R4", "R4B", "R5"],
621
+ description: "Participants in coordinated care for a patient",
622
+ maturityLevel: 2
623
+ },
624
+ {
625
+ resourceType: "ServiceRequest",
626
+ category: "clinical",
627
+ versions: ["R4", "R4B", "R5"],
628
+ description: "Record of a request for a service",
629
+ maturityLevel: 2,
630
+ useCases: [
631
+ "Referral from a GP to a specialist",
632
+ "Lab test order (precedes DiagnosticReport)",
633
+ "Imaging order (precedes ImagingStudy)"
634
+ ],
635
+ keyFields: [
636
+ {
637
+ name: "status",
638
+ required: true,
639
+ note: "draft|active|on-hold|revoked|completed|entered-in-error|unknown"
640
+ },
641
+ {
642
+ name: "intent",
643
+ required: true,
644
+ note: "proposal|plan|directive|order|original-order|reflex-order|filler-order|instance-order|option"
645
+ },
646
+ {
647
+ name: "code",
648
+ required: false,
649
+ note: "what is being requested"
650
+ },
651
+ {
652
+ name: "subject",
653
+ required: true,
654
+ note: "the patient"
655
+ },
656
+ {
657
+ name: "requester",
658
+ required: false,
659
+ note: "who is ordering"
660
+ },
661
+ {
662
+ name: "performer[]",
663
+ required: false,
664
+ note: "who should fulfill the request"
665
+ }
666
+ ],
667
+ versionNotes: {
668
+ "R4B\u2192R5": "code changed to CodeableReference; bodyStructure added; focus added"
669
+ }
670
+ },
671
+ {
672
+ resourceType: "DocumentReference",
673
+ category: "clinical",
674
+ versions: ["R4", "R4B", "R5"],
675
+ description: "Reference to a document",
676
+ maturityLevel: 3,
677
+ useCases: [
678
+ "Clinical notes (progress notes, discharge summaries, op reports)",
679
+ "Scanned or external documents attached to a patient record",
680
+ "CCDA and structured clinical documents"
681
+ ],
682
+ keyFields: [
683
+ {
684
+ name: "status",
685
+ required: true,
686
+ note: "current | superseded | entered-in-error"
687
+ },
688
+ {
689
+ name: "type",
690
+ required: false,
691
+ note: "document type (LOINC document codes)"
692
+ },
693
+ {
694
+ name: "subject",
695
+ required: false,
696
+ note: "the patient"
697
+ },
698
+ {
699
+ name: "content[]",
700
+ required: true,
701
+ note: "the document itself \u2014 attachment with URL or base64 data"
702
+ },
703
+ {
704
+ name: "context",
705
+ required: false,
706
+ note: "encounter, period, and clinical setting"
707
+ }
708
+ ],
709
+ versionNotes: {
710
+ "R4B\u2192R5": "docStatus made more prominent; attester replaces author for formal attestation; version added; basedOn added"
711
+ }
712
+ },
713
+ {
714
+ resourceType: "Consent",
715
+ category: "clinical",
716
+ versions: ["R4", "R4B", "R5"],
717
+ description: "Record of a consent decision",
718
+ maturityLevel: 2
719
+ },
720
+ {
721
+ resourceType: "Goal",
722
+ category: "clinical",
723
+ versions: ["R4", "R4B", "R5"],
724
+ description: "Desired outcome for a patient",
725
+ maturityLevel: 2
726
+ },
727
+ // financial
728
+ {
729
+ resourceType: "Claim",
730
+ category: "financial",
731
+ versions: ["R4", "R4B", "R5"],
732
+ description: "Request for payment for products and/or services",
733
+ maturityLevel: 2
734
+ },
735
+ {
736
+ resourceType: "Coverage",
737
+ category: "financial",
738
+ versions: ["R4", "R4B", "R5"],
739
+ description: "Insurance or medical plan details",
740
+ maturityLevel: 2,
741
+ useCases: [
742
+ "Insurance plan and subscriber information for claims processing",
743
+ "Prior authorization eligibility checks",
744
+ "Patient cost-sharing determination"
745
+ ],
746
+ keyFields: [
747
+ {
748
+ name: "status",
749
+ required: true,
750
+ note: "active | cancelled | draft | entered-in-error"
751
+ },
752
+ {
753
+ name: "beneficiary",
754
+ required: true,
755
+ note: "the patient covered"
756
+ },
757
+ {
758
+ name: "payor[]",
759
+ required: true,
760
+ note: "the insurance organization(s)"
761
+ },
762
+ {
763
+ name: "class[]",
764
+ required: false,
765
+ note: "plan, group, and subgroup identifiers"
766
+ }
767
+ ],
768
+ versionNotes: {
769
+ "R4B\u2192R5": "kind field added (insurance|self-pay|other); paymentBy added for cost-sharing parties"
770
+ }
771
+ },
772
+ {
773
+ resourceType: "ExplanationOfBenefit",
774
+ category: "financial",
775
+ versions: ["R4", "R4B", "R5"],
776
+ description: "Processed claim adjudication details",
777
+ maturityLevel: 2
778
+ },
779
+ // specialized
780
+ {
781
+ resourceType: "Questionnaire",
782
+ category: "specialized",
783
+ versions: ["R4", "R4B", "R5"],
784
+ description: "Structured set of questions",
785
+ maturityLevel: 3
786
+ },
787
+ {
788
+ resourceType: "QuestionnaireResponse",
789
+ category: "specialized",
790
+ versions: ["R4", "R4B", "R5"],
791
+ description: "Responses to a questionnaire",
792
+ maturityLevel: 3
793
+ },
794
+ {
795
+ resourceType: "ResearchStudy",
796
+ category: "specialized",
797
+ versions: ["R4", "R4B", "R5"],
798
+ description: "Investigation and analysis plan",
799
+ maturityLevel: 1
800
+ },
801
+ // conformance
802
+ {
803
+ resourceType: "CapabilityStatement",
804
+ category: "conformance",
805
+ versions: ["R4", "R4B", "R5"],
806
+ description: "Server capability description",
807
+ maturityLevel: "N"
808
+ },
809
+ {
810
+ resourceType: "StructureDefinition",
811
+ category: "conformance",
812
+ versions: ["R4", "R4B", "R5"],
813
+ description: "Definition of a FHIR structure (resource or data type)",
814
+ maturityLevel: "N"
815
+ },
816
+ {
817
+ resourceType: "ValueSet",
818
+ category: "conformance",
819
+ versions: ["R4", "R4B", "R5"],
820
+ description: "Set of coded values",
821
+ maturityLevel: "N"
822
+ },
823
+ {
824
+ resourceType: "CodeSystem",
825
+ category: "conformance",
826
+ versions: ["R4", "R4B", "R5"],
827
+ description: "Definition of a code system",
828
+ maturityLevel: "N"
829
+ }
830
+ ];
831
+ function getResourceInfo(resourceType) {
832
+ return RESOURCE_REGISTRY.find((r) => r.resourceType === resourceType);
833
+ }
834
+ function getResourceDocUrl(resourceType, version) {
835
+ return `${fhirBaseUrl(version ?? DEFAULT_FHIR_VERSION)}/${resourceType.toLowerCase()}.html`;
836
+ }
837
+ function isKnownResourceType(resourceType, version) {
838
+ const info = getResourceInfo(resourceType);
839
+ if (!info) return false;
840
+ if (version === void 0) return true;
841
+ return info.versions.includes(version);
842
+ }
843
+ function listResourceTypes(filters) {
844
+ if (!filters) return RESOURCE_REGISTRY;
845
+ return RESOURCE_REGISTRY.filter((r) => {
846
+ if (filters.version !== void 0 && !r.versions.includes(filters.version)) {
847
+ return false;
848
+ }
849
+ if (filters.category !== void 0 && r.category !== filters.category) {
850
+ return false;
851
+ }
852
+ return true;
853
+ });
854
+ }
855
+
856
+ // src/core/parse.ts
857
+ function isFhirResource(value) {
858
+ return typeof value === "object" && value !== null && "resourceType" in value && typeof value.resourceType === "string";
859
+ }
860
+ function parseJson(input) {
861
+ let parsed;
862
+ try {
863
+ parsed = JSON.parse(input);
864
+ } catch (e) {
865
+ const message = e instanceof SyntaxError ? e.message : "Invalid JSON";
866
+ return { success: false, error: message };
867
+ }
868
+ if (!isFhirResource(parsed)) {
869
+ return { success: false, error: "Missing or invalid resourceType" };
870
+ }
871
+ return { success: true, resource: parsed };
872
+ }
873
+
874
+ // src/core/rules/walk.ts
875
+ var PROTOTYPE_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
876
+ function walkResource(resource, visitor) {
877
+ walkObject(resource, "", visitor);
878
+ }
879
+ function walkObject(obj, prefix, visitor) {
880
+ for (const [key, value] of Object.entries(obj)) {
881
+ if (PROTOTYPE_KEYS.has(key)) continue;
882
+ if (value === null || value === void 0) continue;
883
+ const path = prefix ? `${prefix}.${key}` : key;
884
+ visitor(path, key, value, obj);
885
+ if (Array.isArray(value)) {
886
+ walkArray(value, path, visitor);
887
+ } else if (typeof value === "object") {
888
+ walkObject(value, path, visitor);
889
+ }
890
+ }
891
+ }
892
+ function walkArray(arr, prefix, visitor) {
893
+ for (let i = 0; i < arr.length; i++) {
894
+ const item = arr[i];
895
+ if (item === null || item === void 0) continue;
896
+ const path = `${prefix}[${i}]`;
897
+ if (Array.isArray(item)) {
898
+ walkArray(item, path, visitor);
899
+ } else if (typeof item === "object") {
900
+ walkObject(item, path, visitor);
901
+ }
902
+ }
903
+ }
904
+
905
+ // src/core/profile-registry.ts
906
+ var KNOWN_PROFILES = [
907
+ // FHIR base profiles (defined in the spec itself)
908
+ {
909
+ canonical: "http://hl7.org/fhir/StructureDefinition/vitalsigns",
910
+ name: "Vital Signs",
911
+ igShort: "FHIR Base",
912
+ ig: "HL7 FHIR Base Specification",
913
+ docUrl: "https://hl7.org/fhir/R4/vitalsigns.html"
914
+ },
915
+ {
916
+ canonical: "http://hl7.org/fhir/StructureDefinition/bodyweight",
917
+ name: "Body Weight",
918
+ igShort: "FHIR Base",
919
+ ig: "HL7 FHIR Base Specification",
920
+ docUrl: "https://hl7.org/fhir/R4/bodyweight.html"
921
+ },
922
+ {
923
+ canonical: "http://hl7.org/fhir/StructureDefinition/heartrate",
924
+ name: "Heart Rate",
925
+ igShort: "FHIR Base",
926
+ ig: "HL7 FHIR Base Specification",
927
+ docUrl: "https://hl7.org/fhir/R4/heartrate.html"
928
+ },
929
+ {
930
+ canonical: "http://hl7.org/fhir/StructureDefinition/bp",
931
+ name: "Blood Pressure",
932
+ igShort: "FHIR Base",
933
+ ig: "HL7 FHIR Base Specification",
934
+ docUrl: "https://hl7.org/fhir/R4/bp.html"
935
+ },
936
+ // IPS
937
+ {
938
+ canonical: "http://hl7.org/fhir/uv/ips/StructureDefinition/Bundle-uv-ips",
939
+ name: "IPS Bundle",
940
+ igShort: "IPS",
941
+ ig: "International Patient Summary",
942
+ docUrl: "https://hl7.org/fhir/uv/ips/StructureDefinition-Bundle-uv-ips.html"
943
+ },
944
+ {
945
+ canonical: "http://hl7.org/fhir/uv/ips/StructureDefinition/Patient-uv-ips",
946
+ name: "IPS Patient",
947
+ igShort: "IPS",
948
+ ig: "International Patient Summary",
949
+ docUrl: "https://hl7.org/fhir/uv/ips/StructureDefinition-Patient-uv-ips.html"
950
+ },
951
+ {
952
+ canonical: "http://hl7.org/fhir/uv/ips/StructureDefinition/Composition-uv-ips",
953
+ name: "IPS Composition",
954
+ igShort: "IPS",
955
+ ig: "International Patient Summary",
956
+ docUrl: "https://hl7.org/fhir/uv/ips/StructureDefinition-Composition-uv-ips.html"
957
+ },
958
+ // US Core (selected high-traffic profiles)
959
+ {
960
+ canonical: "http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient",
961
+ name: "US Core Patient",
962
+ igShort: "US Core",
963
+ ig: "US Core Implementation Guide",
964
+ docUrl: "https://hl7.org/fhir/us/core/StructureDefinition-us-core-patient.html"
965
+ },
966
+ {
967
+ canonical: "http://hl7.org/fhir/us/core/StructureDefinition/us-core-observation-lab",
968
+ name: "US Core Laboratory Result Observation",
969
+ igShort: "US Core",
970
+ ig: "US Core Implementation Guide",
971
+ docUrl: "https://hl7.org/fhir/us/core/StructureDefinition-us-core-observation-lab.html"
972
+ },
973
+ {
974
+ canonical: "http://hl7.org/fhir/us/core/StructureDefinition/us-core-condition",
975
+ name: "US Core Condition",
976
+ igShort: "US Core",
977
+ ig: "US Core Implementation Guide",
978
+ docUrl: "https://hl7.org/fhir/us/core/StructureDefinition-us-core-condition.html"
979
+ },
980
+ {
981
+ canonical: "http://hl7.org/fhir/us/core/StructureDefinition/us-core-medicationrequest",
982
+ name: "US Core MedicationRequest",
983
+ igShort: "US Core",
984
+ ig: "US Core Implementation Guide",
985
+ docUrl: "https://hl7.org/fhir/us/core/StructureDefinition-us-core-medicationrequest.html"
986
+ }
987
+ ];
988
+ var PROFILE_NAMESPACES = [
989
+ // IPS
990
+ {
991
+ prefix: "http://hl7.org/fhir/uv/ips/",
992
+ igShort: "IPS",
993
+ ig: "International Patient Summary",
994
+ igUrl: "https://hl7.org/fhir/uv/ips/"
995
+ },
996
+ // US Core
997
+ {
998
+ prefix: "http://hl7.org/fhir/us/core/",
999
+ igShort: "US Core",
1000
+ ig: "US Core Implementation Guide",
1001
+ igUrl: "https://hl7.org/fhir/us/core/"
1002
+ },
1003
+ // AU Base
1004
+ {
1005
+ prefix: "http://hl7.org.au/fhir/StructureDefinition/",
1006
+ igShort: "AU Base",
1007
+ ig: "AU Base Implementation Guide",
1008
+ igUrl: "https://build.fhir.org/ig/hl7au/au-fhir-base/"
1009
+ },
1010
+ // AU Core
1011
+ {
1012
+ prefix: "http://hl7.org.au/fhir/core/",
1013
+ igShort: "AU Core",
1014
+ ig: "AU Core Implementation Guide",
1015
+ igUrl: "https://build.fhir.org/ig/hl7au/au-fhir-core/"
1016
+ },
1017
+ // mCode
1018
+ {
1019
+ prefix: "http://hl7.org/fhir/us/mcode/",
1020
+ igShort: "mCode",
1021
+ ig: "minimal Common Oncology Data Elements",
1022
+ igUrl: "https://hl7.org/fhir/us/mcode/"
1023
+ },
1024
+ // QI Core
1025
+ {
1026
+ prefix: "http://hl7.org/fhir/us/qicore/",
1027
+ igShort: "QI Core",
1028
+ ig: "QI Core Implementation Guide",
1029
+ igUrl: "https://hl7.org/fhir/us/qicore/"
1030
+ },
1031
+ // CARIN Blue Button
1032
+ {
1033
+ prefix: "http://hl7.org/fhir/us/carin-bb/",
1034
+ igShort: "CARIN BB",
1035
+ ig: "CARIN Blue Button Implementation Guide",
1036
+ igUrl: "https://hl7.org/fhir/us/carin-bb/"
1037
+ },
1038
+ // Da Vinci
1039
+ {
1040
+ prefix: "http://hl7.org/fhir/us/davinci-",
1041
+ igShort: "Da Vinci",
1042
+ ig: "Da Vinci Implementation Guides",
1043
+ igUrl: "https://confluence.hl7.org/display/DVP"
1044
+ },
1045
+ // SMART App Launch
1046
+ {
1047
+ prefix: "http://hl7.org/fhir/smart-app-launch/",
1048
+ igShort: "SMART",
1049
+ ig: "SMART App Launch",
1050
+ igUrl: "https://hl7.org/fhir/smart-app-launch/"
1051
+ },
1052
+ // FHIR Base profiles
1053
+ {
1054
+ prefix: "http://hl7.org/fhir/StructureDefinition/",
1055
+ igShort: "FHIR Base",
1056
+ ig: "HL7 FHIR Base Specification",
1057
+ igUrl: "https://hl7.org/fhir/R4/profiling.html"
1058
+ }
1059
+ ];
1060
+ var CANONICAL_URL_PATTERN = /^https?:\/\/.+/;
1061
+ var URN_PATTERN = /^urn:.+/;
1062
+ function isValidCanonicalUrl(url) {
1063
+ return CANONICAL_URL_PATTERN.test(url) || URN_PATTERN.test(url);
1064
+ }
1065
+ function lookupProfile(canonical) {
1066
+ return KNOWN_PROFILES.find((p) => p.canonical === canonical);
1067
+ }
1068
+ function lookupProfileNamespace(canonical) {
1069
+ return PROFILE_NAMESPACES.find((ns) => canonical.startsWith(ns.prefix));
1070
+ }
1071
+
1072
+ // src/core/rules/id-format.ts
1073
+ var FHIR_ID_PATTERN = /^[A-Za-z0-9\-.]{1,64}$/;
1074
+ var idFormatRule = {
1075
+ id: "fhir-id-format",
1076
+ description: "FHIR id values must match [A-Za-z0-9\\-.]{1,64}",
1077
+ check(resource, _version) {
1078
+ const findings = [];
1079
+ walkResource(resource, (path, key, value) => {
1080
+ if (key !== "id") return;
1081
+ if (typeof value !== "string") return;
1082
+ if (!FHIR_ID_PATTERN.test(value)) {
1083
+ findings.push({
1084
+ path,
1085
+ message: `Invalid FHIR id '${value}': must match [A-Za-z0-9\\-.]{1,64}`,
1086
+ severity: "warning",
1087
+ ruleId: "fhir-id-format"
1088
+ });
1089
+ }
1090
+ });
1091
+ return findings;
1092
+ }
1093
+ };
1094
+
1095
+ // src/core/rules/date-format.ts
1096
+ var FHIR_DATE_PATTERN = /^\d{4}(-\d{2}(-\d{2})?)?$/;
1097
+ var FHIR_DATETIME_PATTERN = /^\d{4}(-\d{2}(-\d{2}(T\d{2}:\d{2}(:\d{2}(\.\d+)?)?(Z|[+-]\d{2}:\d{2}))?)?)?$/;
1098
+ var FHIR_INSTANT_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})$/;
1099
+ var INSTANT_FIELD_NAMES = /* @__PURE__ */ new Set(["lastUpdated", "issued", "recorded"]);
1100
+ var DATETIME_FIELD_NAMES = /* @__PURE__ */ new Set([
1101
+ "authoredOn",
1102
+ "occurrenceDateTime",
1103
+ "onsetDateTime",
1104
+ "abatementDateTime",
1105
+ "performedDateTime"
1106
+ ]);
1107
+ function classifyDateField(key) {
1108
+ if (INSTANT_FIELD_NAMES.has(key)) return "instant";
1109
+ if (DATETIME_FIELD_NAMES.has(key) || key.endsWith("DateTime")) return "dateTime";
1110
+ if (key === "date" || key.endsWith("Date") || key.endsWith("date")) return "date";
1111
+ return null;
1112
+ }
1113
+ function patternFor(type) {
1114
+ if (type === "instant") return FHIR_INSTANT_PATTERN;
1115
+ if (type === "dateTime") return FHIR_DATETIME_PATTERN;
1116
+ return FHIR_DATE_PATTERN;
1117
+ }
1118
+ function exampleFor(type) {
1119
+ if (type === "instant") return "e.g. 2024-03-15T10:30:00Z";
1120
+ if (type === "dateTime") return "e.g. 2024-03-15T10:30:00Z or 2024-03-15";
1121
+ return "e.g. 2024, 2024-03, or 2024-03-15";
1122
+ }
1123
+ var dateFormatRule = {
1124
+ id: "fhir-date-format",
1125
+ description: "FHIR date/dateTime/instant fields must follow the FHIR date format subset",
1126
+ check(resource, _version) {
1127
+ const findings = [];
1128
+ walkResource(resource, (path, key, value) => {
1129
+ const fieldType = classifyDateField(key);
1130
+ if (fieldType === null) return;
1131
+ if (typeof value !== "string") return;
1132
+ if (!patternFor(fieldType).test(value)) {
1133
+ findings.push({
1134
+ path,
1135
+ message: `Invalid FHIR ${fieldType} '${value}' at '${path}': ${exampleFor(fieldType)}`,
1136
+ severity: "warning",
1137
+ ruleId: "fhir-date-format"
1138
+ });
1139
+ }
1140
+ });
1141
+ return findings;
1142
+ }
1143
+ };
1144
+
1145
+ // src/core/rules/reference-format.ts
1146
+ var RELATIVE_REF_PATTERN = /^[A-Z][A-Za-z]+\/[A-Za-z0-9\-.]{1,64}$/;
1147
+ var FRAGMENT_REF_PATTERN = /^#.+$/;
1148
+ var ABSOLUTE_REF_PATTERN = /^https?:\/\/.+/;
1149
+ var URN_REF_PATTERN = /^urn:(uuid|oid):.+$/;
1150
+ function isValidReference(ref) {
1151
+ return RELATIVE_REF_PATTERN.test(ref) || FRAGMENT_REF_PATTERN.test(ref) || ABSOLUTE_REF_PATTERN.test(ref) || URN_REF_PATTERN.test(ref);
1152
+ }
1153
+ var referenceFormatRule = {
1154
+ id: "fhir-reference-format",
1155
+ description: "FHIR reference values must be relative (ResourceType/id), absolute URL, fragment (#id), or URN",
1156
+ check(resource, version) {
1157
+ const findings = [];
1158
+ walkResource(resource, (path, key, value) => {
1159
+ if (key !== "reference") return;
1160
+ if (typeof value !== "string") return;
1161
+ if (!isValidReference(value)) {
1162
+ findings.push({
1163
+ path,
1164
+ message: `Invalid FHIR reference '${value}' at '${path}': expected ResourceType/id, absolute URL, #fragment, or urn:uuid:/urn:oid:`,
1165
+ severity: "warning",
1166
+ ruleId: "fhir-reference-format"
1167
+ });
1168
+ return;
1169
+ }
1170
+ if (version !== void 0 && RELATIVE_REF_PATTERN.test(value)) {
1171
+ const refResourceType = value.split("/")[0];
1172
+ if (refResourceType && !isKnownResourceType(refResourceType, version)) {
1173
+ findings.push({
1174
+ path,
1175
+ message: `Unknown resource type '${refResourceType}' in reference '${value}' for ${version}`,
1176
+ severity: "warning",
1177
+ ruleId: "fhir-reference-format"
1178
+ });
1179
+ }
1180
+ }
1181
+ });
1182
+ return findings;
1183
+ }
1184
+ };
1185
+
1186
+ // src/core/rules/data/required-field-defs.ts
1187
+ var REQUIRED_FIELDS = {
1188
+ Observation: [
1189
+ { field: "status", label: "status" },
1190
+ { field: "code", label: "code" }
1191
+ ],
1192
+ Condition: [
1193
+ { field: "subject", label: "subject" }
1194
+ ],
1195
+ Procedure: [
1196
+ { field: "status", label: "status" },
1197
+ { field: "subject", label: "subject" }
1198
+ ],
1199
+ MedicationRequest: [
1200
+ { field: "status", label: "status" },
1201
+ { field: "intent", label: "intent" },
1202
+ { field: "subject", label: "subject" }
1203
+ ],
1204
+ MedicationAdministration: [
1205
+ { field: "status", label: "status" },
1206
+ { field: "subject", label: "subject" }
1207
+ ],
1208
+ MedicationStatement: [
1209
+ { field: "status", label: "status" },
1210
+ { field: "subject", label: "subject" }
1211
+ ],
1212
+ DiagnosticReport: [
1213
+ { field: "status", label: "status" },
1214
+ { field: "code", label: "code" }
1215
+ ],
1216
+ Encounter: [
1217
+ { field: "status", label: "status", versions: ["R4", "R4B"] },
1218
+ { field: "class", label: "class", versions: ["R4", "R4B"] }
1219
+ ],
1220
+ AllergyIntolerance: [
1221
+ { field: "patient", label: "patient", versions: ["R4", "R4B"] },
1222
+ { field: "subject", label: "subject", versions: ["R5"] }
1223
+ ],
1224
+ Immunization: [
1225
+ { field: "status", label: "status" },
1226
+ { field: "vaccineCode", label: "vaccineCode" },
1227
+ { field: "patient", label: "patient" }
1228
+ ],
1229
+ CarePlan: [
1230
+ { field: "status", label: "status" },
1231
+ { field: "intent", label: "intent" },
1232
+ { field: "subject", label: "subject" }
1233
+ ],
1234
+ ServiceRequest: [
1235
+ { field: "status", label: "status" },
1236
+ { field: "intent", label: "intent" },
1237
+ { field: "subject", label: "subject" }
1238
+ ],
1239
+ Bundle: [
1240
+ { field: "type", label: "type" }
1241
+ ],
1242
+ Composition: [
1243
+ { field: "status", label: "status" },
1244
+ { field: "type", label: "type" },
1245
+ { field: "date", label: "date" },
1246
+ { field: "author", label: "author" }
1247
+ ],
1248
+ DocumentReference: [
1249
+ { field: "status", label: "status" },
1250
+ { field: "content", label: "content" }
1251
+ ],
1252
+ Claim: [
1253
+ { field: "status", label: "status" },
1254
+ { field: "type", label: "type" },
1255
+ { field: "use", label: "use" },
1256
+ { field: "patient", label: "patient" },
1257
+ { field: "provider", label: "provider" }
1258
+ ],
1259
+ ExplanationOfBenefit: [
1260
+ { field: "status", label: "status" },
1261
+ { field: "type", label: "type" },
1262
+ { field: "use", label: "use" },
1263
+ { field: "patient", label: "patient" },
1264
+ { field: "provider", label: "provider" },
1265
+ { field: "insurer", label: "insurer" },
1266
+ { field: "outcome", label: "outcome" }
1267
+ ],
1268
+ Coverage: [
1269
+ { field: "status", label: "status" },
1270
+ { field: "beneficiary", label: "beneficiary" }
1271
+ ],
1272
+ // Organization, Patient, Practitioner — nothing truly required beyond resourceType
1273
+ Organization: [],
1274
+ Patient: [],
1275
+ Practitioner: []
1276
+ };
1277
+
1278
+ // src/core/rules/required-fields.ts
1279
+ var RULE_ID = "fhir-required-fields";
1280
+ function resolvePath(obj, path) {
1281
+ const parts = path.split(".");
1282
+ let current = obj;
1283
+ for (const part of parts) {
1284
+ if (current === null || current === void 0 || typeof current !== "object") {
1285
+ return void 0;
1286
+ }
1287
+ current = current[part];
1288
+ }
1289
+ return current;
1290
+ }
1291
+ function isPresent(value) {
1292
+ if (value === null || value === void 0) return false;
1293
+ if (Array.isArray(value)) return value.length > 0;
1294
+ return true;
1295
+ }
1296
+ var requiredFieldsRule = {
1297
+ id: RULE_ID,
1298
+ description: "Required fields for common FHIR resource types must be present (curated top ~20 types)",
1299
+ check(resource, version) {
1300
+ const defs = REQUIRED_FIELDS[resource.resourceType];
1301
+ if (defs === void 0) return [];
1302
+ const findings = [];
1303
+ const docUrl = version ? `${fhirBaseUrl(version)}/${resource.resourceType.toLowerCase()}.html` : `https://hl7.org/fhir/${resource.resourceType.toLowerCase()}.html`;
1304
+ for (const def of defs) {
1305
+ if (def.versions !== void 0 && version !== void 0) {
1306
+ if (!def.versions.includes(version)) continue;
1307
+ }
1308
+ if (def.versions !== void 0 && version === void 0) continue;
1309
+ const value = resolvePath(resource, def.field);
1310
+ if (!isPresent(value)) {
1311
+ findings.push({
1312
+ path: def.field,
1313
+ message: `Missing required field '${def.label}' for ${resource.resourceType}`,
1314
+ severity: "warning",
1315
+ ruleId: RULE_ID,
1316
+ docUrl
1317
+ });
1318
+ }
1319
+ }
1320
+ return findings;
1321
+ }
1322
+ };
1323
+
1324
+ // src/core/rules/data/status-value-defs.ts
1325
+ var STATUS_VALUES = {
1326
+ Observation: [
1327
+ {
1328
+ field: "status",
1329
+ values: [
1330
+ "registered",
1331
+ "preliminary",
1332
+ "final",
1333
+ "amended",
1334
+ "corrected",
1335
+ "cancelled",
1336
+ "entered-in-error",
1337
+ "unknown"
1338
+ ]
1339
+ }
1340
+ ],
1341
+ Condition: [
1342
+ {
1343
+ field: "clinicalStatus.coding[].code",
1344
+ values: ["active", "recurrence", "relapse", "inactive", "remission", "resolved"]
1345
+ },
1346
+ {
1347
+ field: "verificationStatus.coding[].code",
1348
+ values: [
1349
+ "unconfirmed",
1350
+ "provisional",
1351
+ "differential",
1352
+ "confirmed",
1353
+ "refuted",
1354
+ "entered-in-error"
1355
+ ]
1356
+ }
1357
+ ],
1358
+ Procedure: [
1359
+ {
1360
+ field: "status",
1361
+ values: [
1362
+ "preparation",
1363
+ "in-progress",
1364
+ "not-done",
1365
+ "on-hold",
1366
+ "stopped",
1367
+ "completed",
1368
+ "entered-in-error",
1369
+ "unknown"
1370
+ ]
1371
+ }
1372
+ ],
1373
+ MedicationRequest: [
1374
+ {
1375
+ field: "status",
1376
+ values: [
1377
+ "active",
1378
+ "on-hold",
1379
+ "ended",
1380
+ "cancelled",
1381
+ "completed",
1382
+ "entered-in-error",
1383
+ "stopped",
1384
+ "draft",
1385
+ "unknown"
1386
+ ]
1387
+ },
1388
+ {
1389
+ field: "intent",
1390
+ values: [
1391
+ "proposal",
1392
+ "plan",
1393
+ "order",
1394
+ "original-order",
1395
+ "reflex-order",
1396
+ "filler-order",
1397
+ "instance-order",
1398
+ "option"
1399
+ ]
1400
+ }
1401
+ ],
1402
+ DiagnosticReport: [
1403
+ {
1404
+ field: "status",
1405
+ values: [
1406
+ "registered",
1407
+ "partial",
1408
+ "preliminary",
1409
+ "modified",
1410
+ "final",
1411
+ "amended",
1412
+ "corrected",
1413
+ "appended",
1414
+ "cancelled",
1415
+ "entered-in-error",
1416
+ "unknown"
1417
+ ]
1418
+ }
1419
+ ],
1420
+ Encounter: [
1421
+ {
1422
+ field: "status",
1423
+ values: [
1424
+ "planned",
1425
+ "arrived",
1426
+ "triaged",
1427
+ "in-progress",
1428
+ "onleave",
1429
+ "finished",
1430
+ "cancelled",
1431
+ "entered-in-error",
1432
+ "unknown"
1433
+ ],
1434
+ versions: ["R4", "R4B"]
1435
+ },
1436
+ {
1437
+ field: "status",
1438
+ values: [
1439
+ "planned",
1440
+ "in-progress",
1441
+ "on-hold",
1442
+ "discharged",
1443
+ "completed",
1444
+ "cancelled",
1445
+ "discontinued",
1446
+ "entered-in-error",
1447
+ "unknown"
1448
+ ],
1449
+ versions: ["R5"]
1450
+ }
1451
+ ],
1452
+ Bundle: [
1453
+ {
1454
+ field: "type",
1455
+ values: [
1456
+ "document",
1457
+ "message",
1458
+ "transaction",
1459
+ "transaction-response",
1460
+ "batch",
1461
+ "batch-response",
1462
+ "history",
1463
+ "searchset",
1464
+ "collection",
1465
+ "subscription-notification"
1466
+ ]
1467
+ }
1468
+ ],
1469
+ AllergyIntolerance: [
1470
+ {
1471
+ field: "clinicalStatus.coding[].code",
1472
+ values: ["active", "inactive", "resolved"]
1473
+ },
1474
+ {
1475
+ field: "verificationStatus.coding[].code",
1476
+ values: ["unconfirmed", "confirmed", "refuted", "entered-in-error"]
1477
+ }
1478
+ ],
1479
+ CarePlan: [
1480
+ {
1481
+ field: "status",
1482
+ values: [
1483
+ "draft",
1484
+ "active",
1485
+ "on-hold",
1486
+ "revoked",
1487
+ "completed",
1488
+ "entered-in-error",
1489
+ "unknown"
1490
+ ]
1491
+ },
1492
+ {
1493
+ field: "intent",
1494
+ values: ["proposal", "plan", "order", "option", "directive"]
1495
+ }
1496
+ ],
1497
+ ServiceRequest: [
1498
+ {
1499
+ field: "status",
1500
+ values: [
1501
+ "draft",
1502
+ "active",
1503
+ "on-hold",
1504
+ "revoked",
1505
+ "completed",
1506
+ "entered-in-error",
1507
+ "unknown"
1508
+ ]
1509
+ },
1510
+ {
1511
+ field: "intent",
1512
+ values: [
1513
+ "proposal",
1514
+ "plan",
1515
+ "directive",
1516
+ "order",
1517
+ "original-order",
1518
+ "reflex-order",
1519
+ "filler-order",
1520
+ "instance-order",
1521
+ "option"
1522
+ ]
1523
+ }
1524
+ ],
1525
+ DocumentReference: [
1526
+ {
1527
+ field: "status",
1528
+ values: ["current", "superseded", "entered-in-error"]
1529
+ }
1530
+ ],
1531
+ Immunization: [
1532
+ {
1533
+ field: "status",
1534
+ values: ["completed", "entered-in-error", "not-done"]
1535
+ }
1536
+ ],
1537
+ Coverage: [
1538
+ {
1539
+ field: "status",
1540
+ values: ["active", "cancelled", "draft", "entered-in-error"]
1541
+ }
1542
+ ],
1543
+ Claim: [
1544
+ {
1545
+ field: "status",
1546
+ values: ["active", "cancelled", "draft", "entered-in-error"]
1547
+ }
1548
+ ],
1549
+ ExplanationOfBenefit: [
1550
+ {
1551
+ field: "status",
1552
+ values: ["active", "cancelled", "draft", "entered-in-error"]
1553
+ },
1554
+ {
1555
+ field: "outcome",
1556
+ values: ["queued", "complete", "error", "partial"]
1557
+ }
1558
+ ]
1559
+ };
1560
+
1561
+ // src/core/rules/status-values.ts
1562
+ var RULE_ID2 = "fhir-status-values";
1563
+ function readFieldValues(resource, field) {
1564
+ const CODING_ARRAY_PATTERN = /^(.+)\.coding\[\]\.code$/;
1565
+ const match = CODING_ARRAY_PATTERN.exec(field);
1566
+ if (match !== null) {
1567
+ const parentPath = match[1];
1568
+ if (parentPath === void 0) return [];
1569
+ const parent = resource[parentPath];
1570
+ if (parent === null || parent === void 0 || typeof parent !== "object") {
1571
+ return [];
1572
+ }
1573
+ const codingArray = parent["coding"];
1574
+ if (!Array.isArray(codingArray)) return [];
1575
+ const codes = [];
1576
+ for (const entry of codingArray) {
1577
+ if (entry !== null && typeof entry === "object") {
1578
+ const code = entry["code"];
1579
+ if (typeof code === "string") codes.push(code);
1580
+ }
1581
+ }
1582
+ return codes;
1583
+ }
1584
+ const value = resource[field];
1585
+ if (typeof value === "string") return [value];
1586
+ return [];
1587
+ }
1588
+ var statusValuesRule = {
1589
+ id: RULE_ID2,
1590
+ description: "Status field values must match the FHIR-required value set for each resource type",
1591
+ check(resource, version) {
1592
+ const defs = STATUS_VALUES[resource.resourceType];
1593
+ if (defs === void 0) return [];
1594
+ const findings = [];
1595
+ for (const def of defs) {
1596
+ if (def.versions !== void 0 && version !== void 0) {
1597
+ if (!def.versions.includes(version)) continue;
1598
+ }
1599
+ const values = readFieldValues(resource, def.field);
1600
+ if (values.length === 0) continue;
1601
+ for (const actual of values) {
1602
+ if (!def.values.includes(actual)) {
1603
+ findings.push({
1604
+ path: def.field.replace(".coding[].code", ""),
1605
+ message: `Invalid value '${actual}' for ${resource.resourceType}.${def.field}. Expected one of: ${def.values.join(", ")}`,
1606
+ severity: "warning",
1607
+ ruleId: RULE_ID2
1608
+ });
1609
+ }
1610
+ }
1611
+ }
1612
+ return findings;
1613
+ }
1614
+ };
1615
+
1616
+ // src/core/rules/codeable-concept.ts
1617
+ var RULE_ID3 = "fhir-codeable-concept";
1618
+ var URI_PREFIXES = ["http://", "https://", "urn:"];
1619
+ var KNOWN_CODEABLE_CONCEPT_FIELDS = {
1620
+ Observation: ["code", "valueCodeableConcept", "category"],
1621
+ Condition: ["code", "clinicalStatus", "verificationStatus", "category", "severity"],
1622
+ Procedure: ["code", "category"],
1623
+ MedicationRequest: ["medicationCodeableConcept"],
1624
+ DiagnosticReport: ["code", "category"],
1625
+ AllergyIntolerance: ["code", "clinicalStatus", "verificationStatus", "type", "category"],
1626
+ Immunization: ["vaccineCode"],
1627
+ ServiceRequest: ["code", "category"]
1628
+ };
1629
+ function isValidUri(value) {
1630
+ return URI_PREFIXES.some((prefix) => value.startsWith(prefix));
1631
+ }
1632
+ function checkCodingEntry(coding, path, findings) {
1633
+ const hasSystem = "system" in coding;
1634
+ const hasCode = "code" in coding;
1635
+ if (hasSystem && !hasCode) {
1636
+ findings.push({
1637
+ path,
1638
+ message: `Coding at '${path}' has 'system' but is missing 'code'`,
1639
+ severity: "warning",
1640
+ ruleId: RULE_ID3
1641
+ });
1642
+ return;
1643
+ }
1644
+ if (hasCode && !hasSystem) {
1645
+ findings.push({
1646
+ path,
1647
+ message: `Coding at '${path}' has 'code' but is missing 'system'`,
1648
+ severity: "warning",
1649
+ ruleId: RULE_ID3
1650
+ });
1651
+ return;
1652
+ }
1653
+ if (!hasSystem && !hasCode) return;
1654
+ const systemVal = coding["system"];
1655
+ const codeVal = coding["code"];
1656
+ if (typeof systemVal === "string" && !isValidUri(systemVal)) {
1657
+ findings.push({
1658
+ path: `${path}.system`,
1659
+ message: `Coding 'system' should be a URI starting with http://, https://, or urn: \u2014 got '${systemVal}'`,
1660
+ severity: "warning",
1661
+ ruleId: RULE_ID3
1662
+ });
1663
+ }
1664
+ if (typeof codeVal === "string" && codeVal.trim() === "") {
1665
+ findings.push({
1666
+ path: `${path}.code`,
1667
+ message: `Coding 'code' must be a non-empty string`,
1668
+ severity: "warning",
1669
+ ruleId: RULE_ID3
1670
+ });
1671
+ }
1672
+ }
1673
+ var codeableConceptRule = {
1674
+ id: RULE_ID3,
1675
+ description: "Coding and CodeableConcept objects must have correct shape; known CodeableConcept fields must not be plain strings",
1676
+ check(resource, _version) {
1677
+ const findings = [];
1678
+ const knownFields = KNOWN_CODEABLE_CONCEPT_FIELDS[resource.resourceType];
1679
+ if (knownFields !== void 0) {
1680
+ for (const field of knownFields) {
1681
+ const value = resource[field];
1682
+ if (typeof value === "string") {
1683
+ findings.push({
1684
+ path: field,
1685
+ message: `'${field}' should be a CodeableConcept object, not a plain string`,
1686
+ severity: "warning",
1687
+ ruleId: RULE_ID3
1688
+ });
1689
+ }
1690
+ }
1691
+ }
1692
+ walkResource(resource, (path, key, value, parent) => {
1693
+ if (key !== "coding") return;
1694
+ const ccPath = path.endsWith(".coding") ? path.slice(0, -".coding".length) : path;
1695
+ if (!Array.isArray(value)) {
1696
+ findings.push({
1697
+ path,
1698
+ message: `'coding' must be an array`,
1699
+ severity: "warning",
1700
+ ruleId: RULE_ID3
1701
+ });
1702
+ return;
1703
+ }
1704
+ if (value.length === 0 && typeof parent["text"] !== "string") {
1705
+ findings.push({
1706
+ path: ccPath,
1707
+ message: `CodeableConcept has empty 'coding' array and no 'text' \u2014 it carries no meaning`,
1708
+ severity: "warning",
1709
+ ruleId: RULE_ID3
1710
+ });
1711
+ return;
1712
+ }
1713
+ for (let i = 0; i < value.length; i++) {
1714
+ const entry = value[i];
1715
+ if (entry === null || typeof entry !== "object" || Array.isArray(entry)) continue;
1716
+ checkCodingEntry(
1717
+ entry,
1718
+ `${path}[${i}]`,
1719
+ findings
1720
+ );
1721
+ }
1722
+ });
1723
+ return findings;
1724
+ }
1725
+ };
1726
+
1727
+ // src/core/rules/profile-aware.ts
1728
+ var HL7_VALIDATOR_URL = "https://confluence.hl7.org/display/FHIR/Using+the+FHIR+Validator";
1729
+ var RULE_ID4 = "fhir-profile-aware";
1730
+ var profileAwareRule = {
1731
+ id: RULE_ID4,
1732
+ description: "Validates meta.profile entries and identifies known IGs and profiles",
1733
+ check(resource, _version) {
1734
+ const findings = [];
1735
+ const metaRaw = resource.meta;
1736
+ if (!metaRaw) return findings;
1737
+ const profileField = metaRaw["profile"];
1738
+ if (!Array.isArray(profileField) || profileField.length === 0) return findings;
1739
+ const profileArray = profileField;
1740
+ for (let i = 0; i < profileArray.length; i++) {
1741
+ const entry = profileArray[i];
1742
+ const path = `meta.profile[${i}]`;
1743
+ if (typeof entry !== "string") {
1744
+ findings.push({
1745
+ path,
1746
+ message: "meta.profile entries must be strings",
1747
+ severity: "warning",
1748
+ ruleId: RULE_ID4
1749
+ });
1750
+ continue;
1751
+ }
1752
+ if (!isValidCanonicalUrl(entry)) {
1753
+ findings.push({
1754
+ path,
1755
+ message: `Profile URL "${entry}" is not a valid canonical URL (must be an absolute URI)`,
1756
+ severity: "warning",
1757
+ ruleId: RULE_ID4
1758
+ });
1759
+ continue;
1760
+ }
1761
+ const exactMatch = lookupProfile(entry);
1762
+ if (exactMatch !== void 0) {
1763
+ findings.push({
1764
+ path,
1765
+ message: `Declares ${exactMatch.igShort} profile "${exactMatch.name}" \u2192 ${exactMatch.docUrl}`,
1766
+ severity: "info",
1767
+ docUrl: exactMatch.docUrl,
1768
+ ruleId: RULE_ID4
1769
+ });
1770
+ continue;
1771
+ }
1772
+ const namespaceMatch = lookupProfileNamespace(entry);
1773
+ if (namespaceMatch !== void 0) {
1774
+ findings.push({
1775
+ path,
1776
+ message: `Declares ${namespaceMatch.igShort} profile (${namespaceMatch.ig}) \u2014 for conformance validation load the IG in the HL7 FHIR Validator \u2192 ${namespaceMatch.igUrl}`,
1777
+ severity: "info",
1778
+ docUrl: namespaceMatch.igUrl,
1779
+ ruleId: RULE_ID4
1780
+ });
1781
+ continue;
1782
+ }
1783
+ findings.push({
1784
+ path,
1785
+ message: `Declares profile ${entry} \u2014 for conformance validation use the HL7 FHIR Validator with the relevant IG loaded \u2192 ${HL7_VALIDATOR_URL}`,
1786
+ severity: "info",
1787
+ docUrl: HL7_VALIDATOR_URL,
1788
+ ruleId: RULE_ID4
1789
+ });
1790
+ }
1791
+ return findings;
1792
+ }
1793
+ };
1794
+
1795
+ // src/core/rules/index.ts
1796
+ var FORMAT_RULES = [
1797
+ idFormatRule,
1798
+ dateFormatRule,
1799
+ referenceFormatRule
1800
+ ];
1801
+ var STRUCTURAL_RULES = [
1802
+ requiredFieldsRule,
1803
+ statusValuesRule,
1804
+ codeableConceptRule
1805
+ ];
1806
+ var PROFILE_RULES = [profileAwareRule];
1807
+ var ALL_RULES = [
1808
+ ...FORMAT_RULES,
1809
+ ...STRUCTURAL_RULES,
1810
+ ...PROFILE_RULES
1811
+ ];
1812
+ function runRules(resource, rules, version) {
1813
+ const findings = [];
1814
+ for (const rule of rules) {
1815
+ findings.push(...rule.check(resource, version));
1816
+ }
1817
+ return findings;
1818
+ }
1819
+
1820
+ // src/core/validate.ts
1821
+ import { z } from "zod";
1822
+ var fhirMetaSchema = z.object({
1823
+ versionId: z.string().optional(),
1824
+ lastUpdated: z.string().optional()
1825
+ }).passthrough();
1826
+ var fhirResourceSchema = z.object({
1827
+ resourceType: z.string().min(1, "resourceType must be a non-empty string"),
1828
+ id: z.string().optional(),
1829
+ meta: fhirMetaSchema.optional()
1830
+ }).passthrough();
1831
+ function validate(resource, version) {
1832
+ const result = fhirResourceSchema.safeParse(resource);
1833
+ const errors = [];
1834
+ if (!result.success) {
1835
+ for (const issue of result.error.issues) {
1836
+ errors.push({
1837
+ path: issue.path.join("."),
1838
+ message: issue.message,
1839
+ severity: "error"
1840
+ });
1841
+ }
1842
+ }
1843
+ if (version !== void 0) {
1844
+ if (!isKnownResourceType(resource.resourceType, version)) {
1845
+ errors.push({
1846
+ path: "resourceType",
1847
+ message: `resourceType '${resource.resourceType}' is not in the known registry for ${version}. This may be valid \u2014 check https://hl7.org/fhir/resourcelist.html`,
1848
+ severity: "warning",
1849
+ docUrl: "https://hl7.org/fhir/resourcelist.html"
1850
+ });
1851
+ }
1852
+ const metaRaw = resource.meta;
1853
+ const metaFhirVersion = metaRaw?.["fhirVersion"];
1854
+ if (typeof metaFhirVersion === "string") {
1855
+ const mappedVersion = VERSION_STRING_MAP.get(metaFhirVersion);
1856
+ if (mappedVersion !== version) {
1857
+ errors.push({
1858
+ path: "meta.fhirVersion",
1859
+ message: `meta.fhirVersion '${metaFhirVersion}' does not match expected version ${version}`,
1860
+ severity: "warning"
1861
+ });
1862
+ }
1863
+ }
1864
+ if (version === "R5") {
1865
+ const textRaw = resource["text"];
1866
+ if (textRaw !== void 0 && textRaw !== null && typeof textRaw === "object") {
1867
+ if ("status" in textRaw && !("div" in textRaw)) {
1868
+ errors.push({
1869
+ path: "text",
1870
+ message: "R5 recommends narrative text with a div element",
1871
+ severity: "info"
1872
+ });
1873
+ }
1874
+ }
1875
+ }
1876
+ }
1877
+ errors.push(...runRules(resource, FORMAT_RULES, version));
1878
+ if (version !== void 0) {
1879
+ errors.push(...runRules(resource, STRUCTURAL_RULES, version));
1880
+ }
1881
+ errors.push(...runRules(resource, PROFILE_RULES, version));
1882
+ const hint = version !== void 0 ? {
1883
+ message: "For full FHIR schema validation, use the official HL7 FHIR Validator",
1884
+ docUrl: "https://confluence.hl7.org/display/FHIR/Using+the+FHIR+Validator"
1885
+ } : void 0;
1886
+ if (errors.length === 0) {
1887
+ return hint !== void 0 ? { valid: true, hint } : { valid: true };
1888
+ }
1889
+ return hint !== void 0 ? { valid: false, errors, hint } : { valid: false, errors };
1890
+ }
1891
+
1892
+ // src/core/classify.ts
1893
+ function classifyChange(left, right) {
1894
+ if (left === void 0) {
1895
+ return "added";
1896
+ }
1897
+ if (right === void 0) {
1898
+ return "removed";
1899
+ }
1900
+ if (typeof left !== typeof right) {
1901
+ return "type-changed";
1902
+ }
1903
+ return "changed";
1904
+ }
1905
+
1906
+ // src/core/diff.ts
1907
+ function isPlainObject(value) {
1908
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1909
+ }
1910
+ function shouldIgnorePath(path, ignorePaths) {
1911
+ for (const ignorePath of ignorePaths) {
1912
+ if (ignorePath === path) {
1913
+ return true;
1914
+ }
1915
+ if (ignorePath.endsWith(".*")) {
1916
+ const prefix = ignorePath.slice(0, -2);
1917
+ if (path.startsWith(prefix + ".")) {
1918
+ const rest = path.slice(prefix.length + 1);
1919
+ if (!rest.includes(".") && !rest.includes("[")) {
1920
+ return true;
1921
+ }
1922
+ }
1923
+ }
1924
+ }
1925
+ return false;
1926
+ }
1927
+ function walkNodes(left, right, path, ignorePaths, entries) {
1928
+ if (shouldIgnorePath(path, ignorePaths)) {
1929
+ return;
1930
+ }
1931
+ const leftObj = left === void 0 && isPlainObject(right) ? {} : left;
1932
+ const rightObj = right === void 0 && isPlainObject(left) ? {} : right;
1933
+ const leftArr = left === void 0 && Array.isArray(right) ? [] : leftObj;
1934
+ const rightArr = right === void 0 && Array.isArray(left) ? [] : rightObj;
1935
+ if (isPlainObject(leftArr) && isPlainObject(rightArr)) {
1936
+ const allKeys = Array.from(
1937
+ /* @__PURE__ */ new Set([...Object.keys(leftArr), ...Object.keys(rightArr)])
1938
+ ).sort();
1939
+ for (const key of allKeys) {
1940
+ const childPath = path === "" ? key : `${path}.${key}`;
1941
+ walkNodes(leftArr[key], rightArr[key], childPath, ignorePaths, entries);
1942
+ }
1943
+ return;
1944
+ }
1945
+ if (Array.isArray(leftArr) && Array.isArray(rightArr)) {
1946
+ const maxLen = Math.max(leftArr.length, rightArr.length);
1947
+ for (let i = 0; i < maxLen; i++) {
1948
+ const childPath = `${path}[${i}]`;
1949
+ walkNodes(leftArr[i], rightArr[i], childPath, ignorePaths, entries);
1950
+ }
1951
+ return;
1952
+ }
1953
+ if (left === void 0 && right === void 0) {
1954
+ return;
1955
+ }
1956
+ if (left === right) {
1957
+ return;
1958
+ }
1959
+ const kind = classifyChange(left, right);
1960
+ const entry = { kind, path };
1961
+ if (left !== void 0) {
1962
+ entry.left = left;
1963
+ }
1964
+ if (right !== void 0) {
1965
+ entry.right = right;
1966
+ }
1967
+ entries.push(entry);
1968
+ }
1969
+ function diff(left, right, options) {
1970
+ const ignorePaths = options?.ignorePaths ?? [];
1971
+ const entries = [];
1972
+ const allKeys = Array.from(
1973
+ /* @__PURE__ */ new Set([...Object.keys(left), ...Object.keys(right)])
1974
+ ).sort();
1975
+ for (const key of allKeys) {
1976
+ walkNodes(
1977
+ left[key],
1978
+ right[key],
1979
+ key,
1980
+ ignorePaths,
1981
+ entries
1982
+ );
1983
+ }
1984
+ return {
1985
+ resourceType: left.resourceType,
1986
+ identical: entries.length === 0,
1987
+ entries
1988
+ };
1989
+ }
1990
+
1991
+ // src/core/utils/path.ts
1992
+ var PROTOTYPE_KEYS2 = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
1993
+ function getPath(obj, path) {
1994
+ const parts = path.split(".");
1995
+ let current = obj;
1996
+ for (const part of parts) {
1997
+ if (current === null || typeof current !== "object" || Array.isArray(current) || !Object.prototype.hasOwnProperty.call(current, part)) {
1998
+ return void 0;
1999
+ }
2000
+ current = current[part];
2001
+ }
2002
+ return current;
2003
+ }
2004
+ function setPath(obj, path, value) {
2005
+ const parts = path.split(".");
2006
+ let current = obj;
2007
+ for (let i = 0; i < parts.length - 1; i++) {
2008
+ const part = parts[i];
2009
+ if (part === void 0 || PROTOTYPE_KEYS2.has(part) || current === null || typeof current !== "object" || Array.isArray(current) || !Object.prototype.hasOwnProperty.call(current, part)) {
2010
+ return;
2011
+ }
2012
+ current = current[part];
2013
+ }
2014
+ const lastPart = parts[parts.length - 1];
2015
+ if (lastPart !== void 0 && !PROTOTYPE_KEYS2.has(lastPart) && current !== null && typeof current === "object" && !Array.isArray(current)) {
2016
+ current[lastPart] = value;
2017
+ }
2018
+ }
2019
+
2020
+ // src/core/normalize.ts
2021
+ var DATETIME_WITH_TIME_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})$/;
2022
+ var PROTOTYPE_KEYS3 = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
2023
+ function trimStringsDeep(value) {
2024
+ if (typeof value === "string") {
2025
+ return value.trim();
2026
+ }
2027
+ if (Array.isArray(value)) {
2028
+ return value.map(trimStringsDeep);
2029
+ }
2030
+ if (value !== null && typeof value === "object") {
2031
+ const result = {};
2032
+ for (const key of Object.keys(value)) {
2033
+ if (PROTOTYPE_KEYS3.has(key)) continue;
2034
+ result[key] = trimStringsDeep(
2035
+ value[key]
2036
+ );
2037
+ }
2038
+ return result;
2039
+ }
2040
+ return value;
2041
+ }
2042
+ function normalizeDatesDeep(value) {
2043
+ if (typeof value === "string") {
2044
+ if (DATETIME_WITH_TIME_PATTERN.test(value)) {
2045
+ return new Date(value).toISOString();
2046
+ }
2047
+ return value;
2048
+ }
2049
+ if (Array.isArray(value)) {
2050
+ return value.map(normalizeDatesDeep);
2051
+ }
2052
+ if (value !== null && typeof value === "object") {
2053
+ const result = {};
2054
+ for (const key of Object.keys(value)) {
2055
+ if (PROTOTYPE_KEYS3.has(key)) continue;
2056
+ result[key] = normalizeDatesDeep(
2057
+ value[key]
2058
+ );
2059
+ }
2060
+ return result;
2061
+ }
2062
+ return value;
2063
+ }
2064
+ function sortObjectKeysDeep(value) {
2065
+ if (Array.isArray(value)) {
2066
+ return value.map(sortObjectKeysDeep);
2067
+ }
2068
+ if (value !== null && typeof value === "object") {
2069
+ const sorted = {};
2070
+ for (const key of Object.keys(value).sort()) {
2071
+ if (PROTOTYPE_KEYS3.has(key)) continue;
2072
+ sorted[key] = sortObjectKeysDeep(
2073
+ value[key]
2074
+ );
2075
+ }
2076
+ return sorted;
2077
+ }
2078
+ return value;
2079
+ }
2080
+ function sortArrayAtPaths(obj, paths) {
2081
+ for (const path of paths) {
2082
+ const arr = getPath(obj, path);
2083
+ if (Array.isArray(arr)) {
2084
+ const sorted = [...arr].sort((a, b) => {
2085
+ const aStr = JSON.stringify(a);
2086
+ const bStr = JSON.stringify(b);
2087
+ if (aStr < bStr) return -1;
2088
+ if (aStr > bStr) return 1;
2089
+ return 0;
2090
+ });
2091
+ setPath(obj, path, sorted);
2092
+ }
2093
+ }
2094
+ }
2095
+ function normalize(resource, options) {
2096
+ let result = structuredClone(resource);
2097
+ if (options.trimStrings === true) {
2098
+ result = trimStringsDeep(result);
2099
+ }
2100
+ if (options.normalizeDates === true) {
2101
+ result = normalizeDatesDeep(result);
2102
+ }
2103
+ if (options.sortObjectKeys === true) {
2104
+ result = sortObjectKeysDeep(result);
2105
+ }
2106
+ if (options.sortArrayPaths !== void 0 && options.sortArrayPaths.length > 0) {
2107
+ sortArrayAtPaths(result, options.sortArrayPaths);
2108
+ }
2109
+ return result;
2110
+ }
2111
+
2112
+ // src/core/version.ts
2113
+ var TOOL_VERSION = "0.2.0";
2114
+
2115
+ // src/core/summary.ts
2116
+ function summarizeDiff(result) {
2117
+ let added = 0;
2118
+ let removed = 0;
2119
+ let changed = 0;
2120
+ let typeChanged = 0;
2121
+ for (const entry of result.entries) {
2122
+ if (entry.kind === "added") added++;
2123
+ else if (entry.kind === "removed") removed++;
2124
+ else if (entry.kind === "changed") changed++;
2125
+ else if (entry.kind === "type-changed") typeChanged++;
2126
+ }
2127
+ return {
2128
+ added,
2129
+ removed,
2130
+ changed,
2131
+ typeChanged,
2132
+ total: added + removed + changed + typeChanged
2133
+ };
2134
+ }
2135
+
2136
+ // src/core/envelope.ts
2137
+ function buildEnvelope(command, fhirVersion, result) {
2138
+ return {
2139
+ tool: "fhir-resource-diff",
2140
+ version: TOOL_VERSION,
2141
+ command,
2142
+ fhirVersion,
2143
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2144
+ result
2145
+ };
2146
+ }
2147
+
2148
+ // src/formatters/text.ts
2149
+ function formatValue(value) {
2150
+ if (typeof value === "string") {
2151
+ return `"${value}"`;
2152
+ }
2153
+ return JSON.stringify(value);
2154
+ }
2155
+ function formatText(result) {
2156
+ const lines = [];
2157
+ lines.push(`ResourceType: ${result.resourceType}`);
2158
+ if (result.identical) {
2159
+ lines.push("Status: identical");
2160
+ return lines.join("\n");
2161
+ }
2162
+ const count = result.entries.length;
2163
+ lines.push(`Status: ${count} difference(s) found`);
2164
+ const changed = result.entries.filter((e) => e.kind === "changed");
2165
+ const added = result.entries.filter((e) => e.kind === "added");
2166
+ const removed = result.entries.filter((e) => e.kind === "removed");
2167
+ const typeChanged = result.entries.filter((e) => e.kind === "type-changed");
2168
+ if (changed.length > 0) {
2169
+ lines.push("");
2170
+ lines.push("Changed:");
2171
+ for (const entry of changed) {
2172
+ lines.push(
2173
+ ` ${entry.path}: ${formatValue(entry.left)} \u2192 ${formatValue(entry.right)}`
2174
+ );
2175
+ }
2176
+ }
2177
+ if (added.length > 0) {
2178
+ lines.push("");
2179
+ lines.push("Added:");
2180
+ for (const entry of added) {
2181
+ lines.push(` ${entry.path}`);
2182
+ }
2183
+ }
2184
+ if (removed.length > 0) {
2185
+ lines.push("");
2186
+ lines.push("Removed:");
2187
+ for (const entry of removed) {
2188
+ lines.push(` ${entry.path}`);
2189
+ }
2190
+ }
2191
+ if (typeChanged.length > 0) {
2192
+ lines.push("");
2193
+ lines.push("Type-changed:");
2194
+ for (const entry of typeChanged) {
2195
+ lines.push(
2196
+ ` ${entry.path}: ${formatValue(entry.left)} \u2192 ${formatValue(entry.right)}`
2197
+ );
2198
+ }
2199
+ }
2200
+ return lines.join("\n");
2201
+ }
2202
+ var SEVERITY_ICON = {
2203
+ error: "\u2717",
2204
+ warning: "\u26A0",
2205
+ info: "\u2139"
2206
+ };
2207
+ function formatValidationText(result) {
2208
+ const findings = result.valid ? [] : result.errors;
2209
+ const hasErrors = findings.some((e) => e.severity === "error");
2210
+ const hasWarnings = findings.some((e) => e.severity === "warning" || e.severity === "info");
2211
+ const header = hasErrors ? "invalid" : hasWarnings ? "valid (with warnings)" : "valid";
2212
+ const lines = [header];
2213
+ for (const finding of findings) {
2214
+ const icon = SEVERITY_ICON[finding.severity] ?? "\u2717";
2215
+ const pathPrefix = finding.path !== "" ? `${finding.path}: ` : "";
2216
+ lines.push(` ${icon} ${pathPrefix}${finding.message}`);
2217
+ if (finding.docUrl !== void 0) {
2218
+ lines.push(` \u2192 ${finding.docUrl}`);
2219
+ }
2220
+ }
2221
+ if (result.hint !== void 0) {
2222
+ if (findings.length > 0) lines.push("");
2223
+ lines.push(` \u2139 ${result.hint.message}`);
2224
+ lines.push(` \u2192 ${result.hint.docUrl}`);
2225
+ }
2226
+ return lines.join("\n");
2227
+ }
2228
+
2229
+ // src/formatters/json.ts
2230
+ function formatJson(result) {
2231
+ return JSON.stringify(result, null, 2);
2232
+ }
2233
+ function formatValidationJson(result) {
2234
+ const output = { valid: result.valid };
2235
+ if (!result.valid) {
2236
+ output["errors"] = result.errors;
2237
+ }
2238
+ if (result.hint !== void 0) {
2239
+ output["hint"] = result.hint;
2240
+ }
2241
+ return JSON.stringify(output, null, 2);
2242
+ }
2243
+
2244
+ // src/formatters/markdown.ts
2245
+ function formatCellValue(value) {
2246
+ if (typeof value === "string") {
2247
+ return `\`"${value}"\``;
2248
+ }
2249
+ return `\`${JSON.stringify(value)}\``;
2250
+ }
2251
+ function formatMarkdown(result) {
2252
+ const lines = [];
2253
+ lines.push(`## Diff: ${result.resourceType}`);
2254
+ lines.push("");
2255
+ if (result.identical) {
2256
+ lines.push("**Status:** identical");
2257
+ lines.push("");
2258
+ lines.push("| Kind | Path | Left | Right |");
2259
+ lines.push("|------|------|------|-------|");
2260
+ return lines.join("\n");
2261
+ }
2262
+ const count = result.entries.length;
2263
+ lines.push(`**Status:** ${count} difference(s) found`);
2264
+ lines.push("");
2265
+ lines.push("| Kind | Path | Left | Right |");
2266
+ lines.push("|------|------|------|-------|");
2267
+ for (const entry of result.entries) {
2268
+ const path = `\`${entry.path}\``;
2269
+ let left = "";
2270
+ let right = "";
2271
+ if (entry.kind === "changed" || entry.kind === "type-changed") {
2272
+ left = formatCellValue(entry.left);
2273
+ right = formatCellValue(entry.right);
2274
+ }
2275
+ lines.push(`| ${entry.kind} | ${path} | ${left} | ${right} |`);
2276
+ }
2277
+ return lines.join("\n");
2278
+ }
2279
+
2280
+ export {
2281
+ SUPPORTED_FHIR_VERSIONS,
2282
+ DEFAULT_FHIR_VERSION,
2283
+ VERSION_STRING_MAP,
2284
+ detectFhirVersion,
2285
+ resolveFhirVersion,
2286
+ fhirVersionLabel,
2287
+ fhirBaseUrl,
2288
+ isSupportedFhirVersion,
2289
+ RESOURCE_REGISTRY,
2290
+ getResourceInfo,
2291
+ getResourceDocUrl,
2292
+ isKnownResourceType,
2293
+ listResourceTypes,
2294
+ isFhirResource,
2295
+ parseJson,
2296
+ walkResource,
2297
+ KNOWN_PROFILES,
2298
+ PROFILE_NAMESPACES,
2299
+ isValidCanonicalUrl,
2300
+ lookupProfile,
2301
+ lookupProfileNamespace,
2302
+ FORMAT_RULES,
2303
+ PROFILE_RULES,
2304
+ runRules,
2305
+ validate,
2306
+ classifyChange,
2307
+ diff,
2308
+ normalize,
2309
+ TOOL_VERSION,
2310
+ summarizeDiff,
2311
+ buildEnvelope,
2312
+ formatText,
2313
+ formatValidationText,
2314
+ formatJson,
2315
+ formatValidationJson,
2316
+ formatMarkdown
2317
+ };
2318
+ //# sourceMappingURL=chunk-2UUKQJDB.js.map