@oystehr/sdk 4.3.8 → 4.3.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +105 -41
  2. package/dist/cjs/client/client.cjs +137 -5
  3. package/dist/cjs/client/client.cjs.map +1 -1
  4. package/dist/cjs/client/client.d.ts +28 -3
  5. package/dist/cjs/index.min.cjs +1 -1
  6. package/dist/cjs/index.min.cjs.map +1 -1
  7. package/dist/cjs/resources/classes/fhir-ext.cjs +277 -17
  8. package/dist/cjs/resources/classes/fhir-ext.cjs.map +1 -1
  9. package/dist/cjs/resources/classes/fhir-ext.d.ts +114 -12
  10. package/dist/cjs/resources/classes/fhir.cjs +14 -0
  11. package/dist/cjs/resources/classes/fhir.cjs.map +1 -1
  12. package/dist/cjs/resources/classes/fhir.d.ts +14 -0
  13. package/dist/cjs/resources/types/FaxSendParams.d.ts +1 -1
  14. package/dist/cjs/resources/types/ZambdaCreateParams.d.ts +1 -1
  15. package/dist/cjs/resources/types/ZambdaUpdateParams.d.ts +1 -1
  16. package/dist/cjs/resources/types/fhir.d.ts +74 -0
  17. package/dist/esm/client/client.d.ts +28 -3
  18. package/dist/esm/client/client.js +137 -5
  19. package/dist/esm/client/client.js.map +1 -1
  20. package/dist/esm/index.min.js +1 -1
  21. package/dist/esm/index.min.js.map +1 -1
  22. package/dist/esm/resources/classes/fhir-ext.d.ts +114 -12
  23. package/dist/esm/resources/classes/fhir-ext.js +273 -19
  24. package/dist/esm/resources/classes/fhir-ext.js.map +1 -1
  25. package/dist/esm/resources/classes/fhir.d.ts +14 -0
  26. package/dist/esm/resources/classes/fhir.js +15 -1
  27. package/dist/esm/resources/classes/fhir.js.map +1 -1
  28. package/dist/esm/resources/types/FaxSendParams.d.ts +1 -1
  29. package/dist/esm/resources/types/ZambdaCreateParams.d.ts +1 -1
  30. package/dist/esm/resources/types/ZambdaUpdateParams.d.ts +1 -1
  31. package/dist/esm/resources/types/fhir.d.ts +74 -0
  32. package/package.json +1 -1
  33. package/src/client/client.ts +214 -7
  34. package/src/resources/classes/fhir-ext.ts +483 -38
  35. package/src/resources/classes/fhir.ts +14 -0
  36. package/src/resources/types/FaxSendParams.ts +1 -1
  37. package/src/resources/types/ZambdaCreateParams.ts +1 -1
  38. package/src/resources/types/ZambdaUpdateParams.ts +1 -1
  39. package/src/resources/types/fhir.ts +97 -0
@@ -1,6 +1,6 @@
1
1
  import { Address as AddressR4B, HumanName as HumanNameR4B } from 'fhir/r4b';
2
2
  import { Address as AddressR5, HumanName as HumanNameR5 } from 'fhir/r5';
3
- import { BatchBundle, BatchInput, Bundle, FhirCreateParams, FhirDeleteParams, FhirGetParams, FhirHistoryGetParams, FhirHistorySearchParams, FhirPatchParams, FhirResource, FhirResourceReturnValue, FhirSearchParams, FhirUpdateParams, GenerateFriendlyPatientIdParams, TransactionBundle } from '../..';
3
+ import { BatchBundle, BatchInput, Bundle, FhirAsyncBulkOutputResult, FhirAsyncJobHandle, FhirAsyncJobStatus, FhirAsyncWaitOptions, FhirCreateParams, FhirDeleteParams, FhirGetParams, FhirHistoryGetParams, FhirHistorySearchParams, FhirPatchParams, FhirResource, FhirResourceReturnValue, FhirResponseMode, FhirSearchParams, FhirUpdateParams, GenerateFriendlyPatientIdParams, TransactionBundle } from '../..';
4
4
  import { FhirFetcherResponse, OystehrClientRequest, SDKResource } from '../../client/client';
5
5
  /**
6
6
  * Optional parameter that can be passed to the client methods. It allows
@@ -23,18 +23,120 @@ export interface OystehrFHIRUpdateClientRequest extends OystehrClientRequest {
23
23
  * @param request optional OystehrClientRequest object
24
24
  * @returns FHIR Bundle resource
25
25
  */
