@medplum/core 0.9.4 → 0.9.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.js +2602 -2602
- package/dist/esm/index.js +2602 -2602
- package/dist/types/cache.d.ts +12 -12
- package/dist/types/client.d.ts +713 -713
- package/dist/types/crypto.d.ts +9 -9
- package/dist/types/eventtarget.d.ts +13 -13
- package/dist/types/format.d.ts +15 -15
- package/dist/types/hl7.d.ts +43 -43
- package/dist/types/index.d.ts +10 -10
- package/dist/types/jwt.d.ts +5 -5
- package/dist/types/outcomes.d.ts +22 -22
- package/dist/types/readablepromise.d.ts +43 -43
- package/dist/types/search.d.ts +62 -62
- package/dist/types/searchparams.d.ts +35 -35
- package/dist/types/storage.d.ts +47 -47
- package/dist/types/types.d.ts +139 -139
- package/dist/types/utils.d.ts +131 -131
- package/package.json +2 -2
package/dist/esm/index.js
CHANGED
|
@@ -36,1851 +36,1851 @@ function __classPrivateFieldSet(receiver, state, value, kind, f) {
|
|
|
36
36
|
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
var _LRUCache_instances, _LRUCache_max, _LRUCache_cache, _LRUCache_first;
|
|
40
|
-
/**
|
|
41
|
-
* LRU cache (least recently used)
|
|
42
|
-
* Source: https://stackoverflow.com/a/46432113
|
|
43
|
-
*/
|
|
44
|
-
class LRUCache {
|
|
45
|
-
constructor(max = 10) {
|
|
46
|
-
_LRUCache_instances.add(this);
|
|
47
|
-
_LRUCache_max.set(this, void 0);
|
|
48
|
-
_LRUCache_cache.set(this, void 0);
|
|
49
|
-
__classPrivateFieldSet(this, _LRUCache_max, max, "f");
|
|
50
|
-
__classPrivateFieldSet(this, _LRUCache_cache, new Map(), "f");
|
|
51
|
-
}
|
|
52
|
-
clear() {
|
|
53
|
-
__classPrivateFieldGet(this, _LRUCache_cache, "f").clear();
|
|
54
|
-
}
|
|
55
|
-
get(key) {
|
|
56
|
-
const item = __classPrivateFieldGet(this, _LRUCache_cache, "f").get(key);
|
|
57
|
-
if (item) {
|
|
58
|
-
__classPrivateFieldGet(this, _LRUCache_cache, "f").delete(key);
|
|
59
|
-
__classPrivateFieldGet(this, _LRUCache_cache, "f").set(key, item);
|
|
60
|
-
}
|
|
61
|
-
return item;
|
|
62
|
-
}
|
|
63
|
-
set(key, val) {
|
|
64
|
-
if (__classPrivateFieldGet(this, _LRUCache_cache, "f").has(key)) {
|
|
65
|
-
__classPrivateFieldGet(this, _LRUCache_cache, "f").delete(key);
|
|
66
|
-
}
|
|
67
|
-
else if (__classPrivateFieldGet(this, _LRUCache_cache, "f").size >= __classPrivateFieldGet(this, _LRUCache_max, "f")) {
|
|
68
|
-
__classPrivateFieldGet(this, _LRUCache_cache, "f").delete(__classPrivateFieldGet(this, _LRUCache_instances, "m", _LRUCache_first).call(this));
|
|
69
|
-
}
|
|
70
|
-
__classPrivateFieldGet(this, _LRUCache_cache, "f").set(key, val);
|
|
71
|
-
}
|
|
72
|
-
delete(key) {
|
|
73
|
-
__classPrivateFieldGet(this, _LRUCache_cache, "f").delete(key);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
_LRUCache_max = new WeakMap(), _LRUCache_cache = new WeakMap(), _LRUCache_instances = new WeakSet(), _LRUCache_first = function _LRUCache_first() {
|
|
77
|
-
// This works because the Map class maintains ordered keys.
|
|
78
|
-
return __classPrivateFieldGet(this, _LRUCache_cache, "f").keys().next().value;
|
|
39
|
+
var _LRUCache_instances, _LRUCache_max, _LRUCache_cache, _LRUCache_first;
|
|
40
|
+
/**
|
|
41
|
+
* LRU cache (least recently used)
|
|
42
|
+
* Source: https://stackoverflow.com/a/46432113
|
|
43
|
+
*/
|
|
44
|
+
class LRUCache {
|
|
45
|
+
constructor(max = 10) {
|
|
46
|
+
_LRUCache_instances.add(this);
|
|
47
|
+
_LRUCache_max.set(this, void 0);
|
|
48
|
+
_LRUCache_cache.set(this, void 0);
|
|
49
|
+
__classPrivateFieldSet(this, _LRUCache_max, max, "f");
|
|
50
|
+
__classPrivateFieldSet(this, _LRUCache_cache, new Map(), "f");
|
|
51
|
+
}
|
|
52
|
+
clear() {
|
|
53
|
+
__classPrivateFieldGet(this, _LRUCache_cache, "f").clear();
|
|
54
|
+
}
|
|
55
|
+
get(key) {
|
|
56
|
+
const item = __classPrivateFieldGet(this, _LRUCache_cache, "f").get(key);
|
|
57
|
+
if (item) {
|
|
58
|
+
__classPrivateFieldGet(this, _LRUCache_cache, "f").delete(key);
|
|
59
|
+
__classPrivateFieldGet(this, _LRUCache_cache, "f").set(key, item);
|
|
60
|
+
}
|
|
61
|
+
return item;
|
|
62
|
+
}
|
|
63
|
+
set(key, val) {
|
|
64
|
+
if (__classPrivateFieldGet(this, _LRUCache_cache, "f").has(key)) {
|
|
65
|
+
__classPrivateFieldGet(this, _LRUCache_cache, "f").delete(key);
|
|
66
|
+
}
|
|
67
|
+
else if (__classPrivateFieldGet(this, _LRUCache_cache, "f").size >= __classPrivateFieldGet(this, _LRUCache_max, "f")) {
|
|
68
|
+
__classPrivateFieldGet(this, _LRUCache_cache, "f").delete(__classPrivateFieldGet(this, _LRUCache_instances, "m", _LRUCache_first).call(this));
|
|
69
|
+
}
|
|
70
|
+
__classPrivateFieldGet(this, _LRUCache_cache, "f").set(key, val);
|
|
71
|
+
}
|
|
72
|
+
delete(key) {
|
|
73
|
+
__classPrivateFieldGet(this, _LRUCache_cache, "f").delete(key);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
_LRUCache_max = new WeakMap(), _LRUCache_cache = new WeakMap(), _LRUCache_instances = new WeakSet(), _LRUCache_first = function _LRUCache_first() {
|
|
77
|
+
// This works because the Map class maintains ordered keys.
|
|
78
|
+
return __classPrivateFieldGet(this, _LRUCache_cache, "f").keys().next().value;
|
|
79
79
|
};
|
|
80
80
|
|
|
81
|
-
function formatAddress(address, options) {
|
|
82
|
-
const builder = [];
|
|
83
|
-
if (address.line) {
|
|
84
|
-
builder.push(...address.line);
|
|
85
|
-
}
|
|
86
|
-
if (address.city) {
|
|
87
|
-
builder.push(address.city);
|
|
88
|
-
}
|
|
89
|
-
if (address.state) {
|
|
90
|
-
builder.push(address.state);
|
|
91
|
-
}
|
|
92
|
-
if (address.postalCode) {
|
|
93
|
-
builder.push(address.postalCode);
|
|
94
|
-
}
|
|
95
|
-
if (address.use && ((options === null || options === void 0 ? void 0 : options.all) || (options === null || options === void 0 ? void 0 : options.use))) {
|
|
96
|
-
builder.push('[' + address.use + ']');
|
|
97
|
-
}
|
|
98
|
-
return builder.join(', ').trim();
|
|
99
|
-
}
|
|
100
|
-
function formatHumanName(name, options) {
|
|
101
|
-
const builder = [];
|
|
102
|
-
if (name.prefix && ((options === null || options === void 0 ? void 0 : options.all) || (options === null || options === void 0 ? void 0 : options.prefix))) {
|
|
103
|
-
builder.push(...name.prefix);
|
|
104
|
-
}
|
|
105
|
-
if (name.given) {
|
|
106
|
-
builder.push(...name.given);
|
|
107
|
-
}
|
|
108
|
-
if (name.family) {
|
|
109
|
-
builder.push(name.family);
|
|
110
|
-
}
|
|
111
|
-
if (name.suffix && ((options === null || options === void 0 ? void 0 : options.all) || (options === null || options === void 0 ? void 0 : options.suffix))) {
|
|
112
|
-
builder.push(...name.suffix);
|
|
113
|
-
}
|
|
114
|
-
if (name.use && ((options === null || options === void 0 ? void 0 : options.all) || (options === null || options === void 0 ? void 0 : options.use))) {
|
|
115
|
-
builder.push('[' + name.use + ']');
|
|
116
|
-
}
|
|
117
|
-
return builder.join(' ').trim();
|
|
118
|
-
}
|
|
119
|
-
function formatGivenName(name) {
|
|
120
|
-
const builder = [];
|
|
121
|
-
if (name.given) {
|
|
122
|
-
builder.push(...name.given);
|
|
123
|
-
}
|
|
124
|
-
return builder.join(' ').trim();
|
|
125
|
-
}
|
|
126
|
-
function formatFamilyName(name) {
|
|
127
|
-
return name.family || '';
|
|
81
|
+
function formatAddress(address, options) {
|
|
82
|
+
const builder = [];
|
|
83
|
+
if (address.line) {
|
|
84
|
+
builder.push(...address.line);
|
|
85
|
+
}
|
|
86
|
+
if (address.city) {
|
|
87
|
+
builder.push(address.city);
|
|
88
|
+
}
|
|
89
|
+
if (address.state) {
|
|
90
|
+
builder.push(address.state);
|
|
91
|
+
}
|
|
92
|
+
if (address.postalCode) {
|
|
93
|
+
builder.push(address.postalCode);
|
|
94
|
+
}
|
|
95
|
+
if (address.use && ((options === null || options === void 0 ? void 0 : options.all) || (options === null || options === void 0 ? void 0 : options.use))) {
|
|
96
|
+
builder.push('[' + address.use + ']');
|
|
97
|
+
}
|
|
98
|
+
return builder.join(', ').trim();
|
|
99
|
+
}
|
|
100
|
+
function formatHumanName(name, options) {
|
|
101
|
+
const builder = [];
|
|
102
|
+
if (name.prefix && ((options === null || options === void 0 ? void 0 : options.all) || (options === null || options === void 0 ? void 0 : options.prefix))) {
|
|
103
|
+
builder.push(...name.prefix);
|
|
104
|
+
}
|
|
105
|
+
if (name.given) {
|
|
106
|
+
builder.push(...name.given);
|
|
107
|
+
}
|
|
108
|
+
if (name.family) {
|
|
109
|
+
builder.push(name.family);
|
|
110
|
+
}
|
|
111
|
+
if (name.suffix && ((options === null || options === void 0 ? void 0 : options.all) || (options === null || options === void 0 ? void 0 : options.suffix))) {
|
|
112
|
+
builder.push(...name.suffix);
|
|
113
|
+
}
|
|
114
|
+
if (name.use && ((options === null || options === void 0 ? void 0 : options.all) || (options === null || options === void 0 ? void 0 : options.use))) {
|
|
115
|
+
builder.push('[' + name.use + ']');
|
|
116
|
+
}
|
|
117
|
+
return builder.join(' ').trim();
|
|
118
|
+
}
|
|
119
|
+
function formatGivenName(name) {
|
|
120
|
+
const builder = [];
|
|
121
|
+
if (name.given) {
|
|
122
|
+
builder.push(...name.given);
|
|
123
|
+
}
|
|
124
|
+
return builder.join(' ').trim();
|
|
125
|
+
}
|
|
126
|
+
function formatFamilyName(name) {
|
|
127
|
+
return name.family || '';
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
-
/**
|
|
131
|
-
* Creates a reference resource.
|
|
132
|
-
* @param resource The FHIR reesource.
|
|
133
|
-
* @returns A reference resource.
|
|
134
|
-
*/
|
|
135
|
-
function createReference(resource) {
|
|
136
|
-
const reference = getReferenceString(resource);
|
|
137
|
-
const display = getDisplayString(resource);
|
|
138
|
-
return display === reference ? { reference } : { reference, display };
|
|
139
|
-
}
|
|
140
|
-
/**
|
|
141
|
-
* Returns a reference string for a resource.
|
|
142
|
-
* @param resource The FHIR resource.
|
|
143
|
-
* @returns A reference string of the form resourceType/id.
|
|
144
|
-
*/
|
|
145
|
-
function getReferenceString(resource) {
|
|
146
|
-
return resource.resourceType + '/' + resource.id;
|
|
147
|
-
}
|
|
148
|
-
/**
|
|
149
|
-
* Returns the ID portion of a reference.
|
|
150
|
-
* @param reference A FHIR reference.
|
|
151
|
-
* @returns The ID portion of a reference.
|
|
152
|
-
*/
|
|
153
|
-
function resolveId(reference) {
|
|
154
|
-
var _a;
|
|
155
|
-
return (_a = reference === null || reference === void 0 ? void 0 : reference.reference) === null || _a === void 0 ? void 0 : _a.split('/')[1];
|
|
156
|
-
}
|
|
157
|
-
/**
|
|
158
|
-
* Returns true if the resource is a "ProfileResource".
|
|
159
|
-
* @param resource The FHIR resource.
|
|
160
|
-
* @returns True if the resource is a "ProfileResource".
|
|
161
|
-
*/
|
|
162
|
-
function isProfileResource(resource) {
|
|
163
|
-
return (resource.resourceType === 'Patient' ||
|
|
164
|
-
resource.resourceType === 'Practitioner' ||
|
|
165
|
-
resource.resourceType === 'RelatedPerson');
|
|
166
|
-
}
|
|
167
|
-
/**
|
|
168
|
-
* Returns a display string for the resource.
|
|
169
|
-
* @param resource The input resource.
|
|
170
|
-
* @return Human friendly display string.
|
|
171
|
-
*/
|
|
172
|
-
function getDisplayString(resource) {
|
|
173
|
-
var _a, _b;
|
|
174
|
-
if (isProfileResource(resource)) {
|
|
175
|
-
const profileName = getProfileResourceDisplayString(resource);
|
|
176
|
-
if (profileName) {
|
|
177
|
-
return profileName;
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
if (resource.resourceType === 'Device') {
|
|
181
|
-
const deviceName = getDeviceDisplayString(resource);
|
|
182
|
-
if (deviceName) {
|
|
183
|
-
return deviceName;
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
if (resource.resourceType === 'Observation') {
|
|
187
|
-
if ('code' in resource && ((_a = resource.code) === null || _a === void 0 ? void 0 : _a.text)) {
|
|
188
|
-
return (_b = resource.code) === null || _b === void 0 ? void 0 : _b.text;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
if (resource.resourceType === 'User') {
|
|
192
|
-
if (resource.email) {
|
|
193
|
-
return resource.email;
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
if ('name' in resource && resource.name && typeof resource.name === 'string') {
|
|
197
|
-
return resource.name;
|
|
198
|
-
}
|
|
199
|
-
return getReferenceString(resource);
|
|
200
|
-
}
|
|
201
|
-
/**
|
|
202
|
-
* Returns a display string for a profile resource if one is found.
|
|
203
|
-
* @param resource The profile resource.
|
|
204
|
-
* @returns The display name if one is found.
|
|
205
|
-
*/
|
|
206
|
-
function getProfileResourceDisplayString(resource) {
|
|
207
|
-
const names = resource.name;
|
|
208
|
-
if (names && names.length > 0) {
|
|
209
|
-
return formatHumanName(names[0]);
|
|
210
|
-
}
|
|
211
|
-
return undefined;
|
|
212
|
-
}
|
|
213
|
-
/**
|
|
214
|
-
* Returns a display string for a device resource if one is found.
|
|
215
|
-
* @param device The device resource.
|
|
216
|
-
* @returns The display name if one is found.
|
|
217
|
-
*/
|
|
218
|
-
function getDeviceDisplayString(device) {
|
|
219
|
-
const names = device.deviceName;
|
|
220
|
-
if (names && names.length > 0) {
|
|
221
|
-
return names[0].name;
|
|
222
|
-
}
|
|
223
|
-
return undefined;
|
|
224
|
-
}
|
|
225
|
-
/**
|
|
226
|
-
* Returns an image URL for the resource, if one is available.
|
|
227
|
-
* @param resource The input resource.
|
|
228
|
-
* @returns The image URL for the resource or undefined.
|
|
229
|
-
*/
|
|
230
|
-
function getImageSrc(resource) {
|
|
231
|
-
if (isProfileResource(resource)) {
|
|
232
|
-
const photos = resource.photo;
|
|
233
|
-
if (photos) {
|
|
234
|
-
for (const photo of photos) {
|
|
235
|
-
const url = getPhotoImageSrc(photo);
|
|
236
|
-
if (url) {
|
|
237
|
-
return url;
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
if (resource.resourceType === 'Bot' && resource.photo) {
|
|
243
|
-
const url = getPhotoImageSrc(resource.photo);
|
|
244
|
-
if (url) {
|
|
245
|
-
return url;
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
return undefined;
|
|
249
|
-
}
|
|
250
|
-
function getPhotoImageSrc(photo) {
|
|
251
|
-
if (photo.url && photo.contentType && photo.contentType.startsWith('image/')) {
|
|
252
|
-
return photo.url;
|
|
253
|
-
}
|
|
254
|
-
return undefined;
|
|
255
|
-
}
|
|
256
|
-
/**
|
|
257
|
-
* Returns a Date property as a Date.
|
|
258
|
-
* When working with JSON objects, Dates are often serialized as ISO-8601 strings.
|
|
259
|
-
* When that happens, we need to safely convert to a proper Date object.
|
|
260
|
-
* @param date The date property value, which could be a string or a Date object.
|
|
261
|
-
* @returns A Date object.
|
|
262
|
-
*/
|
|
263
|
-
function getDateProperty(date) {
|
|
264
|
-
return date ? new Date(date) : undefined;
|
|
265
|
-
}
|
|
266
|
-
/**
|
|
267
|
-
* Calculates the age in years from the birth date.
|
|
268
|
-
* @param birthDateStr The birth date or start date in ISO-8601 format YYYY-MM-DD.
|
|
269
|
-
* @param endDateStr Optional end date in ISO-8601 format YYYY-MM-DD. Default value is today.
|
|
270
|
-
* @returns The age in years, months, and days.
|
|
271
|
-
*/
|
|
272
|
-
function calculateAge(birthDateStr, endDateStr) {
|
|
273
|
-
const startDate = new Date(birthDateStr);
|
|
274
|
-
startDate.setUTCHours(0, 0, 0, 0);
|
|
275
|
-
const endDate = endDateStr ? new Date(endDateStr) : new Date();
|
|
276
|
-
endDate.setUTCHours(0, 0, 0, 0);
|
|
277
|
-
const startYear = startDate.getUTCFullYear();
|
|
278
|
-
const startMonth = startDate.getUTCMonth();
|
|
279
|
-
const startDay = startDate.getUTCDate();
|
|
280
|
-
const endYear = endDate.getUTCFullYear();
|
|
281
|
-
const endMonth = endDate.getUTCMonth();
|
|
282
|
-
const endDay = endDate.getUTCDate();
|
|
283
|
-
let years = endYear - startYear;
|
|
284
|
-
if (endMonth < startMonth || (endMonth === startMonth && endDay < startDay)) {
|
|
285
|
-
years--;
|
|
286
|
-
}
|
|
287
|
-
let months = endYear * 12 + endMonth - (startYear * 12 + startMonth);
|
|
288
|
-
if (endDay < startDay) {
|
|
289
|
-
months--;
|
|
290
|
-
}
|
|
291
|
-
const days = Math.floor((endDate.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24));
|
|
292
|
-
return { years, months, days };
|
|
293
|
-
}
|
|
294
|
-
/**
|
|
295
|
-
* Calculates the age string for display using the age appropriate units.
|
|
296
|
-
* If the age is greater than or equal to 2 years, then the age is displayed in years.
|
|
297
|
-
* If the age is greater than or equal to 1 month, then the age is displayed in months.
|
|
298
|
-
* Otherwise, the age is displayed in days.
|
|
299
|
-
* @param birthDateStr The birth date or start date in ISO-8601 format YYYY-MM-DD.
|
|
300
|
-
* @param endDateStr Optional end date in ISO-8601 format YYYY-MM-DD. Default value is today.
|
|
301
|
-
* @returns The age string.
|
|
302
|
-
*/
|
|
303
|
-
function calculateAgeString(birthDateStr, endDateStr) {
|
|
304
|
-
const { years, months, days } = calculateAge(birthDateStr, endDateStr);
|
|
305
|
-
if (years >= 2) {
|
|
306
|
-
return years.toString().padStart(3, '0') + 'Y';
|
|
307
|
-
}
|
|
308
|
-
else if (months >= 1) {
|
|
309
|
-
return months.toString().padStart(3, '0') + 'M';
|
|
310
|
-
}
|
|
311
|
-
else {
|
|
312
|
-
return days.toString().padStart(3, '0') + 'D';
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
/**
|
|
316
|
-
* Returns all questionnaire answers as a map by link ID.
|
|
317
|
-
* @param response The questionnaire response resource.
|
|
318
|
-
* @returns Questionnaire answers mapped by link ID.
|
|
319
|
-
*/
|
|
320
|
-
function getQuestionnaireAnswers(response) {
|
|
321
|
-
const result = {};
|
|
322
|
-
buildQuestionnaireAnswerItems(response.item, result);
|
|
323
|
-
return result;
|
|
324
|
-
}
|
|
325
|
-
/**
|
|
326
|
-
* Recursively builds the questionnaire answer items map.
|
|
327
|
-
* @param item The current questionnaire response item.
|
|
328
|
-
* @param result The cumulative result map.
|
|
329
|
-
*/
|
|
330
|
-
function buildQuestionnaireAnswerItems(items, result) {
|
|
331
|
-
if (items) {
|
|
332
|
-
for (const item of items) {
|
|
333
|
-
if (item.linkId && item.answer && item.answer.length > 0) {
|
|
334
|
-
result[item.linkId] = item.answer[0];
|
|
335
|
-
}
|
|
336
|
-
buildQuestionnaireAnswerItems(item.item, result);
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
/**
|
|
341
|
-
* Returns an extension value by extension URLs.
|
|
342
|
-
* @param resource The base resource.
|
|
343
|
-
* @param urls Array of extension URLs. Each entry represents a nested extension.
|
|
344
|
-
* @returns The extension value if found; undefined otherwise.
|
|
345
|
-
*/
|
|
346
|
-
function getExtensionValue(resource, ...urls) {
|
|
347
|
-
var _a;
|
|
348
|
-
// Let curr be the current resource or extension. Extensions can be nested.
|
|
349
|
-
let curr = resource;
|
|
350
|
-
// For each of the urls, try to find a matching nested extension.
|
|
351
|
-
for (let i = 0; i < urls.length && curr; i++) {
|
|
352
|
-
curr = (_a = curr === null || curr === void 0 ? void 0 : curr.extension) === null || _a === void 0 ? void 0 : _a.find((e) => e.url === urls[i]);
|
|
353
|
-
}
|
|
354
|
-
return curr === null || curr === void 0 ? void 0 : curr.valueString;
|
|
355
|
-
}
|
|
356
|
-
/**
|
|
357
|
-
* FHIR JSON stringify.
|
|
358
|
-
* Removes properties with empty string values.
|
|
359
|
-
* Removes objects with zero properties.
|
|
360
|
-
* See: https://www.hl7.org/fhir/json.html
|
|
361
|
-
* @param value The input value.
|
|
362
|
-
* @param pretty Optional flag to pretty-print the JSON.
|
|
363
|
-
* @returns The resulting JSON string.
|
|
364
|
-
*/
|
|
365
|
-
function stringify(value, pretty) {
|
|
366
|
-
return JSON.stringify(value, stringifyReplacer, pretty ? 2 : undefined);
|
|
367
|
-
}
|
|
368
|
-
/**
|
|
369
|
-
* Evaluates JSON key/value pairs for FHIR JSON stringify.
|
|
370
|
-
* Removes properties with empty string values.
|
|
371
|
-
* Removes objects with zero properties.
|
|
372
|
-
* @param {string} k Property key.
|
|
373
|
-
* @param {*} v Property value.
|
|
374
|
-
*/
|
|
375
|
-
function stringifyReplacer(k, v) {
|
|
376
|
-
return !isArrayKey(k) && isEmpty(v) ? undefined : v;
|
|
377
|
-
}
|
|
378
|
-
/**
|
|
379
|
-
* Returns true if the key is an array key.
|
|
380
|
-
* @param k The property key.
|
|
381
|
-
* @returns True if the key is an array key.
|
|
382
|
-
*/
|
|
383
|
-
function isArrayKey(k) {
|
|
384
|
-
return !!k.match(/\d+$/);
|
|
385
|
-
}
|
|
386
|
-
/**
|
|
387
|
-
* Returns true if the value is empty (null, undefined, empty string, or empty object).
|
|
388
|
-
* @param v Any value.
|
|
389
|
-
* @returns True if the value is an empty string or an empty object.
|
|
390
|
-
*/
|
|
391
|
-
function isEmpty(v) {
|
|
392
|
-
if (v === null || v === undefined) {
|
|
393
|
-
return true;
|
|
394
|
-
}
|
|
395
|
-
const t = typeof v;
|
|
396
|
-
return (t === 'string' && v === '') || (t === 'object' && Object.keys(v).length === 0);
|
|
397
|
-
}
|
|
398
|
-
/**
|
|
399
|
-
* Resource equality.
|
|
400
|
-
* Ignores meta.versionId and meta.lastUpdated.
|
|
401
|
-
* @param object1 The first object.
|
|
402
|
-
* @param object2 The second object.
|
|
403
|
-
* @returns True if the objects are equal.
|
|
404
|
-
*/
|
|
405
|
-
function deepEquals(object1, object2, path) {
|
|
406
|
-
if (object1 === object2) {
|
|
407
|
-
return true;
|
|
408
|
-
}
|
|
409
|
-
if (isEmpty(object1) && isEmpty(object2)) {
|
|
410
|
-
return true;
|
|
411
|
-
}
|
|
412
|
-
if (isEmpty(object1) || isEmpty(object2)) {
|
|
413
|
-
return false;
|
|
414
|
-
}
|
|
415
|
-
if (Array.isArray(object1) && Array.isArray(object2)) {
|
|
416
|
-
return deepEqualsArray(object1, object2);
|
|
417
|
-
}
|
|
418
|
-
if (Array.isArray(object1) || Array.isArray(object2)) {
|
|
419
|
-
return false;
|
|
420
|
-
}
|
|
421
|
-
if (isObject(object1) && isObject(object2)) {
|
|
422
|
-
return deepEqualsObject(object1, object2, path);
|
|
423
|
-
}
|
|
424
|
-
if (isObject(object1) || isObject(object2)) {
|
|
425
|
-
return false;
|
|
426
|
-
}
|
|
427
|
-
return false;
|
|
428
|
-
}
|
|
429
|
-
function deepEqualsArray(array1, array2) {
|
|
430
|
-
if (array1.length !== array2.length) {
|
|
431
|
-
return false;
|
|
432
|
-
}
|
|
433
|
-
for (let i = 0; i < array1.length; i++) {
|
|
434
|
-
if (!deepEquals(array1[i], array2[i])) {
|
|
435
|
-
return false;
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
return true;
|
|
439
|
-
}
|
|
440
|
-
function deepEqualsObject(object1, object2, path) {
|
|
441
|
-
const keySet = new Set();
|
|
442
|
-
Object.keys(object1).forEach((k) => keySet.add(k));
|
|
443
|
-
Object.keys(object2).forEach((k) => keySet.add(k));
|
|
444
|
-
if (path === 'meta') {
|
|
445
|
-
keySet.delete('versionId');
|
|
446
|
-
keySet.delete('lastUpdated');
|
|
447
|
-
keySet.delete('author');
|
|
448
|
-
}
|
|
449
|
-
for (const key of keySet) {
|
|
450
|
-
const val1 = object1[key];
|
|
451
|
-
const val2 = object2[key];
|
|
452
|
-
if (!deepEquals(val1, val2, key)) {
|
|
453
|
-
return false;
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
return true;
|
|
457
|
-
}
|
|
458
|
-
/**
|
|
459
|
-
* Returns true if the input string is a UUID.
|
|
460
|
-
* @param input The input string.
|
|
461
|
-
* @returns True if the input string matches the UUID format.
|
|
462
|
-
*/
|
|
463
|
-
function isUUID(input) {
|
|
464
|
-
return !!input.match(/^\w{8}-\w{4}-\w{4}-\w{4}-\w{12}$/i);
|
|
465
|
-
}
|
|
466
|
-
/**
|
|
467
|
-
* Returns true if the input is an object.
|
|
468
|
-
* @param object The candidate object.
|
|
469
|
-
* @returns True if the input is a non-null non-undefined object.
|
|
470
|
-
*/
|
|
471
|
-
function isObject(obj) {
|
|
472
|
-
return obj !== null && typeof obj === 'object';
|
|
473
|
-
}
|
|
474
|
-
/**
|
|
475
|
-
* Returns true if the input array is an array of strings.
|
|
476
|
-
* @param arr Input array.
|
|
477
|
-
* @returns True if the input array is an array of strings.
|
|
478
|
-
*/
|
|
479
|
-
function isStringArray(arr) {
|
|
480
|
-
return arr.every((e) => typeof e === 'string');
|
|
481
|
-
}
|
|
482
|
-
// Precompute hex octets
|
|
483
|
-
// See: https://stackoverflow.com/a/55200387
|
|
484
|
-
const byteToHex = [];
|
|
485
|
-
for (let n = 0; n < 256; n++) {
|
|
486
|
-
byteToHex.push(n.toString(16).padStart(2, '0'));
|
|
487
|
-
}
|
|
488
|
-
/**
|
|
489
|
-
* Converts an ArrayBuffer to hex string.
|
|
490
|
-
* See: https://stackoverflow.com/a/55200387
|
|
491
|
-
* @param arrayBuffer The input array buffer.
|
|
492
|
-
* @returns The resulting hex string.
|
|
493
|
-
*/
|
|
494
|
-
function arrayBufferToHex(arrayBuffer) {
|
|
495
|
-
const bytes = new Uint8Array(arrayBuffer);
|
|
496
|
-
const result = new Array(bytes.length);
|
|
497
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
498
|
-
result[i] = byteToHex[bytes[i]];
|
|
499
|
-
}
|
|
500
|
-
return result.join('');
|
|
501
|
-
}
|
|
502
|
-
/**
|
|
503
|
-
* Converts an ArrayBuffer to a base-64 encoded string.
|
|
504
|
-
* @param arrayBuffer The input array buffer.
|
|
505
|
-
* @returns The base-64 encoded string.
|
|
506
|
-
*/
|
|
507
|
-
function arrayBufferToBase64(arrayBuffer) {
|
|
508
|
-
const bytes = new Uint8Array(arrayBuffer);
|
|
509
|
-
const result = [];
|
|
510
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
511
|
-
result[i] = String.fromCharCode(bytes[i]);
|
|
512
|
-
}
|
|
513
|
-
return window.btoa(result.join(''));
|
|
514
|
-
}
|
|
515
|
-
function capitalize(word) {
|
|
516
|
-
return word.charAt(0).toUpperCase() + word.substring(1);
|
|
517
|
-
}
|
|
518
|
-
function isLowerCase(c) {
|
|
519
|
-
return c === c.toLowerCase();
|
|
130
|
+
/**
|
|
131
|
+
* Creates a reference resource.
|
|
132
|
+
* @param resource The FHIR reesource.
|
|
133
|
+
* @returns A reference resource.
|
|
134
|
+
*/
|
|
135
|
+
function createReference(resource) {
|
|
136
|
+
const reference = getReferenceString(resource);
|
|
137
|
+
const display = getDisplayString(resource);
|
|
138
|
+
return display === reference ? { reference } : { reference, display };
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Returns a reference string for a resource.
|
|
142
|
+
* @param resource The FHIR resource.
|
|
143
|
+
* @returns A reference string of the form resourceType/id.
|
|
144
|
+
*/
|
|
145
|
+
function getReferenceString(resource) {
|
|
146
|
+
return resource.resourceType + '/' + resource.id;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Returns the ID portion of a reference.
|
|
150
|
+
* @param reference A FHIR reference.
|
|
151
|
+
* @returns The ID portion of a reference.
|
|
152
|
+
*/
|
|
153
|
+
function resolveId(reference) {
|
|
154
|
+
var _a;
|
|
155
|
+
return (_a = reference === null || reference === void 0 ? void 0 : reference.reference) === null || _a === void 0 ? void 0 : _a.split('/')[1];
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Returns true if the resource is a "ProfileResource".
|
|
159
|
+
* @param resource The FHIR resource.
|
|
160
|
+
* @returns True if the resource is a "ProfileResource".
|
|
161
|
+
*/
|
|
162
|
+
function isProfileResource(resource) {
|
|
163
|
+
return (resource.resourceType === 'Patient' ||
|
|
164
|
+
resource.resourceType === 'Practitioner' ||
|
|
165
|
+
resource.resourceType === 'RelatedPerson');
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Returns a display string for the resource.
|
|
169
|
+
* @param resource The input resource.
|
|
170
|
+
* @return Human friendly display string.
|
|
171
|
+
*/
|
|
172
|
+
function getDisplayString(resource) {
|
|
173
|
+
var _a, _b;
|
|
174
|
+
if (isProfileResource(resource)) {
|
|
175
|
+
const profileName = getProfileResourceDisplayString(resource);
|
|
176
|
+
if (profileName) {
|
|
177
|
+
return profileName;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (resource.resourceType === 'Device') {
|
|
181
|
+
const deviceName = getDeviceDisplayString(resource);
|
|
182
|
+
if (deviceName) {
|
|
183
|
+
return deviceName;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
if (resource.resourceType === 'Observation') {
|
|
187
|
+
if ('code' in resource && ((_a = resource.code) === null || _a === void 0 ? void 0 : _a.text)) {
|
|
188
|
+
return (_b = resource.code) === null || _b === void 0 ? void 0 : _b.text;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
if (resource.resourceType === 'User') {
|
|
192
|
+
if (resource.email) {
|
|
193
|
+
return resource.email;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if ('name' in resource && resource.name && typeof resource.name === 'string') {
|
|
197
|
+
return resource.name;
|
|
198
|
+
}
|
|
199
|
+
return getReferenceString(resource);
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Returns a display string for a profile resource if one is found.
|
|
203
|
+
* @param resource The profile resource.
|
|
204
|
+
* @returns The display name if one is found.
|
|
205
|
+
*/
|
|
206
|
+
function getProfileResourceDisplayString(resource) {
|
|
207
|
+
const names = resource.name;
|
|
208
|
+
if (names && names.length > 0) {
|
|
209
|
+
return formatHumanName(names[0]);
|
|
210
|
+
}
|
|
211
|
+
return undefined;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Returns a display string for a device resource if one is found.
|
|
215
|
+
* @param device The device resource.
|
|
216
|
+
* @returns The display name if one is found.
|
|
217
|
+
*/
|
|
218
|
+
function getDeviceDisplayString(device) {
|
|
219
|
+
const names = device.deviceName;
|
|
220
|
+
if (names && names.length > 0) {
|
|
221
|
+
return names[0].name;
|
|
222
|
+
}
|
|
223
|
+
return undefined;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Returns an image URL for the resource, if one is available.
|
|
227
|
+
* @param resource The input resource.
|
|
228
|
+
* @returns The image URL for the resource or undefined.
|
|
229
|
+
*/
|
|
230
|
+
function getImageSrc(resource) {
|
|
231
|
+
if (isProfileResource(resource)) {
|
|
232
|
+
const photos = resource.photo;
|
|
233
|
+
if (photos) {
|
|
234
|
+
for (const photo of photos) {
|
|
235
|
+
const url = getPhotoImageSrc(photo);
|
|
236
|
+
if (url) {
|
|
237
|
+
return url;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
if (resource.resourceType === 'Bot' && resource.photo) {
|
|
243
|
+
const url = getPhotoImageSrc(resource.photo);
|
|
244
|
+
if (url) {
|
|
245
|
+
return url;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return undefined;
|
|
249
|
+
}
|
|
250
|
+
function getPhotoImageSrc(photo) {
|
|
251
|
+
if (photo.url && photo.contentType && photo.contentType.startsWith('image/')) {
|
|
252
|
+
return photo.url;
|
|
253
|
+
}
|
|
254
|
+
return undefined;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Returns a Date property as a Date.
|
|
258
|
+
* When working with JSON objects, Dates are often serialized as ISO-8601 strings.
|
|
259
|
+
* When that happens, we need to safely convert to a proper Date object.
|
|
260
|
+
* @param date The date property value, which could be a string or a Date object.
|
|
261
|
+
* @returns A Date object.
|
|
262
|
+
*/
|
|
263
|
+
function getDateProperty(date) {
|
|
264
|
+
return date ? new Date(date) : undefined;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Calculates the age in years from the birth date.
|
|
268
|
+
* @param birthDateStr The birth date or start date in ISO-8601 format YYYY-MM-DD.
|
|
269
|
+
* @param endDateStr Optional end date in ISO-8601 format YYYY-MM-DD. Default value is today.
|
|
270
|
+
* @returns The age in years, months, and days.
|
|
271
|
+
*/
|
|
272
|
+
function calculateAge(birthDateStr, endDateStr) {
|
|
273
|
+
const startDate = new Date(birthDateStr);
|
|
274
|
+
startDate.setUTCHours(0, 0, 0, 0);
|
|
275
|
+
const endDate = endDateStr ? new Date(endDateStr) : new Date();
|
|
276
|
+
endDate.setUTCHours(0, 0, 0, 0);
|
|
277
|
+
const startYear = startDate.getUTCFullYear();
|
|
278
|
+
const startMonth = startDate.getUTCMonth();
|
|
279
|
+
const startDay = startDate.getUTCDate();
|
|
280
|
+
const endYear = endDate.getUTCFullYear();
|
|
281
|
+
const endMonth = endDate.getUTCMonth();
|
|
282
|
+
const endDay = endDate.getUTCDate();
|
|
283
|
+
let years = endYear - startYear;
|
|
284
|
+
if (endMonth < startMonth || (endMonth === startMonth && endDay < startDay)) {
|
|
285
|
+
years--;
|
|
286
|
+
}
|
|
287
|
+
let months = endYear * 12 + endMonth - (startYear * 12 + startMonth);
|
|
288
|
+
if (endDay < startDay) {
|
|
289
|
+
months--;
|
|
290
|
+
}
|
|
291
|
+
const days = Math.floor((endDate.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24));
|
|
292
|
+
return { years, months, days };
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Calculates the age string for display using the age appropriate units.
|
|
296
|
+
* If the age is greater than or equal to 2 years, then the age is displayed in years.
|
|
297
|
+
* If the age is greater than or equal to 1 month, then the age is displayed in months.
|
|
298
|
+
* Otherwise, the age is displayed in days.
|
|
299
|
+
* @param birthDateStr The birth date or start date in ISO-8601 format YYYY-MM-DD.
|
|
300
|
+
* @param endDateStr Optional end date in ISO-8601 format YYYY-MM-DD. Default value is today.
|
|
301
|
+
* @returns The age string.
|
|
302
|
+
*/
|
|
303
|
+
function calculateAgeString(birthDateStr, endDateStr) {
|
|
304
|
+
const { years, months, days } = calculateAge(birthDateStr, endDateStr);
|
|
305
|
+
if (years >= 2) {
|
|
306
|
+
return years.toString().padStart(3, '0') + 'Y';
|
|
307
|
+
}
|
|
308
|
+
else if (months >= 1) {
|
|
309
|
+
return months.toString().padStart(3, '0') + 'M';
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
312
|
+
return days.toString().padStart(3, '0') + 'D';
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Returns all questionnaire answers as a map by link ID.
|
|
317
|
+
* @param response The questionnaire response resource.
|
|
318
|
+
* @returns Questionnaire answers mapped by link ID.
|
|
319
|
+
*/
|
|
320
|
+
function getQuestionnaireAnswers(response) {
|
|
321
|
+
const result = {};
|
|
322
|
+
buildQuestionnaireAnswerItems(response.item, result);
|
|
323
|
+
return result;
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Recursively builds the questionnaire answer items map.
|
|
327
|
+
* @param item The current questionnaire response item.
|
|
328
|
+
* @param result The cumulative result map.
|
|
329
|
+
*/
|
|
330
|
+
function buildQuestionnaireAnswerItems(items, result) {
|
|
331
|
+
if (items) {
|
|
332
|
+
for (const item of items) {
|
|
333
|
+
if (item.linkId && item.answer && item.answer.length > 0) {
|
|
334
|
+
result[item.linkId] = item.answer[0];
|
|
335
|
+
}
|
|
336
|
+
buildQuestionnaireAnswerItems(item.item, result);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Returns an extension value by extension URLs.
|
|
342
|
+
* @param resource The base resource.
|
|
343
|
+
* @param urls Array of extension URLs. Each entry represents a nested extension.
|
|
344
|
+
* @returns The extension value if found; undefined otherwise.
|
|
345
|
+
*/
|
|
346
|
+
function getExtensionValue(resource, ...urls) {
|
|
347
|
+
var _a;
|
|
348
|
+
// Let curr be the current resource or extension. Extensions can be nested.
|
|
349
|
+
let curr = resource;
|
|
350
|
+
// For each of the urls, try to find a matching nested extension.
|
|
351
|
+
for (let i = 0; i < urls.length && curr; i++) {
|
|
352
|
+
curr = (_a = curr === null || curr === void 0 ? void 0 : curr.extension) === null || _a === void 0 ? void 0 : _a.find((e) => e.url === urls[i]);
|
|
353
|
+
}
|
|
354
|
+
return curr === null || curr === void 0 ? void 0 : curr.valueString;
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* FHIR JSON stringify.
|
|
358
|
+
* Removes properties with empty string values.
|
|
359
|
+
* Removes objects with zero properties.
|
|
360
|
+
* See: https://www.hl7.org/fhir/json.html
|
|
361
|
+
* @param value The input value.
|
|
362
|
+
* @param pretty Optional flag to pretty-print the JSON.
|
|
363
|
+
* @returns The resulting JSON string.
|
|
364
|
+
*/
|
|
365
|
+
function stringify(value, pretty) {
|
|
366
|
+
return JSON.stringify(value, stringifyReplacer, pretty ? 2 : undefined);
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Evaluates JSON key/value pairs for FHIR JSON stringify.
|
|
370
|
+
* Removes properties with empty string values.
|
|
371
|
+
* Removes objects with zero properties.
|
|
372
|
+
* @param {string} k Property key.
|
|
373
|
+
* @param {*} v Property value.
|
|
374
|
+
*/
|
|
375
|
+
function stringifyReplacer(k, v) {
|
|
376
|
+
return !isArrayKey(k) && isEmpty(v) ? undefined : v;
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Returns true if the key is an array key.
|
|
380
|
+
* @param k The property key.
|
|
381
|
+
* @returns True if the key is an array key.
|
|
382
|
+
*/
|
|
383
|
+
function isArrayKey(k) {
|
|
384
|
+
return !!k.match(/\d+$/);
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Returns true if the value is empty (null, undefined, empty string, or empty object).
|
|
388
|
+
* @param v Any value.
|
|
389
|
+
* @returns True if the value is an empty string or an empty object.
|
|
390
|
+
*/
|
|
391
|
+
function isEmpty(v) {
|
|
392
|
+
if (v === null || v === undefined) {
|
|
393
|
+
return true;
|
|
394
|
+
}
|
|
395
|
+
const t = typeof v;
|
|
396
|
+
return (t === 'string' && v === '') || (t === 'object' && Object.keys(v).length === 0);
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Resource equality.
|
|
400
|
+
* Ignores meta.versionId and meta.lastUpdated.
|
|
401
|
+
* @param object1 The first object.
|
|
402
|
+
* @param object2 The second object.
|
|
403
|
+
* @returns True if the objects are equal.
|
|
404
|
+
*/
|
|
405
|
+
function deepEquals(object1, object2, path) {
|
|
406
|
+
if (object1 === object2) {
|
|
407
|
+
return true;
|
|
408
|
+
}
|
|
409
|
+
if (isEmpty(object1) && isEmpty(object2)) {
|
|
410
|
+
return true;
|
|
411
|
+
}
|
|
412
|
+
if (isEmpty(object1) || isEmpty(object2)) {
|
|
413
|
+
return false;
|
|
414
|
+
}
|
|
415
|
+
if (Array.isArray(object1) && Array.isArray(object2)) {
|
|
416
|
+
return deepEqualsArray(object1, object2);
|
|
417
|
+
}
|
|
418
|
+
if (Array.isArray(object1) || Array.isArray(object2)) {
|
|
419
|
+
return false;
|
|
420
|
+
}
|
|
421
|
+
if (isObject(object1) && isObject(object2)) {
|
|
422
|
+
return deepEqualsObject(object1, object2, path);
|
|
423
|
+
}
|
|
424
|
+
if (isObject(object1) || isObject(object2)) {
|
|
425
|
+
return false;
|
|
426
|
+
}
|
|
427
|
+
return false;
|
|
428
|
+
}
|
|
429
|
+
function deepEqualsArray(array1, array2) {
|
|
430
|
+
if (array1.length !== array2.length) {
|
|
431
|
+
return false;
|
|
432
|
+
}
|
|
433
|
+
for (let i = 0; i < array1.length; i++) {
|
|
434
|
+
if (!deepEquals(array1[i], array2[i])) {
|
|
435
|
+
return false;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
return true;
|
|
439
|
+
}
|
|
440
|
+
function deepEqualsObject(object1, object2, path) {
|
|
441
|
+
const keySet = new Set();
|
|
442
|
+
Object.keys(object1).forEach((k) => keySet.add(k));
|
|
443
|
+
Object.keys(object2).forEach((k) => keySet.add(k));
|
|
444
|
+
if (path === 'meta') {
|
|
445
|
+
keySet.delete('versionId');
|
|
446
|
+
keySet.delete('lastUpdated');
|
|
447
|
+
keySet.delete('author');
|
|
448
|
+
}
|
|
449
|
+
for (const key of keySet) {
|
|
450
|
+
const val1 = object1[key];
|
|
451
|
+
const val2 = object2[key];
|
|
452
|
+
if (!deepEquals(val1, val2, key)) {
|
|
453
|
+
return false;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
return true;
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Returns true if the input string is a UUID.
|
|
460
|
+
* @param input The input string.
|
|
461
|
+
* @returns True if the input string matches the UUID format.
|
|
462
|
+
*/
|
|
463
|
+
function isUUID(input) {
|
|
464
|
+
return !!input.match(/^\w{8}-\w{4}-\w{4}-\w{4}-\w{12}$/i);
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Returns true if the input is an object.
|
|
468
|
+
* @param object The candidate object.
|
|
469
|
+
* @returns True if the input is a non-null non-undefined object.
|
|
470
|
+
*/
|
|
471
|
+
function isObject(obj) {
|
|
472
|
+
return obj !== null && typeof obj === 'object';
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Returns true if the input array is an array of strings.
|
|
476
|
+
* @param arr Input array.
|
|
477
|
+
* @returns True if the input array is an array of strings.
|
|
478
|
+
*/
|
|
479
|
+
function isStringArray(arr) {
|
|
480
|
+
return arr.every((e) => typeof e === 'string');
|
|
481
|
+
}
|
|
482
|
+
// Precompute hex octets
|
|
483
|
+
// See: https://stackoverflow.com/a/55200387
|
|
484
|
+
const byteToHex = [];
|
|
485
|
+
for (let n = 0; n < 256; n++) {
|
|
486
|
+
byteToHex.push(n.toString(16).padStart(2, '0'));
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Converts an ArrayBuffer to hex string.
|
|
490
|
+
* See: https://stackoverflow.com/a/55200387
|
|
491
|
+
* @param arrayBuffer The input array buffer.
|
|
492
|
+
* @returns The resulting hex string.
|
|
493
|
+
*/
|
|
494
|
+
function arrayBufferToHex(arrayBuffer) {
|
|
495
|
+
const bytes = new Uint8Array(arrayBuffer);
|
|
496
|
+
const result = new Array(bytes.length);
|
|
497
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
498
|
+
result[i] = byteToHex[bytes[i]];
|
|
499
|
+
}
|
|
500
|
+
return result.join('');
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* Converts an ArrayBuffer to a base-64 encoded string.
|
|
504
|
+
* @param arrayBuffer The input array buffer.
|
|
505
|
+
* @returns The base-64 encoded string.
|
|
506
|
+
*/
|
|
507
|
+
function arrayBufferToBase64(arrayBuffer) {
|
|
508
|
+
const bytes = new Uint8Array(arrayBuffer);
|
|
509
|
+
const result = [];
|
|
510
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
511
|
+
result[i] = String.fromCharCode(bytes[i]);
|
|
512
|
+
}
|
|
513
|
+
return window.btoa(result.join(''));
|
|
514
|
+
}
|
|
515
|
+
function capitalize(word) {
|
|
516
|
+
return word.charAt(0).toUpperCase() + word.substring(1);
|
|
517
|
+
}
|
|
518
|
+
function isLowerCase(c) {
|
|
519
|
+
return c === c.toLowerCase();
|
|
520
520
|
}
|
|
521
521
|
|
|
522
|
-
/**
|
|
523
|
-
* Returns a cryptographically secure random string.
|
|
524
|
-
*/
|
|
525
|
-
function getRandomString() {
|
|
526
|
-
const randomItems = new Uint32Array(28);
|
|
527
|
-
crypto.getRandomValues(randomItems);
|
|
528
|
-
return arrayBufferToHex(randomItems.buffer);
|
|
529
|
-
}
|
|
530
|
-
/**
|
|
531
|
-
* Encrypts a string with SHA256 encryption.
|
|
532
|
-
* @param str
|
|
533
|
-
*/
|
|
534
|
-
function encryptSHA256(str) {
|
|
535
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
536
|
-
return crypto.subtle.digest('SHA-256', new TextEncoder().encode(str));
|
|
537
|
-
});
|
|
522
|
+
/**
|
|
523
|
+
* Returns a cryptographically secure random string.
|
|
524
|
+
*/
|
|
525
|
+
function getRandomString() {
|
|
526
|
+
const randomItems = new Uint32Array(28);
|
|
527
|
+
crypto.getRandomValues(randomItems);
|
|
528
|
+
return arrayBufferToHex(randomItems.buffer);
|
|
529
|
+
}
|
|
530
|
+
/**
|
|
531
|
+
* Encrypts a string with SHA256 encryption.
|
|
532
|
+
* @param str
|
|
533
|
+
*/
|
|
534
|
+
function encryptSHA256(str) {
|
|
535
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
536
|
+
return crypto.subtle.digest('SHA-256', new TextEncoder().encode(str));
|
|
537
|
+
});
|
|
538
538
|
}
|
|
539
539
|
|
|
540
|
-
/*
|
|
541
|
-
* Based on: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget
|
|
542
|
-
*/
|
|
543
|
-
var _EventTarget_listeners;
|
|
544
|
-
class EventTarget {
|
|
545
|
-
constructor() {
|
|
546
|
-
_EventTarget_listeners.set(this, void 0);
|
|
547
|
-
__classPrivateFieldSet(this, _EventTarget_listeners, {}, "f");
|
|
548
|
-
}
|
|
549
|
-
addEventListener(type, callback) {
|
|
550
|
-
if (!__classPrivateFieldGet(this, _EventTarget_listeners, "f")[type]) {
|
|
551
|
-
__classPrivateFieldGet(this, _EventTarget_listeners, "f")[type] = [];
|
|
552
|
-
}
|
|
553
|
-
__classPrivateFieldGet(this, _EventTarget_listeners, "f")[type].push(callback);
|
|
554
|
-
}
|
|
555
|
-
removeEventListeneer(type, callback) {
|
|
556
|
-
const array = __classPrivateFieldGet(this, _EventTarget_listeners, "f")[type];
|
|
557
|
-
if (!array) {
|
|
558
|
-
return;
|
|
559
|
-
}
|
|
560
|
-
for (let i = 0; i < array.length; i++) {
|
|
561
|
-
if (array[i] === callback) {
|
|
562
|
-
array.splice(i, 1);
|
|
563
|
-
return;
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
dispatchEvent(event) {
|
|
568
|
-
const array = __classPrivateFieldGet(this, _EventTarget_listeners, "f")[event.type];
|
|
569
|
-
if (array) {
|
|
570
|
-
array.forEach((listener) => listener.call(this, event));
|
|
571
|
-
}
|
|
572
|
-
return !event.defaultPrevented;
|
|
573
|
-
}
|
|
574
|
-
}
|
|
540
|
+
/*
|
|
541
|
+
* Based on: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget
|
|
542
|
+
*/
|
|
543
|
+
var _EventTarget_listeners;
|
|
544
|
+
class EventTarget {
|
|
545
|
+
constructor() {
|
|
546
|
+
_EventTarget_listeners.set(this, void 0);
|
|
547
|
+
__classPrivateFieldSet(this, _EventTarget_listeners, {}, "f");
|
|
548
|
+
}
|
|
549
|
+
addEventListener(type, callback) {
|
|
550
|
+
if (!__classPrivateFieldGet(this, _EventTarget_listeners, "f")[type]) {
|
|
551
|
+
__classPrivateFieldGet(this, _EventTarget_listeners, "f")[type] = [];
|
|
552
|
+
}
|
|
553
|
+
__classPrivateFieldGet(this, _EventTarget_listeners, "f")[type].push(callback);
|
|
554
|
+
}
|
|
555
|
+
removeEventListeneer(type, callback) {
|
|
556
|
+
const array = __classPrivateFieldGet(this, _EventTarget_listeners, "f")[type];
|
|
557
|
+
if (!array) {
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
for (let i = 0; i < array.length; i++) {
|
|
561
|
+
if (array[i] === callback) {
|
|
562
|
+
array.splice(i, 1);
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
dispatchEvent(event) {
|
|
568
|
+
const array = __classPrivateFieldGet(this, _EventTarget_listeners, "f")[event.type];
|
|
569
|
+
if (array) {
|
|
570
|
+
array.forEach((listener) => listener.call(this, event));
|
|
571
|
+
}
|
|
572
|
+
return !event.defaultPrevented;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
575
|
_EventTarget_listeners = new WeakMap();
|
|
576
576
|
|
|
577
|
-
/**
|
|
578
|
-
* Decodes a section of a JWT.
|
|
579
|
-
* See: https://tools.ietf.org/html/rfc7519
|
|
580
|
-
* @param payload
|
|
581
|
-
*/
|
|
582
|
-
function decodePayload(payload) {
|
|
583
|
-
const cleanedPayload = payload.replace(/-/g, '+').replace(/_/g, '/');
|
|
584
|
-
const decodedPayload = decodeBase64(cleanedPayload);
|
|
585
|
-
const uriEncodedPayload = Array.from(decodedPayload).reduce((acc, char) => {
|
|
586
|
-
const uriEncodedChar = ('00' + char.charCodeAt(0).toString(16)).slice(-2);
|
|
587
|
-
return `${acc}%${uriEncodedChar}`;
|
|
588
|
-
}, '');
|
|
589
|
-
const jsonPayload = decodeURIComponent(uriEncodedPayload);
|
|
590
|
-
return JSON.parse(jsonPayload);
|
|
591
|
-
}
|
|
592
|
-
function decodeBase64(data) {
|
|
593
|
-
if (typeof window !== 'undefined') {
|
|
594
|
-
return window.atob(data);
|
|
595
|
-
}
|
|
596
|
-
if (typeof Buffer !== 'undefined') {
|
|
597
|
-
return Buffer.from(data, 'base64').toString('binary');
|
|
598
|
-
}
|
|
599
|
-
throw new Error('Unable to decode base64');
|
|
600
|
-
}
|
|
601
|
-
/**
|
|
602
|
-
* Parses the JWT payload.
|
|
603
|
-
* @param token JWT token
|
|
604
|
-
*/
|
|
605
|
-
function parseJWTPayload(token) {
|
|
606
|
-
const [_header, payload, _signature] = token.split('.');
|
|
607
|
-
return decodePayload(payload);
|
|
577
|
+
/**
|
|
578
|
+
* Decodes a section of a JWT.
|
|
579
|
+
* See: https://tools.ietf.org/html/rfc7519
|
|
580
|
+
* @param payload
|
|
581
|
+
*/
|
|
582
|
+
function decodePayload(payload) {
|
|
583
|
+
const cleanedPayload = payload.replace(/-/g, '+').replace(/_/g, '/');
|
|
584
|
+
const decodedPayload = decodeBase64(cleanedPayload);
|
|
585
|
+
const uriEncodedPayload = Array.from(decodedPayload).reduce((acc, char) => {
|
|
586
|
+
const uriEncodedChar = ('00' + char.charCodeAt(0).toString(16)).slice(-2);
|
|
587
|
+
return `${acc}%${uriEncodedChar}`;
|
|
588
|
+
}, '');
|
|
589
|
+
const jsonPayload = decodeURIComponent(uriEncodedPayload);
|
|
590
|
+
return JSON.parse(jsonPayload);
|
|
591
|
+
}
|
|
592
|
+
function decodeBase64(data) {
|
|
593
|
+
if (typeof window !== 'undefined') {
|
|
594
|
+
return window.atob(data);
|
|
595
|
+
}
|
|
596
|
+
if (typeof Buffer !== 'undefined') {
|
|
597
|
+
return Buffer.from(data, 'base64').toString('binary');
|
|
598
|
+
}
|
|
599
|
+
throw new Error('Unable to decode base64');
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* Parses the JWT payload.
|
|
603
|
+
* @param token JWT token
|
|
604
|
+
*/
|
|
605
|
+
function parseJWTPayload(token) {
|
|
606
|
+
const [_header, payload, _signature] = token.split('.');
|
|
607
|
+
return decodePayload(payload);
|
|
608
608
|
}
|
|
609
609
|
|
|
610
|
-
const OK_ID = 'ok';
|
|
611
|
-
const CREATED_ID = 'created';
|
|
612
|
-
const GONE_ID = 'gone';
|
|
613
|
-
const NOT_MODIFIED_ID = 'not-modified';
|
|
614
|
-
const NOT_FOUND_ID = 'not-found';
|
|
615
|
-
const ACCESS_DENIED = 'access-denied';
|
|
616
|
-
const allOk = {
|
|
617
|
-
resourceType: 'OperationOutcome',
|
|
618
|
-
id: OK_ID,
|
|
619
|
-
issue: [
|
|
620
|
-
{
|
|
621
|
-
severity: 'information',
|
|
622
|
-
code: 'information',
|
|
623
|
-
details: {
|
|
624
|
-
text: 'All OK',
|
|
625
|
-
},
|
|
626
|
-
},
|
|
627
|
-
],
|
|
628
|
-
};
|
|
629
|
-
const created = {
|
|
630
|
-
resourceType: 'OperationOutcome',
|
|
631
|
-
id: CREATED_ID,
|
|
632
|
-
issue: [
|
|
633
|
-
{
|
|
634
|
-
severity: 'information',
|
|
635
|
-
code: 'information',
|
|
636
|
-
details: {
|
|
637
|
-
text: 'Created',
|
|
638
|
-
},
|
|
639
|
-
},
|
|
640
|
-
],
|
|
641
|
-
};
|
|
642
|
-
const notModified = {
|
|
643
|
-
resourceType: 'OperationOutcome',
|
|
644
|
-
id: NOT_MODIFIED_ID,
|
|
645
|
-
issue: [
|
|
646
|
-
{
|
|
647
|
-
severity: 'information',
|
|
648
|
-
code: 'information',
|
|
649
|
-
details: {
|
|
650
|
-
text: 'Not Modified',
|
|
651
|
-
},
|
|
652
|
-
},
|
|
653
|
-
],
|
|
654
|
-
};
|
|
655
|
-
const notFound = {
|
|
656
|
-
resourceType: 'OperationOutcome',
|
|
657
|
-
id: NOT_FOUND_ID,
|
|
658
|
-
issue: [
|
|
659
|
-
{
|
|
660
|
-
severity: 'error',
|
|
661
|
-
code: 'not-found',
|
|
662
|
-
details: {
|
|
663
|
-
text: 'Not found',
|
|
664
|
-
},
|
|
665
|
-
},
|
|
666
|
-
],
|
|
667
|
-
};
|
|
668
|
-
const gone = {
|
|
669
|
-
resourceType: 'OperationOutcome',
|
|
670
|
-
id: GONE_ID,
|
|
671
|
-
issue: [
|
|
672
|
-
{
|
|
673
|
-
severity: 'error',
|
|
674
|
-
code: 'gone',
|
|
675
|
-
details: {
|
|
676
|
-
text: 'Gone',
|
|
677
|
-
},
|
|
678
|
-
},
|
|
679
|
-
],
|
|
680
|
-
};
|
|
681
|
-
const accessDenied = {
|
|
682
|
-
resourceType: 'OperationOutcome',
|
|
683
|
-
id: ACCESS_DENIED,
|
|
684
|
-
issue: [
|
|
685
|
-
{
|
|
686
|
-
severity: 'error',
|
|
687
|
-
code: 'access-denied',
|
|
688
|
-
details: {
|
|
689
|
-
text: 'Access Denied',
|
|
690
|
-
},
|
|
691
|
-
},
|
|
692
|
-
],
|
|
693
|
-
};
|
|
694
|
-
function badRequest(details, expression) {
|
|
695
|
-
return {
|
|
696
|
-
resourceType: 'OperationOutcome',
|
|
697
|
-
issue: [
|
|
698
|
-
{
|
|
699
|
-
severity: 'error',
|
|
700
|
-
code: 'invalid',
|
|
701
|
-
details: {
|
|
702
|
-
text: details,
|
|
703
|
-
},
|
|
704
|
-
expression: expression ? [expression] : undefined,
|
|
705
|
-
},
|
|
706
|
-
],
|
|
707
|
-
};
|
|
708
|
-
}
|
|
709
|
-
function isOk(outcome) {
|
|
710
|
-
return outcome.id === OK_ID || outcome.id === CREATED_ID || outcome.id === NOT_MODIFIED_ID;
|
|
711
|
-
}
|
|
712
|
-
function isNotFound(outcome) {
|
|
713
|
-
return outcome.id === NOT_FOUND_ID;
|
|
714
|
-
}
|
|
715
|
-
function isGone(outcome) {
|
|
716
|
-
return outcome.id === GONE_ID;
|
|
717
|
-
}
|
|
718
|
-
function getStatus(outcome) {
|
|
719
|
-
if (outcome.id === OK_ID) {
|
|
720
|
-
return 200;
|
|
721
|
-
}
|
|
722
|
-
else if (outcome.id === CREATED_ID) {
|
|
723
|
-
return 201;
|
|
724
|
-
}
|
|
725
|
-
else if (outcome.id === NOT_MODIFIED_ID) {
|
|
726
|
-
return 304;
|
|
727
|
-
}
|
|
728
|
-
else if (outcome.id === ACCESS_DENIED) {
|
|
729
|
-
return 403;
|
|
730
|
-
}
|
|
731
|
-
else if (outcome.id === NOT_FOUND_ID) {
|
|
732
|
-
return 404;
|
|
733
|
-
}
|
|
734
|
-
else if (outcome.id === GONE_ID) {
|
|
735
|
-
return 410;
|
|
736
|
-
}
|
|
737
|
-
else {
|
|
738
|
-
return 400;
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
/**
|
|
742
|
-
* Asserts that the operation completed successfully and that the resource is defined.
|
|
743
|
-
* @param outcome The operation outcome.
|
|
744
|
-
* @param resource The resource that may or may not have been returned.
|
|
745
|
-
*/
|
|
746
|
-
function assertOk(outcome, resource) {
|
|
747
|
-
if (!isOk(outcome) || resource === undefined) {
|
|
748
|
-
throw new OperationOutcomeError(outcome);
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
class OperationOutcomeError extends Error {
|
|
752
|
-
constructor(outcome) {
|
|
753
|
-
var _a, _b;
|
|
754
|
-
super((_b = (_a = outcome === null || outcome === void 0 ? void 0 : outcome.issue) === null || _a === void 0 ? void 0 : _a[0].details) === null || _b === void 0 ? void 0 : _b.text);
|
|
755
|
-
this.outcome = outcome;
|
|
756
|
-
}
|
|
610
|
+
const OK_ID = 'ok';
|
|
611
|
+
const CREATED_ID = 'created';
|
|
612
|
+
const GONE_ID = 'gone';
|
|
613
|
+
const NOT_MODIFIED_ID = 'not-modified';
|
|
614
|
+
const NOT_FOUND_ID = 'not-found';
|
|
615
|
+
const ACCESS_DENIED = 'access-denied';
|
|
616
|
+
const allOk = {
|
|
617
|
+
resourceType: 'OperationOutcome',
|
|
618
|
+
id: OK_ID,
|
|
619
|
+
issue: [
|
|
620
|
+
{
|
|
621
|
+
severity: 'information',
|
|
622
|
+
code: 'information',
|
|
623
|
+
details: {
|
|
624
|
+
text: 'All OK',
|
|
625
|
+
},
|
|
626
|
+
},
|
|
627
|
+
],
|
|
628
|
+
};
|
|
629
|
+
const created = {
|
|
630
|
+
resourceType: 'OperationOutcome',
|
|
631
|
+
id: CREATED_ID,
|
|
632
|
+
issue: [
|
|
633
|
+
{
|
|
634
|
+
severity: 'information',
|
|
635
|
+
code: 'information',
|
|
636
|
+
details: {
|
|
637
|
+
text: 'Created',
|
|
638
|
+
},
|
|
639
|
+
},
|
|
640
|
+
],
|
|
641
|
+
};
|
|
642
|
+
const notModified = {
|
|
643
|
+
resourceType: 'OperationOutcome',
|
|
644
|
+
id: NOT_MODIFIED_ID,
|
|
645
|
+
issue: [
|
|
646
|
+
{
|
|
647
|
+
severity: 'information',
|
|
648
|
+
code: 'information',
|
|
649
|
+
details: {
|
|
650
|
+
text: 'Not Modified',
|
|
651
|
+
},
|
|
652
|
+
},
|
|
653
|
+
],
|
|
654
|
+
};
|
|
655
|
+
const notFound = {
|
|
656
|
+
resourceType: 'OperationOutcome',
|
|
657
|
+
id: NOT_FOUND_ID,
|
|
658
|
+
issue: [
|
|
659
|
+
{
|
|
660
|
+
severity: 'error',
|
|
661
|
+
code: 'not-found',
|
|
662
|
+
details: {
|
|
663
|
+
text: 'Not found',
|
|
664
|
+
},
|
|
665
|
+
},
|
|
666
|
+
],
|
|
667
|
+
};
|
|
668
|
+
const gone = {
|
|
669
|
+
resourceType: 'OperationOutcome',
|
|
670
|
+
id: GONE_ID,
|
|
671
|
+
issue: [
|
|
672
|
+
{
|
|
673
|
+
severity: 'error',
|
|
674
|
+
code: 'gone',
|
|
675
|
+
details: {
|
|
676
|
+
text: 'Gone',
|
|
677
|
+
},
|
|
678
|
+
},
|
|
679
|
+
],
|
|
680
|
+
};
|
|
681
|
+
const accessDenied = {
|
|
682
|
+
resourceType: 'OperationOutcome',
|
|
683
|
+
id: ACCESS_DENIED,
|
|
684
|
+
issue: [
|
|
685
|
+
{
|
|
686
|
+
severity: 'error',
|
|
687
|
+
code: 'access-denied',
|
|
688
|
+
details: {
|
|
689
|
+
text: 'Access Denied',
|
|
690
|
+
},
|
|
691
|
+
},
|
|
692
|
+
],
|
|
693
|
+
};
|
|
694
|
+
function badRequest(details, expression) {
|
|
695
|
+
return {
|
|
696
|
+
resourceType: 'OperationOutcome',
|
|
697
|
+
issue: [
|
|
698
|
+
{
|
|
699
|
+
severity: 'error',
|
|
700
|
+
code: 'invalid',
|
|
701
|
+
details: {
|
|
702
|
+
text: details,
|
|
703
|
+
},
|
|
704
|
+
expression: expression ? [expression] : undefined,
|
|
705
|
+
},
|
|
706
|
+
],
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
function isOk(outcome) {
|
|
710
|
+
return outcome.id === OK_ID || outcome.id === CREATED_ID || outcome.id === NOT_MODIFIED_ID;
|
|
711
|
+
}
|
|
712
|
+
function isNotFound(outcome) {
|
|
713
|
+
return outcome.id === NOT_FOUND_ID;
|
|
714
|
+
}
|
|
715
|
+
function isGone(outcome) {
|
|
716
|
+
return outcome.id === GONE_ID;
|
|
717
|
+
}
|
|
718
|
+
function getStatus(outcome) {
|
|
719
|
+
if (outcome.id === OK_ID) {
|
|
720
|
+
return 200;
|
|
721
|
+
}
|
|
722
|
+
else if (outcome.id === CREATED_ID) {
|
|
723
|
+
return 201;
|
|
724
|
+
}
|
|
725
|
+
else if (outcome.id === NOT_MODIFIED_ID) {
|
|
726
|
+
return 304;
|
|
727
|
+
}
|
|
728
|
+
else if (outcome.id === ACCESS_DENIED) {
|
|
729
|
+
return 403;
|
|
730
|
+
}
|
|
731
|
+
else if (outcome.id === NOT_FOUND_ID) {
|
|
732
|
+
return 404;
|
|
733
|
+
}
|
|
734
|
+
else if (outcome.id === GONE_ID) {
|
|
735
|
+
return 410;
|
|
736
|
+
}
|
|
737
|
+
else {
|
|
738
|
+
return 400;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
/**
|
|
742
|
+
* Asserts that the operation completed successfully and that the resource is defined.
|
|
743
|
+
* @param outcome The operation outcome.
|
|
744
|
+
* @param resource The resource that may or may not have been returned.
|
|
745
|
+
*/
|
|
746
|
+
function assertOk(outcome, resource) {
|
|
747
|
+
if (!isOk(outcome) || resource === undefined) {
|
|
748
|
+
throw new OperationOutcomeError(outcome);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
class OperationOutcomeError extends Error {
|
|
752
|
+
constructor(outcome) {
|
|
753
|
+
var _a, _b;
|
|
754
|
+
super((_b = (_a = outcome === null || outcome === void 0 ? void 0 : outcome.issue) === null || _a === void 0 ? void 0 : _a[0].details) === null || _b === void 0 ? void 0 : _b.text);
|
|
755
|
+
this.outcome = outcome;
|
|
756
|
+
}
|
|
757
757
|
}
|
|
758
758
|
|
|
759
|
-
var _ReadablePromise_suspender, _ReadablePromise_status, _ReadablePromise_response, _ReadablePromise_error, _a;
|
|
760
|
-
/**
|
|
761
|
-
* The ReadablePromise class wraps a request promise suitable for React Suspense.
|
|
762
|
-
* See: https://blog.logrocket.com/react-suspense-data-fetching/#wrappromise-js
|
|
763
|
-
* See: https://github.com/ovieokeh/suspense-data-fetching/blob/master/lib/api/wrapPromise.js
|
|
764
|
-
*/
|
|
765
|
-
class ReadablePromise {
|
|
766
|
-
constructor(requestPromise) {
|
|
767
|
-
this[_a] = 'ReadablePromise';
|
|
768
|
-
_ReadablePromise_suspender.set(this, void 0);
|
|
769
|
-
_ReadablePromise_status.set(this, 'pending');
|
|
770
|
-
_ReadablePromise_response.set(this, void 0);
|
|
771
|
-
_ReadablePromise_error.set(this, void 0);
|
|
772
|
-
__classPrivateFieldSet(this, _ReadablePromise_suspender, requestPromise.then((res) => {
|
|
773
|
-
__classPrivateFieldSet(this, _ReadablePromise_status, 'success', "f");
|
|
774
|
-
__classPrivateFieldSet(this, _ReadablePromise_response, res, "f");
|
|
775
|
-
return res;
|
|
776
|
-
}, (err) => {
|
|
777
|
-
__classPrivateFieldSet(this, _ReadablePromise_status, 'error', "f");
|
|
778
|
-
__classPrivateFieldSet(this, _ReadablePromise_error, err, "f");
|
|
779
|
-
throw err;
|
|
780
|
-
}), "f");
|
|
781
|
-
}
|
|
782
|
-
/**
|
|
783
|
-
* Returns true if the promise is pending.
|
|
784
|
-
* @returns True if the Promise is pending.
|
|
785
|
-
*/
|
|
786
|
-
isPending() {
|
|
787
|
-
return __classPrivateFieldGet(this, _ReadablePromise_status, "f") === 'pending';
|
|
788
|
-
}
|
|
789
|
-
/**
|
|
790
|
-
* Attempts to read the value of the promise.
|
|
791
|
-
* If the promise is pending, this method will throw a promise.
|
|
792
|
-
* If the promise rejected, this method will throw the rejection reason.
|
|
793
|
-
* If the promise resolved, this method will return the resolved value.
|
|
794
|
-
* @returns The resolved value of the Promise.
|
|
795
|
-
*/
|
|
796
|
-
read() {
|
|
797
|
-
switch (__classPrivateFieldGet(this, _ReadablePromise_status, "f")) {
|
|
798
|
-
case 'pending':
|
|
799
|
-
throw __classPrivateFieldGet(this, _ReadablePromise_suspender, "f");
|
|
800
|
-
case 'error':
|
|
801
|
-
throw __classPrivateFieldGet(this, _ReadablePromise_error, "f");
|
|
802
|
-
default:
|
|
803
|
-
return __classPrivateFieldGet(this, _ReadablePromise_response, "f");
|
|
804
|
-
}
|
|
805
|
-
}
|
|
806
|
-
/**
|
|
807
|
-
* Attaches callbacks for the resolution and/or rejection of the Promise.
|
|
808
|
-
* @param onfulfilled The callback to execute when the Promise is resolved.
|
|
809
|
-
* @param onrejected The callback to execute when the Promise is rejected.
|
|
810
|
-
* @returns A Promise for the completion of which ever callback is executed.
|
|
811
|
-
*/
|
|
812
|
-
then(onfulfilled, onrejected) {
|
|
813
|
-
return __classPrivateFieldGet(this, _ReadablePromise_suspender, "f").then(onfulfilled, onrejected);
|
|
814
|
-
}
|
|
815
|
-
/**
|
|
816
|
-
* Attaches a callback for only the rejection of the Promise.
|
|
817
|
-
* @param onrejected The callback to execute when the Promise is rejected.
|
|
818
|
-
* @returns A Promise for the completion of the callback.
|
|
819
|
-
*/
|
|
820
|
-
catch(onrejected) {
|
|
821
|
-
return __classPrivateFieldGet(this, _ReadablePromise_suspender, "f").catch(onrejected);
|
|
822
|
-
}
|
|
823
|
-
/**
|
|
824
|
-
* Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The
|
|
825
|
-
* resolved value cannot be modified from the callback.
|
|
826
|
-
* @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected).
|
|
827
|
-
* @returns A Promise for the completion of the callback.
|
|
828
|
-
*/
|
|
829
|
-
finally(onfinally) {
|
|
830
|
-
return __classPrivateFieldGet(this, _ReadablePromise_suspender, "f").finally(onfinally);
|
|
831
|
-
}
|
|
832
|
-
}
|
|
759
|
+
var _ReadablePromise_suspender, _ReadablePromise_status, _ReadablePromise_response, _ReadablePromise_error, _a;
|
|
760
|
+
/**
|
|
761
|
+
* The ReadablePromise class wraps a request promise suitable for React Suspense.
|
|
762
|
+
* See: https://blog.logrocket.com/react-suspense-data-fetching/#wrappromise-js
|
|
763
|
+
* See: https://github.com/ovieokeh/suspense-data-fetching/blob/master/lib/api/wrapPromise.js
|
|
764
|
+
*/
|
|
765
|
+
class ReadablePromise {
|
|
766
|
+
constructor(requestPromise) {
|
|
767
|
+
this[_a] = 'ReadablePromise';
|
|
768
|
+
_ReadablePromise_suspender.set(this, void 0);
|
|
769
|
+
_ReadablePromise_status.set(this, 'pending');
|
|
770
|
+
_ReadablePromise_response.set(this, void 0);
|
|
771
|
+
_ReadablePromise_error.set(this, void 0);
|
|
772
|
+
__classPrivateFieldSet(this, _ReadablePromise_suspender, requestPromise.then((res) => {
|
|
773
|
+
__classPrivateFieldSet(this, _ReadablePromise_status, 'success', "f");
|
|
774
|
+
__classPrivateFieldSet(this, _ReadablePromise_response, res, "f");
|
|
775
|
+
return res;
|
|
776
|
+
}, (err) => {
|
|
777
|
+
__classPrivateFieldSet(this, _ReadablePromise_status, 'error', "f");
|
|
778
|
+
__classPrivateFieldSet(this, _ReadablePromise_error, err, "f");
|
|
779
|
+
throw err;
|
|
780
|
+
}), "f");
|
|
781
|
+
}
|
|
782
|
+
/**
|
|
783
|
+
* Returns true if the promise is pending.
|
|
784
|
+
* @returns True if the Promise is pending.
|
|
785
|
+
*/
|
|
786
|
+
isPending() {
|
|
787
|
+
return __classPrivateFieldGet(this, _ReadablePromise_status, "f") === 'pending';
|
|
788
|
+
}
|
|
789
|
+
/**
|
|
790
|
+
* Attempts to read the value of the promise.
|
|
791
|
+
* If the promise is pending, this method will throw a promise.
|
|
792
|
+
* If the promise rejected, this method will throw the rejection reason.
|
|
793
|
+
* If the promise resolved, this method will return the resolved value.
|
|
794
|
+
* @returns The resolved value of the Promise.
|
|
795
|
+
*/
|
|
796
|
+
read() {
|
|
797
|
+
switch (__classPrivateFieldGet(this, _ReadablePromise_status, "f")) {
|
|
798
|
+
case 'pending':
|
|
799
|
+
throw __classPrivateFieldGet(this, _ReadablePromise_suspender, "f");
|
|
800
|
+
case 'error':
|
|
801
|
+
throw __classPrivateFieldGet(this, _ReadablePromise_error, "f");
|
|
802
|
+
default:
|
|
803
|
+
return __classPrivateFieldGet(this, _ReadablePromise_response, "f");
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
/**
|
|
807
|
+
* Attaches callbacks for the resolution and/or rejection of the Promise.
|
|
808
|
+
* @param onfulfilled The callback to execute when the Promise is resolved.
|
|
809
|
+
* @param onrejected The callback to execute when the Promise is rejected.
|
|
810
|
+
* @returns A Promise for the completion of which ever callback is executed.
|
|
811
|
+
*/
|
|
812
|
+
then(onfulfilled, onrejected) {
|
|
813
|
+
return __classPrivateFieldGet(this, _ReadablePromise_suspender, "f").then(onfulfilled, onrejected);
|
|
814
|
+
}
|
|
815
|
+
/**
|
|
816
|
+
* Attaches a callback for only the rejection of the Promise.
|
|
817
|
+
* @param onrejected The callback to execute when the Promise is rejected.
|
|
818
|
+
* @returns A Promise for the completion of the callback.
|
|
819
|
+
*/
|
|
820
|
+
catch(onrejected) {
|
|
821
|
+
return __classPrivateFieldGet(this, _ReadablePromise_suspender, "f").catch(onrejected);
|
|
822
|
+
}
|
|
823
|
+
/**
|
|
824
|
+
* Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The
|
|
825
|
+
* resolved value cannot be modified from the callback.
|
|
826
|
+
* @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected).
|
|
827
|
+
* @returns A Promise for the completion of the callback.
|
|
828
|
+
*/
|
|
829
|
+
finally(onfinally) {
|
|
830
|
+
return __classPrivateFieldGet(this, _ReadablePromise_suspender, "f").finally(onfinally);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
833
|
_ReadablePromise_suspender = new WeakMap(), _ReadablePromise_status = new WeakMap(), _ReadablePromise_response = new WeakMap(), _ReadablePromise_error = new WeakMap(), _a = Symbol.toStringTag;
|
|
834
834
|
|
|
835
|
-
const DEFAULT_SEARCH_COUNT = 20;
|
|
836
|
-
/**
|
|
837
|
-
* Search operators.
|
|
838
|
-
* These operators represent "modifiers" and "prefixes" in FHIR search.
|
|
839
|
-
* See: https://www.hl7.org/fhir/search.html
|
|
840
|
-
*/
|
|
841
|
-
var Operator;
|
|
842
|
-
(function (Operator) {
|
|
843
|
-
Operator["EQUALS"] = "eq";
|
|
844
|
-
Operator["NOT_EQUALS"] = "ne";
|
|
845
|
-
// Numbers
|
|
846
|
-
Operator["GREATER_THAN"] = "gt";
|
|
847
|
-
Operator["LESS_THAN"] = "lt";
|
|
848
|
-
Operator["GREATER_THAN_OR_EQUALS"] = "ge";
|
|
849
|
-
Operator["LESS_THAN_OR_EQUALS"] = "le";
|
|
850
|
-
// Dates
|
|
851
|
-
Operator["STARTS_AFTER"] = "sa";
|
|
852
|
-
Operator["ENDS_BEFORE"] = "eb";
|
|
853
|
-
Operator["APPROXIMATELY"] = "ap";
|
|
854
|
-
// String
|
|
855
|
-
Operator["CONTAINS"] = "contains";
|
|
856
|
-
Operator["EXACT"] = "exact";
|
|
857
|
-
// Token
|
|
858
|
-
Operator["TEXT"] = "text";
|
|
859
|
-
Operator["ABOVE"] = "above";
|
|
860
|
-
Operator["BELOW"] = "below";
|
|
861
|
-
Operator["IN"] = "in";
|
|
862
|
-
Operator["NOT_IN"] = "not-in";
|
|
863
|
-
Operator["OF_TYPE"] = "of-type";
|
|
864
|
-
})(Operator || (Operator = {}));
|
|
865
|
-
const MODIFIER_OPERATORS = [
|
|
866
|
-
Operator.CONTAINS,
|
|
867
|
-
Operator.EXACT,
|
|
868
|
-
Operator.TEXT,
|
|
869
|
-
Operator.ABOVE,
|
|
870
|
-
Operator.BELOW,
|
|
871
|
-
Operator.IN,
|
|
872
|
-
Operator.NOT_IN,
|
|
873
|
-
Operator.OF_TYPE,
|
|
874
|
-
];
|
|
875
|
-
const PREFIX_OPERATORS = [
|
|
876
|
-
Operator.NOT_EQUALS,
|
|
877
|
-
Operator.GREATER_THAN,
|
|
878
|
-
Operator.LESS_THAN,
|
|
879
|
-
Operator.GREATER_THAN_OR_EQUALS,
|
|
880
|
-
Operator.LESS_THAN_OR_EQUALS,
|
|
881
|
-
Operator.STARTS_AFTER,
|
|
882
|
-
Operator.ENDS_BEFORE,
|
|
883
|
-
Operator.APPROXIMATELY,
|
|
884
|
-
];
|
|
885
|
-
/**
|
|
886
|
-
* Parses a URL into a SearchRequest.
|
|
887
|
-
*
|
|
888
|
-
* See the FHIR search spec: http://hl7.org/fhir/r4/search.html
|
|
889
|
-
*
|
|
890
|
-
* @param url The URL to parse.
|
|
891
|
-
* @returns Parsed search definition.
|
|
892
|
-
*/
|
|
893
|
-
function parseSearchDefinition(url) {
|
|
894
|
-
const location = new URL(url, 'https://example.com/');
|
|
895
|
-
const resourceType = location.pathname
|
|
896
|
-
.replace(/(^\/)|(\/$)/g, '') // Remove leading and trailing slashes
|
|
897
|
-
.split('/')
|
|
898
|
-
.pop();
|
|
899
|
-
const params = new URLSearchParams(location.search);
|
|
900
|
-
let filters = undefined;
|
|
901
|
-
let sortRules = undefined;
|
|
902
|
-
let fields = undefined;
|
|
903
|
-
let offset = undefined;
|
|
904
|
-
let count = undefined;
|
|
905
|
-
let total = undefined;
|
|
906
|
-
params.forEach((value, key) => {
|
|
907
|
-
if (key === '_fields') {
|
|
908
|
-
fields = value.split(',');
|
|
909
|
-
}
|
|
910
|
-
else if (key === '_offset') {
|
|
911
|
-
offset = parseInt(value);
|
|
912
|
-
}
|
|
913
|
-
else if (key === '_count') {
|
|
914
|
-
count = parseInt(value);
|
|
915
|
-
}
|
|
916
|
-
else if (key === '_total') {
|
|
917
|
-
total = value;
|
|
918
|
-
}
|
|
919
|
-
else if (key === '_sort') {
|
|
920
|
-
sortRules = sortRules || [];
|
|
921
|
-
sortRules.push(parseSortRule(value));
|
|
922
|
-
}
|
|
923
|
-
else {
|
|
924
|
-
filters = filters || [];
|
|
925
|
-
filters.push(parseSearchFilter(key, value));
|
|
926
|
-
}
|
|
927
|
-
});
|
|
928
|
-
return {
|
|
929
|
-
resourceType,
|
|
930
|
-
filters,
|
|
931
|
-
fields,
|
|
932
|
-
offset,
|
|
933
|
-
count,
|
|
934
|
-
total,
|
|
935
|
-
sortRules,
|
|
936
|
-
};
|
|
937
|
-
}
|
|
938
|
-
/**
|
|
939
|
-
* Parses a URL query parameter into a sort rule.
|
|
940
|
-
*
|
|
941
|
-
* By default, the sort rule is the field name.
|
|
942
|
-
*
|
|
943
|
-
* Sort rules can be reversed into descending order by prefixing the field name with a minus sign.
|
|
944
|
-
*
|
|
945
|
-
* See sorting: http://hl7.org/fhir/r4/search.html#_sort
|
|
946
|
-
*
|
|
947
|
-
* @param value The URL parameter value.
|
|
948
|
-
* @returns The parsed sort rule.
|
|
949
|
-
*/
|
|
950
|
-
function parseSortRule(value) {
|
|
951
|
-
if (value.startsWith('-')) {
|
|
952
|
-
return { code: value.substring(1), descending: true };
|
|
953
|
-
}
|
|
954
|
-
else {
|
|
955
|
-
return { code: value };
|
|
956
|
-
}
|
|
957
|
-
}
|
|
958
|
-
/**
|
|
959
|
-
* Parses a URL query parameter into a search filter.
|
|
960
|
-
*
|
|
961
|
-
* FHIR search filters can be specified as modifiers or prefixes.
|
|
962
|
-
*
|
|
963
|
-
* For string properties, modifiers are appended to the key, e.g. "name:contains=eve".
|
|
964
|
-
*
|
|
965
|
-
* For date and numeric properties, prefixes are prepended to the value, e.g. "birthdate=gt2000".
|
|
966
|
-
*
|
|
967
|
-
* See the FHIR search spec: http://hl7.org/fhir/r4/search.html
|
|
968
|
-
*
|
|
969
|
-
* @param key The URL parameter key.
|
|
970
|
-
* @param value The URL parameter value.
|
|
971
|
-
* @returns The parsed search filter.
|
|
972
|
-
*/
|
|
973
|
-
function parseSearchFilter(key, value) {
|
|
974
|
-
let code = key;
|
|
975
|
-
let operator = Operator.EQUALS;
|
|
976
|
-
for (const modifier of MODIFIER_OPERATORS) {
|
|
977
|
-
const modifierIndex = code.indexOf(':' + modifier);
|
|
978
|
-
if (modifierIndex !== -1) {
|
|
979
|
-
operator = modifier;
|
|
980
|
-
code = code.substring(0, modifierIndex);
|
|
981
|
-
}
|
|
982
|
-
}
|
|
983
|
-
for (const prefix of PREFIX_OPERATORS) {
|
|
984
|
-
if (value.match(new RegExp('^' + prefix + '\\d'))) {
|
|
985
|
-
operator = prefix;
|
|
986
|
-
value = value.substring(prefix.length);
|
|
987
|
-
}
|
|
988
|
-
}
|
|
989
|
-
return { code, operator, value };
|
|
990
|
-
}
|
|
991
|
-
/**
|
|
992
|
-
* Formats a search definition object into a query string.
|
|
993
|
-
* Note: The return value does not include the resource type.
|
|
994
|
-
* @param {!SearchRequest} definition The search definition.
|
|
995
|
-
* @returns Formatted URL.
|
|
996
|
-
*/
|
|
997
|
-
function formatSearchQuery(definition) {
|
|
998
|
-
const params = [];
|
|
999
|
-
if (definition.fields) {
|
|
1000
|
-
params.push('_fields=' + definition.fields.join(','));
|
|
1001
|
-
}
|
|
1002
|
-
if (definition.filters) {
|
|
1003
|
-
definition.filters.forEach((filter) => params.push(formatFilter(filter)));
|
|
1004
|
-
}
|
|
1005
|
-
if (definition.sortRules && definition.sortRules.length > 0) {
|
|
1006
|
-
params.push(formatSortRules(definition.sortRules));
|
|
1007
|
-
}
|
|
1008
|
-
if (definition.offset !== undefined) {
|
|
1009
|
-
params.push('_offset=' + definition.offset);
|
|
1010
|
-
}
|
|
1011
|
-
if (definition.count !== undefined) {
|
|
1012
|
-
params.push('_count=' + definition.count);
|
|
1013
|
-
}
|
|
1014
|
-
if (definition.total !== undefined) {
|
|
1015
|
-
params.push('_total=' + encodeURIComponent(definition.total));
|
|
1016
|
-
}
|
|
1017
|
-
if (params.length === 0) {
|
|
1018
|
-
return '';
|
|
1019
|
-
}
|
|
1020
|
-
params.sort();
|
|
1021
|
-
return '?' + params.join('&');
|
|
1022
|
-
}
|
|
1023
|
-
function formatFilter(filter) {
|
|
1024
|
-
const modifier = MODIFIER_OPERATORS.includes(filter.operator) ? ':' + filter.operator : '';
|
|
1025
|
-
const prefix = PREFIX_OPERATORS.includes(filter.operator) ? filter.operator : '';
|
|
1026
|
-
return `${filter.code}${modifier}=${prefix}${encodeURIComponent(filter.value)}`;
|
|
1027
|
-
}
|
|
1028
|
-
function formatSortRules(sortRules) {
|
|
1029
|
-
if (!sortRules || sortRules.length === 0) {
|
|
1030
|
-
return '';
|
|
1031
|
-
}
|
|
1032
|
-
return '_sort=' + sortRules.map((sr) => (sr.descending ? '-' + sr.code : sr.code)).join(',');
|
|
835
|
+
const DEFAULT_SEARCH_COUNT = 20;
|
|
836
|
+
/**
|
|
837
|
+
* Search operators.
|
|
838
|
+
* These operators represent "modifiers" and "prefixes" in FHIR search.
|
|
839
|
+
* See: https://www.hl7.org/fhir/search.html
|
|
840
|
+
*/
|
|
841
|
+
var Operator;
|
|
842
|
+
(function (Operator) {
|
|
843
|
+
Operator["EQUALS"] = "eq";
|
|
844
|
+
Operator["NOT_EQUALS"] = "ne";
|
|
845
|
+
// Numbers
|
|
846
|
+
Operator["GREATER_THAN"] = "gt";
|
|
847
|
+
Operator["LESS_THAN"] = "lt";
|
|
848
|
+
Operator["GREATER_THAN_OR_EQUALS"] = "ge";
|
|
849
|
+
Operator["LESS_THAN_OR_EQUALS"] = "le";
|
|
850
|
+
// Dates
|
|
851
|
+
Operator["STARTS_AFTER"] = "sa";
|
|
852
|
+
Operator["ENDS_BEFORE"] = "eb";
|
|
853
|
+
Operator["APPROXIMATELY"] = "ap";
|
|
854
|
+
// String
|
|
855
|
+
Operator["CONTAINS"] = "contains";
|
|
856
|
+
Operator["EXACT"] = "exact";
|
|
857
|
+
// Token
|
|
858
|
+
Operator["TEXT"] = "text";
|
|
859
|
+
Operator["ABOVE"] = "above";
|
|
860
|
+
Operator["BELOW"] = "below";
|
|
861
|
+
Operator["IN"] = "in";
|
|
862
|
+
Operator["NOT_IN"] = "not-in";
|
|
863
|
+
Operator["OF_TYPE"] = "of-type";
|
|
864
|
+
})(Operator || (Operator = {}));
|
|
865
|
+
const MODIFIER_OPERATORS = [
|
|
866
|
+
Operator.CONTAINS,
|
|
867
|
+
Operator.EXACT,
|
|
868
|
+
Operator.TEXT,
|
|
869
|
+
Operator.ABOVE,
|
|
870
|
+
Operator.BELOW,
|
|
871
|
+
Operator.IN,
|
|
872
|
+
Operator.NOT_IN,
|
|
873
|
+
Operator.OF_TYPE,
|
|
874
|
+
];
|
|
875
|
+
const PREFIX_OPERATORS = [
|
|
876
|
+
Operator.NOT_EQUALS,
|
|
877
|
+
Operator.GREATER_THAN,
|
|
878
|
+
Operator.LESS_THAN,
|
|
879
|
+
Operator.GREATER_THAN_OR_EQUALS,
|
|
880
|
+
Operator.LESS_THAN_OR_EQUALS,
|
|
881
|
+
Operator.STARTS_AFTER,
|
|
882
|
+
Operator.ENDS_BEFORE,
|
|
883
|
+
Operator.APPROXIMATELY,
|
|
884
|
+
];
|
|
885
|
+
/**
|
|
886
|
+
* Parses a URL into a SearchRequest.
|
|
887
|
+
*
|
|
888
|
+
* See the FHIR search spec: http://hl7.org/fhir/r4/search.html
|
|
889
|
+
*
|
|
890
|
+
* @param url The URL to parse.
|
|
891
|
+
* @returns Parsed search definition.
|
|
892
|
+
*/
|
|
893
|
+
function parseSearchDefinition(url) {
|
|
894
|
+
const location = new URL(url, 'https://example.com/');
|
|
895
|
+
const resourceType = location.pathname
|
|
896
|
+
.replace(/(^\/)|(\/$)/g, '') // Remove leading and trailing slashes
|
|
897
|
+
.split('/')
|
|
898
|
+
.pop();
|
|
899
|
+
const params = new URLSearchParams(location.search);
|
|
900
|
+
let filters = undefined;
|
|
901
|
+
let sortRules = undefined;
|
|
902
|
+
let fields = undefined;
|
|
903
|
+
let offset = undefined;
|
|
904
|
+
let count = undefined;
|
|
905
|
+
let total = undefined;
|
|
906
|
+
params.forEach((value, key) => {
|
|
907
|
+
if (key === '_fields') {
|
|
908
|
+
fields = value.split(',');
|
|
909
|
+
}
|
|
910
|
+
else if (key === '_offset') {
|
|
911
|
+
offset = parseInt(value);
|
|
912
|
+
}
|
|
913
|
+
else if (key === '_count') {
|
|
914
|
+
count = parseInt(value);
|
|
915
|
+
}
|
|
916
|
+
else if (key === '_total') {
|
|
917
|
+
total = value;
|
|
918
|
+
}
|
|
919
|
+
else if (key === '_sort') {
|
|
920
|
+
sortRules = sortRules || [];
|
|
921
|
+
sortRules.push(parseSortRule(value));
|
|
922
|
+
}
|
|
923
|
+
else {
|
|
924
|
+
filters = filters || [];
|
|
925
|
+
filters.push(parseSearchFilter(key, value));
|
|
926
|
+
}
|
|
927
|
+
});
|
|
928
|
+
return {
|
|
929
|
+
resourceType,
|
|
930
|
+
filters,
|
|
931
|
+
fields,
|
|
932
|
+
offset,
|
|
933
|
+
count,
|
|
934
|
+
total,
|
|
935
|
+
sortRules,
|
|
936
|
+
};
|
|
937
|
+
}
|
|
938
|
+
/**
|
|
939
|
+
* Parses a URL query parameter into a sort rule.
|
|
940
|
+
*
|
|
941
|
+
* By default, the sort rule is the field name.
|
|
942
|
+
*
|
|
943
|
+
* Sort rules can be reversed into descending order by prefixing the field name with a minus sign.
|
|
944
|
+
*
|
|
945
|
+
* See sorting: http://hl7.org/fhir/r4/search.html#_sort
|
|
946
|
+
*
|
|
947
|
+
* @param value The URL parameter value.
|
|
948
|
+
* @returns The parsed sort rule.
|
|
949
|
+
*/
|
|
950
|
+
function parseSortRule(value) {
|
|
951
|
+
if (value.startsWith('-')) {
|
|
952
|
+
return { code: value.substring(1), descending: true };
|
|
953
|
+
}
|
|
954
|
+
else {
|
|
955
|
+
return { code: value };
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
/**
|
|
959
|
+
* Parses a URL query parameter into a search filter.
|
|
960
|
+
*
|
|
961
|
+
* FHIR search filters can be specified as modifiers or prefixes.
|
|
962
|
+
*
|
|
963
|
+
* For string properties, modifiers are appended to the key, e.g. "name:contains=eve".
|
|
964
|
+
*
|
|
965
|
+
* For date and numeric properties, prefixes are prepended to the value, e.g. "birthdate=gt2000".
|
|
966
|
+
*
|
|
967
|
+
* See the FHIR search spec: http://hl7.org/fhir/r4/search.html
|
|
968
|
+
*
|
|
969
|
+
* @param key The URL parameter key.
|
|
970
|
+
* @param value The URL parameter value.
|
|
971
|
+
* @returns The parsed search filter.
|
|
972
|
+
*/
|
|
973
|
+
function parseSearchFilter(key, value) {
|
|
974
|
+
let code = key;
|
|
975
|
+
let operator = Operator.EQUALS;
|
|
976
|
+
for (const modifier of MODIFIER_OPERATORS) {
|
|
977
|
+
const modifierIndex = code.indexOf(':' + modifier);
|
|
978
|
+
if (modifierIndex !== -1) {
|
|
979
|
+
operator = modifier;
|
|
980
|
+
code = code.substring(0, modifierIndex);
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
for (const prefix of PREFIX_OPERATORS) {
|
|
984
|
+
if (value.match(new RegExp('^' + prefix + '\\d'))) {
|
|
985
|
+
operator = prefix;
|
|
986
|
+
value = value.substring(prefix.length);
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
return { code, operator, value };
|
|
990
|
+
}
|
|
991
|
+
/**
|
|
992
|
+
* Formats a search definition object into a query string.
|
|
993
|
+
* Note: The return value does not include the resource type.
|
|
994
|
+
* @param {!SearchRequest} definition The search definition.
|
|
995
|
+
* @returns Formatted URL.
|
|
996
|
+
*/
|
|
997
|
+
function formatSearchQuery(definition) {
|
|
998
|
+
const params = [];
|
|
999
|
+
if (definition.fields) {
|
|
1000
|
+
params.push('_fields=' + definition.fields.join(','));
|
|
1001
|
+
}
|
|
1002
|
+
if (definition.filters) {
|
|
1003
|
+
definition.filters.forEach((filter) => params.push(formatFilter(filter)));
|
|
1004
|
+
}
|
|
1005
|
+
if (definition.sortRules && definition.sortRules.length > 0) {
|
|
1006
|
+
params.push(formatSortRules(definition.sortRules));
|
|
1007
|
+
}
|
|
1008
|
+
if (definition.offset !== undefined) {
|
|
1009
|
+
params.push('_offset=' + definition.offset);
|
|
1010
|
+
}
|
|
1011
|
+
if (definition.count !== undefined) {
|
|
1012
|
+
params.push('_count=' + definition.count);
|
|
1013
|
+
}
|
|
1014
|
+
if (definition.total !== undefined) {
|
|
1015
|
+
params.push('_total=' + encodeURIComponent(definition.total));
|
|
1016
|
+
}
|
|
1017
|
+
if (params.length === 0) {
|
|
1018
|
+
return '';
|
|
1019
|
+
}
|
|
1020
|
+
params.sort();
|
|
1021
|
+
return '?' + params.join('&');
|
|
1022
|
+
}
|
|
1023
|
+
function formatFilter(filter) {
|
|
1024
|
+
const modifier = MODIFIER_OPERATORS.includes(filter.operator) ? ':' + filter.operator : '';
|
|
1025
|
+
const prefix = PREFIX_OPERATORS.includes(filter.operator) ? filter.operator : '';
|
|
1026
|
+
return `${filter.code}${modifier}=${prefix}${encodeURIComponent(filter.value)}`;
|
|
1027
|
+
}
|
|
1028
|
+
function formatSortRules(sortRules) {
|
|
1029
|
+
if (!sortRules || sortRules.length === 0) {
|
|
1030
|
+
return '';
|
|
1031
|
+
}
|
|
1032
|
+
return '_sort=' + sortRules.map((sr) => (sr.descending ? '-' + sr.code : sr.code)).join(',');
|
|
1033
1033
|
}
|
|
1034
1034
|
|
|
1035
|
-
var _ClientStorage_storage, _MemoryStorage_data;
|
|
1036
|
-
/**
|
|
1037
|
-
* The ClientStorage class is a utility class for storing strings and objects.
|
|
1038
|
-
*
|
|
1039
|
-
* When using MedplumClient in the browser, it will be backed by browser localStorage.
|
|
1040
|
-
*
|
|
1041
|
-
* When Using MedplumClient in the server, it will be backed by the MemoryStorage class.
|
|
1042
|
-
*/
|
|
1043
|
-
class ClientStorage {
|
|
1044
|
-
constructor() {
|
|
1045
|
-
_ClientStorage_storage.set(this, void 0);
|
|
1046
|
-
__classPrivateFieldSet(this, _ClientStorage_storage, typeof localStorage !== 'undefined' ? localStorage : new MemoryStorage(), "f");
|
|
1047
|
-
}
|
|
1048
|
-
clear() {
|
|
1049
|
-
__classPrivateFieldGet(this, _ClientStorage_storage, "f").clear();
|
|
1050
|
-
}
|
|
1051
|
-
getString(key) {
|
|
1052
|
-
return __classPrivateFieldGet(this, _ClientStorage_storage, "f").getItem(key) || undefined;
|
|
1053
|
-
}
|
|
1054
|
-
setString(key, value) {
|
|
1055
|
-
if (value) {
|
|
1056
|
-
__classPrivateFieldGet(this, _ClientStorage_storage, "f").setItem(key, value);
|
|
1057
|
-
}
|
|
1058
|
-
else {
|
|
1059
|
-
__classPrivateFieldGet(this, _ClientStorage_storage, "f").removeItem(key);
|
|
1060
|
-
}
|
|
1061
|
-
}
|
|
1062
|
-
getObject(key) {
|
|
1063
|
-
const str = this.getString(key);
|
|
1064
|
-
return str ? JSON.parse(str) : undefined;
|
|
1065
|
-
}
|
|
1066
|
-
setObject(key, value) {
|
|
1067
|
-
this.setString(key, value ? stringify(value) : undefined);
|
|
1068
|
-
}
|
|
1069
|
-
}
|
|
1070
|
-
_ClientStorage_storage = new WeakMap();
|
|
1071
|
-
/**
|
|
1072
|
-
* The MemoryStorage class is a minimal in-memory implementation of the Storage interface.
|
|
1073
|
-
*/
|
|
1074
|
-
class MemoryStorage {
|
|
1075
|
-
constructor() {
|
|
1076
|
-
_MemoryStorage_data.set(this, void 0);
|
|
1077
|
-
__classPrivateFieldSet(this, _MemoryStorage_data, new Map(), "f");
|
|
1078
|
-
}
|
|
1079
|
-
/**
|
|
1080
|
-
* Returns the number of key/value pairs.
|
|
1081
|
-
*/
|
|
1082
|
-
get length() {
|
|
1083
|
-
return __classPrivateFieldGet(this, _MemoryStorage_data, "f").size;
|
|
1084
|
-
}
|
|
1085
|
-
/**
|
|
1086
|
-
* Removes all key/value pairs, if there are any.
|
|
1087
|
-
*/
|
|
1088
|
-
clear() {
|
|
1089
|
-
__classPrivateFieldGet(this, _MemoryStorage_data, "f").clear();
|
|
1090
|
-
}
|
|
1091
|
-
/**
|
|
1092
|
-
* Returns the current value associated with the given key, or null if the given key does not exist.
|
|
1093
|
-
*/
|
|
1094
|
-
getItem(key) {
|
|
1095
|
-
var _a;
|
|
1096
|
-
return (_a = __classPrivateFieldGet(this, _MemoryStorage_data, "f").get(key)) !== null && _a !== void 0 ? _a : null;
|
|
1097
|
-
}
|
|
1098
|
-
/**
|
|
1099
|
-
* Sets the value of the pair identified by key to value, creating a new key/value pair if none existed for key previously.
|
|
1100
|
-
*/
|
|
1101
|
-
setItem(key, value) {
|
|
1102
|
-
if (value) {
|
|
1103
|
-
__classPrivateFieldGet(this, _MemoryStorage_data, "f").set(key, value);
|
|
1104
|
-
}
|
|
1105
|
-
else {
|
|
1106
|
-
__classPrivateFieldGet(this, _MemoryStorage_data, "f").delete(key);
|
|
1107
|
-
}
|
|
1108
|
-
}
|
|
1109
|
-
/**
|
|
1110
|
-
* Removes the key/value pair with the given key, if a key/value pair with the given key exists.
|
|
1111
|
-
*/
|
|
1112
|
-
removeItem(key) {
|
|
1113
|
-
__classPrivateFieldGet(this, _MemoryStorage_data, "f").delete(key);
|
|
1114
|
-
}
|
|
1115
|
-
/**
|
|
1116
|
-
* Returns the name of the nth key, or null if n is greater than or equal to the number of key/value pairs.
|
|
1117
|
-
*/
|
|
1118
|
-
key(index) {
|
|
1119
|
-
return Array.from(__classPrivateFieldGet(this, _MemoryStorage_data, "f").keys())[index];
|
|
1120
|
-
}
|
|
1121
|
-
}
|
|
1035
|
+
var _ClientStorage_storage, _MemoryStorage_data;
|
|
1036
|
+
/**
|
|
1037
|
+
* The ClientStorage class is a utility class for storing strings and objects.
|
|
1038
|
+
*
|
|
1039
|
+
* When using MedplumClient in the browser, it will be backed by browser localStorage.
|
|
1040
|
+
*
|
|
1041
|
+
* When Using MedplumClient in the server, it will be backed by the MemoryStorage class.
|
|
1042
|
+
*/
|
|
1043
|
+
class ClientStorage {
|
|
1044
|
+
constructor() {
|
|
1045
|
+
_ClientStorage_storage.set(this, void 0);
|
|
1046
|
+
__classPrivateFieldSet(this, _ClientStorage_storage, typeof localStorage !== 'undefined' ? localStorage : new MemoryStorage(), "f");
|
|
1047
|
+
}
|
|
1048
|
+
clear() {
|
|
1049
|
+
__classPrivateFieldGet(this, _ClientStorage_storage, "f").clear();
|
|
1050
|
+
}
|
|
1051
|
+
getString(key) {
|
|
1052
|
+
return __classPrivateFieldGet(this, _ClientStorage_storage, "f").getItem(key) || undefined;
|
|
1053
|
+
}
|
|
1054
|
+
setString(key, value) {
|
|
1055
|
+
if (value) {
|
|
1056
|
+
__classPrivateFieldGet(this, _ClientStorage_storage, "f").setItem(key, value);
|
|
1057
|
+
}
|
|
1058
|
+
else {
|
|
1059
|
+
__classPrivateFieldGet(this, _ClientStorage_storage, "f").removeItem(key);
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
getObject(key) {
|
|
1063
|
+
const str = this.getString(key);
|
|
1064
|
+
return str ? JSON.parse(str) : undefined;
|
|
1065
|
+
}
|
|
1066
|
+
setObject(key, value) {
|
|
1067
|
+
this.setString(key, value ? stringify(value) : undefined);
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
_ClientStorage_storage = new WeakMap();
|
|
1071
|
+
/**
|
|
1072
|
+
* The MemoryStorage class is a minimal in-memory implementation of the Storage interface.
|
|
1073
|
+
*/
|
|
1074
|
+
class MemoryStorage {
|
|
1075
|
+
constructor() {
|
|
1076
|
+
_MemoryStorage_data.set(this, void 0);
|
|
1077
|
+
__classPrivateFieldSet(this, _MemoryStorage_data, new Map(), "f");
|
|
1078
|
+
}
|
|
1079
|
+
/**
|
|
1080
|
+
* Returns the number of key/value pairs.
|
|
1081
|
+
*/
|
|
1082
|
+
get length() {
|
|
1083
|
+
return __classPrivateFieldGet(this, _MemoryStorage_data, "f").size;
|
|
1084
|
+
}
|
|
1085
|
+
/**
|
|
1086
|
+
* Removes all key/value pairs, if there are any.
|
|
1087
|
+
*/
|
|
1088
|
+
clear() {
|
|
1089
|
+
__classPrivateFieldGet(this, _MemoryStorage_data, "f").clear();
|
|
1090
|
+
}
|
|
1091
|
+
/**
|
|
1092
|
+
* Returns the current value associated with the given key, or null if the given key does not exist.
|
|
1093
|
+
*/
|
|
1094
|
+
getItem(key) {
|
|
1095
|
+
var _a;
|
|
1096
|
+
return (_a = __classPrivateFieldGet(this, _MemoryStorage_data, "f").get(key)) !== null && _a !== void 0 ? _a : null;
|
|
1097
|
+
}
|
|
1098
|
+
/**
|
|
1099
|
+
* Sets the value of the pair identified by key to value, creating a new key/value pair if none existed for key previously.
|
|
1100
|
+
*/
|
|
1101
|
+
setItem(key, value) {
|
|
1102
|
+
if (value) {
|
|
1103
|
+
__classPrivateFieldGet(this, _MemoryStorage_data, "f").set(key, value);
|
|
1104
|
+
}
|
|
1105
|
+
else {
|
|
1106
|
+
__classPrivateFieldGet(this, _MemoryStorage_data, "f").delete(key);
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
/**
|
|
1110
|
+
* Removes the key/value pair with the given key, if a key/value pair with the given key exists.
|
|
1111
|
+
*/
|
|
1112
|
+
removeItem(key) {
|
|
1113
|
+
__classPrivateFieldGet(this, _MemoryStorage_data, "f").delete(key);
|
|
1114
|
+
}
|
|
1115
|
+
/**
|
|
1116
|
+
* Returns the name of the nth key, or null if n is greater than or equal to the number of key/value pairs.
|
|
1117
|
+
*/
|
|
1118
|
+
key(index) {
|
|
1119
|
+
return Array.from(__classPrivateFieldGet(this, _MemoryStorage_data, "f").keys())[index];
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
1122
|
_MemoryStorage_data = new WeakMap();
|
|
1123
1123
|
|
|
1124
|
-
/**
|
|
1125
|
-
* List of property types.
|
|
1126
|
-
* http://www.hl7.org/fhir/valueset-defined-types.html
|
|
1127
|
-
* The list here includes additions found from StructureDefinition resources.
|
|
1128
|
-
*/
|
|
1129
|
-
var PropertyType;
|
|
1130
|
-
(function (PropertyType) {
|
|
1131
|
-
PropertyType["Address"] = "Address";
|
|
1132
|
-
PropertyType["Age"] = "Age";
|
|
1133
|
-
PropertyType["Annotation"] = "Annotation";
|
|
1134
|
-
PropertyType["Attachment"] = "Attachment";
|
|
1135
|
-
PropertyType["BackboneElement"] = "BackboneElement";
|
|
1136
|
-
PropertyType["CodeableConcept"] = "CodeableConcept";
|
|
1137
|
-
PropertyType["Coding"] = "Coding";
|
|
1138
|
-
PropertyType["ContactDetail"] = "ContactDetail";
|
|
1139
|
-
PropertyType["ContactPoint"] = "ContactPoint";
|
|
1140
|
-
PropertyType["Contributor"] = "Contributor";
|
|
1141
|
-
PropertyType["Count"] = "Count";
|
|
1142
|
-
PropertyType["DataRequirement"] = "DataRequirement";
|
|
1143
|
-
PropertyType["Distance"] = "Distance";
|
|
1144
|
-
PropertyType["Dosage"] = "Dosage";
|
|
1145
|
-
PropertyType["Duration"] = "Duration";
|
|
1146
|
-
PropertyType["Expression"] = "Expression";
|
|
1147
|
-
PropertyType["Extension"] = "Extension";
|
|
1148
|
-
PropertyType["HumanName"] = "HumanName";
|
|
1149
|
-
PropertyType["Identifier"] = "Identifier";
|
|
1150
|
-
PropertyType["MarketingStatus"] = "MarketingStatus";
|
|
1151
|
-
PropertyType["Meta"] = "Meta";
|
|
1152
|
-
PropertyType["Money"] = "Money";
|
|
1153
|
-
PropertyType["Narrative"] = "Narrative";
|
|
1154
|
-
PropertyType["ParameterDefinition"] = "ParameterDefinition";
|
|
1155
|
-
PropertyType["Period"] = "Period";
|
|
1156
|
-
PropertyType["Population"] = "Population";
|
|
1157
|
-
PropertyType["ProdCharacteristic"] = "ProdCharacteristic";
|
|
1158
|
-
PropertyType["ProductShelfLife"] = "ProductShelfLife";
|
|
1159
|
-
PropertyType["Quantity"] = "Quantity";
|
|
1160
|
-
PropertyType["Range"] = "Range";
|
|
1161
|
-
PropertyType["Ratio"] = "Ratio";
|
|
1162
|
-
PropertyType["Reference"] = "Reference";
|
|
1163
|
-
PropertyType["RelatedArtifact"] = "RelatedArtifact";
|
|
1164
|
-
PropertyType["Resource"] = "Resource";
|
|
1165
|
-
PropertyType["SampledData"] = "SampledData";
|
|
1166
|
-
PropertyType["Signature"] = "Signature";
|
|
1167
|
-
PropertyType["SubstanceAmount"] = "SubstanceAmount";
|
|
1168
|
-
PropertyType["SystemString"] = "http://hl7.org/fhirpath/System.String";
|
|
1169
|
-
PropertyType["Timing"] = "Timing";
|
|
1170
|
-
PropertyType["TriggerDefinition"] = "TriggerDefinition";
|
|
1171
|
-
PropertyType["UsageContext"] = "UsageContext";
|
|
1172
|
-
PropertyType["base64Binary"] = "base64Binary";
|
|
1173
|
-
PropertyType["boolean"] = "boolean";
|
|
1174
|
-
PropertyType["canonical"] = "canonical";
|
|
1175
|
-
PropertyType["code"] = "code";
|
|
1176
|
-
PropertyType["date"] = "date";
|
|
1177
|
-
PropertyType["dateTime"] = "dateTime";
|
|
1178
|
-
PropertyType["decimal"] = "decimal";
|
|
1179
|
-
PropertyType["id"] = "id";
|
|
1180
|
-
PropertyType["instant"] = "instant";
|
|
1181
|
-
PropertyType["integer"] = "integer";
|
|
1182
|
-
PropertyType["markdown"] = "markdown";
|
|
1183
|
-
PropertyType["oid"] = "oid";
|
|
1184
|
-
PropertyType["positiveInt"] = "positiveInt";
|
|
1185
|
-
PropertyType["string"] = "string";
|
|
1186
|
-
PropertyType["time"] = "time";
|
|
1187
|
-
PropertyType["unsignedInt"] = "unsignedInt";
|
|
1188
|
-
PropertyType["uri"] = "uri";
|
|
1189
|
-
PropertyType["url"] = "url";
|
|
1190
|
-
PropertyType["uuid"] = "uuid";
|
|
1191
|
-
})(PropertyType || (PropertyType = {}));
|
|
1192
|
-
/**
|
|
1193
|
-
* Creates a new empty IndexedStructureDefinition.
|
|
1194
|
-
* @returns The empty IndexedStructureDefinition.
|
|
1195
|
-
*/
|
|
1196
|
-
function createSchema() {
|
|
1197
|
-
return { types: {} };
|
|
1198
|
-
}
|
|
1199
|
-
function createTypeSchema(typeName, description) {
|
|
1200
|
-
return {
|
|
1201
|
-
display: typeName,
|
|
1202
|
-
description,
|
|
1203
|
-
properties: {},
|
|
1204
|
-
searchParams: {
|
|
1205
|
-
_lastUpdated: {
|
|
1206
|
-
base: [typeName],
|
|
1207
|
-
code: '_lastUpdated',
|
|
1208
|
-
type: 'date',
|
|
1209
|
-
expression: typeName + '.meta.lastUpdated',
|
|
1210
|
-
},
|
|
1211
|
-
},
|
|
1212
|
-
};
|
|
1213
|
-
}
|
|
1214
|
-
/**
|
|
1215
|
-
* Indexes a StructureDefinition for fast lookup.
|
|
1216
|
-
* See comments on IndexedStructureDefinition for more details.
|
|
1217
|
-
* @param schema The output IndexedStructureDefinition.
|
|
1218
|
-
* @param structureDefinition The original StructureDefinition.
|
|
1219
|
-
*/
|
|
1220
|
-
function indexStructureDefinition(schema, structureDefinition) {
|
|
1221
|
-
var _a;
|
|
1222
|
-
const typeName = structureDefinition.name;
|
|
1223
|
-
if (!typeName) {
|
|
1224
|
-
return;
|
|
1225
|
-
}
|
|
1226
|
-
schema.types[typeName] = createTypeSchema(typeName, structureDefinition.description);
|
|
1227
|
-
const elements = (_a = structureDefinition.snapshot) === null || _a === void 0 ? void 0 : _a.element;
|
|
1228
|
-
if (elements) {
|
|
1229
|
-
// Filter out any elements missing path or type
|
|
1230
|
-
const filtered = elements.filter((e) => e.path !== typeName && e.path);
|
|
1231
|
-
// First pass, build types
|
|
1232
|
-
filtered.forEach((element) => indexType(schema, element));
|
|
1233
|
-
// Second pass, build properties
|
|
1234
|
-
filtered.forEach((element) => indexProperty(schema, element));
|
|
1235
|
-
}
|
|
1236
|
-
}
|
|
1237
|
-
/**
|
|
1238
|
-
* Indexes TypeSchema from an ElementDefinition.
|
|
1239
|
-
* In the common case, there will be many ElementDefinition instances per TypeSchema.
|
|
1240
|
-
* Only the first occurrence is saved.
|
|
1241
|
-
* @param schema The output IndexedStructureDefinition.
|
|
1242
|
-
* @param element The input ElementDefinition.
|
|
1243
|
-
*/
|
|
1244
|
-
function indexType(schema, element) {
|
|
1245
|
-
var _a, _b;
|
|
1246
|
-
const path = element.path;
|
|
1247
|
-
const typeCode = (_b = (_a = element.type) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.code;
|
|
1248
|
-
if (typeCode !== 'Element' && typeCode !== 'BackboneElement') {
|
|
1249
|
-
return;
|
|
1250
|
-
}
|
|
1251
|
-
const parts = path.split('.');
|
|
1252
|
-
const typeName = buildTypeName(parts);
|
|
1253
|
-
if (!(typeName in schema.types)) {
|
|
1254
|
-
schema.types[typeName] = createTypeSchema(typeName, element.definition);
|
|
1255
|
-
schema.types[typeName].parentType = buildTypeName(parts.slice(0, parts.length - 1));
|
|
1256
|
-
}
|
|
1257
|
-
}
|
|
1258
|
-
/**
|
|
1259
|
-
* Indexes PropertySchema from an ElementDefinition.
|
|
1260
|
-
* @param schema The output IndexedStructureDefinition.
|
|
1261
|
-
* @param element The input ElementDefinition.
|
|
1262
|
-
*/
|
|
1263
|
-
function indexProperty(schema, element) {
|
|
1264
|
-
const path = element.path;
|
|
1265
|
-
const parts = path.split('.');
|
|
1266
|
-
if (parts.length === 1) {
|
|
1267
|
-
return;
|
|
1268
|
-
}
|
|
1269
|
-
const typeName = buildTypeName(parts.slice(0, parts.length - 1));
|
|
1270
|
-
const typeSchema = schema.types[typeName];
|
|
1271
|
-
const key = parts[parts.length - 1];
|
|
1272
|
-
typeSchema.properties[key] = element;
|
|
1273
|
-
}
|
|
1274
|
-
/**
|
|
1275
|
-
* Indexes a SearchParameter resource for fast lookup.
|
|
1276
|
-
* Indexes by SearchParameter.code, which is the query string parameter name.
|
|
1277
|
-
* @param schema The output IndexedStructureDefinition.
|
|
1278
|
-
* @param searchParam The SearchParameter resource.
|
|
1279
|
-
*/
|
|
1280
|
-
function indexSearchParameter(schema, searchParam) {
|
|
1281
|
-
if (!searchParam.base) {
|
|
1282
|
-
return;
|
|
1283
|
-
}
|
|
1284
|
-
for (const resourceType of searchParam.base) {
|
|
1285
|
-
const typeSchema = schema.types[resourceType];
|
|
1286
|
-
if (!typeSchema) {
|
|
1287
|
-
continue;
|
|
1288
|
-
}
|
|
1289
|
-
if (!typeSchema.searchParams) {
|
|
1290
|
-
typeSchema.searchParams = {};
|
|
1291
|
-
}
|
|
1292
|
-
typeSchema.searchParams[searchParam.code] = searchParam;
|
|
1293
|
-
}
|
|
1294
|
-
}
|
|
1295
|
-
function buildTypeName(components) {
|
|
1296
|
-
return components.map(capitalize).join('');
|
|
1297
|
-
}
|
|
1298
|
-
function getPropertyDisplayName(property) {
|
|
1299
|
-
// Get the property name, which is the remainder after the last period
|
|
1300
|
-
// For example, for path "Patient.birthDate"
|
|
1301
|
-
// the property name is "birthDate"
|
|
1302
|
-
const propertyName = property.path.replaceAll('[x]', '').split('.').pop();
|
|
1303
|
-
// Special case for ID
|
|
1304
|
-
if (propertyName === 'id') {
|
|
1305
|
-
return 'ID';
|
|
1306
|
-
}
|
|
1307
|
-
// Split by capital letters
|
|
1308
|
-
// Capitalize the first letter of each word
|
|
1309
|
-
// Join together with spaces in between
|
|
1310
|
-
// Then normalize whitespace to single space character
|
|
1311
|
-
// For example, for property name "birthDate",
|
|
1312
|
-
// the display name is "Birth Date".
|
|
1313
|
-
return propertyName
|
|
1314
|
-
.split(/(?=[A-Z])/)
|
|
1315
|
-
.map(capitalize)
|
|
1316
|
-
.join(' ')
|
|
1317
|
-
.replace('_', ' ')
|
|
1318
|
-
.replace(/\s+/g, ' ');
|
|
1124
|
+
/**
|
|
1125
|
+
* List of property types.
|
|
1126
|
+
* http://www.hl7.org/fhir/valueset-defined-types.html
|
|
1127
|
+
* The list here includes additions found from StructureDefinition resources.
|
|
1128
|
+
*/
|
|
1129
|
+
var PropertyType;
|
|
1130
|
+
(function (PropertyType) {
|
|
1131
|
+
PropertyType["Address"] = "Address";
|
|
1132
|
+
PropertyType["Age"] = "Age";
|
|
1133
|
+
PropertyType["Annotation"] = "Annotation";
|
|
1134
|
+
PropertyType["Attachment"] = "Attachment";
|
|
1135
|
+
PropertyType["BackboneElement"] = "BackboneElement";
|
|
1136
|
+
PropertyType["CodeableConcept"] = "CodeableConcept";
|
|
1137
|
+
PropertyType["Coding"] = "Coding";
|
|
1138
|
+
PropertyType["ContactDetail"] = "ContactDetail";
|
|
1139
|
+
PropertyType["ContactPoint"] = "ContactPoint";
|
|
1140
|
+
PropertyType["Contributor"] = "Contributor";
|
|
1141
|
+
PropertyType["Count"] = "Count";
|
|
1142
|
+
PropertyType["DataRequirement"] = "DataRequirement";
|
|
1143
|
+
PropertyType["Distance"] = "Distance";
|
|
1144
|
+
PropertyType["Dosage"] = "Dosage";
|
|
1145
|
+
PropertyType["Duration"] = "Duration";
|
|
1146
|
+
PropertyType["Expression"] = "Expression";
|
|
1147
|
+
PropertyType["Extension"] = "Extension";
|
|
1148
|
+
PropertyType["HumanName"] = "HumanName";
|
|
1149
|
+
PropertyType["Identifier"] = "Identifier";
|
|
1150
|
+
PropertyType["MarketingStatus"] = "MarketingStatus";
|
|
1151
|
+
PropertyType["Meta"] = "Meta";
|
|
1152
|
+
PropertyType["Money"] = "Money";
|
|
1153
|
+
PropertyType["Narrative"] = "Narrative";
|
|
1154
|
+
PropertyType["ParameterDefinition"] = "ParameterDefinition";
|
|
1155
|
+
PropertyType["Period"] = "Period";
|
|
1156
|
+
PropertyType["Population"] = "Population";
|
|
1157
|
+
PropertyType["ProdCharacteristic"] = "ProdCharacteristic";
|
|
1158
|
+
PropertyType["ProductShelfLife"] = "ProductShelfLife";
|
|
1159
|
+
PropertyType["Quantity"] = "Quantity";
|
|
1160
|
+
PropertyType["Range"] = "Range";
|
|
1161
|
+
PropertyType["Ratio"] = "Ratio";
|
|
1162
|
+
PropertyType["Reference"] = "Reference";
|
|
1163
|
+
PropertyType["RelatedArtifact"] = "RelatedArtifact";
|
|
1164
|
+
PropertyType["Resource"] = "Resource";
|
|
1165
|
+
PropertyType["SampledData"] = "SampledData";
|
|
1166
|
+
PropertyType["Signature"] = "Signature";
|
|
1167
|
+
PropertyType["SubstanceAmount"] = "SubstanceAmount";
|
|
1168
|
+
PropertyType["SystemString"] = "http://hl7.org/fhirpath/System.String";
|
|
1169
|
+
PropertyType["Timing"] = "Timing";
|
|
1170
|
+
PropertyType["TriggerDefinition"] = "TriggerDefinition";
|
|
1171
|
+
PropertyType["UsageContext"] = "UsageContext";
|
|
1172
|
+
PropertyType["base64Binary"] = "base64Binary";
|
|
1173
|
+
PropertyType["boolean"] = "boolean";
|
|
1174
|
+
PropertyType["canonical"] = "canonical";
|
|
1175
|
+
PropertyType["code"] = "code";
|
|
1176
|
+
PropertyType["date"] = "date";
|
|
1177
|
+
PropertyType["dateTime"] = "dateTime";
|
|
1178
|
+
PropertyType["decimal"] = "decimal";
|
|
1179
|
+
PropertyType["id"] = "id";
|
|
1180
|
+
PropertyType["instant"] = "instant";
|
|
1181
|
+
PropertyType["integer"] = "integer";
|
|
1182
|
+
PropertyType["markdown"] = "markdown";
|
|
1183
|
+
PropertyType["oid"] = "oid";
|
|
1184
|
+
PropertyType["positiveInt"] = "positiveInt";
|
|
1185
|
+
PropertyType["string"] = "string";
|
|
1186
|
+
PropertyType["time"] = "time";
|
|
1187
|
+
PropertyType["unsignedInt"] = "unsignedInt";
|
|
1188
|
+
PropertyType["uri"] = "uri";
|
|
1189
|
+
PropertyType["url"] = "url";
|
|
1190
|
+
PropertyType["uuid"] = "uuid";
|
|
1191
|
+
})(PropertyType || (PropertyType = {}));
|
|
1192
|
+
/**
|
|
1193
|
+
* Creates a new empty IndexedStructureDefinition.
|
|
1194
|
+
* @returns The empty IndexedStructureDefinition.
|
|
1195
|
+
*/
|
|
1196
|
+
function createSchema() {
|
|
1197
|
+
return { types: {} };
|
|
1198
|
+
}
|
|
1199
|
+
function createTypeSchema(typeName, description) {
|
|
1200
|
+
return {
|
|
1201
|
+
display: typeName,
|
|
1202
|
+
description,
|
|
1203
|
+
properties: {},
|
|
1204
|
+
searchParams: {
|
|
1205
|
+
_lastUpdated: {
|
|
1206
|
+
base: [typeName],
|
|
1207
|
+
code: '_lastUpdated',
|
|
1208
|
+
type: 'date',
|
|
1209
|
+
expression: typeName + '.meta.lastUpdated',
|
|
1210
|
+
},
|
|
1211
|
+
},
|
|
1212
|
+
};
|
|
1213
|
+
}
|
|
1214
|
+
/**
|
|
1215
|
+
* Indexes a StructureDefinition for fast lookup.
|
|
1216
|
+
* See comments on IndexedStructureDefinition for more details.
|
|
1217
|
+
* @param schema The output IndexedStructureDefinition.
|
|
1218
|
+
* @param structureDefinition The original StructureDefinition.
|
|
1219
|
+
*/
|
|
1220
|
+
function indexStructureDefinition(schema, structureDefinition) {
|
|
1221
|
+
var _a;
|
|
1222
|
+
const typeName = structureDefinition.name;
|
|
1223
|
+
if (!typeName) {
|
|
1224
|
+
return;
|
|
1225
|
+
}
|
|
1226
|
+
schema.types[typeName] = createTypeSchema(typeName, structureDefinition.description);
|
|
1227
|
+
const elements = (_a = structureDefinition.snapshot) === null || _a === void 0 ? void 0 : _a.element;
|
|
1228
|
+
if (elements) {
|
|
1229
|
+
// Filter out any elements missing path or type
|
|
1230
|
+
const filtered = elements.filter((e) => e.path !== typeName && e.path);
|
|
1231
|
+
// First pass, build types
|
|
1232
|
+
filtered.forEach((element) => indexType(schema, element));
|
|
1233
|
+
// Second pass, build properties
|
|
1234
|
+
filtered.forEach((element) => indexProperty(schema, element));
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
/**
|
|
1238
|
+
* Indexes TypeSchema from an ElementDefinition.
|
|
1239
|
+
* In the common case, there will be many ElementDefinition instances per TypeSchema.
|
|
1240
|
+
* Only the first occurrence is saved.
|
|
1241
|
+
* @param schema The output IndexedStructureDefinition.
|
|
1242
|
+
* @param element The input ElementDefinition.
|
|
1243
|
+
*/
|
|
1244
|
+
function indexType(schema, element) {
|
|
1245
|
+
var _a, _b;
|
|
1246
|
+
const path = element.path;
|
|
1247
|
+
const typeCode = (_b = (_a = element.type) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.code;
|
|
1248
|
+
if (typeCode !== 'Element' && typeCode !== 'BackboneElement') {
|
|
1249
|
+
return;
|
|
1250
|
+
}
|
|
1251
|
+
const parts = path.split('.');
|
|
1252
|
+
const typeName = buildTypeName(parts);
|
|
1253
|
+
if (!(typeName in schema.types)) {
|
|
1254
|
+
schema.types[typeName] = createTypeSchema(typeName, element.definition);
|
|
1255
|
+
schema.types[typeName].parentType = buildTypeName(parts.slice(0, parts.length - 1));
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
/**
|
|
1259
|
+
* Indexes PropertySchema from an ElementDefinition.
|
|
1260
|
+
* @param schema The output IndexedStructureDefinition.
|
|
1261
|
+
* @param element The input ElementDefinition.
|
|
1262
|
+
*/
|
|
1263
|
+
function indexProperty(schema, element) {
|
|
1264
|
+
const path = element.path;
|
|
1265
|
+
const parts = path.split('.');
|
|
1266
|
+
if (parts.length === 1) {
|
|
1267
|
+
return;
|
|
1268
|
+
}
|
|
1269
|
+
const typeName = buildTypeName(parts.slice(0, parts.length - 1));
|
|
1270
|
+
const typeSchema = schema.types[typeName];
|
|
1271
|
+
const key = parts[parts.length - 1];
|
|
1272
|
+
typeSchema.properties[key] = element;
|
|
1273
|
+
}
|
|
1274
|
+
/**
|
|
1275
|
+
* Indexes a SearchParameter resource for fast lookup.
|
|
1276
|
+
* Indexes by SearchParameter.code, which is the query string parameter name.
|
|
1277
|
+
* @param schema The output IndexedStructureDefinition.
|
|
1278
|
+
* @param searchParam The SearchParameter resource.
|
|
1279
|
+
*/
|
|
1280
|
+
function indexSearchParameter(schema, searchParam) {
|
|
1281
|
+
if (!searchParam.base) {
|
|
1282
|
+
return;
|
|
1283
|
+
}
|
|
1284
|
+
for (const resourceType of searchParam.base) {
|
|
1285
|
+
const typeSchema = schema.types[resourceType];
|
|
1286
|
+
if (!typeSchema) {
|
|
1287
|
+
continue;
|
|
1288
|
+
}
|
|
1289
|
+
if (!typeSchema.searchParams) {
|
|
1290
|
+
typeSchema.searchParams = {};
|
|
1291
|
+
}
|
|
1292
|
+
typeSchema.searchParams[searchParam.code] = searchParam;
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
function buildTypeName(components) {
|
|
1296
|
+
return components.map(capitalize).join('');
|
|
1297
|
+
}
|
|
1298
|
+
function getPropertyDisplayName(property) {
|
|
1299
|
+
// Get the property name, which is the remainder after the last period
|
|
1300
|
+
// For example, for path "Patient.birthDate"
|
|
1301
|
+
// the property name is "birthDate"
|
|
1302
|
+
const propertyName = property.path.replaceAll('[x]', '').split('.').pop();
|
|
1303
|
+
// Special case for ID
|
|
1304
|
+
if (propertyName === 'id') {
|
|
1305
|
+
return 'ID';
|
|
1306
|
+
}
|
|
1307
|
+
// Split by capital letters
|
|
1308
|
+
// Capitalize the first letter of each word
|
|
1309
|
+
// Join together with spaces in between
|
|
1310
|
+
// Then normalize whitespace to single space character
|
|
1311
|
+
// For example, for property name "birthDate",
|
|
1312
|
+
// the display name is "Birth Date".
|
|
1313
|
+
return propertyName
|
|
1314
|
+
.split(/(?=[A-Z])/)
|
|
1315
|
+
.map(capitalize)
|
|
1316
|
+
.join(' ')
|
|
1317
|
+
.replace('_', ' ')
|
|
1318
|
+
.replace(/\s+/g, ' ');
|
|
1319
1319
|
}
|
|
1320
1320
|
|
|
1321
|
-
// PKCE auth ased on:
|
|
1322
|
-
// https://aws.amazon.com/blogs/security/how-to-add-authentication-single-page-web-application-with-amazon-cognito-oauth2-implementation/
|
|
1323
|
-
var _MedplumClient_instances, _MedplumClient_fetch, _MedplumClient_storage, _MedplumClient_schema, _MedplumClient_requestCache, _MedplumClient_baseUrl, _MedplumClient_clientId, _MedplumClient_authorizeUrl, _MedplumClient_tokenUrl, _MedplumClient_logoutUrl, _MedplumClient_onUnauthenticated, _MedplumClient_accessToken, _MedplumClient_refreshToken, _MedplumClient_refreshPromise, _MedplumClient_profilePromise, _MedplumClient_profile, _MedplumClient_config, _MedplumClient_addLogin, _MedplumClient_refreshProfile, _MedplumClient_request, _MedplumClient_addFetchOptionsDefaults, _MedplumClient_setRequestContentType, _MedplumClient_setRequestBody, _MedplumClient_handleUnauthenticated, _MedplumClient_startPkce, _MedplumClient_requestAuthorization, _MedplumClient_refresh, _MedplumClient_fetchTokens, _MedplumClient_verifyTokens, _MedplumClient_setupStorageListener;
|
|
1324
|
-
const DEFAULT_BASE_URL = 'https://api.medplum.com/';
|
|
1325
|
-
const DEFAULT_SCOPE = 'launch/patient openid fhirUser offline_access user/*.*';
|
|
1326
|
-
const DEFAULT_RESOURCE_CACHE_SIZE = 1000;
|
|
1327
|
-
const JSON_CONTENT_TYPE = 'application/json';
|
|
1328
|
-
const FHIR_CONTENT_TYPE = 'application/fhir+json';
|
|
1329
|
-
const PATCH_CONTENT_TYPE = 'application/json-patch+json';
|
|
1330
|
-
/**
|
|
1331
|
-
* The MedplumClient class provides a client for the Medplum FHIR server.
|
|
1332
|
-
*
|
|
1333
|
-
* The client can be used in the browser, in a NodeJS application, or in a Medplum Bot.
|
|
1334
|
-
*
|
|
1335
|
-
* The client provides helpful methods for common operations such as:
|
|
1336
|
-
* 1) Authenticating
|
|
1337
|
-
* 2) Creating resources
|
|
1338
|
-
* 2) Reading resources
|
|
1339
|
-
* 3) Updating resources
|
|
1340
|
-
* 5) Deleting resources
|
|
1341
|
-
* 6) Searching
|
|
1342
|
-
* 7) Making GraphQL queries
|
|
1343
|
-
*
|
|
1344
|
-
* Here is a quick example of how to use the client:
|
|
1345
|
-
*
|
|
1346
|
-
* ```typescript
|
|
1347
|
-
* import { MedplumClient } from '@medplum/core';
|
|
1348
|
-
* const medplum = new MedplumClient();
|
|
1349
|
-
* ```
|
|
1350
|
-
*
|
|
1351
|
-
* Create a `Patient`:
|
|
1352
|
-
*
|
|
1353
|
-
* ```typescript
|
|
1354
|
-
* const patient = await medplum.createResource({
|
|
1355
|
-
* resourceType: 'Patient',
|
|
1356
|
-
* name: [{
|
|
1357
|
-
* given: ['Alice'],
|
|
1358
|
-
* family: 'Smith'
|
|
1359
|
-
* }]
|
|
1360
|
-
* });
|
|
1361
|
-
* ```
|
|
1362
|
-
*
|
|
1363
|
-
* Read a `Patient` by ID:
|
|
1364
|
-
*
|
|
1365
|
-
* ```typescript
|
|
1366
|
-
* const patient = await medplum.readResource('Patient', '123');
|
|
1367
|
-
* console.log(patient.name[0].given[0]);
|
|
1368
|
-
* ```
|
|
1369
|
-
*
|
|
1370
|
-
* Search for a `Patient` by name:
|
|
1371
|
-
*
|
|
1372
|
-
* ```typescript
|
|
1373
|
-
* const bundle = await medplum.search('Patient?name=Alice');
|
|
1374
|
-
* console.log(bundle.total);
|
|
1375
|
-
* ```
|
|
1376
|
-
*
|
|
1377
|
-
*/
|
|
1378
|
-
class MedplumClient extends EventTarget {
|
|
1379
|
-
constructor(options) {
|
|
1380
|
-
var _a;
|
|
1381
|
-
super();
|
|
1382
|
-
_MedplumClient_instances.add(this);
|
|
1383
|
-
_MedplumClient_fetch.set(this, void 0);
|
|
1384
|
-
_MedplumClient_storage.set(this, void 0);
|
|
1385
|
-
_MedplumClient_schema.set(this, void 0);
|
|
1386
|
-
_MedplumClient_requestCache.set(this, void 0);
|
|
1387
|
-
_MedplumClient_baseUrl.set(this, void 0);
|
|
1388
|
-
_MedplumClient_clientId.set(this, void 0);
|
|
1389
|
-
_MedplumClient_authorizeUrl.set(this, void 0);
|
|
1390
|
-
_MedplumClient_tokenUrl.set(this, void 0);
|
|
1391
|
-
_MedplumClient_logoutUrl.set(this, void 0);
|
|
1392
|
-
_MedplumClient_onUnauthenticated.set(this, void 0);
|
|
1393
|
-
_MedplumClient_accessToken.set(this, void 0);
|
|
1394
|
-
_MedplumClient_refreshToken.set(this, void 0);
|
|
1395
|
-
_MedplumClient_refreshPromise.set(this, void 0);
|
|
1396
|
-
_MedplumClient_profilePromise.set(this, void 0);
|
|
1397
|
-
_MedplumClient_profile.set(this, void 0);
|
|
1398
|
-
_MedplumClient_config.set(this, void 0);
|
|
1399
|
-
if (options === null || options === void 0 ? void 0 : options.baseUrl) {
|
|
1400
|
-
if (!options.baseUrl.startsWith('http')) {
|
|
1401
|
-
throw new Error('Base URL must start with http or https');
|
|
1402
|
-
}
|
|
1403
|
-
if (!options.baseUrl.endsWith('/')) {
|
|
1404
|
-
throw new Error('Base URL must end with a trailing slash');
|
|
1405
|
-
}
|
|
1406
|
-
}
|
|
1407
|
-
__classPrivateFieldSet(this, _MedplumClient_fetch, (options === null || options === void 0 ? void 0 : options.fetch) || window.fetch.bind(window), "f");
|
|
1408
|
-
__classPrivateFieldSet(this, _MedplumClient_storage, new ClientStorage(), "f");
|
|
1409
|
-
__classPrivateFieldSet(this, _MedplumClient_schema, createSchema(), "f");
|
|
1410
|
-
__classPrivateFieldSet(this, _MedplumClient_requestCache, new LRUCache((_a = options === null || options === void 0 ? void 0 : options.resourceCacheSize) !== null && _a !== void 0 ? _a : DEFAULT_RESOURCE_CACHE_SIZE), "f");
|
|
1411
|
-
__classPrivateFieldSet(this, _MedplumClient_baseUrl, (options === null || options === void 0 ? void 0 : options.baseUrl) || DEFAULT_BASE_URL, "f");
|
|
1412
|
-
__classPrivateFieldSet(this, _MedplumClient_clientId, (options === null || options === void 0 ? void 0 : options.clientId) || '', "f");
|
|
1413
|
-
__classPrivateFieldSet(this, _MedplumClient_authorizeUrl, (options === null || options === void 0 ? void 0 : options.authorizeUrl) || __classPrivateFieldGet(this, _MedplumClient_baseUrl, "f") + 'oauth2/authorize', "f");
|
|
1414
|
-
__classPrivateFieldSet(this, _MedplumClient_tokenUrl, (options === null || options === void 0 ? void 0 : options.tokenUrl) || __classPrivateFieldGet(this, _MedplumClient_baseUrl, "f") + 'oauth2/token', "f");
|
|
1415
|
-
__classPrivateFieldSet(this, _MedplumClient_logoutUrl, (options === null || options === void 0 ? void 0 : options.logoutUrl) || __classPrivateFieldGet(this, _MedplumClient_baseUrl, "f") + 'oauth2/logout', "f");
|
|
1416
|
-
__classPrivateFieldSet(this, _MedplumClient_onUnauthenticated, options === null || options === void 0 ? void 0 : options.onUnauthenticated, "f");
|
|
1417
|
-
const activeLogin = this.getActiveLogin();
|
|
1418
|
-
if (activeLogin) {
|
|
1419
|
-
__classPrivateFieldSet(this, _MedplumClient_accessToken, activeLogin.accessToken, "f");
|
|
1420
|
-
__classPrivateFieldSet(this, _MedplumClient_refreshToken, activeLogin.refreshToken, "f");
|
|
1421
|
-
__classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_refreshProfile).call(this).catch(console.log);
|
|
1422
|
-
}
|
|
1423
|
-
__classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_setupStorageListener).call(this);
|
|
1424
|
-
}
|
|
1425
|
-
/**
|
|
1426
|
-
* Clears all auth state including local storage and session storage.
|
|
1427
|
-
*/
|
|
1428
|
-
clear() {
|
|
1429
|
-
__classPrivateFieldGet(this, _MedplumClient_storage, "f").clear();
|
|
1430
|
-
__classPrivateFieldGet(this, _MedplumClient_requestCache, "f").clear();
|
|
1431
|
-
__classPrivateFieldSet(this, _MedplumClient_accessToken, undefined, "f");
|
|
1432
|
-
__classPrivateFieldSet(this, _MedplumClient_refreshToken, undefined, "f");
|
|
1433
|
-
__classPrivateFieldSet(this, _MedplumClient_profile, undefined, "f");
|
|
1434
|
-
__classPrivateFieldSet(this, _MedplumClient_config, undefined, "f");
|
|
1435
|
-
this.dispatchEvent({ type: 'change' });
|
|
1436
|
-
}
|
|
1437
|
-
/**
|
|
1438
|
-
* Makes an HTTP GET request to the specified URL.
|
|
1439
|
-
*
|
|
1440
|
-
* This is a lower level method for custom requests.
|
|
1441
|
-
* For common operations, we recommend using higher level methods
|
|
1442
|
-
* such as `readResource()`, `search()`, etc.
|
|
1443
|
-
*
|
|
1444
|
-
* @param url The target URL.
|
|
1445
|
-
* @param options Optional fetch options.
|
|
1446
|
-
* @returns Promise to the response content.
|
|
1447
|
-
*/
|
|
1448
|
-
get(url, options = {}) {
|
|
1449
|
-
if (!(options === null || options === void 0 ? void 0 : options.cache)) {
|
|
1450
|
-
const cached = __classPrivateFieldGet(this, _MedplumClient_requestCache, "f").get(url);
|
|
1451
|
-
if (cached) {
|
|
1452
|
-
return cached;
|
|
1453
|
-
}
|
|
1454
|
-
}
|
|
1455
|
-
const promise = new ReadablePromise(__classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_request).call(this, 'GET', url, options));
|
|
1456
|
-
__classPrivateFieldGet(this, _MedplumClient_requestCache, "f").set(url, promise);
|
|
1457
|
-
return promise;
|
|
1458
|
-
}
|
|
1459
|
-
/**
|
|
1460
|
-
* Makes an HTTP POST request to the specified URL.
|
|
1461
|
-
*
|
|
1462
|
-
* This is a lower level method for custom requests.
|
|
1463
|
-
* For common operations, we recommend using higher level methods
|
|
1464
|
-
* such as `createResource()`.
|
|
1465
|
-
*
|
|
1466
|
-
* @param url The target URL.
|
|
1467
|
-
* @param body The content body. Strings and `File` objects are passed directly. Other objects are converted to JSON.
|
|
1468
|
-
* @param contentType The content type to be included in the "Content-Type" header.
|
|
1469
|
-
* @param options Optional fetch options.
|
|
1470
|
-
* @returns Promise to the response content.
|
|
1471
|
-
*/
|
|
1472
|
-
post(url, body, contentType, options = {}) {
|
|
1473
|
-
if (body) {
|
|
1474
|
-
__classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_setRequestBody).call(this, options, body);
|
|
1475
|
-
}
|
|
1476
|
-
if (contentType) {
|
|
1477
|
-
__classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_setRequestContentType).call(this, options, contentType);
|
|
1478
|
-
}
|
|
1479
|
-
__classPrivateFieldGet(this, _MedplumClient_requestCache, "f").delete(url);
|
|
1480
|
-
return __classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_request).call(this, 'POST', url, options);
|
|
1481
|
-
}
|
|
1482
|
-
/**
|
|
1483
|
-
* Makes an HTTP PUT request to the specified URL.
|
|
1484
|
-
*
|
|
1485
|
-
* This is a lower level method for custom requests.
|
|
1486
|
-
* For common operations, we recommend using higher level methods
|
|
1487
|
-
* such as `updateResource()`.
|
|
1488
|
-
*
|
|
1489
|
-
* @param url The target URL.
|
|
1490
|
-
* @param body The content body. Strings and `File` objects are passed directly. Other objects are converted to JSON.
|
|
1491
|
-
* @param contentType The content type to be included in the "Content-Type" header.
|
|
1492
|
-
* @param options Optional fetch options.
|
|
1493
|
-
* @returns Promise to the response content.
|
|
1494
|
-
*/
|
|
1495
|
-
put(url, body, contentType, options = {}) {
|
|
1496
|
-
if (body) {
|
|
1497
|
-
__classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_setRequestBody).call(this, options, body);
|
|
1498
|
-
}
|
|
1499
|
-
if (contentType) {
|
|
1500
|
-
__classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_setRequestContentType).call(this, options, contentType);
|
|
1501
|
-
}
|
|
1502
|
-
__classPrivateFieldGet(this, _MedplumClient_requestCache, "f").delete(url);
|
|
1503
|
-
return __classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_request).call(this, 'PUT', url, options);
|
|
1504
|
-
}
|
|
1505
|
-
/**
|
|
1506
|
-
* Makes an HTTP PATCH request to the specified URL.
|
|
1507
|
-
*
|
|
1508
|
-
* This is a lower level method for custom requests.
|
|
1509
|
-
* For common operations, we recommend using higher level methods
|
|
1510
|
-
* such as `patchResource()`.
|
|
1511
|
-
*
|
|
1512
|
-
* @param url The target URL.
|
|
1513
|
-
* @param operations Array of JSONPatch operations.
|
|
1514
|
-
* @param options Optional fetch options.
|
|
1515
|
-
* @returns Promise to the response content.
|
|
1516
|
-
*/
|
|
1517
|
-
patch(url, operations, options = {}) {
|
|
1518
|
-
__classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_setRequestBody).call(this, options, operations);
|
|
1519
|
-
__classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_setRequestContentType).call(this, options, PATCH_CONTENT_TYPE);
|
|
1520
|
-
__classPrivateFieldGet(this, _MedplumClient_requestCache, "f").delete(url);
|
|
1521
|
-
return __classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_request).call(this, 'PATCH', url, options);
|
|
1522
|
-
}
|
|
1523
|
-
/**
|
|
1524
|
-
* Makes an HTTP DELETE request to the specified URL.
|
|
1525
|
-
*
|
|
1526
|
-
* This is a lower level method for custom requests.
|
|
1527
|
-
* For common operations, we recommend using higher level methods
|
|
1528
|
-
* such as `deleteResource()`.
|
|
1529
|
-
*
|
|
1530
|
-
* @param url The target URL.
|
|
1531
|
-
* @param options Optional fetch options.
|
|
1532
|
-
* @returns Promise to the response content.
|
|
1533
|
-
*/
|
|
1534
|
-
delete(url, options = {}) {
|
|
1535
|
-
__classPrivateFieldGet(this, _MedplumClient_requestCache, "f").delete(url);
|
|
1536
|
-
return __classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_request).call(this, 'DELETE', url, options);
|
|
1537
|
-
}
|
|
1538
|
-
/**
|
|
1539
|
-
* Tries to register a new user.
|
|
1540
|
-
* @param request The registration request.
|
|
1541
|
-
* @returns Promise to the authentication response.
|
|
1542
|
-
*/
|
|
1543
|
-
register(request) {
|
|
1544
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
1545
|
-
const response = yield this.post('auth/register', request);
|
|
1546
|
-
yield this.setActiveLogin(response);
|
|
1547
|
-
});
|
|
1548
|
-
}
|
|
1549
|
-
/**
|
|
1550
|
-
* Initiates a user login flow.
|
|
1551
|
-
* @param email The email address of the user.
|
|
1552
|
-
* @param password The password of the user.
|
|
1553
|
-
* @param remember Optional flag to remember the user.
|
|
1554
|
-
* @returns Promise to the authentication response.
|
|
1555
|
-
*/
|
|
1556
|
-
startLogin(email, password, remember) {
|
|
1557
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
1558
|
-
yield __classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_startPkce).call(this);
|
|
1559
|
-
return this.post('auth/login', {
|
|
1560
|
-
clientId: __classPrivateFieldGet(this, _MedplumClient_clientId, "f"),
|
|
1561
|
-
scope: DEFAULT_SCOPE,
|
|
1562
|
-
codeChallengeMethod: 'S256',
|
|
1563
|
-
codeChallenge: __classPrivateFieldGet(this, _MedplumClient_storage, "f").getString('codeChallenge'),
|
|
1564
|
-
email,
|
|
1565
|
-
password,
|
|
1566
|
-
remember: !!remember,
|
|
1567
|
-
});
|
|
1568
|
-
});
|
|
1569
|
-
}
|
|
1570
|
-
/**
|
|
1571
|
-
* Tries to sign in with Google authentication.
|
|
1572
|
-
* The response parameter is the result of a Google authentication.
|
|
1573
|
-
* See: https://developers.google.com/identity/gsi/web/guides/handle-credential-responses-js-functions
|
|
1574
|
-
* @param googleResponse The Google credential response.
|
|
1575
|
-
* @returns Promise to the authentication response.
|
|
1576
|
-
*/
|
|
1577
|
-
startGoogleLogin(googleResponse) {
|
|
1578
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
1579
|
-
yield __classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_startPkce).call(this);
|
|
1580
|
-
return this.post('auth/google', googleResponse);
|
|
1581
|
-
});
|
|
1582
|
-
}
|
|
1583
|
-
/**
|
|
1584
|
-
* Signs out locally.
|
|
1585
|
-
* Does not invalidate tokens with the server.
|
|
1586
|
-
*/
|
|
1587
|
-
signOut() {
|
|
1588
|
-
this.clear();
|
|
1589
|
-
return Promise.resolve();
|
|
1590
|
-
}
|
|
1591
|
-
/**
|
|
1592
|
-
* Tries to sign in the user.
|
|
1593
|
-
* Returns true if the user is signed in.
|
|
1594
|
-
* This may result in navigating away to the sign in page.
|
|
1595
|
-
*/
|
|
1596
|
-
signInWithRedirect() {
|
|
1597
|
-
const urlParams = new URLSearchParams(window.location.search);
|
|
1598
|
-
const code = urlParams.get('code');
|
|
1599
|
-
if (!code) {
|
|
1600
|
-
__classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_requestAuthorization).call(this);
|
|
1601
|
-
return undefined;
|
|
1602
|
-
}
|
|
1603
|
-
else {
|
|
1604
|
-
return this.processCode(code);
|
|
1605
|
-
}
|
|
1606
|
-
}
|
|
1607
|
-
/**
|
|
1608
|
-
* Tries to sign out the user.
|
|
1609
|
-
* See: https://docs.aws.amazon.com/cognito/latest/developerguide/logout-endpoint.html
|
|
1610
|
-
*/
|
|
1611
|
-
signOutWithRedirect() {
|
|
1612
|
-
window.location.assign(__classPrivateFieldGet(this, _MedplumClient_logoutUrl, "f"));
|
|
1613
|
-
}
|
|
1614
|
-
/**
|
|
1615
|
-
* Builds a FHIR URL from a collection of URL path components.
|
|
1616
|
-
* For example, `buildUrl('/Patient', '123')` returns `fhir/R4/Patient/123`.
|
|
1617
|
-
* @param path The path component of the URL.
|
|
1618
|
-
* @returns The well-formed FHIR URL.
|
|
1619
|
-
*/
|
|
1620
|
-
fhirUrl(...path) {
|
|
1621
|
-
const builder = [__classPrivateFieldGet(this, _MedplumClient_baseUrl, "f"), 'fhir/R4'];
|
|
1622
|
-
path.forEach((p) => builder.push('/', encodeURIComponent(p)));
|
|
1623
|
-
return builder.join('');
|
|
1624
|
-
}
|
|
1625
|
-
/**
|
|
1626
|
-
* Sends a FHIR search request.
|
|
1627
|
-
*
|
|
1628
|
-
* Example using a FHIR search string:
|
|
1629
|
-
*
|
|
1630
|
-
* ```typescript
|
|
1631
|
-
* const bundle = await client.search('Patient?name=Alice');
|
|
1632
|
-
* console.log(bundle);
|
|
1633
|
-
* ```
|
|
1634
|
-
*
|
|
1635
|
-
* Example using a structured search:
|
|
1636
|
-
*
|
|
1637
|
-
* ```typescript
|
|
1638
|
-
* const bundle = await client.search({
|
|
1639
|
-
* resourceType: 'Patient',
|
|
1640
|
-
* filters: [{
|
|
1641
|
-
* code: 'name',
|
|
1642
|
-
* operator: 'eq',
|
|
1643
|
-
* value: 'Alice',
|
|
1644
|
-
* }]
|
|
1645
|
-
* });
|
|
1646
|
-
* console.log(bundle);
|
|
1647
|
-
* ```
|
|
1648
|
-
*
|
|
1649
|
-
* The return value is a FHIR bundle:
|
|
1650
|
-
*
|
|
1651
|
-
* ```json
|
|
1652
|
-
* {
|
|
1653
|
-
* "resourceType": "Bundle",
|
|
1654
|
-
* "type": "searchest",
|
|
1655
|
-
* "total": 1,
|
|
1656
|
-
* "entry": [
|
|
1657
|
-
* {
|
|
1658
|
-
* "resource": {
|
|
1659
|
-
* "resourceType": "Patient",
|
|
1660
|
-
* "name": [
|
|
1661
|
-
* {
|
|
1662
|
-
* "given": [
|
|
1663
|
-
* "George"
|
|
1664
|
-
* ],
|
|
1665
|
-
* "family": "Washington"
|
|
1666
|
-
* }
|
|
1667
|
-
* ],
|
|
1668
|
-
* }
|
|
1669
|
-
* }
|
|
1670
|
-
* ]
|
|
1671
|
-
* }
|
|
1672
|
-
* ```
|
|
1673
|
-
*
|
|
1674
|
-
* See FHIR search for full details: https://www.hl7.org/fhir/search.html
|
|
1675
|
-
*
|
|
1676
|
-
* @param query The search query as either a string or a structured search object.
|
|
1677
|
-
* @returns Promise to the search result bundle.
|
|
1678
|
-
*/
|
|
1679
|
-
search(query, options = {}) {
|
|
1680
|
-
return this.get(typeof query === 'string' ? 'fhir/R4/' + query : this.fhirUrl(query.resourceType) + formatSearchQuery(query), options);
|
|
1681
|
-
}
|
|
1682
|
-
/**
|
|
1683
|
-
* Sends a FHIR search request for a single resource.
|
|
1684
|
-
*
|
|
1685
|
-
* This is a convenience method for `search()` that returns the first resource rather than a `Bundle`.
|
|
1686
|
-
*
|
|
1687
|
-
* Example using a FHIR search string:
|
|
1688
|
-
*
|
|
1689
|
-
* ```typescript
|
|
1690
|
-
* const patient = await client.searchOne('Patient?identifier=123');
|
|
1691
|
-
* console.log(patient);
|
|
1692
|
-
* ```
|
|
1693
|
-
*
|
|
1694
|
-
* The return value is the resource, if available; otherwise, undefined.
|
|
1695
|
-
*
|
|
1696
|
-
* See FHIR search for full details: https://www.hl7.org/fhir/search.html
|
|
1697
|
-
*
|
|
1698
|
-
* @param query The search query as either a string or a structured search object.
|
|
1699
|
-
* @returns Promise to the search result bundle.
|
|
1700
|
-
*/
|
|
1701
|
-
searchOne(query, options = {}) {
|
|
1702
|
-
var _a, _b;
|
|
1703
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
1704
|
-
const search = typeof query === 'string' ? parseSearchDefinition(query) : query;
|
|
1705
|
-
search.count = 1;
|
|
1706
|
-
const bundle = yield this.search(search, options);
|
|
1707
|
-
return (_b = (_a = bundle.entry) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.resource;
|
|
1708
|
-
});
|
|
1709
|
-
}
|
|
1710
|
-
/**
|
|
1711
|
-
* Sends a FHIR search request for an array of resources.
|
|
1712
|
-
*
|
|
1713
|
-
* This is a convenience method for `search()` that returns the resources as an array rather than a `Bundle`.
|
|
1714
|
-
*
|
|
1715
|
-
* Example using a FHIR search string:
|
|
1716
|
-
*
|
|
1717
|
-
* ```typescript
|
|
1718
|
-
* const patients = await client.searchResources('Patient?name=Alice');
|
|
1719
|
-
* console.log(patients);
|
|
1720
|
-
* ```
|
|
1721
|
-
*
|
|
1722
|
-
* The return value is an array of resources.
|
|
1723
|
-
*
|
|
1724
|
-
* See FHIR search for full details: https://www.hl7.org/fhir/search.html
|
|
1725
|
-
*
|
|
1726
|
-
* @param query The search query as either a string or a structured search object.
|
|
1727
|
-
* @returns Promise to the search result bundle.
|
|
1728
|
-
*/
|
|
1729
|
-
searchResources(query, options = {}) {
|
|
1730
|
-
var _a, _b;
|
|
1731
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
1732
|
-
const bundle = yield this.search(query, options);
|
|
1733
|
-
return (_b = (_a = bundle.entry) === null || _a === void 0 ? void 0 : _a.map((entry) => entry.resource)) !== null && _b !== void 0 ? _b : [];
|
|
1734
|
-
});
|
|
1735
|
-
}
|
|
1736
|
-
/**
|
|
1737
|
-
* Searches a ValueSet resource using the "expand" operation.
|
|
1738
|
-
* See: https://www.hl7.org/fhir/operation-valueset-expand.html
|
|
1739
|
-
* @param system The ValueSet system url.
|
|
1740
|
-
* @param filter The search string.
|
|
1741
|
-
* @returns Promise to expanded ValueSet.
|
|
1742
|
-
*/
|
|
1743
|
-
searchValueSet(system, filter, options = {}) {
|
|
1744
|
-
return this.get(this.fhirUrl('ValueSet', '$expand') +
|
|
1745
|
-
`?url=${encodeURIComponent(system)}` +
|
|
1746
|
-
`&filter=${encodeURIComponent(filter)}`, options);
|
|
1747
|
-
}
|
|
1748
|
-
/**
|
|
1749
|
-
* Returns a cached resource if it is available.
|
|
1750
|
-
* @param resourceType The FHIR resource type.
|
|
1751
|
-
* @param id The FHIR resource ID.
|
|
1752
|
-
* @returns The resource if it is available in the cache; undefined otherwise.
|
|
1753
|
-
*/
|
|
1754
|
-
getCached(resourceType, id) {
|
|
1755
|
-
const cached = __classPrivateFieldGet(this, _MedplumClient_requestCache, "f").get(this.fhirUrl(resourceType, id));
|
|
1756
|
-
return cached && !cached.isPending() ? cached.read() : undefined;
|
|
1757
|
-
}
|
|
1758
|
-
/**
|
|
1759
|
-
* Returns a cached resource if it is available.
|
|
1760
|
-
* @param resourceType The FHIR resource type.
|
|
1761
|
-
* @param id The FHIR resource ID.
|
|
1762
|
-
* @returns The resource if it is available in the cache; undefined otherwise.
|
|
1763
|
-
*/
|
|
1764
|
-
getCachedReference(reference) {
|
|
1765
|
-
const refString = reference.reference;
|
|
1766
|
-
const [resourceType, id] = refString.split('/');
|
|
1767
|
-
return this.getCached(resourceType, id);
|
|
1768
|
-
}
|
|
1769
|
-
/**
|
|
1770
|
-
* Reads a resource by resource type and ID.
|
|
1771
|
-
*
|
|
1772
|
-
* Example:
|
|
1773
|
-
*
|
|
1774
|
-
* ```typescript
|
|
1775
|
-
* const patient = await medplum.readResource('Patient', '123');
|
|
1776
|
-
* console.log(patient);
|
|
1777
|
-
* ```
|
|
1778
|
-
*
|
|
1779
|
-
* See the FHIR "read" operation for full details: https://www.hl7.org/fhir/http.html#read
|
|
1780
|
-
*
|
|
1781
|
-
* @param resourceType The FHIR resource type.
|
|
1782
|
-
* @param id The resource ID.
|
|
1783
|
-
* @returns The resource if available; undefined otherwise.
|
|
1784
|
-
*/
|
|
1785
|
-
readResource(resourceType, id) {
|
|
1786
|
-
return this.get(this.fhirUrl(resourceType, id));
|
|
1787
|
-
}
|
|
1788
|
-
/**
|
|
1789
|
-
* Reads a resource by resource type and ID using the in-memory resource cache.
|
|
1790
|
-
*
|
|
1791
|
-
* If the resource is not available in the cache, it will be read from the server.
|
|
1792
|
-
*
|
|
1793
|
-
* Example:
|
|
1794
|
-
*
|
|
1795
|
-
* ```typescript
|
|
1796
|
-
* const patient = await medplum.readCached('Patient', '123');
|
|
1797
|
-
* console.log(patient);
|
|
1798
|
-
* ```
|
|
1799
|
-
*
|
|
1800
|
-
* See the FHIR "read" operation for full details: https://www.hl7.org/fhir/http.html#read
|
|
1801
|
-
*
|
|
1802
|
-
* @param resourceType The FHIR resource type.
|
|
1803
|
-
* @param id The resource ID.
|
|
1804
|
-
* @returns The resource if available; undefined otherwise.
|
|
1805
|
-
*/
|
|
1806
|
-
readCached(resourceType, id) {
|
|
1807
|
-
return this.get(this.fhirUrl(resourceType, id));
|
|
1808
|
-
}
|
|
1809
|
-
/**
|
|
1810
|
-
* Reads a resource by `Reference`.
|
|
1811
|
-
*
|
|
1812
|
-
* This is a convenience method for `readResource()` that accepts a `Reference` object.
|
|
1813
|
-
*
|
|
1814
|
-
* Example:
|
|
1815
|
-
*
|
|
1816
|
-
* ```typescript
|
|
1817
|
-
* const serviceRequest = await medplum.readResource('ServiceRequest', '123');
|
|
1818
|
-
* const patient = await medplum.readReference(serviceRequest.subject);
|
|
1819
|
-
* console.log(patient);
|
|
1820
|
-
* ```
|
|
1821
|
-
*
|
|
1822
|
-
* See the FHIR "read" operation for full details: https://www.hl7.org/fhir/http.html#read
|
|
1823
|
-
*
|
|
1824
|
-
* @param reference The FHIR reference object.
|
|
1825
|
-
* @returns The resource if available; undefined otherwise.
|
|
1826
|
-
*/
|
|
1827
|
-
readReference(reference) {
|
|
1828
|
-
const refString = reference === null || reference === void 0 ? void 0 : reference.reference;
|
|
1829
|
-
if (!refString) {
|
|
1830
|
-
return new ReadablePromise(Promise.reject('Missing reference'));
|
|
1831
|
-
}
|
|
1832
|
-
const [resourceType, id] = refString.split('/');
|
|
1833
|
-
return this.readResource(resourceType, id);
|
|
1834
|
-
}
|
|
1835
|
-
/**
|
|
1836
|
-
* Reads a resource by `Reference` using the in-memory resource cache.
|
|
1837
|
-
*
|
|
1838
|
-
* This is a convenience method for `readResource()` that accepts a `Reference` object.
|
|
1839
|
-
*
|
|
1840
|
-
* If the resource is not available in the cache, it will be read from the server.
|
|
1841
|
-
*
|
|
1842
|
-
* Example:
|
|
1843
|
-
*
|
|
1844
|
-
* ```typescript
|
|
1845
|
-
* const serviceRequest = await medplum.readResource('ServiceRequest', '123');
|
|
1846
|
-
* const patient = await medplum.readCachedReference(serviceRequest.subject);
|
|
1847
|
-
* console.log(patient);
|
|
1848
|
-
* ```
|
|
1849
|
-
*
|
|
1850
|
-
* See the FHIR "read" operation for full details: https://www.hl7.org/fhir/http.html#read
|
|
1851
|
-
*
|
|
1852
|
-
* @param reference The FHIR reference object.
|
|
1853
|
-
* @returns The resource if available; undefined otherwise.
|
|
1854
|
-
*/
|
|
1855
|
-
readCachedReference(reference) {
|
|
1856
|
-
const refString = reference === null || reference === void 0 ? void 0 : reference.reference;
|
|
1857
|
-
if (!refString) {
|
|
1858
|
-
return new ReadablePromise(Promise.reject('Missing reference'));
|
|
1859
|
-
}
|
|
1860
|
-
const [resourceType, id] = refString.split('/');
|
|
1861
|
-
return this.readCached(resourceType, id);
|
|
1862
|
-
}
|
|
1863
|
-
/**
|
|
1864
|
-
* Returns a cached schema for a resource type.
|
|
1865
|
-
* If the schema is not cached, returns undefined.
|
|
1866
|
-
* It is assumed that a client will call requestSchema before using this method.
|
|
1867
|
-
* @param resourceType The FHIR resource type.
|
|
1868
|
-
* @returns The schema if immediately available, undefined otherwise.
|
|
1869
|
-
*/
|
|
1870
|
-
getSchema() {
|
|
1871
|
-
return __classPrivateFieldGet(this, _MedplumClient_schema, "f");
|
|
1872
|
-
}
|
|
1873
|
-
/**
|
|
1874
|
-
* Requests the schema for a resource type.
|
|
1875
|
-
* If the schema is already cached, the promise is resolved immediately.
|
|
1876
|
-
* @param resourceType The FHIR resource type.
|
|
1877
|
-
* @returns Promise to a schema with the requested resource type.
|
|
1878
|
-
*/
|
|
1879
|
-
requestSchema(resourceType) {
|
|
1880
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
1881
|
-
if (resourceType in __classPrivateFieldGet(this, _MedplumClient_schema, "f").types) {
|
|
1882
|
-
return Promise.resolve(__classPrivateFieldGet(this, _MedplumClient_schema, "f"));
|
|
1883
|
-
}
|
|
1321
|
+
// PKCE auth ased on:
|
|
1322
|
+
// https://aws.amazon.com/blogs/security/how-to-add-authentication-single-page-web-application-with-amazon-cognito-oauth2-implementation/
|
|
1323
|
+
var _MedplumClient_instances, _MedplumClient_fetch, _MedplumClient_storage, _MedplumClient_schema, _MedplumClient_requestCache, _MedplumClient_baseUrl, _MedplumClient_clientId, _MedplumClient_authorizeUrl, _MedplumClient_tokenUrl, _MedplumClient_logoutUrl, _MedplumClient_onUnauthenticated, _MedplumClient_accessToken, _MedplumClient_refreshToken, _MedplumClient_refreshPromise, _MedplumClient_profilePromise, _MedplumClient_profile, _MedplumClient_config, _MedplumClient_addLogin, _MedplumClient_refreshProfile, _MedplumClient_request, _MedplumClient_addFetchOptionsDefaults, _MedplumClient_setRequestContentType, _MedplumClient_setRequestBody, _MedplumClient_handleUnauthenticated, _MedplumClient_startPkce, _MedplumClient_requestAuthorization, _MedplumClient_refresh, _MedplumClient_fetchTokens, _MedplumClient_verifyTokens, _MedplumClient_setupStorageListener;
|
|
1324
|
+
const DEFAULT_BASE_URL = 'https://api.medplum.com/';
|
|
1325
|
+
const DEFAULT_SCOPE = 'launch/patient openid fhirUser offline_access user/*.*';
|
|
1326
|
+
const DEFAULT_RESOURCE_CACHE_SIZE = 1000;
|
|
1327
|
+
const JSON_CONTENT_TYPE = 'application/json';
|
|
1328
|
+
const FHIR_CONTENT_TYPE = 'application/fhir+json';
|
|
1329
|
+
const PATCH_CONTENT_TYPE = 'application/json-patch+json';
|
|
1330
|
+
/**
|
|
1331
|
+
* The MedplumClient class provides a client for the Medplum FHIR server.
|
|
1332
|
+
*
|
|
1333
|
+
* The client can be used in the browser, in a NodeJS application, or in a Medplum Bot.
|
|
1334
|
+
*
|
|
1335
|
+
* The client provides helpful methods for common operations such as:
|
|
1336
|
+
* 1) Authenticating
|
|
1337
|
+
* 2) Creating resources
|
|
1338
|
+
* 2) Reading resources
|
|
1339
|
+
* 3) Updating resources
|
|
1340
|
+
* 5) Deleting resources
|
|
1341
|
+
* 6) Searching
|
|
1342
|
+
* 7) Making GraphQL queries
|
|
1343
|
+
*
|
|
1344
|
+
* Here is a quick example of how to use the client:
|
|
1345
|
+
*
|
|
1346
|
+
* ```typescript
|
|
1347
|
+
* import { MedplumClient } from '@medplum/core';
|
|
1348
|
+
* const medplum = new MedplumClient();
|
|
1349
|
+
* ```
|
|
1350
|
+
*
|
|
1351
|
+
* Create a `Patient`:
|
|
1352
|
+
*
|
|
1353
|
+
* ```typescript
|
|
1354
|
+
* const patient = await medplum.createResource({
|
|
1355
|
+
* resourceType: 'Patient',
|
|
1356
|
+
* name: [{
|
|
1357
|
+
* given: ['Alice'],
|
|
1358
|
+
* family: 'Smith'
|
|
1359
|
+
* }]
|
|
1360
|
+
* });
|
|
1361
|
+
* ```
|
|
1362
|
+
*
|
|
1363
|
+
* Read a `Patient` by ID:
|
|
1364
|
+
*
|
|
1365
|
+
* ```typescript
|
|
1366
|
+
* const patient = await medplum.readResource('Patient', '123');
|
|
1367
|
+
* console.log(patient.name[0].given[0]);
|
|
1368
|
+
* ```
|
|
1369
|
+
*
|
|
1370
|
+
* Search for a `Patient` by name:
|
|
1371
|
+
*
|
|
1372
|
+
* ```typescript
|
|
1373
|
+
* const bundle = await medplum.search('Patient?name=Alice');
|
|
1374
|
+
* console.log(bundle.total);
|
|
1375
|
+
* ```
|
|
1376
|
+
*
|
|
1377
|
+
*/
|
|
1378
|
+
class MedplumClient extends EventTarget {
|
|
1379
|
+
constructor(options) {
|
|
1380
|
+
var _a;
|
|
1381
|
+
super();
|
|
1382
|
+
_MedplumClient_instances.add(this);
|
|
1383
|
+
_MedplumClient_fetch.set(this, void 0);
|
|
1384
|
+
_MedplumClient_storage.set(this, void 0);
|
|
1385
|
+
_MedplumClient_schema.set(this, void 0);
|
|
1386
|
+
_MedplumClient_requestCache.set(this, void 0);
|
|
1387
|
+
_MedplumClient_baseUrl.set(this, void 0);
|
|
1388
|
+
_MedplumClient_clientId.set(this, void 0);
|
|
1389
|
+
_MedplumClient_authorizeUrl.set(this, void 0);
|
|
1390
|
+
_MedplumClient_tokenUrl.set(this, void 0);
|
|
1391
|
+
_MedplumClient_logoutUrl.set(this, void 0);
|
|
1392
|
+
_MedplumClient_onUnauthenticated.set(this, void 0);
|
|
1393
|
+
_MedplumClient_accessToken.set(this, void 0);
|
|
1394
|
+
_MedplumClient_refreshToken.set(this, void 0);
|
|
1395
|
+
_MedplumClient_refreshPromise.set(this, void 0);
|
|
1396
|
+
_MedplumClient_profilePromise.set(this, void 0);
|
|
1397
|
+
_MedplumClient_profile.set(this, void 0);
|
|
1398
|
+
_MedplumClient_config.set(this, void 0);
|
|
1399
|
+
if (options === null || options === void 0 ? void 0 : options.baseUrl) {
|
|
1400
|
+
if (!options.baseUrl.startsWith('http')) {
|
|
1401
|
+
throw new Error('Base URL must start with http or https');
|
|
1402
|
+
}
|
|
1403
|
+
if (!options.baseUrl.endsWith('/')) {
|
|
1404
|
+
throw new Error('Base URL must end with a trailing slash');
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
__classPrivateFieldSet(this, _MedplumClient_fetch, (options === null || options === void 0 ? void 0 : options.fetch) || window.fetch.bind(window), "f");
|
|
1408
|
+
__classPrivateFieldSet(this, _MedplumClient_storage, new ClientStorage(), "f");
|
|
1409
|
+
__classPrivateFieldSet(this, _MedplumClient_schema, createSchema(), "f");
|
|
1410
|
+
__classPrivateFieldSet(this, _MedplumClient_requestCache, new LRUCache((_a = options === null || options === void 0 ? void 0 : options.resourceCacheSize) !== null && _a !== void 0 ? _a : DEFAULT_RESOURCE_CACHE_SIZE), "f");
|
|
1411
|
+
__classPrivateFieldSet(this, _MedplumClient_baseUrl, (options === null || options === void 0 ? void 0 : options.baseUrl) || DEFAULT_BASE_URL, "f");
|
|
1412
|
+
__classPrivateFieldSet(this, _MedplumClient_clientId, (options === null || options === void 0 ? void 0 : options.clientId) || '', "f");
|
|
1413
|
+
__classPrivateFieldSet(this, _MedplumClient_authorizeUrl, (options === null || options === void 0 ? void 0 : options.authorizeUrl) || __classPrivateFieldGet(this, _MedplumClient_baseUrl, "f") + 'oauth2/authorize', "f");
|
|
1414
|
+
__classPrivateFieldSet(this, _MedplumClient_tokenUrl, (options === null || options === void 0 ? void 0 : options.tokenUrl) || __classPrivateFieldGet(this, _MedplumClient_baseUrl, "f") + 'oauth2/token', "f");
|
|
1415
|
+
__classPrivateFieldSet(this, _MedplumClient_logoutUrl, (options === null || options === void 0 ? void 0 : options.logoutUrl) || __classPrivateFieldGet(this, _MedplumClient_baseUrl, "f") + 'oauth2/logout', "f");
|
|
1416
|
+
__classPrivateFieldSet(this, _MedplumClient_onUnauthenticated, options === null || options === void 0 ? void 0 : options.onUnauthenticated, "f");
|
|
1417
|
+
const activeLogin = this.getActiveLogin();
|
|
1418
|
+
if (activeLogin) {
|
|
1419
|
+
__classPrivateFieldSet(this, _MedplumClient_accessToken, activeLogin.accessToken, "f");
|
|
1420
|
+
__classPrivateFieldSet(this, _MedplumClient_refreshToken, activeLogin.refreshToken, "f");
|
|
1421
|
+
__classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_refreshProfile).call(this).catch(console.log);
|
|
1422
|
+
}
|
|
1423
|
+
__classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_setupStorageListener).call(this);
|
|
1424
|
+
}
|
|
1425
|
+
/**
|
|
1426
|
+
* Clears all auth state including local storage and session storage.
|
|
1427
|
+
*/
|
|
1428
|
+
clear() {
|
|
1429
|
+
__classPrivateFieldGet(this, _MedplumClient_storage, "f").clear();
|
|
1430
|
+
__classPrivateFieldGet(this, _MedplumClient_requestCache, "f").clear();
|
|
1431
|
+
__classPrivateFieldSet(this, _MedplumClient_accessToken, undefined, "f");
|
|
1432
|
+
__classPrivateFieldSet(this, _MedplumClient_refreshToken, undefined, "f");
|
|
1433
|
+
__classPrivateFieldSet(this, _MedplumClient_profile, undefined, "f");
|
|
1434
|
+
__classPrivateFieldSet(this, _MedplumClient_config, undefined, "f");
|
|
1435
|
+
this.dispatchEvent({ type: 'change' });
|
|
1436
|
+
}
|
|
1437
|
+
/**
|
|
1438
|
+
* Makes an HTTP GET request to the specified URL.
|
|
1439
|
+
*
|
|
1440
|
+
* This is a lower level method for custom requests.
|
|
1441
|
+
* For common operations, we recommend using higher level methods
|
|
1442
|
+
* such as `readResource()`, `search()`, etc.
|
|
1443
|
+
*
|
|
1444
|
+
* @param url The target URL.
|
|
1445
|
+
* @param options Optional fetch options.
|
|
1446
|
+
* @returns Promise to the response content.
|
|
1447
|
+
*/
|
|
1448
|
+
get(url, options = {}) {
|
|
1449
|
+
if (!(options === null || options === void 0 ? void 0 : options.cache)) {
|
|
1450
|
+
const cached = __classPrivateFieldGet(this, _MedplumClient_requestCache, "f").get(url);
|
|
1451
|
+
if (cached) {
|
|
1452
|
+
return cached;
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
const promise = new ReadablePromise(__classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_request).call(this, 'GET', url, options));
|
|
1456
|
+
__classPrivateFieldGet(this, _MedplumClient_requestCache, "f").set(url, promise);
|
|
1457
|
+
return promise;
|
|
1458
|
+
}
|
|
1459
|
+
/**
|
|
1460
|
+
* Makes an HTTP POST request to the specified URL.
|
|
1461
|
+
*
|
|
1462
|
+
* This is a lower level method for custom requests.
|
|
1463
|
+
* For common operations, we recommend using higher level methods
|
|
1464
|
+
* such as `createResource()`.
|
|
1465
|
+
*
|
|
1466
|
+
* @param url The target URL.
|
|
1467
|
+
* @param body The content body. Strings and `File` objects are passed directly. Other objects are converted to JSON.
|
|
1468
|
+
* @param contentType The content type to be included in the "Content-Type" header.
|
|
1469
|
+
* @param options Optional fetch options.
|
|
1470
|
+
* @returns Promise to the response content.
|
|
1471
|
+
*/
|
|
1472
|
+
post(url, body, contentType, options = {}) {
|
|
1473
|
+
if (body) {
|
|
1474
|
+
__classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_setRequestBody).call(this, options, body);
|
|
1475
|
+
}
|
|
1476
|
+
if (contentType) {
|
|
1477
|
+
__classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_setRequestContentType).call(this, options, contentType);
|
|
1478
|
+
}
|
|
1479
|
+
__classPrivateFieldGet(this, _MedplumClient_requestCache, "f").delete(url);
|
|
1480
|
+
return __classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_request).call(this, 'POST', url, options);
|
|
1481
|
+
}
|
|
1482
|
+
/**
|
|
1483
|
+
* Makes an HTTP PUT request to the specified URL.
|
|
1484
|
+
*
|
|
1485
|
+
* This is a lower level method for custom requests.
|
|
1486
|
+
* For common operations, we recommend using higher level methods
|
|
1487
|
+
* such as `updateResource()`.
|
|
1488
|
+
*
|
|
1489
|
+
* @param url The target URL.
|
|
1490
|
+
* @param body The content body. Strings and `File` objects are passed directly. Other objects are converted to JSON.
|
|
1491
|
+
* @param contentType The content type to be included in the "Content-Type" header.
|
|
1492
|
+
* @param options Optional fetch options.
|
|
1493
|
+
* @returns Promise to the response content.
|
|
1494
|
+
*/
|
|
1495
|
+
put(url, body, contentType, options = {}) {
|
|
1496
|
+
if (body) {
|
|
1497
|
+
__classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_setRequestBody).call(this, options, body);
|
|
1498
|
+
}
|
|
1499
|
+
if (contentType) {
|
|
1500
|
+
__classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_setRequestContentType).call(this, options, contentType);
|
|
1501
|
+
}
|
|
1502
|
+
__classPrivateFieldGet(this, _MedplumClient_requestCache, "f").delete(url);
|
|
1503
|
+
return __classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_request).call(this, 'PUT', url, options);
|
|
1504
|
+
}
|
|
1505
|
+
/**
|
|
1506
|
+
* Makes an HTTP PATCH request to the specified URL.
|
|
1507
|
+
*
|
|
1508
|
+
* This is a lower level method for custom requests.
|
|
1509
|
+
* For common operations, we recommend using higher level methods
|
|
1510
|
+
* such as `patchResource()`.
|
|
1511
|
+
*
|
|
1512
|
+
* @param url The target URL.
|
|
1513
|
+
* @param operations Array of JSONPatch operations.
|
|
1514
|
+
* @param options Optional fetch options.
|
|
1515
|
+
* @returns Promise to the response content.
|
|
1516
|
+
*/
|
|
1517
|
+
patch(url, operations, options = {}) {
|
|
1518
|
+
__classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_setRequestBody).call(this, options, operations);
|
|
1519
|
+
__classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_setRequestContentType).call(this, options, PATCH_CONTENT_TYPE);
|
|
1520
|
+
__classPrivateFieldGet(this, _MedplumClient_requestCache, "f").delete(url);
|
|
1521
|
+
return __classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_request).call(this, 'PATCH', url, options);
|
|
1522
|
+
}
|
|
1523
|
+
/**
|
|
1524
|
+
* Makes an HTTP DELETE request to the specified URL.
|
|
1525
|
+
*
|
|
1526
|
+
* This is a lower level method for custom requests.
|
|
1527
|
+
* For common operations, we recommend using higher level methods
|
|
1528
|
+
* such as `deleteResource()`.
|
|
1529
|
+
*
|
|
1530
|
+
* @param url The target URL.
|
|
1531
|
+
* @param options Optional fetch options.
|
|
1532
|
+
* @returns Promise to the response content.
|
|
1533
|
+
*/
|
|
1534
|
+
delete(url, options = {}) {
|
|
1535
|
+
__classPrivateFieldGet(this, _MedplumClient_requestCache, "f").delete(url);
|
|
1536
|
+
return __classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_request).call(this, 'DELETE', url, options);
|
|
1537
|
+
}
|
|
1538
|
+
/**
|
|
1539
|
+
* Tries to register a new user.
|
|
1540
|
+
* @param request The registration request.
|
|
1541
|
+
* @returns Promise to the authentication response.
|
|
1542
|
+
*/
|
|
1543
|
+
register(request) {
|
|
1544
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1545
|
+
const response = yield this.post('auth/register', request);
|
|
1546
|
+
yield this.setActiveLogin(response);
|
|
1547
|
+
});
|
|
1548
|
+
}
|
|
1549
|
+
/**
|
|
1550
|
+
* Initiates a user login flow.
|
|
1551
|
+
* @param email The email address of the user.
|
|
1552
|
+
* @param password The password of the user.
|
|
1553
|
+
* @param remember Optional flag to remember the user.
|
|
1554
|
+
* @returns Promise to the authentication response.
|
|
1555
|
+
*/
|
|
1556
|
+
startLogin(email, password, remember) {
|
|
1557
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1558
|
+
yield __classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_startPkce).call(this);
|
|
1559
|
+
return this.post('auth/login', {
|
|
1560
|
+
clientId: __classPrivateFieldGet(this, _MedplumClient_clientId, "f"),
|
|
1561
|
+
scope: DEFAULT_SCOPE,
|
|
1562
|
+
codeChallengeMethod: 'S256',
|
|
1563
|
+
codeChallenge: __classPrivateFieldGet(this, _MedplumClient_storage, "f").getString('codeChallenge'),
|
|
1564
|
+
email,
|
|
1565
|
+
password,
|
|
1566
|
+
remember: !!remember,
|
|
1567
|
+
});
|
|
1568
|
+
});
|
|
1569
|
+
}
|
|
1570
|
+
/**
|
|
1571
|
+
* Tries to sign in with Google authentication.
|
|
1572
|
+
* The response parameter is the result of a Google authentication.
|
|
1573
|
+
* See: https://developers.google.com/identity/gsi/web/guides/handle-credential-responses-js-functions
|
|
1574
|
+
* @param googleResponse The Google credential response.
|
|
1575
|
+
* @returns Promise to the authentication response.
|
|
1576
|
+
*/
|
|
1577
|
+
startGoogleLogin(googleResponse) {
|
|
1578
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1579
|
+
yield __classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_startPkce).call(this);
|
|
1580
|
+
return this.post('auth/google', googleResponse);
|
|
1581
|
+
});
|
|
1582
|
+
}
|
|
1583
|
+
/**
|
|
1584
|
+
* Signs out locally.
|
|
1585
|
+
* Does not invalidate tokens with the server.
|
|
1586
|
+
*/
|
|
1587
|
+
signOut() {
|
|
1588
|
+
this.clear();
|
|
1589
|
+
return Promise.resolve();
|
|
1590
|
+
}
|
|
1591
|
+
/**
|
|
1592
|
+
* Tries to sign in the user.
|
|
1593
|
+
* Returns true if the user is signed in.
|
|
1594
|
+
* This may result in navigating away to the sign in page.
|
|
1595
|
+
*/
|
|
1596
|
+
signInWithRedirect() {
|
|
1597
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
1598
|
+
const code = urlParams.get('code');
|
|
1599
|
+
if (!code) {
|
|
1600
|
+
__classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_requestAuthorization).call(this);
|
|
1601
|
+
return undefined;
|
|
1602
|
+
}
|
|
1603
|
+
else {
|
|
1604
|
+
return this.processCode(code);
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
/**
|
|
1608
|
+
* Tries to sign out the user.
|
|
1609
|
+
* See: https://docs.aws.amazon.com/cognito/latest/developerguide/logout-endpoint.html
|
|
1610
|
+
*/
|
|
1611
|
+
signOutWithRedirect() {
|
|
1612
|
+
window.location.assign(__classPrivateFieldGet(this, _MedplumClient_logoutUrl, "f"));
|
|
1613
|
+
}
|
|
1614
|
+
/**
|
|
1615
|
+
* Builds a FHIR URL from a collection of URL path components.
|
|
1616
|
+
* For example, `buildUrl('/Patient', '123')` returns `fhir/R4/Patient/123`.
|
|
1617
|
+
* @param path The path component of the URL.
|
|
1618
|
+
* @returns The well-formed FHIR URL.
|
|
1619
|
+
*/
|
|
1620
|
+
fhirUrl(...path) {
|
|
1621
|
+
const builder = [__classPrivateFieldGet(this, _MedplumClient_baseUrl, "f"), 'fhir/R4'];
|
|
1622
|
+
path.forEach((p) => builder.push('/', encodeURIComponent(p)));
|
|
1623
|
+
return builder.join('');
|
|
1624
|
+
}
|
|
1625
|
+
/**
|
|
1626
|
+
* Sends a FHIR search request.
|
|
1627
|
+
*
|
|
1628
|
+
* Example using a FHIR search string:
|
|
1629
|
+
*
|
|
1630
|
+
* ```typescript
|
|
1631
|
+
* const bundle = await client.search('Patient?name=Alice');
|
|
1632
|
+
* console.log(bundle);
|
|
1633
|
+
* ```
|
|
1634
|
+
*
|
|
1635
|
+
* Example using a structured search:
|
|
1636
|
+
*
|
|
1637
|
+
* ```typescript
|
|
1638
|
+
* const bundle = await client.search({
|
|
1639
|
+
* resourceType: 'Patient',
|
|
1640
|
+
* filters: [{
|
|
1641
|
+
* code: 'name',
|
|
1642
|
+
* operator: 'eq',
|
|
1643
|
+
* value: 'Alice',
|
|
1644
|
+
* }]
|
|
1645
|
+
* });
|
|
1646
|
+
* console.log(bundle);
|
|
1647
|
+
* ```
|
|
1648
|
+
*
|
|
1649
|
+
* The return value is a FHIR bundle:
|
|
1650
|
+
*
|
|
1651
|
+
* ```json
|
|
1652
|
+
* {
|
|
1653
|
+
* "resourceType": "Bundle",
|
|
1654
|
+
* "type": "searchest",
|
|
1655
|
+
* "total": 1,
|
|
1656
|
+
* "entry": [
|
|
1657
|
+
* {
|
|
1658
|
+
* "resource": {
|
|
1659
|
+
* "resourceType": "Patient",
|
|
1660
|
+
* "name": [
|
|
1661
|
+
* {
|
|
1662
|
+
* "given": [
|
|
1663
|
+
* "George"
|
|
1664
|
+
* ],
|
|
1665
|
+
* "family": "Washington"
|
|
1666
|
+
* }
|
|
1667
|
+
* ],
|
|
1668
|
+
* }
|
|
1669
|
+
* }
|
|
1670
|
+
* ]
|
|
1671
|
+
* }
|
|
1672
|
+
* ```
|
|
1673
|
+
*
|
|
1674
|
+
* See FHIR search for full details: https://www.hl7.org/fhir/search.html
|
|
1675
|
+
*
|
|
1676
|
+
* @param query The search query as either a string or a structured search object.
|
|
1677
|
+
* @returns Promise to the search result bundle.
|
|
1678
|
+
*/
|
|
1679
|
+
search(query, options = {}) {
|
|
1680
|
+
return this.get(typeof query === 'string' ? 'fhir/R4/' + query : this.fhirUrl(query.resourceType) + formatSearchQuery(query), options);
|
|
1681
|
+
}
|
|
1682
|
+
/**
|
|
1683
|
+
* Sends a FHIR search request for a single resource.
|
|
1684
|
+
*
|
|
1685
|
+
* This is a convenience method for `search()` that returns the first resource rather than a `Bundle`.
|
|
1686
|
+
*
|
|
1687
|
+
* Example using a FHIR search string:
|
|
1688
|
+
*
|
|
1689
|
+
* ```typescript
|
|
1690
|
+
* const patient = await client.searchOne('Patient?identifier=123');
|
|
1691
|
+
* console.log(patient);
|
|
1692
|
+
* ```
|
|
1693
|
+
*
|
|
1694
|
+
* The return value is the resource, if available; otherwise, undefined.
|
|
1695
|
+
*
|
|
1696
|
+
* See FHIR search for full details: https://www.hl7.org/fhir/search.html
|
|
1697
|
+
*
|
|
1698
|
+
* @param query The search query as either a string or a structured search object.
|
|
1699
|
+
* @returns Promise to the search result bundle.
|
|
1700
|
+
*/
|
|
1701
|
+
searchOne(query, options = {}) {
|
|
1702
|
+
var _a, _b;
|
|
1703
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1704
|
+
const search = typeof query === 'string' ? parseSearchDefinition(query) : query;
|
|
1705
|
+
search.count = 1;
|
|
1706
|
+
const bundle = yield this.search(search, options);
|
|
1707
|
+
return (_b = (_a = bundle.entry) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.resource;
|
|
1708
|
+
});
|
|
1709
|
+
}
|
|
1710
|
+
/**
|
|
1711
|
+
* Sends a FHIR search request for an array of resources.
|
|
1712
|
+
*
|
|
1713
|
+
* This is a convenience method for `search()` that returns the resources as an array rather than a `Bundle`.
|
|
1714
|
+
*
|
|
1715
|
+
* Example using a FHIR search string:
|
|
1716
|
+
*
|
|
1717
|
+
* ```typescript
|
|
1718
|
+
* const patients = await client.searchResources('Patient?name=Alice');
|
|
1719
|
+
* console.log(patients);
|
|
1720
|
+
* ```
|
|
1721
|
+
*
|
|
1722
|
+
* The return value is an array of resources.
|
|
1723
|
+
*
|
|
1724
|
+
* See FHIR search for full details: https://www.hl7.org/fhir/search.html
|
|
1725
|
+
*
|
|
1726
|
+
* @param query The search query as either a string or a structured search object.
|
|
1727
|
+
* @returns Promise to the search result bundle.
|
|
1728
|
+
*/
|
|
1729
|
+
searchResources(query, options = {}) {
|
|
1730
|
+
var _a, _b;
|
|
1731
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1732
|
+
const bundle = yield this.search(query, options);
|
|
1733
|
+
return (_b = (_a = bundle.entry) === null || _a === void 0 ? void 0 : _a.map((entry) => entry.resource)) !== null && _b !== void 0 ? _b : [];
|
|
1734
|
+
});
|
|
1735
|
+
}
|
|
1736
|
+
/**
|
|
1737
|
+
* Searches a ValueSet resource using the "expand" operation.
|
|
1738
|
+
* See: https://www.hl7.org/fhir/operation-valueset-expand.html
|
|
1739
|
+
* @param system The ValueSet system url.
|
|
1740
|
+
* @param filter The search string.
|
|
1741
|
+
* @returns Promise to expanded ValueSet.
|
|
1742
|
+
*/
|
|
1743
|
+
searchValueSet(system, filter, options = {}) {
|
|
1744
|
+
return this.get(this.fhirUrl('ValueSet', '$expand') +
|
|
1745
|
+
`?url=${encodeURIComponent(system)}` +
|
|
1746
|
+
`&filter=${encodeURIComponent(filter)}`, options);
|
|
1747
|
+
}
|
|
1748
|
+
/**
|
|
1749
|
+
* Returns a cached resource if it is available.
|
|
1750
|
+
* @param resourceType The FHIR resource type.
|
|
1751
|
+
* @param id The FHIR resource ID.
|
|
1752
|
+
* @returns The resource if it is available in the cache; undefined otherwise.
|
|
1753
|
+
*/
|
|
1754
|
+
getCached(resourceType, id) {
|
|
1755
|
+
const cached = __classPrivateFieldGet(this, _MedplumClient_requestCache, "f").get(this.fhirUrl(resourceType, id));
|
|
1756
|
+
return cached && !cached.isPending() ? cached.read() : undefined;
|
|
1757
|
+
}
|
|
1758
|
+
/**
|
|
1759
|
+
* Returns a cached resource if it is available.
|
|
1760
|
+
* @param resourceType The FHIR resource type.
|
|
1761
|
+
* @param id The FHIR resource ID.
|
|
1762
|
+
* @returns The resource if it is available in the cache; undefined otherwise.
|
|
1763
|
+
*/
|
|
1764
|
+
getCachedReference(reference) {
|
|
1765
|
+
const refString = reference.reference;
|
|
1766
|
+
const [resourceType, id] = refString.split('/');
|
|
1767
|
+
return this.getCached(resourceType, id);
|
|
1768
|
+
}
|
|
1769
|
+
/**
|
|
1770
|
+
* Reads a resource by resource type and ID.
|
|
1771
|
+
*
|
|
1772
|
+
* Example:
|
|
1773
|
+
*
|
|
1774
|
+
* ```typescript
|
|
1775
|
+
* const patient = await medplum.readResource('Patient', '123');
|
|
1776
|
+
* console.log(patient);
|
|
1777
|
+
* ```
|
|
1778
|
+
*
|
|
1779
|
+
* See the FHIR "read" operation for full details: https://www.hl7.org/fhir/http.html#read
|
|
1780
|
+
*
|
|
1781
|
+
* @param resourceType The FHIR resource type.
|
|
1782
|
+
* @param id The resource ID.
|
|
1783
|
+
* @returns The resource if available; undefined otherwise.
|
|
1784
|
+
*/
|
|
1785
|
+
readResource(resourceType, id) {
|
|
1786
|
+
return this.get(this.fhirUrl(resourceType, id));
|
|
1787
|
+
}
|
|
1788
|
+
/**
|
|
1789
|
+
* Reads a resource by resource type and ID using the in-memory resource cache.
|
|
1790
|
+
*
|
|
1791
|
+
* If the resource is not available in the cache, it will be read from the server.
|
|
1792
|
+
*
|
|
1793
|
+
* Example:
|
|
1794
|
+
*
|
|
1795
|
+
* ```typescript
|
|
1796
|
+
* const patient = await medplum.readCached('Patient', '123');
|
|
1797
|
+
* console.log(patient);
|
|
1798
|
+
* ```
|
|
1799
|
+
*
|
|
1800
|
+
* See the FHIR "read" operation for full details: https://www.hl7.org/fhir/http.html#read
|
|
1801
|
+
*
|
|
1802
|
+
* @param resourceType The FHIR resource type.
|
|
1803
|
+
* @param id The resource ID.
|
|
1804
|
+
* @returns The resource if available; undefined otherwise.
|
|
1805
|
+
*/
|
|
1806
|
+
readCached(resourceType, id) {
|
|
1807
|
+
return this.get(this.fhirUrl(resourceType, id));
|
|
1808
|
+
}
|
|
1809
|
+
/**
|
|
1810
|
+
* Reads a resource by `Reference`.
|
|
1811
|
+
*
|
|
1812
|
+
* This is a convenience method for `readResource()` that accepts a `Reference` object.
|
|
1813
|
+
*
|
|
1814
|
+
* Example:
|
|
1815
|
+
*
|
|
1816
|
+
* ```typescript
|
|
1817
|
+
* const serviceRequest = await medplum.readResource('ServiceRequest', '123');
|
|
1818
|
+
* const patient = await medplum.readReference(serviceRequest.subject);
|
|
1819
|
+
* console.log(patient);
|
|
1820
|
+
* ```
|
|
1821
|
+
*
|
|
1822
|
+
* See the FHIR "read" operation for full details: https://www.hl7.org/fhir/http.html#read
|
|
1823
|
+
*
|
|
1824
|
+
* @param reference The FHIR reference object.
|
|
1825
|
+
* @returns The resource if available; undefined otherwise.
|
|
1826
|
+
*/
|
|
1827
|
+
readReference(reference) {
|
|
1828
|
+
const refString = reference === null || reference === void 0 ? void 0 : reference.reference;
|
|
1829
|
+
if (!refString) {
|
|
1830
|
+
return new ReadablePromise(Promise.reject('Missing reference'));
|
|
1831
|
+
}
|
|
1832
|
+
const [resourceType, id] = refString.split('/');
|
|
1833
|
+
return this.readResource(resourceType, id);
|
|
1834
|
+
}
|
|
1835
|
+
/**
|
|
1836
|
+
* Reads a resource by `Reference` using the in-memory resource cache.
|
|
1837
|
+
*
|
|
1838
|
+
* This is a convenience method for `readResource()` that accepts a `Reference` object.
|
|
1839
|
+
*
|
|
1840
|
+
* If the resource is not available in the cache, it will be read from the server.
|
|
1841
|
+
*
|
|
1842
|
+
* Example:
|
|
1843
|
+
*
|
|
1844
|
+
* ```typescript
|
|
1845
|
+
* const serviceRequest = await medplum.readResource('ServiceRequest', '123');
|
|
1846
|
+
* const patient = await medplum.readCachedReference(serviceRequest.subject);
|
|
1847
|
+
* console.log(patient);
|
|
1848
|
+
* ```
|
|
1849
|
+
*
|
|
1850
|
+
* See the FHIR "read" operation for full details: https://www.hl7.org/fhir/http.html#read
|
|
1851
|
+
*
|
|
1852
|
+
* @param reference The FHIR reference object.
|
|
1853
|
+
* @returns The resource if available; undefined otherwise.
|
|
1854
|
+
*/
|
|
1855
|
+
readCachedReference(reference) {
|
|
1856
|
+
const refString = reference === null || reference === void 0 ? void 0 : reference.reference;
|
|
1857
|
+
if (!refString) {
|
|
1858
|
+
return new ReadablePromise(Promise.reject('Missing reference'));
|
|
1859
|
+
}
|
|
1860
|
+
const [resourceType, id] = refString.split('/');
|
|
1861
|
+
return this.readCached(resourceType, id);
|
|
1862
|
+
}
|
|
1863
|
+
/**
|
|
1864
|
+
* Returns a cached schema for a resource type.
|
|
1865
|
+
* If the schema is not cached, returns undefined.
|
|
1866
|
+
* It is assumed that a client will call requestSchema before using this method.
|
|
1867
|
+
* @param resourceType The FHIR resource type.
|
|
1868
|
+
* @returns The schema if immediately available, undefined otherwise.
|
|
1869
|
+
*/
|
|
1870
|
+
getSchema() {
|
|
1871
|
+
return __classPrivateFieldGet(this, _MedplumClient_schema, "f");
|
|
1872
|
+
}
|
|
1873
|
+
/**
|
|
1874
|
+
* Requests the schema for a resource type.
|
|
1875
|
+
* If the schema is already cached, the promise is resolved immediately.
|
|
1876
|
+
* @param resourceType The FHIR resource type.
|
|
1877
|
+
* @returns Promise to a schema with the requested resource type.
|
|
1878
|
+
*/
|
|
1879
|
+
requestSchema(resourceType) {
|
|
1880
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1881
|
+
if (resourceType in __classPrivateFieldGet(this, _MedplumClient_schema, "f").types) {
|
|
1882
|
+
return Promise.resolve(__classPrivateFieldGet(this, _MedplumClient_schema, "f"));
|
|
1883
|
+
}
|
|
1884
1884
|
const query = `{
|
|
1885
1885
|
StructureDefinitionList(name: "${encodeURIComponent(resourceType)}") {
|
|
1886
1886
|
name,
|
|
@@ -1909,789 +1909,789 @@ class MedplumClient extends EventTarget {
|
|
|
1909
1909
|
expression,
|
|
1910
1910
|
target
|
|
1911
1911
|
}
|
|
1912
|
-
}`.replace(/\s+/g, ' ');
|
|
1913
|
-
const response = (yield this.graphql(query));
|
|
1914
|
-
for (const structureDefinition of response.data.StructureDefinitionList) {
|
|
1915
|
-
indexStructureDefinition(__classPrivateFieldGet(this, _MedplumClient_schema, "f"), structureDefinition);
|
|
1916
|
-
}
|
|
1917
|
-
for (const searchParameter of response.data.SearchParameterList) {
|
|
1918
|
-
indexSearchParameter(__classPrivateFieldGet(this, _MedplumClient_schema, "f"), searchParameter);
|
|
1919
|
-
}
|
|
1920
|
-
return __classPrivateFieldGet(this, _MedplumClient_schema, "f");
|
|
1921
|
-
});
|
|
1922
|
-
}
|
|
1923
|
-
/**
|
|
1924
|
-
* Reads resource history by resource type and ID.
|
|
1925
|
-
*
|
|
1926
|
-
* The return value is a bundle of all versions of the resource.
|
|
1927
|
-
*
|
|
1928
|
-
* Example:
|
|
1929
|
-
*
|
|
1930
|
-
* ```typescript
|
|
1931
|
-
* const history = await medplum.readHistory('Patient', '123');
|
|
1932
|
-
* console.log(history);
|
|
1933
|
-
* ```
|
|
1934
|
-
*
|
|
1935
|
-
* See the FHIR "history" operation for full details: https://www.hl7.org/fhir/http.html#history
|
|
1936
|
-
*
|
|
1937
|
-
* @param resourceType The FHIR resource type.
|
|
1938
|
-
* @param id The resource ID.
|
|
1939
|
-
* @returns The resource if available; undefined otherwise.
|
|
1940
|
-
*/
|
|
1941
|
-
readHistory(resourceType, id) {
|
|
1942
|
-
return this.get(this.fhirUrl(resourceType, id, '_history'));
|
|
1943
|
-
}
|
|
1944
|
-
/**
|
|
1945
|
-
* Reads a specific version of a resource by resource type, ID, and version ID.
|
|
1946
|
-
*
|
|
1947
|
-
* Example:
|
|
1948
|
-
*
|
|
1949
|
-
* ```typescript
|
|
1950
|
-
* const version = await medplum.readVersion('Patient', '123', '456');
|
|
1951
|
-
* console.log(version);
|
|
1952
|
-
* ```
|
|
1953
|
-
*
|
|
1954
|
-
* See the FHIR "vread" operation for full details: https://www.hl7.org/fhir/http.html#vread
|
|
1955
|
-
*
|
|
1956
|
-
* @param resourceType The FHIR resource type.
|
|
1957
|
-
* @param id The resource ID.
|
|
1958
|
-
* @returns The resource if available; undefined otherwise.
|
|
1959
|
-
*/
|
|
1960
|
-
readVersion(resourceType, id, vid) {
|
|
1961
|
-
return this.get(this.fhirUrl(resourceType, id, '_history', vid));
|
|
1962
|
-
}
|
|
1963
|
-
readPatientEverything(id) {
|
|
1964
|
-
return this.get(this.fhirUrl('Patient', id, '$everything'));
|
|
1965
|
-
}
|
|
1966
|
-
/**
|
|
1967
|
-
* Creates a new FHIR resource.
|
|
1968
|
-
*
|
|
1969
|
-
* The return value is the newly created resource, including the ID and meta.
|
|
1970
|
-
*
|
|
1971
|
-
* Example:
|
|
1972
|
-
*
|
|
1973
|
-
* ```typescript
|
|
1974
|
-
* const result = await medplum.createResource({
|
|
1975
|
-
* resourceType: 'Patient',
|
|
1976
|
-
* name: [{
|
|
1977
|
-
* family: 'Smith',
|
|
1978
|
-
* given: ['John']
|
|
1979
|
-
* }]
|
|
1980
|
-
* });
|
|
1981
|
-
* console.log(result.id);
|
|
1982
|
-
* ```
|
|
1983
|
-
*
|
|
1984
|
-
* See the FHIR "create" operation for full details: https://www.hl7.org/fhir/http.html#create
|
|
1985
|
-
*
|
|
1986
|
-
* @param resource The FHIR resource to create.
|
|
1987
|
-
* @returns The result of the create operation.
|
|
1988
|
-
*/
|
|
1989
|
-
createResource(resource) {
|
|
1990
|
-
if (!resource.resourceType) {
|
|
1991
|
-
return Promise.reject('Missing resourceType');
|
|
1992
|
-
}
|
|
1993
|
-
return this.post(this.fhirUrl(resource.resourceType), resource);
|
|
1994
|
-
}
|
|
1995
|
-
/**
|
|
1996
|
-
* Conditionally create a new FHIR resource only if some equivalent resource does not already exist on the server.
|
|
1997
|
-
*
|
|
1998
|
-
* The return value is the existing resource or the newly created resource, including the ID and meta.
|
|
1999
|
-
*
|
|
2000
|
-
* Example:
|
|
2001
|
-
*
|
|
2002
|
-
* ```typescript
|
|
2003
|
-
* const result = await medplum.createResourceIfNoneExist(
|
|
2004
|
-
* 'Patient?identifier=123',
|
|
2005
|
-
* {
|
|
2006
|
-
* resourceType: 'Patient',
|
|
2007
|
-
* identifier: [{
|
|
2008
|
-
* system: 'http://example.com/mrn',
|
|
2009
|
-
* value: '123'
|
|
2010
|
-
* }]
|
|
2011
|
-
* name: [{
|
|
2012
|
-
* family: 'Smith',
|
|
2013
|
-
* given: ['John']
|
|
2014
|
-
* }]
|
|
2015
|
-
* });
|
|
2016
|
-
* console.log(result.id);
|
|
2017
|
-
* ```
|
|
2018
|
-
*
|
|
2019
|
-
* This method is syntactic sugar for:
|
|
2020
|
-
*
|
|
2021
|
-
* ```typescript
|
|
2022
|
-
* return searchOne(query) ?? createResource(resource);
|
|
2023
|
-
* ```
|
|
2024
|
-
*
|
|
2025
|
-
* The query parameter only contains the search parameters (what would be in the URL following the "?").
|
|
2026
|
-
*
|
|
2027
|
-
* See the FHIR "conditional create" operation for full details: https://www.hl7.org/fhir/http.html#ccreate
|
|
2028
|
-
*
|
|
2029
|
-
* @param resource The FHIR resource to create.
|
|
2030
|
-
* @param query The search query for an equivalent resource.
|
|
2031
|
-
* @returns The result of the create operation.
|
|
2032
|
-
*/
|
|
2033
|
-
createResourceIfNoneExist(resource, query) {
|
|
2034
|
-
var _a;
|
|
2035
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
2036
|
-
return (_a = (yield this.searchOne(`${resource.resourceType}?${query}`))) !== null && _a !== void 0 ? _a : this.createResource(resource);
|
|
2037
|
-
});
|
|
2038
|
-
}
|
|
2039
|
-
/**
|
|
2040
|
-
* Creates a FHIR `Binary` resource with the provided data content.
|
|
2041
|
-
*
|
|
2042
|
-
* The return value is the newly created resource, including the ID and meta.
|
|
2043
|
-
*
|
|
2044
|
-
* The `data` parameter can be a string or a `File` object.
|
|
2045
|
-
*
|
|
2046
|
-
* A `File` object often comes from a `<input type="file">` element.
|
|
2047
|
-
*
|
|
2048
|
-
* Example:
|
|
2049
|
-
*
|
|
2050
|
-
* ```typescript
|
|
2051
|
-
* const result = await medplum.createBinary(myFile, 'test.jpg', 'image/jpeg');
|
|
2052
|
-
* console.log(result.id);
|
|
2053
|
-
* ```
|
|
2054
|
-
*
|
|
2055
|
-
* See the FHIR "create" operation for full details: https://www.hl7.org/fhir/http.html#create
|
|
2056
|
-
*
|
|
2057
|
-
* @param data The binary data to upload.
|
|
2058
|
-
* @param filename Optional filename for the binary.
|
|
2059
|
-
* @param contentType Content type for the binary.
|
|
2060
|
-
* @returns The result of the create operation.
|
|
2061
|
-
*/
|
|
2062
|
-
createBinary(data, filename, contentType) {
|
|
2063
|
-
let url = this.fhirUrl('Binary');
|
|
2064
|
-
if (filename) {
|
|
2065
|
-
url += '?_filename=' + encodeURIComponent(filename);
|
|
2066
|
-
}
|
|
2067
|
-
return this.post(url, data, contentType);
|
|
2068
|
-
}
|
|
2069
|
-
/**
|
|
2070
|
-
* Creates a PDF as a FHIR `Binary` resource based on pdfmake document definition.
|
|
2071
|
-
*
|
|
2072
|
-
* The return value is the newly created resource, including the ID and meta.
|
|
2073
|
-
*
|
|
2074
|
-
* The `docDefinition` parameter is a pdfmake document definition.
|
|
2075
|
-
*
|
|
2076
|
-
* Example:
|
|
2077
|
-
*
|
|
2078
|
-
* ```typescript
|
|
2079
|
-
* const result = await medplum.createPdf({
|
|
2080
|
-
* content: ['Hello world']
|
|
2081
|
-
* });
|
|
2082
|
-
* console.log(result.id);
|
|
2083
|
-
* ```
|
|
2084
|
-
*
|
|
2085
|
-
* See the pdfmake document definition for full details: https://pdfmake.github.io/docs/0.1/document-definition-object/
|
|
2086
|
-
*
|
|
2087
|
-
* @param docDefinition The FHIR resource to create.
|
|
2088
|
-
* @returns The result of the create operation.
|
|
2089
|
-
*/
|
|
2090
|
-
createPdf(docDefinition, filename) {
|
|
2091
|
-
let url = this.fhirUrl('Binary') + '/$pdf';
|
|
2092
|
-
if (filename) {
|
|
2093
|
-
url += '?_filename=' + encodeURIComponent(filename);
|
|
2094
|
-
}
|
|
2095
|
-
return this.post(url, docDefinition, 'application/json');
|
|
2096
|
-
}
|
|
2097
|
-
/**
|
|
2098
|
-
* Updates a FHIR resource.
|
|
2099
|
-
*
|
|
2100
|
-
* The return value is the updated resource, including the ID and meta.
|
|
2101
|
-
*
|
|
2102
|
-
* Example:
|
|
2103
|
-
*
|
|
2104
|
-
* ```typescript
|
|
2105
|
-
* const result = await medplum.updateResource({
|
|
2106
|
-
* resourceType: 'Patient',
|
|
2107
|
-
* id: '123',
|
|
2108
|
-
* name: [{
|
|
2109
|
-
* family: 'Smith',
|
|
2110
|
-
* given: ['John']
|
|
2111
|
-
* }]
|
|
2112
|
-
* });
|
|
2113
|
-
* console.log(result.meta.versionId);
|
|
2114
|
-
* ```
|
|
2115
|
-
*
|
|
2116
|
-
* See the FHIR "update" operation for full details: https://www.hl7.org/fhir/http.html#update
|
|
2117
|
-
*
|
|
2118
|
-
* @param resource The FHIR resource to update.
|
|
2119
|
-
* @returns The result of the update operation.
|
|
2120
|
-
*/
|
|
2121
|
-
updateResource(resource) {
|
|
2122
|
-
if (!resource.resourceType) {
|
|
2123
|
-
return Promise.reject('Missing resourceType');
|
|
2124
|
-
}
|
|
2125
|
-
if (!resource.id) {
|
|
2126
|
-
return Promise.reject('Missing id');
|
|
2127
|
-
}
|
|
2128
|
-
return this.put(this.fhirUrl(resource.resourceType, resource.id), resource);
|
|
2129
|
-
}
|
|
2130
|
-
/**
|
|
2131
|
-
* Updates a FHIR resource using JSONPatch operations.
|
|
2132
|
-
*
|
|
2133
|
-
* The return value is the updated resource, including the ID and meta.
|
|
2134
|
-
*
|
|
2135
|
-
* Example:
|
|
2136
|
-
*
|
|
2137
|
-
* ```typescript
|
|
2138
|
-
* const result = await medplum.patchResource('Patient', '123', [
|
|
2139
|
-
* {op: 'replace', path: '/name/0/family', value: 'Smith'},
|
|
2140
|
-
* ]);
|
|
2141
|
-
* console.log(result.meta.versionId);
|
|
2142
|
-
* ```
|
|
2143
|
-
*
|
|
2144
|
-
* See the FHIR "update" operation for full details: https://www.hl7.org/fhir/http.html#patch
|
|
2145
|
-
*
|
|
2146
|
-
* See the JSONPatch specification for full details: https://tools.ietf.org/html/rfc6902
|
|
2147
|
-
*
|
|
2148
|
-
* @param resourceType The FHIR resource type.
|
|
2149
|
-
* @param id The resource ID.
|
|
2150
|
-
* @param operations The JSONPatch operations.
|
|
2151
|
-
* @returns The result of the patch operations.
|
|
2152
|
-
*/
|
|
2153
|
-
patchResource(resourceType, id, operations) {
|
|
2154
|
-
return this.patch(this.fhirUrl(resourceType, id), operations);
|
|
2155
|
-
}
|
|
2156
|
-
/**
|
|
2157
|
-
* Deletes a FHIR resource by resource type and ID.
|
|
2158
|
-
*
|
|
2159
|
-
* Example:
|
|
2160
|
-
*
|
|
2161
|
-
* ```typescript
|
|
2162
|
-
* await medplum.deleteResource('Patient', '123');
|
|
2163
|
-
* ```
|
|
2164
|
-
*
|
|
2165
|
-
* See the FHIR "delete" operation for full details: https://www.hl7.org/fhir/http.html#delete
|
|
2166
|
-
*
|
|
2167
|
-
* @param resourceType The FHIR resource type.
|
|
2168
|
-
* @param id The resource ID.
|
|
2169
|
-
* @returns The result of the delete operation.
|
|
2170
|
-
*/
|
|
2171
|
-
deleteResource(resourceType, id) {
|
|
2172
|
-
return this.delete(this.fhirUrl(resourceType, id));
|
|
2173
|
-
}
|
|
2174
|
-
graphql(query, options) {
|
|
2175
|
-
return this.post(this.fhirUrl('$graphql'), { query }, JSON_CONTENT_TYPE, options);
|
|
2176
|
-
}
|
|
2177
|
-
getActiveLogin() {
|
|
2178
|
-
return __classPrivateFieldGet(this, _MedplumClient_storage, "f").getObject('activeLogin');
|
|
2179
|
-
}
|
|
2180
|
-
setActiveLogin(login) {
|
|
2181
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
2182
|
-
__classPrivateFieldSet(this, _MedplumClient_accessToken, login.accessToken, "f");
|
|
2183
|
-
__classPrivateFieldSet(this, _MedplumClient_refreshToken, login.refreshToken, "f");
|
|
2184
|
-
__classPrivateFieldSet(this, _MedplumClient_profile, undefined, "f");
|
|
2185
|
-
__classPrivateFieldSet(this, _MedplumClient_config, undefined, "f");
|
|
2186
|
-
__classPrivateFieldGet(this, _MedplumClient_storage, "f").setObject('activeLogin', login);
|
|
2187
|
-
__classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_addLogin).call(this, login);
|
|
2188
|
-
__classPrivateFieldGet(this, _MedplumClient_requestCache, "f").clear();
|
|
2189
|
-
__classPrivateFieldSet(this, _MedplumClient_refreshPromise, undefined, "f");
|
|
2190
|
-
yield __classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_refreshProfile).call(this);
|
|
2191
|
-
});
|
|
2192
|
-
}
|
|
2193
|
-
setAccessToken(accessToken) {
|
|
2194
|
-
__classPrivateFieldSet(this, _MedplumClient_accessToken, accessToken, "f");
|
|
2195
|
-
__classPrivateFieldSet(this, _MedplumClient_refreshToken, undefined, "f");
|
|
2196
|
-
__classPrivateFieldSet(this, _MedplumClient_profile, undefined, "f");
|
|
2197
|
-
__classPrivateFieldSet(this, _MedplumClient_config, undefined, "f");
|
|
2198
|
-
}
|
|
2199
|
-
getLogins() {
|
|
2200
|
-
var _a;
|
|
2201
|
-
return (_a = __classPrivateFieldGet(this, _MedplumClient_storage, "f").getObject('logins')) !== null && _a !== void 0 ? _a : [];
|
|
2202
|
-
}
|
|
2203
|
-
isLoading() {
|
|
2204
|
-
return !!__classPrivateFieldGet(this, _MedplumClient_profilePromise, "f");
|
|
2205
|
-
}
|
|
2206
|
-
getProfile() {
|
|
2207
|
-
return __classPrivateFieldGet(this, _MedplumClient_profile, "f");
|
|
2208
|
-
}
|
|
2209
|
-
getProfileAsync() {
|
|
2210
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
2211
|
-
if (__classPrivateFieldGet(this, _MedplumClient_profilePromise, "f")) {
|
|
2212
|
-
yield __classPrivateFieldGet(this, _MedplumClient_profilePromise, "f");
|
|
2213
|
-
}
|
|
2214
|
-
return this.getProfile();
|
|
2215
|
-
});
|
|
2216
|
-
}
|
|
2217
|
-
getUserConfiguration() {
|
|
2218
|
-
return __classPrivateFieldGet(this, _MedplumClient_config, "f");
|
|
2219
|
-
}
|
|
2220
|
-
/**
|
|
2221
|
-
* Downloads the URL as a blob.
|
|
2222
|
-
* @param url The URL to request.
|
|
2223
|
-
* @returns Promise to the response body as a blob.
|
|
2224
|
-
*/
|
|
2225
|
-
download(url, options = {}) {
|
|
2226
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
2227
|
-
if (__classPrivateFieldGet(this, _MedplumClient_refreshPromise, "f")) {
|
|
2228
|
-
yield __classPrivateFieldGet(this, _MedplumClient_refreshPromise, "f");
|
|
2229
|
-
}
|
|
2230
|
-
__classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_addFetchOptionsDefaults).call(this, options);
|
|
2231
|
-
const response = yield __classPrivateFieldGet(this, _MedplumClient_fetch, "f").call(this, url, options);
|
|
2232
|
-
return response.blob();
|
|
2233
|
-
});
|
|
2234
|
-
}
|
|
2235
|
-
/**
|
|
2236
|
-
* Processes an OAuth authorization code.
|
|
2237
|
-
* See: https://openid.net/specs/openid-connect-core-1_0.html#TokenRequest
|
|
2238
|
-
* @param code The authorization code received by URL parameter.
|
|
2239
|
-
*/
|
|
2240
|
-
processCode(code) {
|
|
2241
|
-
const pkceState = __classPrivateFieldGet(this, _MedplumClient_storage, "f").getString('pkceState');
|
|
2242
|
-
if (!pkceState) {
|
|
2243
|
-
this.clear();
|
|
2244
|
-
return Promise.reject('Invalid PCKE state');
|
|
2245
|
-
}
|
|
2246
|
-
const codeVerifier = __classPrivateFieldGet(this, _MedplumClient_storage, "f").getString('codeVerifier');
|
|
2247
|
-
if (!codeVerifier) {
|
|
2248
|
-
this.clear();
|
|
2249
|
-
return Promise.reject('Invalid PCKE code verifier');
|
|
2250
|
-
}
|
|
2251
|
-
return __classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_fetchTokens).call(this, 'grant_type=authorization_code' +
|
|
2252
|
-
(__classPrivateFieldGet(this, _MedplumClient_clientId, "f") ? '&client_id=' + encodeURIComponent(__classPrivateFieldGet(this, _MedplumClient_clientId, "f")) : '') +
|
|
2253
|
-
'&code_verifier=' +
|
|
2254
|
-
encodeURIComponent(codeVerifier) +
|
|
2255
|
-
'&redirect_uri=' +
|
|
2256
|
-
encodeURIComponent(getBaseUrl()) +
|
|
2257
|
-
'&code=' +
|
|
2258
|
-
encodeURIComponent(code));
|
|
2259
|
-
}
|
|
2260
|
-
clientCredentials(clientId, clientSecret) {
|
|
2261
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
2262
|
-
return __classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_fetchTokens).call(this, 'grant_type=client_credentials' +
|
|
2263
|
-
'&client_id=' +
|
|
2264
|
-
encodeURIComponent(clientId) +
|
|
2265
|
-
'&client_secret=' +
|
|
2266
|
-
encodeURIComponent(clientSecret));
|
|
2267
|
-
});
|
|
2268
|
-
}
|
|
2269
|
-
}
|
|
2270
|
-
_MedplumClient_fetch = new WeakMap(), _MedplumClient_storage = new WeakMap(), _MedplumClient_schema = new WeakMap(), _MedplumClient_requestCache = new WeakMap(), _MedplumClient_baseUrl = new WeakMap(), _MedplumClient_clientId = new WeakMap(), _MedplumClient_authorizeUrl = new WeakMap(), _MedplumClient_tokenUrl = new WeakMap(), _MedplumClient_logoutUrl = new WeakMap(), _MedplumClient_onUnauthenticated = new WeakMap(), _MedplumClient_accessToken = new WeakMap(), _MedplumClient_refreshToken = new WeakMap(), _MedplumClient_refreshPromise = new WeakMap(), _MedplumClient_profilePromise = new WeakMap(), _MedplumClient_profile = new WeakMap(), _MedplumClient_config = new WeakMap(), _MedplumClient_instances = new WeakSet(), _MedplumClient_addLogin = function _MedplumClient_addLogin(newLogin) {
|
|
2271
|
-
const logins = this.getLogins().filter((login) => { var _a, _b; return ((_a = login.profile) === null || _a === void 0 ? void 0 : _a.reference) !== ((_b = newLogin.profile) === null || _b === void 0 ? void 0 : _b.reference); });
|
|
2272
|
-
logins.push(newLogin);
|
|
2273
|
-
__classPrivateFieldGet(this, _MedplumClient_storage, "f").setObject('logins', logins);
|
|
2274
|
-
}, _MedplumClient_refreshProfile = function _MedplumClient_refreshProfile() {
|
|
2275
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
2276
|
-
__classPrivateFieldSet(this, _MedplumClient_profilePromise, new Promise((resolve, reject) => {
|
|
2277
|
-
this.get('auth/me')
|
|
2278
|
-
.then((result) => {
|
|
2279
|
-
__classPrivateFieldSet(this, _MedplumClient_profilePromise, undefined, "f");
|
|
2280
|
-
__classPrivateFieldSet(this, _MedplumClient_profile, result.profile, "f");
|
|
2281
|
-
__classPrivateFieldSet(this, _MedplumClient_config, result.config, "f");
|
|
2282
|
-
this.dispatchEvent({ type: 'change' });
|
|
2283
|
-
resolve(__classPrivateFieldGet(this, _MedplumClient_profile, "f"));
|
|
2284
|
-
})
|
|
2285
|
-
.catch(reject);
|
|
2286
|
-
}), "f");
|
|
2287
|
-
return __classPrivateFieldGet(this, _MedplumClient_profilePromise, "f");
|
|
2288
|
-
});
|
|
2289
|
-
}, _MedplumClient_request = function _MedplumClient_request(method, url, options = {}) {
|
|
2290
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
2291
|
-
if (__classPrivateFieldGet(this, _MedplumClient_refreshPromise, "f")) {
|
|
2292
|
-
yield __classPrivateFieldGet(this, _MedplumClient_refreshPromise, "f");
|
|
2293
|
-
}
|
|
2294
|
-
if (!url.startsWith('http')) {
|
|
2295
|
-
url = __classPrivateFieldGet(this, _MedplumClient_baseUrl, "f") + url;
|
|
2296
|
-
}
|
|
2297
|
-
options.method = method;
|
|
2298
|
-
__classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_addFetchOptionsDefaults).call(this, options);
|
|
2299
|
-
const response = yield __classPrivateFieldGet(this, _MedplumClient_fetch, "f").call(this, url, options);
|
|
2300
|
-
if (response.status === 401) {
|
|
2301
|
-
// Refresh and try again
|
|
2302
|
-
return __classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_handleUnauthenticated).call(this, method, url, options);
|
|
2303
|
-
}
|
|
2304
|
-
if (response.status === 204 || response.status === 304) {
|
|
2305
|
-
// No content or change
|
|
2306
|
-
return undefined;
|
|
2307
|
-
}
|
|
2308
|
-
const obj = yield response.json();
|
|
2309
|
-
if ((obj === null || obj === void 0 ? void 0 : obj.resourceType) === 'OperationOutcome' && !isOk(obj)) {
|
|
2310
|
-
return Promise.reject(obj);
|
|
2311
|
-
}
|
|
2312
|
-
return obj;
|
|
2313
|
-
});
|
|
2314
|
-
}, _MedplumClient_addFetchOptionsDefaults = function _MedplumClient_addFetchOptionsDefaults(options) {
|
|
2315
|
-
if (!options.headers) {
|
|
2316
|
-
options.headers = {};
|
|
2317
|
-
}
|
|
2318
|
-
const headers = options.headers;
|
|
2319
|
-
if (!headers['Content-Type']) {
|
|
2320
|
-
headers['Content-Type'] = FHIR_CONTENT_TYPE;
|
|
2321
|
-
}
|
|
2322
|
-
if (__classPrivateFieldGet(this, _MedplumClient_accessToken, "f")) {
|
|
2323
|
-
headers['Authorization'] = 'Bearer ' + __classPrivateFieldGet(this, _MedplumClient_accessToken, "f");
|
|
2324
|
-
}
|
|
2325
|
-
if (!options.cache) {
|
|
2326
|
-
options.cache = 'no-cache';
|
|
2327
|
-
}
|
|
2328
|
-
if (!options.credentials) {
|
|
2329
|
-
options.credentials = 'include';
|
|
2330
|
-
}
|
|
2331
|
-
}, _MedplumClient_setRequestContentType = function _MedplumClient_setRequestContentType(options, contentType) {
|
|
2332
|
-
if (!options.headers) {
|
|
2333
|
-
options.headers = {};
|
|
2334
|
-
}
|
|
2335
|
-
const headers = options.headers;
|
|
2336
|
-
headers['Content-Type'] = contentType;
|
|
2337
|
-
}, _MedplumClient_setRequestBody = function _MedplumClient_setRequestBody(options, data) {
|
|
2338
|
-
if (typeof data === 'string' || (typeof File !== 'undefined' && data instanceof File)) {
|
|
2339
|
-
options.body = data;
|
|
2340
|
-
}
|
|
2341
|
-
else if (data) {
|
|
2342
|
-
options.body = stringify(data);
|
|
2343
|
-
}
|
|
2344
|
-
}, _MedplumClient_handleUnauthenticated = function _MedplumClient_handleUnauthenticated(method, url, options) {
|
|
2345
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
2346
|
-
return __classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_refresh).call(this)
|
|
2347
|
-
.then(() => __classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_request).call(this, method, url, options))
|
|
2348
|
-
.catch((error) => {
|
|
2349
|
-
this.clear();
|
|
2350
|
-
if (__classPrivateFieldGet(this, _MedplumClient_onUnauthenticated, "f")) {
|
|
2351
|
-
__classPrivateFieldGet(this, _MedplumClient_onUnauthenticated, "f").call(this);
|
|
2352
|
-
}
|
|
2353
|
-
return Promise.reject(error);
|
|
2354
|
-
});
|
|
2355
|
-
});
|
|
2356
|
-
}, _MedplumClient_startPkce = function _MedplumClient_startPkce() {
|
|
2357
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
2358
|
-
const pkceState = getRandomString();
|
|
2359
|
-
__classPrivateFieldGet(this, _MedplumClient_storage, "f").setString('pkceState', pkceState);
|
|
2360
|
-
const codeVerifier = getRandomString();
|
|
2361
|
-
__classPrivateFieldGet(this, _MedplumClient_storage, "f").setString('codeVerifier', codeVerifier);
|
|
2362
|
-
const arrayHash = yield encryptSHA256(codeVerifier);
|
|
2363
|
-
const codeChallenge = arrayBufferToBase64(arrayHash).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
|
|
2364
|
-
__classPrivateFieldGet(this, _MedplumClient_storage, "f").setString('codeChallenge', codeChallenge);
|
|
2365
|
-
});
|
|
2366
|
-
}, _MedplumClient_requestAuthorization = function _MedplumClient_requestAuthorization() {
|
|
2367
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
2368
|
-
if (!__classPrivateFieldGet(this, _MedplumClient_authorizeUrl, "f")) {
|
|
2369
|
-
return Promise.reject('Missing authorize URL');
|
|
2370
|
-
}
|
|
2371
|
-
__classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_startPkce).call(this);
|
|
2372
|
-
window.location.assign(__classPrivateFieldGet(this, _MedplumClient_authorizeUrl, "f") +
|
|
2373
|
-
'?response_type=code' +
|
|
2374
|
-
'&state=' +
|
|
2375
|
-
encodeURIComponent(__classPrivateFieldGet(this, _MedplumClient_storage, "f").getString('pkceState')) +
|
|
2376
|
-
'&client_id=' +
|
|
2377
|
-
encodeURIComponent(__classPrivateFieldGet(this, _MedplumClient_clientId, "f")) +
|
|
2378
|
-
'&redirect_uri=' +
|
|
2379
|
-
encodeURIComponent(getBaseUrl()) +
|
|
2380
|
-
'&scope=' +
|
|
2381
|
-
encodeURIComponent(DEFAULT_SCOPE) +
|
|
2382
|
-
'&code_challenge_method=S256' +
|
|
2383
|
-
'&code_challenge=' +
|
|
2384
|
-
encodeURIComponent(__classPrivateFieldGet(this, _MedplumClient_storage, "f").getString('codeChallenge')));
|
|
2385
|
-
});
|
|
2386
|
-
}, _MedplumClient_refresh = function _MedplumClient_refresh() {
|
|
2387
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
2388
|
-
if (__classPrivateFieldGet(this, _MedplumClient_refreshPromise, "f")) {
|
|
2389
|
-
return __classPrivateFieldGet(this, _MedplumClient_refreshPromise, "f");
|
|
2390
|
-
}
|
|
2391
|
-
if (!__classPrivateFieldGet(this, _MedplumClient_refreshToken, "f")) {
|
|
2392
|
-
this.clear();
|
|
2393
|
-
return Promise.reject('Invalid refresh token');
|
|
2394
|
-
}
|
|
2395
|
-
__classPrivateFieldSet(this, _MedplumClient_refreshPromise, __classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_fetchTokens).call(this, 'grant_type=refresh_token' +
|
|
2396
|
-
'&client_id=' +
|
|
2397
|
-
encodeURIComponent(__classPrivateFieldGet(this, _MedplumClient_clientId, "f")) +
|
|
2398
|
-
'&refresh_token=' +
|
|
2399
|
-
encodeURIComponent(__classPrivateFieldGet(this, _MedplumClient_refreshToken, "f"))), "f");
|
|
2400
|
-
yield __classPrivateFieldGet(this, _MedplumClient_refreshPromise, "f");
|
|
2401
|
-
});
|
|
2402
|
-
}, _MedplumClient_fetchTokens = function _MedplumClient_fetchTokens(formBody) {
|
|
2403
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
2404
|
-
if (!__classPrivateFieldGet(this, _MedplumClient_tokenUrl, "f")) {
|
|
2405
|
-
return Promise.reject('Missing token URL');
|
|
2406
|
-
}
|
|
2407
|
-
return __classPrivateFieldGet(this, _MedplumClient_fetch, "f").call(this, __classPrivateFieldGet(this, _MedplumClient_tokenUrl, "f"), {
|
|
2408
|
-
method: 'POST',
|
|
2409
|
-
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
2410
|
-
body: formBody,
|
|
2411
|
-
})
|
|
2412
|
-
.then((response) => {
|
|
2413
|
-
if (!response.ok) {
|
|
2414
|
-
return Promise.reject('Failed to fetch tokens');
|
|
2415
|
-
}
|
|
2416
|
-
return response.json();
|
|
2417
|
-
})
|
|
2418
|
-
.then((tokens) => __classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_verifyTokens).call(this, tokens))
|
|
2419
|
-
.then(() => this.getProfile());
|
|
2420
|
-
});
|
|
2421
|
-
}, _MedplumClient_verifyTokens = function _MedplumClient_verifyTokens(tokens) {
|
|
2422
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
2423
|
-
const token = tokens.access_token;
|
|
2424
|
-
// Verify token has not expired
|
|
2425
|
-
const tokenPayload = parseJWTPayload(token);
|
|
2426
|
-
if (Date.now() >= tokenPayload.exp * 1000) {
|
|
2427
|
-
this.clear();
|
|
2428
|
-
return Promise.reject('Token expired');
|
|
2429
|
-
}
|
|
2430
|
-
// Verify app_client_id
|
|
2431
|
-
if (__classPrivateFieldGet(this, _MedplumClient_clientId, "f") && tokenPayload.client_id !== __classPrivateFieldGet(this, _MedplumClient_clientId, "f")) {
|
|
2432
|
-
this.clear();
|
|
2433
|
-
return Promise.reject('Token was not issued for this audience');
|
|
2434
|
-
}
|
|
2435
|
-
yield this.setActiveLogin({
|
|
2436
|
-
accessToken: token,
|
|
2437
|
-
refreshToken: tokens.refresh_token,
|
|
2438
|
-
project: tokens.project,
|
|
2439
|
-
profile: tokens.profile,
|
|
2440
|
-
});
|
|
2441
|
-
});
|
|
2442
|
-
}, _MedplumClient_setupStorageListener = function _MedplumClient_setupStorageListener() {
|
|
2443
|
-
try {
|
|
2444
|
-
window.addEventListener('storage', (e) => {
|
|
2445
|
-
if (e.key === null || e.key === 'activeLogin') {
|
|
2446
|
-
// Storage events fire when different tabs make changes.
|
|
2447
|
-
// On storage clear (key === null) or activeLogin change (key === 'activeLogin')
|
|
2448
|
-
// Refresh the page to ensure the active login is up to date.
|
|
2449
|
-
window.location.reload();
|
|
2450
|
-
}
|
|
2451
|
-
});
|
|
2452
|
-
}
|
|
2453
|
-
catch (err) {
|
|
2454
|
-
// Silently ignore if this environment does not support storage events
|
|
2455
|
-
}
|
|
2456
|
-
};
|
|
2457
|
-
/**
|
|
2458
|
-
* Returns the base URL for the current page.
|
|
2459
|
-
*/
|
|
2460
|
-
function getBaseUrl() {
|
|
2461
|
-
return window.location.protocol + '//' + window.location.host + '/';
|
|
1912
|
+
}`.replace(/\s+/g, ' ');
|
|
1913
|
+
const response = (yield this.graphql(query));
|
|
1914
|
+
for (const structureDefinition of response.data.StructureDefinitionList) {
|
|
1915
|
+
indexStructureDefinition(__classPrivateFieldGet(this, _MedplumClient_schema, "f"), structureDefinition);
|
|
1916
|
+
}
|
|
1917
|
+
for (const searchParameter of response.data.SearchParameterList) {
|
|
1918
|
+
indexSearchParameter(__classPrivateFieldGet(this, _MedplumClient_schema, "f"), searchParameter);
|
|
1919
|
+
}
|
|
1920
|
+
return __classPrivateFieldGet(this, _MedplumClient_schema, "f");
|
|
1921
|
+
});
|
|
1922
|
+
}
|
|
1923
|
+
/**
|
|
1924
|
+
* Reads resource history by resource type and ID.
|
|
1925
|
+
*
|
|
1926
|
+
* The return value is a bundle of all versions of the resource.
|
|
1927
|
+
*
|
|
1928
|
+
* Example:
|
|
1929
|
+
*
|
|
1930
|
+
* ```typescript
|
|
1931
|
+
* const history = await medplum.readHistory('Patient', '123');
|
|
1932
|
+
* console.log(history);
|
|
1933
|
+
* ```
|
|
1934
|
+
*
|
|
1935
|
+
* See the FHIR "history" operation for full details: https://www.hl7.org/fhir/http.html#history
|
|
1936
|
+
*
|
|
1937
|
+
* @param resourceType The FHIR resource type.
|
|
1938
|
+
* @param id The resource ID.
|
|
1939
|
+
* @returns The resource if available; undefined otherwise.
|
|
1940
|
+
*/
|
|
1941
|
+
readHistory(resourceType, id) {
|
|
1942
|
+
return this.get(this.fhirUrl(resourceType, id, '_history'));
|
|
1943
|
+
}
|
|
1944
|
+
/**
|
|
1945
|
+
* Reads a specific version of a resource by resource type, ID, and version ID.
|
|
1946
|
+
*
|
|
1947
|
+
* Example:
|
|
1948
|
+
*
|
|
1949
|
+
* ```typescript
|
|
1950
|
+
* const version = await medplum.readVersion('Patient', '123', '456');
|
|
1951
|
+
* console.log(version);
|
|
1952
|
+
* ```
|
|
1953
|
+
*
|
|
1954
|
+
* See the FHIR "vread" operation for full details: https://www.hl7.org/fhir/http.html#vread
|
|
1955
|
+
*
|
|
1956
|
+
* @param resourceType The FHIR resource type.
|
|
1957
|
+
* @param id The resource ID.
|
|
1958
|
+
* @returns The resource if available; undefined otherwise.
|
|
1959
|
+
*/
|
|
1960
|
+
readVersion(resourceType, id, vid) {
|
|
1961
|
+
return this.get(this.fhirUrl(resourceType, id, '_history', vid));
|
|
1962
|
+
}
|
|
1963
|
+
readPatientEverything(id) {
|
|
1964
|
+
return this.get(this.fhirUrl('Patient', id, '$everything'));
|
|
1965
|
+
}
|
|
1966
|
+
/**
|
|
1967
|
+
* Creates a new FHIR resource.
|
|
1968
|
+
*
|
|
1969
|
+
* The return value is the newly created resource, including the ID and meta.
|
|
1970
|
+
*
|
|
1971
|
+
* Example:
|
|
1972
|
+
*
|
|
1973
|
+
* ```typescript
|
|
1974
|
+
* const result = await medplum.createResource({
|
|
1975
|
+
* resourceType: 'Patient',
|
|
1976
|
+
* name: [{
|
|
1977
|
+
* family: 'Smith',
|
|
1978
|
+
* given: ['John']
|
|
1979
|
+
* }]
|
|
1980
|
+
* });
|
|
1981
|
+
* console.log(result.id);
|
|
1982
|
+
* ```
|
|
1983
|
+
*
|
|
1984
|
+
* See the FHIR "create" operation for full details: https://www.hl7.org/fhir/http.html#create
|
|
1985
|
+
*
|
|
1986
|
+
* @param resource The FHIR resource to create.
|
|
1987
|
+
* @returns The result of the create operation.
|
|
1988
|
+
*/
|
|
1989
|
+
createResource(resource) {
|
|
1990
|
+
if (!resource.resourceType) {
|
|
1991
|
+
return Promise.reject('Missing resourceType');
|
|
1992
|
+
}
|
|
1993
|
+
return this.post(this.fhirUrl(resource.resourceType), resource);
|
|
1994
|
+
}
|
|
1995
|
+
/**
|
|
1996
|
+
* Conditionally create a new FHIR resource only if some equivalent resource does not already exist on the server.
|
|
1997
|
+
*
|
|
1998
|
+
* The return value is the existing resource or the newly created resource, including the ID and meta.
|
|
1999
|
+
*
|
|
2000
|
+
* Example:
|
|
2001
|
+
*
|
|
2002
|
+
* ```typescript
|
|
2003
|
+
* const result = await medplum.createResourceIfNoneExist(
|
|
2004
|
+
* 'Patient?identifier=123',
|
|
2005
|
+
* {
|
|
2006
|
+
* resourceType: 'Patient',
|
|
2007
|
+
* identifier: [{
|
|
2008
|
+
* system: 'http://example.com/mrn',
|
|
2009
|
+
* value: '123'
|
|
2010
|
+
* }]
|
|
2011
|
+
* name: [{
|
|
2012
|
+
* family: 'Smith',
|
|
2013
|
+
* given: ['John']
|
|
2014
|
+
* }]
|
|
2015
|
+
* });
|
|
2016
|
+
* console.log(result.id);
|
|
2017
|
+
* ```
|
|
2018
|
+
*
|
|
2019
|
+
* This method is syntactic sugar for:
|
|
2020
|
+
*
|
|
2021
|
+
* ```typescript
|
|
2022
|
+
* return searchOne(query) ?? createResource(resource);
|
|
2023
|
+
* ```
|
|
2024
|
+
*
|
|
2025
|
+
* The query parameter only contains the search parameters (what would be in the URL following the "?").
|
|
2026
|
+
*
|
|
2027
|
+
* See the FHIR "conditional create" operation for full details: https://www.hl7.org/fhir/http.html#ccreate
|
|
2028
|
+
*
|
|
2029
|
+
* @param resource The FHIR resource to create.
|
|
2030
|
+
* @param query The search query for an equivalent resource.
|
|
2031
|
+
* @returns The result of the create operation.
|
|
2032
|
+
*/
|
|
2033
|
+
createResourceIfNoneExist(resource, query) {
|
|
2034
|
+
var _a;
|
|
2035
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
2036
|
+
return (_a = (yield this.searchOne(`${resource.resourceType}?${query}`))) !== null && _a !== void 0 ? _a : this.createResource(resource);
|
|
2037
|
+
});
|
|
2038
|
+
}
|
|
2039
|
+
/**
|
|
2040
|
+
* Creates a FHIR `Binary` resource with the provided data content.
|
|
2041
|
+
*
|
|
2042
|
+
* The return value is the newly created resource, including the ID and meta.
|
|
2043
|
+
*
|
|
2044
|
+
* The `data` parameter can be a string or a `File` object.
|
|
2045
|
+
*
|
|
2046
|
+
* A `File` object often comes from a `<input type="file">` element.
|
|
2047
|
+
*
|
|
2048
|
+
* Example:
|
|
2049
|
+
*
|
|
2050
|
+
* ```typescript
|
|
2051
|
+
* const result = await medplum.createBinary(myFile, 'test.jpg', 'image/jpeg');
|
|
2052
|
+
* console.log(result.id);
|
|
2053
|
+
* ```
|
|
2054
|
+
*
|
|
2055
|
+
* See the FHIR "create" operation for full details: https://www.hl7.org/fhir/http.html#create
|
|
2056
|
+
*
|
|
2057
|
+
* @param data The binary data to upload.
|
|
2058
|
+
* @param filename Optional filename for the binary.
|
|
2059
|
+
* @param contentType Content type for the binary.
|
|
2060
|
+
* @returns The result of the create operation.
|
|
2061
|
+
*/
|
|
2062
|
+
createBinary(data, filename, contentType) {
|
|
2063
|
+
let url = this.fhirUrl('Binary');
|
|
2064
|
+
if (filename) {
|
|
2065
|
+
url += '?_filename=' + encodeURIComponent(filename);
|
|
2066
|
+
}
|
|
2067
|
+
return this.post(url, data, contentType);
|
|
2068
|
+
}
|
|
2069
|
+
/**
|
|
2070
|
+
* Creates a PDF as a FHIR `Binary` resource based on pdfmake document definition.
|
|
2071
|
+
*
|
|
2072
|
+
* The return value is the newly created resource, including the ID and meta.
|
|
2073
|
+
*
|
|
2074
|
+
* The `docDefinition` parameter is a pdfmake document definition.
|
|
2075
|
+
*
|
|
2076
|
+
* Example:
|
|
2077
|
+
*
|
|
2078
|
+
* ```typescript
|
|
2079
|
+
* const result = await medplum.createPdf({
|
|
2080
|
+
* content: ['Hello world']
|
|
2081
|
+
* });
|
|
2082
|
+
* console.log(result.id);
|
|
2083
|
+
* ```
|
|
2084
|
+
*
|
|
2085
|
+
* See the pdfmake document definition for full details: https://pdfmake.github.io/docs/0.1/document-definition-object/
|
|
2086
|
+
*
|
|
2087
|
+
* @param docDefinition The FHIR resource to create.
|
|
2088
|
+
* @returns The result of the create operation.
|
|
2089
|
+
*/
|
|
2090
|
+
createPdf(docDefinition, filename) {
|
|
2091
|
+
let url = this.fhirUrl('Binary') + '/$pdf';
|
|
2092
|
+
if (filename) {
|
|
2093
|
+
url += '?_filename=' + encodeURIComponent(filename);
|
|
2094
|
+
}
|
|
2095
|
+
return this.post(url, docDefinition, 'application/json');
|
|
2096
|
+
}
|
|
2097
|
+
/**
|
|
2098
|
+
* Updates a FHIR resource.
|
|
2099
|
+
*
|
|
2100
|
+
* The return value is the updated resource, including the ID and meta.
|
|
2101
|
+
*
|
|
2102
|
+
* Example:
|
|
2103
|
+
*
|
|
2104
|
+
* ```typescript
|
|
2105
|
+
* const result = await medplum.updateResource({
|
|
2106
|
+
* resourceType: 'Patient',
|
|
2107
|
+
* id: '123',
|
|
2108
|
+
* name: [{
|
|
2109
|
+
* family: 'Smith',
|
|
2110
|
+
* given: ['John']
|
|
2111
|
+
* }]
|
|
2112
|
+
* });
|
|
2113
|
+
* console.log(result.meta.versionId);
|
|
2114
|
+
* ```
|
|
2115
|
+
*
|
|
2116
|
+
* See the FHIR "update" operation for full details: https://www.hl7.org/fhir/http.html#update
|
|
2117
|
+
*
|
|
2118
|
+
* @param resource The FHIR resource to update.
|
|
2119
|
+
* @returns The result of the update operation.
|
|
2120
|
+
*/
|
|
2121
|
+
updateResource(resource) {
|
|
2122
|
+
if (!resource.resourceType) {
|
|
2123
|
+
return Promise.reject('Missing resourceType');
|
|
2124
|
+
}
|
|
2125
|
+
if (!resource.id) {
|
|
2126
|
+
return Promise.reject('Missing id');
|
|
2127
|
+
}
|
|
2128
|
+
return this.put(this.fhirUrl(resource.resourceType, resource.id), resource);
|
|
2129
|
+
}
|
|
2130
|
+
/**
|
|
2131
|
+
* Updates a FHIR resource using JSONPatch operations.
|
|
2132
|
+
*
|
|
2133
|
+
* The return value is the updated resource, including the ID and meta.
|
|
2134
|
+
*
|
|
2135
|
+
* Example:
|
|
2136
|
+
*
|
|
2137
|
+
* ```typescript
|
|
2138
|
+
* const result = await medplum.patchResource('Patient', '123', [
|
|
2139
|
+
* {op: 'replace', path: '/name/0/family', value: 'Smith'},
|
|
2140
|
+
* ]);
|
|
2141
|
+
* console.log(result.meta.versionId);
|
|
2142
|
+
* ```
|
|
2143
|
+
*
|
|
2144
|
+
* See the FHIR "update" operation for full details: https://www.hl7.org/fhir/http.html#patch
|
|
2145
|
+
*
|
|
2146
|
+
* See the JSONPatch specification for full details: https://tools.ietf.org/html/rfc6902
|
|
2147
|
+
*
|
|
2148
|
+
* @param resourceType The FHIR resource type.
|
|
2149
|
+
* @param id The resource ID.
|
|
2150
|
+
* @param operations The JSONPatch operations.
|
|
2151
|
+
* @returns The result of the patch operations.
|
|
2152
|
+
*/
|
|
2153
|
+
patchResource(resourceType, id, operations) {
|
|
2154
|
+
return this.patch(this.fhirUrl(resourceType, id), operations);
|
|
2155
|
+
}
|
|
2156
|
+
/**
|
|
2157
|
+
* Deletes a FHIR resource by resource type and ID.
|
|
2158
|
+
*
|
|
2159
|
+
* Example:
|
|
2160
|
+
*
|
|
2161
|
+
* ```typescript
|
|
2162
|
+
* await medplum.deleteResource('Patient', '123');
|
|
2163
|
+
* ```
|
|
2164
|
+
*
|
|
2165
|
+
* See the FHIR "delete" operation for full details: https://www.hl7.org/fhir/http.html#delete
|
|
2166
|
+
*
|
|
2167
|
+
* @param resourceType The FHIR resource type.
|
|
2168
|
+
* @param id The resource ID.
|
|
2169
|
+
* @returns The result of the delete operation.
|
|
2170
|
+
*/
|
|
2171
|
+
deleteResource(resourceType, id) {
|
|
2172
|
+
return this.delete(this.fhirUrl(resourceType, id));
|
|
2173
|
+
}
|
|
2174
|
+
graphql(query, options) {
|
|
2175
|
+
return this.post(this.fhirUrl('$graphql'), { query }, JSON_CONTENT_TYPE, options);
|
|
2176
|
+
}
|
|
2177
|
+
getActiveLogin() {
|
|
2178
|
+
return __classPrivateFieldGet(this, _MedplumClient_storage, "f").getObject('activeLogin');
|
|
2179
|
+
}
|
|
2180
|
+
setActiveLogin(login) {
|
|
2181
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
2182
|
+
__classPrivateFieldSet(this, _MedplumClient_accessToken, login.accessToken, "f");
|
|
2183
|
+
__classPrivateFieldSet(this, _MedplumClient_refreshToken, login.refreshToken, "f");
|
|
2184
|
+
__classPrivateFieldSet(this, _MedplumClient_profile, undefined, "f");
|
|
2185
|
+
__classPrivateFieldSet(this, _MedplumClient_config, undefined, "f");
|
|
2186
|
+
__classPrivateFieldGet(this, _MedplumClient_storage, "f").setObject('activeLogin', login);
|
|
2187
|
+
__classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_addLogin).call(this, login);
|
|
2188
|
+
__classPrivateFieldGet(this, _MedplumClient_requestCache, "f").clear();
|
|
2189
|
+
__classPrivateFieldSet(this, _MedplumClient_refreshPromise, undefined, "f");
|
|
2190
|
+
yield __classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_refreshProfile).call(this);
|
|
2191
|
+
});
|
|
2192
|
+
}
|
|
2193
|
+
setAccessToken(accessToken) {
|
|
2194
|
+
__classPrivateFieldSet(this, _MedplumClient_accessToken, accessToken, "f");
|
|
2195
|
+
__classPrivateFieldSet(this, _MedplumClient_refreshToken, undefined, "f");
|
|
2196
|
+
__classPrivateFieldSet(this, _MedplumClient_profile, undefined, "f");
|
|
2197
|
+
__classPrivateFieldSet(this, _MedplumClient_config, undefined, "f");
|
|
2198
|
+
}
|
|
2199
|
+
getLogins() {
|
|
2200
|
+
var _a;
|
|
2201
|
+
return (_a = __classPrivateFieldGet(this, _MedplumClient_storage, "f").getObject('logins')) !== null && _a !== void 0 ? _a : [];
|
|
2202
|
+
}
|
|
2203
|
+
isLoading() {
|
|
2204
|
+
return !!__classPrivateFieldGet(this, _MedplumClient_profilePromise, "f");
|
|
2205
|
+
}
|
|
2206
|
+
getProfile() {
|
|
2207
|
+
return __classPrivateFieldGet(this, _MedplumClient_profile, "f");
|
|
2208
|
+
}
|
|
2209
|
+
getProfileAsync() {
|
|
2210
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
2211
|
+
if (__classPrivateFieldGet(this, _MedplumClient_profilePromise, "f")) {
|
|
2212
|
+
yield __classPrivateFieldGet(this, _MedplumClient_profilePromise, "f");
|
|
2213
|
+
}
|
|
2214
|
+
return this.getProfile();
|
|
2215
|
+
});
|
|
2216
|
+
}
|
|
2217
|
+
getUserConfiguration() {
|
|
2218
|
+
return __classPrivateFieldGet(this, _MedplumClient_config, "f");
|
|
2219
|
+
}
|
|
2220
|
+
/**
|
|
2221
|
+
* Downloads the URL as a blob.
|
|
2222
|
+
* @param url The URL to request.
|
|
2223
|
+
* @returns Promise to the response body as a blob.
|
|
2224
|
+
*/
|
|
2225
|
+
download(url, options = {}) {
|
|
2226
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
2227
|
+
if (__classPrivateFieldGet(this, _MedplumClient_refreshPromise, "f")) {
|
|
2228
|
+
yield __classPrivateFieldGet(this, _MedplumClient_refreshPromise, "f");
|
|
2229
|
+
}
|
|
2230
|
+
__classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_addFetchOptionsDefaults).call(this, options);
|
|
2231
|
+
const response = yield __classPrivateFieldGet(this, _MedplumClient_fetch, "f").call(this, url, options);
|
|
2232
|
+
return response.blob();
|
|
2233
|
+
});
|
|
2234
|
+
}
|
|
2235
|
+
/**
|
|
2236
|
+
* Processes an OAuth authorization code.
|
|
2237
|
+
* See: https://openid.net/specs/openid-connect-core-1_0.html#TokenRequest
|
|
2238
|
+
* @param code The authorization code received by URL parameter.
|
|
2239
|
+
*/
|
|
2240
|
+
processCode(code) {
|
|
2241
|
+
const pkceState = __classPrivateFieldGet(this, _MedplumClient_storage, "f").getString('pkceState');
|
|
2242
|
+
if (!pkceState) {
|
|
2243
|
+
this.clear();
|
|
2244
|
+
return Promise.reject('Invalid PCKE state');
|
|
2245
|
+
}
|
|
2246
|
+
const codeVerifier = __classPrivateFieldGet(this, _MedplumClient_storage, "f").getString('codeVerifier');
|
|
2247
|
+
if (!codeVerifier) {
|
|
2248
|
+
this.clear();
|
|
2249
|
+
return Promise.reject('Invalid PCKE code verifier');
|
|
2250
|
+
}
|
|
2251
|
+
return __classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_fetchTokens).call(this, 'grant_type=authorization_code' +
|
|
2252
|
+
(__classPrivateFieldGet(this, _MedplumClient_clientId, "f") ? '&client_id=' + encodeURIComponent(__classPrivateFieldGet(this, _MedplumClient_clientId, "f")) : '') +
|
|
2253
|
+
'&code_verifier=' +
|
|
2254
|
+
encodeURIComponent(codeVerifier) +
|
|
2255
|
+
'&redirect_uri=' +
|
|
2256
|
+
encodeURIComponent(getBaseUrl()) +
|
|
2257
|
+
'&code=' +
|
|
2258
|
+
encodeURIComponent(code));
|
|
2259
|
+
}
|
|
2260
|
+
clientCredentials(clientId, clientSecret) {
|
|
2261
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
2262
|
+
return __classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_fetchTokens).call(this, 'grant_type=client_credentials' +
|
|
2263
|
+
'&client_id=' +
|
|
2264
|
+
encodeURIComponent(clientId) +
|
|
2265
|
+
'&client_secret=' +
|
|
2266
|
+
encodeURIComponent(clientSecret));
|
|
2267
|
+
});
|
|
2268
|
+
}
|
|
2269
|
+
}
|
|
2270
|
+
_MedplumClient_fetch = new WeakMap(), _MedplumClient_storage = new WeakMap(), _MedplumClient_schema = new WeakMap(), _MedplumClient_requestCache = new WeakMap(), _MedplumClient_baseUrl = new WeakMap(), _MedplumClient_clientId = new WeakMap(), _MedplumClient_authorizeUrl = new WeakMap(), _MedplumClient_tokenUrl = new WeakMap(), _MedplumClient_logoutUrl = new WeakMap(), _MedplumClient_onUnauthenticated = new WeakMap(), _MedplumClient_accessToken = new WeakMap(), _MedplumClient_refreshToken = new WeakMap(), _MedplumClient_refreshPromise = new WeakMap(), _MedplumClient_profilePromise = new WeakMap(), _MedplumClient_profile = new WeakMap(), _MedplumClient_config = new WeakMap(), _MedplumClient_instances = new WeakSet(), _MedplumClient_addLogin = function _MedplumClient_addLogin(newLogin) {
|
|
2271
|
+
const logins = this.getLogins().filter((login) => { var _a, _b; return ((_a = login.profile) === null || _a === void 0 ? void 0 : _a.reference) !== ((_b = newLogin.profile) === null || _b === void 0 ? void 0 : _b.reference); });
|
|
2272
|
+
logins.push(newLogin);
|
|
2273
|
+
__classPrivateFieldGet(this, _MedplumClient_storage, "f").setObject('logins', logins);
|
|
2274
|
+
}, _MedplumClient_refreshProfile = function _MedplumClient_refreshProfile() {
|
|
2275
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
2276
|
+
__classPrivateFieldSet(this, _MedplumClient_profilePromise, new Promise((resolve, reject) => {
|
|
2277
|
+
this.get('auth/me')
|
|
2278
|
+
.then((result) => {
|
|
2279
|
+
__classPrivateFieldSet(this, _MedplumClient_profilePromise, undefined, "f");
|
|
2280
|
+
__classPrivateFieldSet(this, _MedplumClient_profile, result.profile, "f");
|
|
2281
|
+
__classPrivateFieldSet(this, _MedplumClient_config, result.config, "f");
|
|
2282
|
+
this.dispatchEvent({ type: 'change' });
|
|
2283
|
+
resolve(__classPrivateFieldGet(this, _MedplumClient_profile, "f"));
|
|
2284
|
+
})
|
|
2285
|
+
.catch(reject);
|
|
2286
|
+
}), "f");
|
|
2287
|
+
return __classPrivateFieldGet(this, _MedplumClient_profilePromise, "f");
|
|
2288
|
+
});
|
|
2289
|
+
}, _MedplumClient_request = function _MedplumClient_request(method, url, options = {}) {
|
|
2290
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
2291
|
+
if (__classPrivateFieldGet(this, _MedplumClient_refreshPromise, "f")) {
|
|
2292
|
+
yield __classPrivateFieldGet(this, _MedplumClient_refreshPromise, "f");
|
|
2293
|
+
}
|
|
2294
|
+
if (!url.startsWith('http')) {
|
|
2295
|
+
url = __classPrivateFieldGet(this, _MedplumClient_baseUrl, "f") + url;
|
|
2296
|
+
}
|
|
2297
|
+
options.method = method;
|
|
2298
|
+
__classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_addFetchOptionsDefaults).call(this, options);
|
|
2299
|
+
const response = yield __classPrivateFieldGet(this, _MedplumClient_fetch, "f").call(this, url, options);
|
|
2300
|
+
if (response.status === 401) {
|
|
2301
|
+
// Refresh and try again
|
|
2302
|
+
return __classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_handleUnauthenticated).call(this, method, url, options);
|
|
2303
|
+
}
|
|
2304
|
+
if (response.status === 204 || response.status === 304) {
|
|
2305
|
+
// No content or change
|
|
2306
|
+
return undefined;
|
|
2307
|
+
}
|
|
2308
|
+
const obj = yield response.json();
|
|
2309
|
+
if ((obj === null || obj === void 0 ? void 0 : obj.resourceType) === 'OperationOutcome' && !isOk(obj)) {
|
|
2310
|
+
return Promise.reject(obj);
|
|
2311
|
+
}
|
|
2312
|
+
return obj;
|
|
2313
|
+
});
|
|
2314
|
+
}, _MedplumClient_addFetchOptionsDefaults = function _MedplumClient_addFetchOptionsDefaults(options) {
|
|
2315
|
+
if (!options.headers) {
|
|
2316
|
+
options.headers = {};
|
|
2317
|
+
}
|
|
2318
|
+
const headers = options.headers;
|
|
2319
|
+
if (!headers['Content-Type']) {
|
|
2320
|
+
headers['Content-Type'] = FHIR_CONTENT_TYPE;
|
|
2321
|
+
}
|
|
2322
|
+
if (__classPrivateFieldGet(this, _MedplumClient_accessToken, "f")) {
|
|
2323
|
+
headers['Authorization'] = 'Bearer ' + __classPrivateFieldGet(this, _MedplumClient_accessToken, "f");
|
|
2324
|
+
}
|
|
2325
|
+
if (!options.cache) {
|
|
2326
|
+
options.cache = 'no-cache';
|
|
2327
|
+
}
|
|
2328
|
+
if (!options.credentials) {
|
|
2329
|
+
options.credentials = 'include';
|
|
2330
|
+
}
|
|
2331
|
+
}, _MedplumClient_setRequestContentType = function _MedplumClient_setRequestContentType(options, contentType) {
|
|
2332
|
+
if (!options.headers) {
|
|
2333
|
+
options.headers = {};
|
|
2334
|
+
}
|
|
2335
|
+
const headers = options.headers;
|
|
2336
|
+
headers['Content-Type'] = contentType;
|
|
2337
|
+
}, _MedplumClient_setRequestBody = function _MedplumClient_setRequestBody(options, data) {
|
|
2338
|
+
if (typeof data === 'string' || (typeof File !== 'undefined' && data instanceof File)) {
|
|
2339
|
+
options.body = data;
|
|
2340
|
+
}
|
|
2341
|
+
else if (data) {
|
|
2342
|
+
options.body = stringify(data);
|
|
2343
|
+
}
|
|
2344
|
+
}, _MedplumClient_handleUnauthenticated = function _MedplumClient_handleUnauthenticated(method, url, options) {
|
|
2345
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
2346
|
+
return __classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_refresh).call(this)
|
|
2347
|
+
.then(() => __classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_request).call(this, method, url, options))
|
|
2348
|
+
.catch((error) => {
|
|
2349
|
+
this.clear();
|
|
2350
|
+
if (__classPrivateFieldGet(this, _MedplumClient_onUnauthenticated, "f")) {
|
|
2351
|
+
__classPrivateFieldGet(this, _MedplumClient_onUnauthenticated, "f").call(this);
|
|
2352
|
+
}
|
|
2353
|
+
return Promise.reject(error);
|
|
2354
|
+
});
|
|
2355
|
+
});
|
|
2356
|
+
}, _MedplumClient_startPkce = function _MedplumClient_startPkce() {
|
|
2357
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
2358
|
+
const pkceState = getRandomString();
|
|
2359
|
+
__classPrivateFieldGet(this, _MedplumClient_storage, "f").setString('pkceState', pkceState);
|
|
2360
|
+
const codeVerifier = getRandomString();
|
|
2361
|
+
__classPrivateFieldGet(this, _MedplumClient_storage, "f").setString('codeVerifier', codeVerifier);
|
|
2362
|
+
const arrayHash = yield encryptSHA256(codeVerifier);
|
|
2363
|
+
const codeChallenge = arrayBufferToBase64(arrayHash).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
|
|
2364
|
+
__classPrivateFieldGet(this, _MedplumClient_storage, "f").setString('codeChallenge', codeChallenge);
|
|
2365
|
+
});
|
|
2366
|
+
}, _MedplumClient_requestAuthorization = function _MedplumClient_requestAuthorization() {
|
|
2367
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
2368
|
+
if (!__classPrivateFieldGet(this, _MedplumClient_authorizeUrl, "f")) {
|
|
2369
|
+
return Promise.reject('Missing authorize URL');
|
|
2370
|
+
}
|
|
2371
|
+
__classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_startPkce).call(this);
|
|
2372
|
+
window.location.assign(__classPrivateFieldGet(this, _MedplumClient_authorizeUrl, "f") +
|
|
2373
|
+
'?response_type=code' +
|
|
2374
|
+
'&state=' +
|
|
2375
|
+
encodeURIComponent(__classPrivateFieldGet(this, _MedplumClient_storage, "f").getString('pkceState')) +
|
|
2376
|
+
'&client_id=' +
|
|
2377
|
+
encodeURIComponent(__classPrivateFieldGet(this, _MedplumClient_clientId, "f")) +
|
|
2378
|
+
'&redirect_uri=' +
|
|
2379
|
+
encodeURIComponent(getBaseUrl()) +
|
|
2380
|
+
'&scope=' +
|
|
2381
|
+
encodeURIComponent(DEFAULT_SCOPE) +
|
|
2382
|
+
'&code_challenge_method=S256' +
|
|
2383
|
+
'&code_challenge=' +
|
|
2384
|
+
encodeURIComponent(__classPrivateFieldGet(this, _MedplumClient_storage, "f").getString('codeChallenge')));
|
|
2385
|
+
});
|
|
2386
|
+
}, _MedplumClient_refresh = function _MedplumClient_refresh() {
|
|
2387
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
2388
|
+
if (__classPrivateFieldGet(this, _MedplumClient_refreshPromise, "f")) {
|
|
2389
|
+
return __classPrivateFieldGet(this, _MedplumClient_refreshPromise, "f");
|
|
2390
|
+
}
|
|
2391
|
+
if (!__classPrivateFieldGet(this, _MedplumClient_refreshToken, "f")) {
|
|
2392
|
+
this.clear();
|
|
2393
|
+
return Promise.reject('Invalid refresh token');
|
|
2394
|
+
}
|
|
2395
|
+
__classPrivateFieldSet(this, _MedplumClient_refreshPromise, __classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_fetchTokens).call(this, 'grant_type=refresh_token' +
|
|
2396
|
+
'&client_id=' +
|
|
2397
|
+
encodeURIComponent(__classPrivateFieldGet(this, _MedplumClient_clientId, "f")) +
|
|
2398
|
+
'&refresh_token=' +
|
|
2399
|
+
encodeURIComponent(__classPrivateFieldGet(this, _MedplumClient_refreshToken, "f"))), "f");
|
|
2400
|
+
yield __classPrivateFieldGet(this, _MedplumClient_refreshPromise, "f");
|
|
2401
|
+
});
|
|
2402
|
+
}, _MedplumClient_fetchTokens = function _MedplumClient_fetchTokens(formBody) {
|
|
2403
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
2404
|
+
if (!__classPrivateFieldGet(this, _MedplumClient_tokenUrl, "f")) {
|
|
2405
|
+
return Promise.reject('Missing token URL');
|
|
2406
|
+
}
|
|
2407
|
+
return __classPrivateFieldGet(this, _MedplumClient_fetch, "f").call(this, __classPrivateFieldGet(this, _MedplumClient_tokenUrl, "f"), {
|
|
2408
|
+
method: 'POST',
|
|
2409
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
2410
|
+
body: formBody,
|
|
2411
|
+
})
|
|
2412
|
+
.then((response) => {
|
|
2413
|
+
if (!response.ok) {
|
|
2414
|
+
return Promise.reject('Failed to fetch tokens');
|
|
2415
|
+
}
|
|
2416
|
+
return response.json();
|
|
2417
|
+
})
|
|
2418
|
+
.then((tokens) => __classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_verifyTokens).call(this, tokens))
|
|
2419
|
+
.then(() => this.getProfile());
|
|
2420
|
+
});
|
|
2421
|
+
}, _MedplumClient_verifyTokens = function _MedplumClient_verifyTokens(tokens) {
|
|
2422
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
2423
|
+
const token = tokens.access_token;
|
|
2424
|
+
// Verify token has not expired
|
|
2425
|
+
const tokenPayload = parseJWTPayload(token);
|
|
2426
|
+
if (Date.now() >= tokenPayload.exp * 1000) {
|
|
2427
|
+
this.clear();
|
|
2428
|
+
return Promise.reject('Token expired');
|
|
2429
|
+
}
|
|
2430
|
+
// Verify app_client_id
|
|
2431
|
+
if (__classPrivateFieldGet(this, _MedplumClient_clientId, "f") && tokenPayload.client_id !== __classPrivateFieldGet(this, _MedplumClient_clientId, "f")) {
|
|
2432
|
+
this.clear();
|
|
2433
|
+
return Promise.reject('Token was not issued for this audience');
|
|
2434
|
+
}
|
|
2435
|
+
yield this.setActiveLogin({
|
|
2436
|
+
accessToken: token,
|
|
2437
|
+
refreshToken: tokens.refresh_token,
|
|
2438
|
+
project: tokens.project,
|
|
2439
|
+
profile: tokens.profile,
|
|
2440
|
+
});
|
|
2441
|
+
});
|
|
2442
|
+
}, _MedplumClient_setupStorageListener = function _MedplumClient_setupStorageListener() {
|
|
2443
|
+
try {
|
|
2444
|
+
window.addEventListener('storage', (e) => {
|
|
2445
|
+
if (e.key === null || e.key === 'activeLogin') {
|
|
2446
|
+
// Storage events fire when different tabs make changes.
|
|
2447
|
+
// On storage clear (key === null) or activeLogin change (key === 'activeLogin')
|
|
2448
|
+
// Refresh the page to ensure the active login is up to date.
|
|
2449
|
+
window.location.reload();
|
|
2450
|
+
}
|
|
2451
|
+
});
|
|
2452
|
+
}
|
|
2453
|
+
catch (err) {
|
|
2454
|
+
// Silently ignore if this environment does not support storage events
|
|
2455
|
+
}
|
|
2456
|
+
};
|
|
2457
|
+
/**
|
|
2458
|
+
* Returns the base URL for the current page.
|
|
2459
|
+
*/
|
|
2460
|
+
function getBaseUrl() {
|
|
2461
|
+
return window.location.protocol + '//' + window.location.host + '/';
|
|
2462
2462
|
}
|
|
2463
2463
|
|
|
2464
|
-
const SEGMENT_SEPARATOR = '\r';
|
|
2465
|
-
const FIELD_SEPARATOR = '|';
|
|
2466
|
-
const COMPONENT_SEPARATOR = '^';
|
|
2467
|
-
/**
|
|
2468
|
-
* The Hl7Message class represents one HL7 message.
|
|
2469
|
-
* A message is a collection of segments.
|
|
2470
|
-
* Note that we do not strictly parse messages, and only use default delimeters.
|
|
2471
|
-
*/
|
|
2472
|
-
class Hl7Message {
|
|
2473
|
-
constructor(segments) {
|
|
2474
|
-
this.segments = segments;
|
|
2475
|
-
}
|
|
2476
|
-
get(index) {
|
|
2477
|
-
if (typeof index === 'number') {
|
|
2478
|
-
return this.segments[index];
|
|
2479
|
-
}
|
|
2480
|
-
return this.segments.find((s) => s.name === index);
|
|
2481
|
-
}
|
|
2482
|
-
getAll(name) {
|
|
2483
|
-
return this.segments.filter((s) => s.name === name);
|
|
2484
|
-
}
|
|
2485
|
-
toString() {
|
|
2486
|
-
return this.segments.map((s) => s.toString()).join(SEGMENT_SEPARATOR);
|
|
2487
|
-
}
|
|
2488
|
-
buildAck() {
|
|
2489
|
-
var _a, _b, _c, _d, _e, _f;
|
|
2490
|
-
const now = new Date();
|
|
2491
|
-
const msh = this.get('MSH');
|
|
2492
|
-
const sendingApp = ((_a = msh === null || msh === void 0 ? void 0 : msh.get(2)) === null || _a === void 0 ? void 0 : _a.toString()) || '';
|
|
2493
|
-
const sendingFacility = ((_b = msh === null || msh === void 0 ? void 0 : msh.get(3)) === null || _b === void 0 ? void 0 : _b.toString()) || '';
|
|
2494
|
-
const receivingApp = ((_c = msh === null || msh === void 0 ? void 0 : msh.get(4)) === null || _c === void 0 ? void 0 : _c.toString()) || '';
|
|
2495
|
-
const receivingFacility = ((_d = msh === null || msh === void 0 ? void 0 : msh.get(5)) === null || _d === void 0 ? void 0 : _d.toString()) || '';
|
|
2496
|
-
const controlId = ((_e = msh === null || msh === void 0 ? void 0 : msh.get(9)) === null || _e === void 0 ? void 0 : _e.toString()) || '';
|
|
2497
|
-
const versionId = ((_f = msh === null || msh === void 0 ? void 0 : msh.get(12)) === null || _f === void 0 ? void 0 : _f.toString()) || '2.5.1';
|
|
2498
|
-
return new Hl7Message([
|
|
2499
|
-
new Hl7Segment([
|
|
2500
|
-
'MSH',
|
|
2501
|
-
'^~\\&',
|
|
2502
|
-
receivingApp,
|
|
2503
|
-
receivingFacility,
|
|
2504
|
-
sendingApp,
|
|
2505
|
-
sendingFacility,
|
|
2506
|
-
now.toISOString(),
|
|
2507
|
-
'',
|
|
2508
|
-
'ACK',
|
|
2509
|
-
now.getTime().toString(),
|
|
2510
|
-
'P',
|
|
2511
|
-
versionId,
|
|
2512
|
-
]),
|
|
2513
|
-
new Hl7Segment(['MSA', 'AA', controlId, 'OK']),
|
|
2514
|
-
]);
|
|
2515
|
-
}
|
|
2516
|
-
static parse(text) {
|
|
2517
|
-
if (!text.startsWith('MSH|^~\\&')) {
|
|
2518
|
-
const err = new Error('Invalid HL7 message');
|
|
2519
|
-
err.type = 'entity.parse.failed';
|
|
2520
|
-
throw err;
|
|
2521
|
-
}
|
|
2522
|
-
return new Hl7Message(text.split(/[\r\n]+/).map((line) => Hl7Segment.parse(line)));
|
|
2523
|
-
}
|
|
2524
|
-
}
|
|
2525
|
-
/**
|
|
2526
|
-
* The Hl7Segment class represents one HL7 segment.
|
|
2527
|
-
* A segment is a collection of fields.
|
|
2528
|
-
* The name field is the first field.
|
|
2529
|
-
* Note that we do not strictly parse messages, and only use default delimeters.
|
|
2530
|
-
*/
|
|
2531
|
-
class Hl7Segment {
|
|
2532
|
-
constructor(fields) {
|
|
2533
|
-
if (isStringArray(fields)) {
|
|
2534
|
-
this.fields = fields.map((f) => Hl7Field.parse(f));
|
|
2535
|
-
}
|
|
2536
|
-
else {
|
|
2537
|
-
this.fields = fields;
|
|
2538
|
-
}
|
|
2539
|
-
this.name = this.fields[0].components[0];
|
|
2540
|
-
}
|
|
2541
|
-
get(index) {
|
|
2542
|
-
return this.fields[index];
|
|
2543
|
-
}
|
|
2544
|
-
toString() {
|
|
2545
|
-
return this.fields.map((f) => f.toString()).join(FIELD_SEPARATOR);
|
|
2546
|
-
}
|
|
2547
|
-
static parse(text) {
|
|
2548
|
-
return new Hl7Segment(text.split(FIELD_SEPARATOR).map((f) => Hl7Field.parse(f)));
|
|
2549
|
-
}
|
|
2550
|
-
}
|
|
2551
|
-
/**
|
|
2552
|
-
* The Hl7Field class represents one HL7 field.
|
|
2553
|
-
* A field is a collection of components.
|
|
2554
|
-
* Note that we do not strictly parse messages, and only use default delimeters.
|
|
2555
|
-
*/
|
|
2556
|
-
class Hl7Field {
|
|
2557
|
-
constructor(components) {
|
|
2558
|
-
this.components = components;
|
|
2559
|
-
}
|
|
2560
|
-
get(index) {
|
|
2561
|
-
return this.components[index];
|
|
2562
|
-
}
|
|
2563
|
-
toString() {
|
|
2564
|
-
return this.components.join(COMPONENT_SEPARATOR);
|
|
2565
|
-
}
|
|
2566
|
-
static parse(text) {
|
|
2567
|
-
return new Hl7Field(text.split(COMPONENT_SEPARATOR));
|
|
2568
|
-
}
|
|
2464
|
+
const SEGMENT_SEPARATOR = '\r';
|
|
2465
|
+
const FIELD_SEPARATOR = '|';
|
|
2466
|
+
const COMPONENT_SEPARATOR = '^';
|
|
2467
|
+
/**
|
|
2468
|
+
* The Hl7Message class represents one HL7 message.
|
|
2469
|
+
* A message is a collection of segments.
|
|
2470
|
+
* Note that we do not strictly parse messages, and only use default delimeters.
|
|
2471
|
+
*/
|
|
2472
|
+
class Hl7Message {
|
|
2473
|
+
constructor(segments) {
|
|
2474
|
+
this.segments = segments;
|
|
2475
|
+
}
|
|
2476
|
+
get(index) {
|
|
2477
|
+
if (typeof index === 'number') {
|
|
2478
|
+
return this.segments[index];
|
|
2479
|
+
}
|
|
2480
|
+
return this.segments.find((s) => s.name === index);
|
|
2481
|
+
}
|
|
2482
|
+
getAll(name) {
|
|
2483
|
+
return this.segments.filter((s) => s.name === name);
|
|
2484
|
+
}
|
|
2485
|
+
toString() {
|
|
2486
|
+
return this.segments.map((s) => s.toString()).join(SEGMENT_SEPARATOR);
|
|
2487
|
+
}
|
|
2488
|
+
buildAck() {
|
|
2489
|
+
var _a, _b, _c, _d, _e, _f;
|
|
2490
|
+
const now = new Date();
|
|
2491
|
+
const msh = this.get('MSH');
|
|
2492
|
+
const sendingApp = ((_a = msh === null || msh === void 0 ? void 0 : msh.get(2)) === null || _a === void 0 ? void 0 : _a.toString()) || '';
|
|
2493
|
+
const sendingFacility = ((_b = msh === null || msh === void 0 ? void 0 : msh.get(3)) === null || _b === void 0 ? void 0 : _b.toString()) || '';
|
|
2494
|
+
const receivingApp = ((_c = msh === null || msh === void 0 ? void 0 : msh.get(4)) === null || _c === void 0 ? void 0 : _c.toString()) || '';
|
|
2495
|
+
const receivingFacility = ((_d = msh === null || msh === void 0 ? void 0 : msh.get(5)) === null || _d === void 0 ? void 0 : _d.toString()) || '';
|
|
2496
|
+
const controlId = ((_e = msh === null || msh === void 0 ? void 0 : msh.get(9)) === null || _e === void 0 ? void 0 : _e.toString()) || '';
|
|
2497
|
+
const versionId = ((_f = msh === null || msh === void 0 ? void 0 : msh.get(12)) === null || _f === void 0 ? void 0 : _f.toString()) || '2.5.1';
|
|
2498
|
+
return new Hl7Message([
|
|
2499
|
+
new Hl7Segment([
|
|
2500
|
+
'MSH',
|
|
2501
|
+
'^~\\&',
|
|
2502
|
+
receivingApp,
|
|
2503
|
+
receivingFacility,
|
|
2504
|
+
sendingApp,
|
|
2505
|
+
sendingFacility,
|
|
2506
|
+
now.toISOString(),
|
|
2507
|
+
'',
|
|
2508
|
+
'ACK',
|
|
2509
|
+
now.getTime().toString(),
|
|
2510
|
+
'P',
|
|
2511
|
+
versionId,
|
|
2512
|
+
]),
|
|
2513
|
+
new Hl7Segment(['MSA', 'AA', controlId, 'OK']),
|
|
2514
|
+
]);
|
|
2515
|
+
}
|
|
2516
|
+
static parse(text) {
|
|
2517
|
+
if (!text.startsWith('MSH|^~\\&')) {
|
|
2518
|
+
const err = new Error('Invalid HL7 message');
|
|
2519
|
+
err.type = 'entity.parse.failed';
|
|
2520
|
+
throw err;
|
|
2521
|
+
}
|
|
2522
|
+
return new Hl7Message(text.split(/[\r\n]+/).map((line) => Hl7Segment.parse(line)));
|
|
2523
|
+
}
|
|
2524
|
+
}
|
|
2525
|
+
/**
|
|
2526
|
+
* The Hl7Segment class represents one HL7 segment.
|
|
2527
|
+
* A segment is a collection of fields.
|
|
2528
|
+
* The name field is the first field.
|
|
2529
|
+
* Note that we do not strictly parse messages, and only use default delimeters.
|
|
2530
|
+
*/
|
|
2531
|
+
class Hl7Segment {
|
|
2532
|
+
constructor(fields) {
|
|
2533
|
+
if (isStringArray(fields)) {
|
|
2534
|
+
this.fields = fields.map((f) => Hl7Field.parse(f));
|
|
2535
|
+
}
|
|
2536
|
+
else {
|
|
2537
|
+
this.fields = fields;
|
|
2538
|
+
}
|
|
2539
|
+
this.name = this.fields[0].components[0];
|
|
2540
|
+
}
|
|
2541
|
+
get(index) {
|
|
2542
|
+
return this.fields[index];
|
|
2543
|
+
}
|
|
2544
|
+
toString() {
|
|
2545
|
+
return this.fields.map((f) => f.toString()).join(FIELD_SEPARATOR);
|
|
2546
|
+
}
|
|
2547
|
+
static parse(text) {
|
|
2548
|
+
return new Hl7Segment(text.split(FIELD_SEPARATOR).map((f) => Hl7Field.parse(f)));
|
|
2549
|
+
}
|
|
2550
|
+
}
|
|
2551
|
+
/**
|
|
2552
|
+
* The Hl7Field class represents one HL7 field.
|
|
2553
|
+
* A field is a collection of components.
|
|
2554
|
+
* Note that we do not strictly parse messages, and only use default delimeters.
|
|
2555
|
+
*/
|
|
2556
|
+
class Hl7Field {
|
|
2557
|
+
constructor(components) {
|
|
2558
|
+
this.components = components;
|
|
2559
|
+
}
|
|
2560
|
+
get(index) {
|
|
2561
|
+
return this.components[index];
|
|
2562
|
+
}
|
|
2563
|
+
toString() {
|
|
2564
|
+
return this.components.join(COMPONENT_SEPARATOR);
|
|
2565
|
+
}
|
|
2566
|
+
static parse(text) {
|
|
2567
|
+
return new Hl7Field(text.split(COMPONENT_SEPARATOR));
|
|
2568
|
+
}
|
|
2569
2569
|
}
|
|
2570
2570
|
|
|
2571
|
-
var SearchParameterType;
|
|
2572
|
-
(function (SearchParameterType) {
|
|
2573
|
-
SearchParameterType["BOOLEAN"] = "BOOLEAN";
|
|
2574
|
-
SearchParameterType["NUMBER"] = "NUMBER";
|
|
2575
|
-
SearchParameterType["QUANTITY"] = "QUANTITY";
|
|
2576
|
-
SearchParameterType["TEXT"] = "TEXT";
|
|
2577
|
-
SearchParameterType["REFERENCE"] = "REFERENCE";
|
|
2578
|
-
SearchParameterType["DATE"] = "DATE";
|
|
2579
|
-
SearchParameterType["DATETIME"] = "DATETIME";
|
|
2580
|
-
SearchParameterType["PERIOD"] = "PERIOD";
|
|
2581
|
-
})(SearchParameterType || (SearchParameterType = {}));
|
|
2582
|
-
/**
|
|
2583
|
-
* Returns the type details of a SearchParameter.
|
|
2584
|
-
*
|
|
2585
|
-
* The SearchParameter resource has a "type" parameter, but that is missing some critical information.
|
|
2586
|
-
*
|
|
2587
|
-
* For example:
|
|
2588
|
-
* 1) The "date" type includes "date", "datetime", and "period".
|
|
2589
|
-
* 2) The "token" type includes enums and booleans.
|
|
2590
|
-
* 3) Arrays/multiple values are not reflected at all.
|
|
2591
|
-
*
|
|
2592
|
-
* @param structureDefinitions Collection of StructureDefinition resources indexed by name.
|
|
2593
|
-
* @param resourceType The root resource type.
|
|
2594
|
-
* @param searchParam The search parameter.
|
|
2595
|
-
* @returns The search parameter type details.
|
|
2596
|
-
*/
|
|
2597
|
-
function getSearchParameterDetails(structureDefinitions, resourceType, searchParam) {
|
|
2598
|
-
var _a, _b, _c, _d;
|
|
2599
|
-
if (searchParam.code === '_lastUpdated') {
|
|
2600
|
-
return { columnName: 'lastUpdated', type: SearchParameterType.DATETIME };
|
|
2601
|
-
}
|
|
2602
|
-
const columnName = convertCodeToColumnName(searchParam.code);
|
|
2603
|
-
const expression = (_a = getExpressionForResourceType(resourceType, searchParam.expression)) === null || _a === void 0 ? void 0 : _a.split('.');
|
|
2604
|
-
if (!expression) {
|
|
2605
|
-
// This happens on compound types
|
|
2606
|
-
// In the future, explore returning multiple column definitions
|
|
2607
|
-
return { columnName, type: SearchParameterType.TEXT };
|
|
2608
|
-
}
|
|
2609
|
-
let baseType = resourceType;
|
|
2610
|
-
let elementDefinition = undefined;
|
|
2611
|
-
let propertyType = undefined;
|
|
2612
|
-
let array = false;
|
|
2613
|
-
for (let i = 1; i < expression.length; i++) {
|
|
2614
|
-
const propertyName = expression[i];
|
|
2615
|
-
elementDefinition = (_c = (_b = structureDefinitions.types[baseType]) === null || _b === void 0 ? void 0 : _b.properties) === null || _c === void 0 ? void 0 : _c[propertyName];
|
|
2616
|
-
if (!elementDefinition) {
|
|
2617
|
-
// This happens on complex properties such as "collected[x]"/"collectedDateTime"/"collectedPeriod"
|
|
2618
|
-
// In the future, explore returning multiple column definitions
|
|
2619
|
-
return { columnName, type: SearchParameterType.TEXT, array };
|
|
2620
|
-
}
|
|
2621
|
-
if (elementDefinition.max === '*') {
|
|
2622
|
-
array = true;
|
|
2623
|
-
}
|
|
2624
|
-
propertyType = (_d = elementDefinition.type) === null || _d === void 0 ? void 0 : _d[0].code;
|
|
2625
|
-
if (!propertyType) {
|
|
2626
|
-
// This happens when one of parent properties uses contentReference
|
|
2627
|
-
// In the future, explore following the reference
|
|
2628
|
-
return { columnName, type: SearchParameterType.TEXT, array };
|
|
2629
|
-
}
|
|
2630
|
-
if (i < expression.length - 1) {
|
|
2631
|
-
if (propertyType === 'Element' || propertyType === 'BackboneElement') {
|
|
2632
|
-
baseType = baseType + capitalize(propertyName);
|
|
2633
|
-
}
|
|
2634
|
-
else {
|
|
2635
|
-
baseType = propertyType;
|
|
2636
|
-
}
|
|
2637
|
-
}
|
|
2638
|
-
}
|
|
2639
|
-
const type = getSearchParameterType(searchParam, propertyType);
|
|
2640
|
-
return { columnName, type, elementDefinition, array };
|
|
2641
|
-
}
|
|
2642
|
-
/**
|
|
2643
|
-
* Converts a hyphen-delimited code to camelCase string.
|
|
2644
|
-
* @param code The search parameter code.
|
|
2645
|
-
* @returns The SQL column name.
|
|
2646
|
-
*/
|
|
2647
|
-
function convertCodeToColumnName(code) {
|
|
2648
|
-
return code.split('-').reduce((result, word, index) => result + (index ? capitalize(word) : word), '');
|
|
2649
|
-
}
|
|
2650
|
-
function getSearchParameterType(searchParam, propertyType) {
|
|
2651
|
-
let type = SearchParameterType.TEXT;
|
|
2652
|
-
switch (searchParam.type) {
|
|
2653
|
-
case 'date':
|
|
2654
|
-
type = SearchParameterType.DATE;
|
|
2655
|
-
break;
|
|
2656
|
-
case 'number':
|
|
2657
|
-
type = SearchParameterType.NUMBER;
|
|
2658
|
-
break;
|
|
2659
|
-
case 'quantity':
|
|
2660
|
-
type = SearchParameterType.QUANTITY;
|
|
2661
|
-
break;
|
|
2662
|
-
case 'reference':
|
|
2663
|
-
type = SearchParameterType.REFERENCE;
|
|
2664
|
-
break;
|
|
2665
|
-
case 'token':
|
|
2666
|
-
if (propertyType === 'boolean') {
|
|
2667
|
-
type = SearchParameterType.BOOLEAN;
|
|
2668
|
-
}
|
|
2669
|
-
break;
|
|
2670
|
-
}
|
|
2671
|
-
return type;
|
|
2672
|
-
}
|
|
2673
|
-
function getExpressionForResourceType(resourceType, expression) {
|
|
2674
|
-
const expressions = expression.split(' | ');
|
|
2675
|
-
for (const e of expressions) {
|
|
2676
|
-
const simplified = simplifyExpression(e);
|
|
2677
|
-
if (simplified.startsWith(resourceType + '.')) {
|
|
2678
|
-
return simplified;
|
|
2679
|
-
}
|
|
2680
|
-
}
|
|
2681
|
-
return undefined;
|
|
2682
|
-
}
|
|
2683
|
-
function simplifyExpression(input) {
|
|
2684
|
-
let result = input.trim();
|
|
2685
|
-
if (result.startsWith('(') && result.endsWith(')')) {
|
|
2686
|
-
result = result.substring(1, result.length - 1);
|
|
2687
|
-
}
|
|
2688
|
-
if (result.includes(' as ')) {
|
|
2689
|
-
result = result.substring(0, result.indexOf(' as '));
|
|
2690
|
-
}
|
|
2691
|
-
if (result.includes('.where(')) {
|
|
2692
|
-
result = result.substring(0, result.indexOf('.where('));
|
|
2693
|
-
}
|
|
2694
|
-
return result;
|
|
2571
|
+
var SearchParameterType;
|
|
2572
|
+
(function (SearchParameterType) {
|
|
2573
|
+
SearchParameterType["BOOLEAN"] = "BOOLEAN";
|
|
2574
|
+
SearchParameterType["NUMBER"] = "NUMBER";
|
|
2575
|
+
SearchParameterType["QUANTITY"] = "QUANTITY";
|
|
2576
|
+
SearchParameterType["TEXT"] = "TEXT";
|
|
2577
|
+
SearchParameterType["REFERENCE"] = "REFERENCE";
|
|
2578
|
+
SearchParameterType["DATE"] = "DATE";
|
|
2579
|
+
SearchParameterType["DATETIME"] = "DATETIME";
|
|
2580
|
+
SearchParameterType["PERIOD"] = "PERIOD";
|
|
2581
|
+
})(SearchParameterType || (SearchParameterType = {}));
|
|
2582
|
+
/**
|
|
2583
|
+
* Returns the type details of a SearchParameter.
|
|
2584
|
+
*
|
|
2585
|
+
* The SearchParameter resource has a "type" parameter, but that is missing some critical information.
|
|
2586
|
+
*
|
|
2587
|
+
* For example:
|
|
2588
|
+
* 1) The "date" type includes "date", "datetime", and "period".
|
|
2589
|
+
* 2) The "token" type includes enums and booleans.
|
|
2590
|
+
* 3) Arrays/multiple values are not reflected at all.
|
|
2591
|
+
*
|
|
2592
|
+
* @param structureDefinitions Collection of StructureDefinition resources indexed by name.
|
|
2593
|
+
* @param resourceType The root resource type.
|
|
2594
|
+
* @param searchParam The search parameter.
|
|
2595
|
+
* @returns The search parameter type details.
|
|
2596
|
+
*/
|
|
2597
|
+
function getSearchParameterDetails(structureDefinitions, resourceType, searchParam) {
|
|
2598
|
+
var _a, _b, _c, _d;
|
|
2599
|
+
if (searchParam.code === '_lastUpdated') {
|
|
2600
|
+
return { columnName: 'lastUpdated', type: SearchParameterType.DATETIME };
|
|
2601
|
+
}
|
|
2602
|
+
const columnName = convertCodeToColumnName(searchParam.code);
|
|
2603
|
+
const expression = (_a = getExpressionForResourceType(resourceType, searchParam.expression)) === null || _a === void 0 ? void 0 : _a.split('.');
|
|
2604
|
+
if (!expression) {
|
|
2605
|
+
// This happens on compound types
|
|
2606
|
+
// In the future, explore returning multiple column definitions
|
|
2607
|
+
return { columnName, type: SearchParameterType.TEXT };
|
|
2608
|
+
}
|
|
2609
|
+
let baseType = resourceType;
|
|
2610
|
+
let elementDefinition = undefined;
|
|
2611
|
+
let propertyType = undefined;
|
|
2612
|
+
let array = false;
|
|
2613
|
+
for (let i = 1; i < expression.length; i++) {
|
|
2614
|
+
const propertyName = expression[i];
|
|
2615
|
+
elementDefinition = (_c = (_b = structureDefinitions.types[baseType]) === null || _b === void 0 ? void 0 : _b.properties) === null || _c === void 0 ? void 0 : _c[propertyName];
|
|
2616
|
+
if (!elementDefinition) {
|
|
2617
|
+
// This happens on complex properties such as "collected[x]"/"collectedDateTime"/"collectedPeriod"
|
|
2618
|
+
// In the future, explore returning multiple column definitions
|
|
2619
|
+
return { columnName, type: SearchParameterType.TEXT, array };
|
|
2620
|
+
}
|
|
2621
|
+
if (elementDefinition.max === '*') {
|
|
2622
|
+
array = true;
|
|
2623
|
+
}
|
|
2624
|
+
propertyType = (_d = elementDefinition.type) === null || _d === void 0 ? void 0 : _d[0].code;
|
|
2625
|
+
if (!propertyType) {
|
|
2626
|
+
// This happens when one of parent properties uses contentReference
|
|
2627
|
+
// In the future, explore following the reference
|
|
2628
|
+
return { columnName, type: SearchParameterType.TEXT, array };
|
|
2629
|
+
}
|
|
2630
|
+
if (i < expression.length - 1) {
|
|
2631
|
+
if (propertyType === 'Element' || propertyType === 'BackboneElement') {
|
|
2632
|
+
baseType = baseType + capitalize(propertyName);
|
|
2633
|
+
}
|
|
2634
|
+
else {
|
|
2635
|
+
baseType = propertyType;
|
|
2636
|
+
}
|
|
2637
|
+
}
|
|
2638
|
+
}
|
|
2639
|
+
const type = getSearchParameterType(searchParam, propertyType);
|
|
2640
|
+
return { columnName, type, elementDefinition, array };
|
|
2641
|
+
}
|
|
2642
|
+
/**
|
|
2643
|
+
* Converts a hyphen-delimited code to camelCase string.
|
|
2644
|
+
* @param code The search parameter code.
|
|
2645
|
+
* @returns The SQL column name.
|
|
2646
|
+
*/
|
|
2647
|
+
function convertCodeToColumnName(code) {
|
|
2648
|
+
return code.split('-').reduce((result, word, index) => result + (index ? capitalize(word) : word), '');
|
|
2649
|
+
}
|
|
2650
|
+
function getSearchParameterType(searchParam, propertyType) {
|
|
2651
|
+
let type = SearchParameterType.TEXT;
|
|
2652
|
+
switch (searchParam.type) {
|
|
2653
|
+
case 'date':
|
|
2654
|
+
type = SearchParameterType.DATE;
|
|
2655
|
+
break;
|
|
2656
|
+
case 'number':
|
|
2657
|
+
type = SearchParameterType.NUMBER;
|
|
2658
|
+
break;
|
|
2659
|
+
case 'quantity':
|
|
2660
|
+
type = SearchParameterType.QUANTITY;
|
|
2661
|
+
break;
|
|
2662
|
+
case 'reference':
|
|
2663
|
+
type = SearchParameterType.REFERENCE;
|
|
2664
|
+
break;
|
|
2665
|
+
case 'token':
|
|
2666
|
+
if (propertyType === 'boolean') {
|
|
2667
|
+
type = SearchParameterType.BOOLEAN;
|
|
2668
|
+
}
|
|
2669
|
+
break;
|
|
2670
|
+
}
|
|
2671
|
+
return type;
|
|
2672
|
+
}
|
|
2673
|
+
function getExpressionForResourceType(resourceType, expression) {
|
|
2674
|
+
const expressions = expression.split(' | ');
|
|
2675
|
+
for (const e of expressions) {
|
|
2676
|
+
const simplified = simplifyExpression(e);
|
|
2677
|
+
if (simplified.startsWith(resourceType + '.')) {
|
|
2678
|
+
return simplified;
|
|
2679
|
+
}
|
|
2680
|
+
}
|
|
2681
|
+
return undefined;
|
|
2682
|
+
}
|
|
2683
|
+
function simplifyExpression(input) {
|
|
2684
|
+
let result = input.trim();
|
|
2685
|
+
if (result.startsWith('(') && result.endsWith(')')) {
|
|
2686
|
+
result = result.substring(1, result.length - 1);
|
|
2687
|
+
}
|
|
2688
|
+
if (result.includes(' as ')) {
|
|
2689
|
+
result = result.substring(0, result.indexOf(' as '));
|
|
2690
|
+
}
|
|
2691
|
+
if (result.includes('.where(')) {
|
|
2692
|
+
result = result.substring(0, result.indexOf('.where('));
|
|
2693
|
+
}
|
|
2694
|
+
return result;
|
|
2695
2695
|
}
|
|
2696
2696
|
|
|
2697
2697
|
export { COMPONENT_SEPARATOR, DEFAULT_SEARCH_COUNT, FIELD_SEPARATOR, Hl7Field, Hl7Message, Hl7Segment, LRUCache, MedplumClient, OperationOutcomeError, Operator, PropertyType, ReadablePromise, SEGMENT_SEPARATOR, SearchParameterType, accessDenied, allOk, arrayBufferToBase64, arrayBufferToHex, assertOk, badRequest, buildTypeName, calculateAge, calculateAgeString, capitalize, createReference, createSchema, createTypeSchema, created, deepEquals, formatAddress, formatFamilyName, formatGivenName, formatHumanName, formatSearchQuery, getDateProperty, getDisplayString, getExpressionForResourceType, getExtensionValue, getImageSrc, getPropertyDisplayName, getQuestionnaireAnswers, getReferenceString, getSearchParameterDetails, getStatus, gone, indexSearchParameter, indexStructureDefinition, isGone, isLowerCase, isNotFound, isObject, isOk, isProfileResource, isStringArray, isUUID, notFound, notModified, parseSearchDefinition, resolveId, stringify };
|