@ovok/core 0.4.35 → 0.4.37

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 (54) hide show
  1. package/dist/client/auth/session-fetch.d.ts +35 -0
  2. package/dist/client/auth/session-fetch.js +207 -0
  3. package/dist/client/auth/session-http.d.ts +11 -0
  4. package/dist/client/auth/session-http.js +180 -0
  5. package/dist/client/experience/methods.js +27 -37
  6. package/dist/client/notifications/methods.js +4 -5
  7. package/dist/client/observation/services/creator.js +4 -18
  8. package/dist/client/observation/services/single-value-services.d.ts +31 -0
  9. package/dist/client/observation/services/single-value-services.js +95 -0
  10. package/dist/client/observation/types/measurement/MeasurementDevice.d.ts +9 -0
  11. package/dist/client/observation/utils/getDeviceObject.js +18 -4
  12. package/dist/client/ovok-client.d.ts +2 -13
  13. package/dist/client/ovok-client.js +8 -356
  14. package/dist/client/patient/methods.js +2 -11
  15. package/dist/client/patient-sharing/methods.js +1 -2
  16. package/dist/client/platform/methods.js +14 -24
  17. package/dist/client/query-string.d.ts +3 -0
  18. package/dist/client/query-string.js +15 -0
  19. package/dist/client/questionnaire-response/methods/extractQuestionnaireObservations.js +1 -2
  20. package/dist/client/questionnaire-response/methods/populateQuestionnaire.js +1 -2
  21. package/dist/client/signals/methods.js +16 -17
  22. package/dist/client/slim/methods.js +58 -68
  23. package/dist/conformance/capability-requirements.js +1 -1
  24. package/dist/hooks/account-hooks.js +6 -68
  25. package/dist/hooks/api-hooks.d.ts +5 -0
  26. package/dist/hooks/api-hooks.js +22 -67
  27. package/dist/hooks/email-hooks.js +1 -40
  28. package/dist/hooks/experience-hooks.js +4 -60
  29. package/dist/hooks/notification-hooks.js +5 -55
  30. package/dist/hooks/notification-invalidation.d.ts +6 -0
  31. package/dist/hooks/notification-invalidation.js +52 -0
  32. package/dist/hooks/patient-hooks.js +13 -113
  33. package/dist/hooks/patient-sharing-hooks.js +1 -5
  34. package/dist/hooks/platform-hooks.js +3 -51
  35. package/dist/hooks/signals-hooks.js +2 -45
  36. package/package.json +1 -1
  37. package/dist/client/observation/services/blood-glucose.d.ts +0 -12
  38. package/dist/client/observation/services/blood-glucose.js +0 -36
  39. package/dist/client/observation/services/body-temperature.d.ts +0 -13
  40. package/dist/client/observation/services/body-temperature.js +0 -36
  41. package/dist/client/observation/services/body-weight.d.ts +0 -13
  42. package/dist/client/observation/services/body-weight.js +0 -36
  43. package/dist/client/observation/services/heart-rate-variability.d.ts +0 -13
  44. package/dist/client/observation/services/heart-rate-variability.js +0 -38
  45. package/dist/client/observation/services/heart-rate.d.ts +0 -13
  46. package/dist/client/observation/services/heart-rate.js +0 -39
  47. package/dist/client/observation/services/respiratory-rate.d.ts +0 -13
  48. package/dist/client/observation/services/respiratory-rate.js +0 -38
  49. package/dist/client/observation/services/resting-heart-rate.d.ts +0 -13
  50. package/dist/client/observation/services/resting-heart-rate.js +0 -38
  51. package/dist/client/observation/services/vo2-max.d.ts +0 -13
  52. package/dist/client/observation/services/vo2-max.js +0 -38
  53. package/dist/client/observation/services/walking-heart-rate-average.d.ts +0 -12
  54. package/dist/client/observation/services/walking-heart-rate-average.js +0 -38
