@ovok/core 0.4.37 → 0.4.39

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 (99) hide show
  1. package/dist/client/auth/account-storage.d.ts +1 -0
  2. package/dist/client/auth/account-storage.js +2 -1
  3. package/dist/{utils → client}/auth/exchange-code.d.ts +1 -1
  4. package/dist/{utils → client}/auth/generate-code-challenge-and-verifier.js +5 -17
  5. package/dist/{utils → client}/auth/handle-tenant-code.d.ts +1 -1
  6. package/dist/{utils → client}/auth/handle-tenant-code.js +1 -1
  7. package/dist/client/auth/methods/appleLogin.js +2 -6
  8. package/dist/client/auth/methods/changePassword.js +2 -1
  9. package/dist/client/auth/methods/getSessions.js +2 -1
  10. package/dist/client/auth/methods/googleLogin.js +2 -6
  11. package/dist/client/auth/methods/login.js +26 -44
  12. package/dist/client/auth/methods/logout.js +2 -1
  13. package/dist/client/auth/methods/requestDeleteUser.js +5 -4
  14. package/dist/client/auth/methods/revokeSessions.js +2 -1
  15. package/dist/client/auth/methods/socialLogin.d.ts +7 -0
  16. package/dist/client/auth/methods/socialLogin.js +11 -0
  17. package/dist/client/auth/session-fetch.js +10 -12
  18. package/dist/client/auth/session-http.js +8 -19
  19. package/dist/client/auth/sha256.d.ts +2 -0
  20. package/dist/client/auth/sha256.js +65 -0
  21. package/dist/{utils → client}/auth/start-login.d.ts +2 -2
  22. package/dist/client/change-history/methods.js +2 -1
  23. package/dist/client/cms/methods.js +16 -28
  24. package/dist/client/consent/methods.js +3 -2
  25. package/dist/client/email/methods.js +2 -1
  26. package/dist/client/errors/ovok-error-utils.d.ts +6 -0
  27. package/dist/client/errors/ovok-error-utils.js +10 -1
  28. package/dist/client/errors/ovok-error.js +6 -13
  29. package/dist/client/errors/rate-limit-error.js +2 -6
  30. package/dist/client/experience/methods.js +13 -12
  31. package/dist/client/invites/methods.js +8 -12
  32. package/dist/client/notifications/methods.js +4 -3
  33. package/dist/client/observation/fhir-systems.d.ts +4 -0
  34. package/dist/client/observation/fhir-systems.js +7 -0
  35. package/dist/client/observation/methods/generateObservationBodiesByMeasurement.js +23 -29
  36. package/dist/client/observation/methods/getLatestObservationsByCodes.js +6 -9
  37. package/dist/client/observation/methods/observationsToMeasurements.js +44 -91
  38. package/dist/client/observation/services/base-observation-service.js +4 -3
  39. package/dist/client/observation/services/blood-pressure.js +1 -4
  40. package/dist/client/observation/services/{baby-sleep.d.ts → component-observation-services.d.ts} +18 -13
  41. package/dist/client/observation/services/component-observation-services.js +69 -0
  42. package/dist/client/observation/services/creator.d.ts +1 -2
  43. package/dist/client/observation/services/creator.js +14 -18
  44. package/dist/client/observation/services/ecg-observation-fragments.js +5 -6
  45. package/dist/client/observation/services/ecg.js +3 -2
  46. package/dist/client/observation/services/lipid-panel.js +8 -12
  47. package/dist/client/observation/services/measurement-time.d.ts +4 -0
  48. package/dist/client/observation/services/measurement-time.js +6 -0
  49. package/dist/client/observation/services/single-value-services.d.ts +5 -0
  50. package/dist/client/observation/services/single-value-services.js +6 -1
  51. package/dist/client/observation/services/uric-acid-service.js +6 -7
  52. package/dist/client/observation/types/generate-measurement/HealthConnectTypes.d.ts +2 -458
  53. package/dist/client/observation/types/generate-measurement/HealthConnectTypes.js +0 -6
  54. package/dist/client/offline/offline-measurement-queue.js +31 -28
  55. package/dist/client/ovok-client.d.ts +13 -2
  56. package/dist/client/ovok-client.js +95 -45
  57. package/dist/client/patient/methods.js +25 -25
  58. package/dist/client/patient-sharing/methods.js +4 -3
  59. package/dist/client/platform/methods.js +17 -16
  60. package/dist/client/platform/types.d.ts +3 -6
  61. package/dist/client/questionnaire-response/methods/createQuestionnaireResponseWithObservations.js +2 -1
  62. package/dist/client/questionnaire-response/methods/submitQuestionnaireResponse.js +2 -1
  63. package/dist/client/request-options.d.ts +2 -0
  64. package/dist/client/request-options.js +6 -0
  65. package/dist/client/signals/errors.js +2 -6
  66. package/dist/client/signals/methods.js +14 -13
  67. package/dist/client/slim/live-read.d.ts +4 -0
  68. package/dist/client/slim/live-read.js +87 -0
  69. package/dist/client/slim/methods.d.ts +3 -0
  70. package/dist/client/slim/methods.js +63 -146
  71. package/dist/client/storage.js +3 -2
  72. package/dist/conformance/capability-requirements.js +1 -1
  73. package/dist/conformance/capability.js +8 -7
  74. package/dist/hooks/account-hooks.js +1 -1
  75. package/dist/hooks/api-hooks.d.ts +5 -6
  76. package/dist/hooks/api-hooks.js +10 -24
  77. package/dist/hooks/cms-hooks.js +8 -13
  78. package/dist/hooks/experience-hooks.js +1 -1
  79. package/dist/hooks/index.d.ts +2 -2
  80. package/dist/hooks/index.js +4 -4
  81. package/dist/hooks/invites-hooks.js +0 -1
  82. package/dist/hooks/notification-hooks.d.ts +4 -1
  83. package/dist/hooks/notification-hooks.js +28 -35
  84. package/dist/hooks/notification-invalidation.d.ts +1 -1
  85. package/dist/hooks/notification-note-hooks.js +13 -28
  86. package/dist/hooks/observation-hooks.js +2 -4
  87. package/dist/hooks/patient-hooks.js +1 -1
  88. package/dist/hooks/patient-sharing-hooks.js +1 -2
  89. package/dist/hooks/poll-hooks.js +10 -8
  90. package/dist/hooks/signals-alert-hooks.js +4 -6
  91. package/package.json +1 -1
  92. package/dist/client/observation/services/baby-sleep.js +0 -69
  93. package/dist/client/observation/services/respiratory-therapy.d.ts +0 -28
  94. package/dist/client/observation/services/respiratory-therapy.js +0 -69
  95. package/dist/utils/auth/sha256.d.ts +0 -8
  96. package/dist/utils/auth/sha256.js +0 -300
  97. /package/dist/{utils → client}/auth/exchange-code.js +0 -0
  98. /package/dist/{utils → client}/auth/generate-code-challenge-and-verifier.d.ts +0 -0
  99. /package/dist/{utils → client}/auth/start-login.js +0 -0
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.stableMeasurementId = exports.entryForObservation = exports.OFFLINE_MEASUREMENT_IDENTIFIER_SYSTEM = exports.DEFAULT_OFFLINE_MEASUREMENT_QUEUE_KEY = exports.OfflineMeasurementQueue = exports.OFFLINE_MEASUREMENT_QUEUE_STORAGE_KEY = void 0;
4
+ const fhir_systems_1 = require("../observation/fhir-systems");
5
+ const request_options_1 = require("../request-options");
4
6
  exports.OFFLINE_MEASUREMENT_QUEUE_STORAGE_KEY = "ovok.core.offline-measurements";
