@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.
@@ -0,0 +1,373 @@
1
+ import { code, id } from "@haste-health/fhir-types/r4/types";
2
+ import {
3
+ AllResourceTypes,
4
+ FHIR_VERSION,
5
+ Resource,
6
+ ResourceType,
7
+ } from "@haste-health/fhir-types/versions";
8
+
9
+ import type { Parameters } from "../url.js";
10
+ import {
11
+ Interaction,
12
+ Request,
13
+ RequestLevel,
14
+ RequestType,
15
+ ResponseType,
16
+ } from "./utilities.js";
17
+
18
+ export * from "./utilities.js";
19
+
20
+ export interface InstanceInteraction<Version extends FHIR_VERSION>
21
+ extends Request<Version, "instance"> {
22
+ resource: ResourceType<Version>;
23
+ id: id;
24
+ }
25
+
26
+ export interface TypeInteraction<Version extends FHIR_VERSION>
27
+ extends Request<Version, "type"> {
28
+ resource: ResourceType<Version>;
29
+ }
30
+
31
+ export interface SystemInteraction<Version extends FHIR_VERSION>
32
+ extends Request<Version, "system"> {}
33
+
34
+ export interface ReadRequest<Version extends FHIR_VERSION>
35
+ extends InstanceInteraction<Version> {
36
+ type: RequestType["read"];
37
+ }
38
+
39
+ export interface VersionReadRequest<Version extends FHIR_VERSION>
40
+ extends InstanceInteraction<Version> {
41
+ type: RequestType["vread"];
42
+ versionId: string;
43
+ }
44
+
45
+ export interface InstanceUpdateRequest<Version extends FHIR_VERSION>
46
+ extends InstanceInteraction<Version> {
47
+ type: RequestType["update"];
48
+ body: Resource<Version, ResourceType<Version>>;
49
+ }
50
+
51
+ // TODO - implement patch type
52
+ export interface PatchRequest<Version extends FHIR_VERSION>
53
+ extends InstanceInteraction<Version> {
54
+ type: RequestType["patch"];
55
+ body: unknown;
56
+ }
57
+
58
+ export interface InstanceDeleteRequest<Version extends FHIR_VERSION>
59
+ extends InstanceInteraction<Version> {
60
+ type: RequestType["delete"];
61
+ }
62
+
63
+ export interface TypeDeleteRequest<Version extends FHIR_VERSION>
64
+ extends TypeInteraction<Version> {
65
+ parameters: Parameters<Version>;
66
+ type: RequestType["delete"];
67
+ }
68
+
69
+ export interface SystemDeleteRequest<Version extends FHIR_VERSION>
70
+ extends SystemInteraction<Version> {
71
+ parameters: Parameters<Version>;
72
+ type: RequestType["delete"];
73
+ }
74
+
75
+ export interface HistoryInstanceRequest<Version extends FHIR_VERSION>
76
+ extends InstanceInteraction<Version> {
77
+ type: RequestType["history"];
78
+ parameters?: Parameters<Version>;
79
+ }
80
+
81
+ export interface CreateRequest<Version extends FHIR_VERSION>
82
+ extends TypeInteraction<Version> {
83
+ type: RequestType["create"];
84
+ body: Resource<Version, ResourceType<Version>>;
85
+ }
86
+
87
+ export interface ConditionalUpdateRequest<Version extends FHIR_VERSION>
88
+ extends TypeInteraction<Version> {
89
+ type: RequestType["update"];
90
+ parameters: Parameters<Version>;
91
+ body: Resource<Version, ResourceType<Version>>;
92
+ }
93
+
94
+ export interface TypeSearchRequest<Version extends FHIR_VERSION>
95
+ extends TypeInteraction<Version> {
96
+ parameters: Parameters<Version>;
97
+ type: RequestType["search"];
98
+ }
99
+
100
+ export interface TypeHistoryRequest<Version extends FHIR_VERSION>
101
+ extends TypeInteraction<Version> {
102
+ type: RequestType["history"];
103
+ parameters?: Parameters<Version>;
104
+ }
105
+
106
+ export interface CapabilitiesRequest<Version extends FHIR_VERSION>
107
+ extends SystemInteraction<Version> {
108
+ type: RequestType["capabilities"];
109
+ }
110
+
111
+ export interface BatchRequest<Version extends FHIR_VERSION>
112
+ extends SystemInteraction<Version> {
113
+ type: RequestType["batch"];
114
+ body: Resource<Version, "Bundle">;
115
+ }
116
+
117
+ export interface TransactionRequest<Version extends FHIR_VERSION>
118
+ extends SystemInteraction<Version> {
119
+ type: RequestType["transaction"];
120
+ body: Resource<Version, "Bundle">;
121
+ }
122
+
123
+ export interface SystemHistoryRequest<Version extends FHIR_VERSION>
124
+ extends SystemInteraction<Version> {
125
+ type: RequestType["history"];
126
+ parameters?: Parameters<Version>;
127
+ }
128
+
129
+ export interface SystemSearchRequest<Version extends FHIR_VERSION>
130
+ extends SystemInteraction<Version> {
131
+ parameters: Parameters<Version>;
132
+ type: RequestType["search"];
133
+ }
134
+
135
+ export interface InvokeInstanceRequest<Version extends FHIR_VERSION>
136
+ extends InstanceInteraction<Version> {
137
+ type: RequestType["invoke"];
138
+ operation: code;
139
+ body: Resource<Version, "Parameters">;
140
+ }
141
+
142
+ export interface InvokeTypeRequest<Version extends FHIR_VERSION>
143
+ extends TypeInteraction<Version> {
144
+ type: RequestType["invoke"];
145
+ operation: code;
146
+ body: Resource<Version, "Parameters">;
147
+ }
148
+
149
+ export interface InvokeSystemRequest<Version extends FHIR_VERSION>
150
+ extends SystemInteraction<Version> {
151
+ type: RequestType["invoke"];
152
+ operation: code;
153
+ body: Resource<Version, "Parameters">;
154
+ }
155
+
156
+ export interface ReadResponse<Version extends FHIR_VERSION>
157
+ extends InstanceInteraction<Version> {
158
+ type: ResponseType["read"];
159
+ body: Resource<Version, ResourceType<Version>>;
160
+ }
161
+
162
+ export interface VersionReadResponse<Version extends FHIR_VERSION>
163
+ extends InstanceInteraction<Version> {
164
+ type: ResponseType["vread"];
165
+ versionId: string;
166
+ body: Resource<Version, ResourceType<Version>>;
167
+ }
168
+
169
+ export interface UpdateResponse<Version extends FHIR_VERSION>
170
+ extends InstanceInteraction<Version> {
171
+ type: ResponseType["update"];
172
+ created?: boolean;
173
+ body: Resource<Version, ResourceType<Version>>;
174
+ }
175
+
176
+ // TODO - implement patch type
177
+ export interface PatchResponse<Version extends FHIR_VERSION>
178
+ extends InstanceInteraction<Version> {
179
+ type: ResponseType["patch"];
180
+ body: Resource<Version, ResourceType<Version>>;
181
+ }
182
+
183
+ export interface InstanceDeleteResponse<Version extends FHIR_VERSION>
184
+ extends InstanceInteraction<Version> {
185
+ type: ResponseType["delete"];
186
+ deletion?: Resource<Version, AllResourceTypes>;
187
+ }
188
+
189
+ export interface TypeDeleteResponse<Version extends FHIR_VERSION>
190
+ extends TypeInteraction<Version> {
191
+ parameters: Parameters<Version>;
192
+ type: ResponseType["delete"];
193
+ // For conditional deletes include the resources that were deleted.
194
+ deletion?: Resource<Version, AllResourceTypes>[];
195
+ }
196
+
197
+ export interface SystemDeleteResponse<Version extends FHIR_VERSION>
198
+ extends SystemInteraction<Version> {
199
+ parameters: Parameters<Version>;
200
+ type: ResponseType["delete"];
201
+ // For conditional deletes include the resources that were deleted.
202
+ deletion?: Resource<Version, AllResourceTypes>[];
203
+ }
204
+
205
+ export interface InstanceHistoryResponse<Version extends FHIR_VERSION>
206
+ extends InstanceInteraction<Version> {
207
+ type: ResponseType["history"];
208
+ body: Resource<Version, "Bundle">;
209
+ }
210
+
211
+ export interface CreateResponse<Version extends FHIR_VERSION>
212
+ extends TypeInteraction<Version> {
213
+ type: ResponseType["create"];
214
+ body: Resource<Version, ResourceType<Version>>;
215
+ }
216
+
217
+ export interface TypeSearchResponse<Version extends FHIR_VERSION>
218
+ extends TypeInteraction<Version> {
219
+ parameters: Parameters<Version>;
220
+ type: ResponseType["search"];
221
+ body: Resource<Version, "Bundle">;
222
+ }
223
+
224
+ export interface TypeHistoryResponse<Version extends FHIR_VERSION>
225
+ extends TypeInteraction<Version> {
226
+ type: ResponseType["history"];
227
+ body: Resource<Version, "Bundle">;
228
+ }
229
+
230
+ export interface CapabilitiesResponse<Version extends FHIR_VERSION>
231
+ extends SystemInteraction<Version> {
232
+ type: ResponseType["capabilities"];
233
+ body: Resource<Version, "CapabilityStatement">;
234
+ }
235
+
236
+ export interface BatchResponse<Version extends FHIR_VERSION>
237
+ extends SystemInteraction<Version> {
238
+ type: ResponseType["batch"];
239
+ body: Resource<Version, "Bundle">;
240
+ }
241
+
242
+ export interface TransactionResponse<Version extends FHIR_VERSION>
243
+ extends SystemInteraction<Version> {
244
+ type: ResponseType["transaction"];
245
+ body: Resource<Version, "Bundle">;
246
+ }
247
+
248
+ export interface SystemHistoryResponse<Version extends FHIR_VERSION>
249
+ extends SystemInteraction<Version> {
250
+ type: ResponseType["history"];
251
+ body: Resource<Version, "Bundle">;
252
+ }
253
+
254
+ export interface SystemSearchResponse<Version extends FHIR_VERSION>
255
+ extends SystemInteraction<Version> {
256
+ parameters: Parameters<Version>;
257
+ type: ResponseType["search"];
258
+ body: Resource<Version, "Bundle">;
259
+ }
260
+
261
+ export interface InvokeInstanceResponse<Version extends FHIR_VERSION>
262
+ extends InstanceInteraction<Version> {
263
+ type: ResponseType["invoke"];
264
+ operation: code;
265
+ body: Resource<Version, "Parameters">;
266
+ }
267
+
268
+ export interface InvokeTypeResponse<Version extends FHIR_VERSION>
269
+ extends TypeInteraction<Version> {
270
+ type: ResponseType["invoke"];
271
+ operation: code;
272
+ body: Resource<Version, "Parameters">;
273
+ }
274
+
275
+ export interface InvokeSystemResponse<Version extends FHIR_VERSION>
276
+ extends SystemInteraction<Version> {
277
+ type: ResponseType["invoke"];
278
+ operation: code;
279
+ body: Resource<Version, "Parameters">;
280
+ }
281
+
282
+ export type InvokeResponse<Version extends FHIR_VERSION> =
283
+ | InvokeInstanceResponse<Version>
284
+ | InvokeTypeResponse<Version>
285
+ | InvokeSystemResponse<Version>;
286
+
287
+ export type InvokeRequest<Version extends FHIR_VERSION> =
288
+ | InvokeInstanceRequest<Version>
289
+ | InvokeTypeRequest<Version>
290
+ | InvokeSystemRequest<Version>;
291
+
292
+ export type DeleteRequest<Version extends FHIR_VERSION> =
293
+ | InstanceDeleteRequest<Version>
294
+ | TypeDeleteRequest<Version>
295
+ | SystemDeleteRequest<Version>;
296
+ export type DeleteResponse<Version extends FHIR_VERSION> =
297
+ | InstanceDeleteResponse<Version>
298
+ | TypeDeleteResponse<Version>
299
+ | SystemDeleteResponse<Version>;
300
+
301
+ export type HistoryRequest<Version extends FHIR_VERSION> =
302
+ | HistoryInstanceRequest<Version>
303
+ | TypeHistoryRequest<Version>
304
+ | SystemHistoryRequest<Version>;
305
+ export type HistoryResponse<Version extends FHIR_VERSION> =
306
+ | InstanceHistoryResponse<Version>
307
+ | TypeHistoryResponse<Version>
308
+ | SystemHistoryResponse<Version>;
309
+
310
+ export type SearchRequest<Version extends FHIR_VERSION> =
311
+ | TypeSearchRequest<Version>
312
+ | SystemSearchRequest<Version>;
313
+ export type SearchResponse<Version extends FHIR_VERSION> =
314
+ | TypeSearchResponse<Version>
315
+ | SystemSearchResponse<Version>;
316
+
317
+ export type UpdateRequest<Version extends FHIR_VERSION> =
318
+ | InstanceUpdateRequest<Version>
319
+ | ConditionalUpdateRequest<Version>;
320
+
321
+ interface ErrorResponse<
322
+ Version extends FHIR_VERSION,
323
+ Level extends keyof RequestLevel
324
+ > extends Request<Version, Level> {
325
+ type: ResponseType["error"];
326
+ body: Resource<Version, "OperationOutcome">;
327
+ }
328
+
329
+ export type FHIRErrorResponse<Version extends FHIR_VERSION> =
330
+ | ErrorResponse<Version, "system">
331
+ | ErrorResponse<Version, "type">
332
+ | ErrorResponse<Version, "instance">;
333
+
334
+ type InteractionToRequest<Version extends FHIR_VERSION> = {
335
+ invoke: InvokeRequest<Version>;
336
+ read: ReadRequest<Version>;
337
+ vread: VersionReadRequest<Version>;
338
+ update: UpdateRequest<Version>;
339
+ patch: PatchRequest<Version>;
340
+ delete: DeleteRequest<Version>;
341
+ history: HistoryRequest<Version>;
342
+ create: CreateRequest<Version>;
343
+ search: SearchRequest<Version>;
344
+ capabilities: CapabilitiesRequest<Version>;
345
+ batch: BatchRequest<Version>;
346
+ transaction: TransactionRequest<Version>;
347
+ };
348
+
349
+ export type FHIRRequest<
350
+ Version extends FHIR_VERSION,
351
+ I extends Interaction[keyof Interaction]
352
+ > = InteractionToRequest<Version>[I];
353
+
354
+ type InteractionToResponse<Version extends FHIR_VERSION> = {
355
+ error: FHIRErrorResponse<Version>;
356
+ invoke: InvokeResponse<Version>;
357
+ read: ReadResponse<Version>;
358
+ vread: VersionReadResponse<Version>;
359
+ update: UpdateResponse<Version>;
360
+ patch: PatchResponse<Version>;
361
+ history: HistoryResponse<Version>;
362
+ delete: DeleteResponse<Version>;
363
+ create: CreateResponse<Version>;
364
+ capabilities: CapabilitiesResponse<Version>;
365
+ batch: BatchResponse<Version>;
366
+ transaction: TransactionResponse<Version>;
367
+ search: SearchResponse<Version>;
368
+ };
369
+
370
+ export type FHIRResponse<
371
+ Version extends FHIR_VERSION,
372
+ I extends Interaction[keyof Interaction] | "error"
373
+ > = InteractionToResponse<Version>[I];
@@ -0,0 +1,62 @@
1
+ import { FHIR_VERSIONS_SUPPORTED } from "@haste-health/fhir-types/versions";
2
+
3
+ export type RequestLevel = {
4
+ instance: "instance";
5
+ system: "system";
6
+ type: "type";
7
+ };
8
+
9
+ export type Interaction = {
10
+ read: "read";
11
+ vread: "vread";
12
+ update: "update";
13
+ patch: "patch";
14
+ delete: "delete";
15
+ history: "history";
16
+ create: "create";
17
+ search: "search";
18
+ capabilities: "capabilities";
19
+ batch: "batch";
20
+ transaction: "transaction";
21
+ invoke: "invoke";
22
+ };
23
+
24
+ export function toInteraction<I extends AllInteractions>(
25
+ r: RequestType[I] | ResponseType[I]
26
+ ): I {
27
+ return r.split("-")[0] as I;
28
+ }
29
+
30
+ export type AllInteractions = Interaction[keyof Interaction];
31
+
32
+ export type RequestType = {
33
+ [I in Interaction[keyof Interaction]]: `${I}-request`;
34
+ };
35
+
36
+ export type ResponseType = {
37
+ [I in Interaction[keyof Interaction]]: `${I}-response`;
38
+ } & { error: "error-response" };
39
+
40
+ export function RequestType<I extends Interaction[keyof Interaction]>(
41
+ interaction: I
42
+ ): RequestType[I] {
43
+ return `${interaction}-request` as RequestType[I];
44
+ }
45
+
46
+ export function ResponseType<
47
+ I extends Interaction[keyof Interaction] | "error"
48
+ >(interaction: I): ResponseType[I] {
49
+ return `${interaction}-response` as ResponseType[I];
50
+ }
51
+
52
+ export type Request<
53
+ Version extends (typeof FHIR_VERSIONS_SUPPORTED)[number],
54
+ level extends keyof RequestLevel
55
+ > = {
56
+ fhirVersion: Version;
57
+ level: RequestLevel[level];
58
+ http?: {
59
+ status: number;
60
+ headers: Record<string, string>;
61
+ };
62
+ };
@@ -0,0 +1,52 @@
1
+ import { expect, test } from "@jest/globals";
2
+
3
+ import parseFHIRSearch, {
4
+ escapeParameter,
5
+ splitParameter,
6
+ unescapeParameter,
7
+ } from "./url.js";
8
+
9
+ test("Test resource level", () => {
10
+ expect(parseFHIRSearch("Patient?name:text=bob")).toEqual([
11
+ { name: "name", modifier: "text", value: ["bob"] },
12
+ ]);
13
+ });
14
+
15
+ test("Test System level", () => {
16
+ expect(
17
+ parseFHIRSearch("Patient?name:text=bob&lastUpdated:not-in=1980-01-01"),
18
+ ).toEqual([
19
+ { name: "name", modifier: "text", value: ["bob"] },
20
+ {
21
+ name: "lastUpdated",
22
+ modifier: "not-in",
23
+ value: ["1980-01-01"],
24
+ },
25
+ ]);
26
+ });
27
+
28
+ test("TEST ESCAPING", () => {
29
+ expect(escapeParameter("test|123")).toEqual("test\\|123");
30
+ expect(unescapeParameter(escapeParameter("test|123"))).toEqual("test|123");
31
+ expect(escapeParameter("test\\123")).toEqual("test\\\\123");
32
+ expect(unescapeParameter(escapeParameter("test\\123"))).toEqual("test\\123");
33
+ expect(escapeParameter("test$123")).toEqual("test\\$123");
34
+ expect(escapeParameter("test,123")).toEqual("test\\,123");
35
+ });
36
+
37
+ test("TEST Parameter Split", () => {
38
+ expect(splitParameter("test\\|123|456", "|")).toEqual(["test|123", "456"]);
39
+ expect(splitParameter("|test\\|123|456|", "|")).toEqual([
40
+ "",
41
+ "test|123",
42
+ "456",
43
+ "",
44
+ ]);
45
+
46
+ expect(splitParameter("|123|test\\|123|456", "|")).toEqual([
47
+ "",
48
+ "123",
49
+ "test|123",
50
+ "456",
51
+ ]);
52
+ });
package/src/url.ts ADDED
@@ -0,0 +1,141 @@
1
+ import { FHIR_VERSION, Resource } from "@haste-health/fhir-types/versions";
2
+ import { OperationError, outcomeError } from "@haste-health/operation-outcomes";
3
+
4
+ type SPECIAL_CHARACTER = "\\" | "|" | "$" | ",";
5
+ const SPECIAL_CHARACTERS: SPECIAL_CHARACTER[] = ["\\", "|", "$", ","];
6
+
7
+ /**
8
+ * Returns string with split pieces and unescapes special characters from the split piece.
9
+ * @param parameter Parameter to be split
10
+ * @param specialCharacter One of special characters that get escaped on parameter.
11
+ */
12
+ export function splitParameter(
13
+ parameter: string,
14
+ specialCharacter: SPECIAL_CHARACTER
15
+ ): string[] {
16
+ const specialCharEg = new RegExp(`\\${specialCharacter}`, "g");
17
+ let prevIndex = -1;
18
+ const pieces = [];
19
+ let match;
20
+
21
+ while ((match = specialCharEg.exec(parameter))) {
22
+ if (match.index === 0 || parameter[match.index - 1] !== "\\") {
23
+ pieces.push(parameter.substring(prevIndex + 1, match.index));
24
+ prevIndex = match.index;
25
+ }
26
+ }
27
+ pieces.push(parameter.substring(prevIndex + 1));
28
+
29
+ return pieces.map(unescapeParameter);
30
+ }
31
+
32
+ /**
33
+ * Escapes a parameter values special characters
34
+ * Reference: https://hl7.org/fhir/R4/search.html#escaping
35
+ * @param parameter Parameter value to escape
36
+ * @returns Escaped Parameter
37
+ */
38
+ export function escapeParameter(parameter: string): string {
39
+ return SPECIAL_CHARACTERS.reduce(
40
+ (parameter: string, character: string): string => {
41
+ return parameter.replaceAll(character, `\\${character}`);
42
+ },
43
+ parameter
44
+ );
45
+ }
46
+
47
+ /**
48
+ * Unescapes a parameter values special characters.
49
+ * Reference: https://hl7.org/fhir/R4/search.html#escaping
50
+ * @param parameter Escaped Parameter
51
+ * @returns Unescaped Parameter
52
+ */
53
+ export function unescapeParameter(parameter: string): string {
54
+ return SPECIAL_CHARACTERS.reduce(
55
+ (parameter: string, character: string): string => {
56
+ return parameter.replaceAll(`\\${character}`, character);
57
+ },
58
+ parameter
59
+ );
60
+ }
61
+
62
+ export interface ParsedParameter<T> {
63
+ name: string;
64
+ value: T[];
65
+ modifier?: string;
66
+ chains?: string[];
67
+ }
68
+
69
+ export interface SearchParameterResource<Version extends FHIR_VERSION>
70
+ extends ParsedParameter<string | number> {
71
+ type: "resource";
72
+ searchParameter: Resource<Version, "SearchParameter">;
73
+ chainedParameters?: Resource<Version, "SearchParameter">[][];
74
+ }
75
+
76
+ export interface SearchParameterResult
77
+ extends ParsedParameter<string | number> {
78
+ type: "result";
79
+ }
80
+
81
+ export type MetaParameter<Version extends FHIR_VERSION> =
82
+ | SearchParameterResource<Version>
83
+ | SearchParameterResult;
84
+
85
+ export type Parameters<Version extends FHIR_VERSION> =
86
+ | ParsedParameter<string | number>[]
87
+ | MetaParameter<Version>[];
88
+
89
+ /**
90
+ * Given a query string create complex FHIR Query object.
91
+ * @param queryParams Raw query parameters pulled off url
92
+ * @returns Record of parsed parameters with name modifier and value.
93
+ */
94
+ export function parseQuery(
95
+ queryParams: string | undefined
96
+ ): ParsedParameter<string>[] {
97
+ const parameters = !queryParams
98
+ ? []
99
+ : queryParams
100
+ .split("&")
101
+ .map((param) => param.split("="))
102
+ .reduce(
103
+ (
104
+ parameters,
105
+ [key, value]
106
+ ): Record<string, ParsedParameter<string>> => {
107
+ const chains = key.split(".");
108
+
109
+ const [name, modifier] = chains[0].split(":");
110
+
111
+ const searchParam: ParsedParameter<string> = {
112
+ name,
113
+ modifier,
114
+ value: value.split(",").map((v) => decodeURIComponent(v)),
115
+ };
116
+
117
+ if (chains.length > 1) searchParam.chains = chains.slice(1);
118
+ if (modifier) searchParam.modifier = modifier;
119
+
120
+ return { ...parameters, [searchParam.name]: searchParam };
121
+ },
122
+ {}
123
+ );
124
+
125
+ return Object.values(parameters);
126
+ }
127
+
128
+ /**
129
+ * Given a url string parsequery parameters.
130
+ * @param url Any url to parse out query parameters.
131
+ * @returns Record of parsed parameters with name modifier and value.
132
+ */
133
+ export default function parseUrl(
134
+ url: string
135
+ ): ParsedParameter<string | number>[] {
136
+ const chunks = url.split("?");
137
+ if (chunks.length > 2)
138
+ throw new OperationError(outcomeError("invalid", "Invalid query string"));
139
+ const [_, queryParams] = chunks;
140
+ return parseQuery(queryParams);
141
+ }