@@ -0,0 +1,35 @@
1
+ import { IClientStorage } from "@medplum/core";
2
+ import type { OvokClientOptions } from "../ovok-client";
3
+ export type OvokUnauthenticatedReason = {
4
+ type: "refresh-rejected";
5
+ status: 400 | 401 | 403;
6
+ } | {
7
+ type: "refresh-unavailable";
8
+ status?: number;
9
+ } | {
10
+ type: "authentication-rejected";
11
+ status: number;
12
+ } | {
13
+ type: "request-unauthorized";
14
+ status: 401;
15
+ };
16
+ type SessionFetchContext = {
17
+ config: OvokClientOptions;
18
+ storage: IClientStorage;
19
+ fetchImplementation: (url: string, options?: RequestInit) => Promise<Response>;
20
+ normalizedBaseUrl: string;
21
+ tokenUrl: string;
22
+ };
23
+ /**
24
+ * The client's `fetch`: request timeouts, token-refresh retries within the access token's
25
+ * lifetime, and tracking why a session ended, which `onUnauthenticated` reports.
26
+ */
27
+ export declare function createSessionFetch({ config, storage, fetchImplementation, normalizedBaseUrl, tokenUrl }: SessionFetchContext): {
28
+ fetch: (url: string, options: RequestInit | undefined) => Promise<Response>;
29
+ onUnauthenticated: () => void;
30
+ refreshState: {
31
+ unavailable: boolean;
32
+ };
33
+ setClearActiveLogin: (clear: () => void) => void;
34
+ };
35
+ export {};
@@ -0,0 +1,207 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createSessionFetch = createSessionFetch;
4
+ /* eslint-disable max-lines -- one refresh state machine; splitting it would scatter its shared session state. */
5
+ const core_1 = require("@medplum/core");
6
+ const account_storage_1 = require("./account-storage");
7
+ const session_http_1 = require("./session-http");
8
+ /**
9
+ * The client's `fetch`: request timeouts, token-refresh retries within the access token's
10
+ * lifetime, and tracking why a session ended, which `onUnauthenticated` reports.
11
+ */
12
+ function createSessionFetch({ config, storage, fetchImplementation, normalizedBaseUrl, tokenUrl }) {
13
+ let unauthenticatedReason;
14
+ let clearActiveLogin = () => undefined;
15
+ let terminalRefreshToken;
16
+ let terminalRefreshError;
17
+ const refreshState = { unavailable: false };
18
+ const isTokenRefresh = (url, options) => new URL(url, normalizedBaseUrl).href === tokenUrl &&
19
+ (options === null || options === void 0 ? void 0 : options.method) === "POST" &&
20
+ typeof options.body === "string" &&
21
+ new URLSearchParams(options.body).get("grant_type") === "refresh_token";
22
+ const markActiveAccountExpired = () => {
23
+ var _a, _b;
24
+ const accountId = (_b = (_a = storage.getObject("activeLogin")) === null || _a === void 0 ? void 0 : _a.profile) === null || _b === void 0 ? void 0 : _b.reference;
25
+ if (accountId) {
26
+ (0, account_storage_1.setStoredAccountExpired)(storage, accountId, true);
27
+ }
28
+ };
29
+ const fetchOnce = async (url, options) => {
30
+ var _a, _b;
31
+ const response = await (0, session_http_1.fetchWithTimeout)(fetchImplementation, url, options, config.requestTimeoutMs);
32
+ const requestUrl = new URL(url, normalizedBaseUrl);
33
+ if (isTokenRefresh(url, options)) {
34
+ if (response.status === 400) {
35
+ let invalidGrant = false;
36
+ try {
37
+ invalidGrant = (await response.clone().json()).error === "invalid_grant";
38
+ }
39
+ catch (_c) {
40
+ // Do not sign out unless the response confirms that the refresh token was revoked.
41
+ }
42
+ if (!invalidGrant) {
43
+ throw (0, session_http_1.refreshResponseError)(response);
44
+ }
45
+ }
46
+ if ([400, 401, 403].includes(response.status)) {
47
+ unauthenticatedReason = {
48
+ type: "refresh-rejected",
49
+ status: response.status,
50
+ };
51
+ markActiveAccountExpired();
52
+ }
53
+ else if (response.ok) {
54
+ const accountId = (_b = (_a = storage.getObject("activeLogin")) === null || _a === void 0 ? void 0 : _a.profile) === null || _b === void 0 ? void 0 : _b.reference;
55
+ if (accountId) {
56
+ (0, account_storage_1.setStoredAccountExpired)(storage, accountId, false);
57
+ }
58
+ unauthenticatedReason = undefined;
59
+ }
60
+ }
61
+ else if (requestUrl.href === tokenUrl && !response.ok) {
62
+ unauthenticatedReason = {
63
+ type: "authentication-rejected",
64
+ status: response.status,
65
+ };
66
+ }
67
+ else if (response.status === 401) {
68
+ unauthenticatedReason = { type: "request-unauthorized", status: 401 };
69
+ }
70
+ return isTokenRefresh(url, options)
71
+ ? response
72
+ : (0, session_http_1.withHttpErrorMetadata)(response);
73
+ };
74
+ const fetchWithRefreshRetry = async (url, options) => {
75
+ var _a, _b, _c, _d;
76
+ const requestUrl = new URL(url, normalizedBaseUrl);
77
+ if (!isTokenRefresh(url, options)) {
78
+ const response = await fetchOnce(url, options);
79
+ const sessionEnded = requestUrl.pathname.endsWith("/auth/session/current") && response.status === 401;
80
+ if (sessionEnded) {
81
+ markActiveAccountExpired();
82
+ }
83
+ return response;
84
+ }
85
+ const login = storage.getObject("activeLogin");
86
+ if (terminalRefreshError) {
87
+ if ((login === null || login === void 0 ? void 0 : login.accessToken) && login.accessToken !== terminalRefreshToken) {
88
+ terminalRefreshToken = undefined;
89
+ terminalRefreshError = undefined;
90
+ }
91
+ else {
92
+ throw terminalRefreshError;
93
+ }
94
+ }
95
+ const expiresAt = (login === null || login === void 0 ? void 0 : login.accessToken) ? (0, core_1.tryGetJwtExpiration)(login.accessToken) : undefined;
96
+ const signal = options === null || options === void 0 ? void 0 : options.signal;
97
+ if (expiresAt !== undefined && options) {
98
+ const tokenRequestOptions = options;
99
+ tokenRequestOptions.maxRetries = 0;
100
+ tokenRequestOptions.maxRetryTime = 0;
101
+ }
102
+ if (expiresAt === undefined) {
103
+ const response = await fetchOnce(url, options);
104
+ if ((0, session_http_1.isTemporaryRefreshFailure)(response.status)) {
105
+ void ((_a = response.body) === null || _a === void 0 ? void 0 : _a.cancel().catch(() => undefined));
106
+ if (config.keepSessionOnRefreshUnavailable) {
107
+ refreshState.unavailable = true;
108
+ }
109
+ throw (0, session_http_1.refreshUnavailableError)(response);
110
+ }
111
+ return response;
112
+ }
113
+ let attempt = 0;
114
+ let firstAttempt = true;
115
+ let lastResponse;
116
+ let lastNetworkError;
117
+ while (firstAttempt || Date.now() < expiresAt) {
118
+ firstAttempt = false;
119
+ try {
120
+ lastResponse = await fetchOnce(url, options);
121
+ }
122
+ catch (error) {
123
+ if ((signal === null || signal === void 0 ? void 0 : signal.aborted) || !(0, session_http_1.isNetworkError)(error)) {
124
+ throw error;
125
+ }
126
+ if (config.keepSessionOnRefreshUnavailable) {
127
+ refreshState.unavailable = true;
128
+ lastNetworkError = error;
129
+ break;
130
+ }
131
+ if (Date.now() >= expiresAt) {
132
+ lastNetworkError = error;
133
+ break;
134
+ }
135
+ lastNetworkError = error;
136
+ const remainingMs = expiresAt - Date.now();
137
+ const backoffMs = (0, session_http_1.refreshBackoffMs)(attempt);
138
+ await (0, session_http_1.waitForRetry)(Math.min(backoffMs, remainingMs), signal);
139
+ attempt += 1;
140
+ continue;
141
+ }
142
+ if (!(0, session_http_1.isTemporaryRefreshFailure)(lastResponse.status)) {
143
+ return lastResponse;
144
+ }
145
+ if (config.keepSessionOnRefreshUnavailable) {
146
+ refreshState.unavailable = true;
147
+ break;
148
+ }
149
+ const remainingMs = expiresAt - Date.now();
150
+ if (remainingMs <= 0) {
151
+ break;
152
+ }
153
+ const backoffMs = (0, session_http_1.refreshBackoffMs)(attempt);
154
+ const waitMs = Math.min(Math.max((_b = (0, session_http_1.retryAfterMs)(lastResponse)) !== null && _b !== void 0 ? _b : 0, backoffMs), remainingMs);
155
+ if (waitMs >= remainingMs) {
156
+ await (0, session_http_1.waitForRetry)(remainingMs, signal);
157
+ break;
158
+ }
159
+ void ((_c = lastResponse.body) === null || _c === void 0 ? void 0 : _c.cancel().catch(() => undefined));
160
+ await (0, session_http_1.waitForRetry)(waitMs, signal);
161
+ attempt += 1;
162
+ }
163
+ const status = lastResponse === null || lastResponse === void 0 ? void 0 : lastResponse.status;
164
+ const reason = {
165
+ type: "refresh-unavailable",
166
+ ...(status === undefined ? {} : { status }),
167
+ };
168
+ if (config.keepSessionOnRefreshUnavailable) {
169
+ refreshState.unavailable = true;
170
+ if (lastResponse) {
171
+ return lastResponse;
172
+ }
173
+ throw lastNetworkError !== null && lastNetworkError !== void 0 ? lastNetworkError : (0, session_http_1.refreshUnavailableError)(undefined);
174
+ }
175
+ unauthenticatedReason = reason;
176
+ markActiveAccountExpired();
177
+ if (lastResponse) {
178
+ return lastResponse;
179
+ }
180
+ terminalRefreshToken = login === null || login === void 0 ? void 0 : login.accessToken;
181
+ terminalRefreshError = lastNetworkError !== null && lastNetworkError !== void 0 ? lastNetworkError : (0, session_http_1.refreshUnavailableError)(undefined);
182
+ clearActiveLogin();
183
+ (_d = config.onUnauthenticated) === null || _d === void 0 ? void 0 : _d.call(config, reason);
184
+ unauthenticatedReason = undefined;
185
+ throw terminalRefreshError;
186
+ };
187
+ const onUnauthenticated = () => {
188
+ var _a;
189
+ if (refreshState.unavailable) {
190
+ return;
191
+ }
192
+ const reason = unauthenticatedReason !== null && unauthenticatedReason !== void 0 ? unauthenticatedReason : {
193
+ type: "request-unauthorized",
194
+ status: 401,
195
+ };
196
+ unauthenticatedReason = undefined;
197
+ (_a = config.onUnauthenticated) === null || _a === void 0 ? void 0 : _a.call(config, reason);
198
+ };
199
+ return {
200
+ fetch: fetchWithRefreshRetry,
201
+ onUnauthenticated,
202
+ refreshState,
203
+ setClearActiveLogin: (clear) => {
204
+ clearActiveLogin = clear;
205
+ },
206
+ };
207
+ }
@@ -0,0 +1,11 @@
1
+ export declare const retryAfterMs: (response: Response) => number | undefined;
2
+ export declare const withHttpErrorMetadata: (response: Response) => Promise<Response>;
3
+ export declare const waitForRetry: (milliseconds: number, signal?: AbortSignal) => Promise<void>;
4
+ export declare const isTemporaryRefreshFailure: (status: number) => boolean;
5
+ export declare const refreshUnavailableError: (response?: Response) => Error;
6
+ export declare const refreshResponseError: (response: Response) => Error;
7
+ export declare const isNetworkError: (error: unknown) => boolean;
8
+ /** Fetches with an upper time bound, still honouring the caller's own abort signal. */
9
+ export declare const fetchWithTimeout: (fetchImplementation: (url: string, options?: RequestInit) => Promise<Response>, url: string, options: RequestInit | undefined, requestTimeoutMs: number | undefined) => Promise<Response>;
10
+ /** Exponential refresh backoff: 500 ms doubling per attempt, capped at 30 s. */
11
+ export declare const refreshBackoffMs: (attempt: number) => number;
@@ -0,0 +1,180 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.refreshBackoffMs = exports.fetchWithTimeout = exports.isNetworkError = exports.refreshResponseError = exports.refreshUnavailableError = exports.isTemporaryRefreshFailure = exports.waitForRetry = exports.withHttpErrorMetadata = exports.retryAfterMs = void 0;
4
+ const ovok_error_utils_1 = require("../errors/ovok-error-utils");
5
+ const retryAfterMs = (response) => {
6
+ var _a, _b;
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;
10
+ }
11
+ const seconds = Number(value);
12
+ if (Number.isFinite(seconds)) {
13
+ return Math.max(0, seconds * 1000);
14
+ }
15
+ const date = Date.parse(value);
16
+ return Number.isFinite(date) ? Math.max(0, date - Date.now()) : undefined;
17
+ };
18
+ exports.retryAfterMs = retryAfterMs;
19
+ const timeoutError = () => {
20
+ const error = new Error("The request timed out.");
21
+ error.name = "TimeoutError";
22
+ return error;
23
+ };
24
+ const withHttpErrorMetadata = async (response) => {
25
+ var _a, _b;
26
+ if (response.ok || typeof response.clone !== "function") {
27
+ return response;
28
+ }
29
+ let bodyText = "";
30
+ let body;
31
+ try {
32
+ bodyText = await response.clone().text();
33
+ body = JSON.parse(bodyText);
34
+ }
35
+ catch (_c) {
36
+ body = undefined;
37
+ }
38
+ const outcome = body && typeof body === "object" && body.resourceType === "OperationOutcome"
39
+ ? body
40
+ : undefined;
41
+ const envelope = body && typeof body === "object" ? body : undefined;
42
+ const nestedError = (envelope === null || envelope === void 0 ? void 0 : envelope.error) && typeof envelope.error === "object"
43
+ ? envelope.error
44
+ : undefined;
45
+ const safeResponse = envelope
46
+ ? Object.fromEntries(Object.entries(envelope).filter(([key]) => ["code", "error", "message", "requestId", "retryAfterSec", "status", "statusCode", "errors", "missing"].includes(key)))
47
+ : { message: typeof body === "string" ? body : response.statusText };
48
+ const status = response.status;
49
+ 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);
50
+ 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);
51
+ const message = typeof (envelope === null || envelope === void 0 ? void 0 : envelope.message) === "string"
52
+ ? envelope.message
53
+ : typeof (nestedError === null || nestedError === void 0 ? void 0 : nestedError.message) === "string"
54
+ ? nestedError.message
55
+ : response.statusText || `HTTP ${status}`;
56
+ const extensions = [];
57
+ const addMetadata = (url, value, kind) => {
58
+ if (value !== undefined) {
59
+ extensions.push({ url, [`value${kind}`]: value });
60
+ }
61
+ };
62
+ addMetadata("status", status, "Integer");
63
+ addMetadata("requestId", requestId, "String");
64
+ addMetadata("retryAfterMs", retryAfter, "Integer");
65
+ if (!outcome) {
66
+ extensions.push({ url: "response", valueString: JSON.stringify(safeResponse) });
67
+ }
68
+ const enrichedOutcome = outcome !== null && outcome !== void 0 ? outcome : {
69
+ resourceType: "OperationOutcome",
70
+ id: "ovok-http-error",
71
+ issue: [{
72
+ severity: "error",
73
+ code: status === 429 ? "throttled" : "exception",
74
+ details: { text: bodyText || message },
75
+ }],
76
+ };
77
+ const existingExtensions = Array.isArray(enrichedOutcome.extension)
78
+ ? enrichedOutcome.extension
79
+ : [];
80
+ enrichedOutcome.extension = [
81
+ ...existingExtensions,
82
+ { url: ovok_error_utils_1.OVOK_HTTP_ERROR_EXTENSION_URL, extension: extensions },
83
+ ];
84
+ const headers = new Headers(response.headers);
85
+ headers.delete("content-length");
86
+ headers.set("content-type", "application/fhir+json");
87
+ return new Response(JSON.stringify(enrichedOutcome), {
88
+ status,
89
+ statusText: response.statusText,
90
+ headers,
91
+ });
92
+ };
93
+ exports.withHttpErrorMetadata = withHttpErrorMetadata;
94
+ const waitForRetry = (milliseconds, signal) => new Promise((resolve, reject) => {
95
+ var _a;
96
+ if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
97
+ const error = new Error("Token refresh aborted");
98
+ error.name = "AbortError";
99
+ reject((_a = signal.reason) !== null && _a !== void 0 ? _a : error);
100
+ return;
101
+ }
102
+ const timer = setTimeout(() => {
103
+ signal === null || signal === void 0 ? void 0 : signal.removeEventListener("abort", abort);
104
+ resolve();
105
+ }, milliseconds);
106
+ const abort = () => {
107
+ var _a;
108
+ clearTimeout(timer);
109
+ const error = new Error("Token refresh aborted");
110
+ error.name = "AbortError";
111
+ reject((_a = signal === null || signal === void 0 ? void 0 : signal.reason) !== null && _a !== void 0 ? _a : error);
112
+ };
113
+ signal === null || signal === void 0 ? void 0 : signal.addEventListener("abort", abort, { once: true });
114
+ });
115
+ exports.waitForRetry = waitForRetry;
116
+ const isTemporaryRefreshFailure = (status) => status === 429 || (status >= 500 && status <= 599);
117
+ exports.isTemporaryRefreshFailure = isTemporaryRefreshFailure;
118
+ const refreshUnavailableError = (response) => {
119
+ const error = new Error("Token refresh is temporarily unavailable");
120
+ error.name = "NetworkError";
121
+ Object.assign(error, { cause: response });
122
+ return error;
123
+ };
124
+ exports.refreshUnavailableError = refreshUnavailableError;
125
+ const refreshResponseError = (response) => {
126
+ const error = new Error(`Token refresh returned HTTP ${response.status}`);
127
+ error.name = "TokenRefreshError";
128
+ Object.assign(error, { status: response.status, cause: response });
129
+ return error;
130
+ };
131
+ exports.refreshResponseError = refreshResponseError;
132
+ const isNetworkError = (error) => {
133
+ if (!(error instanceof Error)) {
134
+ return false;
135
+ }
136
+ if (error.name === "NetworkError" ||
137
+ error.name === "UnknownHostException" ||
138
+ /network request failed|failed to fetch|load failed|unknownhostexception|econn|enotfound/i.test(error.message)) {
139
+ return true;
140
+ }
141
+ return (0, exports.isNetworkError)(error.cause);
142
+ };
143
+ exports.isNetworkError = isNetworkError;
144
+ /** Fetches with an upper time bound, still honouring the caller's own abort signal. */
145
+ const fetchWithTimeout = async (fetchImplementation, url, options, requestTimeoutMs) => {
146
+ if (requestTimeoutMs === undefined) {
147
+ return fetchImplementation(url, options);
148
+ }
149
+ const controller = new AbortController();
150
+ const callerSignal = options === null || options === void 0 ? void 0 : options.signal;
151
+ let timedOut = false;
152
+ const abortFromCaller = () => controller.abort(callerSignal === null || callerSignal === void 0 ? void 0 : callerSignal.reason);
153
+ const timeout = setTimeout(() => {
154
+ timedOut = true;
155
+ controller.abort(timeoutError());
156
+ }, requestTimeoutMs);
157
+ if (callerSignal === null || callerSignal === void 0 ? void 0 : callerSignal.aborted) {
158
+ abortFromCaller();
159
+ }
160
+ else {
161
+ callerSignal === null || callerSignal === void 0 ? void 0 : callerSignal.addEventListener("abort", abortFromCaller, { once: true });
162
+ }
163
+ try {
164
+ return await fetchImplementation(url, { ...options, signal: controller.signal });
165
+ }
166
+ catch (error) {
167
+ if (timedOut) {
168
+ throw timeoutError();
169
+ }
170
+ throw error;
171
+ }
172
+ finally {
173
+ clearTimeout(timeout);
174
+ callerSignal === null || callerSignal === void 0 ? void 0 : callerSignal.removeEventListener("abort", abortFromCaller);
175
+ }
176
+ };
177
+ exports.fetchWithTimeout = fetchWithTimeout;
178
+ /** Exponential refresh backoff: 500 ms doubling per attempt, capped at 30 s. */
179
+ const refreshBackoffMs = (attempt) => Math.min(500 * (2 ** Math.min(attempt, 6)), 30000);
180
+ exports.refreshBackoffMs = refreshBackoffMs;
@@ -34,85 +34,75 @@ exports.listChatSessions = listChatSessions;
34
34
  exports.getChatSession = getChatSession;