26
- export declare function search<T extends FhirResource>(this: SDKResource, params: FhirSearchParams<T>, request?: OystehrClientRequest): Promise<FhirFetcherResponse<Bundle<T>>>;
27
- export declare function create<T extends FhirResource>(this: SDKResource, params: FhirCreateParams<T>, request?: OystehrClientRequest): Promise<FhirFetcherResponse<FhirResourceReturnValue<T>>>;
28
- export declare function get<T extends FhirResource>(this: SDKResource, { resourceType, id }: FhirGetParams<T>, request?: OystehrClientRequest): Promise<FhirFetcherResponse<FhirResourceReturnValue<T>>>;
29
- export declare function update<T extends FhirResource>(this: SDKResource, params: FhirUpdateParams<T>, request?: OystehrFHIRUpdateClientRequest): Promise<FhirFetcherResponse<FhirResourceReturnValue<T>>>;
30
- export declare function patch<T extends FhirResource>(this: SDKResource, { resourceType, id, operations }: FhirPatchParams<T>, request?: OystehrFHIRUpdateClientRequest): Promise<FhirFetcherResponse<FhirResourceReturnValue<T>>>;
31
- declare function del<T extends FhirResource>(this: SDKResource, { resourceType, id }: FhirDeleteParams<T>, request?: OystehrClientRequest): Promise<FhirFetcherResponse<FhirResourceReturnValue<T>>>;
26
+ export declare function search<T extends FhirResource>(this: SDKResource, params: FhirSearchParams<T>, request: OystehrClientRequest & {
27
+ mode: Exclude<FhirResponseMode, 'sync'>;
28
+ }): Promise<FhirAsyncJobHandle>;
29
+ export declare function search<T extends FhirResource>(this: SDKResource, params: FhirSearchParams<T>, request?: OystehrClientRequest & {
30
+ mode?: 'sync' | undefined;
31
+ }): Promise<FhirFetcherResponse<Bundle<T>>>;
32
+ /**
33
+ * Performs an iterative FHIR search over initial request and following "next" urls,
34
+ * collecting all pages into a single Bundle.
35
+ *
36
+ * @param params FHIR search parameters plus optional pageSize that will overwrite _count in params
37
+ * @param request optional OystehrClientRequest object
38
+ * @returns FHIR Bundle resource that contains all entries across all pages. Bundle-level metadata
39
+ * (id, meta, total, etc.) is taken from the first page.
40
+ */
41
+ export declare function searchAndGetAllPages<T extends FhirResource>(this: SDKResource, params: FhirSearchParams<T> & {
42
+ pageSize?: number;
43
+ }, request?: OystehrClientRequest & {
44
+ mode?: 'sync' | undefined;
45
+ }): Promise<Bundle<T>>;
46
+ export declare function create<T extends FhirResource>(this: SDKResource, params: FhirCreateParams<T>, request: OystehrClientRequest & {
47
+ mode: Exclude<FhirResponseMode, 'sync'>;
48
+ }): Promise<FhirAsyncJobHandle>;
49
+ export declare function create<T extends FhirResource>(this: SDKResource, params: FhirCreateParams<T>, request?: OystehrClientRequest & {
50
+ mode?: 'sync' | undefined;
51
+ }): Promise<FhirFetcherResponse<FhirResourceReturnValue<T>>>;
52
+ export declare function get<T extends FhirResource>(this: SDKResource, { resourceType, id }: FhirGetParams<T>, request: OystehrClientRequest & {
53
+ mode: Exclude<FhirResponseMode, 'sync'>;
54
+ }): Promise<FhirAsyncJobHandle>;
55
+ export declare function get<T extends FhirResource>(this: SDKResource, { resourceType, id }: FhirGetParams<T>, request?: OystehrClientRequest & {
56
+ mode?: 'sync' | undefined;
57
+ }): Promise<FhirFetcherResponse<FhirResourceReturnValue<T>>>;
58
+ export declare function update<T extends FhirResource>(this: SDKResource, params: FhirUpdateParams<T>, request: OystehrFHIRUpdateClientRequest & {
59
+ mode: Exclude<FhirResponseMode, 'sync'>;
60
+ }): Promise<FhirAsyncJobHandle>;
61
+ export declare function update<T extends FhirResource>(this: SDKResource, params: FhirUpdateParams<T>, request?: OystehrFHIRUpdateClientRequest & {
62
+ mode?: 'sync' | undefined;
63
+ }): Promise<FhirFetcherResponse<FhirResourceReturnValue<T>>>;
64
+ export declare function patch<T extends FhirResource>(this: SDKResource, params: FhirPatchParams<T>, request: OystehrFHIRUpdateClientRequest & {
65
+ mode: Exclude<FhirResponseMode, 'sync'>;
66
+ }): Promise<FhirAsyncJobHandle>;
67
+ export declare function patch<T extends FhirResource>(this: SDKResource, params: FhirPatchParams<T>, request?: OystehrFHIRUpdateClientRequest & {
68
+ mode?: 'sync' | undefined;
69
+ }): Promise<FhirFetcherResponse<FhirResourceReturnValue<T>>>;
70
+ declare function del<T extends FhirResource>(this: SDKResource, params: FhirDeleteParams<T>, request: OystehrClientRequest & {
71
+ mode: Exclude<FhirResponseMode, 'sync'>;
72
+ }): Promise<FhirAsyncJobHandle>;
73
+ declare function del<T extends FhirResource>(this: SDKResource, params: FhirDeleteParams<T>, request?: OystehrClientRequest & {
74
+ mode?: 'sync' | undefined;
75
+ }): Promise<FhirFetcherResponse<FhirResourceReturnValue<T>>>;
32
76
  export { del as delete };
