@ovok/core 0.4.1 → 0.4.2

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
@@ -17,6 +17,7 @@ Core TypeScript SDK for healthcare applications. Provides authentication, AI-FHI
17
17
  - 🧭 Runtime discovery of the backend FHIR and public API capabilities
18
18
  - 🧩 Typed accounts, projects, localization, and patient-observation APIs
19
19
  - 🏥 Typed CareHub patient, device, telemetry, notification, and monitoring APIs
20
+ - 🚨 Typed Signals thresholds, alerts, and project settings APIs
20
21
 
21
22
  ## Installation
22
23
 
@@ -151,6 +152,40 @@ const slots = await client.listPatientSlots({
151
152
  });
152
153
  ```
153
154
 
155
+ ## Signals APIs
156
+
157
+ The Signals client methods and hooks cover resident thresholds, the project's alert feed, and
158
+ Signals tenant settings. Ovok forwards observations for enabled residents to Signals on the
159
+ backend; continue saving readings through the existing Observation APIs and do not submit the
160
+ same readings to Signals separately.
161
+
162
+ ```ts
163
+ const thresholds = await client.getSignalsThresholds(patientId);
164
+ const updated = await client.setSignalsThreshold(patientId, "8867-4", {
165
+ low: 50,
166
+ high: 120,
167
+ reason: "Updated after review",
168
+ });
169
+ const alerts = await client.listSignalsAlerts({
170
+ status: "OPEN",
171
+ codes: ["8867-4"],
172
+ limit: 25,
173
+ });
174
+ await client.acknowledgeSignalsAlert(alerts.items[0].id);
175
+ const settings = await client.getSignalsSettings();
176
+ ```
177
+
178
+ Use `editable` and `source` from threshold rows to present the available controls, but let the
179
+ backend enforce permissions. When lowering alert sensitivity or removing a bound, provide a
180
+ non-empty `reason`; the backend decides whether the caller has the required capability. A
181
+ `shared` settings tenant is read-only. Signal errors are exposed as `SignalsError` values and can
182
+ be checked with `isSignalsError`.
183
+
184
+ `useSignalsAlerts` accumulates pages through `loadMore`. Check `hasMore` rather than the current
185
+ item count: access filtering can produce a short or empty page with another cursor. Query changes
186
+ and `reload` start again at the first page. The hook exposes `reload` so a screen can refresh its
187
+ alert list after `useAcknowledgeSignalsAlert` succeeds.
188
+
154
189
  ## CareHub APIs
155
190
 
156
191
  CareHub methods cover the verified `/v1/slim/*` and `/v2/slim/*` application routes for
@@ -14,6 +14,7 @@ import * as patientMethods from "./patient/methods";
14
14
  import * as patientSharingMethods from "./patient-sharing/methods";
15
15
  import * as platformMethods from "./platform/methods";
16
16
  import * as questionnaireResponseMethods from "./questionnaire-response/methods";
17
+ import * as signalsMethods from "./signals/methods";
17
18
  export declare const MAX_CONDITIONAL_OBSERVATION_ENTRIES = 8;
18
19
  export { MAX_OBSERVATION_BUNDLE_BYTES } from "./observation/observation-bundle-utils";
19
20
  export type SaveObservationsResult = {
@@ -59,7 +60,7 @@ export declare class OvokClient extends MedplumClient {
59
60
  */
60
61
  getSubscriptionManager(): SubscriptionManager;
61
62
  }
62
- type Methods = typeof authMethods & typeof observationMethods & typeof questionnaireResponseMethods & typeof aiMethods & typeof aiFhirMethods & typeof botMethods & typeof carehubMethods & typeof platformMethods & typeof experienceMethods & typeof patientMethods & typeof patientSharingMethods;
63
+ type Methods = typeof authMethods & typeof observationMethods & typeof questionnaireResponseMethods & typeof aiMethods & typeof aiFhirMethods & typeof botMethods & typeof carehubMethods & typeof platformMethods & typeof experienceMethods & typeof patientMethods & typeof patientSharingMethods & typeof signalsMethods;
63
64
  type Omitted<T> = Omit<T, "executeBot">;