35
35
  exports.listChatMessages = listChatMessages;
36
36
  exports.searchAIFhir = searchAIFhir;
37
- const segment = (value) => encodeURIComponent(value);
38
- const query = (params) => {
39
- const values = new URLSearchParams();
40
- for (const [key, value] of Object.entries(params)) {
41
- if (value !== undefined) {
42
- values.set(key, String(value));
43
- }
44
- }
45
- const encoded = values.toString();
46
- return encoded === "" ? "" : `?${encoded}`;
47
- };
48
- const documentUrl = (client, path, params) => `${client.getBaseUrl().replace(/\/$/, "")}${path}${query(params)}`;
37
+ const query_string_1 = require("../query-string");
38
+ const documentUrl = (client, path, params) => `${client.getBaseUrl().replace(/\/$/, "")}${path}${(0, query_string_1.toQueryString)(params)}`;
49
39
  async function requestConsentSignature(consentId, requestOptions) {
50
- return this.post(`/consent/signature/${segment(consentId)}`, undefined, undefined, requestOptions);
40
+ return this.post(`/consent/signature/${encodeURIComponent(consentId)}`, undefined, undefined, requestOptions);
51
41
  }
52
42
  async function createDocumentUpload(body, requestOptions) {
53
43
  return this.post("/document", body, undefined, requestOptions);
54
44
  }
