@medplum/core 2.0.21 → 2.0.23

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.
Files changed (58) hide show
  1. package/dist/cjs/index.cjs +720 -494
  2. package/dist/cjs/index.cjs.map +1 -1
  3. package/dist/cjs/index.min.cjs +1 -1
  4. package/dist/esm/client.mjs +197 -114
  5. package/dist/esm/client.mjs.map +1 -1
  6. package/dist/esm/crypto.mjs +3 -1
  7. package/dist/esm/crypto.mjs.map +1 -1
  8. package/dist/esm/fhirlexer/parse.mjs.map +1 -1
  9. package/dist/esm/fhirlexer/tokenize.mjs +2 -2
  10. package/dist/esm/fhirlexer/tokenize.mjs.map +1 -1
  11. package/dist/esm/fhirpath/atoms.mjs +63 -56
  12. package/dist/esm/fhirpath/atoms.mjs.map +1 -1
  13. package/dist/esm/fhirpath/functions.mjs +370 -252
  14. package/dist/esm/fhirpath/functions.mjs.map +1 -1
  15. package/dist/esm/fhirpath/parse.mjs +4 -2
  16. package/dist/esm/fhirpath/parse.mjs.map +1 -1
  17. package/dist/esm/format.mjs +6 -4
  18. package/dist/esm/format.mjs.map +1 -1
  19. package/dist/esm/hl7.mjs +1 -1
  20. package/dist/esm/hl7.mjs.map +1 -1
  21. package/dist/esm/index.min.mjs +1 -1
  22. package/dist/esm/index.mjs +2 -1
  23. package/dist/esm/index.mjs.map +1 -1
  24. package/dist/esm/jwt.mjs +4 -2
  25. package/dist/esm/jwt.mjs.map +1 -1
  26. package/dist/esm/outcomes.mjs +14 -11
  27. package/dist/esm/outcomes.mjs.map +1 -1
  28. package/dist/esm/schema.mjs +4 -10
  29. package/dist/esm/schema.mjs.map +1 -1
  30. package/dist/esm/search/details.mjs +4 -5
  31. package/dist/esm/search/details.mjs.map +1 -1
  32. package/dist/esm/search/match.mjs +1 -0
  33. package/dist/esm/search/match.mjs.map +1 -1
  34. package/dist/esm/search/search.mjs +1 -1
  35. package/dist/esm/search/search.mjs.map +1 -1
  36. package/dist/esm/storage.mjs +8 -0
  37. package/dist/esm/storage.mjs.map +1 -1
  38. package/dist/esm/types.mjs +1 -0
  39. package/dist/esm/types.mjs.map +1 -1
  40. package/dist/esm/utils.mjs +8 -7
  41. package/dist/esm/utils.mjs.map +1 -1
  42. package/dist/types/client.d.ts +128 -69
  43. package/dist/types/crypto.d.ts +3 -1
  44. package/dist/types/fhirlexer/parse.d.ts +7 -3
  45. package/dist/types/fhirpath/atoms.d.ts +21 -21
  46. package/dist/types/fhirpath/functions.d.ts +2 -2
  47. package/dist/types/fhirpath/parse.d.ts +2 -1
  48. package/dist/types/hl7.d.ts +1 -1
  49. package/dist/types/index.d.ts +1 -0
  50. package/dist/types/jwt.d.ts +2 -1
  51. package/dist/types/outcomes.d.ts +7 -1
  52. package/dist/types/schema.d.ts +4 -10
  53. package/dist/types/search/details.d.ts +0 -1
  54. package/dist/types/search/search.d.ts +1 -1
  55. package/dist/types/storage.d.ts +8 -0
  56. package/dist/types/typeschema/types.d.ts +0 -1
  57. package/dist/types/utils.d.ts +5 -12
  58. package/package.json +1 -1
@@ -1,17 +1,17 @@
1
+ import { encodeBase64 } from './base64.mjs';
1
2
  import { LRUCache } from './cache.mjs';
2
3
  import { getRandomString, encryptSHA256 } from './crypto.mjs';
3
4
  import { EventTarget } from './eventtarget.mjs';
4
5
  import { parseJWTPayload } from './jwt.mjs';
5
- import { OperationOutcomeError, notFound, normalizeOperationOutcome, isOk } from './outcomes.mjs';
6
+ import { OperationOutcomeError, notFound, normalizeOperationOutcome, isOk, badRequest } from './outcomes.mjs';
6
7
  import { ReadablePromise } from './readablepromise.mjs';
7
8
  import { ClientStorage } from './storage.mjs';
8
9
  import { globalSchema, indexStructureDefinition, indexSearchParameter } from './types.mjs';
9
10
  import { createReference, arrayBufferToBase64 } from './utils.mjs';
10
- import { encodeBase64 } from './base64.mjs';
11
11
 
12
12
  // PKCE auth based on:
13
13
  // https://aws.amazon.com/blogs/security/how-to-add-authentication-single-page-web-application-with-amazon-cognito-oauth2-implementation/
14
- const MEDPLUM_VERSION = "2.0.21-87271fa1" ;
14
+ const MEDPLUM_VERSION = "2.0.23-b244eeae" ;
15
15
  const DEFAULT_BASE_URL = 'https://api.medplum.com/';
16
16
  const DEFAULT_RESOURCE_CACHE_SIZE = 1000;
17
17
  const DEFAULT_CACHE_TIME = 60000; // 60 seconds
@@ -98,7 +98,6 @@ var OAuthTokenType;
98
98
  * <head>
99
99
  * <meta name="algolia:pageRank" content="100" />
100
100
  * </head>
101
-
102
101
  */
