@ovok/core 0.4.42 → 0.4.44

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.
package/README.md CHANGED
@@ -330,6 +330,28 @@ validation errors or 424 when the baseline has not been collected. Hooks never r
330
330
  a stale body; transient 503 transport retries do not clear the session.
331
331
  The unconfirmed-limit list and repush routes require project-admin access; a 403 remains an error.
332
332
 
333
+ Resident deletion returns 409 while the resident has a device; unassign the device with
334
+ `updateSlimPatient()` before deleting. Resident, room, and device writes can also return 422 when
335
+ their device, room, and zone assignments disagree; show the server message and reload before
336
+ retrying. Device serial conflicts return 409 when another customer holds the serial and 422 when
337
+ another device in the project already uses it. Renaming a device moves its serial: old-serial
338
+ readings stop reaching it, and live values return after a reading arrives under the new serial.
339
+ Resident updates replace the editable fields; omitting `locationId` unassigns its devices, and names
340
+ are trimmed by the server.
341
+
342
+ A resident threshold save can return 409 while another save is running; nothing changed, so wait
343
+ briefly and send it again. A home threshold save can return 502 when Signals did not take every
344
+ resident's limits; nothing was written. A home with no saved limits reads as the default template
345
+ with heart rate 40–110 and respiratory rate 8–25 enabled, and `id: null`.
346
+
347
+ Notification feeds and episode reads can wait 5 seconds for Signals before returning 503
348
+ `signals_unavailable`; realtime trends can wait 12 seconds. Realtime trends may also be shed
349
+ immediately under load. Wait for `retryAfterMs` before trying again. The paged medical feed returns
350
+ 503 during an outage, while recent medical notifications may return the last good page, up to 10
351
+ minutes old, without a marker. Episode `values` contains only readings the episode counted, not
352
+ valueless or history readings. Acknowledgements can take about 12 seconds and are safe to repeat.
353
+ Realtime trends allow 20 calls per minute per caller; load once per view instead of polling.
354
+
333
355
  `getLegalConsent()` returns the signed-in practitioner's own terms status. If
334
356
  `needsLegalConsentReacceptance(response.legal)` is true, show the current terms before continuing.
335
357
 
@@ -424,8 +446,9 @@ The backend allows three kinds of read:
424
446
  Signed out, a read rejects with `SignInRequiredError` (`isSignInRequiredError(error)`) before
425
447
  any request is sent, unless it passes `allowSignedOut: true` and a `tenantCode`. `getLegalPage`,
426
448
  `getTranslations` and their hooks turn `allowSignedOut` on by default; pass it yourself only for
427
- another collection the backend serves without a key or session. A request that returns 401 can
428
- clear the session storage and call `onUnauthenticated`. A hook that rendered before sign-in needs
449
+ another collection the backend serves without a key or session. A 401 from an ordinary request can
450
+ clear the session storage and call `onUnauthenticated`; a 401 from password sign-in is instead a
451
+ `LoginError` and leaves the active account untouched. A hook that rendered before sign-in needs
429
452
  `reload()` once the session exists.
430
453
 
431
454
  `locale` takes an app language or a CMS locale (`en`, `de`, `fr`, `es`). `toCmsLocale` maps
@@ -483,9 +506,11 @@ create a second copy of the same observation. The queue is opt-in and requires d
483
506
 
484
507
  ## Rate limits
485
508
 
486
- Authentication methods reject with `RateLimitError` when the server returns a throttling outcome.
487
- Use `isRateLimitError(error)` and `retryAfterMs` to show a translated wait message without parsing
488
- backend text.
509
+ Ovok-defined methods, including measurement saves, reject with `RateLimitError` when the server
510
+ returns 429. A 429 or 503 with a retry delay is returned without an automatic retry; wait for
511
+ `retryAfterMs` before trying again. Use `isRateLimitError(error)` and `retryAfterMs` to show a
512
+ translated wait message without parsing backend text. Inherited Medplum FHIR methods keep their
513
+ `OperationOutcomeError`; use `readOvokError(error)` to read their status and delay.
489
514
 
490
515
  ## Session management
491
516
 
@@ -517,6 +542,14 @@ if ("nextStep" in result && result.nextStep === "mfa") {
517
542
  }