55
45
  async function listDocuments(params = {}, requestOptions) {
56
- return this.get(`/document${query(params)}`, ...(requestOptions === undefined ? [] : [requestOptions]));
46
+ return this.get(`/document${(0, query_string_1.toQueryString)(params)}`, ...(requestOptions === undefined ? [] : [requestOptions]));
57
47
  }
58
48
  /** Returns the authenticated document endpoint; navigating/fetching it follows the backend redirect. */
59
49
  async function getDocumentDownloadUrl(documentId, params = {}) {
60
- return documentUrl(this, `/document/${segment(documentId)}`, params);
50
+ return documentUrl(this, `/document/${encodeURIComponent(documentId)}`, params);
61
51
  }
62
52
  /** Returns a public document endpoint for use in an image, download, or media element. */
63
53
  async function getPublicDocumentDownloadUrl(token, params = {}) {
64
- return documentUrl(this, `/document/public/${segment(token)}`, params);
54
+ return documentUrl(this, `/document/public/${encodeURIComponent(token)}`, params);
65
55
  }
66
56
  async function deleteDocument(documentId, requestOptions) {
67
- return (await this.delete(`/document/${segment(documentId)}`, ...(requestOptions === undefined ? [] : [requestOptions])));
57
+ return (await this.delete(`/document/${encodeURIComponent(documentId)}`, ...(requestOptions === undefined ? [] : [requestOptions])));
68
58
  }
