@ovok/core 0.4.10 → 0.4.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +34 -1
  2. package/dist/client/change-history/index.d.ts +2 -0
  3. package/dist/client/change-history/index.js +18 -0
  4. package/dist/client/change-history/methods.d.ts +4 -0
  5. package/dist/client/change-history/methods.js +25 -0
  6. package/dist/client/change-history/types.d.ts +34 -0
  7. package/dist/client/change-history/types.js +2 -0
  8. package/dist/client/consent/index.d.ts +2 -0
  9. package/dist/client/consent/index.js +18 -0
  10. package/dist/client/consent/methods.d.ts +6 -0
  11. package/dist/client/consent/methods.js +20 -0
  12. package/dist/client/consent/types.d.ts +26 -0
  13. package/dist/client/consent/types.js +7 -0
  14. package/dist/client/errors/ovok-error-utils.d.ts +10 -0
  15. package/dist/client/errors/ovok-error-utils.js +150 -0
  16. package/dist/client/errors/ovok-error.d.ts +16 -0
  17. package/dist/client/errors/ovok-error.js +67 -122
  18. package/dist/client/notifications/index.d.ts +2 -0
  19. package/dist/client/notifications/index.js +18 -0
  20. package/dist/client/notifications/methods.d.ts +7 -0
  21. package/dist/client/notifications/methods.js +29 -0
  22. package/dist/client/notifications/types.d.ts +90 -0
  23. package/dist/client/notifications/types.js +2 -0
  24. package/dist/client/offline/offline-measurement-queue.js +5 -1
  25. package/dist/client/ovok-client.d.ts +4 -1
  26. package/dist/client/ovok-client.js +14 -2
  27. package/dist/client/signals/methods.d.ts +15 -1
  28. package/dist/client/signals/methods.js +36 -0
  29. package/dist/client/signals/types.d.ts +16 -0
  30. package/dist/client/slim/methods.d.ts +20 -8
  31. package/dist/client/slim/methods.js +34 -1
  32. package/dist/client/slim/types.d.ts +51 -1
  33. package/dist/conformance/api-requirements.js +25 -1
  34. package/dist/conformance/capability-requirements.js +1 -1
  35. package/dist/hooks/account-hooks.d.ts +19 -0
  36. package/dist/hooks/account-hooks.js +92 -0
  37. package/dist/hooks/api-hooks.d.ts +1 -0
  38. package/dist/hooks/api-hooks.js +1 -0
  39. package/dist/hooks/index.d.ts +2 -0
  40. package/dist/hooks/index.js +31 -2
  41. package/dist/hooks/notification-hooks.d.ts +32 -0
  42. package/dist/hooks/notification-hooks.js +144 -0
  43. package/dist/hooks/notification-invalidation.d.ts +3 -0
  44. package/dist/hooks/notification-invalidation.js +23 -0
  45. package/dist/hooks/signals-alert-hooks.js +2 -0
  46. package/dist/index.d.ts +3 -0
  47. package/dist/index.js +3 -0
  48. package/package.json +1 -1
package/README.md CHANGED
@@ -305,12 +305,45 @@ PDF export. These routes require the backend's bearer session and Slim permissio
305
305
  const residents = await client.listSlimPatients({ residentState: "active" });
306
306
  const devices = await client.listSlimDevices({ active: true });
307
307
  const telemetry = await client.getSlimDeviceTelemetry(deviceId);
