@metriport/fhir-sdk 1.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +124 -0
- package/dist/__tests__/env-setup.d.ts +2 -0
- package/dist/__tests__/env-setup.d.ts.map +1 -0
- package/dist/__tests__/env-setup.js +37 -0
- package/dist/__tests__/env-setup.js.map +1 -0
- package/dist/__tests__/fhir-bundle-sdk-basic.test.d.ts +2 -0
- package/dist/__tests__/fhir-bundle-sdk-basic.test.d.ts.map +1 -0
- package/dist/__tests__/fhir-bundle-sdk-basic.test.js +19 -0
- package/dist/__tests__/fhir-bundle-sdk-basic.test.js.map +1 -0
- package/dist/__tests__/fhir-bundle-sdk.test.d.ts +2 -0
- package/dist/__tests__/fhir-bundle-sdk.test.d.ts.map +1 -0
- package/dist/__tests__/fhir-bundle-sdk.test.js +433 -0
- package/dist/__tests__/fhir-bundle-sdk.test.js.map +1 -0
- package/dist/__tests__/fixtures/fhir-bundles.d.ts +31 -0
- package/dist/__tests__/fixtures/fhir-bundles.d.ts.map +1 -0
- package/dist/__tests__/fixtures/fhir-bundles.js +369 -0
- package/dist/__tests__/fixtures/fhir-bundles.js.map +1 -0
- package/dist/__tests__/phase1-verification.test.d.ts +2 -0
- package/dist/__tests__/phase1-verification.test.d.ts.map +1 -0
- package/dist/__tests__/phase1-verification.test.js +141 -0
- package/dist/__tests__/phase1-verification.test.js.map +1 -0
- package/dist/__tests__/phase2-verification.test.d.ts +2 -0
- package/dist/__tests__/phase2-verification.test.d.ts.map +1 -0
- package/dist/__tests__/phase2-verification.test.js +234 -0
- package/dist/__tests__/phase2-verification.test.js.map +1 -0
- package/dist/__tests__/phase3-verification.test.d.ts +2 -0
- package/dist/__tests__/phase3-verification.test.d.ts.map +1 -0
- package/dist/__tests__/phase3-verification.test.js +121 -0
- package/dist/__tests__/phase3-verification.test.js.map +1 -0
- package/dist/__tests__/phase4-verification.test.d.ts +2 -0
- package/dist/__tests__/phase4-verification.test.d.ts.map +1 -0
- package/dist/__tests__/phase4-verification.test.js +168 -0
- package/dist/__tests__/phase4-verification.test.js.map +1 -0
- package/dist/__tests__/phase5-verification.test.d.ts +2 -0
- package/dist/__tests__/phase5-verification.test.d.ts.map +1 -0
- package/dist/__tests__/phase5-verification.test.js +200 -0
- package/dist/__tests__/phase5-verification.test.js.map +1 -0
- package/dist/index.d.ts +245 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +681 -0
- package/dist/index.js.map +1 -0
- package/dist/types/smart-resources.d.ts +219 -0
- package/dist/types/smart-resources.d.ts.map +1 -0
- package/dist/types/smart-resources.js +140 -0
- package/dist/types/smart-resources.js.map +1 -0
- package/package.json +69 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,681 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FhirBundleSdk = void 0;
|
|
4
|
+
const smart_resources_1 = require("./types/smart-resources");
|
|
5
|
+
function getResourceIdentifier(entry) {
|
|
6
|
+
if (entry.resource?.id) {
|
|
7
|
+
return entry.resource.id;
|
|
8
|
+
}
|
|
9
|
+
if (entry.fullUrl) {
|
|
10
|
+
return entry.fullUrl;
|
|
11
|
+
}
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* FHIR Bundle SDK for parsing, querying, and manipulating FHIR bundles with reference resolution
|
|
16
|
+
*/
|
|
17
|
+
class FhirBundleSdk {
|
|
18
|
+
constructor(bundle) {
|
|
19
|
+
this.resourcesById = new Map();
|
|
20
|
+
this.resourcesByFullUrl = new Map();
|
|
21
|
+
this.resourcesByType = new Map();
|
|
22
|
+
// Smart resource caching to maintain object identity
|
|
23
|
+
this.smartResourceCache = new WeakMap();
|
|
24
|
+
// Array caching for type-specific getters to maintain reference identity
|
|
25
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
26
|
+
this.smartResourceArrayCache = new Map();
|
|
27
|
+
// Circular reference protection
|
|
28
|
+
this.resolutionStack = new Set();
|
|
29
|
+
// FR-1.1, FR-1.4: Initialize bundle and create indexes
|
|
30
|
+
this.bundle = bundle;
|
|
31
|
+
this.bundle.total = bundle.entry?.length ?? 0;
|
|
32
|
+
this.buildResourceIndexes();
|
|
33
|
+
}
|
|
34
|
+
get total() {
|
|
35
|
+
if (!this.bundle.entry) {
|
|
36
|
+
throw new Error("No valid total - bundle property `entry` is undefined");
|
|
37
|
+
}
|
|
38
|
+
return this.bundle.entry.length;
|
|
39
|
+
}
|
|
40
|
+
get entry() {
|
|
41
|
+
if (!this.bundle.entry) {
|
|
42
|
+
console.error("Bundle property `entry` is undefined");
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
return this.bundle.entry;
|
|
46
|
+
}
|
|
47
|
+
toObject() {
|
|
48
|
+
return this.bundle;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Create a new FhirBundleSdk instance (async for backwards compatibility)
|
|
52
|
+
* FR-1.2: Validate bundle resourceType
|
|
53
|
+
*/
|
|
54
|
+
static async create(bundle) {
|
|
55
|
+
return FhirBundleSdk.createSync(bundle);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Create a new FhirBundleSdk instance synchronously
|
|
59
|
+
* FR-1.2: Validate bundle resourceType
|
|
60
|
+
*/
|
|
61
|
+
static createSync(bundle) {
|
|
62
|
+
// FR-1.2: Validate bundle resourceType
|
|
63
|
+
if (bundle.resourceType !== "Bundle") {
|
|
64
|
+
throw new Error("Invalid bundle: resourceType must be 'Bundle'");
|
|
65
|
+
}
|
|
66
|
+
return new FhirBundleSdk(bundle);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Build O(1) indexes for resource lookup
|
|
70
|
+
*/
|
|
71
|
+
buildResourceIndexes() {
|
|
72
|
+
if (!this.bundle.entry) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
for (const entry of this.bundle.entry) {
|
|
76
|
+
if (!entry.resource) {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
const resource = entry.resource;
|
|
80
|
+
// Index by resource.id if it exists
|
|
81
|
+
if (resource.id) {
|
|
82
|
+
this.resourcesById.set(resource.id, resource);
|
|
83
|
+
}
|
|
84
|
+
// Index by fullUrl if it exists
|
|
85
|
+
if (entry.fullUrl) {
|
|
86
|
+
this.resourcesByFullUrl.set(entry.fullUrl, resource);
|
|
87
|
+
}
|
|
88
|
+
// Index by resource type for type-specific getters
|
|
89
|
+
const resourceType = resource.resourceType;
|
|
90
|
+
if (!this.resourcesByType.has(resourceType)) {
|
|
91
|
+
this.resourcesByType.set(resourceType, []);
|
|
92
|
+
}
|
|
93
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
94
|
+
this.resourcesByType.get(resourceType).push(resource);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Create a smart resource with reference resolution methods
|
|
99
|
+
* FR-5.1: Resources returned by SDK have additional getter methods for each Reference field
|
|
100
|
+
* FR-5.7: Reference resolution operates in O(1) time complexity per reference
|
|
101
|
+
* FR-5.8: Original reference fields remain unchanged
|
|
102
|
+
*/
|
|
103
|
+
createSmartResource(resource) {
|
|
104
|
+
// Check cache first to maintain object identity
|
|
105
|
+
const cached = this.smartResourceCache.get(resource);
|
|
106
|
+
if (cached) {
|
|
107
|
+
return cached;
|
|
108
|
+
}
|
|
109
|
+
const smartResource = new Proxy(resource, {
|
|
110
|
+
get: (target, prop, receiver) => {
|
|
111
|
+
// Handle the smart resource marker
|
|
112
|
+
if (prop === "__isSmartResource") {
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
// Check if this is a reference method call
|
|
116
|
+
if (typeof prop === "string" && (0, smart_resources_1.isReferenceMethod)(prop, target.resourceType)) {
|
|
117
|
+
return () => this.resolveReference(prop, target);
|
|
118
|
+
}
|
|
119
|
+
// Return original property
|
|
120
|
+
return Reflect.get(target, prop, receiver);
|
|
121
|
+
},
|
|
122
|
+
// Ensure JSON serialization works correctly (FR-5.8)
|
|
123
|
+
ownKeys: target => {
|
|
124
|
+
return Reflect.ownKeys(target).filter(key => key !== "__isSmartResource");
|
|
125
|
+
},
|
|
126
|
+
getOwnPropertyDescriptor: (target, prop) => {
|
|
127
|
+
if (prop === "__isSmartResource") {
|
|
128
|
+
return undefined;
|
|
129
|
+
}
|
|
130
|
+
return Reflect.getOwnPropertyDescriptor(target, prop);
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
// Cache the smart resource
|
|
134
|
+
this.smartResourceCache.set(resource, smartResource);
|
|
135
|
+
return smartResource;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Resolve a reference method call to actual resources
|
|
139
|
+
* FR-5.2-5.6: Handle different reference types and patterns
|
|
140
|
+
*/
|
|
141
|
+
resolveReference(methodName, resource) {
|
|
142
|
+
const referenceField = (0, smart_resources_1.getReferenceField)(methodName, resource.resourceType);
|
|
143
|
+
if (!referenceField) {
|
|
144
|
+
return undefined;
|
|
145
|
+
}
|
|
146
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
147
|
+
const referenceValue = resource[referenceField];
|
|
148
|
+
if (!referenceValue) {
|
|
149
|
+
// FR-5.6: Return appropriate empty value for missing references
|
|
150
|
+
return this.isArrayReferenceField(referenceField, resource.resourceType) ? [] : undefined;
|
|
151
|
+
}
|
|
152
|
+
// Handle array references
|
|
153
|
+
if (Array.isArray(referenceValue)) {
|
|
154
|
+
const resolvedResources = [];
|
|
155
|
+
for (const ref of referenceValue) {
|
|
156
|
+
const resolved = this.resolveReferenceObject(ref);
|
|
157
|
+
if (resolved) {
|
|
158
|
+
resolvedResources.push(this.createSmartResource(resolved));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return resolvedResources;
|
|
162
|
+
}
|
|
163
|
+
// Handle single reference - we know it's not an array at this point
|
|
164
|
+
const resolved = this.resolveReferenceObject(referenceValue);
|
|
165
|
+
if (resolved) {
|
|
166
|
+
// Type assertion is safe here because we've established this is the single reference path
|
|
167
|
+
return this.createSmartResource(resolved);
|
|
168
|
+
}
|
|
169
|
+
return undefined;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Resolve a single reference object to a resource
|
|
173
|
+
* FR-5.5: Reference resolution methods handle both resource.id and fullUrl matching
|
|
174
|
+
*/
|
|
175
|
+
resolveReferenceObject(referenceObj) {
|
|
176
|
+
if (!referenceObj || typeof referenceObj !== "object" || !("reference" in referenceObj)) {
|
|
177
|
+
return undefined;
|
|
178
|
+
}
|
|
179
|
+
const reference = referenceObj.reference;
|
|
180
|
+
// Circular reference protection
|
|
181
|
+
if (this.resolutionStack.has(reference)) {
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
184
|
+
this.resolutionStack.add(reference);
|
|
185
|
+
try {
|
|
186
|
+
// Try to resolve by resource ID (e.g., "Patient/123")
|
|
187
|
+
if (reference.includes("/")) {
|
|
188
|
+
const [, resourceId] = reference.split("/");
|
|
189
|
+
if (resourceId && this.resourcesById.has(resourceId)) {
|
|
190
|
+
return this.resourcesById.get(resourceId);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
// Try to resolve by fullUrl (e.g., "urn:uuid:123")
|
|
194
|
+
if (this.resourcesByFullUrl.has(reference)) {
|
|
195
|
+
return this.resourcesByFullUrl.get(reference);
|
|
196
|
+
}
|
|
197
|
+
return undefined;
|
|
198
|
+
}
|
|
199
|
+
finally {
|
|
200
|
+
this.resolutionStack.delete(reference);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Check if a reference field expects an array of references
|
|
205
|
+
*/
|
|
206
|
+
isArrayReferenceField(fieldName, resourceType) {
|
|
207
|
+
// Known array reference fields
|
|
208
|
+
const arrayFields = new Set(["performer", "participant", "result", "generalPractitioner"]);
|
|
209
|
+
return arrayFields.has(fieldName);
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Find all Reference fields in a resource recursively
|
|
213
|
+
*/
|
|
214
|
+
findAllReferences(resource) {
|
|
215
|
+
const references = [];
|
|
216
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
217
|
+
function searchObject(obj, path = "") {
|
|
218
|
+
if (!obj || typeof obj !== "object") {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
// Check if this object is a Reference
|
|
222
|
+
if (obj.reference && typeof obj.reference === "string") {
|
|
223
|
+
references.push({
|
|
224
|
+
field: path || "reference",
|
|
225
|
+
reference: obj.reference,
|
|
226
|
+
});
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
// Recursively search object properties
|
|
230
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
231
|
+
const currentPath = path ? `${path}.${key}` : key;
|
|
232
|
+
if (Array.isArray(value)) {
|
|
233
|
+
value.forEach((item, index) => {
|
|
234
|
+
searchObject(item, `${currentPath}[${index}]`);
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
else if (value && typeof value === "object") {
|
|
238
|
+
searchObject(value, currentPath);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
searchObject(resource);
|
|
243
|
+
return references;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Check if a reference can be resolved within the bundle
|
|
247
|
+
*/
|
|
248
|
+
canResolveReference(reference) {
|
|
249
|
+
// Try to resolve by resource ID (e.g., "Patient/123")
|
|
250
|
+
if (reference.includes("/")) {
|
|
251
|
+
const [, resourceId] = reference.split("/");
|
|
252
|
+
if (resourceId && this.resourcesById.has(resourceId)) {
|
|
253
|
+
return true;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
// Try to resolve by fullUrl (e.g., "urn:uuid:123")
|
|
257
|
+
if (this.resourcesByFullUrl.has(reference)) {
|
|
258
|
+
return true;
|
|
259
|
+
}
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* FR-2.1: Validate all references in the bundle
|
|
264
|
+
* FR-2.2: Identifies references by Resource/id pattern and fullUrl references
|
|
265
|
+
* FR-2.3: Handles both relative and absolute references
|
|
266
|
+
* FR-2.4: Returns validation result with broken reference details
|
|
267
|
+
*/
|
|
268
|
+
lookForBrokenReferences() {
|
|
269
|
+
const brokenReferences = [];
|
|
270
|
+
if (!this.bundle.entry) {
|
|
271
|
+
return { hasBrokenReferences: false, brokenReferences: [] };
|
|
272
|
+
}
|
|
273
|
+
for (const entry of this.bundle.entry) {
|
|
274
|
+
if (!entry.resource) {
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
const resource = entry.resource;
|
|
278
|
+
const resourceReferences = this.findAllReferences(resource);
|
|
279
|
+
for (const { field, reference } of resourceReferences) {
|
|
280
|
+
if (!this.canResolveReference(reference)) {
|
|
281
|
+
brokenReferences.push({
|
|
282
|
+
sourceResourceId: resource.id || entry.fullUrl || "unknown",
|
|
283
|
+
sourceResourceType: resource.resourceType,
|
|
284
|
+
referenceField: field,
|
|
285
|
+
reference: reference,
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return {
|
|
291
|
+
hasBrokenReferences: brokenReferences.length > 0,
|
|
292
|
+
brokenReferences: brokenReferences,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* FR-3.1: Get resource by ID with type parameter support
|
|
297
|
+
* FR-3.2: Method searches both resource.id and entry.fullUrl for matches
|
|
298
|
+
* FR-3.4: Method returns undefined if resource not found
|
|
299
|
+
* FR-3.5: Lookup operates in O(1) time complexity
|
|
300
|
+
* FR-5.1: Returns smart resource with reference resolution methods
|
|
301
|
+
*/
|
|
302
|
+
getResourceById(id) {
|
|
303
|
+
// First try to find by resource.id
|
|
304
|
+
const resourceById = this.resourcesById.get(id);
|
|
305
|
+
if (resourceById) {
|
|
306
|
+
return this.createSmartResource(resourceById);
|
|
307
|
+
}
|
|
308
|
+
// Then try to find by fullUrl
|
|
309
|
+
const resourceByFullUrl = this.resourcesByFullUrl.get(id);
|
|
310
|
+
if (resourceByFullUrl) {
|
|
311
|
+
return this.createSmartResource(resourceByFullUrl);
|
|
312
|
+
}
|
|
313
|
+
// Return undefined if not found (FR-3.4)
|
|
314
|
+
return undefined;
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Generic helper method to get a resource by ID with type validation
|
|
318
|
+
*/
|
|
319
|
+
getResourceByIdAndType(id, resourceType) {
|
|
320
|
+
const resource = this.getResourceById(id);
|
|
321
|
+
if (resource && resource.resourceType === resourceType) {
|
|
322
|
+
return resource;
|
|
323
|
+
}
|
|
324
|
+
return undefined;
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Generic helper method to get all resources of a specific type
|
|
328
|
+
* FR-10.1: Returns references to cached objects, not copies
|
|
329
|
+
*/
|
|
330
|
+
getResourcesByType(resourceType) {
|
|
331
|
+
// Check cache first to maintain array reference identity
|
|
332
|
+
const cached = this.smartResourceArrayCache.get(resourceType);
|
|
333
|
+
if (cached) {
|
|
334
|
+
return cached;
|
|
335
|
+
}
|
|
336
|
+
const resources = (this.resourcesByType.get(resourceType) || []);
|
|
337
|
+
const smartResources = resources.map(resource => this.createSmartResource(resource));
|
|
338
|
+
// Cache the array to maintain reference identity
|
|
339
|
+
this.smartResourceArrayCache.set(resourceType, smartResources);
|
|
340
|
+
return smartResources;
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Create a new bundle entry from an existing entry, preserving fullUrl
|
|
344
|
+
*/
|
|
345
|
+
createBundleEntry(originalEntry, resource) {
|
|
346
|
+
if (originalEntry.fullUrl) {
|
|
347
|
+
return {
|
|
348
|
+
fullUrl: originalEntry.fullUrl,
|
|
349
|
+
resource: resource,
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
return {
|
|
353
|
+
resource: resource,
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Create a new bundle with specified entries, maintaining original metadata
|
|
358
|
+
*/
|
|
359
|
+
createExportBundle(entries) {
|
|
360
|
+
const exportBundle = {
|
|
361
|
+
resourceType: "Bundle",
|
|
362
|
+
type: this.bundle.type || "collection",
|
|
363
|
+
total: entries.length,
|
|
364
|
+
entry: entries,
|
|
365
|
+
};
|
|
366
|
+
// Preserve original bundle metadata (FR-6.4)
|
|
367
|
+
if (this.bundle.id) {
|
|
368
|
+
exportBundle.id = this.bundle.id;
|
|
369
|
+
}
|
|
370
|
+
if (this.bundle.meta) {
|
|
371
|
+
exportBundle.meta = { ...this.bundle.meta };
|
|
372
|
+
}
|
|
373
|
+
if (this.bundle.identifier) {
|
|
374
|
+
exportBundle.identifier = this.bundle.identifier;
|
|
375
|
+
}
|
|
376
|
+
if (this.bundle.timestamp) {
|
|
377
|
+
exportBundle.timestamp = this.bundle.timestamp;
|
|
378
|
+
}
|
|
379
|
+
return exportBundle;
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Find original bundle entry for a given resource
|
|
383
|
+
*/
|
|
384
|
+
findOriginalEntry(resource) {
|
|
385
|
+
if (!this.bundle.entry) {
|
|
386
|
+
return undefined;
|
|
387
|
+
}
|
|
388
|
+
return this.bundle.entry.find(entry => entry.resource === resource || (entry.resource?.id && entry.resource.id === resource.id));
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* FR-6.1: Export subset of resources by their IDs
|
|
392
|
+
* FR-6.4: Exported bundles maintain original bundle metadata but update total count
|
|
393
|
+
* FR-6.5: Exported bundles include only resources that exist in the original bundle
|
|
394
|
+
* FR-6.6: Exported bundles preserve original entry.fullUrl values
|
|
395
|
+
*/
|
|
396
|
+
exportSubset(resourceIds) {
|
|
397
|
+
const exportEntries = [];
|
|
398
|
+
for (const resourceId of resourceIds) {
|
|
399
|
+
const resource = this.getResourceById(resourceId);
|
|
400
|
+
if (resource) {
|
|
401
|
+
const originalEntry = this.findOriginalEntry(resource);
|
|
402
|
+
if (originalEntry) {
|
|
403
|
+
exportEntries.push(this.createBundleEntry(originalEntry, resource));
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
// FR-6.5: Silently skip resources that don't exist
|
|
407
|
+
}
|
|
408
|
+
return this.createExportBundle(exportEntries);
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* FR-6.2: Export all resources of a specific type
|
|
412
|
+
* FR-6.4: Exported bundles maintain original bundle metadata but update total count
|
|
413
|
+
* FR-6.6: Exported bundles preserve original entry.fullUrl values
|
|
414
|
+
*/
|
|
415
|
+
exportByType(resourceType) {
|
|
416
|
+
const resources = this.resourcesByType.get(resourceType) || [];
|
|
417
|
+
const exportEntries = [];
|
|
418
|
+
for (const resource of resources) {
|
|
419
|
+
const originalEntry = this.findOriginalEntry(resource);
|
|
420
|
+
if (originalEntry) {
|
|
421
|
+
exportEntries.push(this.createBundleEntry(originalEntry, resource));
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
return this.createExportBundle(exportEntries);
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* FR-6.3: Export all resources of specified types
|
|
428
|
+
* FR-6.4: Exported bundles maintain original bundle metadata but update total count
|
|
429
|
+
* FR-6.6: Exported bundles preserve original entry.fullUrl values
|
|
430
|
+
*/
|
|
431
|
+
exportByTypes(resourceTypes) {
|
|
432
|
+
const exportEntries = [];
|
|
433
|
+
for (const resourceType of resourceTypes) {
|
|
434
|
+
const resources = this.resourcesByType.get(resourceType) || [];
|
|
435
|
+
for (const resource of resources) {
|
|
436
|
+
const originalEntry = this.findOriginalEntry(resource);
|
|
437
|
+
if (originalEntry) {
|
|
438
|
+
exportEntries.push(this.createBundleEntry(originalEntry, resource));
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
return this.createExportBundle(exportEntries);
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Concatenate entries from another FhirBundleSdk with this bundle
|
|
446
|
+
* Returns a new bundle with combined entries while preserving original metadata
|
|
447
|
+
*/
|
|
448
|
+
async concatEntries(otherSdk) {
|
|
449
|
+
const currentEntries = this.bundle.entry || [];
|
|
450
|
+
const otherEntries = otherSdk.bundle.entry || [];
|
|
451
|
+
const combinedEntries = [...currentEntries, ...otherEntries];
|
|
452
|
+
const resultBundle = this.createExportBundle(combinedEntries);
|
|
453
|
+
return await FhirBundleSdk.create(resultBundle);
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Diff this bundle with another bundle or FhirBundleSdk by comparing resource ids.
|
|
457
|
+
* Returns three FhirBundleSdk instances: common, baseOnly, parameterOnly.
|
|
458
|
+
*/
|
|
459
|
+
async diff(other) {
|
|
460
|
+
const baseBundle = this.bundle;
|
|
461
|
+
const parameterBundle = other instanceof FhirBundleSdk ? other.bundle : other;
|
|
462
|
+
const commonEntries = [];
|
|
463
|
+
const baseOnlyEntries = [];
|
|
464
|
+
const parameterOnlyEntries = [];
|
|
465
|
+
// Create maps with resource identifiers (prefer resource.id, fallback to fullUrl)
|
|
466
|
+
const baseResourceIdentifiers = new Map();
|
|
467
|
+
const parameterResourceIdentifiers = new Map();
|
|
468
|
+
// Populate base bundle identifiers
|
|
469
|
+
for (const entry of baseBundle?.entry ?? []) {
|
|
470
|
+
const identifier = getResourceIdentifier(entry);
|
|
471
|
+
if (identifier) {
|
|
472
|
+
baseResourceIdentifiers.set(identifier, entry);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
// Populate parameter bundle identifiers
|
|
476
|
+
for (const entry of parameterBundle?.entry ?? []) {
|
|
477
|
+
const identifier = getResourceIdentifier(entry);
|
|
478
|
+
if (identifier) {
|
|
479
|
+
parameterResourceIdentifiers.set(identifier, entry);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
// Find common and base-only resources
|
|
483
|
+
for (const [identifier, entry] of baseResourceIdentifiers.entries()) {
|
|
484
|
+
if (parameterResourceIdentifiers.has(identifier)) {
|
|
485
|
+
commonEntries.push(entry);
|
|
486
|
+
}
|
|
487
|
+
else {
|
|
488
|
+
baseOnlyEntries.push(entry);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
// Find parameter-only resources
|
|
492
|
+
for (const [identifier, entry] of parameterResourceIdentifiers.entries()) {
|
|
493
|
+
if (!baseResourceIdentifiers.has(identifier)) {
|
|
494
|
+
parameterOnlyEntries.push(entry);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
return {
|
|
498
|
+
common: await FhirBundleSdk.create(this.createExportBundle(commonEntries)),
|
|
499
|
+
baseOnly: await FhirBundleSdk.create(this.createExportBundle(baseOnlyEntries)),
|
|
500
|
+
parameterOnly: await FhirBundleSdk.create(this.createExportBundle(parameterOnlyEntries)),
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
exports.FhirBundleSdk = FhirBundleSdk;
|
|
505
|
+
/**
|
|
506
|
+
* Configuration for dynamically generated resource getter methods.
|
|
507
|
+
*
|
|
508
|
+
* Each entry in this array automatically generates both single and collection getter methods.
|
|
509
|
+
*
|
|
510
|
+
* **Example:**
|
|
511
|
+
* ```typescript
|
|
512
|
+
* {
|
|
513
|
+
* resourceType: 'Patient',
|
|
514
|
+
* singleGetterMethodName: 'getPatientById',
|
|
515
|
+
* collectionGetterMethodName: 'getPatients'
|
|
516
|
+
* }
|
|
517
|
+
* ```
|
|
518
|
+
*
|
|
519
|
+
* **Generates the equivalent of:**
|
|
520
|
+
* ```typescript
|
|
521
|
+
* // Single resource getter
|
|
522
|
+
* getPatientById(id: string): Smart<Patient> | undefined {
|
|
523
|
+
* return this.getResourceByIdAndType<Patient>(id, 'Patient');
|
|
524
|
+
* }
|
|
525
|
+
*
|
|
526
|
+
* // Collection getter
|
|
527
|
+
* getPatients(): Smart<Patient>[] {
|
|
528
|
+
* return this.getResourcesByType<Patient>('Patient');
|
|
529
|
+
* }
|
|
530
|
+
* ```
|
|
531
|
+
*
|
|
532
|
+
* **Usage:**
|
|
533
|
+
* ```typescript
|
|
534
|
+
* const sdk = await FhirBundleSdk.create(bundle);
|
|
535
|
+
* const patient = sdk.getPatientById('patient-123'); // Smart<Patient> | undefined
|
|
536
|
+
* const allPatients = sdk.getPatients(); // Smart<Patient>[]
|
|
537
|
+
* ```
|
|
538
|
+
*
|
|
539
|
+
* To add a new resource type, simply add a new entry to this array and declare
|
|
540
|
+
* the corresponding method signatures in the class body.
|
|
541
|
+
*/
|
|
542
|
+
FhirBundleSdk.RESOURCE_METHODS = [
|
|
543
|
+
{
|
|
544
|
+
resourceType: "Patient",
|
|
545
|
+
singleGetterMethodName: "getPatientById",
|
|
546
|
+
collectionGetterMethodName: "getPatients",
|
|
547
|
+
},
|
|
548
|
+
{
|
|
549
|
+
resourceType: "Observation",
|
|
550
|
+
singleGetterMethodName: "getObservationById",
|
|
551
|
+
collectionGetterMethodName: "getObservations",
|
|
552
|
+
},
|
|
553
|
+
{
|
|
554
|
+
resourceType: "Encounter",
|
|
555
|
+
singleGetterMethodName: "getEncounterById",
|
|
556
|
+
collectionGetterMethodName: "getEncounters",
|
|
557
|
+
},
|
|
558
|
+
{
|
|
559
|
+
resourceType: "AllergyIntolerance",
|
|
560
|
+
singleGetterMethodName: "getAllergyIntoleranceById",
|
|
561
|
+
collectionGetterMethodName: "getAllergyIntolerances",
|
|
562
|
+
},
|
|
563
|
+
{
|
|
564
|
+
resourceType: "Condition",
|
|
565
|
+
singleGetterMethodName: "getConditionById",
|
|
566
|
+
collectionGetterMethodName: "getConditions",
|
|
567
|
+
},
|
|
568
|
+
{
|
|
569
|
+
resourceType: "Organization",
|
|
570
|
+
singleGetterMethodName: "getOrganizationById",
|
|
571
|
+
collectionGetterMethodName: "getOrganizations",
|
|
572
|
+
},
|
|
573
|
+
{
|
|
574
|
+
resourceType: "Location",
|
|
575
|
+
singleGetterMethodName: "getLocationById",
|
|
576
|
+
collectionGetterMethodName: "getLocations",
|
|
577
|
+
},
|
|
578
|
+
{
|
|
579
|
+
resourceType: "Practitioner",
|
|
580
|
+
singleGetterMethodName: "getPractitionerById",
|
|
581
|
+
collectionGetterMethodName: "getPractitioners",
|
|
582
|
+
},
|
|
583
|
+
{
|
|
584
|
+
resourceType: "DiagnosticReport",
|
|
585
|
+
singleGetterMethodName: "getDiagnosticReportById",
|
|
586
|
+
collectionGetterMethodName: "getDiagnosticReports",
|
|
587
|
+
},
|
|
588
|
+
{
|
|
589
|
+
resourceType: "Composition",
|
|
590
|
+
singleGetterMethodName: "getCompositionById",
|
|
591
|
+
collectionGetterMethodName: "getCompositions",
|
|
592
|
+
},
|
|
593
|
+
{
|
|
594
|
+
resourceType: "Coverage",
|
|
595
|
+
singleGetterMethodName: "getCoverageById",
|
|
596
|
+
collectionGetterMethodName: "getCoverages",
|
|
597
|
+
},
|
|
598
|
+
{
|
|
599
|
+
resourceType: "DocumentReference",
|
|
600
|
+
singleGetterMethodName: "getDocumentReferenceById",
|
|
601
|
+
collectionGetterMethodName: "getDocumentReferences",
|
|
602
|
+
},
|
|
603
|
+
{
|
|
604
|
+
resourceType: "Immunization",
|
|
605
|
+
singleGetterMethodName: "getImmunizationById",
|
|
606
|
+
collectionGetterMethodName: "getImmunizations",
|
|
607
|
+
},
|
|
608
|
+
{
|
|
609
|
+
resourceType: "Medication",
|
|
610
|
+
singleGetterMethodName: "getMedicationById",
|
|
611
|
+
collectionGetterMethodName: "getMedications",
|
|
612
|
+
},
|
|
613
|
+
{
|
|
614
|
+
resourceType: "MedicationRequest",
|
|
615
|
+
singleGetterMethodName: "getMedicationRequestById",
|
|
616
|
+
collectionGetterMethodName: "getMedicationRequests",
|
|
617
|
+
},
|
|
618
|
+
{
|
|
619
|
+
resourceType: "Procedure",
|
|
620
|
+
singleGetterMethodName: "getProcedureById",
|
|
621
|
+
collectionGetterMethodName: "getProcedures",
|
|
622
|
+
},
|
|
623
|
+
{
|
|
624
|
+
resourceType: "FamilyMemberHistory",
|
|
625
|
+
singleGetterMethodName: "getFamilyMemberHistoryById",
|
|
626
|
+
collectionGetterMethodName: "getFamilyMemberHistories",
|
|
627
|
+
},
|
|
628
|
+
{
|
|
629
|
+
resourceType: "MedicationAdministration",
|
|
630
|
+
singleGetterMethodName: "getMedicationAdministrationById",
|
|
631
|
+
collectionGetterMethodName: "getMedicationAdministrations",
|
|
632
|
+
},
|
|
633
|
+
{
|
|
634
|
+
resourceType: "MedicationDispense",
|
|
635
|
+
singleGetterMethodName: "getMedicationDispenseById",
|
|
636
|
+
collectionGetterMethodName: "getMedicationDispenses",
|
|
637
|
+
},
|
|
638
|
+
{
|
|
639
|
+
resourceType: "MedicationStatement",
|
|
640
|
+
singleGetterMethodName: "getMedicationStatementById",
|
|
641
|
+
collectionGetterMethodName: "getMedicationStatements",
|
|
642
|
+
},
|
|
643
|
+
{
|
|
644
|
+
resourceType: "RelatedPerson",
|
|
645
|
+
singleGetterMethodName: "getRelatedPersonById",
|
|
646
|
+
collectionGetterMethodName: "getRelatedPersons",
|
|
647
|
+
},
|
|
648
|
+
{
|
|
649
|
+
resourceType: "RiskAssessment",
|
|
650
|
+
singleGetterMethodName: "getRiskAssessmentById",
|
|
651
|
+
collectionGetterMethodName: "getRiskAssessments",
|
|
652
|
+
},
|
|
653
|
+
{
|
|
654
|
+
resourceType: "ServiceRequest",
|
|
655
|
+
singleGetterMethodName: "getServiceRequestById",
|
|
656
|
+
collectionGetterMethodName: "getServiceRequests",
|
|
657
|
+
},
|
|
658
|
+
];
|
|
659
|
+
// Static initialization block to generate methods
|
|
660
|
+
(() => {
|
|
661
|
+
// Generate both single and collection getter methods from unified configuration
|
|
662
|
+
for (const { resourceType, singleGetterMethodName, collectionGetterMethodName, } of FhirBundleSdk.RESOURCE_METHODS) {
|
|
663
|
+
// Generate single resource getter (e.g., getPatientById)
|
|
664
|
+
Object.defineProperty(FhirBundleSdk.prototype, singleGetterMethodName, {
|
|
665
|
+
value: function (id) {
|
|
666
|
+
return this.getResourceByIdAndType(id, resourceType);
|
|
667
|
+
},
|
|
668
|
+
writable: false,
|
|
669
|
+
configurable: false,
|
|
670
|
+
});
|
|
671
|
+
// Generate collection getter (e.g., getPatients)
|
|
672
|
+
Object.defineProperty(FhirBundleSdk.prototype, collectionGetterMethodName, {
|
|
673
|
+
value: function () {
|
|
674
|
+
return this.getResourcesByType(resourceType);
|
|
675
|
+
},
|
|
676
|
+
writable: false,
|
|
677
|
+
configurable: false,
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
})();
|
|
681
|
+
//# sourceMappingURL=index.js.map
|