33
- export declare function history<T extends FhirResource>(this: SDKResource, { resourceType, id }: FhirHistorySearchParams<T>, request?: OystehrClientRequest): Promise<FhirFetcherResponse<Bundle<T>>>;
34
- export declare function history<T extends FhirResource>(this: SDKResource, { resourceType, id, versionId }: FhirHistoryGetParams<T>, request?: OystehrClientRequest): Promise<FhirFetcherResponse<T>>;
35
- export declare function history<T extends FhirResource>(this: SDKResource, { resourceType, id, count, offset }: FhirHistorySearchParams<T>, request?: OystehrClientRequest): Promise<FhirFetcherResponse<Bundle<T>>>;
36
- export declare function batch<BundleContentType extends FhirResource>(this: SDKResource, input: BatchInput<BundleContentType>, request?: OystehrClientRequest): Promise<FhirFetcherResponse<BatchBundle<BundleContentType>>>;
37
- export declare function transaction<BundleContentType extends FhirResource>(this: SDKResource, input: BatchInput<BundleContentType>, request?: OystehrClientRequest): Promise<FhirFetcherResponse<TransactionBundle<BundleContentType>>>;
77
+ /**
78
+ * Fetches the status of an async job. If the job is still in progress, returns an object with status 202. If the job is completed, returns the job result with status 200. If the job has failed, returns an object with status 500 and an OperationOutcome resource describing the failure. If the job has expired, returns an object with status 410.
79
+ * @param this The SDKResource context (this is an extension method and should be called with the SDKResource instance as the context, e.g. `sdkResource.getAsyncJob(jobId)`)
80
+ * @param jobId The ID of the async job to fetch
81
+ * @param request Optional OystehrClientRequest for authentication and headers
82
+ * @returns A Promise that resolves to the FhirAsyncJobStatus
83
+ */
84
+ export declare function getAsyncJob<T extends FhirResource>(this: SDKResource, jobId: string, request?: OystehrClientRequest): Promise<FhirAsyncJobStatus<T>>;
85
+ /**
86
+ * Waits for an async job to complete by polling its status until it reaches a terminal state (success, failure, or expiration) or the specified timeout is reached. Returns the final job status. Throws if the job fails, expires, or does not complete within the timeout.
87
+ * @param this The SDKResource context (this is an extension method and should be called with the SDKResource instance as the context, e.g. `sdkResource.waitForAsyncJob(jobId)`)
88
+ * @param jobId The ID of the async job to wait for
89
+ * @param options Optional FhirAsyncWaitOptions to configure polling behavior
90
+ * @param request Optional OystehrClientRequest for authentication and headers
91
+ * @returns A Promise that resolves to the final FhirAsyncJobStatus
92
+ */
93
+ export declare function waitForAsyncJob<T extends FhirResource>(this: SDKResource, jobId: string, options?: FhirAsyncWaitOptions, request?: OystehrClientRequest): Promise<FhirAsyncJobStatus<T>>;
94
+ /**
95
+ * Waits for an async job to complete and retrieves the bulk output manifest and files. Throws if the job fails, expires, or does not complete within the specified timeout.
96
+ * @param this The SDKResource context (this is an extension method and should be called with the SDKResource instance as the context, e.g. `sdkResource.waitForAsyncBulkOutput(jobId)`)
97
+ * @param jobId The ID of the async job to wait for
98
+ * @param options Optional FhirAsyncWaitOptions to configure polling behavior
99
+ * @param request Optional OystehrClientRequest for authentication and headers
100
+ * @returns A Promise that resolves to a FhirAsyncBulkOutputResult containing the bulk output manifest and files
101
+ */
102
+ export declare function waitForAsyncBulkOutput<T extends FhirResource>(this: SDKResource, jobId: string, options?: FhirAsyncWaitOptions, request?: OystehrClientRequest): Promise<FhirAsyncBulkOutputResult<T>>;
103
+ /**
104
+ * Wrapper around waitForAsyncBulkOutput that transforms the retrieved bulk output files into a single Bundle resource containing all the output resources as entries. This is a convenience method for use cases where you want to work with the bulk output as a Bundle, but it may not be efficient for large outputs due to the overhead of downloading and parsing all files and constructing the Bundle in memory.
105
+ * Can be slow due to downloading and parsing potentially large NDJSON files, so use only if you need the full output as a Bundle resource. For more efficient processing of large bulk outputs, use waitForAsyncBulkOutput directly.
106
+ * @param jobId the ID of the async job to wait for
107
+ * @param options optional FhirAsyncWaitOptions to configure polling behavior
108
+ * @param request optional OystehrClientRequest for authentication and headers
109
+ * @returns a Promise that resolves to a Bundle containing all resources from the bulk output
110
+ */
111
+ export declare function waitForAsyncBulkBundle<T extends FhirResource>(this: SDKResource, jobId: string, options?: FhirAsyncWaitOptions, request?: OystehrClientRequest): Promise<Bundle<T>>;
112
+ export declare function cancelAsyncJob(this: SDKResource, jobId: string, request?: OystehrClientRequest): Promise<void>;
113
+ export declare function history<T extends FhirResource>(this: SDKResource, { resourceType, id }: FhirHistorySearchParams<T>, request: OystehrClientRequest & {
114
+ mode: Exclude<FhirResponseMode, 'sync'>;
115
+ }): Promise<FhirAsyncJobHandle>;
116
+ export declare function history<T extends FhirResource>(this: SDKResource, { resourceType, id, versionId }: FhirHistoryGetParams<T>, request: OystehrClientRequest & {
117
+ mode: Exclude<FhirResponseMode, 'sync'>;
118
+ }): Promise<FhirAsyncJobHandle>;
119
+ export declare function history<T extends FhirResource>(this: SDKResource, { resourceType, id, versionId }: FhirHistoryGetParams<T>, request?: OystehrClientRequest & {
120
+ mode?: 'sync' | undefined;
121
+ }): Promise<FhirFetcherResponse<T>>;
122
+ export declare function history<T extends FhirResource>(this: SDKResource, { resourceType, id }: FhirHistorySearchParams<T>, request?: OystehrClientRequest & {
123
+ mode?: 'sync' | undefined;
124
+ }): Promise<FhirFetcherResponse<Bundle<T>>>;
125
+ export declare function history<T extends FhirResource>(this: SDKResource, { resourceType, id, count, offset }: FhirHistorySearchParams<T>, request?: OystehrClientRequest & {
126
+ mode?: 'sync' | undefined;
127
+ }): Promise<FhirFetcherResponse<Bundle<T>>>;
128
+ export declare function batch<BundleContentType extends FhirResource>(this: SDKResource, input: BatchInput<BundleContentType>, request: OystehrClientRequest & {
129
+ mode: Exclude<FhirResponseMode, 'sync'>;
130
+ }): Promise<FhirAsyncJobHandle>;
131
+ export declare function batch<BundleContentType extends FhirResource>(this: SDKResource, input: BatchInput<BundleContentType>, request?: OystehrClientRequest & {
132
+ mode?: 'sync' | undefined;
133
+ }): Promise<FhirFetcherResponse<BatchBundle<BundleContentType>>>;
134
+ export declare function transaction<BundleContentType extends FhirResource>(this: SDKResource, input: BatchInput<BundleContentType>, request: OystehrClientRequest & {
135
+ mode: Exclude<FhirResponseMode, 'sync'>;
136
+ }): Promise<FhirAsyncJobHandle>;
137
+ export declare function transaction<BundleContentType extends FhirResource>(this: SDKResource, input: BatchInput<BundleContentType>, request?: OystehrClientRequest & {
138
+ mode?: 'sync' | undefined;
139
+ }): Promise<FhirFetcherResponse<TransactionBundle<BundleContentType>>>;
38
140
  export declare function generateFriendlyPatientId(this: SDKResource, { id }: GenerateFriendlyPatientIdParams, request?: OystehrClientRequest): Promise<FhirFetcherResponse<FhirResource>>;