5
7
  exports.DEFAULT_OFFLINE_MEASUREMENT_QUEUE_KEY = exports.OFFLINE_MEASUREMENT_QUEUE_STORAGE_KEY;
6
8
  const DEFAULT_MAX_RETRIES = 8;
@@ -8,7 +10,6 @@ const DEFAULT_RETRY_BACKOFF_MS = 5000;
8
10
  const DEFAULT_MAX_RETRY_BACKOFF_MS = 60 * 60 * 1000;
9
11
  const IDENTIFIER_SYSTEM = "https://api.ovok.com/fhir/StructureDefinition/offline-measurement-id";
10
12
  exports.OFFLINE_MEASUREMENT_IDENTIFIER_SYSTEM = IDENTIFIER_SYSTEM;
11
- const DEVICE_READING_IDENTIFIER_SYSTEM = "https://api.ovok.com/fhir/StructureDefinition/device-reading-id";
12
13
  const canonicalize = (value) => {
13
14
  if (value instanceof Date) {
14
15
  return value.toISOString();
@@ -47,10 +48,10 @@ const stableValueId = (input) => {
47
48
  };
48
49
  const stableMeasurementId = (observation) => {
49
50
  const identifiers = observation.identifier;
50
- if (!(identifiers === null || identifiers === void 0 ? void 0 : identifiers.some((identifier) => identifier.system === DEVICE_READING_IDENTIFIER_SYSTEM))) {
51
+ if (!(identifiers === null || identifiers === void 0 ? void 0 : identifiers.some((identifier) => identifier.system === fhir_systems_1.DEVICE_READING_IDENTIFIER_SYSTEM))) {
51
52
  return stableValueId(observation);
52
53
  }
53
- const remainingIdentifiers = identifiers.filter((identifier) => identifier.system !== DEVICE_READING_IDENTIFIER_SYSTEM);
54
+ const remainingIdentifiers = identifiers.filter((identifier) => identifier.system !== fhir_systems_1.DEVICE_READING_IDENTIFIER_SYSTEM);
54
55
  const legacyObservation = { ...observation };
55
56
  if (remainingIdentifiers.length > 0) {
56
57
  legacyObservation.identifier = remainingIdentifiers;
@@ -68,9 +69,9 @@ const entryForObservation = (observation) => {
68
69
  system: IDENTIFIER_SYSTEM,
69
70
  value: id,
70
71
  };
71
- const sourceIdentifier = (_a = observation.identifier) === null || _a === void 0 ? void 0 : _a.find((item) => item.system === DEVICE_READING_IDENTIFIER_SYSTEM);
72
+ const sourceIdentifier = (_a = observation.identifier) === null || _a === void 0 ? void 0 : _a.find((item) => item.system === fhir_systems_1.DEVICE_READING_IDENTIFIER_SYSTEM);
72
73
  const ifNoneExist = sourceIdentifier
73
- ? `identifier=${encodeURIComponent(`${IDENTIFIER_SYSTEM}|${id}`)},${encodeURIComponent(`${DEVICE_READING_IDENTIFIER_SYSTEM}|${sourceIdentifier.value}`)}`
74
+ ? `identifier=${encodeURIComponent(`${IDENTIFIER_SYSTEM}|${id}`)},${encodeURIComponent(`${fhir_systems_1.DEVICE_READING_IDENTIFIER_SYSTEM}|${sourceIdentifier.value}`)}`
74
75
  : `identifier=${IDENTIFIER_SYSTEM}|${id}`;
75
76
  return {
76
77
  fullUrl: `urn:uuid:${id}`,
@@ -94,6 +95,24 @@ const isFailedResponse = (bundle) => {
94
95
  return status.startsWith("4") || status.startsWith("5");
95
96
  })) !== null && _b !== void 0 ? _b : false;
96
97
  };
98
+ /** The stored failure: name, and code/status when present; never the message or payload. */
99
+ const summarizeFailure = (error) => {
100
+ const failure = error;
101
+ return {
102
+ name: typeof (failure === null || failure === void 0 ? void 0 : failure.name) === "string" ? failure.name : "Error",
103
+ ...(typeof (failure === null || failure === void 0 ? void 0 : failure.code) === "string" ? { code: failure.code } : {}),
104
+ ...(typeof (failure === null || failure === void 0 ? void 0 : failure.status) === "number" ? { status: failure.status } : {}),
105
+ };
106
+ };
107
+ /** Counts an item that stays in the queue. */
108
+ const tally = (result, item) => {
109
+ if (item.exhausted) {
110
+ result.exhausted++;
111
+ }
112
+ else {
113
+ result.queued++;
114
+ }
115
+ };
97
116
  class OfflineMeasurementQueue {
98
117
  constructor(storage, transport, options = {}) {
99
118
  var _a, _b, _c, _d;
@@ -123,13 +142,13 @@ class OfflineMeasurementQueue {
123
142
  });
124
143
  this.writeQueue(queue);
125
144
  }
126
- return { id, flush: await this.flushQueue(queue, {}, ...(requestOptions === undefined ? [] : [requestOptions])) };
145
+ return { id, flush: await this.flushQueue(queue, {}, ...(0, request_options_1.optionalArg)(requestOptions)) };
127
146
  });
128
147
  };