69
59
  async function updateDocumentMetadata(documentId, body, requestOptions) {
70
- return (await this.patch(`/document/${segment(documentId)}`, body, ...(requestOptions === undefined ? [] : [requestOptions])));
60
+ return (await this.patch(`/document/${encodeURIComponent(documentId)}`, body, ...(requestOptions === undefined ? [] : [requestOptions])));
71
61
  }
72
62
  async function generateDocumentReplacementUpload(documentId, body, requestOptions) {
73
- return this.post(`/document/${segment(documentId)}`, body, undefined, requestOptions);
63
+ return this.post(`/document/${encodeURIComponent(documentId)}`, body, undefined, requestOptions);
74
64
  }
75
65
  async function searchMappedContent(type, language, params = {}, requestOptions) {
76
- return this.get(`/cms/${segment(type)}/map/${segment(language)}${query(params)}`, ...(requestOptions === undefined ? [] : [requestOptions]));
66
+ return this.get(`/cms/${encodeURIComponent(type)}/map/${encodeURIComponent(language)}${(0, query_string_1.toQueryString)(params)}`, ...(requestOptions === undefined ? [] : [requestOptions]));
77
67
  }
78
68
  async function copyContentsFromParent(type, language, requestOptions) {
79
- return this.post(`/cms/${segment(type)}/${segment(language)}/copy-from-parent-project`, undefined, undefined, requestOptions);
69
+ return this.post(`/cms/${encodeURIComponent(type)}/${encodeURIComponent(language)}/copy-from-parent-project`, undefined, undefined, requestOptions);
80
70
  }
