@medplum/fhir-router 2.0.14 → 2.0.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,36 +1,7 @@
1
- import { OperationOutcomeError, badRequest, normalizeOperationOutcome, parseSearchUrl, getStatus, isOk, getReferenceString, allOk, LRUCache, forbidden, getResourceTypes, getResourceTypeSchema, isResourceTypeSchema, getElementDefinition, buildTypeName, capitalize, getSearchParameters, Operator, parseSearchRequest, notFound, created, deepClone, matchesSearchRequest, globalSchema, evalFhirPath } from '@medplum/core';
1
+ import { OperationOutcomeError, badRequest, normalizeOperationOutcome, parseSearchUrl, getReferenceString, getStatus, isOk, allOk, LRUCache, forbidden, getResourceTypes, getResourceTypeSchema, isResourceTypeSchema, getElementDefinition, buildTypeName, capitalize, getSearchParameters, Operator, parseSearchRequest, notFound, created, deepClone, matchesSearchRequest, globalSchema, evalFhirPath } from '@medplum/core';
2
2
  import DataLoader from 'dataloader';
3
3
  import { applyPatch } from 'rfc6902';
4
4
 
5
- /******************************************************************************
6
- Copyright (c) Microsoft Corporation.
7
-
8
- Permission to use, copy, modify, and/or distribute this software for any
9
- purpose with or without fee is hereby granted.
10
-
11
- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
12
- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
13
- AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
14
- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
15
- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
16
- OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
17
- PERFORMANCE OF THIS SOFTWARE.
18
- ***************************************************************************** */
19
-
20
- function __classPrivateFieldGet(receiver, state, kind, f) {
21
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
22
- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
23
- return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
24
- }
25
-
26
- function __classPrivateFieldSet(receiver, state, value, kind, f) {
27
- if (kind === "m") throw new TypeError("Private method is not writable");
28
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
29
- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
30
- return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
31
- }
32
-
33
- var _BatchProcessor_instances, _BatchProcessor_ids, _BatchProcessor_processBatchEntry, _BatchProcessor_validateEntry, _BatchProcessor_parsePatchBody, _BatchProcessor_addReplacementId, _BatchProcessor_rewriteIds, _BatchProcessor_rewriteIdsInArray, _BatchProcessor_rewriteIdsInObject, _BatchProcessor_rewriteIdsInString;
34
5
  /**
35
6
  * Processes a FHIR batch request.
36
7
  *
@@ -56,12 +27,10 @@ class BatchProcessor {
56
27
  * @param bundle The input bundle.
57
28
  */
58
29
  constructor(router, repo, bundle) {
59
- _BatchProcessor_instances.add(this);
60
30
  this.router = router;
61
31
  this.repo = repo;
62
32
  this.bundle = bundle;
63
- _BatchProcessor_ids.set(this, void 0);
64
- __classPrivateFieldSet(this, _BatchProcessor_ids, {}, "f");
33
+ this.ids = {};
65
34
  }