39
141
  export declare function formatAddress(address: AddressR4B | AddressR5, options?: {
40
142
  all?: boolean;
@@ -1,5 +1,5 @@
1
1
  import { addParamsToSearch } from '../../client/client.js';
2
- import { OystehrFHIRError, OystehrSdkError } from '../../errors/index.js';
2
+ import { OystehrSdkError, OystehrFHIRError } from '../../errors/index.js';
3
3
 
4
4
  // Code adapted from https://github.com/sindresorhus/uint8array-extras
5
5
  const MAX_BLOCK_SIZE = 65_535;
@@ -193,13 +193,9 @@ function assertRetrievedResource(config, resource) {
193
193
  }
194
194
  }
195
195
  }
196
- /**
197
- * Performs a FHIR search and returns the results as a Bundle resource
198
- *
199
- * @param options FHIR resource type and FHIR search parameters
200
- * @param request optional OystehrClientRequest object
201
- * @returns FHIR Bundle resource
202
- */
196
+ function isAsyncRequestMode(mode) {
197
+ return mode === 'async-bundle' || mode === 'async-bulk';
198
+ }
203
199
  async function search(params, request) {
204
200
  const { resourceType } = params;
205
201
  const taggedParams = applyTagSearchParams(this.config, params.params);
@@ -213,6 +209,13 @@ async function search(params, request) {
213
209
  return acc;
214
210
  }, {});