81
71
  async function copyContentFromParent(type, language, key, requestOptions) {
82
- return this.post(`/cms/${segment(type)}/${segment(language)}/${segment(key)}/copy-from-parent-project`, undefined, undefined, requestOptions);
72
+ return this.post(`/cms/${encodeURIComponent(type)}/${encodeURIComponent(language)}/${encodeURIComponent(key)}/copy-from-parent-project`, undefined, undefined, requestOptions);
83
73
  }
84
74
  async function createContent(type, language, body, requestOptions) {
85
- return this.post(`/cms/${segment(type)}/${segment(language)}`, body, undefined, requestOptions);
75
+ return this.post(`/cms/${encodeURIComponent(type)}/${encodeURIComponent(language)}`, body, undefined, requestOptions);
86
76
  }
87
77
  async function updateContent(type, language, key, body, requestOptions) {
88
- return (await this.put(`/cms/${segment(type)}/${segment(language)}/${segment(key)}`, body, undefined, requestOptions));
78
+ return (await this.put(`/cms/${encodeURIComponent(type)}/${encodeURIComponent(language)}/${encodeURIComponent(key)}`, body, undefined, requestOptions));
89
79
  }
90
80
  async function getContent(type, language, key, requestOptions) {
91
- return this.get(`/cms/${segment(type)}/${segment(language)}/${segment(key)}`, ...(requestOptions === undefined ? [] : [requestOptions]));
81
+ return this.get(`/cms/${encodeURIComponent(type)}/${encodeURIComponent(language)}/${encodeURIComponent(key)}`, ...(requestOptions === undefined ? [] : [requestOptions]));
92
82
  }
93
83
  async function deleteContent(type, language, key, requestOptions) {
94
- await this.delete(`/cms/${segment(type)}/${segment(language)}/${segment(key)}`, ...(requestOptions === undefined ? [] : [requestOptions]));
84
+ await this.delete(`/cms/${encodeURIComponent(type)}/${encodeURIComponent(language)}/${encodeURIComponent(key)}`, ...(requestOptions === undefined ? [] : [requestOptions]));
95
85
  }
96
86
  async function searchContent(type, language, params = {}, requestOptions) {
97
- return this.get(`/cms/${segment(type)}/${segment(language)}${query(params)}`, ...(requestOptions === undefined ? [] : [requestOptions]));
87
+ return this.get(`/cms/${encodeURIComponent(type)}/${encodeURIComponent(language)}${(0, query_string_1.toQueryString)(params)}`, ...(requestOptions === undefined ? [] : [requestOptions]));
98
88
  }
99
89
  async function createVideoCallAppointment(body, requestOptions) {
100
90
  return this.post("/video-call/livekit/appointment", body, undefined, requestOptions);
101
91
  }