518
543
  ```
519
544
 
545
+ ### Password sign-in errors
546
+
547
+ Password sign-in failures throw `LoginError`. A 401 has `code: "invalid_credentials"` and the
548
+ server's message; it does not refresh tokens, clear an existing account, or call
549
+ `onUnauthenticated`. A 429 has `code: "rate_limited"` and includes `retryAfterMs` when the server
550
+ provides a retry delay. Show the error or a wait message to the user, then honor that delay before
551
+ another attempt. The backend controls the lockout threshold and duration.
552
+
520
553
  ## Patient registration invitations
521
554
 
522
555
  When a project invites people with a business email address to join as clinicians,
@@ -3,8 +3,31 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createSessionFetch = createSessionFetch;
4
4
  /* eslint-disable max-lines -- one refresh state machine; splitting it would scatter its shared session state. */
5
5
  const core_1 = require("@medplum/core");
6
+ const ovok_error_1 = require("../errors/ovok-error");
6
7
  const account_storage_1 = require("./account-storage");
7
8
  const session_http_1 = require("./session-http");
9
+ const LoginError_1 = require("./types/LoginError");
10
+ /** These POSTs authenticate a new session; a failed attempt must not invalidate an existing one. */
11
+ const isSignInRequest = (url, options, normalizedBaseUrl) => {
12
+ var _a;
13
+ if (((_a = options === null || options === void 0 ? void 0 : options.method) === null || _a === void 0 ? void 0 : _a.toUpperCase()) !== "POST") {
14
+ return false;
15
+ }
16
+ const baseUrl = new URL(normalizedBaseUrl);
17
+ const requestUrl = new URL(url, normalizedBaseUrl);
18
+ if (requestUrl.origin !== baseUrl.origin) {
19
+ return false;
20
+ }
21
+ const path = (requestUrl.pathname.startsWith(baseUrl.pathname)
22
+ ? requestUrl.pathname.slice(baseUrl.pathname.length)
23
+ : requestUrl.pathname.slice(1)).replace(/^\/+|\/+$/g, "");
24
+ return [
25
+ /^auth\/login$/,
26
+ /^v2\/auth\/login$/,
27
+ /^auth\/tenant\/(?:Patient|Practitioner)\/login\/(?:start|mfa|token)$/,
28
+ /^v1\/saas\/login(?:\/token)?$/,
29
+ ].some((route) => route.test(path));
30
+ };
8
31
  /**
9
32
  * The client's `fetch`: request timeouts, token-refresh retries within the access token's
10
33
  * lifetime, and tracking why a session ended, which `onUnauthenticated` reports.
@@ -28,6 +51,15 @@ function createSessionFetch({ config, storage, fetchImplementation, normalizedBa
28
51
  };
29
52
  const fetchOnce = async (url, options) => {
30
53
  const response = await (0, session_http_1.fetchWithTimeout)(fetchImplementation, url, options, config.requestTimeoutMs);
54
+ const signInRequest = isSignInRequest(url, options, normalizedBaseUrl);
55
+ const retry = !response.ok && !isTokenRefresh(url, options)
56
+ ? { milliseconds: await (0, session_http_1.retryAfterMs)(response) }
57
+ : undefined;
58
+ if ((retry === null || retry === void 0 ? void 0 : retry.milliseconds) !== undefined &&
59
+ (response.status === 429 || response.status === 503) &&
60
+ options) {
61
+ options.maxRetryTime = 0;
62
+ }
31
63
  const requestUrl = new URL(url, normalizedBaseUrl);
32
64
  if (isTokenRefresh(url, options)) {
33
65
  if (response.status === 400) {
@@ -63,12 +95,18 @@ function createSessionFetch({ config, storage, fetchImplementation, normalizedBa
63
95
  status: response.status,
64
96
  };
65
97
  }
66
- else if (response.status === 401) {
98
+ else if (response.status === 401 && !signInRequest) {
67
99
  unauthenticatedReason = { type: "request-unauthorized", status: 401 };
68
100
  }
69
- return isTokenRefresh(url, options)
101
+ const responseWithMetadata = isTokenRefresh(url, options)
70
102
  ? response
71
- : (0, session_http_1.withHttpErrorMetadata)(response);
103
+ : await (0, session_http_1.withHttpErrorMetadata)(response, retry);
104
+ if (signInRequest && (response.status === 401 || response.status === 429)) {
105
+ const status = response.status;
106
+ const details = (0, ovok_error_1.readOvokError)({ outcome: await responseWithMetadata.clone().json() });
107
+ throw new LoginError_1.LoginError(status === 401 ? "invalid_credentials" : "rate_limited", details.message, [], details.requestId, status, details.retryAfterMs);
108
+ }
109
+ return responseWithMetadata;
72
110
  };
73
111
  const fetchWithRefreshRetry = async (url, options) => {
74
112
  var _a, _b, _c, _d, _e;
@@ -150,7 +188,7 @@ function createSessionFetch({ config, storage, fetchImplementation, normalizedBa
150
188
  break;
151
189
  }
152
190
  const backoffMs = (0, session_http_1.refreshBackoffMs)(attempt);
153
- const waitMs = Math.min(Math.max((_b = (0, session_http_1.retryAfterMs)(lastResponse)) !== null && _b !== void 0 ? _b : 0, backoffMs), remainingMs);
191
+ const waitMs = Math.min(Math.max((_b = (await (0, session_http_1.retryAfterMs)(lastResponse))) !== null && _b !== void 0 ? _b : 0, backoffMs), remainingMs);
154
192
  if (waitMs >= remainingMs) {
155
193
  await (0, session_http_1.waitForRetry)(remainingMs, signal);
156
194
  break;
@@ -1,5 +1,7 @@
1
- export declare const retryAfterMs: (response: Response) => number | undefined;
2
- export declare const withHttpErrorMetadata: (response: Response) => Promise<Response>;
1
+ export declare const retryAfterMs: (response: Response) => Promise<number | undefined>;
2
+ export declare const withHttpErrorMetadata: (response: Response, retry?: {
3
+ milliseconds: number | undefined;
4
+ }) => Promise<Response>;
3
5
  export declare const waitForRetry: (milliseconds: number, signal?: AbortSignal) => Promise<void>;
4
6
  export declare const isTemporaryRefreshFailure: (status: number) => boolean;
5
7
  export declare const refreshUnavailableError: (response?: Response) => Error;
@@ -2,23 +2,31 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.refreshBackoffMs = exports.fetchWithTimeout = exports.isNetworkError = exports.refreshResponseError = exports.refreshUnavailableError = exports.isTemporaryRefreshFailure = exports.waitForRetry = exports.withHttpErrorMetadata = exports.retryAfterMs = void 0;
4
4
  const ovok_error_utils_1 = require("../errors/ovok-error-utils");
5
- const retryAfterMs = (response) => {
5
+ const retryAfterMs = async (response) => {
6
6
  var _a, _b;
7
7
  const value = (_b = (_a = response.headers) === null || _a === void 0 ? void 0 : _a.get) === null || _b === void 0 ? void 0 : _b.call(_a, "Retry-After");
8
- if (!value) {
9
- return undefined;
8
+ if (value) {
9
+ const seconds = Number(value);
10
+ if (Number.isFinite(seconds)) {
11
+ return Math.max(0, seconds * 1000);
12
+ }
13
+ const date = Date.parse(value);
14
+ if (Number.isFinite(date)) {
15
+ return Math.max(0, date - Date.now());
16
+ }
17
+ }
18
+ try {
19
+ const body = await response.clone().json();
20
+ return typeof body.retryAfterSec === "number" ? Math.max(0, body.retryAfterSec * 1000) : undefined;
10
21
  }
11
- const seconds = Number(value);
12
- if (Number.isFinite(seconds)) {
13
- return Math.max(0, seconds * 1000);
22
+ catch (_c) {
23
+ return undefined;
14
24
  }
15
- const date = Date.parse(value);
16
- return Number.isFinite(date) ? Math.max(0, date - Date.now()) : undefined;
17
25
  };
18
26
  exports.retryAfterMs = retryAfterMs;
19
27
  const timeoutError = () => (0, ovok_error_utils_1.namedError)("TimeoutError", "The request timed out.");
20
- const withHttpErrorMetadata = async (response) => {
21
- var _a, _b;
28
+ const withHttpErrorMetadata = async (response, retry) => {
29
+ var _a;
22
30
  if (response.ok || typeof response.clone !== "function") {
23
31
  return response;
24
32
  }
@@ -28,7 +36,7 @@ const withHttpErrorMetadata = async (response) => {
28
36
  bodyText = await response.clone().text();
29
37
  body = JSON.parse(bodyText);
30
38
  }
31
- catch (_c) {
39
+ catch (_b) {
32
40
  body = undefined;
33
41
  }
34
42
  const outcome = body && typeof body === "object" && body.resourceType === "OperationOutcome"
@@ -42,8 +50,8 @@ const withHttpErrorMetadata = async (response) => {
42
50
  ? Object.fromEntries(Object.entries(envelope).filter(([key]) => ["code", "error", "message", "requestId", "retryAfterSec", "status", "statusCode", "errors", "missing"].includes(key)))
43
51
  : { message: typeof body === "string" ? body : response.statusText };
44
52
  const status = response.status;
45
- const retryAfter = (_a = (0, exports.retryAfterMs)(response)) !== null && _a !== void 0 ? _a : (typeof (envelope === null || envelope === void 0 ? void 0 : envelope.retryAfterSec) === "number" ? envelope.retryAfterSec * 1000 : undefined);
46
- const requestId = (_b = response.headers.get("x-request-id")) !== null && _b !== void 0 ? _b : (typeof (envelope === null || envelope === void 0 ? void 0 : envelope.requestId) === "string" ? envelope.requestId : undefined);
53
+ const retryAfter = retry ? retry.milliseconds : await (0, exports.retryAfterMs)(response);
54
+ const requestId = (_a = response.headers.get("x-request-id")) !== null && _a !== void 0 ? _a : (typeof (envelope === null || envelope === void 0 ? void 0 : envelope.requestId) === "string" ? envelope.requestId : undefined);
47
55
  const message = typeof (envelope === null || envelope === void 0 ? void 0 : envelope.message) === "string"
48
56
  ? envelope.message
49
57
  : typeof (nestedError === null || nestedError === void 0 ? void 0 : nestedError.message) === "string"
@@ -79,6 +87,7 @@ const withHttpErrorMetadata = async (response) => {
79
87
  ];
80
88
  const headers = new Headers(response.headers);
81
89
  headers.delete("content-length");
90
+ headers.delete("content-encoding");
82
91
  headers.set("content-type", "application/fhir+json");
83
92
  return new Response(JSON.stringify(enrichedOutcome), {
84
93
  status,
@@ -1,12 +1,13 @@
1
1
  import type { LoginResponse } from "./LoginResponse";
2
2
  import type { StartLoginResponse } from "./StartLoginResponse";
3
- /** A login selection error with fields apps can branch on without parsing prose. */
3
+ /** A typed sign-in error with fields apps can branch on without parsing prose. */
4
4
  export declare class LoginError extends Error {
5
- readonly code: "profile_not_found" | "tenant_code_required" | "tenant_code_not_found";
5
+ readonly code: "profile_not_found" | "tenant_code_required" | "tenant_code_not_found" | "invalid_credentials" | "rate_limited";
6
6
  readonly profiles: LoginProfile[];
7
- readonly status = 400;
7
+ readonly status: 400 | 401 | 429;
8
8
  readonly requestId: string | undefined;
9
- constructor(code: "profile_not_found" | "tenant_code_required" | "tenant_code_not_found", message: string, profiles?: LoginProfile[], requestId?: string);
9
+ readonly retryAfterMs: number | undefined;
10
+ constructor(code: "profile_not_found" | "tenant_code_required" | "tenant_code_not_found" | "invalid_credentials" | "rate_limited", message: string, profiles?: LoginProfile[], requestId?: string, status?: 400 | 401 | 429, retryAfterMs?: number);
10
11
  }
11
12
  export type LoginProfile = NonNullable<StartLoginResponse["profiles"]>[number];
12
13
  export type TenantLoginStart = {
@@ -1,15 +1,16 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.LoginError = void 0;
4
- /** A login selection error with fields apps can branch on without parsing prose. */
4
+ /** A typed sign-in error with fields apps can branch on without parsing prose. */
5
5
  class LoginError extends Error {
6
- constructor(code, message, profiles = [], requestId) {
6
+ constructor(code, message, profiles = [], requestId, status = 400, retryAfterMs) {
7
7
  super(message);
8
8
  this.code = code;
9
9
  this.profiles = profiles;
10
- this.status = 400;
11
10
  this.name = "LoginError";
11
+ this.status = status;
12
12
  this.requestId = requestId;
13
+ this.retryAfterMs = retryAfterMs;
13
14
  }
14
15
  }
15
16
  exports.LoginError = LoginError;
@@ -9,9 +9,10 @@ function serializeChangeHistoryQuery(params = {}) {
9
9
  if (value === undefined) {
10
10
  continue;
11
11
  }
12
- if (key === "types" && Array.isArray(value)) {
13
- for (const type of value) {
14
- search.append("types[]", type);
12
+ if (Array.isArray(value)) {
13
+ const name = key === "types" ? "types[]" : key;
14
+ for (const item of value) {
15
+ search.append(name, item);
15
16
  }
16
17
  }
17
18
  else {
@@ -1,28 +1,41 @@
1
- export type ChangeHistoryKey = "deviceCreated" | "deviceDeleted" | "deviceProjectChanged" | "deviceLocationChanged" | "deviceZoneChanged" | "deviceRenamed" | "locationCreated" | "locationDeleted" | "locationRenamed" | "locationZoneChanged" | "locationFloorChanged" | "locationRoomChanged" | "locationBedChanged" | "zoneCreated" | "zoneDeleted" | "zoneRenamed";
1
+ export type ChangeHistoryKey = "residentDeleted" | "residentMaidenNameChanged" | "orgThresholdRangeChanged" | "patientSignalsThresholdSet" | "patientSignalsThresholdRemoved" | "orgSignalsSettingsChanged" | "userInvited" | "userRenamed" | "userRoleChanged" | "userRemoved" | "roleCreated" | "roleDeleted" | "rolePermissionsChanged" | "residentAssignedToDeviceAndLocation" | "residentUnassignedFromDevicesAndLocation" | "residentUnassignedFromDevicesAndLocationWithZone" | "residentUnassignedFromDeviceAndLocation" | "residentDeviceAssigned" | "residentDeviceUnassigned" | "residentMovedToLocation" | "residentRemovedFromLocation" | "residentMovedToZone" | "residentRemovedFromZone" | "patientThresholdMetricSetToIndividual" | "patientThresholdMetricSetToGlobal" | "patientTimeOutOfBedMetricSetToIndividual" | "patientTimeOutOfBedMetricSetToGlobal" | "orgTimeOutOfBedMetricUpdated" | "residentDischarged" | "residentReactivated" | "residentArchived" | "residentNameChanged" | "orgThresholdMetricUpdated" | "patientThresholdMetricUpdated" | "deviceCreated" | "deviceDeleted" | "deviceProjectChanged" | "deviceLocationChanged" | "deviceZoneChanged" | "deviceRenamed" | "locationCreated" | "locationDeleted" | "locationRenamed" | "locationZoneChanged" | "locationFloorChanged" | "locationRoomChanged" | "locationBedChanged" | "zoneCreated" | "zoneDeleted" | "zoneRenamed";
2
+ export type ChangeHistoryType = "Threshold" | "Location" | "Zone" | "Device" | "Resident" | "Access";
3
+ export type ChangeHistoryScope = "all" | "global";
4
+ export type ChangeHistorySort = "asc" | "desc";
5
+ export type ChangeHistoryAction = "created" | "deleted" | "renamed" | "moved" | "assigned" | "unassigned" | "discharged" | "reactivated" | "thresholdChanged";
6
+ export type ChangeHistoryResident = {
7
+ patientId: string;
8
+ residentId: string | null;
9
+ };
10
+ export type ChangeHistoryReference = `Patient/${string}` | `Device/${string}` | `Location/${string}` | `Organization/${string}` | `Practitioner/${string}` | `AccessPolicy/${string}`;
2
11
  export type ChangeHistoryRow = {
3
12
  id: string;
4
- type: string;
5
- user: Record<string, unknown> | null;
6
- residentId: string | null;
7
- deviceId: string | null;
13
+ type: ChangeHistoryType;
14
+ user: string;
15
+ residentId: string;
16
+ deviceId: string;
17
+ resident: ChangeHistoryResident | null;
8
18
  locationId?: string | null;
9
19
  zoneId?: string | null;
10
20
  time: string;
11
21
  details: {
12
22
  key: ChangeHistoryKey | (string & {});
13
- values: Record<string, unknown>;
23
+ values: Record<string, string | number | null>;
14
24
  };
15
25
  };
16
26
  export type ChangeHistoryQuery = {
17
27
  page?: number;
18
28
  count?: number;
19
- types?: readonly string[];
20
- scope?: string;
21
- userId?: string;
22
- residentId?: string;
23
- deviceId?: string;
24
- locationId?: string;
25
- zoneId?: string;
29
+ types?: readonly ChangeHistoryType[];
30
+ scope?: ChangeHistoryScope;
31
+ userId?: string | readonly string[];
32
+ residentId?: string | readonly string[];
33
+ deviceId?: string | readonly string[];
34
+ locationId?: string | readonly string[];
35
+ zoneId?: string | readonly string[];
36
+ relatedTo?: ChangeHistoryReference | readonly ChangeHistoryReference[];
37
+ actions?: ChangeHistoryAction | readonly ChangeHistoryAction[];
38
+ sort?: ChangeHistorySort;
26
39
  startDate?: string;
27
40
  endDate?: string;
28
41
  };
@@ -103,8 +103,8 @@ function mapOvokApiError(error, options = {}) {
103
103
  }
104
104
  const details = (0, exports.readOvokError)(error);
105
105
  const typedStatuses = options.includeAccessErrors
106
- ? [403, 404, 409, 422, 424, 503]
107
- : [409, 422, 424, 503];
106
+ ? [403, 404, 409, 410, 422, 424, 503]
107
+ : [409, 410, 422, 424, 503];
108
108
  return typedStatuses.includes((_a = details.status) !== null && _a !== void 0 ? _a : 0)
109
109
  ? new OvokApiError(details, error)
110
110
  : error;
@@ -128,6 +128,8 @@ export type SlimMedicalEpisodeValue = {
128
128
  observedAt: string | null;
129
129
  recordedAt: string;
130
130
  };
131
+ /** STALE closes an out-of-bed episode that stayed open for 48 hours. */
132
+ export type SlimEpisodeCloseReason = "RECOVERED" | "TIMEOUT" | "ACKNOWLEDGED" | "CROSSED" | "THRESHOLD_CHANGED" | "AWAY" | "STALE" | "MANUAL";
131
133
  export type SlimMedicalEpisodeCore = {
132
134
  id: string;
133
135
  patientId: string;
@@ -137,7 +139,7 @@ export type SlimMedicalEpisodeCore = {
137
139
  status: string;
138
140
  openedAt: string;
139
141
  closedAt: string | null;
140
- closeReason: string | null;
142
+ closeReason: SlimEpisodeCloseReason | (string & {}) | null;
141
143
  lastSampleAt: string | null;
142
144
  durationMs: number | null;
143
145
  observationCount: number | null;
@@ -136,6 +136,7 @@ export declare class OvokClient extends MedplumClient {
136
136
  * account differs from `account`: the rest of a reading is never written under someone else's sign-in.
137
137
  */
138
138
  private executeObservationBundle;
139
+ private mapError;
139
140
  private bindMethods;
140
141
  /**
141
142
  * Gets the `SubscriptionManager` for WebSocket subscriptions.
@@ -43,6 +43,7 @@ const errors_1 = require("./auth/errors");
43
43
  const authMethods = __importStar(require("./auth/methods"));
44
44
  const session_fetch_1 = require("./auth/session-fetch");
45
45
  const session_http_1 = require("./auth/session-http");
46
+ const LoginError_1 = require("./auth/types/LoginError");
46
47
  const botMethods = __importStar(require("./bot/methods"));
47
48
  const changeHistoryMethods = __importStar(require("./change-history/methods"));
48
49
  const aiMethods = __importStar(require("./chat/ai/methods"));
@@ -125,7 +126,7 @@ class OvokClient extends core_1.MedplumClient {
125
126
  this.refreshState = session.refreshState;
126
127
  this.socialLoginClientId = (_e = config.socialLoginClientId) !== null && _e !== void 0 ? _e : "";
127
128
  this.addEventListener("profileRefreshed", () => this.syncSavedAccountProfile());
128
- this.bindMethods(authMethods);
129
+ this.bindMethods(authMethods, (error) => error instanceof LoginError_1.LoginError ? error : this.mapError(error));
129
130
  this.bindMethods(observationMethods);
130
131
  this.bindMethods(questionnaireResponseMethods);
131
132
  this.bindMethods(aiMethods);
@@ -261,7 +262,7 @@ class OvokClient extends core_1.MedplumClient {
261
262
  ((_b = previousLogin.profile) === null || _b === void 0 ? void 0 : _b.reference) !== accountId) {
262
263
  await this.restoreActiveLogin(previousLogin);
263
264
  }
264
- throw error;
265
+ throw this.mapError(error);
265
266
  }
266
267
  }
267
268
  /**
@@ -284,7 +285,7 @@ class OvokClient extends core_1.MedplumClient {
284
285
  }
285
286
  catch (error) {
286
287
  if (!sessionClient.getSavedAccounts().some((account) => account.id === accountId && account.expired)) {
287
- throw error;
288
+ throw this.mapError(error);
288
289
  }
289
290
  }
290
291
  this.removeSavedAccount(accountId);
@@ -326,11 +327,21 @@ class OvokClient extends core_1.MedplumClient {
326
327
  }
327
328
  /** Reads Ovok's composed FHIR and public-route capability statement. */
328
329
  async getCapabilityStatement(version = "R4", requestOptions) {
329
- return this.get(`/fhir/${version}/metadata`, ...(0, request_options_1.optionalArg)(requestOptions));
330
+ try {
331
+ return await this.get(`/fhir/${version}/metadata`, ...(0, request_options_1.optionalArg)(requestOptions));
332
+ }
333
+ catch (error) {
334
+ throw this.mapError(error);
335
+ }
330
336
  }
331
337
  /** Returns the non-FHIR public routes published by the backend statement. */
332
338
  async getPublicApiEndpoints(version = "R4", requestOptions) {
333
- return (0, capability_1.getOvokPublicApiEndpoints)(await this.getCapabilityStatement(version, ...(0, request_options_1.optionalArg)(requestOptions)));
339
+ try {
340
+ return (0, capability_1.getOvokPublicApiEndpoints)(await this.getCapabilityStatement(version, ...(0, request_options_1.optionalArg)(requestOptions)));
341
+ }
342
+ catch (error) {
343
+ throw this.mapError(error);
344
+ }
334
345
  }
335
346
  /**
336
347
  * Saves one reading's observations, main first, with conditional creates so a retry cannot create a duplicate.
@@ -518,16 +529,24 @@ class OvokClient extends core_1.MedplumClient {
518
529
  * account differs from `account`: the rest of a reading is never written under someone else's sign-in.
519
530
  */
520
531
  async executeObservationBundle(bundle, requestOptions, account = this.accountKey()) {
521
- const response = await (0, observation_bundle_utils_1.executeObservationBundle)(bundle, (request) => {
522
- if (this.accountKey() !== account) {
523
- throw new Error("The signed-in account changed during the save; the rest of it was not sent.");
524
- }
525
- return this.executeBatch(request, ...(0, request_options_1.optionalArg)(requestOptions));
526
- });
527
- this.invalidateSearches("Observation");
528
- return response;
532
+ try {
533
+ const response = await (0, observation_bundle_utils_1.executeObservationBundle)(bundle, (request) => {
534
+ if (this.accountKey() !== account) {
535
+ throw new Error("The signed-in account changed during the save; the rest of it was not sent.");
536
+ }
537
+ return this.executeBatch(request, ...(0, request_options_1.optionalArg)(requestOptions));
538
+ });
539
+ this.invalidateSearches("Observation");
540
+ return response;
541
+ }
542
+ catch (error) {
543
+ throw this.mapError(error);
544
+ }
545
+ }
546
+ mapError(error) {
547
+ return (0, ovok_error_1.mapOvokApiError)((0, rate_limit_error_1.mapRateLimitError)(error));
529
548
  }
530
- bindMethods(methods, mapError = (error) => (0, ovok_error_1.mapOvokApiError)((0, rate_limit_error_1.mapRateLimitError)(error))) {
549
+ bindMethods(methods, mapError = (error) => this.mapError(error)) {
531
550
  for (const [name, method] of Object.entries(methods)) {
532
551
  const boundMethod = method.bind(this);
533
552
  this[name] = async (...args) => {
@@ -7,18 +7,22 @@ export declare function listSlimPatients(this: OvokClient, params?: SlimPatientL
7
7
  export declare function getSlimPatient(this: OvokClient, patientId: string, requestOptions?: MedplumRequestOptions): Promise<SlimPatient>;
8
8
  export declare function getSlimPatientEverything(this: OvokClient, patientId: string, requestOptions?: MedplumRequestOptions): Promise<Bundle<Resource>>;
9
9
  export declare function getSlimPatientCurrentVitals(this: OvokClient, patientId: string, requestOptions?: MedplumRequestOptions): Promise<Record<string, unknown>>;
10
+ /** 422 when organizationId has no locationId or deviceId, or the assigned devices span zones. */
10
11
  export declare function createSlimPatient(this: OvokClient, body: SlimCreatePatient, requestOptions?: MedplumRequestOptions): Promise<SlimPatient>;
12
+ /** Replaces editable fields; omitting locationId unassigns devices; 422 for null, empty, or [] deviceId with a location or devices across zones. Names are trimmed. */
11
13
  export declare function updateSlimPatient(this: OvokClient, patientId: string, body: SlimUpdatePatient, requestOptions?: MedplumRequestOptions): Promise<SlimPatient>;
14
+ /** 409 while the resident has a device; unassign it first with updateSlimPatient and no locationId. */
12
15
  export declare function deleteSlimPatient(this: OvokClient, patientId: string, requestOptions?: MedplumRequestOptions): Promise<void>;
13
16
  export declare function listSlimDevices(this: OvokClient, params?: SlimDeviceListQuery, requestOptions?: MedplumRequestOptions): Promise<SlimPage<SlimDeviceListItem>>;
14
17
  export declare function getSlimDevice(this: OvokClient, deviceId: string, requestOptions?: MedplumRequestOptions): Promise<SlimDevice>;
18
+ /** 409 when another customer holds the serial; 422 when this project already uses it or its organization differs from the room's zone. */
15
19
  export declare function createSlimDevice(this: OvokClient, body: SlimCreateDevice, requestOptions?: MedplumRequestOptions): Promise<SlimDevice>;
20
+ /** 409 when another customer holds the serial; 422 when this project already uses it or its organization differs from the room's zone. Renaming moves the serial, so old-serial readings stop and live values return after a reading under the new serial. */
16
21
  export declare function updateSlimDevice(this: OvokClient, deviceId: string, body: SlimUpdateDevice, requestOptions?: MedplumRequestOptions): Promise<SlimDevice>;
17
22
  export declare function deleteSlimDevice(this: OvokClient, deviceId: string, requestOptions?: MedplumRequestOptions): Promise<void>;
18
23
  export declare function assignSlimDeviceToProject(this: OvokClient, body: SlimAssignDevice, requestOptions?: MedplumRequestOptions): Promise<SlimDevice>;
19
24
  export declare function listSlimLiveDevices(this: OvokClient, params?: SlimLiveQuery, requestOptions?: MedplumRequestOptions): Promise<SlimPage<Record<string, unknown>>>;
20
25
  export declare function listSlimLiveTelemetry(this: OvokClient, params?: SlimLiveQuery, requestOptions?: MedplumRequestOptions): Promise<SlimLiveTelemetry>;
21
- export declare function getSlimDeviceSleepStatus(this: OvokClient, deviceId: string, requestOptions?: MedplumRequestOptions): Promise<Record<string, unknown>>;
22
26
  export declare function getSlimDeviceTelemetry(this: OvokClient, deviceId: string, requestOptions?: MedplumRequestOptions): Promise<SlimDeviceTelemetry>;
23
27
  export declare function listSlimEffectiveObservationIds(this: OvokClient, requestOptions?: MedplumRequestOptions): Promise<Record<string, SlimObservationIds>>;
24
28
  export declare function listSlimLastObservationIds(this: OvokClient, requestOptions?: MedplumRequestOptions): Promise<Record<string, SlimObservationIds>>;
@@ -28,6 +32,7 @@ export declare function getSlimDeviceLastObservationIds(this: OvokClient, device
28
32
  export declare function listSlimNotifications(this: OvokClient, params?: SlimNotificationsQuery, requestOptions?: MedplumRequestOptions): Promise<SlimNotificationsResponse>;
29
33
  export declare function getRecentSlimNotifications(this: OvokClient, count?: number, requestOptions?: MedplumRequestOptions): Promise<SlimRecentNotificationsResponse>;
30
34
  export declare function listSlimAlarms(this: OvokClient, params?: SlimAlarmQuery, requestOptions?: MedplumRequestOptions): Promise<SlimAlarmsPage>;
35
+ /** Can take about 12 s; a 503 signals_unavailable means it was not confirmed, so repeat after retryAfterMs (safe to repeat). */
31
36
  export declare function acknowledgeSlimAlarm(this: OvokClient, notificationId: string, requestOptions?: MedplumRequestOptions): Promise<SlimNotification>;
32
37
  export declare function getRecentSlimAlarms(this: OvokClient, count?: number, requestOptions?: MedplumRequestOptions): Promise<SlimRecentAlarms>;
33
38
  /** Same as `getRecentSlimAlarms`. */
@@ -35,24 +40,33 @@ export declare function getRecentAlarms(this: OvokClient, count?: number, reques
35
40
  export declare function getRecentSlimOutOfBed(this: OvokClient, count?: number, requestOptions?: MedplumRequestOptions): Promise<SlimRecentOutOfBed>;
36
41
  /** Same as `getRecentSlimOutOfBed`. */
37
42
  export declare function getRecentOutOfBed(this: OvokClient, count?: number, requestOptions?: MedplumRequestOptions): Promise<SlimRecentOutOfBed>;
43
+ /** Omitting status lists new and reviewed rows; active is new, completed reviewed, and revoked hand-closed. */
38
44
  export declare function listSlimOutOfBedNotifications(this: OvokClient, params?: Omit<SlimNotificationsQuery, "type">, requestOptions?: MedplumRequestOptions): Promise<SlimNotificationsResponse>;
39
45
  export declare function listSlimMedicalNotifications(this: OvokClient, params?: SlimMedicalNotificationsQuery, requestOptions?: MedplumRequestOptions): Promise<SlimMedicalNotificationsPage>;
46
+ /** During a Signals outage, this may return the last good page from up to 10 minutes ago, without a marker. */
40
47
  export declare function getRecentSlimMedicalNotifications(this: OvokClient, count?: number, requestOptions?: MedplumRequestOptions): Promise<SlimRecentNotificationsResponse>;
41
48
  export declare function getSlimMedicalNotification(this: OvokClient, episodeId: string, requestOptions?: MedplumRequestOptions): Promise<SlimMedicalNotificationDetail>;
49
+ /** Can take about 12 s; a 503 signals_unavailable means it was not confirmed, so repeat after retryAfterMs (safe to repeat). */
42
50
  export declare function acknowledgeSlimMedicalNotification(this: OvokClient, episodeId: string, requestOptions?: MedplumRequestOptions): Promise<SlimMedicalNotificationRow>;
43
51
  /** Same as `acknowledgeSlimAlarm`. */
44
52
  export declare function acknowledgeSlimNotification(this: OvokClient, notificationId: string, requestOptions?: MedplumRequestOptions): Promise<SlimNotification>;
53
+ /** A home with no stored limits reads as the default template, not as unset. */
45
54
  export declare function getSlimThreshold(this: OvokClient, requestOptions?: MedplumRequestOptions): Promise<SlimThreshold>;
55
+ /** A 502 means Signals did not take every resident's limits and nothing was written. */
46
56
  export declare function updateSlimThreshold(this: OvokClient, body: {
47
57
  settings: SlimThresholdSettingsUpdateV1;
48
58
  }, requestOptions?: MedplumRequestOptions): Promise<SlimThreshold>;
49
59
  export declare function getSlimPatientThreshold(this: OvokClient, patientId: string, requestOptions?: MedplumRequestOptions): Promise<Record<string, unknown>>;
60
+ /** 409 means another save for this resident is running and nothing changed; wait briefly, then send again. */
50
61
  export declare function updateSlimPatientThreshold(this: OvokClient, patientId: string, body: {
51
62
  settings: SlimThresholdSettingsUpdateV1;
52
63
  }, requestOptions?: MedplumRequestOptions): Promise<Record<string, unknown>>;
64
+ /** A home with no stored limits reads as the default template, not as unset. */
53
65
  export declare function getSlimThresholdV2(this: OvokClient, requestOptions?: MedplumRequestOptions): Promise<SlimThresholdV2>;
66
+ /** A 502 means Signals did not take every resident's limits and nothing was written. */
54
67
  export declare function updateSlimThresholdV2(this: OvokClient, body: SlimThresholdV2Update, requestOptions?: MedplumRequestOptions): Promise<SlimThresholdV2>;
55
68
  export declare function getSlimPatientThresholdV2(this: OvokClient, patientId: string, requestOptions?: MedplumRequestOptions): Promise<SlimPatientThresholdV2>;
69
+ /** 409 means another save for this resident is running and nothing changed; wait briefly, then send again. */
56
70
  export declare function updateSlimPatientThresholdV2(this: OvokClient, patientId: string, body: SlimThresholdV2Update, requestOptions?: MedplumRequestOptions): Promise<SlimPatientThresholdV2>;
57
71
  export declare function getSlimTrends(this: OvokClient, params: {
58
72
  patientId: string;
@@ -60,6 +74,7 @@ export declare function getSlimTrends(this: OvokClient, params: {
60
74
  endDate: string;
61
75
  graphType: SlimHistoricalGraphType | readonly SlimHistoricalGraphType[];
62
76
  }, requestOptions?: MedplumRequestOptions): Promise<SlimTrends>;
77
+ /** At most 20 calls per minute per caller; load once per view and wait retryAfterMs after 503. */
63
78
  export declare function getSlimRealtimeTrends(this: OvokClient, params: {
64
79
  patientId: string;
65
80
  startTime: string;
@@ -74,6 +89,7 @@ export declare function getSlimSleepStatistics(this: OvokClient, params: {
74
89
  export declare function listSlimLocations(this: OvokClient, params?: SlimLocationQuery, requestOptions?: MedplumRequestOptions): Promise<SlimPage<SlimLocation>>;
75
90
  export declare function getSlimLocation(this: OvokClient, locationId: string, requestOptions?: MedplumRequestOptions): Promise<SlimLocation>;
76
91
  export declare function createSlimLocation(this: OvokClient, body: SlimCreateLocation, requestOptions?: MedplumRequestOptions): Promise<SlimLocation>;
92
+ /** 422 Device not found means the requested device is unreadable; nothing was saved. */
77
93
  export declare function updateSlimLocation(this: OvokClient, locationId: string, body: SlimUpdateLocation, requestOptions?: MedplumRequestOptions): Promise<SlimLocation>;
78
94
  export declare function deleteSlimLocation(this: OvokClient, locationId: string, requestOptions?: MedplumRequestOptions): Promise<void>;
79
95
  export declare function listSlimZones(this: OvokClient, params?: {
@@ -15,7 +15,6 @@ exports.deleteSlimDevice = deleteSlimDevice;
15
15
  exports.assignSlimDeviceToProject = assignSlimDeviceToProject;
16
16
  exports.listSlimLiveDevices = listSlimLiveDevices;
17
17
  exports.listSlimLiveTelemetry = listSlimLiveTelemetry;
18
- exports.getSlimDeviceSleepStatus = getSlimDeviceSleepStatus;
19
18
  exports.getSlimDeviceTelemetry = getSlimDeviceTelemetry;
20
19
  exports.listSlimEffectiveObservationIds = listSlimEffectiveObservationIds;
21
20
  exports.listSlimLastObservationIds = listSlimLastObservationIds;
@@ -97,12 +96,15 @@ async function getSlimPatientEverything(patientId, requestOptions) {
97
96
  async function getSlimPatientCurrentVitals(patientId, requestOptions) {
98
97
  return this.get(`/v1/slim/patient/${encodeURIComponent(patientId)}/current-vitals`, ...(0, request_options_1.optionalArg)(requestOptions));
99
98
  }
99
+ /** 422 when organizationId has no locationId or deviceId, or the assigned devices span zones. */
100
100
  async function createSlimPatient(body, requestOptions) {
101
101
  return this.post("/v1/slim/patient", body, undefined, requestOptions);
102
102
  }
103
+ /** Replaces editable fields; omitting locationId unassigns devices; 422 for null, empty, or [] deviceId with a location or devices across zones. Names are trimmed. */
103
104
  async function updateSlimPatient(patientId, body, requestOptions) {
104
105
  return (await this.put(`/v1/slim/patient/${encodeURIComponent(patientId)}`, body, undefined, requestOptions));
105
106
  }
107
+ /** 409 while the resident has a device; unassign it first with updateSlimPatient and no locationId. */
106
108
  async function deleteSlimPatient(patientId, requestOptions) {
107
109
  await this.delete(`/v1/slim/patient/${encodeURIComponent(patientId)}`, ...(0, request_options_1.optionalArg)(requestOptions));
108
110
  }
@@ -112,9 +114,11 @@ async function listSlimDevices(params = {}, requestOptions) {
112
114
  async function getSlimDevice(deviceId, requestOptions) {
113
115
  return this.get(`/v1/slim/device/${encodeURIComponent(deviceId)}`, ...(0, request_options_1.optionalArg)(requestOptions));
114
116
  }
117
+ /** 409 when another customer holds the serial; 422 when this project already uses it or its organization differs from the room's zone. */
115
118
  async function createSlimDevice(body, requestOptions) {
116
119
  return this.post("/v1/slim/device", body, undefined, requestOptions);
117
120
  }
121
+ /** 409 when another customer holds the serial; 422 when this project already uses it or its organization differs from the room's zone. Renaming moves the serial, so old-serial readings stop and live values return after a reading under the new serial. */
118
122
  async function updateSlimDevice(deviceId, body, requestOptions) {
119
123
  return (await this.put(`/v1/slim/device/${encodeURIComponent(deviceId)}`, body, undefined, requestOptions));
120
124
  }
@@ -132,9 +136,6 @@ async function listSlimLiveTelemetry(params = {}, requestOptions) {
132
136
  validateLiveCount(params);
133
137
  return (0, live_read_1.liveGet)(this, `/v1/slim/device/live/telemetry${(0, query_string_1.toQueryString)(params)}`, requestOptions);
134
138
  }
135
- async function getSlimDeviceSleepStatus(deviceId, requestOptions) {
136
- return (0, live_read_1.liveGet)(this, `/v1/slim/device/live/cloudwatch/${encodeURIComponent(deviceId)}`, requestOptions);
137
- }
138
139
  async function getSlimDeviceTelemetry(deviceId, requestOptions) {
139
140
  return (0, live_read_1.liveGet)(this, `/v1/slim/telemetry/${encodeURIComponent(deviceId)}`, requestOptions);
140
141
  }
@@ -160,6 +161,7 @@ async function getRecentSlimNotifications(count, requestOptions) {
160
161
  async function listSlimAlarms(params = {}, requestOptions) {
161
162
  return this.get(`/v1/slim/notifications/alarms${(0, query_string_1.toQueryString)(params)}`, { cache: "reload", ...requestOptions });
162
163
  }
164
+ /** Can take about 12 s; a 503 signals_unavailable means it was not confirmed, so repeat after retryAfterMs (safe to repeat). */
163
165
  async function acknowledgeSlimAlarm(notificationId, requestOptions) {
164
166
  return (await this.patch(`/v1/slim/notifications/${encodeURIComponent(notificationId)}/ack`, [], ...(0, request_options_1.optionalArg)(requestOptions)));
165
167
  }
@@ -177,6 +179,7 @@ async function getRecentSlimOutOfBed(count, requestOptions) {
177
179
  function getRecentOutOfBed(count, requestOptions) {
178
180
  return getRecentSlimOutOfBed.call(this, count, ...(0, request_options_1.optionalArg)(requestOptions));
179
181
  }
182
+ /** Omitting status lists new and reviewed rows; active is new, completed reviewed, and revoked hand-closed. */
180
183
  async function listSlimOutOfBedNotifications(params = {}, requestOptions) {
181
184
  return this.get(`/v1/slim/notifications/oob${(0, query_string_1.toQueryString)(params)}`, { cache: "reload", ...requestOptions });
182
185
  }
@@ -191,12 +194,14 @@ function validateMedicalCodes(codes) {
191
194
  throw new TypeError("codes must contain at most 32 valid LOINC codes or PI.");
192
195
  }
193
196
  }
197
+ /** During a Signals outage, this may return the last good page from up to 10 minutes ago, without a marker. */
194
198
  async function getRecentSlimMedicalNotifications(count, requestOptions) {
195
199
  return this.get(`/v1/slim/notifications/recent/medical${(0, query_string_1.toQueryString)({ count })}`, { cache: "reload", ...requestOptions });
196
200
  }
197
201
  async function getSlimMedicalNotification(episodeId, requestOptions) {
198
202
  return this.get(`/v1/slim/notifications/medical/${encodeURIComponent(episodeId)}`, { cache: "reload", ...requestOptions });
199
203
  }
204
+ /** Can take about 12 s; a 503 signals_unavailable means it was not confirmed, so repeat after retryAfterMs (safe to repeat). */
200
205
  async function acknowledgeSlimMedicalNotification(episodeId, requestOptions) {
201
206
  return this.post(`/v1/slim/notifications/medical/${encodeURIComponent(episodeId)}/ack`, undefined, undefined, requestOptions);
202
207
  }
@@ -204,33 +209,44 @@ async function acknowledgeSlimMedicalNotification(episodeId, requestOptions) {
204
209
  function acknowledgeSlimNotification(notificationId, requestOptions) {
205
210
  return acknowledgeSlimAlarm.call(this, notificationId, ...(0, request_options_1.optionalArg)(requestOptions));
206
211
  }
212
+ /** A home with no stored limits reads as the default template, not as unset. */
207
213
  async function getSlimThreshold(requestOptions) {
208
214
  return this.get("/v1/slim/threshold", ...(0, request_options_1.optionalArg)(requestOptions));
209
215
  }
216
+ /** A 502 means Signals did not take every resident's limits and nothing was written. */
210
217
  async function updateSlimThreshold(body, requestOptions) {
211
- return (await this.put("/v1/slim/threshold", body, undefined, requestOptions));
218
+ return (await this.put("/v1/slim/threshold", body, undefined, {
219
+ ...requestOptions, maxRetries: 0, maxRetryTime: 0,
220
+ }));
212
221
  }
213
222
  async function getSlimPatientThreshold(patientId, requestOptions) {
214
223
  return this.get(`/v1/slim/threshold/patient/${encodeURIComponent(patientId)}`, ...(0, request_options_1.optionalArg)(requestOptions));
215
224
  }
225
+ /** 409 means another save for this resident is running and nothing changed; wait briefly, then send again. */
216
226
  async function updateSlimPatientThreshold(patientId, body, requestOptions) {
217
- return (await this.put(`/v1/slim/threshold/patient/${encodeURIComponent(patientId)}`, body, undefined, requestOptions));
227
+ return (await this.put(`/v1/slim/threshold/patient/${encodeURIComponent(patientId)}`, body, undefined, { ...requestOptions, maxRetries: 0, maxRetryTime: 0 }));
218
228
  }
229
+ /** A home with no stored limits reads as the default template, not as unset. */
219
230
  async function getSlimThresholdV2(requestOptions) {
220
231
  return this.get("/v2/slim/threshold", ...(0, request_options_1.optionalArg)(requestOptions));
221
232
  }
233
+ /** A 502 means Signals did not take every resident's limits and nothing was written. */
222
234
  async function updateSlimThresholdV2(body, requestOptions) {
223
- return (await this.put("/v2/slim/threshold", body, undefined, requestOptions));
235
+ return (await this.put("/v2/slim/threshold", body, undefined, {
236
+ ...requestOptions, maxRetries: 0, maxRetryTime: 0,
237
+ }));
224
238
  }
225
239
  async function getSlimPatientThresholdV2(patientId, requestOptions) {
226
240
  return this.get(`/v2/slim/threshold/patient/${encodeURIComponent(patientId)}`, ...(0, request_options_1.optionalArg)(requestOptions));
227
241
  }
242
+ /** 409 means another save for this resident is running and nothing changed; wait briefly, then send again. */
228
243
  async function updateSlimPatientThresholdV2(patientId, body, requestOptions) {
229
- return (await this.put(`/v2/slim/threshold/patient/${encodeURIComponent(patientId)}`, body, undefined, requestOptions));
244
+ return (await this.put(`/v2/slim/threshold/patient/${encodeURIComponent(patientId)}`, body, undefined, { ...requestOptions, maxRetries: 0, maxRetryTime: 0 }));
230
245
  }
231
246
  async function getSlimTrends(params, requestOptions) {
232
247
  return this.get(`/v1/slim/trends/graphs${(0, query_string_1.toQueryString)(params)}`, ...(0, request_options_1.optionalArg)(requestOptions));
233
248
  }
249
+ /** At most 20 calls per minute per caller; load once per view and wait retryAfterMs after 503. */
234
250
  async function getSlimRealtimeTrends(params, requestOptions) {
235
251
  return this.get(`/v1/slim/trends/realtime${(0, query_string_1.toQueryString)(params)}`, ...(0, request_options_1.optionalArg)(requestOptions));
236
252
  }
@@ -246,6 +262,7 @@ async function getSlimLocation(locationId, requestOptions) {
246
262
  async function createSlimLocation(body, requestOptions) {
247
263
  return this.post("/v1/slim/location", body, undefined, requestOptions);
248
264
  }
265
+ /** 422 Device not found means the requested device is unreadable; nothing was saved. */
249
266
  async function updateSlimLocation(locationId, body, requestOptions) {
250
267
  return (await this.put(`/v1/slim/location/${encodeURIComponent(locationId)}`, body, undefined, requestOptions));
251
268
  }
@@ -38,9 +38,10 @@ export type SlimPatientListItem = {
38
38
  locationName: string | null;
39
39
  deviceId: string | null;
40
40
  deviceName: string | null;
41
- medicalSettings: string | null;
42
- bedActivitySettings: string | null;
41
+ medicalSettings: SlimSettingsScope | null;
42
+ bedActivitySettings: SlimSettingsScope | null;
43
43
  };
44
+ export type SlimSettingsScope = "inactive" | "global" | "individual";
44
45
  export type SlimPatientListQuery = {
45
46
  page?: number;
46
47
  count?: number;
@@ -51,8 +52,8 @@ export type SlimPatientListQuery = {
51
52
  includeDischargedRecent?: boolean;
52
53
  organizationId?: string;
53
54
  hasDevice?: boolean;
54
- medicalSettings?: string;
55
- bedActivitySettings?: string;
55
+ medicalSettings?: SlimSettingsScope;
56
+ bedActivitySettings?: SlimSettingsScope;
56
57
  };
57
58
  export type SlimCreatePatient = {
58
59
  name: SlimHumanName[];
@@ -63,6 +64,7 @@ export type SlimCreatePatient = {
63
64
  locationId?: string;
64
65
  admissionDate?: string;
65
66
  };
67
+ /** Replaces the editable fields; omitting locationId unassigns the resident's devices. */
66
68
  export type SlimUpdatePatient = Partial<SlimCreatePatient> & {
67
69
  residentState?: SlimResidentState;
68
70
  dischargeDate?: string;
@@ -222,6 +224,7 @@ export type SlimThresholdSettingsUpdateV1 = Record<string, SlimThresholdSettingU
222
224
  export type SlimThresholdV2Setting = {
223
225
  mode: string;
224
226
  deviationPercent: number | null;
227
+ /** Null when the bed window is 23 hours 59 minutes or longer. */
225
228
  baselineAverage: number | null;
226
229
  effectiveBand: {
227
230
  min: number;
@@ -239,7 +242,7 @@ export type SignalsSyncStatus = {
239
242
  reason: string;
240
243
  } | null;
241
244
  export type SlimThresholdV2 = {
242
- id?: string;
245
+ id: string | null;
243
246
  projectId?: string;
244
247
  status?: "active" | "retired";
245
248
  date?: string;
@@ -122,7 +122,6 @@ exports.OVOK_CORE_API_REQUIREMENTS = {
122
122
  { method: "PUT", path: "/v1/slim/device/assign/project", group: "Slim/Device" },
123
123
  { method: "GET", path: "/v1/slim/device/live", group: "Slim/Device" },
124
124
  { method: "GET", path: "/v1/slim/device/live/telemetry", group: "Slim/Device" },
125
- { method: "GET", path: "/v1/slim/device/live/cloudwatch/:deviceId", group: "Slim/Device" },
126
125
  { method: "GET", path: "/v1/slim/telemetry/:deviceId", group: "Slim/Telemetry" },
127
126
  { method: "GET", path: "/v1/slim/telemetry/effectiveIds", group: "Slim/Telemetry" },
128
127
  { method: "GET", path: "/v1/slim/telemetry/lastIds", group: "Slim/Telemetry" },
@@ -48,7 +48,7 @@ exports.OVOK_CORE_REQUIREMENTS = {
48
48
  description: "The FHIR surface a server must provide for the @ovok/core client library's own methods to function.",
49
49
  software: {
50
50
  name: "@ovok/core",
51
- version: "0.4.42",
51
+ version: "0.4.44",
52
52
  },
53
53
  fhirVersion: "4.0.1",
54
54
  format: ["json"],
@@ -7,6 +7,7 @@ export declare const useRecentSlimNotifications: (count?: number) => OvokRequest
7
7
  export declare const useSlimAlarms: (params?: SlimAlarmQuery) => OvokRequestState<SlimAlarmsPage>;
8
8
  export declare const useRecentAlarms: (count?: number) => OvokRequestState<SlimRecentAlarms>;
9
9
  export declare const useRecentOutOfBed: (count?: number) => OvokRequestState<SlimRecentOutOfBed>;
10
+ /** Without status, loads new and reviewed rows together; active is new, completed reviewed, and revoked hand-closed. */
10
11
  export declare const useSlimOutOfBedNotifications: (params?: Omit<SlimNotificationsQuery, "type">) => OvokRequestState<SlimNotificationsResponse>;
11
12
  /** Fetches every cursor page; a short page is not treated as the end of the feed. */
12
13
  export declare const useSlimMedicalNotifications: (params?: Omit<SlimMedicalNotificationsQuery, "cursor">, options?: {
@@ -73,6 +73,7 @@ const useRecentAlarms = (count) => useSlimNotificationFeed((client, signal) => c
73
73
  exports.useRecentAlarms = useRecentAlarms;
74
74
  const useRecentOutOfBed = (count) => useSlimNotificationFeed((client, signal) => client.getRecentOutOfBed(count, { signal }), count, "recent");
75
75
  exports.useRecentOutOfBed = useRecentOutOfBed;
76
+ /** Without status, loads new and reviewed rows together; active is new, completed reviewed, and revoked hand-closed. */
76
77
  const useSlimOutOfBedNotifications = (params = {}) => useSlimNotificationFeed((client, signal) => client.listSlimOutOfBedNotifications(params, { signal }), params, "resources");
77
78
  exports.useSlimOutOfBedNotifications = useSlimOutOfBedNotifications;
78
79
  /** Fetches every cursor page; a short page is not treated as the end of the feed. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ovok/core",
3
- "version": "0.4.42",
3
+ "version": "0.4.44",
4
4
  "private": false,
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",