103
102
  class MedplumClient extends EventTarget {
104
103
  constructor(options) {
@@ -151,6 +150,16 @@ class MedplumClient extends EventTarget {
151
150
  getBaseUrl() {
152
151
  return this.baseUrl;
153
152
  }
153
+ /**
154
+ * Returns the current authorize URL.
155
+ * By default, this is set to `https://api.medplum.com/oauth2/authorize`.
156
+ * This can be overridden by setting the `authorizeUrl` option when creating the client.
157
+ * @category HTTP
158
+ * @returns The current authorize URL.
159
+ */
160
+ getAuthorizeUrl() {
161
+ return this.authorizeUrl;
162
+ }
154
163
  /**
155
164
  * Clears all auth state including local storage and session storage.
156
165
  * @category Authentication
@@ -165,12 +174,14 @@ class MedplumClient extends EventTarget {
165
174
  * @category Authentication
166
175
  */
167
176
  clearActiveLogin() {
177
+ if (this.basicAuth) {
178
+ return;
179
+ }
168
180
  this.storage.setString('activeLogin', undefined);
169
181
  this.requestCache?.clear();
170
182
  this.accessToken = undefined;
171
183
  this.refreshToken = undefined;
172
- this.profile = undefined;
173
- this.config = undefined;
184
+ this.sessionDetails = undefined;
174
185
  this.dispatchEvent({ type: 'change' });
175
186
  }
176
187
  /**
@@ -210,7 +221,6 @@ class MedplumClient extends EventTarget {
210
221
  * This is a lower level method for custom requests.
211
222
  * For common operations, we recommend using higher level methods
212
223
  * such as `readResource()`, `search()`, etc.
213
- *
214
224
  * @category HTTP
215
225
  * @param url The target URL.
216
226
  * @param options Optional fetch options.
@@ -250,7 +260,6 @@ class MedplumClient extends EventTarget {
250
260
  * This is a lower level method for custom requests.
251
261
  * For common operations, we recommend using higher level methods
252
262
  * such as `createResource()`.
253
- *
254
263
  * @category HTTP
255
264
  * @param url The target URL.
256
265
  * @param body The content body. Strings and `File` objects are passed directly. Other objects are converted to JSON.
@@ -275,7 +284,6 @@ class MedplumClient extends EventTarget {
275
284
  * This is a lower level method for custom requests.
276
285
  * For common operations, we recommend using higher level methods
277
286
  * such as `updateResource()`.
278
- *
279
287
  * @category HTTP
280
288
  * @param url The target URL.
281
289
  * @param body The content body. Strings and `File` objects are passed directly. Other objects are converted to JSON.
@@ -300,7 +308,6 @@ class MedplumClient extends EventTarget {
300
308
  * This is a lower level method for custom requests.
301
309
  * For common operations, we recommend using higher level methods
302
310
  * such as `patchResource()`.
303
- *
304
311
  * @category HTTP
305
312
  * @param url The target URL.
306
313
  * @param operations Array of JSONPatch operations.
@@ -321,13 +328,12 @@ class MedplumClient extends EventTarget {
321
328
  * This is a lower level method for custom requests.
322
329
  * For common operations, we recommend using higher level methods
323
330
  * such as `deleteResource()`.
324
- *
325
331
  * @category HTTP
326
332
  * @param url The target URL.
327
333
  * @param options Optional fetch options.
328
334
  * @returns Promise to the response content.
329
335
  */
330
- delete(url, options = {}) {
336
+ delete(url, options) {
331
337
  url = url.toString();
332
338
  this.invalidateUrl(url);
333
339
  return this.request('DELETE', url, options);
@@ -338,53 +344,54 @@ class MedplumClient extends EventTarget {
338
344
  * This method is part of the two different user registration flows:
339
345
  * 1) New Practitioner and new Project
340
346
  * 2) New Patient registration
341
- *
342
347
  * @category Authentication
343
348
  * @param newUserRequest Register request including email and password.
349
+ * @param options Optional fetch options.
344
350
  * @returns Promise to the authentication response.
345
351
  */
346
- async startNewUser(newUserRequest) {
352
+ async startNewUser(newUserRequest, options) {
347
353
  const { codeChallengeMethod, codeChallenge } = await this.startPkce();
348
354
  return this.post('auth/newuser', {
349
355
  ...newUserRequest,
350
356
  codeChallengeMethod,
351
357
  codeChallenge,
352
- });
358
+ }, undefined, options);
353
359
  }
354
360
  /**
355
361
  * Initiates a new project flow.
356
362
  *
357
363
  * This requires a partial login from `startNewUser` or `startNewGoogleUser`.
358
- *
359
364
  * @param newProjectRequest Register request including email and password.
365
+ * @param options Optional fetch options.
360
366
  * @returns Promise to the authentication response.
361
367
  */
362
- async startNewProject(newProjectRequest) {
363
- return this.post('auth/newproject', newProjectRequest);
368
+ async startNewProject(newProjectRequest, options) {
369
+ return this.post('auth/newproject', newProjectRequest, undefined, options);
364
370
  }
365
371
  /**
366
372
  * Initiates a new patient flow.
367
373
  *
368
374
  * This requires a partial login from `startNewUser` or `startNewGoogleUser`.
369
- *
370
375
  * @param newPatientRequest Register request including email and password.
376
+ * @param options Optional fetch options.
371
377
  * @returns Promise to the authentication response.
372
378
  */
373
- async startNewPatient(newPatientRequest) {
374
- return this.post('auth/newpatient', newPatientRequest);
379
+ async startNewPatient(newPatientRequest, options) {
380
+ return this.post('auth/newpatient', newPatientRequest, undefined, options);
375
381
  }
376
382
  /**
377
383
  * Initiates a user login flow.
378
384
  * @category Authentication
379
385
  * @param loginRequest Login request including email and password.
386
+ * @param options Optional fetch options.
380
387
  * @returns Promise to the authentication response.
381
388
  */
382
- async startLogin(loginRequest) {
389
+ async startLogin(loginRequest, options) {
383
390
  return this.post('auth/login', {
384
391
  ...(await this.ensureCodeChallenge(loginRequest)),
385
392
  clientId: loginRequest.clientId ?? this.clientId,
386
393
  scope: loginRequest.scope,
387
- });
394
+ }, undefined, options);
388
395
  }
389
396
  /**
390
397
  * Tries to sign in with Google authentication.
@@ -392,14 +399,15 @@ class MedplumClient extends EventTarget {
392
399
  * See: https://developers.google.com/identity/gsi/web/guides/handle-credential-responses-js-functions
393
400
  * @category Authentication
394
401
  * @param loginRequest Login request including Google credential response.
402
+ * @param options Optional fetch options.
395
403
  * @returns Promise to the authentication response.
396
404
  */
397
- async startGoogleLogin(loginRequest) {
405
+ async startGoogleLogin(loginRequest, options) {
398
406
  return this.post('auth/google', {
399
407
  ...(await this.ensureCodeChallenge(loginRequest)),
400
408
  clientId: loginRequest.clientId ?? this.clientId,
401
409
  scope: loginRequest.scope,
402
- });
410
+ }, undefined, options);
403
411
  }
404
412
  /**
405
413
  * Returns the PKCE code challenge and method.
@@ -430,6 +438,7 @@ class MedplumClient extends EventTarget {
430
438
  * This may result in navigating away to the sign in page.
431
439
  * @category Authentication
432
440
  * @param loginParams Optional login parameters.
441
+ * @returns The user profile resource if available.
433
442
  */
434
443
  async signInWithRedirect(loginParams) {
435
444
  const urlParams = new URLSearchParams(window.location.search);
@@ -466,6 +475,7 @@ class MedplumClient extends EventTarget {
466
475
  * Exchange an external access token for a Medplum access token.
467
476
  * @param token The access token that was generated by the external identity provider.
468
477
  * @param clientId The ID of the `ClientApplication` in your Medplum project that will be making the exchange request.
478
+ * @returns The user profile resource.
469
479
  * @category Authentication
470
480
  */
471
481
  async exchangeExternalAccessToken(token, clientId) {
@@ -564,14 +574,13 @@ class MedplumClient extends EventTarget {
564
574
  * ```
565
575
  *
566
576
  * See FHIR search for full details: https://www.hl7.org/fhir/search.html
567
- *
568
577
  * @category Search
569
578
  * @param resourceType The FHIR resource type.
570
579
  * @param query Optional FHIR search query or structured query object. Can be any valid input to the URLSearchParams() constructor.
571
580
  * @param options Optional fetch options.
572
581
  * @returns Promise to the search result bundle.
573
582
  */
574
- search(resourceType, query, options = {}) {
583
+ search(resourceType, query, options) {
575
584
  const url = this.fhirSearchUrl(resourceType, query);
576
585
  const cacheKey = url.toString() + '-search';
577
586
  const cached = this.getCacheEntry(cacheKey, options);
@@ -605,14 +614,13 @@ class MedplumClient extends EventTarget {
605
614
  * The return value is the resource, if available; otherwise, undefined.
606
615
  *
607
616
  * See FHIR search for full details: https://www.hl7.org/fhir/search.html
608
- *
609
617
  * @category Search
610
618
  * @param resourceType The FHIR resource type.
611
619
  * @param query Optional FHIR search query or structured query object. Can be any valid input to the URLSearchParams() constructor.
612
620
  * @param options Optional fetch options.
613
621
  * @returns Promise to the first search result.
614
622
  */
615
- searchOne(resourceType, query, options = {}) {
623
+ searchOne(resourceType, query, options) {
616
624
  const url = this.fhirSearchUrl(resourceType, query);
617
625
  url.searchParams.set('_count', '1');
618
626
  url.searchParams.sort();
@@ -640,14 +648,13 @@ class MedplumClient extends EventTarget {
640
648
  * The return value is an array of resources.
641
649
  *
642
650
  * See FHIR search for full details: https://www.hl7.org/fhir/search.html
643
- *
644
651
  * @category Search
645
652
  * @param resourceType The FHIR resource type.
646
653
  * @param query Optional FHIR search query or structured query object. Can be any valid input to the URLSearchParams() constructor.
647
654
  * @param options Optional fetch options.
648
655
  * @returns Promise to the array of search results.
649
656
  */
650
- searchResources(resourceType, query, options = {}) {
657
+ searchResources(resourceType, query, options) {
651
658
  const url = this.fhirSearchUrl(resourceType, query);
652
659
  const cacheKey = url.toString() + '-searchResources';
653
660
  const cached = this.getCacheEntry(cacheKey, options);
@@ -672,14 +679,13 @@ class MedplumClient extends EventTarget {
672
679
  * }
673
680
  * }
674
681
  * ```
675
- *
676
682
  * @category Search
677
683
  * @param resourceType The FHIR resource type.
678
684
  * @param query Optional FHIR search query or structured query object. Can be any valid input to the URLSearchParams() constructor.
679
685
  * @param options Optional fetch options.
680
- * @returns An async generator, where each result is an array of resources for each page.
686
+ * @yields An async generator, where each result is an array of resources for each page.
681
687
  */
682
- async *searchResourcePages(resourceType, query, options = {}) {
688
+ async *searchResourcePages(resourceType, query, options) {
683
689
  let url = this.fhirSearchUrl(resourceType, query);
684
690
  while (url) {
685
691
  const searchParams = new URL(url).searchParams;
@@ -695,14 +701,13 @@ class MedplumClient extends EventTarget {
695
701
  /**
696
702
  * Searches a ValueSet resource using the "expand" operation.
697
703
  * See: https://www.hl7.org/fhir/operation-valueset-expand.html
698
- *
699
704
  * @category Search
700
705
  * @param system The ValueSet system url.
701
706
  * @param filter The search string.
702
707
  * @param options Optional fetch options.
703
708
  * @returns Promise to expanded ValueSet.
704
709
  */
705
- searchValueSet(system, filter, options = {}) {
710
+ searchValueSet(system, filter, options) {
706
711
  const url = this.fhirUrl('ValueSet', '$expand');
707
712
  url.searchParams.set('url', system);
708
713
  url.searchParams.set('filter', filter);
@@ -722,8 +727,7 @@ class MedplumClient extends EventTarget {
722
727
  /**
723
728
  * Returns a cached resource if it is available.
724
729
  * @category Caching
725
- * @param resourceType The FHIR resource type.
726
- * @param id The FHIR resource ID.
730
+ * @param reference The FHIR reference.
727
731
  * @returns The resource if it is available in the cache; undefined otherwise.
728
732
  */
729
733
  getCachedReference(reference) {
@@ -751,14 +755,13 @@ class MedplumClient extends EventTarget {
751
755
  * ```
752
756
  *
753
757
  * See the FHIR "read" operation for full details: https://www.hl7.org/fhir/http.html#read
754
- *
755
758
  * @category Read
756
759
  * @param resourceType The FHIR resource type.
757
760
  * @param id The resource ID.
758
761
  * @param options Optional fetch options.
759
762
  * @returns The resource if available; undefined otherwise.
760
763
  */
761
- readResource(resourceType, id, options = {}) {
764
+ readResource(resourceType, id, options) {
762
765
  return this.get(this.fhirUrl(resourceType, id), options);
763
766
  }
764
767
  /**
@@ -775,13 +778,12 @@ class MedplumClient extends EventTarget {
775
778
  * ```
776
779
  *
777
780
  * See the FHIR "read" operation for full details: https://www.hl7.org/fhir/http.html#read
778
- *
779
781
  * @category Read
780
782
  * @param reference The FHIR reference object.
781
783
  * @param options Optional fetch options.
782
784
  * @returns The resource if available; undefined otherwise.
783
785
  */
784
- readReference(reference, options = {}) {
786
+ readReference(reference, options) {
785
787
  const refString = reference?.reference;
786
788
  if (!refString) {
787
789
  return new ReadablePromise(Promise.reject(new Error('Missing reference')));
@@ -877,14 +879,13 @@ class MedplumClient extends EventTarget {
877
879
  * ```
878
880
  *
879
881
  * See the FHIR "history" operation for full details: https://www.hl7.org/fhir/http.html#history
880
- *
881
882
  * @category Read
882
883
  * @param resourceType The FHIR resource type.
883
884
  * @param id The resource ID.
884
885
  * @param options Optional fetch options.
885
886
  * @returns Promise to the resource history.
886
887
  */
887
- readHistory(resourceType, id, options = {}) {
888
+ readHistory(resourceType, id, options) {
888
889
  return this.get(this.fhirUrl(resourceType, id, '_history'), options);
889
890
  }
890
891
  /**
@@ -898,7 +899,6 @@ class MedplumClient extends EventTarget {
898
899
  * ```
899
900
  *
900
901
  * See the FHIR "vread" operation for full details: https://www.hl7.org/fhir/http.html#vread
901
- *
902
902
  * @category Read
903
903
  * @param resourceType The FHIR resource type.
904
904
  * @param id The resource ID.
@@ -906,7 +906,7 @@ class MedplumClient extends EventTarget {
906
906
  * @param options Optional fetch options.
907
907
  * @returns The resource if available; undefined otherwise.
908
908
  */
909
- readVersion(resourceType, id, vid, options = {}) {
909
+ readVersion(resourceType, id, vid, options) {
910
910
  return this.get(this.fhirUrl(resourceType, id, '_history', vid), options);
911
911
  }
912
912
  /**
@@ -920,13 +920,12 @@ class MedplumClient extends EventTarget {
920
920
  * ```
921
921
  *
922
922
  * See the FHIR "patient-everything" operation for full details: https://hl7.org/fhir/operation-patient-everything.html
923
- *
924
923
  * @category Read
925
924
  * @param id The Patient Id
926
925
  * @param options Optional fetch options.
927
926
  * @returns A Bundle of all Resources related to the Patient
928
927
  */
929
- readPatientEverything(id, options = {}) {
928
+ readPatientEverything(id, options) {
930
929
  return this.get(this.fhirUrl('Patient', id, '$everything'), options);
931
930
  }
932
931
  /**
@@ -948,17 +947,17 @@ class MedplumClient extends EventTarget {
948
947
  * ```
949
948
  *
950
949
  * See the FHIR "create" operation for full details: https://www.hl7.org/fhir/http.html#create
951
- *
952
950
  * @category Create
953
951
  * @param resource The FHIR resource to create.
952
+ * @param options Optional fetch options.
954
953
  * @returns The result of the create operation.
955
954
  */
956
- createResource(resource) {
955
+ createResource(resource, options) {
957
956
  if (!resource.resourceType) {
958
957
  throw new Error('Missing resourceType');
959
958
  }
960
959
  this.invalidateSearches(resource.resourceType);
961
- return this.post(this.fhirUrl(resource.resourceType), resource);
960
+ return this.post(this.fhirUrl(resource.resourceType), resource, undefined, options);
962
961
  }
963
962
  /**
964
963
  * Conditionally create a new FHIR resource only if some equivalent resource does not already exist on the server.
@@ -994,14 +993,15 @@ class MedplumClient extends EventTarget {
994
993
  * The query parameter only contains the search parameters (what would be in the URL following the "?").
995
994
  *
996
995
  * See the FHIR "conditional create" operation for full details: https://www.hl7.org/fhir/http.html#ccreate
997
- *
998
996
  * @category Create
999
997
  * @param resource The FHIR resource to create.
1000
998
  * @param query The search query for an equivalent resource (should not include resource type or "?").
999
+ * @param options Optional fetch options.
1001
1000
  * @returns The result of the create operation.
1002
1001
  */
1003
- async createResourceIfNoneExist(resource, query) {
1004
- return ((await this.searchOne(resource.resourceType, query)) ?? this.createResource(resource));
1002
+ async createResourceIfNoneExist(resource, query, options) {
1003
+ return ((await this.searchOne(resource.resourceType, query, options)) ??
1004
+ this.createResource(resource, options));
1005
1005
  }
1006
1006
  /**
1007
1007
  * Creates a FHIR `Binary` resource with the provided data content.
@@ -1020,11 +1020,11 @@ class MedplumClient extends EventTarget {
1020
1020
  * ```
1021
1021
  *
1022
1022
  * See the FHIR "create" operation for full details: https://www.hl7.org/fhir/http.html#create
1023
- *
1024
1023
  * @category Create
1025
1024
  * @param data The binary data to upload.
1026
1025
  * @param filename Optional filename for the binary.
1027
1026
  * @param contentType Content type for the binary.
1027
+ * @param onProgress Optional callback for progress events.
1028
1028
  * @returns The result of the create operation.
1029
1029
  */
1030
1030
  createBinary(data, filename, contentType, onProgress) {
@@ -1083,9 +1083,11 @@ class MedplumClient extends EventTarget {
1083
1083
  * ```
1084
1084
  *
1085
1085
  * See the pdfmake document definition for full details: https://pdfmake.github.io/docs/0.1/document-definition-object/
1086
- *
1087
1086
  * @category Media
1088
1087
  * @param docDefinition The PDF document definition.
1088
+ * @param filename Optional filename for the PDF binary resource.
1089
+ * @param tableLayouts Optional pdfmake custom table layout.
1090
+ * @param fonts Optional pdfmake custom font dictionary.
1089
1091
  * @returns The result of the create operation.
1090
1092
  */
1091
1093
  async createPdf(docDefinition, filename, tableLayouts, fonts) {
@@ -1099,13 +1101,13 @@ class MedplumClient extends EventTarget {
1099
1101
  * Creates a FHIR `Communication` resource with the provided data content.
1100
1102
  *
1101
1103
  * This is a convenience method to handle commmon cases where a `Communication` resource is created with a `payload`.
1102
- *
1103
1104
  * @category Create
1104
1105
  * @param resource The FHIR resource to comment on.
1105
1106
  * @param text The text of the comment.
1107
+ * @param options Optional fetch options.
1106
1108
  * @returns The result of the create operation.
1107
1109
  */
1108
- createComment(resource, text) {
1110
+ createComment(resource, text, options) {
1109
1111
  const profile = this.getProfile();
1110
1112
  let encounter = undefined;
1111
1113
  let subject = undefined;
@@ -1128,7 +1130,7 @@ class MedplumClient extends EventTarget {
1128
1130
  sender: profile ? createReference(profile) : undefined,
1129
1131
  sent: new Date().toISOString(),
1130
1132
  payload: [{ contentString: text }],
1131
- });
1133
+ }, options);
1132
1134
  }
1133
1135
  /**
1134
1136
  * Updates a FHIR resource.
@@ -1150,12 +1152,12 @@ class MedplumClient extends EventTarget {
1150
1152
  * ```
1151
1153
  *
1152
1154
  * See the FHIR "update" operation for full details: https://www.hl7.org/fhir/http.html#update
1153
- *
1154
1155
  * @category Write
1155
1156
  * @param resource The FHIR resource to update.
1157
+ * @param options Optional fetch options.
1156
1158
  * @returns The result of the update operation.
1157
1159
  */
1158
- async updateResource(resource) {
1160
+ async updateResource(resource, options) {
1159
1161
  if (!resource.resourceType) {
1160
1162
  throw new Error('Missing resourceType');
1161
1163
  }
@@ -1163,7 +1165,7 @@ class MedplumClient extends EventTarget {
1163
1165
  throw new Error('Missing id');
1164
1166
  }
1165
1167
  this.invalidateSearches(resource.resourceType);
1166
- let result = await this.put(this.fhirUrl(resource.resourceType, resource.id), resource);
1168
+ let result = await this.put(this.fhirUrl(resource.resourceType, resource.id), resource, undefined, options);
1167
1169
  if (!result) {
1168
1170
  // On 304 not modified, result will be undefined
1169
1171
  // Return the user input instead
@@ -1190,16 +1192,16 @@ class MedplumClient extends EventTarget {
1190
1192
  * See the FHIR "update" operation for full details: https://www.hl7.org/fhir/http.html#patch
1191
1193
  *
1192
1194
  * See the JSONPatch specification for full details: https://tools.ietf.org/html/rfc6902
1193
- *
1194
1195
  * @category Write
1195
1196
  * @param resourceType The FHIR resource type.
1196
1197
  * @param id The resource ID.
1197
1198
  * @param operations The JSONPatch operations.
1199
+ * @param options Optional fetch options.
1198
1200
  * @returns The result of the patch operations.
1199
1201
  */
1200
- patchResource(resourceType, id, operations) {
1202
+ patchResource(resourceType, id, operations, options) {
1201
1203
  this.invalidateSearches(resourceType);
1202
- return this.patch(this.fhirUrl(resourceType, id), operations);
1204
+ return this.patch(this.fhirUrl(resourceType, id), operations, options);
1203
1205
  }
1204
1206
  /**
1205
1207
  * Deletes a FHIR resource by resource type and ID.
@@ -1211,16 +1213,16 @@ class MedplumClient extends EventTarget {
1211
1213
  * ```
1212
1214
  *
1213
1215
  * See the FHIR "delete" operation for full details: https://www.hl7.org/fhir/http.html#delete
1214
- *
1215
1216
  * @category Delete
1216
1217
  * @param resourceType The FHIR resource type.
1217
1218
  * @param id The resource ID.
1219
+ * @param options Optional fetch options.
1218
1220
  * @returns The result of the delete operation.
1219
1221
  */
1220
- deleteResource(resourceType, id) {
1222
+ deleteResource(resourceType, id, options) {
1221
1223
  this.deleteCacheEntry(this.fhirUrl(resourceType, id).toString());
1222
1224
  this.invalidateSearches(resourceType);
1223
- return this.delete(this.fhirUrl(resourceType, id));
1225
+ return this.delete(this.fhirUrl(resourceType, id), options);
1224
1226
  }
1225
1227
  /**
1226
1228
  * Executes the validate operation with the provided resource.
@@ -1235,12 +1237,12 @@ class MedplumClient extends EventTarget {
1235
1237
  * ```
1236
1238
  *
1237
1239
  * See the FHIR "$validate" operation for full details: https://www.hl7.org/fhir/resource-operation-validate.html
1238
- *
1239
1240
  * @param resource The FHIR resource.
1241
+ * @param options Optional fetch options.
1240
1242
  * @returns The validate operation outcome.
1241
1243
  */
1242
- validateResource(resource) {
1243
- return this.post(this.fhirUrl(resource.resourceType, '$validate'), resource);
1244
+ validateResource(resource, options) {
1245
+ return this.post(this.fhirUrl(resource.resourceType, '$validate'), resource, undefined, options);
1244
1246
  }
1245
1247
  /**
1246
1248
  * Executes a bot by ID or Identifier.
@@ -1250,7 +1252,7 @@ class MedplumClient extends EventTarget {
1250
1252
  * @param options Optional fetch options.
1251
1253
  * @returns The Bot return value.
1252
1254
  */
1253
- executeBot(idOrIdentifier, body, contentType, options = {}) {
1255
+ executeBot(idOrIdentifier, body, contentType, options) {
1254
1256
  let url;
1255
1257
  if (typeof idOrIdentifier === 'string') {
1256
1258
  const id = idOrIdentifier;
@@ -1310,7 +1312,7 @@ class MedplumClient extends EventTarget {
1310
1312
  * @param options Optional fetch options.
1311
1313
  * @returns The FHIR batch/transaction response bundle.
1312
1314
  */
1313
- executeBatch(bundle, options = {}) {
1315
+ executeBatch(bundle, options) {
1314
1316
  return this.post(this.fhirBaseUrl.slice(0, -1), bundle, undefined, options);
1315
1317
  }
1316
1318
  /**
@@ -1347,11 +1349,12 @@ class MedplumClient extends EventTarget {
1347
1349
  *
1348
1350
  * See options here: https://nodemailer.com/extras/mailcomposer/
1349
1351
  * @category Media
1350
- * @param options The MailComposer options.
1352
+ * @param email The MailComposer options.
1353
+ * @param options Optional fetch options.
1351
1354
  * @returns Promise to the operation outcome.
1352
1355
  */
1353
- sendEmail(email) {
1354
- return this.post('email/v1/send', email, 'application/json');
1356
+ sendEmail(email, options) {
1357
+ return this.post('email/v1/send', email, 'application/json', options);
1355
1358
  }
1356
1359
  /**
1357
1360
  * Executes a GraphQL query.
@@ -1393,7 +1396,6 @@ class MedplumClient extends EventTarget {
1393
1396
  * See the GraphQL documentation for more details: https://graphql.org/learn/
1394
1397
  *
1395
1398
  * See the FHIR GraphQL documentation for FHIR specific details: https://www.hl7.org/fhir/graphql.html
1396
- *
1397
1399
  * @category Read
1398
1400
  * @param query The GraphQL query.
1399
1401
  * @param operationName Optional GraphQL operation name.
@@ -1408,15 +1410,15 @@ class MedplumClient extends EventTarget {
1408
1410
  *
1409
1411
  * Executes the $graph operation on this resource to fetch a Bundle of resources linked to the target resource
1410
1412
  * according to a graph definition
1411
-
1412
1413
  * @category Read
1413
1414
  * @param resourceType The FHIR resource type.
1414
1415
  * @param id The resource ID.
1415
1416
  * @param graphName `name` parameter of the GraphDefinition
1417
+ * @param options Optional fetch options.
1416
1418
  * @returns A Bundle
1417
1419
  */
1418
- readResourceGraph(resourceType, id, graphName) {
1419
- return this.get(`${this.fhirUrl(resourceType, id)}/$graph?graph=${graphName}`);
1420
+ readResourceGraph(resourceType, id, graphName, options) {
1421
+ return this.get(`${this.fhirUrl(resourceType, id)}/$graph?graph=${graphName}`, options);
1420
1422
  }
1421
1423
  /**
1422
1424
  * @category Authentication
@@ -1426,11 +1428,16 @@ class MedplumClient extends EventTarget {
1426
1428
  return this.storage.getObject('activeLogin');
1427
1429
  }
1428
1430
  /**
1431
+ * Sets the active login.
1432
+ * @param login The new active login state.
1429
1433
  * @category Authentication
1430
1434
  */
1431
1435
  async setActiveLogin(login) {
1432
1436
  this.clearActiveLogin();
1433
1437
  this.accessToken = login.accessToken;
1438
+ if (this.basicAuth) {
1439
+ return;
1440
+ }
1434
1441
  this.refreshToken = login.refreshToken;
1435
1442
  this.storage.setObject('activeLogin', login);
1436
1443
  this.addLogin(login);
@@ -1439,6 +1446,7 @@ class MedplumClient extends EventTarget {
1439
1446
  }
1440
1447
  /**
1441
1448
  * Returns the current access token.
1449
+ * @returns The current access token.
1442
1450
  * @category Authentication
1443
1451
  */
1444
1452
  getAccessToken() {
@@ -1446,15 +1454,17 @@ class MedplumClient extends EventTarget {
1446
1454
  }
1447
1455
  /**
1448
1456
  * Sets the current access token.
1457
+ * @param accessToken The new access token.
1449
1458
  * @category Authentication
1450
1459
  */
1451
1460
  setAccessToken(accessToken) {
1452
1461
  this.accessToken = accessToken;
1453
1462
  this.refreshToken = undefined;
1454
- this.profile = undefined;
1455
- this.config = undefined;
1463
+ this.sessionDetails = undefined;
1456
1464
  }
1457
1465
  /**
1466
+ * Returns the list of available logins.
1467
+ * @returns The list of available logins.
1458
1468
  * @category Authentication
1459
1469
  */
1460
1470
  getLogins() {
@@ -1467,31 +1477,73 @@ class MedplumClient extends EventTarget {
1467
1477
  }
1468
1478
  async refreshProfile() {
1469
1479
  this.profilePromise = new Promise((resolve, reject) => {
1480
+ if (this.basicAuth) {
1481
+ return;
1482
+ }
1470
1483
  this.get('auth/me')
1471
1484
  .then((result) => {
1472
1485
  this.profilePromise = undefined;
1473
- this.profile = result.profile;
1474
- this.config = result.config;
1486
+ this.sessionDetails = result;
1475
1487
  this.dispatchEvent({ type: 'change' });
1476
- resolve(this.profile);
1488
+ resolve(result.profile);
1477
1489
  })
1478
1490
  .catch(reject);
1479
1491
  });
1480
1492
  return this.profilePromise;
1481
1493
  }
1482
1494
  /**
1495
+ * Returns true if the client is waiting for authentication.
1496
+ * @returns True if the client is waiting for authentication.
1483
1497
  * @category Authentication
1484
1498
  */
1485
1499
  isLoading() {
1486
1500
  return !!this.profilePromise;
1487
1501
  }
1488
1502
  /**
1503
+ * Returns true if the current user is authenticated as a super admin.
1504
+ * @returns True if the current user is authenticated as a super admin.
1505
+ * @category Authentication
1506
+ */
1507
+ isSuperAdmin() {
1508
+ return !!this.sessionDetails?.project?.superAdmin;
1509
+ }
1510
+ /**
1511
+ * Returns true if the current user is authenticated as a project admin.
1512
+ * @returns True if the current user is authenticated as a project admin.
1513
+ * @category Authentication
1514
+ */
1515
+ isProjectAdmin() {
1516
+ return !!this.sessionDetails?.membership?.admin;
1517
+ }
1518
+ /**
1519
+ * Returns the current project if available.
1520
+ * @returns The current project if available.
1521
+ * @category User Profile
1522
+ */
1523
+ getProject() {
1524
+ return this.sessionDetails?.project;
1525
+ }
1526
+ /**
1527
+ * Returns the current project membership if available.
1528
+ * @returns The current project membership if available.
1529
+ * @category User Profile
1530
+ */
1531
+ getProjectMembership() {
1532
+ return this.sessionDetails?.membership;
1533
+ }
1534
+ /**
1535
+ * Returns the current user profile resource if available.
1536
+ * This method does not wait for loading promises.
1537
+ * @returns The current user profile resource if available.
1489
1538
  * @category User Profile
1490
1539
  */
1491
1540
  getProfile() {
1492
- return this.profile;
1541
+ return this.sessionDetails?.profile;
1493
1542
  }
1494
1543
  /**
1544
+ * Returns the current user profile resource if available.
1545
+ * This method waits for loading promises.
1546
+ * @returns The current user profile resource if available.
1495
1547
  * @category User Profile
1496
1548
  */
1497
1549
  async getProfileAsync() {
@@ -1501,16 +1553,26 @@ class MedplumClient extends EventTarget {
1501
1553
  return this.getProfile();
1502
1554
  }
1503
1555
  /**
1556
+ * Returns the current user configuration if available.
1557
+ * @returns The current user configuration if available.
1504
1558
  * @category User Profile
1505
1559
  */
1506
1560
  getUserConfiguration() {
1507
- return this.config;
1561
+ return this.sessionDetails?.config;
1562
+ }
1563
+ /**
1564
+ * Returns the current user access policy if available.
1565
+ * @returns The current user access policy if available.
1566
+ * @category User Profile
1567
+ */
1568
+ getAccessPolicy() {
1569
+ return this.sessionDetails?.accessPolicy;
1508
1570
  }
1509
1571
  /**
1510
1572
  * Downloads the URL as a blob.
1511
- *
1512
1573
  * @category Read
1513
1574
  * @param url The URL to request.
1575
+ * @param options Optional fetch request init options.
1514
1576
  * @returns Promise to the response body as a blob.
1515
1577
  */
1516
1578
  async download(url, options = {}) {
@@ -1524,12 +1586,13 @@ class MedplumClient extends EventTarget {
1524
1586
  /**
1525
1587
  * Upload media to the server and create a Media instance for the uploaded content.
1526
1588
  * @param contents The contents of the media file, as a string, Uint8Array, File, or Blob.
1527
- * @param contentType The media type of the content
1528
- * @param filename The name of the file to be uploaded, or undefined if not applicable
1529
- * @param additionalFields Additional fields for Media
1589
+ * @param contentType The media type of the content.
1590
+ * @param filename The name of the file to be uploaded, or undefined if not applicable.
1591
+ * @param additionalFields Additional fields for Media.
1592
+ * @param options Optional fetch options.
1530
1593
  * @returns Promise that resolves to the created Media
1531
1594
  */
1532
- async uploadMedia(contents, contentType, filename, additionalFields) {
1595
+ async uploadMedia(contents, contentType, filename, additionalFields, options) {
1533
1596
  const binary = await this.createBinary(contents, filename, contentType);
1534
1597
  return this.createResource({
1535
1598
  ...additionalFields,
@@ -1539,11 +1602,10 @@ class MedplumClient extends EventTarget {
1539
1602
  url: 'Binary/' + binary.id,
1540
1603
  title: filename,
1541
1604
  },
1542
- });
1605
+ }, options);
1543
1606
  }
1544
1607
  /**
1545
1608
  * Performs Bulk Data Export operation request flow. See The FHIR "Bulk Data Export" for full details: https://build.fhir.org/ig/HL7/bulk-data/export.html#bulk-data-export
1546
- *
1547
1609
  * @param exportLevel Optional export level. Defaults to system level export. 'Group/:id' - Group of Patients, 'Patient' - All Patients.
1548
1610
  * @param resourceTypes A string of comma-delimited FHIR resource types.
1549
1611
  * @param since Resources will be included in the response if their state has changed after the supplied time (e.g. if Resource.meta.lastUpdated is later than the supplied _since time).
@@ -1559,7 +1621,6 @@ class MedplumClient extends EventTarget {
1559
1621
  if (since) {
1560
1622
  url.searchParams.set('_since', since);
1561
1623
  }
1562
- options.method = exportLevel ? 'GET' : 'POST';
1563
1624
  this.addFetchOptionsDefaults(options);
1564
1625
  const headers = options.headers;
1565
1626
  headers['Prefer'] = 'respond-async';
@@ -1623,10 +1684,10 @@ class MedplumClient extends EventTarget {
1623
1684
  }
1624
1685
  /**
1625
1686
  * Makes an HTTP request.
1626
- * @param {string} method
1627
- * @param {string} url
1628
- * @param {string=} contentType
1629
- * @param {Object=} body
1687
+ * @param method The HTTP method (GET, POST, etc).
1688
+ * @param url The target URL.
1689
+ * @param options Optional fetch request init options.
1690
+ * @returns The JSON content body if available.
1630
1691
  */
1631
1692
  async request(method, url, options = {}) {
1632
1693
  if (this.refreshPromise) {
@@ -1691,9 +1752,7 @@ class MedplumClient extends EventTarget {
1691
1752
  let resultResponse;
1692
1753
  const retryDelay = 200;
1693
1754
  while (checkStatus) {
1694
- const fetchOptions = {
1695
- method: 'GET',
1696
- };
1755
+ const fetchOptions = {};
1697
1756
  this.addFetchOptionsDefaults(fetchOptions);
1698
1757
  const statusResponse = await this.fetchWithRetry(statusUrl, fetchOptions);
1699
1758
  if (statusResponse.status !== 202) {
@@ -1769,7 +1828,7 @@ class MedplumClient extends EventTarget {
1769
1828
  if (this.accessToken) {
1770
1829
  headers['Authorization'] = 'Bearer ' + this.accessToken;
1771
1830
  }
1772
- if (this.basicAuth) {
1831
+ else if (this.basicAuth) {
1773
1832
  headers['Authorization'] = 'Basic ' + this.basicAuth;
1774
1833
  }
1775
1834
  if (!options.cache) {
@@ -1813,8 +1872,8 @@ class MedplumClient extends EventTarget {
1813
1872
  * Otherwise, calls unauthenticated callbacks and rejects.
1814
1873
  * @param method The HTTP method of the original request.
1815
1874
  * @param url The URL of the original request.
1816
- * @param contentType The content type of the original request.
1817
- * @param body The body of the original request.
1875
+ * @param options Optional fetch request init options.
1876
+ * @returns The result of the retry.
1818
1877
  */
1819
1878
  handleUnauthenticated(method, url, options) {
1820
1879
  if (this.refresh()) {
@@ -1830,6 +1889,7 @@ class MedplumClient extends EventTarget {
1830
1889
  * Starts a new PKCE flow.
1831
1890
  * These PKCE values are stateful, and must survive redirects and page refreshes.
1832
1891
  * @category Authentication
1892
+ * @returns The PKCE code challenge details.
1833
1893
  */
1834
1894
  async startPkce() {
1835
1895
  const pkceState = getRandomString();
@@ -1844,7 +1904,8 @@ class MedplumClient extends EventTarget {
1844
1904
  /**
1845
1905
  * Redirects the user to the login screen for authorization.
1846
1906
  * Clears all auth state including local storage and session storage.
1847
- * See: https://openid.net/specs/openid-connect-core-1_0.html#AuthorizationEndpoint
1907
+ * @param loginParams The authorization login parameters.
1908
+ * @see https://openid.net/specs/openid-connect-core-1_0.html#AuthorizationEndpoint
1848
1909
  */
1849
1910
  async requestAuthorization(loginParams) {
1850
1911
  const loginRequest = await this.ensureCodeChallenge(loginParams || {});
@@ -1863,6 +1924,7 @@ class MedplumClient extends EventTarget {
1863
1924
  * See: https://openid.net/specs/openid-connect-core-1_0.html#TokenRequest
1864
1925
  * @param code The authorization code received by URL parameter.
1865
1926
  * @param loginParams Optional login parameters.
1927
+ * @returns The user profile resource.
1866
1928
  * @category Authentication
1867
1929
  */
1868
1930
  processCode(code, loginParams) {
@@ -1881,7 +1943,8 @@ class MedplumClient extends EventTarget {
1881
1943
  }
1882
1944
  /**
1883
1945
  * Tries to refresh the auth tokens.
1884
- * See: https://openid.net/specs/openid-connect-core-1_0.html#RefreshTokens
1946
+ * @returns The refresh promise if available; otherwise undefined.
1947
+ * @see https://openid.net/specs/openid-connect-core-1_0.html#RefreshTokens
1885
1948
  */
1886
1949
  refresh() {
1887
1950
  if (this.refreshPromise) {
@@ -1934,7 +1997,6 @@ class MedplumClient extends EventTarget {
1934
1997
  * // Example Search
1935
1998
  * await medplum.searchResources('Patient')
1936
1999
  * ```
1937
- *
1938
2000
  * @category Authentication
1939
2001
  * @param clientId The client ID.
1940
2002
  * @param clientSecret The client secret.
@@ -1948,7 +2010,7 @@ class MedplumClient extends EventTarget {
1948
2010
  * Invite a user to a project.
1949
2011
  * @param projectId The project ID.
1950
2012
  * @param body The InviteBody.
1951
- * @returns Promise that returns an invite result or an operation outcome.
2013
+ * @returns Promise that returns a project membership or an operation outcome.
1952
2014
  */
1953
2015
  async invite(projectId, body) {
1954
2016
  return this.post('admin/projects/' + projectId + '/invite', body);
@@ -1957,17 +2019,29 @@ class MedplumClient extends EventTarget {
1957
2019
  * Makes a POST request to the tokens endpoint.
1958
2020
  * See: https://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint
1959
2021
  * @param formBody Token parameters in URL encoded format.
2022
+ * @returns The user profile resource.
1960
2023
  */
1961
2024
  async fetchTokens(formBody) {
1962
- const response = await this.fetch(this.tokenUrl, {
2025
+ const options = {
1963
2026
  method: 'POST',
1964
2027
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
1965
2028
  body: formBody,
1966
2029
  credentials: 'include',
1967
- });
2030
+ };
2031
+ const headers = options.headers;
2032
+ if (this.basicAuth) {
2033
+ headers['Authorization'] = `Basic ${this.basicAuth}`;
2034
+ }
2035
+ const response = await this.fetch(this.tokenUrl, options);
1968
2036
  if (!response.ok) {
1969
2037
  this.clearActiveLogin();
1970
- throw new Error('Failed to fetch tokens');
2038
+ try {
2039
+ const error = await response.json();
2040
+ throw new OperationOutcomeError(badRequest(error.error_description));
2041
+ }
2042
+ catch (err) {
2043
+ throw new OperationOutcomeError(badRequest('Failed to fetch tokens'), err);
2044
+ }
1971
2045
  }
1972
2046
  const tokens = await response.json();
1973
2047
  await this.verifyTokens(tokens);
@@ -1977,7 +2051,8 @@ class MedplumClient extends EventTarget {
1977
2051
  * Verifies the tokens received from the auth server.
1978
2052
  * Validates the JWT against the JWKS.
1979
2053
  * See: https://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint
1980
- * @param tokens
2054
+ * @param tokens The token response.
2055
+ * @returns Promise to complete.
1981
2056
  */
1982
2057
  async verifyTokens(tokens) {
1983
2058
  const token = tokens.access_token;
@@ -1988,7 +2063,14 @@ class MedplumClient extends EventTarget {
1988
2063
  throw new Error('Token expired');
1989
2064
  }
1990
2065
  // Verify app_client_id
1991
- if (this.clientId && tokenPayload.client_id !== this.clientId) {
2066
+ // external tokenPayload
2067
+ if (tokenPayload.cid) {
2068
+ if (tokenPayload.cid !== this.clientId) {
2069
+ this.clearActiveLogin();
2070
+ throw new Error('Token was not issued for this audience');
2071
+ }
2072
+ }
2073
+ else if (this.clientId && tokenPayload.client_id !== this.clientId) {
1992
2074
  this.clearActiveLogin();
1993
2075
  throw new Error('Token was not issued for this audience');
1994
2076
  }
@@ -2042,6 +2124,7 @@ function getDefaultFetch() {
2042
2124
  }
2043
2125
  /**
2044
2126
  * Returns the base URL for the current page.
2127
+ * @returns The window origin string.
2045
2128
  * @category HTTP
2046
2129
  */
2047
2130
  function getWindowOrigin() {