102
92
  async function updateVideoCallAppointment(appointmentId, body, requestOptions) {
103
- return (await this.put(`/video-call/livekit/appointment/${segment(appointmentId)}`, body, undefined, requestOptions));
93
+ return (await this.put(`/video-call/livekit/appointment/${encodeURIComponent(appointmentId)}`, body, undefined, requestOptions));
104
94
  }
105
95
  async function listVideoCallAppointments(params = {}, requestOptions) {
106
- return this.get(`/video-call/livekit/appointment${query(params)}`, ...(requestOptions === undefined ? [] : [requestOptions]));
96
+ return this.get(`/video-call/livekit/appointment${(0, query_string_1.toQueryString)(params)}`, ...(requestOptions === undefined ? [] : [requestOptions]));
107
97
  }
108
98
  async function getVideoCallAccessPermission(appointmentId, requestOptions) {
109
- return this.get(`/video-call/livekit/access-permission/${segment(appointmentId)}`, ...(requestOptions === undefined ? [] : [requestOptions]));
99
+ return this.get(`/video-call/livekit/access-permission/${encodeURIComponent(appointmentId)}`, ...(requestOptions === undefined ? [] : [requestOptions]));
110
100
  }
111
101
  async function createVideoCallGuestAccess(appointmentId, body, requestOptions) {
112
- return this.post(`/video-call/livekit/guest-access/${segment(appointmentId)}`, body, undefined, requestOptions);
102
+ return this.post(`/video-call/livekit/guest-access/${encodeURIComponent(appointmentId)}`, body, undefined, requestOptions);
113
103
  }
114
104
  async function createVideoCallUserAccess(appointmentId, requestOptions) {
115
- return this.post(`/video-call/livekit/user-access/${segment(appointmentId)}`, undefined, undefined, requestOptions);
105
+ return this.post(`/video-call/livekit/user-access/${encodeURIComponent(appointmentId)}`, undefined, undefined, requestOptions);
116
106
  }
