@medplum/fhir-router 5.1.23 → 5.1.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,5 @@
1
1
  import type { Bundle } from '@medplum/fhirtypes';
2
+ import type { BundleEntry } from '@medplum/fhirtypes';
2
3
  import type { CapabilityStatementRestInteraction } from '@medplum/fhirtypes';
3
4
  import type { CapabilityStatementRestResourceInteraction } from '@medplum/fhirtypes';
4
5
  import type { Event as Event_2 } from '@medplum/core';
@@ -20,6 +21,223 @@ export declare interface BatchEvent extends Event_2 {
20
21
  size?: number;
21
22
  }
22
23
 
24
+ /**
25
+ * The durable, JSON-serializable state produced by preprocessing a batch bundle. This is
26
+ * everything required to (re)hydrate a {@link BatchProcessor} and resume processing after a
27
+ * server restart or worker crash, EXCEPT the progress marker (the index into `bundleInfo.ordering`
28
+ * of the next entry to process) and the per-entry results produced so far, both of which the
29
+ * caller (e.g. the async batch worker) is responsible for persisting separately.
30
+ *
31
+ * @see BatchProcessor.preprocess
32
+ * @see BatchProcessor.fromState
33
+ */
34
+ export declare interface BatchInitialState {
35
+ /**
36
+ * The preprocessed bundle. During preprocessing the bundle is mutated in place: `create`
37
+ * entries are assigned resource IDs and conditional `update`/`delete` URLs are rewritten to
38
+ * absolute references. This mutated form MUST be persisted and reloaded verbatim — re-running
39
+ * preprocessing on the original bundle would assign fresh random IDs and break references to
40
+ * resources that were already created.
41
+ */
42
+ bundle: Bundle;
43
+ bundleInfo: BundlePreprocessInfo;
44
+ resolvedIdentities: Record<string, string>;
45
+ /**
46
+ * Result entries produced during preprocessing itself (i.e. error responses for malformed
47
+ * entries in a non-transaction bundle), keyed by their index in the original bundle. These
48
+ * entries are never re-processed, so they are captured here rather than in per-entry progress.
49
+ */
50
+ preprocessResults: Record<number, BundleEntry>;
51
+ }
52
+
53
+ /**
54
+ * The BatchProcessor class contains the state for processing a batch/transaction bundle.
55
+ * In particular, it tracks rewritten IDs as necessary.
56
+ *
57
+ * The processor supports two modes of operation:
58
+ *
59
+ * 1. One-shot via {@link BatchProcessor.run}, used for synchronous batch/transaction requests.
60
+ * This preprocesses and then processes every entry within a single call.
61
+ *
62
+ * 2. Re-entrant, used by the async batch worker. The caller drives processing one entry at a
63
+ * time via {@link BatchProcessor.preprocess}, {@link BatchProcessor.hasMoreEntries}, and
64
+ * {@link BatchProcessor.processNextEntry}, persisting durable state ({@link BatchInitialState},
65
+ * progress marker, and per-entry results) as it goes. After a restart the processor is
66
+ * rehydrated via {@link BatchProcessor.fromState} and processing resumes from the persisted
67
+ * marker. Only `batch` bundles are re-entrant; `transaction` bundles must use {@link BatchProcessor.run}.
68
+ */
69
+ export declare class BatchProcessor {
70
+ private readonly router;
71
+ private repo;
72
+ private readonly bundle;
73
+ private readonly req;
74
+ private resolvedIdentities;
75
+ private bundleInfo;
76
+ private resultEntries;
77
+ /** Index into `bundleInfo.ordering` of the next entry to process. */
78
+ private position;
79
+ /** Indices (into the original bundle) of results produced since the last {@link BatchProcessor.takePendingResults} call. */
80
+ private pendingIndices;
81
+ /** Error messages accumulated across processed entries, for the post-batch telemetry event. */
82
+ private readonly errors;
83
+ /**
84
+ * Creates a batch processor.
85
+ * @param router - The FHIR router.
86
+ * @param repo - The FHIR repository.
87
+ * @param bundle - The input bundle.
88
+ * @param req - The request for the batch.
89
+ */
90
+ constructor(router: FhirRouter, repo: FhirRepository, bundle: Bundle, req: FhirRequest);
91
+ /**
92
+ * Rehydrates a batch processor from previously-persisted durable state so that processing can
93
+ * resume after a server restart or worker crash. Intended for the re-entrant (async batch)
94
+ * flow; the caller is responsible for loading the {@link BatchInitialState} and progress marker
95
+ * from durable storage, and for assembling the final response bundle from the durably-persisted
96
+ * result entries (this instance only tracks results produced in the current run).
97
+ * @param router - The FHIR router.
98
+ * @param repo - The FHIR repository.
99
+ * @param req - The request for the batch.
100
+ * @param initialState - The durable state produced by {@link BatchProcessor.preprocess}.
101
+ * @param position - The index into `bundleInfo.ordering` of the next entry to process.
102
+ * @returns A batch processor positioned to resume at `position`.
103
+ */
104
+ static fromState(router: FhirRouter, repo: FhirRepository, req: FhirRequest, initialState: BatchInitialState, position: number): BatchProcessor;
105
+ /**
106
+ * Processes a FHIR batch or transaction request in a single call.
107
+ * @returns The bundle response.
108
+ */
109
+ run(): Promise<Bundle>;
110
+ /**
111
+ * Validates and preprocesses the bundle, scanning entries to resolve identities, rewrite
112
+ * conditional references, and compute processing order.
113
+ *
114
+ * For the re-entrant flow, the returned {@link BatchInitialState} should be persisted to
115
+ * durable storage before processing entries so that the processor can be rehydrated via
116
+ * {@link BatchProcessor.fromState} after a restart.
117
+ * @returns The durable initial state for the batch.
118
+ */
119
+ preprocess(): Promise<BatchInitialState>;
120
+ /**
121
+ * @returns True if there are more entries to process, i.e. {@link BatchProcessor.processNextEntry} should be
122
+ * called again.
123
+ */
124
+ hasMoreEntries(): boolean;
125
+ /**
126
+ * @returns The index into `bundleInfo.ordering` of the next entry to process. This is the
127
+ * progress marker that should be persisted (together with the result entries produced so far)
128
+ * to allow resuming via {@link BatchProcessor.fromState}.
129
+ */
130
+ getPosition(): number;
131
+ /**
132
+ * Returns the result entries produced since the last call (keyed by their index in the
133
+ * original bundle) and clears the pending buffer. The caller persists these to durable
134
+ * storage as a checkpoint before advancing the durable progress marker.
135
+ * @returns Newly-produced result entries, keyed by original bundle index or `undefined`
136
+ * if no new entries were produced.
137
+ */
138
+ takePendingResults(): Record<number, BundleEntry> | undefined;
139
+ private processEntriesAndBuild;
140
+ private dispatchPreEvent;
141
+ private dispatchPostEvent;
142
+ /**
143
+ * Assembles the response bundle from the result entries recorded on this instance. Used by the
144
+ * one-shot {@link BatchProcessor.run} flow. The re-entrant flow instead assembles the final bundle from durably
145
+ * persisted results via {@link buildBatchResponseBundle}.
146
+ * @returns The bundle response.
147
+ */
148
+ private buildResultBundle;
149
+ /**
150
+ * Executes a callback with the provided repository intended for use with transaction-scoped operations.
151
+ * @param repo - The repository to use.
152
+ * @param callback - The callback to execute.
153
+ * @returns The result of the callback.
154
+ */
155
+ private withRepo;
156
+ /**
157
+ * Scans the Bundle in order to ensure entries are processed in the correct sequence,
158
+ * as well as to identify any operations that might require specific handling.
159
+ *
160
+ * @param results - The array of result entries, to track partial results.
161
+ * @returns The information gathered from scanning the Bundle.
162
+ */
163
+ private preprocessBundle;
164
+ /**
165
+ * Resolves the resource identity associated with an entry, and tracks it for later reference rewriting.
166
+ * @see https://www.hl7.org/fhir/R4/http.html#trules
167
+ *
168
+ * @param entry - The entry to resolve.
169
+ * @param index - The index of the Bundle entry.
170
+ * @param seenIdentities - The set of resolved identities that have already been seen in preprocessing.
171
+ * @returns - The (error) result for the entry, if it could not be preprocessed.
172
+ */
173
+ private preprocessEntry;
174
+ private resolveIdentity;
175
+ private getRouteForEntry;
176
+ private resolveCreateIdentity;
177
+ private resolveModificationIdentity;
178
+ /**
179
+ * Processes the next entry in the bundle ordering (the entry at the current progress marker),
180
+ * recording its result and advancing the marker.
181
+ *
182
+ * For `batch` bundles, per-entry errors are captured as error result entries and processing
183
+ * continues; a 429 (rate limit) terminates the batch, filling all remaining entries with the
184
+ * rate-limit outcome.
185
+ *
186
+ * Must be called only after {@link BatchProcessor.preprocess} or {@link BatchProcessor.fromState}.
187
+ * Callers should guard with {@link BatchProcessor.hasMoreEntries}. The re-entrant flow only supports
188
+ * `batch` semantics. For `transaction` bundles, use {@link BatchProcessor.run}.
189
+ */
190
+ processNextEntry(): Promise<void>;
191
+ /**
192
+ * Processes the next entry in the bundle ordering (the entry at the current progress marker),
193
+ * recording its result and advancing the marker.
194
+ *
195
+ * For `batch` bundles, per-entry errors are captured as error result entries and processing
196
+ * continues; a 429 (rate limit) terminates the batch, filling all remaining entries with the
197
+ * rate-limit outcome. For `transaction` bundles, any entry error is thrown so the enclosing
198
+ * transaction rolls back.
199
+ */
200
+ private processNextEntryImpl;
201
+ private setResult;
202
+ /**
203
+ * Processes a single entry from a FHIR batch request.
204
+ * @param entry - The bundle entry.
205
+ * @returns The bundle entry response.
206
+ */
207
+ private processBatchEntry;
208
+ /**
209
+ * Constructs the equivalent HTTP request for a Bundle entry, based on its `request` field.
210
+ * @param entry - The Bundle entry to parse.
211
+ * @param route - The route associated with the request.
212
+ * @returns The HTTP request to perform the operation specified by the given batch entry.
213
+ */
214
+ private parseBatchRequest;
215
+ private parsePatchBody;
216
+ private parsePatchParameter;
217
+ private rewriteIds;
218
+ private rewriteIdsInArray;
219
+ private rewriteIdsInObject;
220
+ private rewriteIdsInString;
221
+ private isTransaction;
222
+ }
223
+
224
+ /**
225
+ * Assembles a batch/transaction response bundle from result entries that were collected across
226
+ * multiple re-entrant processing runs and persisted to durable storage. Used by the async batch
227
+ * worker to build the final (or a partial) response bundle from checkpointed results.
228
+ * @param bundleType - The type of the request bundle (`batch` or `transaction`).
229
+ * @param entryCount - The number of entries in the original request bundle.
230
+ * @param results - The result entries, keyed by their index in the original request bundle.
231
+ * @returns The response bundle, with one entry per original request entry.
232
+ */
233
+ export declare function buildBatchResponseBundle(bundleType: Bundle['type'], entryCount: number, results: Record<number, BundleEntry>): Bundle;
234
+
235
+ export declare type BundlePreprocessInfo = {
236
+ ordering: number[];
237
+ requiresStrongTransaction: boolean;
238
+ updates: number;
239
+ };
240
+
23
241
  export declare function createResourceImpl<T extends Resource>(resourceType: T['resourceType'], resource: T, repo: FhirRepository, options?: CreateResourceOptions): Promise<FhirResponse>;