64
65
  declare module "./ovok-client" {
65
66
  interface OvokClient extends Omitted<Methods> {
@@ -51,6 +51,8 @@ const patientMethods = __importStar(require("./patient/methods"));
51
51
  const patientSharingMethods = __importStar(require("./patient-sharing/methods"));
52
52
  const platformMethods = __importStar(require("./platform/methods"));
53
53
  const questionnaireResponseMethods = __importStar(require("./questionnaire-response/methods"));
54
+ const errors_1 = require("./signals/errors");
55
+ const signalsMethods = __importStar(require("./signals/methods"));
54
56
  exports.MAX_CONDITIONAL_OBSERVATION_ENTRIES = 8;
55
57
  var observation_bundle_utils_2 = require("./observation/observation-bundle-utils");
56
58
  Object.defineProperty(exports, "MAX_OBSERVATION_BUNDLE_BYTES", { enumerable: true, get: function () { return observation_bundle_utils_2.MAX_OBSERVATION_BUNDLE_BYTES; } });
@@ -75,6 +77,7 @@ class OvokClient extends core_1.MedplumClient {
75
77
  this.bindMethods(experienceMethods);
76
78
  this.bindMethods(patientMethods);
77
79
  this.bindMethods(patientSharingMethods);
80
+ this.bindMethods(signalsMethods, (error) => (0, errors_1.mapSignalsError)((0, rate_limit_error_1.mapRateLimitError)(error), { signalsRequest: true }));
78
81
  this.clientStorage = config.storage;
79
82
  this.offlineQueueOptions = (_b = config.offlineQueue) !== null && _b !== void 0 ? _b : {};
80
83
  }
@@ -141,7 +144,7 @@ class OvokClient extends core_1.MedplumClient {
141
144
  async executeObservationBundle(bundle) {
142
145
  return (0, observation_bundle_utils_1.executeObservationBundle)(bundle, (request) => this.executeBatch(request));
143
146
  }
144
- bindMethods(methods) {
147
+ bindMethods(methods, mapError = rate_limit_error_1.mapRateLimitError) {
145
148
  for (const [name, method] of Object.entries(methods)) {
146
149
  const boundMethod = method.bind(this);
147
150
  this[name] = async (...args) => {
@@ -149,7 +152,7 @@ class OvokClient extends core_1.MedplumClient {
149
152
  return await boundMethod(...args);
150
153
  }
151
154
  catch (error) {
152
- throw (0, rate_limit_error_1.mapRateLimitError)(error);
155
+ throw mapError(error);
153
156
  }
154
157
  };
155
158
  }
@@ -0,0 +1,19 @@
1
+ import { SignalsErrorCode, SignalsFieldError } from "./types";
2
+ type SignalsErrorOptions = {
3
+ cause?: unknown;
4
+ fieldErrors?: SignalsFieldError[];
5
+ };
6
+ /** A typed error returned by an Ovok Signals API route. */
7
+ export declare class SignalsError extends Error {
8
+ readonly code: SignalsErrorCode;
9
+ readonly status: number;
10
+ readonly fieldErrors: SignalsFieldError[];
11
+ readonly cause: unknown;
12
+ constructor(code: SignalsErrorCode, status: number, message: string, options?: SignalsErrorOptions);
13
+ }
14
+ export declare function isSignalsError(error: unknown): error is SignalsError;
15
+ /** Converts the Signals error envelope while leaving unrelated errors untouched. */
16
+ export declare function mapSignalsError(error: unknown, options?: {
17
+ signalsRequest?: boolean;
18
+ }): unknown;
19
+ export {};
@@ -0,0 +1,184 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SignalsError = void 0;
4
+ exports.isSignalsError = isSignalsError;
5
+ exports.mapSignalsError = mapSignalsError;
6
+ const ovok_error_1 = require("../errors/ovok-error");
7
+ const SIGNALS_ERROR_STATUSES = {
8
+ signals_threshold_not_editable: 409,
9
+ signals_patient_not_enrolled: 409,
10
+ signals_conflict: 409,
11
+ signals_code_not_medical: 422,
12
+ signals_reason_required: 422,
13
+ signals_episodes_disabled: 409,
14
+ signals_bad_request: 422,
15
+ signals_shared_tenant: 409,
16
+ signals_audit_failed: 503,
17
+ signals_write_mismatch: 503,
18
+ signals_unavailable: 503,
19
+ signals_undo_failed: 503,
20
+ signals_forbidden: 403,
21
+ signals_not_found: 404,
22
+ validation_error: 422,
23
+ };
24
+ const SIGNALS_ERROR_CODES = new Set(Object.keys(SIGNALS_ERROR_STATUSES));
25
+ /** A typed error returned by an Ovok Signals API route. */
26
+ class SignalsError extends Error {
27
+ constructor(code, status, message, options = {}) {
28
+ var _a;
29
+ super(message);
30
+ this.name = "SignalsError";
31
+ this.code = code;
32
+ this.status = status;
33
+ this.fieldErrors = (_a = options.fieldErrors) !== null && _a !== void 0 ? _a : [];
34
+ if (options.cause !== undefined) {
35
+ Object.defineProperty(this, "cause", {
36
+ configurable: true,
37
+ enumerable: false,
38
+ value: options.cause,
39
+ writable: false,
40
+ });
41
+ }
42
+ }
43
+ }
44
+ exports.SignalsError = SignalsError;
45
+ function isSignalsError(error) {
46
+ return error instanceof SignalsError;
47
+ }
48
+ /** Converts the Signals error envelope while leaving unrelated errors untouched. */
49
+ function mapSignalsError(error, options = {}) {
50
+ if (isSignalsError(error)) {
51
+ return error;
52
+ }
53
+ const payload = responsePayload(error);
54
+ const code = responseCode(payload);
55
+ const parsed = (0, ovok_error_1.parseOvokError)(payload !== null && payload !== void 0 ? payload : error);
56
+ const message = (parsed.message === "[object Object]" || parsed.message === "undefined") &&
57
+ error instanceof Error
58
+ ? error.message
59
+ : parsed.message;
60
+ const status = responseStatus(error, payload);
61
+ if (code && SIGNALS_ERROR_CODES.has(code)) {
62
+ const knownCode = code;
63
+ const resolvedStatus = status !== null && status !== void 0 ? status : SIGNALS_ERROR_STATUSES[knownCode];
64
+ if (resolvedStatus !== undefined) {
65
+ return new SignalsError(knownCode, resolvedStatus, message, {
66
+ cause: error,
67
+ fieldErrors: parsed.fieldErrors,
68
+ });
69
+ }
70
+ }
71
+ if (status === 409 && options.signalsRequest && !code) {
72
+ return new SignalsError("signals_conflict", status, message, { cause: error });
73
+ }
74
+ if (status === 403 && !code) {
75
+ return new SignalsError("signals_forbidden", status, message, { cause: error });
76
+ }
77
+ if (status === 404 && !code) {
78
+ return new SignalsError("signals_not_found", status, message, { cause: error });
79
+ }
80
+ if (status === 422 && !code) {
81
+ const validationCode = /reason.*(required|missing|provide|must)|(?:required|missing).{0,20}reason/i.test(message)
82
+ ? "signals_reason_required"
83
+ : "validation_error";
84
+ return new SignalsError(validationCode, status, message, {
85
+ cause: error,
86
+ fieldErrors: parsed.fieldErrors,
87
+ });
88
+ }
89
+ return error;
90
+ }
91
+ function responsePayload(error) {
92
+ var _a, _b;
93
+ const root = asRecord(error);
94
+ const candidates = [
95
+ root === null || root === void 0 ? void 0 : root.body,
96
+ root === null || root === void 0 ? void 0 : root.data,
97
+ (_a = asRecord(root === null || root === void 0 ? void 0 : root.response)) === null || _a === void 0 ? void 0 : _a.data,
98
+ (_b = asRecord(root === null || root === void 0 ? void 0 : root.response)) === null || _b === void 0 ? void 0 : _b.body,
99
+ root === null || root === void 0 ? void 0 : root.outcome,
100
+ ];
101
+ for (const candidate of candidates) {
102
+ const parsed = parseJson(candidate);
103
+ if (parsed !== undefined) {
104
+ return parsed;
105
+ }
106
+ }
107
+ if (error instanceof Error) {
108
+ const parsed = parseJson(error.message);
109
+ if (parsed !== undefined) {
110
+ return parsed;
111
+ }
112
+ }
113
+ return error;
114
+ }
115
+ function responseCode(payload) {
116
+ var _a, _b, _c, _d, _e, _f;
117
+ const root = asRecord(payload);
118
+ const error = asRecord(root === null || root === void 0 ? void 0 : root.error);
119
+ const directCode = (_b = (_a = (typeof (root === null || root === void 0 ? void 0 : root.error) === "string" ? root.error : undefined)) !== null && _a !== void 0 ? _a : (typeof (root === null || root === void 0 ? void 0 : root.code) === "string" ? root.code : undefined)) !== null && _b !== void 0 ? _b : (typeof (error === null || error === void 0 ? void 0 : error.code) === "string" ? error.code : undefined);
120
+ if (directCode) {
121
+ return directCode;
122
+ }
123
+ const outcome = (_d = (_c = asRecord(root === null || root === void 0 ? void 0 : root.outcome)) !== null && _c !== void 0 ? _c : root) !== null && _d !== void 0 ? _d : {};
124
+ const issues = Array.isArray(outcome.issue) ? outcome.issue : [];
125
+ for (const issue of issues) {
126
+ const issueRecord = asRecord(issue);
127
+ const details = asRecord(issueRecord === null || issueRecord === void 0 ? void 0 : issueRecord.details);
128
+ const coding = Array.isArray(details === null || details === void 0 ? void 0 : details.coding) ? details.coding : [];
129
+ for (const entry of coding) {
130
+ const entryCode = (_e = asRecord(entry)) === null || _e === void 0 ? void 0 : _e.code;
131
+ if (typeof entryCode === "string" && entryCode.startsWith("signals_")) {
132
+ return entryCode;
133
+ }
134
+ }
135
+ const diagnostic = parseJson(issueRecord === null || issueRecord === void 0 ? void 0 : issueRecord.diagnostics);
136
+ const diagnosticRecord = asRecord(diagnostic);
137
+ const diagnosticCode = (_f = diagnosticRecord === null || diagnosticRecord === void 0 ? void 0 : diagnosticRecord.error) !== null && _f !== void 0 ? _f : diagnosticRecord === null || diagnosticRecord === void 0 ? void 0 : diagnosticRecord.code;
138
+ if (typeof diagnosticCode === "string") {
139
+ return diagnosticCode;
140
+ }
141
+ }
142
+ return undefined;
143
+ }
144
+ function responseStatus(error, payload) {
145
+ var _a, _b, _c, _d, _e, _f;
146
+ const root = asRecord(error);
147
+ const response = asRecord(root === null || root === void 0 ? void 0 : root.response);
148
+ const body = asRecord(payload);
149
+ const status = (_d = (_c = (_b = (_a = root === null || root === void 0 ? void 0 : root.status) !== null && _a !== void 0 ? _a : root === null || root === void 0 ? void 0 : root.statusCode) !== null && _b !== void 0 ? _b : response === null || response === void 0 ? void 0 : response.status) !== null && _c !== void 0 ? _c : body === null || body === void 0 ? void 0 : body.status) !== null && _d !== void 0 ? _d : body === null || body === void 0 ? void 0 : body.statusCode;
150
+ if (typeof status === "number") {
151
+ return status;
152
+ }
153
+ const outcome = (_e = asRecord(body === null || body === void 0 ? void 0 : body.outcome)) !== null && _e !== void 0 ? _e : body;
154
+ const issues = Array.isArray(outcome === null || outcome === void 0 ? void 0 : outcome.issue) ? outcome.issue : [];
155
+ for (const issue of issues) {
156
+ const issueCode = (_f = asRecord(issue)) === null || _f === void 0 ? void 0 : _f.code;
157
+ if (issueCode === "conflict") {
158
+ return 409;
159
+ }
160
+ if (issueCode === "forbidden") {
161
+ return 403;
162
+ }
163
+ if (issueCode === "not-found") {
164
+ return 404;
165
+ }
166
+ }
167
+ return undefined;
168
+ }
169
+ function parseJson(value) {
170
+ if (typeof value !== "string") {
171
+ return value;
172
+ }
173
+ try {
174
+ return JSON.parse(value);
175
+ }
176
+ catch (_a) {
177
+ return undefined;
178
+ }
179
+ }
180
+ function asRecord(value) {
181
+ return value && typeof value === "object"
182
+ ? value
183
+ : undefined;
184
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./errors";
2
+ export * from "./methods";
3
+ export * from "./types";
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./errors"), exports);
18
+ __exportStar(require("./methods"), exports);
19
+ __exportStar(require("./types"), exports);
@@ -0,0 +1,11 @@
1
+ import { OvokClient } from "../ovok-client";
2
+ import { SetSignalsThresholdBody, SignalsAlert, SignalsAlertsPage, SignalsAlertsQuery, SignalsSettingsPatch, SignalsSettingsResponse, SignalsThreshold, SignalsThresholds } from "./types";
3
+ export declare function getSignalsThresholds(this: OvokClient, patientId: string): Promise<SignalsThresholds>;
4
+ export declare function setSignalsThreshold(this: OvokClient, patientId: string, code: string, body: SetSignalsThresholdBody): Promise<SignalsThreshold>;
5
+ export declare function removeSignalsThreshold(this: OvokClient, patientId: string, code: string, reason: string): Promise<void>;
6
+ export declare function serializeSignalsAlertsQuery(query?: SignalsAlertsQuery): string;
7
+ export declare function listSignalsAlerts(this: OvokClient, query?: SignalsAlertsQuery): Promise<SignalsAlertsPage>;
8
+ export declare function getSignalsAlert(this: OvokClient, episodeId: string): Promise<SignalsAlert>;
9
+ export declare function acknowledgeSignalsAlert(this: OvokClient, episodeId: string): Promise<SignalsAlert>;
10
+ export declare function getSignalsSettings(this: OvokClient): Promise<SignalsSettingsResponse>;
11
+ export declare function updateSignalsSettings(this: OvokClient, patch: SignalsSettingsPatch, reason?: string | null): Promise<SignalsSettingsResponse>;
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getSignalsThresholds = getSignalsThresholds;
4
+ exports.setSignalsThreshold = setSignalsThreshold;
5
+ exports.removeSignalsThreshold = removeSignalsThreshold;
6
+ exports.serializeSignalsAlertsQuery = serializeSignalsAlertsQuery;
7
+ exports.listSignalsAlerts = listSignalsAlerts;
8
+ exports.getSignalsAlert = getSignalsAlert;
9
+ exports.acknowledgeSignalsAlert = acknowledgeSignalsAlert;
10
+ exports.getSignalsSettings = getSignalsSettings;
11
+ exports.updateSignalsSettings = updateSignalsSettings;
12
+ const segment = (value) => encodeURIComponent(value);
13
+ function getSignalsThresholds(patientId) {
14
+ return this.get(`/v1/signals/patients/${segment(patientId)}/thresholds`);
15
+ }
16
+ function setSignalsThreshold(patientId, code, body) {
17
+ return this.put(`/v1/signals/patients/${segment(patientId)}/thresholds/${segment(code)}`, body);
18
+ }
19
+ function removeSignalsThreshold(patientId, code, reason) {
20
+ return this.delete(`/v1/signals/patients/${segment(patientId)}/thresholds/${segment(code)}?reason=${segment(reason)}`);
21
+ }
22
+ function serializeSignalsAlertsQuery(query = {}) {
23
+ const params = [];
24
+ if (query.status !== undefined) {
25
+ params.push(["status", query.status]);
26
+ }
27
+ if (query.since !== undefined) {
28
+ params.push(["since", query.since]);
29
+ }
30
+ if (query.acknowledged !== undefined) {
31
+ params.push(["acknowledged", String(query.acknowledged)]);
32
+ }
33
+ if (query.codes !== undefined) {
34
+ params.push(["codes", query.codes.join(",")]);
35
+ }
36
+ if (query.limit !== undefined) {
37
+ params.push(["limit", String(query.limit)]);
38
+ }
39
+ if (query.cursor !== undefined) {
40
+ params.push(["cursor", query.cursor]);
41
+ }
42
+ return params
43
+ .map(([key, value]) => `${segment(key)}=${segment(value)}`)
44
+ .join("&");
45
+ }
46
+ function listSignalsAlerts(query = {}) {
47
+ const serialized = serializeSignalsAlertsQuery(query);
48
+ return this.get(`/v1/signals/alerts${serialized ? `?${serialized}` : ""}`);
49
+ }
50
+ function getSignalsAlert(episodeId) {
51
+ return this.get(`/v1/signals/alerts/${segment(episodeId)}`);
52
+ }
53
+ function acknowledgeSignalsAlert(episodeId) {
54
+ return this.post(`/v1/signals/alerts/${segment(episodeId)}/ack`);
55
+ }
56
+ function getSignalsSettings() {
57
+ return this.get("/v1/signals/settings");
58
+ }
59
+ function updateSignalsSettings(patch, reason) {
60
+ return this.patch("/v1/signals/settings", {
61
+ ...patch,
62
+ ...(reason !== undefined ? { reason } : {}),
63
+ });
64
+ }
@@ -0,0 +1,54 @@
1
+ import { ValidationError } from "../../types/ValidationError";
2
+ export type SignalsThresholdSource = "organization" | "individual" | "auto" | "unknown";
3
+ export type SignalsThreshold = {
4
+ code: string;
5
+ low: number | null;
6
+ high: number | null;
7
+ editable: boolean;
8
+ version: number;
9
+ updatedAt: string;
10
+ source: SignalsThresholdSource;
11
+ };
12
+ export type SignalsThresholds = {
13
+ patientId: string;
14
+ thresholds: SignalsThreshold[];
15
+ };
16
+ export type SetSignalsThresholdBody = {
17
+ low: number | null;
18
+ high: number | null;
19
+ reason?: string | null;
20
+ };
21
+ export type SignalsAlertStatus = "OPEN" | "CLOSED";
22
+ export type SignalsAlert = {
23
+ id: string;
24
+ patientId: string;
25
+ signalsPatientId: string;
26
+ code: string;
27
+ status: SignalsAlertStatus;
28
+ [key: string]: unknown;
29
+ };
30
+ export type SignalsAlertsQuery = {
31
+ status?: SignalsAlertStatus;
32
+ since?: string;
33
+ acknowledged?: boolean;
34
+ codes?: readonly string[];
35
+ limit?: number;
36
+ cursor?: string;
37
+ };
38
+ export type SignalsAlertsPage = {
39
+ items: SignalsAlert[];
40
+ nextCursor: string | null;
41
+ };
42
+ export type SignalsSettings = {
43
+ episodicAlerts: boolean;
44
+ synchronousMode: boolean;
45
+ throttleLimit: number | null;
46
+ };
47
+ export type SignalsTenantKind = "project" | "shared";
48
+ export type SignalsSettingsResponse = {
49
+ tenant: SignalsTenantKind;
50
+ settings: SignalsSettings;
51
+ };
52
+ export type SignalsSettingsPatch = Partial<SignalsSettings>;
53
+ export type SignalsErrorCode = "signals_threshold_not_editable" | "signals_patient_not_enrolled" | "signals_conflict" | "signals_code_not_medical" | "signals_reason_required" | "signals_episodes_disabled" | "signals_bad_request" | "signals_shared_tenant" | "signals_audit_failed" | "signals_write_mismatch" | "signals_unavailable" | "signals_undo_failed" | "signals_forbidden" | "signals_not_found" | "validation_error";
54
+ export type SignalsFieldError = ValidationError;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -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.1",
51
+ version: "0.4.2",
52
52
  },
53
53
  fhirVersion: "4.0.1",
54
54
  format: ["json"],
@@ -11,3 +11,4 @@ export { useConsentSignature, useContent, useCreateVideoCallAppointment, useCrea
11
11
  export { useI18nextDocument, useLocales, useLocalizations, useUpdateI18nextDocument, useUpdateLocalization, } from "./platform-hooks";
12
12
  export { useCurrentPatientProfile, useExtractQuestionnaireObservations, usePatientAppointments, usePatientCarePlans, usePatientDevices, usePatientDeviceTelemetry, usePatientDeviceTelemetryHistory, usePatientMeasurement, usePatientMeasurementHistory, usePatientDashboard, usePatientLatestObservations, usePatientNotifications, usePatientQuestionnaireResponses, usePatientQuestionnaires, usePatientSchedules, usePatientSlots, usePopulateQuestionnaire, useSubmitQuestionnaireResponse, } from "./patient-hooks";
13
13
  export { useCreatePatientSharingInvite, usePatientSharing, usePatientSharingSettings, useRedeemPatientSharingInvite, useRevokePatientSharing, useUpdatePatientSharingSettings, } from "./patient-sharing-hooks";
14
+ export { useAcknowledgeSignalsAlert, useRemoveSignalsThreshold, useSetSignalsThreshold, useSignalsAlert, useSignalsAlerts, useSignalsSettings, useSignalsThresholds, useUpdateSignalsSettings, } from "./signals-hooks";
@@ -35,7 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  })();
36
36
  Object.defineProperty(exports, "__esModule", { value: true });
37
37
  exports.usePatientMeasurement = exports.usePatientDeviceTelemetryHistory = exports.usePatientDeviceTelemetry = exports.usePatientDevices = exports.usePatientCarePlans = exports.usePatientAppointments = exports.useExtractQuestionnaireObservations = exports.useCurrentPatientProfile = exports.useUpdateLocalization = exports.useUpdateI18nextDocument = exports.useLocalizations = exports.useLocales = exports.useI18nextDocument = exports.useVideoCallAppointments = exports.useVideoCallAccess = exports.useDocuments = exports.useCreateVideoCallUserAccess = exports.useCreateVideoCallAppointment = exports.useContent = exports.useConsentSignature = exports.useRecentCarehubNotifications = exports.usePublicApiEndpoints = exports.useProjectSettings = exports.useProjectFeatures = exports.useOvokRequest = exports.useOvokMutation = exports.useCapabilityStatement = exports.useCarehubPatients = exports.useCarehubNotifications = exports.useCarehubDevices = exports.useCarehubDeviceTelemetry = exports.useAdministeredProjects = exports.useUrineTests = exports.useSaveMeasurement = exports.useObservations = exports.useEcgRecording = exports.observationsToMeasurements = exports.OvokProvider = exports.useClient = exports.useCachedBinaryUrl = exports.reactContext = exports.useSubscription = exports.useSearchResources = exports.useSearchOne = exports.useSearch = exports.useResource = exports.usePrevious = exports.useMedplumProfile = exports.useMedplumNavigate = exports.useMedplumContext = void 0;
38
- exports.useUpdatePatientSharingSettings = exports.useRevokePatientSharing = exports.useRedeemPatientSharingInvite = exports.usePatientSharingSettings = exports.usePatientSharing = exports.useCreatePatientSharingInvite = exports.useSubmitQuestionnaireResponse = exports.usePopulateQuestionnaire = exports.usePatientSlots = exports.usePatientSchedules = exports.usePatientQuestionnaires = exports.usePatientQuestionnaireResponses = exports.usePatientNotifications = exports.usePatientLatestObservations = exports.usePatientDashboard = exports.usePatientMeasurementHistory = void 0;
38
+ exports.useUpdateSignalsSettings = exports.useSignalsThresholds = exports.useSignalsSettings = exports.useSignalsAlerts = exports.useSignalsAlert = exports.useSetSignalsThreshold = exports.useRemoveSignalsThreshold = exports.useAcknowledgeSignalsAlert = exports.useUpdatePatientSharingSettings = exports.useRevokePatientSharing = exports.useRedeemPatientSharingInvite = exports.usePatientSharingSettings = exports.usePatientSharing = exports.useCreatePatientSharingInvite = exports.useSubmitQuestionnaireResponse = exports.usePopulateQuestionnaire = exports.usePatientSlots = exports.usePatientSchedules = exports.usePatientQuestionnaires = exports.usePatientQuestionnaireResponses = exports.usePatientNotifications = exports.usePatientLatestObservations = exports.usePatientDashboard = exports.usePatientMeasurementHistory = void 0;
39
39
  /*
40
40
  * The only React-dependent module in the package.
41
41
  *
@@ -130,3 +130,12 @@ Object.defineProperty(exports, "usePatientSharingSettings", { enumerable: true,
130
130
  Object.defineProperty(exports, "useRedeemPatientSharingInvite", { enumerable: true, get: function () { return patient_sharing_hooks_1.useRedeemPatientSharingInvite; } });
131
131
  Object.defineProperty(exports, "useRevokePatientSharing", { enumerable: true, get: function () { return patient_sharing_hooks_1.useRevokePatientSharing; } });
132
132
  Object.defineProperty(exports, "useUpdatePatientSharingSettings", { enumerable: true, get: function () { return patient_sharing_hooks_1.useUpdatePatientSharingSettings; } });
133
+ var signals_hooks_1 = require("./signals-hooks");
134
+ Object.defineProperty(exports, "useAcknowledgeSignalsAlert", { enumerable: true, get: function () { return signals_hooks_1.useAcknowledgeSignalsAlert; } });
135
+ Object.defineProperty(exports, "useRemoveSignalsThreshold", { enumerable: true, get: function () { return signals_hooks_1.useRemoveSignalsThreshold; } });
136
+ Object.defineProperty(exports, "useSetSignalsThreshold", { enumerable: true, get: function () { return signals_hooks_1.useSetSignalsThreshold; } });
137
+ Object.defineProperty(exports, "useSignalsAlert", { enumerable: true, get: function () { return signals_hooks_1.useSignalsAlert; } });
138
+ Object.defineProperty(exports, "useSignalsAlerts", { enumerable: true, get: function () { return signals_hooks_1.useSignalsAlerts; } });
139
+ Object.defineProperty(exports, "useSignalsSettings", { enumerable: true, get: function () { return signals_hooks_1.useSignalsSettings; } });
140
+ Object.defineProperty(exports, "useSignalsThresholds", { enumerable: true, get: function () { return signals_hooks_1.useSignalsThresholds; } });
141
+ Object.defineProperty(exports, "useUpdateSignalsSettings", { enumerable: true, get: function () { return signals_hooks_1.useUpdateSignalsSettings; } });
@@ -0,0 +1,34 @@
1
+ import { SetSignalsThresholdBody, SignalsAlert, SignalsAlertsQuery, SignalsSettingsPatch, SignalsSettingsResponse, SignalsThreshold, SignalsThresholds } from "../client/signals";
2
+ import { OvokMutationState, OvokRequestState } from "./api-hooks";
3
+ type SetSignalsThresholdParams = {
4
+ patientId: string;
5
+ code: string;
6
+ body: SetSignalsThresholdBody;
7
+ };
8
+ type RemoveSignalsThresholdParams = {
9
+ patientId: string;
10
+ code: string;
11
+ reason: string;
12
+ };
13
+ type UpdateSignalsSettingsParams = {
14
+ patch: SignalsSettingsPatch;
15
+ reason?: string | null;
16
+ };
17
+ type SignalsAlertsHookQuery = Omit<SignalsAlertsQuery, "cursor">;
18
+ export type SignalsAlertsHookState = {
19
+ items: SignalsAlert[];
20
+ loading: boolean;
21
+ error: Error | undefined;
22
+ reload: () => void;
23
+ hasMore: boolean;
24
+ loadMore: () => Promise<void>;
25
+ };
26
+ export declare const useSignalsThresholds: (patientId?: string) => OvokRequestState<SignalsThresholds>;
27
+ export declare const useSetSignalsThreshold: () => OvokMutationState<SetSignalsThresholdParams, SignalsThreshold>;
28
+ export declare const useRemoveSignalsThreshold: () => OvokMutationState<RemoveSignalsThresholdParams, void>;
29
+ export declare const useSignalsAlerts: (query?: SignalsAlertsHookQuery) => SignalsAlertsHookState;
30
+ export declare const useSignalsAlert: (episodeId?: string) => OvokRequestState<SignalsAlert>;
31
+ export declare const useAcknowledgeSignalsAlert: () => OvokMutationState<string, SignalsAlert>;
32
+ export declare const useSignalsSettings: () => OvokRequestState<SignalsSettingsResponse>;
33
+ export declare const useUpdateSignalsSettings: () => OvokMutationState<UpdateSignalsSettingsParams, SignalsSettingsResponse>;
34
+ export {};
@@ -0,0 +1,152 @@
1
+ "use strict";
2
+ "use client";
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
36
+ Object.defineProperty(exports, "__esModule", { value: true });
37
+ exports.useUpdateSignalsSettings = exports.useSignalsSettings = exports.useAcknowledgeSignalsAlert = exports.useSignalsAlert = exports.useSignalsAlerts = exports.useRemoveSignalsThreshold = exports.useSetSignalsThreshold = exports.useSignalsThresholds = void 0;
38
+ const React = __importStar(require("react"));
39
+ const api_hooks_1 = require("./api-hooks");
40
+ const index_1 = require("./index");
41
+ const useSignalsThresholds = (patientId) => {
42
+ const client = (0, index_1.useClient)();
43
+ const load = React.useCallback(() => client.getSignalsThresholds(patientId), [client, patientId]);
44
+ return (0, api_hooks_1.useOvokRequest)(load, patientId !== undefined);
45
+ };
46
+ exports.useSignalsThresholds = useSignalsThresholds;
47
+ const useSetSignalsThreshold = () => (0, api_hooks_1.useOvokMutation)((client, params) => client.setSignalsThreshold(params.patientId, params.code, params.body));
48
+ exports.useSetSignalsThreshold = useSetSignalsThreshold;
49
+ const useRemoveSignalsThreshold = () => (0, api_hooks_1.useOvokMutation)((client, params) => client.removeSignalsThreshold(params.patientId, params.code, params.reason));
50
+ exports.useRemoveSignalsThreshold = useRemoveSignalsThreshold;
51
+ const useSignalsAlerts = (query = {}) => {
52
+ const client = (0, index_1.useClient)();
53
+ const queryKey = JSON.stringify(query);
54
+ const querySnapshot = React.useMemo(() => JSON.parse(queryKey), [queryKey]);
55
+ const [items, setItems] = React.useState([]);
56
+ const [nextCursor, setNextCursor] = React.useState(null);
57
+ const [loading, setLoading] = React.useState(true);
58
+ const [error, setError] = React.useState();
59
+ const [revision, setRevision] = React.useState(0);
60
+ const generation = React.useRef(0);
61
+ const loadingRef = React.useRef(false);
62
+ React.useEffect(() => {
63
+ const requestGeneration = generation.current + 1;
64
+ generation.current = requestGeneration;
65
+ loadingRef.current = true;
66
+ setItems([]);
67
+ setNextCursor(null);
68
+ setError(undefined);
69
+ setLoading(true);
70
+ client
71
+ .listSignalsAlerts(querySnapshot)
72
+ .then((page) => {
73
+ if (generation.current === requestGeneration) {
74
+ setItems(page.items);
75
+ setNextCursor(page.nextCursor);
76
+ }
77
+ })
78
+ .catch((reason) => {
79
+ if (generation.current === requestGeneration) {
80
+ setError(toError(reason));
81
+ }
82
+ })
83
+ .finally(() => {
84
+ if (generation.current === requestGeneration) {
85
+ loadingRef.current = false;
86
+ setLoading(false);
87
+ }
88
+ });
89
+ return () => {
90
+ generation.current += 1;
91
+ };
92
+ }, [client, queryKey, querySnapshot, revision]);
93
+ const loadMore = React.useCallback(async () => {
94
+ if (nextCursor === null || loadingRef.current) {
95
+ return;
96
+ }
97
+ const requestGeneration = generation.current;
98
+ loadingRef.current = true;
99
+ setLoading(true);
100
+ setError(undefined);
101
+ try {
102
+ const page = await client.listSignalsAlerts({
103
+ ...querySnapshot,
104
+ cursor: nextCursor,
105
+ });
106
+ if (generation.current === requestGeneration) {
107
+ setItems((current) => [...current, ...page.items]);
108
+ setNextCursor(page.nextCursor);
109
+ }
110
+ }
111
+ catch (reason) {
112
+ if (generation.current === requestGeneration) {
113
+ setError(toError(reason));
114
+ }
115
+ }
116
+ finally {
117
+ if (generation.current === requestGeneration) {
118
+ loadingRef.current = false;
119
+ setLoading(false);
120
+ }
121
+ }
122
+ }, [client, nextCursor, querySnapshot]);
123
+ const reload = React.useCallback(() => {
124
+ setRevision((current) => current + 1);
125
+ }, []);
126
+ return {
127
+ items,
128
+ loading,
129
+ error,
130
+ reload,
131
+ hasMore: nextCursor !== null,
132
+ loadMore,
133
+ };
134
+ };
135
+ exports.useSignalsAlerts = useSignalsAlerts;
136
+ const useSignalsAlert = (episodeId) => {
137
+ const client = (0, index_1.useClient)();
138
+ const load = React.useCallback(() => client.getSignalsAlert(episodeId), [client, episodeId]);
139
+ return (0, api_hooks_1.useOvokRequest)(load, episodeId !== undefined);
140
+ };
141
+ exports.useSignalsAlert = useSignalsAlert;
142
+ const useAcknowledgeSignalsAlert = () => (0, api_hooks_1.useOvokMutation)((client, episodeId) => client.acknowledgeSignalsAlert(episodeId));
143
+ exports.useAcknowledgeSignalsAlert = useAcknowledgeSignalsAlert;
144
+ const useSignalsSettings = () => {
145
+ const client = (0, index_1.useClient)();
146
+ const load = React.useCallback(() => client.getSignalsSettings(), [client]);
147
+ return (0, api_hooks_1.useOvokRequest)(load);
148
+ };
149
+ exports.useSignalsSettings = useSignalsSettings;
150
+ const useUpdateSignalsSettings = () => (0, api_hooks_1.useOvokMutation)((client, params) => client.updateSignalsSettings(params.patch, params.reason));
151
+ exports.useUpdateSignalsSettings = useUpdateSignalsSettings;
152
+ const toError = (error) => error instanceof Error ? error : new Error(String(error));
package/dist/index.d.ts CHANGED
@@ -941,6 +941,7 @@ export * from "./client/carehub/types";
941
941
  export * from "./client/experience/types";
942
942
  export * from "./client/patient/types";
943
943
  export * from "./client/patient-sharing";
944
+ export { SignalsError, isSignalsError, mapSignalsError } from "./client/signals/errors";
944
945
  export * from "./client/storage";
945
946
  export * from "./client/questionnaire-response/methods";
946
947
  export * from "./client/questionnaire-response/types";
package/dist/index.js CHANGED
@@ -36,7 +36,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
36
36
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.saveMeasurement = exports.MAX_OBSERVATION_BUNDLE_BYTES = exports.MAX_CONDITIONAL_OBSERVATION_ENTRIES = exports.OvokClient = exports.Medplum = void 0;
39
+ exports.mapSignalsError = exports.isSignalsError = exports.SignalsError = exports.saveMeasurement = exports.MAX_OBSERVATION_BUNDLE_BYTES = exports.MAX_CONDITIONAL_OBSERVATION_ENTRIES = exports.OvokClient = exports.Medplum = void 0;
40
40
  const MedplumCore = __importStar(require("@medplum/core"));
41
41
  __exportStar(require("./types"), exports);
42
42
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -64,6 +64,10 @@ __exportStar(require("./client/carehub/types"), exports);
64
64
  __exportStar(require("./client/experience/types"), exports);
65
65
  __exportStar(require("./client/patient/types"), exports);
66
66
  __exportStar(require("./client/patient-sharing"), exports);
67
+ var errors_1 = require("./client/signals/errors");
68
+ Object.defineProperty(exports, "SignalsError", { enumerable: true, get: function () { return errors_1.SignalsError; } });
69
+ Object.defineProperty(exports, "isSignalsError", { enumerable: true, get: function () { return errors_1.isSignalsError; } });
70
+ Object.defineProperty(exports, "mapSignalsError", { enumerable: true, get: function () { return errors_1.mapSignalsError; } });
67
71
  __exportStar(require("./client/storage"), exports);
68
72
  __exportStar(require("./client/questionnaire-response/methods"), exports);
69
73
  __exportStar(require("./client/questionnaire-response/types"), exports);
@@ -18,5 +18,6 @@ export * from "../client/chat/ai/types/CreateAiSessionProps";
18
18
  export * from "../client/chat/ai/types/SessionMessagesResponse";
19
19
  export * from "../client/chat/ai/types/CreateAiSessionResponse";
20
20
  export * from "../client/ai-fhir/types";
21
+ export * from "../client/signals/types";
21
22
  export * from "./ValidationError";
22
23
  export * from "@medplum/core";
@@ -38,6 +38,7 @@ __exportStar(require("../client/chat/ai/types/CreateAiSessionProps"), exports);
38
38
  __exportStar(require("../client/chat/ai/types/SessionMessagesResponse"), exports);
39
39
  __exportStar(require("../client/chat/ai/types/CreateAiSessionResponse"), exports);
40
40
  __exportStar(require("../client/ai-fhir/types"), exports);
41
+ __exportStar(require("../client/signals/types"), exports);
41
42
  // Common types
42
43
  __exportStar(require("./ValidationError"), exports);
43
44
  __exportStar(require("@medplum/core"), exports);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ovok/core",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "private": false,
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -37,6 +37,7 @@
37
37
  "@types/jest": "^29.5.14",
38
38
  "@types/node": "^20.11.0",
39
39
  "@types/react": "^18.3.12",
40
+ "@types/react-test-renderer": "^18.3.1",
40
41
  "eslint": "^9.15.0",
41
42
  "eslint-plugin-check-file": "^2.8.0",
42
43
  "eslint-plugin-import": "^2.31.0",
@@ -45,6 +46,7 @@
45
46
  "globals": "^15.12.0",
46
47
  "jest": "^29.7.0",
47
48
  "react": "^18.3.1",
49
+ "react-test-renderer": "^18.3.1",
48
50
  "release-it": "^19.0.6",
49
51
  "ts-jest": "^29.2.5",
50
52
  "ts-node": "^10.9.2",