129
148
  this.flush = (options = {}, requestOptions) => this.runExclusive(async () => {
130
149
  var _a, _b;
131
150
  await ((_b = (_a = this.storage).getInitPromise) === null || _b === void 0 ? void 0 : _b.call(_a));
132
- return this.flushQueue(this.readQueue(), options, ...(requestOptions === undefined ? [] : [requestOptions]));
151
+ return this.flushQueue(this.readQueue(), options, ...(0, request_options_1.optionalArg)(requestOptions));
133
152
  });
134
153
  this.runExclusive = (operation) => {
135
154
  const next = this.operation.then(operation, operation);
@@ -180,12 +199,7 @@ class OfflineMeasurementQueue {
180
199
  }
181
200
  if ((item.exhausted && !options.includeExhausted) ||
182
201
  item.nextAttemptAt > now) {
183
- if (item.exhausted) {
184
- result.exhausted++;
185
- }
186
- else {
187
- result.queued++;
188
- }
202
+ tally(result, item);
189
203
  continue;
190
204
  }
191
205
  const ownerKey = (_a = this.getOwnerKey) === null || _a === void 0 ? void 0 : _a.call(this);
@@ -200,7 +214,7 @@ class OfflineMeasurementQueue {
200
214
  resourceType: "Bundle",
201
215
  type: "transaction",
202
216
  entry: item.entries,
203
- }, ...(requestOptions === undefined ? [] : [requestOptions]));
217
+ }, ...(0, request_options_1.optionalArg)(requestOptions));
204
218
  if (isFailedResponse(response)) {
205
219
  throw response;
206
220
  }
@@ -217,20 +231,9 @@ class OfflineMeasurementQueue {
217
231
  : Date.now() +
218
232
  Math.min(this.maxRetryBackoffMs, this.retryBackoffMs * 2 ** (attempt - 1));
219
233
  changed = true;
220
- const failure = error;
221
- const lastFailure = {
222
- name: typeof (failure === null || failure === void 0 ? void 0 : failure.name) === "string" ? failure.name : "Error",
223
- ...(typeof (failure === null || failure === void 0 ? void 0 : failure.code) === "string" ? { code: failure.code } : {}),
224
- ...(typeof (failure === null || failure === void 0 ? void 0 : failure.status) === "number" ? { status: failure.status } : {}),
225
- };
226
- item.lastFailure = lastFailure;
227
- result.failures[item.id] = lastFailure;
228
- if (item.exhausted) {
229
- result.exhausted++;
230
- }
231
- else {
232
- result.queued++;
233
- }
234
+ item.lastFailure = summarizeFailure(error);
235
+ result.failures[item.id] = item.lastFailure;
236
+ tally(result, item);
234
237
  }
235
238
  }
