@medplum/core 2.0.22 → 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 (62) hide show
  1. package/dist/cjs/index.cjs +14080 -13218
  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 +111 -34
  9. package/dist/esm/client.mjs.map +1 -1
  10. package/dist/esm/fhirlexer/parse.mjs.map +1 -1
  11. package/dist/esm/fhirlexer/tokenize.mjs +2 -2
  12. package/dist/esm/fhirlexer/tokenize.mjs.map +1 -1
  13. package/dist/esm/fhirmapper/parse.mjs +1 -1
  14. package/dist/esm/fhirmapper/parse.mjs.map +1 -1
  15. package/dist/esm/fhirpath/atoms.mjs +63 -56
  16. package/dist/esm/fhirpath/atoms.mjs.map +1 -1
  17. package/dist/esm/fhirpath/functions.mjs +196 -128
  18. package/dist/esm/fhirpath/functions.mjs.map +1 -1
  19. package/dist/esm/fhirpath/parse.mjs +6 -3
  20. package/dist/esm/fhirpath/parse.mjs.map +1 -1
  21. package/dist/esm/fhirpath/utils.mjs +4 -5
  22. package/dist/esm/fhirpath/utils.mjs.map +1 -1
  23. package/dist/esm/format.mjs +1 -1
  24. package/dist/esm/format.mjs.map +1 -1
  25. package/dist/esm/hl7.mjs +6 -6
  26. package/dist/esm/hl7.mjs.map +1 -1
  27. package/dist/esm/index.min.mjs +1 -1
  28. package/dist/esm/index.mjs +5 -2
  29. package/dist/esm/index.mjs.map +1 -1
  30. package/dist/esm/outcomes.mjs +51 -24
  31. package/dist/esm/outcomes.mjs.map +1 -1
  32. package/dist/esm/readablepromise.mjs +1 -1
  33. package/dist/esm/readablepromise.mjs.map +1 -1
  34. package/dist/esm/schema.mjs +1 -1
  35. package/dist/esm/schema.mjs.map +1 -1
  36. package/dist/esm/search/details.mjs +18 -20
  37. package/dist/esm/search/details.mjs.map +1 -1
  38. package/dist/esm/search/match.mjs +7 -5
  39. package/dist/esm/search/match.mjs.map +1 -1
  40. package/dist/esm/search/search.mjs +14 -5
  41. package/dist/esm/search/search.mjs.map +1 -1
  42. package/dist/esm/types.mjs +1 -1
  43. package/dist/esm/types.mjs.map +1 -1
  44. package/dist/esm/typeschema/types.mjs +278 -0
  45. package/dist/esm/typeschema/types.mjs.map +1 -0
  46. package/dist/esm/typeschema/validation.mjs +262 -0
  47. package/dist/esm/typeschema/validation.mjs.map +1 -0
  48. package/dist/esm/utils.mjs +3 -3
  49. package/dist/esm/utils.mjs.map +1 -1
  50. package/dist/types/access.d.ts +48 -0
  51. package/dist/types/client.d.ts +69 -25
  52. package/dist/types/fhirlexer/parse.d.ts +12 -7
  53. package/dist/types/fhirpath/atoms.d.ts +21 -21
  54. package/dist/types/fhirpath/functions.d.ts +2 -4
  55. package/dist/types/fhirpath/parse.d.ts +2 -1
  56. package/dist/types/index.d.ts +3 -0
  57. package/dist/types/outcomes.d.ts +10 -2
  58. package/dist/types/search/search.d.ts +7 -0
  59. package/dist/types/typeschema/types.d.ts +5 -2
  60. package/dist/types/typeschema/validation.d.ts +2 -0
  61. package/dist/types/utils.d.ts +1 -8
  62. 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 } 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.22-f51ac45a" ;
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 {
@@ -150,6 +150,16 @@ class MedplumClient extends EventTarget {
150
150
  getBaseUrl() {
151
151
  return this.baseUrl;
152
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
+ }
153
163
  /**
154
164
  * Clears all auth state including local storage and session storage.
155
165
  * @category Authentication
@@ -171,8 +181,7 @@ class MedplumClient extends EventTarget {
171
181
  this.requestCache?.clear();
172
182
  this.accessToken = undefined;
173
183
  this.refreshToken = undefined;
174
- this.profile = undefined;
175
- this.config = undefined;
184
+ this.sessionDetails = undefined;
176
185
  this.dispatchEvent({ type: 'change' });
177
186
  }
178
187
  /**
@@ -681,12 +690,12 @@ class MedplumClient extends EventTarget {
681
690
  while (url) {
682
691
  const searchParams = new URL(url).searchParams;
683
692
  const bundle = await this.search(resourceType, searchParams, options);
684
- const nextLink = bundle?.link?.find((link) => link.relation === 'next');
685
- if (!bundle?.entry?.length && !nextLink) {
693
+ const nextLink = bundle.link?.find((link) => link.relation === 'next');
694
+ if (!bundle.entry?.length && !nextLink) {
686
695
  break;
687
696
  }
688
- yield bundle?.entry?.map((e) => e.resource) ?? [];
689
- 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;
690
699
  }
691
700
  }
692
701
  /**
@@ -713,7 +722,7 @@ class MedplumClient extends EventTarget {
713
722
  */
714
723
  getCached(resourceType, id) {
715
724
  const cached = this.requestCache?.get(this.fhirUrl(resourceType, id).toString())?.value;
716
- return cached && cached.isOk() ? cached.read() : undefined;
725
+ return cached?.isOk() ? cached.read() : undefined;
717
726
  }
718
727
  /**
719
728
  * Returns a cached resource if it is available.
@@ -775,7 +784,7 @@ class MedplumClient extends EventTarget {
775
784
  * @returns The resource if available; undefined otherwise.
776
785
  */
777
786
  readReference(reference, options) {
778
- const refString = reference?.reference;
787
+ const refString = reference.reference;
779
788
  if (!refString) {
780
789
  return new ReadablePromise(Promise.reject(new Error('Missing reference')));
781
790
  }
@@ -1451,8 +1460,7 @@ class MedplumClient extends EventTarget {
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
  /**
1458
1466
  * Returns the list of available logins.
@@ -1475,10 +1483,9 @@ class MedplumClient extends EventTarget {
1475
1483
  this.get('auth/me')
1476
1484
  .then((result) => {
1477
1485
  this.profilePromise = undefined;
1478
- this.profile = result.profile;
1479
- this.config = result.config;
1486
+ this.sessionDetails = result;
1480
1487
  this.dispatchEvent({ type: 'change' });
1481
- resolve(this.profile);
1488
+ resolve(result.profile);
1482
1489
  })
1483
1490
  .catch(reject);
1484
1491
  });
@@ -1492,6 +1499,38 @@ class MedplumClient extends EventTarget {
1492
1499
  isLoading() {
1493
1500
  return !!this.profilePromise;
1494
1501
  }
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
+ }
1495
1534
  /**
1496
1535
  * Returns the current user profile resource if available.
1497
1536
  * This method does not wait for loading promises.
@@ -1499,7 +1538,7 @@ class MedplumClient extends EventTarget {
1499
1538
  * @category User Profile
1500
1539
  */
1501
1540
  getProfile() {
1502
- return this.profile;
1541
+ return this.sessionDetails?.profile;
1503
1542
  }
1504
1543
  /**
1505
1544
  * Returns the current user profile resource if available.
@@ -1519,7 +1558,15 @@ class MedplumClient extends EventTarget {
1519
1558
  * @category User Profile
1520
1559
  */
1521
1560
  getUserConfiguration() {
1522
- 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;
1523
1570
  }
1524
1571
  /**
1525
1572
  * Downloads the URL as a blob.
@@ -1565,7 +1612,9 @@ class MedplumClient extends EventTarget {
1565
1612
  * @param options Optional fetch options.
1566
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
1567
1614
  */
1568
- async bulkExport(exportLevel = '', resourceTypes, since, options = {}) {
1615
+ async bulkExport(
1616
+ //eslint-disable-next-line default-param-last
1617
+ exportLevel = '', resourceTypes, since, options) {
1569
1618
  const fhirPath = exportLevel ? `${exportLevel}/` : exportLevel;
1570
1619
  const url = this.fhirUrl(`${fhirPath}$export`);
1571
1620
  if (resourceTypes) {
@@ -1574,17 +1623,35 @@ class MedplumClient extends EventTarget {
1574
1623
  if (since) {
1575
1624
  url.searchParams.set('_since', since);
1576
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 = {}) {
1577
1636
  this.addFetchOptionsDefaults(options);
1578
1637
  const headers = options.headers;
1579
1638
  headers['Prefer'] = 'respond-async';
1580
- const response = await this.fetchWithRetry(url.toString(), options);
1639
+ const response = await this.fetchWithRetry(url, options);
1581
1640
  if (response.status === 202) {
1641
+ // Accepted content location can come from multiple sources
1642
+ // The authoritative source is the "Content-Location" HTTP header.
1582
1643
  const contentLocation = response.headers.get('content-location');
1583
1644
  if (contentLocation) {
1584
- 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);
1585
1652
  }
1586
1653
  }
1587
- return await this.parseResponse(response, 'POST', url.toString());
1654
+ return this.parseResponse(response, 'POST', url);
1588
1655
  }
1589
1656
  //
1590
1657
  // Private helpers
@@ -1646,13 +1713,10 @@ class MedplumClient extends EventTarget {
1646
1713
  if (this.refreshPromise) {
1647
1714
  await this.refreshPromise;
1648
1715
  }
1649
- if (!url.startsWith('http')) {
1650
- url = this.baseUrl + url;
1651
- }
1652
1716
  options.method = method;
1653
1717
  this.addFetchOptionsDefaults(options);
1654
1718
  const response = await this.fetchWithRetry(url, options);
1655
- return await this.parseResponse(response, method, url, options);
1719
+ return this.parseResponse(response, method, url, options);
1656
1720
  }
1657
1721
  async parseResponse(response, method, url, options = {}) {
1658
1722
  if (response.status === 401) {
@@ -1683,6 +1747,9 @@ class MedplumClient extends EventTarget {
1683
1747
  return obj;
1684
1748
  }
1685
1749
  async fetchWithRetry(url, options) {
1750
+ if (!url.startsWith('http')) {
1751
+ url = this.baseUrl + url;
1752
+ }
1686
1753
  const maxRetries = 3;
1687
1754
  const retryDelay = 200;
1688
1755
  let response = undefined;
@@ -1696,14 +1763,16 @@ class MedplumClient extends EventTarget {
1696
1763
  catch (err) {
1697
1764
  this.retryCatch(retry, maxRetries, err);
1698
1765
  }
1699
- await new Promise((resolve) => setTimeout(resolve, retryDelay));
1766
+ await new Promise((resolve) => {
1767
+ setTimeout(resolve, retryDelay);
1768
+ });
1700
1769
  }
1701
1770
  return response;
1702
1771
  }
1703
1772
  async pollStatus(statusUrl) {
1704
1773
  let checkStatus = true;
1705
1774
  let resultResponse;
1706
- const retryDelay = 200;
1775
+ const retryDelay = 2000;
1707
1776
  while (checkStatus) {
1708
1777
  const fetchOptions = {};
1709
1778
  this.addFetchOptionsDefaults(fetchOptions);
@@ -1712,9 +1781,11 @@ class MedplumClient extends EventTarget {
1712
1781
  checkStatus = false;
1713
1782
  resultResponse = statusResponse;
1714
1783
  }
1715
- await new Promise((resolve) => setTimeout(resolve, retryDelay));
1784
+ await new Promise((resolve) => {
1785
+ setTimeout(resolve, retryDelay);
1786
+ });
1716
1787
  }
1717
- return await this.parseResponse(resultResponse, 'POST', statusUrl);
1788
+ return this.parseResponse(resultResponse, 'POST', statusUrl);
1718
1789
  }
1719
1790
  /**
1720
1791
  * Executes a batch of requests that were automatically batched together.
@@ -1850,7 +1921,7 @@ class MedplumClient extends EventTarget {
1850
1921
  const codeVerifier = getRandomString();
1851
1922
  sessionStorage.setItem('codeVerifier', codeVerifier);
1852
1923
  const arrayHash = await encryptSHA256(codeVerifier);
1853
- const codeChallenge = arrayBufferToBase64(arrayHash).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
1924
+ const codeChallenge = arrayBufferToBase64(arrayHash).replaceAll('+', '-').replaceAll('/', '_').replaceAll('=', '');
1854
1925
  sessionStorage.setItem('codeChallenge', codeChallenge);
1855
1926
  return { codeChallengeMethod: 'S256', codeChallenge };
1856
1927
  }
@@ -1963,7 +2034,7 @@ class MedplumClient extends EventTarget {
1963
2034
  * Invite a user to a project.
1964
2035
  * @param projectId The project ID.
1965
2036
  * @param body The InviteBody.
1966
- * @returns Promise that returns an invite result or an operation outcome.
2037
+ * @returns Promise that returns a project membership or an operation outcome.
1967
2038
  */
1968
2039
  async invite(projectId, body) {
1969
2040
  return this.post('admin/projects/' + projectId + '/invite', body);
@@ -1988,7 +2059,13 @@ class MedplumClient extends EventTarget {
1988
2059
  const response = await this.fetch(this.tokenUrl, options);
1989
2060
  if (!response.ok) {
1990
2061
  this.clearActiveLogin();
1991
- throw new Error('Failed to fetch tokens');
2062
+ try {
2063
+ const error = await response.json();
2064
+ throw new OperationOutcomeError(badRequest(error.error_description));
2065
+ }
2066
+ catch (err) {
2067
+ throw new OperationOutcomeError(badRequest('Failed to fetch tokens'), err);
2068
+ }
1992
2069
  }
1993
2070
  const tokens = await response.json();
1994
2071
  await this.verifyTokens(tokens);