@medplum/core 2.0.23 → 2.0.24

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 (55) hide show
  1. package/dist/cjs/index.cjs +14080 -13352
  2. package/dist/cjs/index.cjs.map +1 -1
  3. package/dist/cjs/index.min.cjs +1 -1
  4. package/dist/esm/access.mjs +142 -0
  5. package/dist/esm/access.mjs.map +1 -0
  6. package/dist/esm/bundle.mjs +3 -3
  7. package/dist/esm/bundle.mjs.map +1 -1
  8. package/dist/esm/client.mjs +49 -25
  9. package/dist/esm/client.mjs.map +1 -1
  10. package/dist/esm/fhirlexer/parse.mjs.map +1 -1
  11. package/dist/esm/fhirmapper/parse.mjs +1 -1
  12. package/dist/esm/fhirmapper/parse.mjs.map +1 -1
  13. package/dist/esm/fhirpath/functions.mjs +2 -2
  14. package/dist/esm/fhirpath/functions.mjs.map +1 -1
  15. package/dist/esm/fhirpath/parse.mjs +2 -1
  16. package/dist/esm/fhirpath/parse.mjs.map +1 -1
  17. package/dist/esm/fhirpath/utils.mjs +4 -5
  18. package/dist/esm/fhirpath/utils.mjs.map +1 -1
  19. package/dist/esm/format.mjs +1 -1
  20. package/dist/esm/format.mjs.map +1 -1
  21. package/dist/esm/hl7.mjs +6 -6
  22. package/dist/esm/hl7.mjs.map +1 -1
  23. package/dist/esm/index.min.mjs +1 -1
  24. package/dist/esm/index.mjs +5 -2
  25. package/dist/esm/index.mjs.map +1 -1
  26. package/dist/esm/outcomes.mjs +38 -14
  27. package/dist/esm/outcomes.mjs.map +1 -1
  28. package/dist/esm/readablepromise.mjs +1 -1
  29. package/dist/esm/readablepromise.mjs.map +1 -1
  30. package/dist/esm/schema.mjs +1 -1
  31. package/dist/esm/schema.mjs.map +1 -1
  32. package/dist/esm/search/details.mjs +14 -16
  33. package/dist/esm/search/details.mjs.map +1 -1
  34. package/dist/esm/search/match.mjs +7 -5
  35. package/dist/esm/search/match.mjs.map +1 -1
  36. package/dist/esm/search/search.mjs +14 -5
  37. package/dist/esm/search/search.mjs.map +1 -1
  38. package/dist/esm/types.mjs +1 -1
  39. package/dist/esm/types.mjs.map +1 -1
  40. package/dist/esm/typeschema/types.mjs +278 -0
  41. package/dist/esm/typeschema/types.mjs.map +1 -0
  42. package/dist/esm/typeschema/validation.mjs +262 -0
  43. package/dist/esm/typeschema/validation.mjs.map +1 -0
  44. package/dist/esm/utils.mjs +3 -3
  45. package/dist/esm/utils.mjs.map +1 -1
  46. package/dist/types/access.d.ts +48 -0
  47. package/dist/types/client.d.ts +15 -20
  48. package/dist/types/fhirlexer/parse.d.ts +5 -4
  49. package/dist/types/fhirpath/functions.d.ts +1 -3
  50. package/dist/types/index.d.ts +3 -0
  51. package/dist/types/outcomes.d.ts +3 -1
  52. package/dist/types/search/search.d.ts +7 -0
  53. package/dist/types/typeschema/types.d.ts +5 -2
  54. package/dist/types/typeschema/validation.d.ts +2 -0
  55. package/package.json +1 -1