236
239
  if (changed) {
@@ -23,8 +23,7 @@ import * as platformMethods from "./platform/methods";
23
23
  import * as questionnaireResponseMethods from "./questionnaire-response/methods";
24
24
  import * as signalsMethods from "./signals/methods";
25
25
  import * as slimMethods from "./slim/methods";
26
- export declare const MAX_CONDITIONAL_OBSERVATION_ENTRIES = 8;
27
- export { MAX_OBSERVATION_BUNDLE_BYTES } from "./observation/observation-bundle-utils";
26
+ export { MAX_CONDITIONAL_OBSERVATION_ENTRIES, MAX_OBSERVATION_BUNDLE_BYTES, } from "./observation/observation-bundle-utils";
28
27
  export type { OvokUnauthenticatedReason } from "./auth/session-fetch";
29
28
  export type SaveObservationsResult = {
30
29
  status: "saved";
@@ -51,6 +50,8 @@ export declare class OvokClient extends MedplumClient {
51
50
  private readonly keepSessionOnRefreshUnavailable;
52
51
  private readonly refreshState;
53
52
  private offlineMeasurementQueue;
53
+ private profileRetry;
54
+ private profileRetryMs;
54
55
  /**
55
56
  * @param config - The configuration for the client.
56
57
  * @param config.socialLoginClientId - The social login client id to use for the client.
@@ -58,6 +59,12 @@ export declare class OvokClient extends MedplumClient {
58
59
  constructor(config: OvokClientOptions);
59
60
  get<T = unknown>(url: URL | string, options?: MedplumRequestOptions): ReadablePromise<T>;
60
61
  refreshIfExpired(gracePeriod?: number): Promise<void>;
62
+ /** A profile load that failed is tried again now, rather than answering with its old error. */
63
+ getProfileAsync(): ReturnType<MedplumClient["getProfileAsync"]>;
64
+ clearActiveLogin(): void;
65
+ /** While signed in without a profile, a failed load is tried again: after 2 s, doubling to a minute. */
66
+ private retryProfileOnFailure;
67
+ private cancelProfileRetry;
61
68
  post<T = unknown>(url: URL | string, body?: unknown, contentType?: string, options?: MedplumRequestOptions): Promise<T>;
62
69
  /** Lists saved accounts without exposing their access or refresh tokens. */
63
70
  getSavedAccounts(): OvokSavedAccount[];
@@ -84,12 +91,16 @@ export declare class OvokClient extends MedplumClient {
84
91
  /** Flushes measurements queued while offline. Call this on connectivity/app-resume events. */
85
92
  flushOfflineMeasurementQueue(options?: OfflineMeasurementFlushOptions, requestOptions?: MedplumRequestOptions): Promise<OfflineMeasurementFlushResult>;
86
93
  private getOfflineMeasurementQueue;
94
+ /** Queued measurements belong to one account on one server and project. */
95
+ private offlineOwnerKey;
87
96
  /**
88
97
  * Medplum clears its entire client storage after an unrecoverable 401. Keep
89
98
  * saved accounts (marking only the active one expired) and queued measurements.
90
99
  * Use clearAllAccounts() when the caller intends to remove all saved accounts.
91
100
  */
92
101
  clear(): void;
102
+ /** Runs Medplum's storage clear but keeps queued offline measurements; `restore` runs in between. */
103
+ private clearKeepingQueue;
93
104
  private getLogin;
94
105
  private getExpiredAccountIds;
95
106
  private createAccountSessionClient;
@@ -61,13 +61,17 @@ const patientMethods = __importStar(require("./patient/methods"));
61
61
  const patientSharingMethods = __importStar(require("./patient-sharing/methods"));
62
62
  const platformMethods = __importStar(require("./platform/methods"));
63
63
  const questionnaireResponseMethods = __importStar(require("./questionnaire-response/methods"));
64
+ const request_options_1 = require("./request-options");
64
65
  const errors_2 = require("./signals/errors");
65
66
  const signalsMethods = __importStar(require("./signals/methods"));
66
67
  const slimMethods = __importStar(require("./slim/methods"));
67
- exports.MAX_CONDITIONAL_OBSERVATION_ENTRIES = 8;
68
68
  var observation_bundle_utils_2 = require("./observation/observation-bundle-utils");
69
+ Object.defineProperty(exports, "MAX_CONDITIONAL_OBSERVATION_ENTRIES", { enumerable: true, get: function () { return observation_bundle_utils_2.MAX_CONDITIONAL_OBSERVATION_ENTRIES; } });
69
70
  Object.defineProperty(exports, "MAX_OBSERVATION_BUNDLE_BYTES", { enumerable: true, get: function () { return observation_bundle_utils_2.MAX_OBSERVATION_BUNDLE_BYTES; } });
70
71
  const isRetrySafePost = (pathname) => pathname === "/ai/fhir/search";
72
+ /** A failed profile load is tried again after this long, doubling up to the maximum. */
73
+ const PROFILE_RETRY_MS = 2000;
74
+ const PROFILE_RETRY_MAX_MS = 60000;
71
75
  class OvokClient extends core_1.MedplumClient {
72
76
  /**
73
77
  * @param config - The configuration for the client.
@@ -91,7 +95,22 @@ class OvokClient extends core_1.MedplumClient {
91
95
  fetch: session.fetch,
92
96
  });
93
97
  this.socialLoginClientId = "";
98
+ this.profileRetryMs = PROFILE_RETRY_MS;
94
99
  session.setClearActiveLogin(this.clearActiveLogin.bind(this));
100
+ // Medplum keeps a failed `auth/me` as the profile for good: `isLoading()` stays true and `getProfileAsync()`
101
+ // keeps rejecting. So a failed load is tried again while signed in.
102
+ const loader = this;
103
+ const refreshProfile = loader.refreshProfile.bind(this);
104
+ loader.refreshProfile = () => {
105
+ this.cancelProfileRetry();
106
+ const loading = refreshProfile();
107
+ this.retryProfileOnFailure(loading);
108
+ return loading;
109
+ };
110
+ // A saved session resumed inside super() started loading before the retry existed.
111
+ if (loader.profilePromise) {
112
+ this.retryProfileOnFailure(loader.profilePromise);
113
+ }
95
114
  this.hasExplicitStorageAdapter = config.storage !== undefined;
96
115
  this.clientOptions = config;
97
116
  this.keepSessionOnRefreshUnavailable = config.keepSessionOnRefreshUnavailable === true;
@@ -130,7 +149,6 @@ class OvokClient extends core_1.MedplumClient {
130
149
  return request;
131
150
  }
132
151
  async refreshIfExpired(gracePeriod) {
133
- var _a;
134
152
  const activeLogin = this.getActiveLogin();
135
153
  const accessToken = this.getAccessToken();
136
154
  const expiresAt = accessToken ? (0, core_1.tryGetJwtExpiration)(accessToken) : undefined;
@@ -146,9 +164,7 @@ class OvokClient extends core_1.MedplumClient {
146
164
  !(activeAccount === null || activeAccount === void 0 ? void 0 : activeAccount.expired) &&
147
165
  expiresAt !== undefined &&
148
166
  expiresAt > Date.now()) {
149
- (_a = this.clientStorage) === null || _a === void 0 ? void 0 : _a.setObject("activeLogin", activeLogin);
150
- this.setAccessToken(accessToken, activeLogin.refreshToken);
151
- return;
167
+ return; // the still-valid access token carries the request
152
168
  }
153
169
  throw error;
154
170
  }
@@ -156,6 +172,37 @@ class OvokClient extends core_1.MedplumClient {
156
172
  this.refreshState.unavailable = false;
157
173
  }
158
174
  }
175
+ /** A profile load that failed is tried again now, rather than answering with its old error. */
176
+ getProfileAsync() {
177
+ return this.profileRetry
178
+ ? this.refreshProfile()
179
+ : super.getProfileAsync();
180
+ }
181
+ clearActiveLogin() {
182
+ this.cancelProfileRetry();
183
+ super.clearActiveLogin();
184
+ }
185
+ /** While signed in without a profile, a failed load is tried again: after 2 s, doubling to a minute. */
186
+ retryProfileOnFailure(loading) {
187
+ loading.then(() => {
188
+ this.profileRetryMs = PROFILE_RETRY_MS;
189
+ }, () => {
190
+ var _a, _b;
191
+ if (!this.getActiveLogin() || this.getProfile()) {
192
+ return;
193
+ }
194
+ const retry = setTimeout(() => {
195
+ void this.refreshProfile().catch(() => undefined);
196
+ }, this.profileRetryMs);
197
+ (_b = (_a = retry).unref) === null || _b === void 0 ? void 0 : _b.call(_a); // never keeps a Node process alive
198
+ this.profileRetry = retry;
199
+ this.profileRetryMs = Math.min(this.profileRetryMs * 2, PROFILE_RETRY_MAX_MS);
200
+ });
201
+ }
202
+ cancelProfileRetry() {
203
+ clearTimeout(this.profileRetry);
204
+ this.profileRetry = undefined;
205
+ }
159
206
  post(url, body, contentType, options) {
160
207
  const pathname = new URL(url.toString(), this.getBaseUrl()).pathname;
161
208
  if (!isRetrySafePost(pathname)) {
@@ -225,7 +272,7 @@ class OvokClient extends core_1.MedplumClient {
225
272
  const wasActive = previousAccountId === accountId;
226
273
  const sessionClient = this.createAccountSessionClient(login);
227
274
  try {
228
- await sessionClient.delete("auth/session/current", ...(requestOptions === undefined ? [] : [requestOptions]));
275
+ await sessionClient.delete("auth/session/current", ...(0, request_options_1.optionalArg)(requestOptions));
229
276
  }
230
277
  catch (error) {
231
278
  if (!sessionClient.getSavedAccounts().some((account) => account.id === accountId && account.expired)) {
@@ -253,14 +300,7 @@ class OvokClient extends core_1.MedplumClient {
253
300
  }
254
301
  /** Removes every saved account and expiry marker from client storage. */
255
302
  clearAllAccounts() {
256
- var _a;
257
- const storage = this.clientStorage;
258
- const queueKey = (_a = this.offlineQueueOptions.storageKey) !== null && _a !== void 0 ? _a : offline_1.OFFLINE_MEASUREMENT_QUEUE_STORAGE_KEY;
259
- const queuedMeasurements = storage === null || storage === void 0 ? void 0 : storage.getObject(queueKey);
260
- super.clear();
261
- if (Array.isArray(queuedMeasurements)) {
262
- storage === null || storage === void 0 ? void 0 : storage.setObject(queueKey, queuedMeasurements);
263
- }
303
+ this.clearKeepingQueue();
264
304
  }
265
305
  /** Reauthentication clears the expired marker for the account that signed in. */
266
306
  async setActiveLogin(login) {
@@ -274,22 +314,22 @@ class OvokClient extends core_1.MedplumClient {
274
314
  /** Saves a measurement immediately, or durably queues it when the opt-in queue is enabled. */
275
315
  async saveMeasurement(params, requestOptions) {
276
316
  const observations = await this.generateObservationBodiesByMeasurement(params);
277
- return this.saveObservations(observations, ...(requestOptions === undefined ? [] : [requestOptions]));
317
+ return this.saveObservations(observations, ...(0, request_options_1.optionalArg)(requestOptions));
278
318
  }
279
319
  /** Reads Ovok's composed FHIR and public-route capability statement. */
280
320
  async getCapabilityStatement(version = "R4", requestOptions) {
281
- return this.get(`/fhir/${version}/metadata`, ...(requestOptions === undefined ? [] : [requestOptions]));
321
+ return this.get(`/fhir/${version}/metadata`, ...(0, request_options_1.optionalArg)(requestOptions));
282
322
  }
283
323
  /** Returns the non-FHIR public routes published by the backend statement. */
284
324
  async getPublicApiEndpoints(version = "R4", requestOptions) {
285
- return (0, capability_1.getOvokPublicApiEndpoints)(await this.getCapabilityStatement(version, ...(requestOptions === undefined ? [] : [requestOptions])));
325
+ return (0, capability_1.getOvokPublicApiEndpoints)(await this.getCapabilityStatement(version, ...(0, request_options_1.optionalArg)(requestOptions)));
286
326
  }
287
327
  /** Saves observations with conditional creates so a retry cannot create a duplicate. */
288
328
  async saveObservations(observations, requestOptions) {
289
329
  const linkedObservations = (0, observation_bundle_utils_1.linkObservationMembers)(observations);
290
330
  const queue = this.getOfflineMeasurementQueue();
291
331
  if (queue) {
292
- const { id, flush } = await queue.enqueueAndFlush(linkedObservations, ...(requestOptions === undefined ? [] : [requestOptions]));
332
+ const { id, flush } = await queue.enqueueAndFlush(linkedObservations, ...(0, request_options_1.optionalArg)(requestOptions));
293
333
  const response = flush.responses[id];
294
334
  return response
295
335
  ? { status: "saved", response }
@@ -311,7 +351,7 @@ class OvokClient extends core_1.MedplumClient {
311
351
  failures: {},
312
352
  };
313
353
  }
314
- return queue.flush(options, ...(requestOptions === undefined ? [] : [requestOptions]));
354
+ return queue.flush(options, ...(0, request_options_1.optionalArg)(requestOptions));
315
355
  }
316
356
  getOfflineMeasurementQueue() {
317
357
  var _a;
@@ -321,31 +361,33 @@ class OvokClient extends core_1.MedplumClient {
321
361
  if (!this.clientStorage || !this.hasExplicitStorageAdapter) {
322
362
  throw new Error("Offline measurement queue requires a durable client storage adapter.");
323
363
  }
324
- (_a = this.offlineMeasurementQueue) !== null && _a !== void 0 ? _a : (this.offlineMeasurementQueue = new offline_1.OfflineMeasurementQueue(this.clientStorage, (bundle, requestOptions) => this.executeObservationBundle(bundle, ...(requestOptions === undefined ? [] : [requestOptions])), {
364
+ (_a = this.offlineMeasurementQueue) !== null && _a !== void 0 ? _a : (this.offlineMeasurementQueue = new offline_1.OfflineMeasurementQueue(this.clientStorage, (bundle, requestOptions) => this.executeObservationBundle(bundle, ...(0, request_options_1.optionalArg)(requestOptions)), {
325
365
  ...this.offlineQueueOptions,
326
- getOwnerKey: () => {
327
- var _a, _b, _c;
328
- const login = this.getActiveLogin();
329
- const profileReference = (_a = login === null || login === void 0 ? void 0 : login.profile) === null || _a === void 0 ? void 0 : _a.reference;
330
- if (!profileReference) {
331
- return undefined;
332
- }
333
- return [
334
- this.getBaseUrl(),
335
- (_c = (_b = login === null || login === void 0 ? void 0 : login.project) === null || _b === void 0 ? void 0 : _b.reference) !== null && _c !== void 0 ? _c : "",
336
- profileReference,
337
- ].join("|");
338
- },
366
+ getOwnerKey: () => this.offlineOwnerKey(),
339
367
  }));
340
368
  return this.offlineMeasurementQueue;
341
369
  }
370
+ /** Queued measurements belong to one account on one server and project. */
371
+ offlineOwnerKey() {
372
+ var _a, _b, _c;
373
+ const login = this.getActiveLogin();
374
+ const profileReference = (_a = login === null || login === void 0 ? void 0 : login.profile) === null || _a === void 0 ? void 0 : _a.reference;
375
+ if (!profileReference) {
376
+ return undefined;
377
+ }
378
+ return [
379
+ this.getBaseUrl(),
380
+ (_c = (_b = login === null || login === void 0 ? void 0 : login.project) === null || _b === void 0 ? void 0 : _b.reference) !== null && _c !== void 0 ? _c : "",
381
+ profileReference,
382
+ ].join("|");
383
+ }
342
384
  /**
343
385
  * Medplum clears its entire client storage after an unrecoverable 401. Keep
344
386
  * saved accounts (marking only the active one expired) and queued measurements.
345
387
  * Use clearAllAccounts() when the caller intends to remove all saved accounts.
346
388
  */
347
389
  clear() {
348
- var _a, _b, _c;
390
+ var _a, _b;
349
391
  const storage = this.clientStorage;
350
392
  const activeAccountId = (_b = (_a = this.getActiveLogin()) === null || _a === void 0 ? void 0 : _a.profile) === null || _b === void 0 ? void 0 : _b.reference;
351
393
  const logins = this.getLogins().map((login) => {
@@ -358,15 +400,23 @@ class OvokClient extends core_1.MedplumClient {
358
400
  if (activeAccountId) {
359
401
  expiredAccountIds.add(activeAccountId);
360
402
  }
361
- const queueKey = (_c = this.offlineQueueOptions.storageKey) !== null && _c !== void 0 ? _c : offline_1.OFFLINE_MEASUREMENT_QUEUE_STORAGE_KEY;
403
+ this.clearKeepingQueue(() => {
404
+ if (logins.length > 0) {
405
+ storage === null || storage === void 0 ? void 0 : storage.setObject(account_storage_1.LOGINS_STORAGE_KEY, logins);
406
+ }
407
+ if (expiredAccountIds.size > 0) {
408
+ storage === null || storage === void 0 ? void 0 : storage.setObject(account_storage_1.EXPIRED_ACCOUNTS_STORAGE_KEY, Array.from(expiredAccountIds));
409
+ }
410
+ });
411
+ }
412
+ /** Runs Medplum's storage clear but keeps queued offline measurements; `restore` runs in between. */
413
+ clearKeepingQueue(restore) {
414
+ var _a;
415
+ const storage = this.clientStorage;
416
+ const queueKey = (_a = this.offlineQueueOptions.storageKey) !== null && _a !== void 0 ? _a : offline_1.OFFLINE_MEASUREMENT_QUEUE_STORAGE_KEY;
362
417
  const queuedMeasurements = storage === null || storage === void 0 ? void 0 : storage.getObject(queueKey);
363
418
  super.clear();
364
- if (logins.length > 0) {
365
- storage === null || storage === void 0 ? void 0 : storage.setObject(account_storage_1.LOGINS_STORAGE_KEY, logins);
366
- }
367
- if (expiredAccountIds.size > 0) {
368
- storage === null || storage === void 0 ? void 0 : storage.setObject(account_storage_1.EXPIRED_ACCOUNTS_STORAGE_KEY, Array.from(expiredAccountIds));
369
- }
419
+ restore === null || restore === void 0 ? void 0 : restore();
370
420
  if (Array.isArray(queuedMeasurements)) {
371
421
  storage === null || storage === void 0 ? void 0 : storage.setObject(queueKey, queuedMeasurements);
372
422
  }
@@ -380,7 +430,7 @@ class OvokClient extends core_1.MedplumClient {
380
430
  }
381
431
  createAccountSessionClient(login) {
382
432
  const storage = new core_1.ClientStorage(new core_1.MemoryStorage(), this.clientOptions.storagePrefix);
383
- storage.setObject("activeLogin", login);
433
+ storage.setObject(account_storage_1.ACTIVE_LOGIN_STORAGE_KEY, login);
384
434
  storage.setObject(account_storage_1.LOGINS_STORAGE_KEY, [login]);
385
435
  const client = new OvokClient({
386
436
  ...this.clientOptions,
@@ -413,7 +463,7 @@ class OvokClient extends core_1.MedplumClient {
413
463
  ...login,
414
464
  profile: { ...login.profile, display: (0, core_1.getDisplayString)(profile) },
415
465
  };
416
- (_b = this.clientStorage) === null || _b === void 0 ? void 0 : _b.setObject("activeLogin", updatedLogin);
466
+ (_b = this.clientStorage) === null || _b === void 0 ? void 0 : _b.setObject(account_storage_1.ACTIVE_LOGIN_STORAGE_KEY, updatedLogin);
417
467
  (_c = this.clientStorage) === null || _c === void 0 ? void 0 : _c.setObject(account_storage_1.LOGINS_STORAGE_KEY, this.getLogins().map((savedLogin) => { var _a; return ((_a = savedLogin.profile) === null || _a === void 0 ? void 0 : _a.reference) === accountId ? updatedLogin : savedLogin; }));
418
468
  }
419
469
  async restoreActiveLogin(login) {
@@ -430,10 +480,10 @@ class OvokClient extends core_1.MedplumClient {
430
480
  resourceType: "Bundle",
431
481
  type: "transaction",
432
482
  entry: entries,
433
- }, ...(requestOptions === undefined ? [] : [requestOptions]));
483
+ }, ...(0, request_options_1.optionalArg)(requestOptions));
434
484
  }
435
485
  async executeObservationBundle(bundle, requestOptions) {
436
- const response = await (0, observation_bundle_utils_1.executeObservationBundle)(bundle, (request) => this.executeBatch(request, ...(requestOptions === undefined ? [] : [requestOptions])));
486
+ const response = await (0, observation_bundle_utils_1.executeObservationBundle)(bundle, (request) => this.executeBatch(request, ...(0, request_options_1.optionalArg)(requestOptions)));
437
487
  this.invalidateSearches("Observation");
438
488
  return response;
439
489
  }
@@ -22,70 +22,70 @@ exports.listPatientNotifications = listPatientNotifications;
22
22
  exports.getPatientNotification = getPatientNotification;
23
23
  exports.getPatientDashboard = getPatientDashboard;
24
24
  const query_string_1 = require("../query-string");
25
+ const request_options_1 = require("../request-options");
25
26
  async function getCurrentPatientProfile(requestOptions) {
26
- return this.get("/auth/me", ...(requestOptions === undefined ? [] : [requestOptions]));
27
+ return this.get("/auth/me", ...(0, request_options_1.optionalArg)(requestOptions));
27
28
  }
28
29
  async function getPatientLatestObservations(params = {}, requestOptions) {
29
- const code = Array.isArray(params.code) ? params.code.join(",") : params.code;
30
30
  return this.get(`/fhir/Observation/$lastn${(0, query_string_1.toQueryString)({
31
31
  patient: params.patient,
32
32
  max: params.max,
33
- code,
34
- })}`, { ...{ cache: "no-cache" }, ...requestOptions });
33
+ code: params.code,
34
+ })}`, { cache: "no-cache", ...requestOptions });
35
35
  }
36
36
  async function listPatientAppointments(params = {}, requestOptions) {
37
- return this.searchResources("Appointment", params, ...(requestOptions === undefined ? [] : [requestOptions]));
37
+ return this.searchResources("Appointment", params, ...(0, request_options_1.optionalArg)(requestOptions));
38
38
  }
39
39
  async function getPatientAppointment(appointmentId, requestOptions) {
40
- return this.readResource("Appointment", appointmentId, ...(requestOptions === undefined ? [] : [requestOptions]));
40
+ return this.readResource("Appointment", appointmentId, ...(0, request_options_1.optionalArg)(requestOptions));
41
41
  }
42
42
  async function createPatientAppointment(appointment, requestOptions) {
43
- return this.createResource(appointment, ...(requestOptions === undefined ? [] : [requestOptions]));
43
+ return this.createResource(appointment, ...(0, request_options_1.optionalArg)(requestOptions));
44
44
  }
45
45
  async function updatePatientAppointment(appointment, requestOptions) {
46
- return this.updateResource(appointment, ...(requestOptions === undefined ? [] : [requestOptions]));
46
+ return this.updateResource(appointment, ...(0, request_options_1.optionalArg)(requestOptions));
47
47
  }
48
48
  async function listPatientSchedules(params = {}, requestOptions) {
49
- return this.searchResources("Schedule", params, ...(requestOptions === undefined ? [] : [requestOptions]));
49
+ return this.searchResources("Schedule", params, ...(0, request_options_1.optionalArg)(requestOptions));
50
50
  }
51
51
  async function listPatientSlots(params = {}, requestOptions) {
52
- return this.searchResources("Slot", params, ...(requestOptions === undefined ? [] : [requestOptions]));
52
+ return this.searchResources("Slot", params, ...(0, request_options_1.optionalArg)(requestOptions));
53
53
  }
54
54
  async function listPatientMeasurementHistory(params = {}, requestOptions) {
55
- return (await this.searchResources("Observation", params, ...(requestOptions === undefined ? [] : [requestOptions])));
55
+ return (await this.searchResources("Observation", params, ...(0, request_options_1.optionalArg)(requestOptions)));
56
56
  }
57
57
  async function getPatientMeasurement(measurementId, requestOptions) {
58
- return this.readResource("Observation", measurementId, ...(requestOptions === undefined ? [] : [requestOptions]));
58
+ return this.readResource("Observation", measurementId, ...(0, request_options_1.optionalArg)(requestOptions));
59
59
  }
60
60
  async function listPatientCarePlans(params = {}, requestOptions) {
61
- return (await this.searchResources("CarePlan", params, ...(requestOptions === undefined ? [] : [requestOptions])));
61
+ return (await this.searchResources("CarePlan", params, ...(0, request_options_1.optionalArg)(requestOptions)));
62
62
  }
63
63
  async function getPatientCarePlan(carePlanId, requestOptions) {
64
- return this.readResource("CarePlan", carePlanId, ...(requestOptions === undefined ? [] : [requestOptions]));
64
+ return this.readResource("CarePlan", carePlanId, ...(0, request_options_1.optionalArg)(requestOptions));
65
65
  }
66
66
  async function listPatientQuestionnaires(params = {}, requestOptions) {
67
- return (await this.searchResources("Questionnaire", params, ...(requestOptions === undefined ? [] : [requestOptions])));
67
+ return (await this.searchResources("Questionnaire", params, ...(0, request_options_1.optionalArg)(requestOptions)));
68
68
  }
69
69
  async function getPatientQuestionnaire(questionnaireId, requestOptions) {
70
- return this.readResource("Questionnaire", questionnaireId, ...(requestOptions === undefined ? [] : [requestOptions]));
70
+ return this.readResource("Questionnaire", questionnaireId, ...(0, request_options_1.optionalArg)(requestOptions));
71
71
  }
72
72
  async function listPatientQuestionnaireResponses(params = {}, requestOptions) {
73
- return (await this.searchResources("QuestionnaireResponse", params, ...(requestOptions === undefined ? [] : [requestOptions])));
73
+ return (await this.searchResources("QuestionnaireResponse", params, ...(0, request_options_1.optionalArg)(requestOptions)));
74
74
  }
75
75
  async function getPatientQuestionnaireResponse(questionnaireResponseId, requestOptions) {
76
- return this.readResource("QuestionnaireResponse", questionnaireResponseId, ...(requestOptions === undefined ? [] : [requestOptions]));
76
+ return this.readResource("QuestionnaireResponse", questionnaireResponseId, ...(0, request_options_1.optionalArg)(requestOptions));
77
77
  }
78
78
  async function listPatientDevices(params = {}, requestOptions) {
79
- return (await this.searchResources("Device", params, ...(requestOptions === undefined ? [] : [requestOptions])));
79
+ return (await this.searchResources("Device", params, ...(0, request_options_1.optionalArg)(requestOptions)));
80
80
  }
81
81
  async function getPatientDevice(deviceId, requestOptions) {
82
- return this.readResource("Device", deviceId, ...(requestOptions === undefined ? [] : [requestOptions]));
82
+ return this.readResource("Device", deviceId, ...(0, request_options_1.optionalArg)(requestOptions));
83
83
  }
84
84
  async function listPatientNotifications(params = {}, requestOptions) {
85
- return (await this.searchResources("CommunicationRequest", params, ...(requestOptions === undefined ? [] : [requestOptions])));
85
+ return (await this.searchResources("CommunicationRequest", params, ...(0, request_options_1.optionalArg)(requestOptions)));
86
86
  }
87
87
  async function getPatientNotification(notificationId, requestOptions) {
88
- return this.readResource("CommunicationRequest", notificationId, ...(requestOptions === undefined ? [] : [requestOptions]));
88
+ return this.readResource("CommunicationRequest", notificationId, ...(0, request_options_1.optionalArg)(requestOptions));
89
89
  }
90
90
  async function getPatientDashboard(params = {}, requestOptions) {
91
91
  var _a, _b, _c, _d, _e, _f;
@@ -94,16 +94,16 @@ async function getPatientDashboard(params = {}, requestOptions) {
94
94
  const latestObservations = await this.getPatientLatestObservations({
95
95
  ...params.latestObservations,
96
96
  patient: (_b = (_a = params.latestObservations) === null || _a === void 0 ? void 0 : _a.patient) !== null && _b !== void 0 ? _b : patient,
97
- }, ...(requestOptions === undefined ? [] : [requestOptions]));
97
+ }, ...(0, request_options_1.optionalArg)(requestOptions));
98
98
  const [measurementHistory, carePlans] = await Promise.all([
99
99
  this.listPatientMeasurementHistory({
100
100
  ...params.measurementHistory,
101
101
  patient: (_d = (_c = params.measurementHistory) === null || _c === void 0 ? void 0 : _c.patient) !== null && _d !== void 0 ? _d : patient,
102
- }, ...(requestOptions === undefined ? [] : [requestOptions])),
102
+ }, ...(0, request_options_1.optionalArg)(requestOptions)),
103
103
  this.listPatientCarePlans({
104
104
  ...params.carePlans,
105
105
  patient: (_f = (_e = params.carePlans) === null || _e === void 0 ? void 0 : _e.patient) !== null && _f !== void 0 ? _f : patient,
106
- }, ...(requestOptions === undefined ? [] : [requestOptions])),
106
+ }, ...(0, request_options_1.optionalArg)(requestOptions)),
107
107
  ]);
108
108
  return { carePlans, latestObservations, measurementHistory, profile };
109
109
  }
@@ -6,6 +6,7 @@ exports.listPatientSharing = listPatientSharing;
6
6
  exports.revokePatientSharing = revokePatientSharing;
7
7
  exports.getPatientSharingSettings = getPatientSharingSettings;
8
8
  exports.updatePatientSharingSettings = updatePatientSharingSettings;
9
+ const request_options_1 = require("../request-options");
9
10
  function createPatientSharingInvite(body, requestOptions) {
10
11
  return this.post("/v1/patient-sharing/invites", body, undefined, requestOptions);
11
12
  }
@@ -16,7 +17,7 @@ function redeemPatientSharingInvite(body, requestOptions) {
16
17
  });
17
18
  }
18
19
  function listPatientSharing(requestOptions) {
19
- return this.get("/v1/patient-sharing/shares", ...(requestOptions === undefined ? [] : [requestOptions])).then((shares) => {
20
+ return this.get("/v1/patient-sharing/shares", ...(0, request_options_1.optionalArg)(requestOptions)).then((shares) => {
20
21
  var _a;
21
22
  return ({
22
23
  ...shares,
@@ -25,11 +26,11 @@ function listPatientSharing(requestOptions) {
25
26
  });
26
27
  }
27
28
  async function revokePatientSharing(consentId, requestOptions) {
28
- await this.delete(`/v1/patient-sharing/shares/${encodeURIComponent(consentId)}`, ...(requestOptions === undefined ? [] : [requestOptions]));
29
+ await this.delete(`/v1/patient-sharing/shares/${encodeURIComponent(consentId)}`, ...(0, request_options_1.optionalArg)(requestOptions));
29
30
  this.invalidateUrl("/v1/patient-sharing/shares");
30
31
  }
31
32
  function getPatientSharingSettings(requestOptions) {
32
- return this.get("/v1/patient-sharing/settings", ...(requestOptions === undefined ? [] : [requestOptions]));
33
+ return this.get("/v1/patient-sharing/settings", ...(0, request_options_1.optionalArg)(requestOptions));
33
34
  }
34
35
  function updatePatientSharingSettings(settings, requestOptions) {
35
36
  return this.put("/v1/patient-sharing/settings", settings, undefined, requestOptions);