308
- const alerts = await client.listSlimMedicalNotifications({ count: 25 });
308
+ const alerts = await client.listSlimMedicalNotifications({ limit: 25, codes: ["8867-4"] });
309
+ const alarms = await client.listSlimAlarms({ status: "active", sort: "newest" });
309
310
  ```
310
311
 
311
312
  Slim is intentionally represented as typed application APIs rather than changing the FHIR
312
313
  observation save/offline behavior used by native integrations.
313
314
 
315
+ Use the separate `/oob`, `/alarms`, and `/medical` feeds instead of the deprecated mixed
316
+ `listSlimNotifications` endpoint. The medical React hook reads every cursor page; it stops only
317
+ when `nextCursor` is `null`. Feed `noteCount` is nullable: `null` means the count is unknown, not
318
+ zero. Notification notes are practitioner-authored and do not acknowledge or otherwise change an
319
+ alert. After a note mutation, mounted note and alert-feed hooks reload their data.
320
+
321
+ Writes can return `OvokApiError`, which keeps the HTTP status, machine code, validation field errors,
322
+ and any missing-baseline details. In particular, reload the affected record before retrying a 409;
323
+ do not automatically replay a stale assignment. A 409 `signals_project_migrating` should be shown
324
+ as a temporary home-migration conflict, not retried in a loop. Threshold writes can return 422
325
+ validation errors or 424 when the baseline has not been collected. Hooks never replay a write with
326
+ a stale body; transient 503 transport retries, when enabled by Medplum, do not clear the session.
327
+ The unconfirmed-limit list and repush routes require project-admin access; a 403 remains an error.
328
+
329
+ `getLegalConsent()` returns the signed-in practitioner's own terms status. If
330
+ `needsLegalConsentReacceptance(response.legal)` is true, show the current terms before continuing.
331
+
332
+ ```ts
333
+ if (needsLegalConsentReacceptance(legalConsent.legal)) {
334
+ // Show the current terms and request acceptance.
335
+ }
336
+
337
+ try {
338
+ await client.updateSlimPatientThresholdV2(patientId, body);
339
+ } catch (error) {
340
+ if (isOvokApiError(error) && error.status === 424) {
341
+ console.info(error.missing);
342
+ }
343
+ throw error;
344
+ }
345
+ ```
346
+
314
347
  ## Application APIs
315
348
 
316
349
  The client also exposes typed application APIs for consent signatures, document upload credentials,
@@ -0,0 +1,2 @@
1
+ export * from "./methods";
2
+ export * from "./types";
@@ -0,0 +1,18 @@
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("./methods"), exports);
18
+ __exportStar(require("./types"), exports);
@@ -0,0 +1,4 @@
1
+ import { OvokClient } from "../ovok-client";
2
+ import { ChangeHistoryPage, ChangeHistoryQuery } from "./types";
3
+ export declare function serializeChangeHistoryQuery(params?: ChangeHistoryQuery): string;
4
+ export declare function listChangeHistory(this: OvokClient, params?: ChangeHistoryQuery): Promise<ChangeHistoryPage>;
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.serializeChangeHistoryQuery = serializeChangeHistoryQuery;
4
+ exports.listChangeHistory = listChangeHistory;
5
+ function serializeChangeHistoryQuery(params = {}) {
6
+ const search = new URLSearchParams();
7
+ for (const [key, value] of Object.entries(params)) {
8
+ if (value === undefined) {
9
+ continue;
10
+ }
11
+ if (key === "types" && Array.isArray(value)) {
12
+ for (const type of value) {
13
+ search.append("types[]", type);
14
+ }
15
+ }
16
+ else {
17
+ search.set(key, String(value));
18
+ }
19
+ }
20
+ const encoded = search.toString();
21
+ return encoded ? `?${encoded}` : "";
22
+ }
23
+ function listChangeHistory(params = {}) {
24
+ return this.get(`/v1/slim/change-history${serializeChangeHistoryQuery(params)}`);
25
+ }
@@ -0,0 +1,34 @@
1
+ export type ChangeHistoryKey = "deviceCreated" | "deviceDeleted" | "deviceProjectChanged" | "deviceLocationChanged" | "deviceZoneChanged" | "deviceRenamed" | "locationCreated" | "locationDeleted" | "locationRenamed" | "locationZoneChanged" | "locationFloorChanged" | "locationRoomChanged" | "locationBedChanged" | "zoneCreated" | "zoneDeleted" | "zoneRenamed";
2
+ export type ChangeHistoryRow = {
3
+ id: string;
4
+ type: string;
5
+ user: Record<string, unknown> | null;
6
+ residentId: string | null;
7
+ deviceId: string | null;
8
+ locationId?: string | null;
9
+ zoneId?: string | null;
10
+ time: string;
11
+ details: {
12
+ key: ChangeHistoryKey | (string & {});
13
+ values: Record<string, unknown>;
14
+ };
15
+ };
16
+ export type ChangeHistoryQuery = {
17
+ page?: number;
18
+ count?: number;
19
+ types?: readonly string[];
20
+ scope?: string;
21
+ userId?: string;
22
+ residentId?: string;
23
+ deviceId?: string;
24
+ locationId?: string;
25
+ zoneId?: string;
26
+ startDate?: string;
27
+ endDate?: string;
28
+ };
29
+ export type ChangeHistoryPage = {
30
+ total: number;
31
+ page: number;
32
+ count: number;
33
+ resources: ChangeHistoryRow[];
34
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,2 @@
1
+ export * from "./methods";
2
+ export * from "./types";
@@ -0,0 +1,18 @@
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("./methods"), exports);
18
+ __exportStar(require("./types"), exports);
@@ -0,0 +1,6 @@
1
+ import { OvokClient } from "../ovok-client";
2
+ import { LegalConsentResponse, MedicalConsentResponse } from "./types";
3
+ export declare function getLegalConsent(this: OvokClient): Promise<LegalConsentResponse>;
4
+ export declare function setLegalConsent(this: OvokClient, accepted: boolean): Promise<LegalConsentResponse>;
5
+ export declare function getMedicalConsent(this: OvokClient): Promise<MedicalConsentResponse>;
6
+ export declare function setMedicalConsent(this: OvokClient, accepted: boolean): Promise<MedicalConsentResponse>;
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getLegalConsent = getLegalConsent;
4
+ exports.setLegalConsent = setLegalConsent;
5
+ exports.getMedicalConsent = getMedicalConsent;
6
+ exports.setMedicalConsent = setMedicalConsent;
7
+ function getLegalConsent() {
8
+ return this.get("/v1/me/consent/legal");
9
+ }
10
+ function setLegalConsent(accepted) {
11
+ const body = { termsAndConditionsConsent: accepted };
12
+ return this.post("/v1/me/consent/legal", body);
13
+ }
14
+ function getMedicalConsent() {
15
+ return this.get("/v1/me/consent/medical");
16
+ }
17
+ function setMedicalConsent(accepted) {
18
+ const body = { medicalSettingsNotificationsConsent: accepted };
19
+ return this.post("/v1/me/consent/medical", body);
20
+ }
@@ -0,0 +1,26 @@
1
+ export type LegalConsentRecord = {
2
+ consentId: string;
3
+ status: "active" | "revoked";
4
+ termsUrl?: string | null;
5
+ termsUpdatedAt?: string | null;
6
+ acceptedAt?: string | null;
7
+ revokedAt?: string | null;
8
+ revokedReason?: "tos-updated" | "withdrawn" | null;
9
+ };
10
+ export type LegalConsentResponse = {
11
+ legal: LegalConsentRecord | null;
12
+ };
13
+ export type MedicalConsentRecord = {
14
+ consentId: string;
15
+ acceptedAt: string | null;
16
+ };
17
+ export type MedicalConsentResponse = {
18
+ medical: MedicalConsentRecord | null;
19
+ };
20
+ export type LegalConsentUpdate = {
21
+ termsAndConditionsConsent: boolean;
22
+ };
23
+ export type MedicalConsentUpdate = {
24
+ medicalSettingsNotificationsConsent: boolean;
25
+ };
26
+ export declare function needsLegalConsentReacceptance(legal: LegalConsentRecord | null | undefined): boolean;
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.needsLegalConsentReacceptance = needsLegalConsentReacceptance;
4
+ function needsLegalConsentReacceptance(legal) {
5
+ return legal == null ||
6
+ (legal.status === "revoked" && legal.revokedReason === "tos-updated");
7
+ }
@@ -0,0 +1,10 @@
1
+ import { ValidationError } from "../../types/ValidationError";
2
+ export declare const asRecord: (value: unknown) => Record<string, unknown> | undefined;
3
+ export declare function readMessage(payload: Record<string, unknown> | undefined, error: unknown): string;
4
+ export declare function readFieldErrors(payload: Record<string, unknown> | undefined): ValidationError[];
5
+ export declare function codeValue(value: unknown): string | undefined;
6
+ export declare function codeFromOutcome(value: unknown): string | undefined;
7
+ export declare function statusFromOutcome(value: unknown): number | undefined;
8
+ export declare function numberValue(value: unknown): number | undefined;
9
+ export declare function stringValue(value: unknown): string | undefined;
10
+ export declare function parseJson(value: unknown): unknown;
@@ -0,0 +1,150 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.asRecord = void 0;
4
+ exports.readMessage = readMessage;
5
+ exports.readFieldErrors = readFieldErrors;
6
+ exports.codeValue = codeValue;
7
+ exports.codeFromOutcome = codeFromOutcome;
8
+ exports.statusFromOutcome = statusFromOutcome;
9
+ exports.numberValue = numberValue;
10
+ exports.stringValue = stringValue;
11
+ exports.parseJson = parseJson;
12
+ const asRecord = (value) => value && typeof value === "object" ? value : undefined;
13
+ exports.asRecord = asRecord;
14
+ function readMessage(payload, error) {
15
+ var _a, _b, _c;
16
+ const nestedMessage = stringValue((_a = (0, exports.asRecord)(payload === null || payload === void 0 ? void 0 : payload.error)) === null || _a === void 0 ? void 0 : _a.message);
17
+ const message = (_c = (_b = stringValue(payload === null || payload === void 0 ? void 0 : payload.message)) !== null && _b !== void 0 ? _b : nestedMessage) !== null && _c !== void 0 ? _c : payload === null || payload === void 0 ? void 0 : payload.message;
18
+ if (typeof message === "string" && message.length > 0) {
19
+ return message;
20
+ }
21
+ if (Array.isArray(message)) {
22
+ const messages = message
23
+ .map((entry) => { var _a; return stringValue((_a = (0, exports.asRecord)(entry)) === null || _a === void 0 ? void 0 : _a.message); })
24
+ .filter((entry) => entry !== undefined);
25
+ if (messages.length > 0) {
26
+ return messages.join("; ");
27
+ }
28
+ }
29
+ if (error instanceof Error) {
30
+ return error.message;
31
+ }
32
+ return typeof error === "string" ? error : String(error);
33
+ }
34
+ function readFieldErrors(payload) {
35
+ const messageErrors = Array.isArray(payload === null || payload === void 0 ? void 0 : payload.message) ? payload.message : [];
36
+ const nested = (0, exports.asRecord)(payload === null || payload === void 0 ? void 0 : payload.error);
37
+ const rawErrors = Array.isArray(nested === null || nested === void 0 ? void 0 : nested.errors)
38
+ ? nested.errors
39
+ : Array.isArray(payload === null || payload === void 0 ? void 0 : payload.errors)
40
+ ? payload.errors
41
+ : messageErrors;
42
+ const envelopeErrors = rawErrors.flatMap((value) => {
43
+ const record = (0, exports.asRecord)(value);
44
+ if (!record || typeof record.message !== "string") {
45
+ return [];
46
+ }
47
+ const path = Array.isArray(record.path)
48
+ ? record.path.map((segment) => String(segment))
49
+ : [];
50
+ return [{
51
+ ...(stringValue(record.code) ? { code: stringValue(record.code) } : {}),
52
+ message: record.message,
53
+ path,
54
+ ...(stringValue(record.validation)
55
+ ? { validation: stringValue(record.validation) }
56
+ : {}),
57
+ }];
58
+ });
59
+ const outcome = Array.isArray(payload === null || payload === void 0 ? void 0 : payload.issue) ? payload.issue : [];
60
+ const nestedEnvelopeErrors = outcome.flatMap((value) => {
61
+ var _a;
62
+ const details = (0, exports.asRecord)((_a = (0, exports.asRecord)(value)) === null || _a === void 0 ? void 0 : _a.details);
63
+ const nested = (0, exports.asRecord)(parseJson(details === null || details === void 0 ? void 0 : details.text));
64
+ return nested ? readFieldErrors(nested) : [];
65
+ });
66
+ const outcomeErrors = outcome.flatMap((value) => {
67
+ var _a;
68
+ const issue = (0, exports.asRecord)(value);
69
+ const details = (0, exports.asRecord)(issue === null || issue === void 0 ? void 0 : issue.details);
70
+ const message = (_a = stringValue(details === null || details === void 0 ? void 0 : details.text)) !== null && _a !== void 0 ? _a : stringValue(issue === null || issue === void 0 ? void 0 : issue.diagnostics);
71
+ if ((issue === null || issue === void 0 ? void 0 : issue.severity) !== "error" || !message) {
72
+ return [];
73
+ }
74
+ const expressions = Array.isArray(issue.expression)
75
+ ? issue.expression
76
+ : Array.isArray(issue.location)
77
+ ? issue.location
78
+ : [];
79
+ const path = expressions.flatMap((expression) => typeof expression === "string" ? expression.split(".").filter(Boolean) : []);
80
+ return [{
81
+ ...(stringValue(issue.code) ? { code: stringValue(issue.code) } : {}),
82
+ message,
83
+ path,
84
+ }];
85
+ });
86
+ return [...envelopeErrors, ...nestedEnvelopeErrors, ...outcomeErrors];
87
+ }
88
+ function codeValue(value) {
89
+ return typeof value === "string" && /^[a-z][a-z0-9_]+$/.test(value)
90
+ ? value
91
+ : undefined;
92
+ }
93
+ function codeFromOutcome(value) {
94
+ var _a, _b;
95
+ const outcome = (0, exports.asRecord)(value);
96
+ const issues = Array.isArray(outcome === null || outcome === void 0 ? void 0 : outcome.issue) ? outcome.issue : [];
97
+ for (const issue of issues) {
98
+ const record = (0, exports.asRecord)(issue);
99
+ const details = (0, exports.asRecord)(record === null || record === void 0 ? void 0 : record.details);
100
+ const coding = Array.isArray(details === null || details === void 0 ? void 0 : details.coding) ? details.coding : [];
101
+ for (const entry of coding) {
102
+ const code = codeValue((_a = (0, exports.asRecord)(entry)) === null || _a === void 0 ? void 0 : _a.code);
103
+ if (code === null || code === void 0 ? void 0 : code.startsWith("signals_")) {
104
+ return code;
105
+ }
106
+ }
107
+ const diagnostic = (0, exports.asRecord)(parseJson(record === null || record === void 0 ? void 0 : record.diagnostics));
108
+ const code = (_b = codeValue(diagnostic === null || diagnostic === void 0 ? void 0 : diagnostic.error)) !== null && _b !== void 0 ? _b : codeValue(diagnostic === null || diagnostic === void 0 ? void 0 : diagnostic.code);
109
+ if (code) {
110
+ return code;
111
+ }
112
+ }
113
+ return undefined;
114
+ }
115
+ function statusFromOutcome(value) {
116
+ var _a, _b;
117
+ const outcome = (0, exports.asRecord)(value);
118
+ const issues = Array.isArray(outcome === null || outcome === void 0 ? void 0 : outcome.issue) ? outcome.issue : [];
119
+ const statusByIssue = {
120
+ conflict: 409,
121
+ forbidden: 403,
122
+ "not-found": 404,
123
+ "too-many-requests": 429,
124
+ throttled: 429,
125
+ };
126
+ for (const issue of issues) {
127
+ const status = statusByIssue[String((_b = (_a = (0, exports.asRecord)(issue)) === null || _a === void 0 ? void 0 : _a.code) !== null && _b !== void 0 ? _b : "")];
128
+ if (status) {
129
+ return status;
130
+ }
131
+ }
132
+ return undefined;
133
+ }
134
+ function numberValue(value) {
135
+ return typeof value === "number" && Number.isFinite(value) ? value : undefined;
136
+ }
137
+ function stringValue(value) {
138
+ return typeof value === "string" && value.length > 0 ? value : undefined;
139
+ }
140
+ function parseJson(value) {
141
+ if (typeof value !== "string") {
142
+ return undefined;
143
+ }
144
+ try {
145
+ return JSON.parse(value);
146
+ }
147
+ catch (_a) {
148
+ return undefined;
149
+ }
150
+ }
@@ -1,4 +1,5 @@
1
1
  import { ValidationError } from "../../types/ValidationError";
2
+ export { asRecord, readFieldErrors, readMessage } from "./ovok-error-utils";
2
3
  export type ParsedOvokError = {
3
4
  message: string;
4
5
  fieldErrors: ValidationError[];
@@ -6,8 +7,23 @@ export type ParsedOvokError = {
6
7
  export type OvokErrorDetails = ParsedOvokError & {
7
8
  status: number | undefined;
8
9
  code: string | undefined;
10
+ missing: string[];
9
11
  };
10
12
  /** Reads the error details Medplum preserves from Ovok's non-FHIR responses. */
11
13
  export declare const readOvokError: (error: unknown) => OvokErrorDetails;
14
+ /** A typed HTTP error for Ovok API responses whose status carries application meaning. */
15
+ export declare class OvokApiError extends Error {
16
+ readonly status: number;
17
+ readonly code: string | undefined;
18
+ readonly fieldErrors: ValidationError[];
19
+ readonly missing: string[];
20
+ readonly cause: unknown;
21
+ constructor(details: OvokErrorDetails, cause: unknown);
22
+ }
23
+ export declare function isOvokApiError(error: unknown): error is OvokApiError;
24
+ /** Types the status-bearing write failures without changing auth or rate-limit handling. */
25
+ export declare function mapOvokApiError(error: unknown, options?: {
26
+ includeAccessErrors?: boolean;
27
+ }): unknown;
12
28
  /** Normalizes Ovok's Nest and FHIR error envelopes without exposing transport details. */
13
29
  export declare const parseOvokError: (error: unknown) => ParsedOvokError;
@@ -1,6 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.parseOvokError = exports.readOvokError = void 0;
3
+ exports.parseOvokError = exports.OvokApiError = exports.readOvokError = exports.readMessage = exports.readFieldErrors = exports.asRecord = void 0;
4
+ exports.isOvokApiError = isOvokApiError;
5
+ exports.mapOvokApiError = mapOvokApiError;
6
+ const ovok_error_utils_1 = require("./ovok-error-utils");
7
+ var ovok_error_utils_2 = require("./ovok-error-utils");
8
+ Object.defineProperty(exports, "asRecord", { enumerable: true, get: function () { return ovok_error_utils_2.asRecord; } });
9
+ Object.defineProperty(exports, "readFieldErrors", { enumerable: true, get: function () { return ovok_error_utils_2.readFieldErrors; } });
10
+ Object.defineProperty(exports, "readMessage", { enumerable: true, get: function () { return ovok_error_utils_2.readMessage; } });
4
11
  const STATUS_BY_BARE_CODE = {
5
12
  INVALID_SETTING_VALUE: 422,
6
13
  APP_URL_NOT_ALLOWED: 400,
@@ -17,17 +24,25 @@ const HTTP_REASON_PHRASES = new Set([
17
24
  "internal server error",
18
25
  "service unavailable",
19
26
  ]);
20
- const asRecord = (value) => value && typeof value === "object" ? value : undefined;
21
27
  /** Reads the error details Medplum preserves from Ovok's non-FHIR responses. */
22
28
  const readOvokError = (error) => {
23
29
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t;
24
- const root = asRecord(error);
25
- const response = asRecord(root === null || root === void 0 ? void 0 : root.response);
30
+ if (error instanceof OvokApiError) {
31
+ return {
32
+ status: error.status,
33
+ code: error.code,
34
+ message: error.message,
35
+ fieldErrors: error.fieldErrors,
36
+ missing: error.missing,
37
+ };
38
+ }
39
+ const root = (0, ovok_error_utils_1.asRecord)(error);
40
+ const response = (0, ovok_error_utils_1.asRecord)(root === null || root === void 0 ? void 0 : root.response);
26
41
  const messagePayload = error instanceof Error
27
- ? parseJson(error.message)
28
- : parseJson(error);
29
- const payload = (_f = (_e = (_d = (_c = (_b = (_a = asRecord(messagePayload)) !== null && _a !== void 0 ? _a : asRecord(root === null || root === void 0 ? void 0 : root.body)) !== null && _b !== void 0 ? _b : asRecord(root === null || root === void 0 ? void 0 : root.data)) !== null && _c !== void 0 ? _c : asRecord(response === null || response === void 0 ? void 0 : response.data)) !== null && _d !== void 0 ? _d : asRecord(response === null || response === void 0 ? void 0 : response.body)) !== null && _e !== void 0 ? _e : asRecord(root === null || root === void 0 ? void 0 : root.outcome)) !== null && _f !== void 0 ? _f : root;
30
- const nestedError = asRecord(payload === null || payload === void 0 ? void 0 : payload.error);
42
+ ? (0, ovok_error_utils_1.parseJson)(error.message)
43
+ : (0, ovok_error_utils_1.parseJson)(error);
44
+ const payload = (_f = (_e = (_d = (_c = (_b = (_a = (0, ovok_error_utils_1.asRecord)(messagePayload)) !== null && _a !== void 0 ? _a : (0, ovok_error_utils_1.asRecord)(root === null || root === void 0 ? void 0 : root.body)) !== null && _b !== void 0 ? _b : (0, ovok_error_utils_1.asRecord)(root === null || root === void 0 ? void 0 : root.data)) !== null && _c !== void 0 ? _c : (0, ovok_error_utils_1.asRecord)(response === null || response === void 0 ? void 0 : response.data)) !== null && _d !== void 0 ? _d : (0, ovok_error_utils_1.asRecord)(response === null || response === void 0 ? void 0 : response.body)) !== null && _e !== void 0 ? _e : (0, ovok_error_utils_1.asRecord)(root === null || root === void 0 ? void 0 : root.outcome)) !== null && _f !== void 0 ? _f : root;
45
+ const nestedError = (0, ovok_error_utils_1.asRecord)(payload === null || payload === void 0 ? void 0 : payload.error);
31
46
  const bareMessage = typeof error === "string"
32
47
  ? error
33
48
  : error instanceof Error
@@ -37,130 +52,60 @@ const readOvokError = (error) => {
37
52
  && !HTTP_REASON_PHRASES.has(bareMessage.toLowerCase())
38
53
  ? bareMessage
39
54
  : undefined;
40
- const code = (_k = (_j = (_h = (_g = stringValue(payload === null || payload === void 0 ? void 0 : payload.code)) !== null && _g !== void 0 ? _g : stringValue(nestedError === null || nestedError === void 0 ? void 0 : nestedError.code)) !== null && _h !== void 0 ? _h : codeValue(payload === null || payload === void 0 ? void 0 : payload.error)) !== null && _j !== void 0 ? _j : bareCode) !== null && _k !== void 0 ? _k : codeFromOutcome((_l = payload === null || payload === void 0 ? void 0 : payload.outcome) !== null && _l !== void 0 ? _l : payload);
41
- const status = (_t = (_r = (_q = (_p = (_o = (_m = numberValue(root === null || root === void 0 ? void 0 : root.status)) !== null && _m !== void 0 ? _m : numberValue(root === null || root === void 0 ? void 0 : root.statusCode)) !== null && _o !== void 0 ? _o : numberValue(response === null || response === void 0 ? void 0 : response.status)) !== null && _p !== void 0 ? _p : numberValue(payload === null || payload === void 0 ? void 0 : payload.status)) !== null && _q !== void 0 ? _q : numberValue(payload === null || payload === void 0 ? void 0 : payload.statusCode)) !== null && _r !== void 0 ? _r : statusFromOutcome((_s = payload === null || payload === void 0 ? void 0 : payload.outcome) !== null && _s !== void 0 ? _s : payload)) !== null && _t !== void 0 ? _t : (code ? STATUS_BY_BARE_CODE[code] : undefined);
42
- const fieldErrors = readFieldErrors(payload);
55
+ const code = (_k = (_j = (_h = (_g = (0, ovok_error_utils_1.stringValue)(payload === null || payload === void 0 ? void 0 : payload.code)) !== null && _g !== void 0 ? _g : (0, ovok_error_utils_1.stringValue)(nestedError === null || nestedError === void 0 ? void 0 : nestedError.code)) !== null && _h !== void 0 ? _h : (0, ovok_error_utils_1.codeValue)(payload === null || payload === void 0 ? void 0 : payload.error)) !== null && _j !== void 0 ? _j : bareCode) !== null && _k !== void 0 ? _k : (0, ovok_error_utils_1.codeFromOutcome)((_l = payload === null || payload === void 0 ? void 0 : payload.outcome) !== null && _l !== void 0 ? _l : payload);
56
+ const status = (_t = (_r = (_q = (_p = (_o = (_m = (0, ovok_error_utils_1.numberValue)(root === null || root === void 0 ? void 0 : root.status)) !== null && _m !== void 0 ? _m : (0, ovok_error_utils_1.numberValue)(root === null || root === void 0 ? void 0 : root.statusCode)) !== null && _o !== void 0 ? _o : (0, ovok_error_utils_1.numberValue)(response === null || response === void 0 ? void 0 : response.status)) !== null && _p !== void 0 ? _p : (0, ovok_error_utils_1.numberValue)(payload === null || payload === void 0 ? void 0 : payload.status)) !== null && _q !== void 0 ? _q : (0, ovok_error_utils_1.numberValue)(payload === null || payload === void 0 ? void 0 : payload.statusCode)) !== null && _r !== void 0 ? _r : (0, ovok_error_utils_1.statusFromOutcome)((_s = payload === null || payload === void 0 ? void 0 : payload.outcome) !== null && _s !== void 0 ? _s : payload)) !== null && _t !== void 0 ? _t : (code ? STATUS_BY_BARE_CODE[code] : undefined);
57
+ const fieldErrors = (0, ovok_error_utils_1.readFieldErrors)(payload);
58
+ const missing = Array.isArray(payload === null || payload === void 0 ? void 0 : payload.missing)
59
+ ? payload.missing.filter((entry) => typeof entry === "string")
60
+ : [];
43
61
  return {
44
62
  status,
45
63
  code,
46
- message: readMessage(payload, error),
64
+ message: (0, ovok_error_utils_1.readMessage)(payload, error),
47
65
  fieldErrors,
66
+ missing,
48
67
  };
49
68
  };
50
69
  exports.readOvokError = readOvokError;
70
+ /** A typed HTTP error for Ovok API responses whose status carries application meaning. */
71
+ class OvokApiError extends Error {
72
+ constructor(details, cause) {
73
+ var _a;
74
+ super(details.message);
75
+ this.name = "OvokApiError";
76
+ this.status = (_a = details.status) !== null && _a !== void 0 ? _a : 0;
77
+ this.code = details.code;
78
+ this.fieldErrors = details.fieldErrors;
79
+ this.missing = details.missing;
80
+ Object.defineProperty(this, "cause", {
81
+ configurable: true,
82
+ enumerable: false,
83
+ value: cause,
84
+ writable: false,
85
+ });
86
+ }
87
+ }
88
+ exports.OvokApiError = OvokApiError;
89
+ function isOvokApiError(error) {
90
+ return error instanceof OvokApiError;
91
+ }
92
+ /** Types the status-bearing write failures without changing auth or rate-limit handling. */
93
+ function mapOvokApiError(error, options = {}) {
94
+ var _a;
95
+ if (isOvokApiError(error)) {
96
+ return error;
97
+ }
98
+ const details = (0, exports.readOvokError)(error);
99
+ const typedStatuses = options.includeAccessErrors
100
+ ? [403, 404, 409, 422, 424, 503]
101
+ : [409, 422, 424, 503];
102
+ return typedStatuses.includes((_a = details.status) !== null && _a !== void 0 ? _a : 0)
103
+ ? new OvokApiError(details, error)
104
+ : error;
105
+ }
51
106
  /** Normalizes Ovok's Nest and FHIR error envelopes without exposing transport details. */
52
107
  const parseOvokError = (error) => {
53
108
  const { message, fieldErrors } = (0, exports.readOvokError)(error);
54
109
  return { message, fieldErrors };
55
110
  };
56
111
  exports.parseOvokError = parseOvokError;
57
- function readMessage(payload, error) {
58
- var _a, _b, _c;
59
- const nestedMessage = stringValue((_a = asRecord(payload === null || payload === void 0 ? void 0 : payload.error)) === null || _a === void 0 ? void 0 : _a.message);
60
- const message = (_c = (_b = stringValue(payload === null || payload === void 0 ? void 0 : payload.message)) !== null && _b !== void 0 ? _b : nestedMessage) !== null && _c !== void 0 ? _c : payload === null || payload === void 0 ? void 0 : payload.message;
61
- if (typeof message === "string" && message.length > 0) {
62
- return message;
63
- }
64
- if (Array.isArray(message)) {
65
- const messages = message
66
- .map((entry) => { var _a; return stringValue((_a = asRecord(entry)) === null || _a === void 0 ? void 0 : _a.message); })
67
- .filter((entry) => entry !== undefined);
68
- if (messages.length > 0) {
69
- return messages.join("; ");
70
- }
71
- }
72
- if (error instanceof Error) {
73
- return error.message;
74
- }
75
- return typeof error === "string" ? error : String(error);
76
- }
77
- function readFieldErrors(payload) {
78
- const messageErrors = Array.isArray(payload === null || payload === void 0 ? void 0 : payload.message) ? payload.message : [];
79
- const nested = asRecord(payload === null || payload === void 0 ? void 0 : payload.error);
80
- const rawErrors = Array.isArray(nested === null || nested === void 0 ? void 0 : nested.errors)
81
- ? nested.errors
82
- : Array.isArray(payload === null || payload === void 0 ? void 0 : payload.errors)
83
- ? payload.errors
84
- : messageErrors;
85
- return rawErrors.flatMap((value) => {
86
- const record = asRecord(value);
87
- if (!record || typeof record.message !== "string") {
88
- return [];
89
- }
90
- const path = Array.isArray(record.path)
91
- ? record.path.map((segment) => String(segment))
92
- : [];
93
- return [{
94
- ...(stringValue(record.code) ? { code: stringValue(record.code) } : {}),
95
- message: record.message,
96
- path,
97
- ...(stringValue(record.validation)
98
- ? { validation: stringValue(record.validation) }
99
- : {}),
100
- }];
101
- });
102
- }
103
- function codeValue(value) {
104
- return typeof value === "string" && /^[a-z][a-z0-9_]+$/.test(value)
105
- ? value
106
- : undefined;
107
- }
108
- function codeFromOutcome(value) {
109
- var _a, _b;
110
- const outcome = asRecord(value);
111
- const issues = Array.isArray(outcome === null || outcome === void 0 ? void 0 : outcome.issue) ? outcome.issue : [];
112
- for (const issue of issues) {
113
- const record = asRecord(issue);
114
- const details = asRecord(record === null || record === void 0 ? void 0 : record.details);
115
- const coding = Array.isArray(details === null || details === void 0 ? void 0 : details.coding) ? details.coding : [];
116
- for (const entry of coding) {
117
- const code = codeValue((_a = asRecord(entry)) === null || _a === void 0 ? void 0 : _a.code);
118
- if (code === null || code === void 0 ? void 0 : code.startsWith("signals_")) {
119
- return code;
120
- }
121
- }
122
- const diagnostics = parseJson(record === null || record === void 0 ? void 0 : record.diagnostics);
123
- const diagnostic = asRecord(diagnostics);
124
- const code = (_b = codeValue(diagnostic === null || diagnostic === void 0 ? void 0 : diagnostic.error)) !== null && _b !== void 0 ? _b : codeValue(diagnostic === null || diagnostic === void 0 ? void 0 : diagnostic.code);
125
- if (code) {
126
- return code;
127
- }
128
- }
129
- return undefined;
130
- }
131
- function statusFromOutcome(value) {
132
- var _a, _b;
133
- const outcome = asRecord(value);
134
- const issues = Array.isArray(outcome === null || outcome === void 0 ? void 0 : outcome.issue) ? outcome.issue : [];
135
- const statusByIssue = {
136
- conflict: 409,
137
- forbidden: 403,
138
- "not-found": 404,
139
- "too-many-requests": 429,
140
- throttled: 429,
141
- };
142
- for (const issue of issues) {
143
- const status = statusByIssue[String((_b = (_a = asRecord(issue)) === null || _a === void 0 ? void 0 : _a.code) !== null && _b !== void 0 ? _b : "")];
144
- if (status) {
145
- return status;
146
- }
147
- }
148
- return undefined;
149
- }
150
- function numberValue(value) {
151
- return typeof value === "number" && Number.isFinite(value) ? value : undefined;
152
- }
153
- function stringValue(value) {
154
- return typeof value === "string" && value.length > 0 ? value : undefined;
155
- }
156
- function parseJson(value) {
157
- if (typeof value !== "string") {
158
- return undefined;
159
- }
160
- try {
161
- return JSON.parse(value);
162
- }
163
- catch (_a) {
164
- return undefined;
165
- }
166
- }
@@ -0,0 +1,2 @@
1
+ export * from "./methods";
2
+ export * from "./types";