@@ -0,0 +1,142 @@
1
+ import { parseCriteriaAsSearchRequest } from './search/search.mjs';
2
+ import { matchesSearchRequest } from './search/match.mjs';
3
+
4
+ /**
5
+ * Public resource types are in the "public" project.
6
+ * They are available to all users.
7
+ */
8
+ const publicResourceTypes = [
9
+ 'CapabilityStatement',
10
+ 'CompartmentDefinition',
11
+ 'ImplementationGuide',
12
+ 'OperationDefinition',
13
+ 'SearchParameter',
14
+ 'StructureDefinition',
15
+ ];
16
+ /**
17
+ * Protected resource types are in the "medplum" project.
18
+ * Reading and writing is limited to the system account.
19
+ */
20
+ const protectedResourceTypes = ['DomainConfiguration', 'JsonWebKey', 'Login', 'User'];
21
+ /**
22
+ * Project admin resource types are special resources that are only
23
+ * accessible to project administrators.
24
+ */
25
+ const projectAdminResourceTypes = ['PasswordChangeRequest', 'Project', 'ProjectMembership'];
26
+ /**
27
+ * Determines if the current user can read the specified resource type.
28
+ * @param accessPolicy The access policy.
29
+ * @param resourceType The resource type.
30
+ * @returns True if the current user can read the specified resource type.
31
+ */
32
+ function canReadResourceType(accessPolicy, resourceType) {
33
+ if (accessPolicy.resource) {
34
+ for (const resourcePolicy of accessPolicy.resource) {
35
+ if (matchesAccessPolicyResourceType(resourcePolicy.resourceType, resourceType)) {
36
+ return true;
37
+ }
38
+ }
39
+ }
40
+ return false;
41
+ }
42
+ /**
43
+ * Determines if the current user can write the specified resource type.
44
+ * This is a preliminary check before evaluating a write operation in depth.
45
+ * If a user cannot write a resource type at all, then don't bother looking up previous versions.
46
+ * @param accessPolicy The access policy.
47
+ * @param resourceType The resource type.
48
+ * @returns True if the current user can write the specified resource type.
49
+ */
50
+ function canWriteResourceType(accessPolicy, resourceType) {
51
+ if (protectedResourceTypes.includes(resourceType)) {
52
+ return false;
53
+ }
54
+ if (publicResourceTypes.includes(resourceType)) {
55
+ return false;
56
+ }
57
+ if (accessPolicy.resource) {
58
+ for (const resourcePolicy of accessPolicy.resource) {
59
+ if (matchesAccessPolicyResourceType(resourcePolicy.resourceType, resourceType) && !resourcePolicy.readonly) {
60
+ return true;
61
+ }
62
+ }
63
+ }
64
+ return false;
65
+ }
66
+ /**
67
+ * Determines if the current user can write the specified resource.
68
+ * This is a more in-depth check after building the candidate result of a write operation.
69
+ * @param accessPolicy The access policy.
70
+ * @param resource The resource.
71
+ * @returns True if the current user can write the specified resource type.
72
+ */
73
+ function canWriteResource(accessPolicy, resource) {
74
+ const resourceType = resource.resourceType;
75
+ if (!canWriteResourceType(accessPolicy, resourceType)) {
76
+ return false;
77
+ }
78
+ return matchesAccessPolicy(accessPolicy, resource, false);
79
+ }
80
+ /**
81
+ * Returns true if the resource satisfies the current access policy.
82
+ * @param accessPolicy The access policy.
83
+ * @param resource The resource.
84
+ * @param readonlyMode True if the resource is being read.
85
+ * @returns True if the resource matches the access policy.
86
+ */
87
+ function matchesAccessPolicy(accessPolicy, resource, readonlyMode) {
88
+ if (accessPolicy.resource) {
89
+ for (const resourcePolicy of accessPolicy.resource) {
90
+ if (matchesAccessPolicyResourcePolicy(resource, resourcePolicy, readonlyMode)) {
91
+ return true;
92
+ }
93
+ }
94
+ }
95
+ return false;
96
+ }
97
+ /**
98
+ * Returns true if the resource satisfies the specified access policy resource policy.
99
+ * @param resource The resource.
100
+ * @param resourcePolicy One per-resource policy section from the access policy.
101
+ * @param readonlyMode True if the resource is being read.
102
+ * @returns True if the resource matches the access policy.
103
+ */
104
+ function matchesAccessPolicyResourcePolicy(resource, resourcePolicy, readonlyMode) {
105
+ const resourceType = resource.resourceType;
106
+ if (!matchesAccessPolicyResourceType(resourcePolicy.resourceType, resourceType)) {
107
+ return false;
108
+ }
109
+ if (!readonlyMode && resourcePolicy.readonly) {
110
+ return false;
111
+ }
112
+ if (resourcePolicy.compartment &&
113
+ !resource.meta?.compartment?.find((c) => c.reference === resourcePolicy.compartment?.reference)) {
114
+ // Deprecated - to be removed
115
+ return false;
116
+ }
117
+ if (resourcePolicy.criteria &&
118
+ !matchesSearchRequest(resource, parseCriteriaAsSearchRequest(resourcePolicy.criteria))) {
119
+ return false;
120
+ }
121
+ return true;
122
+ }
123
+ /**
124
+ * Returns true if the resource type matches the access policy resource type.
125
+ * @param accessPolicyResourceType The resource type from the access policy.
126
+ * @param resourceType The candidate resource resource type.
127
+ * @returns True if the resource type matches the access policy resource type.
128
+ */
129
+ function matchesAccessPolicyResourceType(accessPolicyResourceType, resourceType) {
130
+ if (accessPolicyResourceType === resourceType) {
131
+ return true;
132
+ }
133
+ if (accessPolicyResourceType === '*' && !projectAdminResourceTypes.includes(resourceType)) {
134
+ // Project admin resource types are not allowed to be wildcarded
135
+ // Project admin resource types must be explicitly included
136
+ return true;
137
+ }
138
+ return false;
139
+ }
140
+
141
+ export { canReadResourceType, canWriteResource, canWriteResourceType, matchesAccessPolicy, projectAdminResourceTypes, protectedResourceTypes, publicResourceTypes };
142
+ //# sourceMappingURL=access.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"access.mjs","sources":["../../src/access.ts"],"sourcesContent":["import { AccessPolicy, AccessPolicyResource, Resource, ResourceType } from '@medplum/fhirtypes';\nimport { parseCriteriaAsSearchRequest } from './search/search';\nimport { matchesSearchRequest } from './search/match';\n\n/**\n * Public resource types are in the \"public\" project.\n * They are available to all users.\n */\nexport const publicResourceTypes = [\n 'CapabilityStatement',\n 'CompartmentDefinition',\n 'ImplementationGuide',\n 'OperationDefinition',\n 'SearchParameter',\n 'StructureDefinition',\n];\n\n/**\n * Protected resource types are in the \"medplum\" project.\n * Reading and writing is limited to the system account.\n */\nexport const protectedResourceTypes = ['DomainConfiguration', 'JsonWebKey', 'Login', 'User'];\n\n/**\n * Project admin resource types are special resources that are only\n * accessible to project administrators.\n */\nexport const projectAdminResourceTypes = ['PasswordChangeRequest', 'Project', 'ProjectMembership'];\n\n/**\n * Determines if the current user can read the specified resource type.\n * @param accessPolicy The access policy.\n * @param resourceType The resource type.\n * @returns True if the current user can read the specified resource type.\n */\nexport function canReadResourceType(accessPolicy: AccessPolicy, resourceType: ResourceType): boolean {\n if (accessPolicy.resource) {\n for (const resourcePolicy of accessPolicy.resource) {\n if (matchesAccessPolicyResourceType(resourcePolicy.resourceType, resourceType)) {\n return true;\n }\n }\n }\n return false;\n}\n\n/**\n * Determines if the current user can write the specified resource type.\n * This is a preliminary check before evaluating a write operation in depth.\n * If a user cannot write a resource type at all, then don't bother looking up previous versions.\n * @param accessPolicy The access policy.\n * @param resourceType The resource type.\n * @returns True if the current user can write the specified resource type.\n */\nexport function canWriteResourceType(accessPolicy: AccessPolicy, resourceType: ResourceType): boolean {\n if (protectedResourceTypes.includes(resourceType)) {\n return false;\n }\n if (publicResourceTypes.includes(resourceType)) {\n return false;\n }\n if (accessPolicy.resource) {\n for (const resourcePolicy of accessPolicy.resource) {\n if (matchesAccessPolicyResourceType(resourcePolicy.resourceType, resourceType) && !resourcePolicy.readonly) {\n return true;\n }\n }\n }\n return false;\n}\n\n/**\n * Determines if the current user can write the specified resource.\n * This is a more in-depth check after building the candidate result of a write operation.\n * @param accessPolicy The access policy.\n * @param resource The resource.\n * @returns True if the current user can write the specified resource type.\n */\nexport function canWriteResource(accessPolicy: AccessPolicy, resource: Resource): boolean {\n const resourceType = resource.resourceType;\n if (!canWriteResourceType(accessPolicy, resourceType)) {\n return false;\n }\n return matchesAccessPolicy(accessPolicy, resource, false);\n}\n\n/**\n * Returns true if the resource satisfies the current access policy.\n * @param accessPolicy The access policy.\n * @param resource The resource.\n * @param readonlyMode True if the resource is being read.\n * @returns True if the resource matches the access policy.\n */\nexport function matchesAccessPolicy(accessPolicy: AccessPolicy, resource: Resource, readonlyMode: boolean): boolean {\n if (accessPolicy.resource) {\n for (const resourcePolicy of accessPolicy.resource) {\n if (matchesAccessPolicyResourcePolicy(resource, resourcePolicy, readonlyMode)) {\n return true;\n }\n }\n }\n return false;\n}\n\n/**\n * Returns true if the resource satisfies the specified access policy resource policy.\n * @param resource The resource.\n * @param resourcePolicy One per-resource policy section from the access policy.\n * @param readonlyMode True if the resource is being read.\n * @returns True if the resource matches the access policy.\n */\nfunction matchesAccessPolicyResourcePolicy(\n resource: Resource,\n resourcePolicy: AccessPolicyResource,\n readonlyMode: boolean\n): boolean {\n const resourceType = resource.resourceType;\n if (!matchesAccessPolicyResourceType(resourcePolicy.resourceType, resourceType)) {\n return false;\n }\n if (!readonlyMode && resourcePolicy.readonly) {\n return false;\n }\n if (\n resourcePolicy.compartment &&\n !resource.meta?.compartment?.find((c) => c.reference === resourcePolicy.compartment?.reference)\n ) {\n // Deprecated - to be removed\n return false;\n }\n if (\n resourcePolicy.criteria &&\n !matchesSearchRequest(resource, parseCriteriaAsSearchRequest(resourcePolicy.criteria))\n ) {\n return false;\n }\n return true;\n}\n\n/**\n * Returns true if the resource type matches the access policy resource type.\n * @param accessPolicyResourceType The resource type from the access policy.\n * @param resourceType The candidate resource resource type.\n * @returns True if the resource type matches the access policy resource type.\n */\nfunction matchesAccessPolicyResourceType(\n accessPolicyResourceType: string | undefined,\n resourceType: ResourceType\n): boolean {\n if (accessPolicyResourceType === resourceType) {\n return true;\n }\n if (accessPolicyResourceType === '*' && !projectAdminResourceTypes.includes(resourceType)) {\n // Project admin resource types are not allowed to be wildcarded\n // Project admin resource types must be explicitly included\n return true;\n }\n return false;\n}\n"],"names":[],"mappings":"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"}
@@ -9,9 +9,9 @@
9
9
  */