66
35
  /**
67
36
  * Processes a FHIR batch request.
@@ -83,9 +52,9 @@ class BatchProcessor {
83
52
  }
84
53
  const resultEntries = [];
85
54
  for (const entry of entries) {
86
- const rewritten = __classPrivateFieldGet(this, _BatchProcessor_instances, "m", _BatchProcessor_rewriteIdsInObject).call(this, entry);
55
+ const rewritten = this.rewriteIdsInObject(entry);
87
56
  try {
88
- resultEntries.push(await __classPrivateFieldGet(this, _BatchProcessor_instances, "m", _BatchProcessor_processBatchEntry).call(this, rewritten));
57
+ resultEntries.push(await this.processBatchEntry(rewritten));
89
58
  }
90
59
  catch (err) {
91
60
  resultEntries.push(buildBundleResponse(normalizeOperationOutcome(err)));
@@ -97,102 +66,108 @@ class BatchProcessor {
97
66
  entry: resultEntries,
98
67
  };
99
68
  }
100
- }
101
- _BatchProcessor_ids = new WeakMap(), _BatchProcessor_instances = new WeakSet(), _BatchProcessor_processBatchEntry =
102
- /**
103
- * Processes a single entry from a FHIR batch request.
104
- * @param entry The bundle entry.
105
- * @returns The bundle entry response.
106
- */
107
- async function _BatchProcessor_processBatchEntry(entry) {
108
- __classPrivateFieldGet(this, _BatchProcessor_instances, "m", _BatchProcessor_validateEntry).call(this, entry);
109
- const request = entry.request;
110
- if (entry.resource?.resourceType && request.ifNoneExist) {
111
- const baseUrl = `https://example.com/${entry.resource.resourceType}`;
112
- const searchUrl = new URL('?' + request.ifNoneExist, baseUrl);
113
- const searchBundle = await this.repo.search(parseSearchUrl(searchUrl));
114
- const entries = searchBundle?.entry;
115
- if (entries.length > 1) {
116
- return buildBundleResponse(badRequest('Multiple matches'));
69
+ /**
70
+ * Processes a single entry from a FHIR batch request.
71
+ * @param entry The bundle entry.
72
+ * @returns The bundle entry response.
73
+ */
74
+ async processBatchEntry(entry) {
75
+ this.validateEntry(entry);
76
+ const request = entry.request;
77
+ if (entry.resource?.resourceType && request.ifNoneExist) {
78
+ const baseUrl = `https://example.com/${entry.resource.resourceType}`;
79
+ const searchUrl = new URL('?' + request.ifNoneExist, baseUrl);
80
+ const searchBundle = await this.repo.search(parseSearchUrl(searchUrl));
81
+ const entries = searchBundle?.entry;
82
+ if (entries.length > 1) {
83
+ return buildBundleResponse(badRequest('Multiple matches'));
84
+ }
85
+ if (entries.length === 1) {
86
+ const matchingResource = entries[0].resource;
87
+ if (entry.fullUrl) {
88
+ this.addReplacementId(entry.fullUrl, matchingResource);
89
+ }
90
+ return buildBundleResponse(allOk, matchingResource);
91
+ }
92
+ }
93
+ let body = entry.resource;
94
+ if (request.method === 'PATCH') {
95
+ body = this.parsePatchBody(entry);
96
+ }
97
+ // Pass in dummy host for parsing purposes.
98
+ // The host is ignored.
99
+ const url = new URL(request.url, 'https://example.com/');
100
+ const result = await this.router.handleRequest({
101
+ method: request.method,
102
+ pathname: url.pathname,
103
+ params: Object.create(null),
104
+ query: Object.fromEntries(url.searchParams),
105
+ body,
106
+ }, this.repo);
107
+ if (entry.fullUrl && result.length === 2) {
108
+ this.addReplacementId(entry.fullUrl, result[1]);
109
+ }
110
+ return buildBundleResponse(result[0], result[1]);
111
+ }
112
+ validateEntry(entry) {
113
+ if (!entry.request) {
114
+ throw new OperationOutcomeError(badRequest('Missing entry.request'));
115
+ }
116
+ if (!entry.request.method) {
117
+ throw new OperationOutcomeError(badRequest('Missing entry.request.method'));
118
+ }
119
+ if (!entry.request.url) {
120
+ throw new OperationOutcomeError(badRequest('Missing entry.request.url'));
121
+ }
122
+ }
123
+ parsePatchBody(entry) {
124
+ const patchResource = entry.resource;
125
+ if (!patchResource) {
126
+ throw new OperationOutcomeError(badRequest('Missing entry.resource'));
127
+ }
128
+ if (patchResource.resourceType !== 'Binary') {
129
+ throw new OperationOutcomeError(badRequest('Patch resource must be a Binary'));
117
130
  }
118
- if (entries.length === 1) {
119
- const matchingResource = entries[0].resource;
120
- if (entry.fullUrl) {
121
- __classPrivateFieldGet(this, _BatchProcessor_instances, "m", _BatchProcessor_addReplacementId).call(this, entry.fullUrl, matchingResource);
131
+ if (!patchResource.data) {
132
+ throw new OperationOutcomeError(badRequest('Missing entry.resource.data'));
133
+ }
134
+ return JSON.parse(Buffer.from(patchResource.data, 'base64').toString('utf8'));
135
+ }
136
+ addReplacementId(fullUrl, resource) {
137
+ if (fullUrl?.startsWith('urn:uuid:')) {
138
+ this.ids[fullUrl] = resource;
139
+ }
140
+ }
141
+ rewriteIds(input) {
142
+ if (Array.isArray(input)) {
143
+ return this.rewriteIdsInArray(input);
144
+ }
145
+ if (typeof input === 'string') {
146
+ return this.rewriteIdsInString(input);
147
+ }
148
+ if (typeof input === 'object') {
149
+ return this.rewriteIdsInObject(input);
150
+ }
151
+ return input;
152
+ }
153
+ rewriteIdsInArray(input) {
154
+ return input.map((item) => this.rewriteIds(item));
155
+ }
156
+ rewriteIdsInObject(input) {
157
+ return Object.fromEntries(Object.entries(input).map(([k, v]) => [k, this.rewriteIds(v)]));
158
+ }
159
+ rewriteIdsInString(input) {
160
+ const matches = input.match(/urn:uuid:\w{8}-\w{4}-\w{4}-\w{4}-\w{12}/);
161
+ if (matches) {
162
+ const fullUrl = matches[0];
163
+ const resource = this.ids[fullUrl];
164
+ if (resource) {
165
+ return input.replaceAll(fullUrl, getReferenceString(resource));
122
166
  }
123
- return buildBundleResponse(allOk, matchingResource);
124
- }
125
- }
126
- let body = entry.resource;
127
- if (request.method === 'PATCH') {
128
- body = __classPrivateFieldGet(this, _BatchProcessor_instances, "m", _BatchProcessor_parsePatchBody).call(this, entry);
129
- }
130
- // Pass in dummy host for parsing purposes.
131
- // The host is ignored.
132
- const url = new URL(request.url, 'https://example.com/');
133
- const result = await this.router.handleRequest({
134
- method: request.method,
135
- pathname: url.pathname,
136
- params: Object.create(null),
137
- query: Object.fromEntries(url.searchParams),
138
- body,
139
- }, this.repo);
140
- if (entry.fullUrl && result.length === 2) {
141
- __classPrivateFieldGet(this, _BatchProcessor_instances, "m", _BatchProcessor_addReplacementId).call(this, entry.fullUrl, result[1]);
142
- }
143
- return buildBundleResponse(result[0], result[1]);
144
- }, _BatchProcessor_validateEntry = function _BatchProcessor_validateEntry(entry) {
145
- if (!entry.request) {
146
- throw new OperationOutcomeError(badRequest('Missing entry.request'));
147
- }
148
- if (!entry.request.method) {
149
- throw new OperationOutcomeError(badRequest('Missing entry.request.method'));
150
- }
151
- if (!entry.request.url) {
152
- throw new OperationOutcomeError(badRequest('Missing entry.request.url'));
153
- }
154
- }, _BatchProcessor_parsePatchBody = function _BatchProcessor_parsePatchBody(entry) {
155
- const patchResource = entry.resource;
156
- if (!patchResource) {
157
- throw new OperationOutcomeError(badRequest('Missing entry.resource'));
158
- }
159
- if (patchResource.resourceType !== 'Binary') {
160
- throw new OperationOutcomeError(badRequest('Patch resource must be a Binary'));
161
- }
162
- if (!patchResource.data) {
163
- throw new OperationOutcomeError(badRequest('Missing entry.resource.data'));
164
- }
165
- return JSON.parse(Buffer.from(patchResource.data, 'base64').toString('utf8'));
166
- }, _BatchProcessor_addReplacementId = function _BatchProcessor_addReplacementId(fullUrl, resource) {
167
- if (fullUrl?.startsWith('urn:uuid:')) {
168
- __classPrivateFieldGet(this, _BatchProcessor_ids, "f")[fullUrl] = resource;
169
- }
170
- }, _BatchProcessor_rewriteIds = function _BatchProcessor_rewriteIds(input) {
171
- if (Array.isArray(input)) {
172
- return __classPrivateFieldGet(this, _BatchProcessor_instances, "m", _BatchProcessor_rewriteIdsInArray).call(this, input);
173
- }
174
- if (typeof input === 'string') {
175
- return __classPrivateFieldGet(this, _BatchProcessor_instances, "m", _BatchProcessor_rewriteIdsInString).call(this, input);
176
- }
177
- if (typeof input === 'object') {
178
- return __classPrivateFieldGet(this, _BatchProcessor_instances, "m", _BatchProcessor_rewriteIdsInObject).call(this, input);
179
- }
180
- return input;
181
- }, _BatchProcessor_rewriteIdsInArray = function _BatchProcessor_rewriteIdsInArray(input) {
182
- return input.map((item) => __classPrivateFieldGet(this, _BatchProcessor_instances, "m", _BatchProcessor_rewriteIds).call(this, item));
183
- }, _BatchProcessor_rewriteIdsInObject = function _BatchProcessor_rewriteIdsInObject(input) {
184
- return Object.fromEntries(Object.entries(input).map(([k, v]) => [k, __classPrivateFieldGet(this, _BatchProcessor_instances, "m", _BatchProcessor_rewriteIds).call(this, v)]));
185
- }, _BatchProcessor_rewriteIdsInString = function _BatchProcessor_rewriteIdsInString(input) {
186
- const matches = input.match(/urn:uuid:\w{8}-\w{4}-\w{4}-\w{4}-\w{12}/);
187
- if (matches) {
188
- const fullUrl = matches[0];
189
- const resource = __classPrivateFieldGet(this, _BatchProcessor_ids, "f")[fullUrl];
190
- if (resource) {
191
- return input.replaceAll(fullUrl, getReferenceString(resource));
192
- }
193
- }
194
- return input;
195
- };
167
+ }
168
+ return input;
169
+ }
170
+ }
196
171
  function buildBundleResponse(outcome, resource) {
197
172
  return {
198
173
  response: {
@@ -13969,7 +13944,6 @@ class FhirRouter {
13969
13944
  }
13970
13945
  }
13971
13946
 
13972
- var _MemoryRepository_resources, _MemoryRepository_history;
13973
13947
  class BaseRepository {
13974
13948
  /**
13975
13949
  * Searches for a single FHIR resource.
@@ -14007,10 +13981,8 @@ class BaseRepository {
14007
13981
  class MemoryRepository extends BaseRepository {
14008
13982
  constructor() {
14009
13983
  super();
14010
- _MemoryRepository_resources.set(this, void 0);
14011
- _MemoryRepository_history.set(this, void 0);
14012
- __classPrivateFieldSet(this, _MemoryRepository_resources, {}, "f");
14013
- __classPrivateFieldSet(this, _MemoryRepository_history, {}, "f");
13984
+ this.resources = new Map();
13985
+ this.history = new Map();
14014
13986
  }
14015
13987
  async createResource(resource) {
14016
13988
  const result = deepClone(resource);
@@ -14027,17 +13999,23 @@ class MemoryRepository extends BaseRepository {
14027
13999
  result.meta.lastUpdated = new Date().toISOString();
14028
14000
  }
14029
14001
  const { resourceType, id } = result;
14030
- if (!(resourceType in __classPrivateFieldGet(this, _MemoryRepository_resources, "f"))) {
14031
- __classPrivateFieldGet(this, _MemoryRepository_resources, "f")[resourceType] = {};
14002
+ let resources = this.resources.get(resourceType);
14003
+ if (!resources) {
14004
+ resources = new Map();
14005
+ this.resources.set(resourceType, resources);
14032
14006
  }
14033
- if (!(resourceType in __classPrivateFieldGet(this, _MemoryRepository_history, "f"))) {
14034
- __classPrivateFieldGet(this, _MemoryRepository_history, "f")[resourceType] = {};
14007
+ resources.set(id, result);
14008
+ let resourceTypeHistory = this.history.get(resourceType);
14009
+ if (!resourceTypeHistory) {
14010
+ resourceTypeHistory = new Map();
14011
+ this.history.set(resourceType, resourceTypeHistory);
14035
14012
  }
14036
- if (!(id in __classPrivateFieldGet(this, _MemoryRepository_history, "f")[resourceType])) {
14037
- __classPrivateFieldGet(this, _MemoryRepository_history, "f")[resourceType][id] = [];
14013
+ let resourceHistory = resourceTypeHistory.get(id);
14014
+ if (!resourceHistory) {
14015
+ resourceHistory = [];
14016
+ resourceTypeHistory.set(id, resourceHistory);
14038
14017
  }
14039
- __classPrivateFieldGet(this, _MemoryRepository_resources, "f")[resourceType][id] = result;
14040
- __classPrivateFieldGet(this, _MemoryRepository_history, "f")[resourceType][id].push(result);
14018
+ resourceHistory.push(result);
14041
14019
  return deepClone(result);
14042
14020
  }
14043
14021
  updateResource(resource) {
@@ -14066,7 +14044,7 @@ class MemoryRepository extends BaseRepository {
14066
14044
  return this.updateResource(resource);
14067
14045
  }
14068
14046
  async readResource(resourceType, id) {
14069
- const resource = __classPrivateFieldGet(this, _MemoryRepository_resources, "f")?.[resourceType]?.[id];
14047
+ const resource = this.resources.get(resourceType)?.get(id);
14070
14048
  if (!resource) {
14071
14049
  throw new OperationOutcomeError(notFound);
14072
14050
  }
@@ -14087,14 +14065,17 @@ class MemoryRepository extends BaseRepository {
14087
14065
  return {
14088
14066
  resourceType: 'Bundle',
14089
14067
  type: 'history',
14090
- entry: (__classPrivateFieldGet(this, _MemoryRepository_history, "f")?.[resourceType]?.[id] ?? [])
14068
+ entry: (this.history.get(resourceType)?.get(id) || [])
14091
14069
  .reverse()
14092
14070
  .map((version) => ({ resource: deepClone(version) })),
14093
14071
  };
14094
14072
  }
14095
14073
  async readVersion(resourceType, id, versionId) {
14096
14074
  await this.readResource(resourceType, id);
14097
- const version = __classPrivateFieldGet(this, _MemoryRepository_history, "f")?.[resourceType]?.[id]?.find((v) => v.meta?.versionId === versionId);
14075
+ const version = this.history
14076
+ .get(resourceType)
14077
+ ?.get(id)
14078
+ ?.find((v) => v.meta?.versionId === versionId);
14098
14079
  if (!version) {
14099
14080
  throw new OperationOutcomeError(notFound);
14100
14081
  }
@@ -14102,8 +14083,13 @@ class MemoryRepository extends BaseRepository {
14102
14083
  }
14103
14084
  async search(searchRequest) {
14104
14085
  const { resourceType } = searchRequest;
14105
- const resources = __classPrivateFieldGet(this, _MemoryRepository_resources, "f")[resourceType] ?? {};
14106
- const result = Object.values(resources).filter((resource) => matchesSearchRequest(resource, searchRequest));
14086
+ const resources = this.resources.get(resourceType) || new Map();
14087
+ const result = [];
14088
+ for (const resource of resources.values()) {
14089
+ if (matchesSearchRequest(resource, searchRequest)) {
14090
+ result.push(resource);
14091
+ }
14092
+ }
14107
14093
  let entry = result.map((resource) => ({ resource: deepClone(resource) }));
14108
14094
  if (searchRequest.sortRules) {
14109
14095
  for (const sortRule of searchRequest.sortRules) {
@@ -14124,13 +14110,12 @@ class MemoryRepository extends BaseRepository {
14124
14110
  };
14125
14111
  }
14126
14112
  async deleteResource(resourceType, id) {
14127
- if (!__classPrivateFieldGet(this, _MemoryRepository_resources, "f")?.[resourceType]?.[id]) {
14113
+ if (!this.resources.get(resourceType)?.get(id)) {
14128
14114
  throw new OperationOutcomeError(notFound);
14129
14115
  }
14130
- delete __classPrivateFieldGet(this, _MemoryRepository_resources, "f")[resourceType][id];
14116
+ this.resources.get(resourceType)?.delete(id);
14131
14117
  }
14132
14118
  }
14133
- _MemoryRepository_resources = new WeakMap(), _MemoryRepository_history = new WeakMap();
14134
14119
  const sortComparator = (a, b, sortRule) => {
14135
14120
  const searchParam = globalSchema.types[a.resourceType]?.searchParams?.[sortRule.code];
14136
14121
  const expression = searchParam?.expression;