215
211
  }
212
+ const requestMode = request?.mode;
213
+ if (isAsyncRequestMode(requestMode)) {
214
+ return await this.startAsyncJob(`/${resourceType}/_search`, 'POST', paramMap ?? {}, requestMode, {
215
+ ...request,
216
+ contentType: 'application/x-www-form-urlencoded',
217
+ });
218
+ }
216
219
  const requestBundle = await this.fhirRequest(`/${resourceType}/_search`, 'POST')(paramMap, {
217
220
  ...request,
218
221
  contentType: 'application/x-www-form-urlencoded',
@@ -226,12 +229,61 @@ async function search(params, request) {
226
229
  };
227
230
  return bundle;
228
231
  }
232
+ /**
233
+ * Performs an iterative FHIR search over initial request and following "next" urls,
234
+ * collecting all pages into a single Bundle.
235
+ *
236
+ * @param params FHIR search parameters plus optional pageSize that will overwrite _count in params
237
+ * @param request optional OystehrClientRequest object
238
+ * @returns FHIR Bundle resource that contains all entries across all pages. Bundle-level metadata
239
+ * (id, meta, total, etc.) is taken from the first page.
240
+ */
241
+ async function searchAndGetAllPages(params, request) {
242
+ const { pageSize, ...searchParams } = params;
243
+ let firstPageParams = searchParams;
244
+ if (pageSize) {
245
+ const baseParams = (searchParams.params ?? []).filter((p) => p.name !== '_count') ?? [];
246
+ firstPageParams = { ...searchParams, params: [...baseParams, { name: '_count', value: pageSize }] };
247
+ }
248
+ const allEntries = [];
249
+ const typedSearch = (search);
250
+ // search returns Bundle, and fhirRequest in the while block returns FhirBundle
251
+ let currentBundle = await typedSearch.call(this, firstPageParams, request);
252
+ const firstBundle = { ...currentBundle, link: currentBundle.link?.filter((link) => link.relation !== 'next') };
253
+ // eslint-disable-next-line no-constant-condition
254
+ while (true) {
255
+ const entries = currentBundle.entry;
256
+ if (entries) {
257
+ allEntries.push(...entries);
258
+ }
259
+ const nextLink = currentBundle.link?.find((link) => link.relation === 'next')?.url;
260
+ if (!nextLink) {
261
+ break;
262
+ }
263
+ currentBundle = await this.fhirRequest(nextLink, 'GET')({}, request);
264
+ }
265
+ return {
266
+ ...firstBundle,
267
+ entry: allEntries.length ? allEntries : undefined,
268
+ unbundle: function () {
269
+ return this.entry?.map((e) => e.resource).filter((r) => r !== undefined) ?? [];
270
+ },
271
+ };
272
+ }
229
273
  async function create(params, request) {
230
274
  const tagged = applyTagToResource(this.config, params);
231
275
  const { resourceType } = tagged;
232
- return this.fhirRequest(`/${resourceType}`, 'POST')(tagged, request);
276
+ const requestMode = request?.mode;
277
+ if (isAsyncRequestMode(requestMode)) {
278
+ return await this.startAsyncJob(`/${resourceType}`, 'POST', tagged, requestMode, request);
279
+ }
280
+ return await this.fhirRequest(`/${resourceType}`, 'POST')(tagged, request);
233
281
  }
234
282
  async function get({ resourceType, id }, request) {
283
+ const requestMode = request?.mode;
284
+ if (isAsyncRequestMode(requestMode)) {
285
+ return await this.startAsyncJob(`/${resourceType}/${id}`, 'GET', {}, requestMode, request);
286
+ }
235
287
  const result = await this.fhirRequest(`/${resourceType}/${id}`, 'GET')({}, request);
236
288
  assertRetrievedResource(this.config, result);
237
289
  return result;
@@ -239,23 +291,215 @@ async function get({ resourceType, id }, request) {
239
291
  async function update(params, request) {
240
292
  const tagged = applyTagToResource(this.config, params);
241
293
  const { id, resourceType } = tagged;
242
- return this.fhirRequest(`/${resourceType}/${id}`, 'PUT')(tagged, {
294
+ const requestMode = request?.mode;
295
+ const ifMatchRequest = {
243
296
  ...request,
244
297
  ifMatch: request?.optimisticLockingVersionId ? `W/"${request.optimisticLockingVersionId}"` : undefined,
245
- });
298
+ };
299
+ if (isAsyncRequestMode(requestMode)) {
300
+ return await this.startAsyncJob(`/${resourceType}/${id}`, 'PUT', tagged, requestMode, ifMatchRequest);
301
+ }
302
+ return await this.fhirRequest(`/${resourceType}/${id}`, 'PUT')(tagged, ifMatchRequest);
246
303
  }
247
304
  async function patch({ resourceType, id, operations }, request) {
248
305
  const taggedOperations = applyTagToPatchOperations(this.config, operations);
249
- return this.fhirRequest(`/${resourceType}/${id}`, 'PATCH')(taggedOperations, {
306
+ const requestMode = request?.mode;
307
+ const ifMatchRequest = {
250
308
  ...request,
251
- contentType: 'application/json-patch+json',
252
309
  ifMatch: request?.optimisticLockingVersionId ? `W/"${request.optimisticLockingVersionId}"` : undefined,
310
+ };
311
+ if (isAsyncRequestMode(requestMode)) {
312
+ return await this.startAsyncJob(`/${resourceType}/${id}`, 'PATCH', taggedOperations, requestMode, {
313
+ ...ifMatchRequest,
314
+ contentType: 'application/json-patch+json',
315
+ });
316
+ }
317
+ return this.fhirRequest(`/${resourceType}/${id}`, 'PATCH')(taggedOperations, {
318
+ ...ifMatchRequest,
319
+ contentType: 'application/json-patch+json',
253
320
  });
254
321
  }
255
322
  async function del({ resourceType, id }, request) {
256
- return this.fhirRequest(`/${resourceType}/${id}`, 'DELETE')({}, request);
323
+ const requestMode = request?.mode;
324
+ if (isAsyncRequestMode(requestMode)) {
325
+ return await this.startAsyncJob(`/${resourceType}/${id}`, 'DELETE', {}, requestMode, request);
326
+ }
327
+ return await this.fhirRequest(`/${resourceType}/${id}`, 'DELETE')({}, request);
328
+ }
329
+ function getRetryDelayMs(retryAfter, fallbackMs) {
330
+ if (!retryAfter) {
331
+ return fallbackMs;
332
+ }
333
+ const asSeconds = Number(retryAfter);
334
+ if (Number.isFinite(asSeconds) && asSeconds >= 0) {
335
+ return Math.max(0, Math.floor(asSeconds * 1000));
336
+ }
337
+ const asTimestamp = Date.parse(retryAfter);
338
+ if (Number.isFinite(asTimestamp)) {
339
+ return Math.max(0, asTimestamp - Date.now());
340
+ }
341
+ return fallbackMs;
342
+ }
343
+ /**
344
+ * Fetches the status of an async job. If the job is still in progress, returns an object with status 202. If the job is completed, returns the job result with status 200. If the job has failed, returns an object with status 500 and an OperationOutcome resource describing the failure. If the job has expired, returns an object with status 410.
345
+ * @param this The SDKResource context (this is an extension method and should be called with the SDKResource instance as the context, e.g. `sdkResource.getAsyncJob(jobId)`)
346
+ * @param jobId The ID of the async job to fetch
347
+ * @param request Optional OystehrClientRequest for authentication and headers
348
+ * @returns A Promise that resolves to the FhirAsyncJobStatus
349
+ */
350
+ async function getAsyncJob(jobId, request) {
351
+ return await this.fetchAsyncJobStatus(jobId, request);
352
+ }
353
+ /**
354
+ * Waits for an async job to complete by polling its status until it reaches a terminal state (success, failure, or expiration) or the specified timeout is reached. Returns the final job status. Throws if the job fails, expires, or does not complete within the timeout.
355
+ * @param this The SDKResource context (this is an extension method and should be called with the SDKResource instance as the context, e.g. `sdkResource.waitForAsyncJob(jobId)`)
356
+ * @param jobId The ID of the async job to wait for
357
+ * @param options Optional FhirAsyncWaitOptions to configure polling behavior
358
+ * @param request Optional OystehrClientRequest for authentication and headers
359
+ * @returns A Promise that resolves to the final FhirAsyncJobStatus
360
+ */
361
+ async function waitForAsyncJob(jobId, options, request) {
362
+ // 5 seconds poll interval by default
363
+ const pollIntervalMs = options?.pollIntervalMs ?? 5000;
364
+ // 15 minutes timout by default
365
+ const timeoutMs = options?.timeoutMs ?? 900000;
366
+ const attempts = Math.max(1, Math.ceil(timeoutMs / pollIntervalMs));
367
+ for (let attempt = 0; attempt < attempts; attempt++) {
368
+ const status = await this.fetchAsyncJobStatus(jobId, request);
369
+ if (status.status !== 202) {
370
+ return status;
371
+ }
372
+ if (attempt < attempts - 1) {
373
+ const retryAfter = 'retryAfter' in status ? status.retryAfter : undefined;
374
+ await new Promise((resolve) => setTimeout(resolve, getRetryDelayMs(retryAfter, pollIntervalMs)));
375
+ }
376
+ }
377
+ throw new OystehrSdkError({
378
+ message: `Async job ${jobId} did not complete within ${timeoutMs} ms`,
379
+ code: 408,
380
+ });
381
+ }
382
+ function parseNdjsonResources(ndjson, sourceUrl) {
383
+ const resources = [];
384
+ const lines = ndjson.split('\n');
385
+ for (let index = 0; index < lines.length; index++) {
386
+ const line = lines[index].trim();
387
+ if (line.length === 0) {
388
+ continue;
389
+ }
390
+ try {
391
+ resources.push(JSON.parse(line));
392
+ }
393
+ catch (error) {
394
+ throw new OystehrSdkError({
395
+ message: `Failed to parse NDJSON line ${index + 1} from ${sourceUrl}`,
396
+ code: 500,
397
+ cause: error,
398
+ });
399
+ }
400
+ }
401
+ return resources;
402
+ }
403
+ /**
404
+ * Waits for an async job to complete and retrieves the bulk output manifest and files. Throws if the job fails, expires, or does not complete within the specified timeout.
405
+ * @param this The SDKResource context (this is an extension method and should be called with the SDKResource instance as the context, e.g. `sdkResource.waitForAsyncBulkOutput(jobId)`)
406
+ * @param jobId The ID of the async job to wait for
407
+ * @param options Optional FhirAsyncWaitOptions to configure polling behavior
408
+ * @param request Optional OystehrClientRequest for authentication and headers
409
+ * @returns A Promise that resolves to a FhirAsyncBulkOutputResult containing the bulk output manifest and files
410
+ */
411
+ async function waitForAsyncBulkOutput(jobId, options, request) {
412
+ const status = await waitForAsyncJob.call(this, jobId, options, request);
413
+ if (status.status === 404) {
414
+ throw new OystehrSdkError({
415
+ message: `Async job ${jobId} not found`,
416
+ code: 404,
417
+ });
418
+ }
419
+ if (status.status === 410) {
420
+ throw new OystehrSdkError({
421
+ message: `Async job ${jobId} expired`,
422
+ code: 410,
423
+ });
424
+ }
425
+ if (status.status !== 200 || !('mode' in status) || status.mode !== 'bulk') {
426
+ throw new OystehrSdkError({
427
+ message: `Async job ${jobId} did not complete in bulk mode`,
428
+ code: status.status,
429
+ });
430
+ }
431
+ const accessToken = request?.accessToken ?? this.config.accessToken;
432
+ const projectId = request?.projectId ?? this.config.projectId;
433
+ if (status.manifest.requiresAccessToken && !accessToken) {
434
+ throw new OystehrSdkError({
435
+ message: `Bulk output for async job ${jobId} requires an access token`,
436
+ code: 401,
437
+ });
438
+ }
439
+ const fetchImpl = this.config.fetch ?? fetch;
440
+ const headers = {};
441
+ if (projectId) {
442
+ headers['x-zapehr-project-id'] = projectId;
443
+ headers['x-oystehr-project-id'] = projectId;
444
+ }
445
+ if (status.manifest.requiresAccessToken && accessToken) {
446
+ headers.Authorization = `Bearer ${accessToken}`;
447
+ }
448
+ const requestHeaders = Object.keys(headers).length > 0 ? headers : undefined;
449
+ const output = await Promise.all(status.manifest.output.map(async (file) => {
450
+ const response = await fetchImpl(new Request(file.url, {
451
+ method: 'GET',
452
+ headers: requestHeaders,
453
+ }));
454
+ if (!response.ok) {
455
+ throw new OystehrSdkError({
456
+ message: `Failed to download bulk output (${file.type}): HTTP ${response.status}`,
457
+ code: response.status,
458
+ });
459
+ }
460
+ const ndjson = await response.text();
461
+ return {
462
+ ...file,
463
+ resources: parseNdjsonResources(ndjson, file.url),
464
+ };
465
+ }));
466
+ return {
467
+ manifest: status.manifest,
468
+ output,
469
+ };
470
+ }
471
+ /**
472
+ * Wrapper around waitForAsyncBulkOutput that transforms the retrieved bulk output files into a single Bundle resource containing all the output resources as entries. This is a convenience method for use cases where you want to work with the bulk output as a Bundle, but it may not be efficient for large outputs due to the overhead of downloading and parsing all files and constructing the Bundle in memory.
473
+ * Can be slow due to downloading and parsing potentially large NDJSON files, so use only if you need the full output as a Bundle resource. For more efficient processing of large bulk outputs, use waitForAsyncBulkOutput directly.
474
+ * @param jobId the ID of the async job to wait for
475
+ * @param options optional FhirAsyncWaitOptions to configure polling behavior
476
+ * @param request optional OystehrClientRequest for authentication and headers
477
+ * @returns a Promise that resolves to a Bundle containing all resources from the bulk output
478
+ */
479
+ async function waitForAsyncBulkBundle(jobId, options, request) {
480
+ const bulkOutput = await waitForAsyncBulkOutput.call(this, jobId, options, request);
481
+ const resources = bulkOutput.output.flatMap((file) => file.resources);
482
+ const bundle = {
483
+ resourceType: 'Bundle',
484
+ type: 'collection',
485
+ entry: resources.map((resource) => ({ resource })),
486
+ unbundle: function () {
487
+ return this.entry?.map((entry) => entry.resource).filter((value) => value !== undefined) ?? [];
488
+ },
489
+ };
490
+ return bundle;
491
+ }
492
+ async function cancelAsyncJob(jobId, request) {
493
+ await this.fhirRequest(`/async-job/${jobId}`, 'DELETE')({}, request);
257
494
  }
258
495
  async function history({ resourceType, id, versionId, count, offset, }, request) {
496
+ const requestMode = request?.mode;
497
+ if (isAsyncRequestMode(requestMode)) {
498
+ if (versionId) {
499
+ return await this.startAsyncJob(`/${resourceType}/${id}/_history/${versionId}`, 'GET', {}, requestMode, request);
500
+ }
501
+ return await this.startAsyncJob(`/${resourceType}/${id}/_history`, 'GET', {}, requestMode, request);
502
+ }
259
503
  if (versionId) {
260
504
  return this.fhirRequest(`/${resourceType}/${id}/_history/${versionId}`, 'GET')({}, request);
261
505
  }
@@ -399,11 +643,16 @@ function batchInputRequestToBundleEntryItem(request, config) {
399
643
  throw new Error('Unrecognized method');
400
644
  }
401
645
  async function batch(input, request) {
402
- const resp = await this.fhirRequest('/', 'POST')({
646
+ const requestPayload = {
403
647
  resourceType: 'Bundle',
404
648
  type: 'batch',
405
649
  entry: input.requests.map((req) => batchInputRequestToBundleEntryItem(req, this.config)),
406
- }, request);
650
+ };
651
+ const requestMode = request?.mode;
652
+ if (isAsyncRequestMode(requestMode)) {
653
+ return await this.startAsyncJob('/', 'POST', requestPayload, requestMode, request);
654
+ }
655
+ const resp = await this.fhirRequest('/', 'POST')(requestPayload, request);
407
656
  // Validate each GET/HEAD retrieval entry against the tag config.
408
657
  // Violations are replaced with a synthetic 404 OperationOutcome entry; batch entries are independent.
409
658
  const rawEntries = resp.entry;
@@ -446,11 +695,16 @@ async function batch(input, request) {
446
695
  return bundle;
447
696
  }
448
697
  async function transaction(input, request) {
449
- const resp = await this.fhirRequest('/', 'POST')({
698
+ const requestPayload = {
450
699
  resourceType: 'Bundle',
451
700
  type: 'transaction',
452
701
  entry: input.requests.map((req) => batchInputRequestToBundleEntryItem(req, this.config)),
453
- }, request);
702
+ };
703
+ const requestMode = request?.mode;
704
+ if (isAsyncRequestMode(requestMode)) {
705
+ return await this.startAsyncJob('/', 'POST', requestPayload, requestMode, request);
706
+ }
707
+ const resp = await this.fhirRequest('/', 'POST')(requestPayload, request);
454
708
  // Validate each GET/HEAD retrieval entry against the tag config.
455
709
  // A violation throws OystehrFHIRError(404) — transactions are all-or-nothing.
456
710
  if (this.config.workspaceTag || this.config.ignoreTags?.length) {
@@ -520,5 +774,5 @@ function formatHumanName(name, options) {
520
774
  return builder.join(' ').trim();
521
775
  }
522
776
 
523
- export { batch, create, del as delete, formatAddress, formatHumanName, generateFriendlyPatientId, get, history, patch, search, transaction, update };
777
+ export { batch, cancelAsyncJob, create, del as delete, formatAddress, formatHumanName, generateFriendlyPatientId, get, getAsyncJob, history, patch, search, searchAndGetAllPages, transaction, update, waitForAsyncBulkBundle, waitForAsyncBulkOutput, waitForAsyncJob };
524
778
  //# sourceMappingURL=fhir-ext.js.map