10
10
  function convertToTransactionBundle(bundle) {
11
11
  for (const entry of bundle.entry || []) {
12
- delete entry?.resource?.meta;
13
- entry.fullUrl = 'urn:uuid:' + entry?.resource?.id;
14
- delete entry?.resource?.id;
12
+ delete entry.resource?.meta;
13
+ entry.fullUrl = 'urn:uuid:' + entry.resource?.id;
14
+ delete entry.resource?.id;
15
15
  }
16
16
  const input = bundle.entry;
17
17
  const jsonString = JSON.stringify({
@@ -1 +1 @@
1
- {"version":3,"file":"bundle.mjs","sources":["../../src/bundle.ts"],"sourcesContent":["import { Bundle } from '@medplum/fhirtypes';\n\n/**\n * More on Bundles can be found here\n * http://hl7.org/fhir/R4/bundle.html\n */\n\n/**\n * Takes a bundle and creates a Transaction Type bundle\n * @param bundle The Bundle object that we'll receive from the search query\n * @returns transaction type bundle\n */\nexport function convertToTransactionBundle(bundle: Bundle): Bundle {\n for (const entry of bundle.entry || []) {\n delete entry?.resource?.meta;\n entry.fullUrl = 'urn:uuid:' + entry?.resource?.id;\n delete entry?.resource?.id;\n }\n const input = bundle.entry;\n const jsonString = JSON.stringify(\n {\n resourceType: 'Bundle',\n type: 'transaction',\n entry: input?.map((entry: any) => ({\n fullUrl: entry.fullUrl,\n request: { method: 'POST', url: entry.resource.resourceType },\n resource: entry.resource,\n })),\n },\n replacer,\n 2\n );\n return JSON.parse(jsonString);\n}\n\nfunction replacer(key: string, value: string): string {\n if (key === 'reference' && typeof value === 'string' && value.includes('/')) {\n return 'urn:uuid:' + value.split('/')[1];\n }\n return value;\n}\n"],"names":[],"mappings":"AAEA;;;AAGG;AAEH;;;;AAIG;AACG,SAAU,0BAA0B,CAAC,MAAc,EAAA;IACvD,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,KAAK,IAAI,EAAE,EAAE;AACtC,QAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,IAAI,CAAC;QAC7B,KAAK,CAAC,OAAO,GAAG,WAAW,GAAG,KAAK,EAAE,QAAQ,EAAE,EAAE,CAAC;AAClD,QAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,EAAE,CAAC;AAC5B,KAAA;AACD,IAAA,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;AAC3B,IAAA,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,CAC/B;AACE,QAAA,YAAY,EAAE,QAAQ;AACtB,QAAA,IAAI,EAAE,aAAa;QACnB,KAAK,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC,KAAU,MAAM;YACjC,OAAO,EAAE,KAAK,CAAC,OAAO;AACtB,YAAA,OAAO,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,CAAC,QAAQ,CAAC,YAAY,EAAE;YAC7D,QAAQ,EAAE,KAAK,CAAC,QAAQ;AACzB,SAAA,CAAC,CAAC;AACJ,KAAA,EACD,QAAQ,EACR,CAAC,CACF,CAAC;AACF,IAAA,OAAO,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;AAChC,CAAC;AAED,SAAS,QAAQ,CAAC,GAAW,EAAE,KAAa,EAAA;AAC1C,IAAA,IAAI,GAAG,KAAK,WAAW,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE;QAC3E,OAAO,WAAW,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;AAC1C,KAAA;AACD,IAAA,OAAO,KAAK,CAAC;AACf;;;;"}
1
+ {"version":3,"file":"bundle.mjs","sources":["../../src/bundle.ts"],"sourcesContent":["import { Bundle } from '@medplum/fhirtypes';\n\n/**\n * More on Bundles can be found here\n * http://hl7.org/fhir/R4/bundle.html\n */\n\n/**\n * Takes a bundle and creates a Transaction Type bundle\n * @param bundle The Bundle object that we'll receive from the search query\n * @returns transaction type bundle\n */\nexport function convertToTransactionBundle(bundle: Bundle): Bundle {\n for (const entry of bundle.entry || []) {\n delete entry.resource?.meta;\n entry.fullUrl = 'urn:uuid:' + entry.resource?.id;\n delete entry.resource?.id;\n }\n const input = bundle.entry;\n const jsonString = JSON.stringify(\n {\n resourceType: 'Bundle',\n type: 'transaction',\n entry: input?.map((entry: any) => ({\n fullUrl: entry.fullUrl,\n request: { method: 'POST', url: entry.resource.resourceType },\n resource: entry.resource,\n })),\n },\n replacer,\n 2\n );\n return JSON.parse(jsonString);\n}\n\nfunction replacer(key: string, value: string): string {\n if (key === 'reference' && typeof value === 'string' && value.includes('/')) {\n return 'urn:uuid:' + value.split('/')[1];\n }\n return value;\n}\n"],"names":[],"mappings":"AAEA;;;AAGG;AAEH;;;;AAIG;AACG,SAAU,0BAA0B,CAAC,MAAc,EAAA;IACvD,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,KAAK,IAAI,EAAE,EAAE;AACtC,QAAA,OAAO,KAAK,CAAC,QAAQ,EAAE,IAAI,CAAC;QAC5B,KAAK,CAAC,OAAO,GAAG,WAAW,GAAG,KAAK,CAAC,QAAQ,EAAE,EAAE,CAAC;AACjD,QAAA,OAAO,KAAK,CAAC,QAAQ,EAAE,EAAE,CAAC;AAC3B,KAAA;AACD,IAAA,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;AAC3B,IAAA,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,CAC/B;AACE,QAAA,YAAY,EAAE,QAAQ;AACtB,QAAA,IAAI,EAAE,aAAa;QACnB,KAAK,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC,KAAU,MAAM;YACjC,OAAO,EAAE,KAAK,CAAC,OAAO;AACtB,YAAA,OAAO,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,CAAC,QAAQ,CAAC,YAAY,EAAE;YAC7D,QAAQ,EAAE,KAAK,CAAC,QAAQ;AACzB,SAAA,CAAC,CAAC;AACJ,KAAA,EACD,QAAQ,EACR,CAAC,CACF,CAAC;AACF,IAAA,OAAO,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;AAChC,CAAC;AAED,SAAS,QAAQ,CAAC,GAAW,EAAE,KAAa,EAAA;AAC1C,IAAA,IAAI,GAAG,KAAK,WAAW,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE;QAC3E,OAAO,WAAW,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;AAC1C,KAAA;AACD,IAAA,OAAO,KAAK,CAAC;AACf;;;;"}
@@ -3,7 +3,7 @@ import { LRUCache } from './cache.mjs';
3
3
  import { getRandomString, encryptSHA256 } from './crypto.mjs';
4
4
  import { EventTarget } from './eventtarget.mjs';
5
5
  import { parseJWTPayload } from './jwt.mjs';
6
- import { OperationOutcomeError, notFound, normalizeOperationOutcome, isOk, badRequest } from './outcomes.mjs';
6
+ import { isOperationOutcome, OperationOutcomeError, notFound, normalizeOperationOutcome, isOk, badRequest } from './outcomes.mjs';
7
7
  import { ReadablePromise } from './readablepromise.mjs';
8
8
  import { ClientStorage } from './storage.mjs';
9
9
  import { globalSchema, indexStructureDefinition, indexSearchParameter } from './types.mjs';
@@ -11,7 +11,7 @@ import { createReference, arrayBufferToBase64 } from './utils.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.23-b244eeae" ;
14
+ const MEDPLUM_VERSION = "2.0.24-8a2dc16" ;
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
@@ -107,7 +107,7 @@ class MedplumClient extends EventTarget {
107
107
  throw new Error('Base URL must start with http or https');
108
108
  }
109
109
  }
110
- this.fetch = options?.fetch || getDefaultFetch();
110
+ this.fetch = options?.fetch ?? getDefaultFetch();
111
111
  this.storage = options?.storage || new ClientStorage();
112
112
  this.createPdfImpl = options?.createPdf;
113
113
  this.baseUrl = ensureTrailingSlash(options?.baseUrl) || DEFAULT_BASE_URL;
@@ -125,7 +125,7 @@ class MedplumClient extends EventTarget {
125
125
  this.requestCache = undefined;
126
126
  }
127
127
  if (options?.autoBatchTime) {
128
- this.autoBatchTime = options?.autoBatchTime ?? 0;
128
+ this.autoBatchTime = options.autoBatchTime ?? 0;
129
129
  this.autoBatchQueue = [];
130
130
  }
131
131
  else {
@@ -690,12 +690,12 @@ class MedplumClient extends EventTarget {
690
690
  while (url) {
691
691
  const searchParams = new URL(url).searchParams;
692
692
  const bundle = await this.search(resourceType, searchParams, options);
693
- const nextLink = bundle?.link?.find((link) => link.relation === 'next');
694
- if (!bundle?.entry?.length && !nextLink) {
693
+ const nextLink = bundle.link?.find((link) => link.relation === 'next');
694
+ if (!bundle.entry?.length && !nextLink) {
695
695
  break;
696
696
  }
697
- yield bundle?.entry?.map((e) => e.resource) ?? [];
698
- url = nextLink?.url ? new URL(nextLink?.url) : undefined;
697
+ yield bundle.entry?.map((e) => e.resource) ?? [];
698
+ url = nextLink?.url ? new URL(nextLink.url) : undefined;
699
699
  }
700
700
  }
701
701
  /**
@@ -722,7 +722,7 @@ class MedplumClient extends EventTarget {
722
722
  */
723
723
  getCached(resourceType, id) {
724
724
  const cached = this.requestCache?.get(this.fhirUrl(resourceType, id).toString())?.value;
725
- return cached && cached.isOk() ? cached.read() : undefined;
725
+ return cached?.isOk() ? cached.read() : undefined;
726
726
  }
727
727
  /**
728
728
  * Returns a cached resource if it is available.
@@ -784,7 +784,7 @@ class MedplumClient extends EventTarget {
784
784
  * @returns The resource if available; undefined otherwise.
785
785
  */
786
786
  readReference(reference, options) {
787
- const refString = reference?.reference;
787
+ const refString = reference.reference;
788
788
  if (!refString) {
789
789
  return new ReadablePromise(Promise.reject(new Error('Missing reference')));
790
790
  }
@@ -1505,7 +1505,7 @@ class MedplumClient extends EventTarget {
1505
1505
  * @category Authentication
1506
1506
  */
1507
1507
  isSuperAdmin() {
1508
- return !!this.sessionDetails?.project?.superAdmin;
1508
+ return !!this.sessionDetails?.project.superAdmin;
1509
1509
  }
1510
1510
  /**
1511
1511
  * Returns true if the current user is authenticated as a project admin.
@@ -1513,7 +1513,7 @@ class MedplumClient extends EventTarget {
1513
1513
  * @category Authentication
1514
1514
  */
1515
1515
  isProjectAdmin() {
1516
- return !!this.sessionDetails?.membership?.admin;
1516
+ return !!this.sessionDetails?.membership.admin;
1517
1517
  }
1518
1518
  /**
1519
1519
  * Returns the current project if available.
@@ -1612,7 +1612,9 @@ class MedplumClient extends EventTarget {
1612
1612
  * @param options Optional fetch options.
1613
1613
  * @returns Bulk Data Response containing links to Bulk Data files. See "Response - Complete Status" for full details: https://build.fhir.org/ig/HL7/bulk-data/export.html#response---complete-status
1614
1614
  */
1615
- async bulkExport(exportLevel = '', resourceTypes, since, options = {}) {
1615
+ async bulkExport(
1616
+ //eslint-disable-next-line default-param-last
1617
+ exportLevel = '', resourceTypes, since, options) {
1616
1618
  const fhirPath = exportLevel ? `${exportLevel}/` : exportLevel;
1617
1619
  const url = this.fhirUrl(`${fhirPath}$export`);
1618
1620
  if (resourceTypes) {
@@ -1621,17 +1623,35 @@ class MedplumClient extends EventTarget {
1621
1623
  if (since) {
1622
1624
  url.searchParams.set('_since', since);
1623
1625
  }
1626
+ return this.startAsyncRequest(url.toString(), options);
1627
+ }
1628
+ /**
1629
+ * Starts an async request following the FHIR "Asynchronous Request Pattern".
1630
+ * See: https://hl7.org/fhir/r4/async.html
1631
+ * @param url The URL to request.
1632
+ * @param options Optional fetch options.
1633
+ * @returns The response body.
1634
+ */
1635
+ async startAsyncRequest(url, options = {}) {
1624
1636
  this.addFetchOptionsDefaults(options);
1625
1637
  const headers = options.headers;
1626
1638
  headers['Prefer'] = 'respond-async';
1627
- const response = await this.fetchWithRetry(url.toString(), options);
1639
+ const response = await this.fetchWithRetry(url, options);
1628
1640
  if (response.status === 202) {
1641
+ // Accepted content location can come from multiple sources
1642
+ // The authoritative source is the "Content-Location" HTTP header.
1629
1643
  const contentLocation = response.headers.get('content-location');
1630
1644
  if (contentLocation) {
1631
- return await this.pollStatus(contentLocation);
1645
+ return this.pollStatus(contentLocation);
1646
+ }
1647
+ // However, "Content-Location" may not be available due to CORS limitations.
1648
+ // In this case, we use the OperationOutcome.diagnostics field.
1649
+ const body = await response.json();
1650
+ if (isOperationOutcome(body) && body.issue?.[0]?.diagnostics) {
1651
+ return this.pollStatus(body.issue[0].diagnostics);
1632
1652
  }
1633
1653
  }
1634
- return await this.parseResponse(response, 'POST', url.toString());
1654
+ return this.parseResponse(response, 'POST', url);
1635
1655
  }
1636
1656
  //
1637
1657
  // Private helpers
@@ -1693,13 +1713,10 @@ class MedplumClient extends EventTarget {
1693
1713
  if (this.refreshPromise) {
1694
1714
  await this.refreshPromise;
1695
1715
  }
1696
- if (!url.startsWith('http')) {
1697
- url = this.baseUrl + url;
1698
- }
1699
1716
  options.method = method;
1700
1717
  this.addFetchOptionsDefaults(options);
1701
1718
  const response = await this.fetchWithRetry(url, options);
1702
- return await this.parseResponse(response, method, url, options);
1719
+ return this.parseResponse(response, method, url, options);
1703
1720
  }
1704
1721
  async parseResponse(response, method, url, options = {}) {
1705
1722
  if (response.status === 401) {
@@ -1730,6 +1747,9 @@ class MedplumClient extends EventTarget {
1730
1747
  return obj;
1731
1748
  }
1732
1749
  async fetchWithRetry(url, options) {
1750
+ if (!url.startsWith('http')) {
1751
+ url = this.baseUrl + url;
1752
+ }
1733
1753
  const maxRetries = 3;
1734
1754
  const retryDelay = 200;
1735
1755
  let response = undefined;
@@ -1743,14 +1763,16 @@ class MedplumClient extends EventTarget {
1743
1763
  catch (err) {
1744
1764
  this.retryCatch(retry, maxRetries, err);
1745
1765
  }
1746
- await new Promise((resolve) => setTimeout(resolve, retryDelay));
1766
+ await new Promise((resolve) => {
1767
+ setTimeout(resolve, retryDelay);
1768
+ });
1747
1769
  }
1748
1770
  return response;
1749
1771
  }
1750
1772
  async pollStatus(statusUrl) {
1751
1773
  let checkStatus = true;
1752
1774
  let resultResponse;
1753
- const retryDelay = 200;
1775
+ const retryDelay = 2000;
1754
1776
  while (checkStatus) {
1755
1777
  const fetchOptions = {};
1756
1778
  this.addFetchOptionsDefaults(fetchOptions);
@@ -1759,9 +1781,11 @@ class MedplumClient extends EventTarget {
1759
1781
  checkStatus = false;
1760
1782
  resultResponse = statusResponse;
1761
1783
  }
1762
- await new Promise((resolve) => setTimeout(resolve, retryDelay));
1784
+ await new Promise((resolve) => {
1785
+ setTimeout(resolve, retryDelay);
1786
+ });
1763
1787
  }
1764
- return await this.parseResponse(resultResponse, 'POST', statusUrl);
1788
+ return this.parseResponse(resultResponse, 'POST', statusUrl);
1765
1789
  }
1766
1790
  /**
1767
1791
  * Executes a batch of requests that were automatically batched together.
@@ -1897,7 +1921,7 @@ class MedplumClient extends EventTarget {
1897
1921
  const codeVerifier = getRandomString();
1898
1922
  sessionStorage.setItem('codeVerifier', codeVerifier);
1899
1923
  const arrayHash = await encryptSHA256(codeVerifier);
1900
- const codeChallenge = arrayBufferToBase64(arrayHash).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
1924
+ const codeChallenge = arrayBufferToBase64(arrayHash).replaceAll('+', '-').replaceAll('/', '_').replaceAll('=', '');
1901
1925
  sessionStorage.setItem('codeChallenge', codeChallenge);
1902
1926
  return { codeChallengeMethod: 'S256', codeChallenge };
1903
1927
  }