@haste-health/client 0.15.3
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/README.md +228 -0
- package/lib/http/index.d.ts +29 -0
- package/lib/http/index.js +566 -0
- package/lib/http/index.js.map +1 -0
- package/lib/index.d.ts +39 -0
- package/lib/index.js +341 -0
- package/lib/index.js.map +1 -0
- package/lib/interface.d.ts +38 -0
- package/lib/interface.js +2 -0
- package/lib/interface.js.map +1 -0
- package/lib/middleware/index.d.ts +15 -0
- package/lib/middleware/index.js +34 -0
- package/lib/middleware/index.js.map +1 -0
- package/lib/types/index.d.ts +225 -0
- package/lib/types/index.js +2 -0
- package/lib/types/index.js.map +1 -0
- package/lib/types/r4.d.ts +257 -0
- package/lib/types/r4.js +2 -0
- package/lib/types/r4.js.map +1 -0
- package/lib/types/r4b.d.ts +257 -0
- package/lib/types/r4b.js +2 -0
- package/lib/types/r4b.js.map +1 -0
- package/lib/types/utilities.d.ts +40 -0
- package/lib/types/utilities.js +10 -0
- package/lib/types/utilities.js.map +1 -0
- package/lib/url.d.ts +51 -0
- package/lib/url.js +83 -0
- package/lib/url.js.map +1 -0
- package/package.json +45 -0
- package/src/http/index.ts +673 -0
- package/src/index.ts +555 -0
- package/src/interface.ts +203 -0
- package/src/middleware/index.test.ts +120 -0
- package/src/middleware/index.ts +95 -0
- package/src/types/index.ts +373 -0
- package/src/types/utilities.ts +62 -0
- package/src/url.test.ts +52 -0
- package/src/url.ts +141 -0
|
@@ -0,0 +1,673 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import { Bundle, id } from "@haste-health/fhir-types/r4/types";
|
|
3
|
+
import * as r4b from "@haste-health/fhir-types/r4b/types";
|
|
4
|
+
import { FHIR_VERSION, R4, R4B } from "@haste-health/fhir-types/versions";
|
|
5
|
+
import { OperationError, outcomeError } from "@haste-health/operation-outcomes";
|
|
6
|
+
|
|
7
|
+
import { AsynchronousClient } from "../index.js";
|
|
8
|
+
import { MiddlewareAsync, createMiddlewareAsync } from "../middleware/index.js";
|
|
9
|
+
import {
|
|
10
|
+
AllInteractions,
|
|
11
|
+
FHIRErrorResponse,
|
|
12
|
+
FHIRRequest,
|
|
13
|
+
FHIRResponse,
|
|
14
|
+
} from "../types/index.js";
|
|
15
|
+
import { ParsedParameter } from "../url.js";
|
|
16
|
+
|
|
17
|
+
type DeriveFHIRURL = (fhirVersion: FHIR_VERSION) => string;
|
|
18
|
+
export type HTTPClientState = {
|
|
19
|
+
authenticate?: () => void;
|
|
20
|
+
getAccessToken?: () => Promise<string>;
|
|
21
|
+
url: string | DeriveFHIRURL;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type HTTPContext = {
|
|
25
|
+
headers?: Record<string, string>;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
function parametersToQueryString(
|
|
29
|
+
parameters: ParsedParameter<string | number>[]
|
|
30
|
+
): string {
|
|
31
|
+
return parameters
|
|
32
|
+
.map((p) => {
|
|
33
|
+
const name = p.chains ? [p.name, ...p.chains].join(".") : p.name;
|
|
34
|
+
return `${name}${p.modifier ? `:${p.modifier}` : ""}=${p.value
|
|
35
|
+
.map((v) => encodeURIComponent(v))
|
|
36
|
+
.join(",")}`;
|
|
37
|
+
})
|
|
38
|
+
.join("&");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const pathJoin = (parts: string[], sep = "/") =>
|
|
42
|
+
parts.join(sep).replace(new RegExp(sep + "{1,}", "g"), sep);
|
|
43
|
+
|
|
44
|
+
function fhirUrlChunk(version: string) {
|
|
45
|
+
switch (version) {
|
|
46
|
+
case R4:
|
|
47
|
+
return "r4";
|
|
48
|
+
case R4B:
|
|
49
|
+
return "r4b";
|
|
50
|
+
default:
|
|
51
|
+
return version;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Used as default and for display purposes in admin app.
|
|
57
|
+
* @param domain HasteHealth Domain
|
|
58
|
+
* @param fhirVersion FHIRVersion
|
|
59
|
+
* @returns HasteHealth VersionedURL.
|
|
60
|
+
*/
|
|
61
|
+
export const deriveHasteHealthVersionedURL = (
|
|
62
|
+
domain: string,
|
|
63
|
+
fhirVersion: FHIR_VERSION
|
|
64
|
+
) => {
|
|
65
|
+
return new URL(
|
|
66
|
+
pathJoin([new URL(domain).pathname, `/${fhirUrlChunk(fhirVersion)}`]),
|
|
67
|
+
domain
|
|
68
|
+
).toString();
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
async function toHTTPRequest(
|
|
72
|
+
state: HTTPClientState,
|
|
73
|
+
context: HTTPContext,
|
|
74
|
+
request: FHIRRequest<FHIR_VERSION, AllInteractions>
|
|
75
|
+
): Promise<{
|
|
76
|
+
url: string;
|
|
77
|
+
headers?: Record<string, string>;
|
|
78
|
+
method: string;
|
|
79
|
+
body?: string;
|
|
80
|
+
}> {
|
|
81
|
+
const headers: Record<string, string> = {
|
|
82
|
+
"Content-Type": "application/fhir+json",
|
|
83
|
+
...context.headers,
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
let FHIRUrl =
|
|
87
|
+
typeof state.url === "string"
|
|
88
|
+
? deriveHasteHealthVersionedURL(state.url, request.fhirVersion)
|
|
89
|
+
: state.url(request.fhirVersion);
|
|
90
|
+
if (!FHIRUrl.endsWith("/")) {
|
|
91
|
+
FHIRUrl = FHIRUrl + "/";
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (state.getAccessToken) {
|
|
95
|
+
const token = await state.getAccessToken();
|
|
96
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
97
|
+
}
|
|
98
|
+
switch (request.type) {
|
|
99
|
+
case "capabilities-request": {
|
|
100
|
+
return { headers, url: new URL("metadata", FHIRUrl).href, method: "GET" };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
case "create-request": {
|
|
104
|
+
return {
|
|
105
|
+
url: new URL(request.resource, FHIRUrl).href,
|
|
106
|
+
method: "POST",
|
|
107
|
+
body: JSON.stringify(request.body),
|
|
108
|
+
headers,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
case "update-request": {
|
|
112
|
+
switch (request.level) {
|
|
113
|
+
case "instance": {
|
|
114
|
+
return {
|
|
115
|
+
url: new URL(`${request.resource}/${request.id}`, FHIRUrl).href,
|
|
116
|
+
method: "PUT",
|
|
117
|
+
body: JSON.stringify(request.body),
|
|
118
|
+
headers,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
case "type": {
|
|
122
|
+
const queryString = parametersToQueryString(request.parameters);
|
|
123
|
+
return {
|
|
124
|
+
url: new URL(
|
|
125
|
+
`${request.resource}${queryString ? `?${queryString}` : ""}`,
|
|
126
|
+
FHIRUrl
|
|
127
|
+
).href,
|
|
128
|
+
method: "PUT",
|
|
129
|
+
body: JSON.stringify(request.body),
|
|
130
|
+
headers,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
default: {
|
|
134
|
+
throw new OperationError(outcomeError("exception", "Invalid level"));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
case "patch-request": {
|
|
139
|
+
return {
|
|
140
|
+
url: new URL(`${request.resource}/${request.id}`, FHIRUrl).href,
|
|
141
|
+
method: "PATCH",
|
|
142
|
+
body: JSON.stringify(request.body),
|
|
143
|
+
headers,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
case "read-request": {
|
|
147
|
+
return {
|
|
148
|
+
url: new URL(`${request.resource}/${request.id}`, FHIRUrl).href,
|
|
149
|
+
method: "GET",
|
|
150
|
+
headers,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
case "vread-request": {
|
|
154
|
+
return {
|
|
155
|
+
url: new URL(
|
|
156
|
+
`${request.resource}/${request.id}/_history/${request.versionId}`,
|
|
157
|
+
FHIRUrl
|
|
158
|
+
).href,
|
|
159
|
+
method: "GET",
|
|
160
|
+
headers,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
case "delete-request": {
|
|
164
|
+
switch (request.level) {
|
|
165
|
+
case "instance": {
|
|
166
|
+
return {
|
|
167
|
+
url: new URL(`${request.resource}/${request.id}`, FHIRUrl).href,
|
|
168
|
+
method: "DELETE",
|
|
169
|
+
headers,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
case "type": {
|
|
173
|
+
const queryString = parametersToQueryString(request.parameters);
|
|
174
|
+
return {
|
|
175
|
+
url: new URL(
|
|
176
|
+
`${request.resource}${queryString ? `?${queryString}` : ""}`,
|
|
177
|
+
FHIRUrl
|
|
178
|
+
).href,
|
|
179
|
+
method: "DELETE",
|
|
180
|
+
headers,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
case "system": {
|
|
184
|
+
const queryString = parametersToQueryString(request.parameters);
|
|
185
|
+
return {
|
|
186
|
+
url: new URL(`${queryString ? `?${queryString}` : ""}`, FHIRUrl)
|
|
187
|
+
.href,
|
|
188
|
+
method: "DELETE",
|
|
189
|
+
headers,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
throw new OperationError(outcomeError("exception", "Invalid level"));
|
|
194
|
+
}
|
|
195
|
+
case "history-request": {
|
|
196
|
+
let historyUrl;
|
|
197
|
+
const queryString = parametersToQueryString(request.parameters || []);
|
|
198
|
+
switch (request.level) {
|
|
199
|
+
case "instance": {
|
|
200
|
+
historyUrl = new URL(
|
|
201
|
+
`${request.resource}/${request.id}/_history`,
|
|
202
|
+
FHIRUrl
|
|
203
|
+
).href;
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
case "type": {
|
|
207
|
+
historyUrl = new URL(`${request.resource}/_history`, FHIRUrl).href;
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
case "system": {
|
|
211
|
+
historyUrl = new URL(`_history`, FHIRUrl).href;
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
url: new URL(`${queryString ? `?${queryString}` : ""}`, historyUrl)
|
|
218
|
+
.href,
|
|
219
|
+
method: "GET",
|
|
220
|
+
headers,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
case "batch-request":
|
|
225
|
+
case "transaction-request": {
|
|
226
|
+
return {
|
|
227
|
+
url: FHIRUrl,
|
|
228
|
+
method: "POST",
|
|
229
|
+
body: JSON.stringify(request.body),
|
|
230
|
+
headers,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
case "search-request": {
|
|
234
|
+
const queryString = parametersToQueryString(request.parameters);
|
|
235
|
+
let searchURL;
|
|
236
|
+
switch (request.level) {
|
|
237
|
+
case "type":
|
|
238
|
+
searchURL = new URL(
|
|
239
|
+
`${request.resource}${queryString ? `?${queryString}` : ""}`,
|
|
240
|
+
FHIRUrl
|
|
241
|
+
).href;
|
|
242
|
+
break;
|
|
243
|
+
case "system":
|
|
244
|
+
searchURL = new URL(
|
|
245
|
+
`${queryString ? `?${queryString}` : ""}`,
|
|
246
|
+
FHIRUrl
|
|
247
|
+
).href;
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return {
|
|
252
|
+
url: searchURL,
|
|
253
|
+
method: "GET",
|
|
254
|
+
headers,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
case "invoke-request": {
|
|
259
|
+
let invokeURL;
|
|
260
|
+
switch (request.level) {
|
|
261
|
+
case "instance":
|
|
262
|
+
invokeURL = new URL(
|
|
263
|
+
`${request.resource}/${request.id}/$${request.operation}`,
|
|
264
|
+
FHIRUrl
|
|
265
|
+
).href;
|
|
266
|
+
break;
|
|
267
|
+
case "type":
|
|
268
|
+
invokeURL = new URL(
|
|
269
|
+
`${request.resource}/$${request.operation}`,
|
|
270
|
+
FHIRUrl
|
|
271
|
+
).href;
|
|
272
|
+
break;
|
|
273
|
+
case "system":
|
|
274
|
+
invokeURL = new URL(`$${request.operation}`, FHIRUrl).href;
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
return {
|
|
278
|
+
url: invokeURL,
|
|
279
|
+
method: "POST",
|
|
280
|
+
body: JSON.stringify(request.body),
|
|
281
|
+
headers,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export class ResponseError<Version extends FHIR_VERSION> extends Error {
|
|
288
|
+
private readonly _request: FHIRRequest<Version, AllInteractions>;
|
|
289
|
+
private readonly _response: FHIRResponse<Version, "error">;
|
|
290
|
+
constructor(
|
|
291
|
+
request: FHIRRequest<Version, AllInteractions>,
|
|
292
|
+
response: FHIRErrorResponse<Version>
|
|
293
|
+
) {
|
|
294
|
+
super();
|
|
295
|
+
this._request = request;
|
|
296
|
+
this._response = response;
|
|
297
|
+
}
|
|
298
|
+
get response() {
|
|
299
|
+
return this._response;
|
|
300
|
+
}
|
|
301
|
+
get request() {
|
|
302
|
+
return this._request;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export function isResponseError(e: unknown): e is ResponseError<FHIR_VERSION> {
|
|
307
|
+
return e instanceof ResponseError;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
async function httpResponseToFHIRResponse<Version extends FHIR_VERSION>(
|
|
311
|
+
request: FHIRRequest<Version, AllInteractions>,
|
|
312
|
+
response: Response
|
|
313
|
+
): Promise<FHIRResponse<Version, AllInteractions>> {
|
|
314
|
+
if (response.status >= 400) {
|
|
315
|
+
switch (response.status) {
|
|
316
|
+
case 401: {
|
|
317
|
+
throw new ResponseError(request, {
|
|
318
|
+
fhirVersion: request.fhirVersion,
|
|
319
|
+
level: request.level,
|
|
320
|
+
type: "error-response",
|
|
321
|
+
body: outcomeError("login", "Unauthorized") as any,
|
|
322
|
+
http: {
|
|
323
|
+
status: response.status,
|
|
324
|
+
headers: Object.fromEntries(response.headers),
|
|
325
|
+
},
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
case 403: {
|
|
329
|
+
throw new ResponseError(request, {
|
|
330
|
+
fhirVersion: request.fhirVersion,
|
|
331
|
+
level: request.level,
|
|
332
|
+
type: "error-response",
|
|
333
|
+
body: outcomeError("forbidden", "Forbidden") as any,
|
|
334
|
+
http: {
|
|
335
|
+
status: response.status,
|
|
336
|
+
headers: Object.fromEntries(response.headers),
|
|
337
|
+
},
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
default: {
|
|
341
|
+
if (!response.body) throw new Error(response.statusText);
|
|
342
|
+
const oo = await response.json();
|
|
343
|
+
|
|
344
|
+
if (!("resourceType" in oo) || oo.resourceType !== "OperationOutcome") {
|
|
345
|
+
throw new Error(response.statusText);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
throw new ResponseError(request, {
|
|
349
|
+
fhirVersion: request.fhirVersion,
|
|
350
|
+
level: request.level,
|
|
351
|
+
type: "error-response",
|
|
352
|
+
body: oo,
|
|
353
|
+
http: {
|
|
354
|
+
status: response.status,
|
|
355
|
+
headers: Object.fromEntries(response.headers),
|
|
356
|
+
},
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
switch (request.type) {
|
|
362
|
+
case "invoke-request": {
|
|
363
|
+
if (!response.body)
|
|
364
|
+
throw new OperationError(outcomeError("exception", "No response body"));
|
|
365
|
+
const parameters = await response.json();
|
|
366
|
+
switch (request.level) {
|
|
367
|
+
case "system": {
|
|
368
|
+
return {
|
|
369
|
+
fhirVersion: request.fhirVersion,
|
|
370
|
+
type: "invoke-response",
|
|
371
|
+
operation: request.operation,
|
|
372
|
+
level: "system",
|
|
373
|
+
body: parameters,
|
|
374
|
+
} as FHIRResponse<Version, "invoke">;
|
|
375
|
+
}
|
|
376
|
+
case "type": {
|
|
377
|
+
return {
|
|
378
|
+
fhirVersion: request.fhirVersion,
|
|
379
|
+
type: "invoke-response",
|
|
380
|
+
operation: request.operation,
|
|
381
|
+
level: "type",
|
|
382
|
+
resource: request.resource,
|
|
383
|
+
body: parameters,
|
|
384
|
+
} as FHIRResponse<Version, "invoke">;
|
|
385
|
+
}
|
|
386
|
+
case "instance": {
|
|
387
|
+
return {
|
|
388
|
+
fhirVersion: request.fhirVersion,
|
|
389
|
+
type: "invoke-response",
|
|
390
|
+
operation: request.operation,
|
|
391
|
+
level: "instance",
|
|
392
|
+
resource: request.resource,
|
|
393
|
+
id: request.id,
|
|
394
|
+
body: parameters,
|
|
395
|
+
} as FHIRResponse<Version, "invoke">;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
throw new OperationError(outcomeError("exception", "Invalid level"));
|
|
399
|
+
}
|
|
400
|
+
case "read-request": {
|
|
401
|
+
if (!response.body)
|
|
402
|
+
throw new OperationError(outcomeError("exception", "No response body"));
|
|
403
|
+
const resource = await response.json();
|
|
404
|
+
return {
|
|
405
|
+
fhirVersion: request.fhirVersion,
|
|
406
|
+
level: "instance",
|
|
407
|
+
type: "read-response",
|
|
408
|
+
resource: request.resource,
|
|
409
|
+
id: request.id,
|
|
410
|
+
body: resource,
|
|
411
|
+
} as FHIRResponse<Version, "read">;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
case "vread-request": {
|
|
415
|
+
if (!response.body)
|
|
416
|
+
throw new OperationError(outcomeError("exception", "No response body"));
|
|
417
|
+
const vresource = await response.json();
|
|
418
|
+
return {
|
|
419
|
+
fhirVersion: request.fhirVersion,
|
|
420
|
+
level: "instance",
|
|
421
|
+
type: "vread-response",
|
|
422
|
+
resource: request.resource,
|
|
423
|
+
id: request.id,
|
|
424
|
+
versionId: request.versionId,
|
|
425
|
+
body: vresource,
|
|
426
|
+
} as FHIRResponse<Version, "vread">;
|
|
427
|
+
}
|
|
428
|
+
case "update-request": {
|
|
429
|
+
if (!response.body)
|
|
430
|
+
throw new OperationError(outcomeError("exception", "No response body"));
|
|
431
|
+
const uresource = await response.json();
|
|
432
|
+
|
|
433
|
+
switch (request.level) {
|
|
434
|
+
case "instance": {
|
|
435
|
+
return {
|
|
436
|
+
fhirVersion: request.fhirVersion,
|
|
437
|
+
type: "update-response",
|
|
438
|
+
level: "instance",
|
|
439
|
+
resource: request.resource,
|
|
440
|
+
id: request.id,
|
|
441
|
+
body: uresource,
|
|
442
|
+
} as FHIRResponse<Version, "update">;
|
|
443
|
+
}
|
|
444
|
+
case "type": {
|
|
445
|
+
const location = response.headers.get("Location") ?? "";
|
|
446
|
+
const parts = location.split("/");
|
|
447
|
+
return {
|
|
448
|
+
fhirVersion: request.fhirVersion,
|
|
449
|
+
type: "update-response",
|
|
450
|
+
level: "instance",
|
|
451
|
+
resource: request.resource,
|
|
452
|
+
id: parts[parts.length - 1] as id,
|
|
453
|
+
body: uresource,
|
|
454
|
+
} as FHIRResponse<Version, "update">;
|
|
455
|
+
}
|
|
456
|
+
default: {
|
|
457
|
+
throw new OperationError(outcomeError("exception", "Invalid level"));
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
case "patch-request": {
|
|
462
|
+
if (!response.body)
|
|
463
|
+
throw new OperationError(outcomeError("exception", "No response body"));
|
|
464
|
+
const presource = await response.json();
|
|
465
|
+
return {
|
|
466
|
+
fhirVersion: request.fhirVersion,
|
|
467
|
+
type: "patch-response",
|
|
468
|
+
level: "instance",
|
|
469
|
+
resource: request.resource,
|
|
470
|
+
id: request.id,
|
|
471
|
+
body: presource,
|
|
472
|
+
} as FHIRResponse<Version, "patch">;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
case "delete-request": {
|
|
476
|
+
switch (request.level) {
|
|
477
|
+
case "instance": {
|
|
478
|
+
return {
|
|
479
|
+
fhirVersion: request.fhirVersion,
|
|
480
|
+
type: "delete-response",
|
|
481
|
+
level: "instance",
|
|
482
|
+
resource: request.resource,
|
|
483
|
+
id: request.id,
|
|
484
|
+
} as FHIRResponse<Version, "delete">;
|
|
485
|
+
}
|
|
486
|
+
case "type": {
|
|
487
|
+
return {
|
|
488
|
+
fhirVersion: request.fhirVersion,
|
|
489
|
+
type: "delete-response",
|
|
490
|
+
level: "type",
|
|
491
|
+
resource: request.resource,
|
|
492
|
+
parameters: request.parameters,
|
|
493
|
+
} as FHIRResponse<Version, "delete">;
|
|
494
|
+
}
|
|
495
|
+
case "system": {
|
|
496
|
+
return {
|
|
497
|
+
fhirVersion: request.fhirVersion,
|
|
498
|
+
type: "delete-response",
|
|
499
|
+
level: "system",
|
|
500
|
+
parameters: request.parameters,
|
|
501
|
+
} as FHIRResponse<Version, "delete">;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
throw new OperationError(outcomeError("exception", "Invalid level"));
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
case "history-request": {
|
|
508
|
+
if (!response.body)
|
|
509
|
+
throw new OperationError(outcomeError("exception", "No response body"));
|
|
510
|
+
|
|
511
|
+
const bundle = (await response.json()) as Bundle | r4b.Bundle;
|
|
512
|
+
switch (request.level) {
|
|
513
|
+
case "system": {
|
|
514
|
+
return {
|
|
515
|
+
fhirVersion: request.fhirVersion,
|
|
516
|
+
type: "history-response",
|
|
517
|
+
level: "system",
|
|
518
|
+
body: bundle,
|
|
519
|
+
} as FHIRResponse<Version, "history">;
|
|
520
|
+
}
|
|
521
|
+
case "type": {
|
|
522
|
+
return {
|
|
523
|
+
fhirVersion: request.fhirVersion,
|
|
524
|
+
type: "history-response",
|
|
525
|
+
level: "type",
|
|
526
|
+
resource: request.resource,
|
|
527
|
+
body: bundle,
|
|
528
|
+
} as FHIRResponse<Version, "history">;
|
|
529
|
+
}
|
|
530
|
+
case "instance": {
|
|
531
|
+
return {
|
|
532
|
+
fhirVersion: request.fhirVersion,
|
|
533
|
+
type: "history-response",
|
|
534
|
+
level: "instance",
|
|
535
|
+
resource: request.resource,
|
|
536
|
+
id: request.id,
|
|
537
|
+
body: bundle,
|
|
538
|
+
} as FHIRResponse<Version, "history">;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
throw new OperationError(outcomeError("exception", "Invalid level"));
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
case "create-request": {
|
|
545
|
+
if (!response.body)
|
|
546
|
+
throw new OperationError(outcomeError("exception", "No response body"));
|
|
547
|
+
const resource = await response.json();
|
|
548
|
+
return {
|
|
549
|
+
fhirVersion: request.fhirVersion,
|
|
550
|
+
type: "create-response",
|
|
551
|
+
level: "type",
|
|
552
|
+
resource: request.resource,
|
|
553
|
+
body: resource,
|
|
554
|
+
} as FHIRResponse<Version, "create">;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
case "search-request": {
|
|
558
|
+
if (!response.body)
|
|
559
|
+
throw new OperationError(outcomeError("exception", "No response body"));
|
|
560
|
+
const bundle = (await response.json()) as Bundle | r4b.Bundle;
|
|
561
|
+
switch (request.level) {
|
|
562
|
+
case "system": {
|
|
563
|
+
return {
|
|
564
|
+
fhirVersion: request.fhirVersion,
|
|
565
|
+
type: "search-response",
|
|
566
|
+
level: "system",
|
|
567
|
+
parameters: request.parameters,
|
|
568
|
+
body: bundle,
|
|
569
|
+
} as FHIRResponse<Version, "search">;
|
|
570
|
+
}
|
|
571
|
+
case "type": {
|
|
572
|
+
return {
|
|
573
|
+
fhirVersion: request.fhirVersion,
|
|
574
|
+
type: "search-response",
|
|
575
|
+
level: "type",
|
|
576
|
+
parameters: request.parameters,
|
|
577
|
+
resource: request.resource,
|
|
578
|
+
body: bundle,
|
|
579
|
+
} as FHIRResponse<Version, "search">;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
throw new OperationError(outcomeError("exception", "Invalid level"));
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
case "capabilities-request": {
|
|
586
|
+
if (!response.body)
|
|
587
|
+
throw new OperationError(outcomeError("exception", "No response body"));
|
|
588
|
+
const capabilities = await response.json();
|
|
589
|
+
return {
|
|
590
|
+
fhirVersion: request.fhirVersion,
|
|
591
|
+
level: "system",
|
|
592
|
+
type: "capabilities-response",
|
|
593
|
+
body: capabilities,
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
case "batch-request": {
|
|
598
|
+
if (!response.body)
|
|
599
|
+
throw new OperationError(outcomeError("exception", "No response body"));
|
|
600
|
+
const batch = await response.json();
|
|
601
|
+
return {
|
|
602
|
+
fhirVersion: request.fhirVersion,
|
|
603
|
+
type: "batch-response",
|
|
604
|
+
level: "system",
|
|
605
|
+
body: batch,
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
case "transaction-request": {
|
|
610
|
+
if (!response.body)
|
|
611
|
+
throw new OperationError(outcomeError("exception", "No response body"));
|
|
612
|
+
const transaction = await response.json();
|
|
613
|
+
return {
|
|
614
|
+
fhirVersion: request.fhirVersion,
|
|
615
|
+
type: "transaction-response",
|
|
616
|
+
level: "system",
|
|
617
|
+
body: transaction,
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
function httpMiddleware(state: HTTPClientState): MiddlewareAsync<HTTPContext> {
|
|
624
|
+
return createMiddlewareAsync<HTTPClientState, HTTPContext>(state, [
|
|
625
|
+
async (state, context) => {
|
|
626
|
+
try {
|
|
627
|
+
const httpRequest = await toHTTPRequest(
|
|
628
|
+
state,
|
|
629
|
+
context.ctx,
|
|
630
|
+
context.request
|
|
631
|
+
);
|
|
632
|
+
const response = await fetch(httpRequest.url, {
|
|
633
|
+
method: httpRequest.method,
|
|
634
|
+
headers: httpRequest.headers,
|
|
635
|
+
body: httpRequest.body,
|
|
636
|
+
});
|
|
637
|
+
const fhirResponse = await httpResponseToFHIRResponse(
|
|
638
|
+
context.request,
|
|
639
|
+
response
|
|
640
|
+
);
|
|
641
|
+
fhirResponse.http = {
|
|
642
|
+
status: response.status,
|
|
643
|
+
headers: Object.fromEntries(response.headers),
|
|
644
|
+
};
|
|
645
|
+
return [
|
|
646
|
+
state,
|
|
647
|
+
{
|
|
648
|
+
...context,
|
|
649
|
+
|
|
650
|
+
response: fhirResponse,
|
|
651
|
+
},
|
|
652
|
+
];
|
|
653
|
+
} catch (e) {
|
|
654
|
+
if (isResponseError(e)) {
|
|
655
|
+
if (e.response.body.issue[0].code === "login") {
|
|
656
|
+
if (state.authenticate) {
|
|
657
|
+
state.authenticate();
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
throw e;
|
|
662
|
+
}
|
|
663
|
+
},
|
|
664
|
+
]);
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
export default function createHTTPClient(
|
|
668
|
+
initialState: HTTPClientState
|
|
669
|
+
): AsynchronousClient<HTTPContext> {
|
|
670
|
+
// Removing trailing slash
|
|
671
|
+
const middleware = httpMiddleware(initialState);
|
|
672
|
+
return new AsynchronousClient<HTTPContext>(middleware);
|
|
673
|
+
}
|