24
242
 
25
243
  export declare type CreateResourceOptions = {
@@ -1,4 +1,5 @@
1
1
  import type { Bundle } from '@medplum/fhirtypes';
2
+ import type { BundleEntry } from '@medplum/fhirtypes';
2
3
  import type { CapabilityStatementRestInteraction } from '@medplum/fhirtypes';
3
4
  import type { CapabilityStatementRestResourceInteraction } from '@medplum/fhirtypes';
4
5
  import type { Event as Event_2 } from '@medplum/core';
@@ -20,6 +21,223 @@ export declare interface BatchEvent extends Event_2 {
20
21
  size?: number;
21
22
  }
22
23
 
24
+ /**
25
+ * The durable, JSON-serializable state produced by preprocessing a batch bundle. This is
26
+ * everything required to (re)hydrate a {@link BatchProcessor} and resume processing after a
27
+ * server restart or worker crash, EXCEPT the progress marker (the index into `bundleInfo.ordering`
28
+ * of the next entry to process) and the per-entry results produced so far, both of which the
29
+ * caller (e.g. the async batch worker) is responsible for persisting separately.
30
+ *
31
+ * @see BatchProcessor.preprocess
32
+ * @see BatchProcessor.fromState
33
+ */
34
+ export declare interface BatchInitialState {
35
+ /**
36
+ * The preprocessed bundle. During preprocessing the bundle is mutated in place: `create`
37
+ * entries are assigned resource IDs and conditional `update`/`delete` URLs are rewritten to
38
+ * absolute references. This mutated form MUST be persisted and reloaded verbatim — re-running
39
+ * preprocessing on the original bundle would assign fresh random IDs and break references to
40
+ * resources that were already created.
41
+ */
42
+ bundle: Bundle;
43
+ bundleInfo: BundlePreprocessInfo;
44
+ resolvedIdentities: Record<string, string>;
45
+ /**
46
+ * Result entries produced during preprocessing itself (i.e. error responses for malformed
47
+ * entries in a non-transaction bundle), keyed by their index in the original bundle. These
48
+ * entries are never re-processed, so they are captured here rather than in per-entry progress.
49
+ */
50
+ preprocessResults: Record<number, BundleEntry>;
51
+ }
52
+
53
+ /**
54
+ * The BatchProcessor class contains the state for processing a batch/transaction bundle.
55
+ * In particular, it tracks rewritten IDs as necessary.
56
+ *
57
+ * The processor supports two modes of operation:
58
+ *
59
+ * 1. One-shot via {@link BatchProcessor.run}, used for synchronous batch/transaction requests.
60
+ * This preprocesses and then processes every entry within a single call.
61
+ *
62
+ * 2. Re-entrant, used by the async batch worker. The caller drives processing one entry at a
63
+ * time via {@link BatchProcessor.preprocess}, {@link BatchProcessor.hasMoreEntries}, and
64
+ * {@link BatchProcessor.processNextEntry}, persisting durable state ({@link BatchInitialState},
65
+ * progress marker, and per-entry results) as it goes. After a restart the processor is
66
+ * rehydrated via {@link BatchProcessor.fromState} and processing resumes from the persisted
67
+ * marker. Only `batch` bundles are re-entrant; `transaction` bundles must use {@link BatchProcessor.run}.
68
+ */
69
+ export declare class BatchProcessor {
70
+ private readonly router;
71
+ private repo;
72
+ private readonly bundle;
73
+ private readonly req;
74
+ private resolvedIdentities;
75
+ private bundleInfo;
76
+ private resultEntries;
77
+ /** Index into `bundleInfo.ordering` of the next entry to process. */
78
+ private position;
79
+ /** Indices (into the original bundle) of results produced since the last {@link BatchProcessor.takePendingResults} call. */
80
+ private pendingIndices;
81
+ /** Error messages accumulated across processed entries, for the post-batch telemetry event. */
82
+ private readonly errors;
83
+ /**
84
+ * Creates a batch processor.
85
+ * @param router - The FHIR router.
86
+ * @param repo - The FHIR repository.
87
+ * @param bundle - The input bundle.
88
+ * @param req - The request for the batch.
89
+ */
90
+ constructor(router: FhirRouter, repo: FhirRepository, bundle: Bundle, req: FhirRequest);
91
+ /**
92
+ * Rehydrates a batch processor from previously-persisted durable state so that processing can
93
+ * resume after a server restart or worker crash. Intended for the re-entrant (async batch)
94
+ * flow; the caller is responsible for loading the {@link BatchInitialState} and progress marker
95
+ * from durable storage, and for assembling the final response bundle from the durably-persisted
96
+ * result entries (this instance only tracks results produced in the current run).
97
+ * @param router - The FHIR router.
98
+ * @param repo - The FHIR repository.
99
+ * @param req - The request for the batch.
100
+ * @param initialState - The durable state produced by {@link BatchProcessor.preprocess}.
101
+ * @param position - The index into `bundleInfo.ordering` of the next entry to process.
102
+ * @returns A batch processor positioned to resume at `position`.
103
+ */
104
+ static fromState(router: FhirRouter, repo: FhirRepository, req: FhirRequest, initialState: BatchInitialState, position: number): BatchProcessor;
105
+ /**
106
+ * Processes a FHIR batch or transaction request in a single call.
107
+ * @returns The bundle response.
108
+ */
109
+ run(): Promise<Bundle>;
110
+ /**
111
+ * Validates and preprocesses the bundle, scanning entries to resolve identities, rewrite
112
+ * conditional references, and compute processing order.
113
+ *
114
+ * For the re-entrant flow, the returned {@link BatchInitialState} should be persisted to
115
+ * durable storage before processing entries so that the processor can be rehydrated via
116
+ * {@link BatchProcessor.fromState} after a restart.
117
+ * @returns The durable initial state for the batch.
118
+ */
119
+ preprocess(): Promise<BatchInitialState>;
120
+ /**
121
+ * @returns True if there are more entries to process, i.e. {@link BatchProcessor.processNextEntry} should be
122
+ * called again.
123
+ */
124
+ hasMoreEntries(): boolean;
125
+ /**
126
+ * @returns The index into `bundleInfo.ordering` of the next entry to process. This is the
127
+ * progress marker that should be persisted (together with the result entries produced so far)
128
+ * to allow resuming via {@link BatchProcessor.fromState}.
129
+ */
130
+ getPosition(): number;
131
+ /**
132
+ * Returns the result entries produced since the last call (keyed by their index in the
133
+ * original bundle) and clears the pending buffer. The caller persists these to durable
134
+ * storage as a checkpoint before advancing the durable progress marker.
135
+ * @returns Newly-produced result entries, keyed by original bundle index or `undefined`
136
+ * if no new entries were produced.
137
+ */
138
+ takePendingResults(): Record<number, BundleEntry> | undefined;
139
+ private processEntriesAndBuild;
140
+ private dispatchPreEvent;
141
+ private dispatchPostEvent;
142
+ /**
143
+ * Assembles the response bundle from the result entries recorded on this instance. Used by the
144
+ * one-shot {@link BatchProcessor.run} flow. The re-entrant flow instead assembles the final bundle from durably
145
+ * persisted results via {@link buildBatchResponseBundle}.
146
+ * @returns The bundle response.
147
+ */
148
+ private buildResultBundle;
149
+ /**
150
+ * Executes a callback with the provided repository intended for use with transaction-scoped operations.
151
+ * @param repo - The repository to use.
152
+ * @param callback - The callback to execute.
153
+ * @returns The result of the callback.
154
+ */
155
+ private withRepo;
156
+ /**
157
+ * Scans the Bundle in order to ensure entries are processed in the correct sequence,
158
+ * as well as to identify any operations that might require specific handling.
159
+ *
160
+ * @param results - The array of result entries, to track partial results.
161
+ * @returns The information gathered from scanning the Bundle.
162
+ */
163
+ private preprocessBundle;
164
+ /**
165
+ * Resolves the resource identity associated with an entry, and tracks it for later reference rewriting.
166
+ * @see https://www.hl7.org/fhir/R4/http.html#trules
167
+ *
168
+ * @param entry - The entry to resolve.
169
+ * @param index - The index of the Bundle entry.
170
+ * @param seenIdentities - The set of resolved identities that have already been seen in preprocessing.
171
+ * @returns - The (error) result for the entry, if it could not be preprocessed.
172
+ */
173
+ private preprocessEntry;
174
+ private resolveIdentity;
175
+ private getRouteForEntry;
176
+ private resolveCreateIdentity;
177
+ private resolveModificationIdentity;
178
+ /**
179
+ * Processes the next entry in the bundle ordering (the entry at the current progress marker),
180
+ * recording its result and advancing the marker.
181
+ *
182
+ * For `batch` bundles, per-entry errors are captured as error result entries and processing
183
+ * continues; a 429 (rate limit) terminates the batch, filling all remaining entries with the
184
+ * rate-limit outcome.
185
+ *
186
+ * Must be called only after {@link BatchProcessor.preprocess} or {@link BatchProcessor.fromState}.
187
+ * Callers should guard with {@link BatchProcessor.hasMoreEntries}. The re-entrant flow only supports
188
+ * `batch` semantics. For `transaction` bundles, use {@link BatchProcessor.run}.
189
+ */
190
+ processNextEntry(): Promise<void>;
191
+ /**
192
+ * Processes the next entry in the bundle ordering (the entry at the current progress marker),
193
+ * recording its result and advancing the marker.
194
+ *
195
+ * For `batch` bundles, per-entry errors are captured as error result entries and processing
196
+ * continues; a 429 (rate limit) terminates the batch, filling all remaining entries with the
197
+ * rate-limit outcome. For `transaction` bundles, any entry error is thrown so the enclosing
198
+ * transaction rolls back.
199
+ */
200
+ private processNextEntryImpl;
201
+ private setResult;
202
+ /**
203
+ * Processes a single entry from a FHIR batch request.
204
+ * @param entry - The bundle entry.
205
+ * @returns The bundle entry response.
206
+ */
207
+ private processBatchEntry;
208
+ /**
209
+ * Constructs the equivalent HTTP request for a Bundle entry, based on its `request` field.
210
+ * @param entry - The Bundle entry to parse.
211
+ * @param route - The route associated with the request.
212
+ * @returns The HTTP request to perform the operation specified by the given batch entry.
213
+ */
214
+ private parseBatchRequest;
215
+ private parsePatchBody;
216
+ private parsePatchParameter;
217
+ private rewriteIds;
218
+ private rewriteIdsInArray;
219
+ private rewriteIdsInObject;
220
+ private rewriteIdsInString;
221
+ private isTransaction;
222
+ }
223
+
224
+ /**
225
+ * Assembles a batch/transaction response bundle from result entries that were collected across
226
+ * multiple re-entrant processing runs and persisted to durable storage. Used by the async batch
227
+ * worker to build the final (or a partial) response bundle from checkpointed results.
228
+ * @param bundleType - The type of the request bundle (`batch` or `transaction`).
229
+ * @param entryCount - The number of entries in the original request bundle.
230
+ * @param results - The result entries, keyed by their index in the original request bundle.
231
+ * @returns The response bundle, with one entry per original request entry.
232
+ */
233
+ export declare function buildBatchResponseBundle(bundleType: Bundle['type'], entryCount: number, results: Record<number, BundleEntry>): Bundle;
234
+
235
+ export declare type BundlePreprocessInfo = {
236
+ ordering: number[];
237
+ requiresStrongTransaction: boolean;
238
+ updates: number;
239
+ };
240
+
23
241
  export declare function createResourceImpl<T extends Resource>(resourceType: T['resourceType'], resource: T, repo: FhirRepository, options?: CreateResourceOptions): Promise<FhirResponse>;
24
242
 
25
243
  export declare type CreateResourceOptions = {