117
107
  async function sendVideoCallInvite(body, requestOptions) {
118
108
  return this.post("/video-call/livekit/mail/invite", body, undefined, requestOptions);
@@ -124,7 +114,7 @@ async function sendVideoCallCancellation(body, requestOptions) {
124
114
  return this.post("/video-call/livekit/mail/cancel", body, undefined, requestOptions);
125
115
  }
126
116
  async function sendVideoCallActiveNotification(appointmentId, requestOptions) {
127
- return this.post(`/video-call/livekit/notification/active-call/${segment(appointmentId)}`, undefined, undefined, requestOptions);
117
+ return this.post(`/video-call/livekit/notification/active-call/${encodeURIComponent(appointmentId)}`, undefined, undefined, requestOptions);
128
118
  }
129
119
  async function translate(body, requestOptions) {
130
120
  return this.post("/ai/translation", body, undefined, requestOptions);
@@ -133,16 +123,16 @@ async function createChatSession(body, requestOptions) {
133
123
  return this.post("/ai/session", body, undefined, requestOptions);
134
124
  }
135
125
  async function sendChatMessage(sessionId, body, requestOptions) {
136
- return this.post(`/ai/session/${segment(sessionId)}/message`, body, undefined, requestOptions);
126
+ return this.post(`/ai/session/${encodeURIComponent(sessionId)}/message`, body, undefined, requestOptions);
137
127
  }
138
128
  async function listChatSessions(params = {}, requestOptions) {
139
- return this.get(`/ai/session${query(params)}`, ...(requestOptions === undefined ? [] : [requestOptions]));
129
+ return this.get(`/ai/session${(0, query_string_1.toQueryString)(params)}`, ...(requestOptions === undefined ? [] : [requestOptions]));
140
130
  }
141
131
  async function getChatSession(sessionId, params = {}, requestOptions) {
142
- return this.get(`/ai/session/${segment(sessionId)}${query(params)}`, ...(requestOptions === undefined ? [] : [requestOptions]));
132
+ return this.get(`/ai/session/${encodeURIComponent(sessionId)}${(0, query_string_1.toQueryString)(params)}`, ...(requestOptions === undefined ? [] : [requestOptions]));
143
133
  }
144
134
  async function listChatMessages(sessionId, params = {}, requestOptions) {
145
- return this.get(`/ai/session/${segment(sessionId)}/message${query(params)}`, ...(requestOptions === undefined ? [] : [requestOptions]));
135
+ return this.get(`/ai/session/${encodeURIComponent(sessionId)}/message${(0, query_string_1.toQueryString)(params)}`, ...(requestOptions === undefined ? [] : [requestOptions]));
146
136
  }
147
137
  async function searchAIFhir(request, requestOptions) {
148
138
  return this.post("/ai/fhir/search", request, undefined, requestOptions);
@@ -5,7 +5,6 @@ exports.listNotificationNotes = listNotificationNotes;
5
5
  exports.updateNotificationNote = updateNotificationNote;
6
6
  exports.patchNotificationNote = patchNotificationNote;
7
7
  exports.deleteNotificationNote = deleteNotificationNote;
8
- const segment = (value) => encodeURIComponent(value);
9
8
  const TARGET_PATH = {
10
9
  oob: "oob",
11
10
  alarm: "alarms",
@@ -16,14 +15,14 @@ function createNotificationNote(body, requestOptions) {
16
15
  return this.post("/v1/slim/notifications/notes", body, undefined, requestOptions);
17
16
  }
18
17
  function listNotificationNotes(kind, id, requestOptions) {
19
- return this.get(`/v1/slim/notifications/${TARGET_PATH[kind]}/${segment(id)}/notes`, { ...{ cache: "reload" }, ...requestOptions });
18
+ return this.get(`/v1/slim/notifications/${TARGET_PATH[kind]}/${encodeURIComponent(id)}/notes`, { ...{ cache: "reload" }, ...requestOptions });
20
19
  }
21
20
  function updateNotificationNote(noteId, body, requestOptions) {
22
- return this.put(`/v1/slim/notifications/notes/${segment(noteId)}`, body, undefined, requestOptions);
21
+ return this.put(`/v1/slim/notifications/notes/${encodeURIComponent(noteId)}`, body, undefined, requestOptions);
23
22
  }
24
23
  function patchNotificationNote(noteId, body, requestOptions) {
25
- return this.patch(`/v1/slim/notifications/notes/${segment(noteId)}`, body, ...(requestOptions === undefined ? [] : [requestOptions]));
24
+ return this.patch(`/v1/slim/notifications/notes/${encodeURIComponent(noteId)}`, body, ...(requestOptions === undefined ? [] : [requestOptions]));
26
25
  }
27
26
  async function deleteNotificationNote(noteId, requestOptions) {
28
- await this.delete(`/v1/slim/notifications/notes/${segment(noteId)}`, ...(requestOptions === undefined ? [] : [requestOptions]));
27
+ await this.delete(`/v1/slim/notifications/notes/${encodeURIComponent(noteId)}`, ...(requestOptions === undefined ? [] : [requestOptions]));
29
28
  }
@@ -3,22 +3,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ObservationServiceCreator = void 0;
4
4
  const types_1 = require("../../../types");
5
5
  const baby_sleep_1 = require("./baby-sleep");
6
- const blood_glucose_1 = require("./blood-glucose");
7
6
  const blood_oxygen_1 = require("./blood-oxygen");
8
7
  const blood_pressure_1 = require("./blood-pressure");
9
- const body_temperature_1 = require("./body-temperature");
10
- const body_weight_1 = require("./body-weight");
11
8
  const ecg_1 = require("./ecg");
12
- const heart_rate_1 = require("./heart-rate");
13
- const heart_rate_variability_1 = require("./heart-rate-variability");
14
9
  const lipid_panel_1 = require("./lipid-panel");
15
- const respiratory_rate_1 = require("./respiratory-rate");
16
10
  const respiratory_therapy_1 = require("./respiratory-therapy");
17
- const resting_heart_rate_1 = require("./resting-heart-rate");
11
+ const single_value_services_1 = require("./single-value-services");
18
12
  const step_count_1 = require("./step-count");
19
13
  const uric_acid_service_1 = require("./uric-acid-service");
20
- const vo2_max_1 = require("./vo2-max");
21
- const walking_heart_rate_average_1 = require("./walking-heart-rate-average");
22
14
  class ObservationServiceCreator {
23
15
  constructor() {
24
16
  this.services = new Map();
@@ -31,19 +23,13 @@ class ObservationServiceCreator {
31
23
  return ObservationServiceCreator.instance;
32
24
  }
33
25
  initializeServices() {
34
- this.services.set(types_1.MeasurementTypeKey.bodyWeight, new body_weight_1.BodyWeightService());
26
+ for (const [type, service] of (0, single_value_services_1.singleValueObservationServices)()) {
27
+ this.services.set(type, service);
28
+ }
35
29
  this.services.set(types_1.MeasurementTypeKey.bloodPressure, new blood_pressure_1.BloodPressureService());
36
- this.services.set(types_1.MeasurementTypeKey.bloodGlucose, new blood_glucose_1.BloodGlucoseService());
37
- this.services.set(types_1.MeasurementTypeKey.temperature, new body_temperature_1.BodyTemperatureService());
38
30
  this.services.set(types_1.MeasurementTypeKey.bloodOxygenPulseRate, new blood_oxygen_1.BloodOxygenService());
39
31
  this.services.set(types_1.MeasurementTypeKey.ecg, new ecg_1.EcgService());
40
32
  this.services.set(types_1.MeasurementTypeKey.stepCount, new step_count_1.StepCountService());
41
- this.services.set(types_1.MeasurementTypeKey.restingHeartRate, new resting_heart_rate_1.RestingHeartRateService());
42
- this.services.set(types_1.MeasurementTypeKey.walkingHeartRateAverage, new walking_heart_rate_average_1.WalkingHeartRateAverageService());
43
- this.services.set(types_1.MeasurementTypeKey.heartRateVariability, new heart_rate_variability_1.HeartRateVariabilityService());
44
- this.services.set(types_1.MeasurementTypeKey.heartRate, new heart_rate_1.HeartRateService());
45
- this.services.set(types_1.MeasurementTypeKey.respiratoryRate, new respiratory_rate_1.RespiratoryRateService());
46
- this.services.set(types_1.MeasurementTypeKey.vo2Max, new vo2_max_1.Vo2MaxService());
47
33
  this.services.set(types_1.MeasurementTypeKey.urineAnalysis, new uric_acid_service_1.UricAcidService());
48
34
  this.services.set(types_1.MeasurementTypeKey.uricAcid, new uric_acid_service_1.UricAcidService());
49
35
  this.services.set(types_1.MeasurementTypeKey.lipidPanel, new lipid_panel_1.LipidPanelService());