@ovok/core 0.2.64 → 0.3.1
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 +23 -0
- package/dist/client/auth/methods/getSessions.d.ts +4 -0
- package/dist/client/auth/methods/getSessions.js +7 -0
- package/dist/client/auth/methods/index.d.ts +3 -0
- package/dist/client/auth/methods/index.js +3 -0
- package/dist/client/auth/methods/logout.d.ts +3 -0
- package/dist/client/auth/methods/logout.js +12 -0
- package/dist/client/auth/methods/revokeSessions.d.ts +4 -0
- package/dist/client/auth/methods/revokeSessions.js +10 -0
- package/dist/client/auth/types/Session.d.ts +12 -0
- package/dist/client/auth/types/Session.js +2 -0
- package/dist/client/errors/rate-limit-error.d.ts +11 -0
- package/dist/client/errors/rate-limit-error.js +44 -0
- package/dist/client/observation/methods/index.d.ts +1 -0
- package/dist/client/observation/methods/index.js +1 -0
- package/dist/client/observation/methods/observationsToMeasurements.d.ts +10 -0
- package/dist/client/observation/methods/observationsToMeasurements.js +247 -0
- package/dist/client/observation/methods/saveMeasurement.d.ts +4 -0
- package/dist/client/observation/methods/saveMeasurement.js +6 -0
- package/dist/client/observation/services/creator.js +1 -0
- package/dist/client/observation/services/ecg-observation-fragments.js +3 -1
- package/dist/client/observation/services/uric-acid-service.d.ts +1 -0
- package/dist/client/observation/services/uric-acid-service.js +8 -0
- package/dist/client/observation/types/MeasurementTypeKey.d.ts +2 -0
- package/dist/client/observation/types/MeasurementTypeKey.js +2 -0
- package/dist/client/observation/types/measurement/UrineAnalyzeMeasurement.d.ts +2 -0
- package/dist/client/observation/types/measurement/index.d.ts +6 -0
- package/dist/client/observation/types/measurement/index.js +6 -0
- package/dist/client/offline/index.d.ts +1 -0
- package/dist/client/offline/index.js +1 -0
- package/dist/client/offline/offline-measurement-queue.d.ts +2 -1
- package/dist/client/offline/offline-measurement-queue.js +12 -3
- package/dist/client/offline/shared-measurement-queue.d.ts +14 -0
- package/dist/client/offline/shared-measurement-queue.js +5 -0
- package/dist/client/ovok-client.js +10 -1
- package/dist/conformance/capability-requirements.js +1 -1
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.js +7 -1
- package/dist/hooks/observation-hooks.d.ts +38 -0
- package/dist/hooks/observation-hooks.js +89 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +5 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -67,3 +67,26 @@ await client.flushOfflineMeasurementQueue();
|
|
|
67
67
|
Queued observations are persisted in `storage`, retried with exponential backoff, and sent as
|
|
68
68
|
conditional creates using a stable identifier. A retry after a process death therefore cannot
|
|
69
69
|
create a second copy of the same observation. The queue is opt-in and requires durable storage.
|
|
70
|
+
|
|
71
|
+
## Rate limits
|
|
72
|
+
|
|
73
|
+
Authentication methods reject with `RateLimitError` when the server returns a throttling outcome.
|
|
74
|
+
Use `isRateLimitError(error)` and `retryAfterMs` to show a translated wait message without parsing
|
|
75
|
+
backend text.
|
|
76
|
+
|
|
77
|
+
## Session management
|
|
78
|
+
|
|
79
|
+
`logout()` revokes the current server session and clears the local login even when the server is
|
|
80
|
+
unreachable. Use `getSessions()` to show the account's active sessions and `revokeSessions("other")`
|
|
81
|
+
or a session id to sign out elsewhere. Access-token and refresh-token lifetimes remain controlled
|
|
82
|
+
by the server's `ClientApplication` configuration.
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
await client.logout();
|
|
86
|
+
|
|
87
|
+
const sessions = await client.getSessions();
|
|
88
|
+
await client.revokeSessions("other");
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Pass `onUnauthenticated` in the normal Medplum client options to react when a request can no
|
|
92
|
+
longer refresh its session.
|
|
@@ -22,3 +22,6 @@ __exportStar(require("./requestDeleteUser"), exports);
|
|
|
22
22
|
__exportStar(require("./googleLogin"), exports);
|
|
23
23
|
__exportStar(require("./appleLogin"), exports);
|
|
24
24
|
__exportStar(require("./resetPassword"), exports);
|
|
25
|
+
__exportStar(require("./logout"), exports);
|
|
26
|
+
__exportStar(require("./getSessions"), exports);
|
|
27
|
+
__exportStar(require("./revokeSessions"), exports);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.logout = logout;
|
|
4
|
+
/** Revokes the current server session and always clears the local login. */
|
|
5
|
+
async function logout() {
|
|
6
|
+
try {
|
|
7
|
+
await this.delete("auth/session/current");
|
|
8
|
+
}
|
|
9
|
+
finally {
|
|
10
|
+
this.clearActiveLogin();
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { OvokClient } from "../../ovok-client";
|
|
2
|
+
import { SessionRevokeOption } from "../types/Session";
|
|
3
|
+
/** Revokes the selected server-side session or session group. */
|
|
4
|
+
export declare function revokeSessions(this: OvokClient, option: SessionRevokeOption): Promise<void>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.revokeSessions = revokeSessions;
|
|
4
|
+
/** Revokes the selected server-side session or session group. */
|
|
5
|
+
async function revokeSessions(option) {
|
|
6
|
+
await this.delete(`auth/session/${encodeURIComponent(option)}`);
|
|
7
|
+
if (option === "current") {
|
|
8
|
+
this.clearActiveLogin();
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** A server-side login session for the current account. */
|
|
2
|
+
export interface OvokSession {
|
|
3
|
+
id: string;
|
|
4
|
+
authMethod?: string;
|
|
5
|
+
ip?: string;
|
|
6
|
+
browser?: string;
|
|
7
|
+
os?: string;
|
|
8
|
+
createdAt?: string;
|
|
9
|
+
lastActiveAt?: string;
|
|
10
|
+
}
|
|
11
|
+
/** Session selector accepted by the session revocation endpoint. */
|
|
12
|
+
export type SessionRevokeOption = "current" | "other" | "all" | (string & {});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** A typed error returned when the server asks the client to slow down. */
|
|
2
|
+
export declare class RateLimitError extends Error {
|
|
3
|
+
readonly code: "rate_limit";
|
|
4
|
+
readonly retryAfterMs: number | undefined;
|
|
5
|
+
constructor(retryAfterMs?: number, options?: {
|
|
6
|
+
cause?: unknown;
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
export declare function isRateLimitError(error: unknown): error is RateLimitError;
|
|
10
|
+
/** Converts Medplum's untyped throttling outcome to the Core error contract. */
|
|
11
|
+
export declare function mapRateLimitError(error: unknown): unknown;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RateLimitError = void 0;
|
|
4
|
+
exports.isRateLimitError = isRateLimitError;
|
|
5
|
+
exports.mapRateLimitError = mapRateLimitError;
|
|
6
|
+
const core_1 = require("@medplum/core");
|
|
7
|
+
/** A typed error returned when the server asks the client to slow down. */
|
|
8
|
+
class RateLimitError extends Error {
|
|
9
|
+
constructor(retryAfterMs, options) {
|
|
10
|
+
super("Too many requests");
|
|
11
|
+
this.code = "rate_limit";
|
|
12
|
+
this.name = "RateLimitError";
|
|
13
|
+
this.retryAfterMs = retryAfterMs;
|
|
14
|
+
if (options) {
|
|
15
|
+
Object.defineProperty(this, "cause", {
|
|
16
|
+
configurable: true,
|
|
17
|
+
enumerable: false,
|
|
18
|
+
value: options.cause,
|
|
19
|
+
writable: false,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
exports.RateLimitError = RateLimitError;
|
|
25
|
+
function isRateLimitError(error) {
|
|
26
|
+
return error instanceof RateLimitError;
|
|
27
|
+
}
|
|
28
|
+
/** Converts Medplum's untyped throttling outcome to the Core error contract. */
|
|
29
|
+
function mapRateLimitError(error) {
|
|
30
|
+
if (isRateLimitError(error)) {
|
|
31
|
+
return error;
|
|
32
|
+
}
|
|
33
|
+
if (error instanceof core_1.OperationOutcomeError && isRateLimitOutcome(error.outcome)) {
|
|
34
|
+
return new RateLimitError((0, core_1.getRateLimitReset)(error.outcome), { cause: error });
|
|
35
|
+
}
|
|
36
|
+
return error;
|
|
37
|
+
}
|
|
38
|
+
function isRateLimitOutcome(outcome) {
|
|
39
|
+
if (!(0, core_1.isOperationOutcome)(outcome)) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
return (outcome.id === "too-many-requests" ||
|
|
43
|
+
outcome.issue.some((issue) => { var _a; return ["throttled", "too-many-requests"].includes((_a = issue.code) !== null && _a !== void 0 ? _a : ""); }));
|
|
44
|
+
}
|
|
@@ -16,3 +16,4 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
__exportStar(require("./getLatestObservationsByCodes"), exports);
|
|
18
18
|
__exportStar(require("./generateObservationBodiesByMeasurement"), exports);
|
|
19
|
+
__exportStar(require("./observationsToMeasurements"), exports);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Observation } from "@medplum/fhirtypes";
|
|
2
|
+
import { BloodGlucoseMeasurement, BloodPressureMeasurement, BodyTemperatureMeasurement, BodyWeightMeasurement, EcgMeasurement, HeartRateMeasurement, HeartRateVariabilityMeasurement, Measurement, MeasurementTypeKey, PulseOximeterMeasurement, RespiratoryRateMeasurement, RestingHeartRateMeasurement, StepCountMeasurement, UrineAnalyzeMeasurement, Vo2MaxMeasurement, WalkingHeartRateAverageMeasurement } from "../../../types";
|
|
3
|
+
export type SupportedMeasurement = BloodGlucoseMeasurement | BloodPressureMeasurement | BodyTemperatureMeasurement | BodyWeightMeasurement | EcgMeasurement | HeartRateMeasurement | HeartRateVariabilityMeasurement | PulseOximeterMeasurement | RespiratoryRateMeasurement | RestingHeartRateMeasurement | StepCountMeasurement | UrineAnalyzeMeasurement | Vo2MaxMeasurement | WalkingHeartRateAverageMeasurement | Measurement;
|
|
4
|
+
type SupportedType = Exclude<MeasurementTypeKey, MeasurementTypeKey.symptomQuestionnaire>;
|
|
5
|
+
export declare const observationCodesForTypes: (types: readonly SupportedType[]) => string;
|
|
6
|
+
/** Converts FHIR observations back into SDK measurements without patient/app assumptions. */
|
|
7
|
+
export declare const observationsToMeasurements: <T extends readonly SupportedType[]>(observations: readonly Observation[], types: T) => Array<Extract<SupportedMeasurement, {
|
|
8
|
+
measurementTypeKey: T[number];
|
|
9
|
+
}>>;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/* eslint-disable max-lines */
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.observationsToMeasurements = exports.observationCodesForTypes = void 0;
|
|
5
|
+
const types_1 = require("../../../types");
|
|
6
|
+
const codeOf = (observation) => {
|
|
7
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
8
|
+
return (_d = (_c = (_b = (_a = observation.code) === null || _a === void 0 ? void 0 : _a.coding) === null || _b === void 0 ? void 0 : _b.find((coding) => coding.system === "http://loinc.org")) === null || _c === void 0 ? void 0 : _c.code) !== null && _d !== void 0 ? _d : (_g = (_f = (_e = observation.code) === null || _e === void 0 ? void 0 : _e.coding) === null || _f === void 0 ? void 0 : _f[0]) === null || _g === void 0 ? void 0 : _g.code;
|
|
9
|
+
};
|
|
10
|
+
const valueOf = (observation) => {
|
|
11
|
+
var _a;
|
|
12
|
+
const value = (_a = observation.valueQuantity) === null || _a === void 0 ? void 0 : _a.value;
|
|
13
|
+
return typeof value === "number" ? value : undefined;
|
|
14
|
+
};
|
|
15
|
+
const recordedAtOf = (observation) => {
|
|
16
|
+
var _a, _b;
|
|
17
|
+
const value = (_a = observation.effectiveDateTime) !== null && _a !== void 0 ? _a : (_b = observation.effectivePeriod) === null || _b === void 0 ? void 0 : _b.start;
|
|
18
|
+
if (!value) {
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
const date = new Date(value);
|
|
22
|
+
return Number.isNaN(date.getTime()) ? undefined : date;
|
|
23
|
+
};
|
|
24
|
+
const groupKey = (observation) => {
|
|
25
|
+
var _a, _b, _c;
|
|
26
|
+
const id = (_a = observation.id) !== null && _a !== void 0 ? _a : "";
|
|
27
|
+
const recordingId = ((_b = observation.hasMember) === null || _b === void 0 ? void 0 : _b.length) ? id.replace(/-\d+$/, "") : id;
|
|
28
|
+
return recordingId || ((_c = recordedAtOf(observation)) === null || _c === void 0 ? void 0 : _c.toISOString()) || "unknown";
|
|
29
|
+
};
|
|
30
|
+
const ecgGroupKey = (observation, observations) => {
|
|
31
|
+
const parent = observations.find((candidate) => candidate.id &&
|
|
32
|
+
observation.id &&
|
|
33
|
+
candidate.id !== observation.id &&
|
|
34
|
+
observation.id.startsWith(`${candidate.id}-`));
|
|
35
|
+
if (parent) {
|
|
36
|
+
return groupKey(parent);
|
|
37
|
+
}
|
|
38
|
+
if (codeOf(observation) === types_1.ObservationCode.ECG) {
|
|
39
|
+
return groupKey(observation);
|
|
40
|
+
}
|
|
41
|
+
const recordedAt = recordedAtOf(observation);
|
|
42
|
+
const root = observations.find((candidate) => {
|
|
43
|
+
var _a;
|
|
44
|
+
const start = (_a = candidate.effectivePeriod) === null || _a === void 0 ? void 0 : _a.start;
|
|
45
|
+
return (codeOf(candidate) === types_1.ObservationCode.ECG &&
|
|
46
|
+
start &&
|
|
47
|
+
recordedAt &&
|
|
48
|
+
new Date(start).toISOString().slice(0, 16) ===
|
|
49
|
+
recordedAt.toISOString().slice(0, 16));
|
|
50
|
+
});
|
|
51
|
+
return root ? groupKey(root) : groupKey(observation);
|
|
52
|
+
};
|
|
53
|
+
const flattenMembers = (observations) => {
|
|
54
|
+
const byReference = new Map(observations
|
|
55
|
+
.filter((observation) => observation.id)
|
|
56
|
+
.map((observation) => [`Observation/${observation.id}`, observation]));
|
|
57
|
+
const flattened = [];
|
|
58
|
+
const add = (observation) => {
|
|
59
|
+
var _a;
|
|
60
|
+
flattened.push(observation);
|
|
61
|
+
(_a = observation.hasMember) === null || _a === void 0 ? void 0 : _a.forEach((reference) => {
|
|
62
|
+
const member = reference.reference ? byReference.get(reference.reference) : undefined;
|
|
63
|
+
if (member) {
|
|
64
|
+
add(member);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
observations.filter((observation) => { var _a; return !((_a = observation.hasMember) === null || _a === void 0 ? void 0 : _a.length); }).forEach(add);
|
|
69
|
+
observations.filter((observation) => { var _a; return (_a = observation.hasMember) === null || _a === void 0 ? void 0 : _a.length; }).forEach((observation) => {
|
|
70
|
+
if (!flattened.includes(observation)) {
|
|
71
|
+
add(observation);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
return flattened;
|
|
75
|
+
};
|
|
76
|
+
const firstValue = (observations, code) => observations.find((observation) => codeOf(observation) === code)
|
|
77
|
+
? valueOf(observations.find((observation) => codeOf(observation) === code))
|
|
78
|
+
: undefined;
|
|
79
|
+
const baseMeasurement = (measurementTypeKey, observation) => ({
|
|
80
|
+
measurementTypeKey,
|
|
81
|
+
...(recordedAtOf(observation) ? { recordedAt: recordedAtOf(observation) } : {}),
|
|
82
|
+
});
|
|
83
|
+
const inverseGroup = (type, observations) => {
|
|
84
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
85
|
+
const first = observations[0];
|
|
86
|
+
const measurement = baseMeasurement(type, first);
|
|
87
|
+
const value = (code) => firstValue(observations, code);
|
|
88
|
+
switch (type) {
|
|
89
|
+
case types_1.MeasurementTypeKey.bloodPressure:
|
|
90
|
+
return {
|
|
91
|
+
...measurement,
|
|
92
|
+
measurementTypeKey: type,
|
|
93
|
+
systolic: value(types_1.ObservationCode.SYSTOLIC_BLOOD_PRESSURE),
|
|
94
|
+
diastolic: value(types_1.ObservationCode.DIASTOLIC_BLOOD_PRESSURE),
|
|
95
|
+
meanArterialPressure: value(types_1.ObservationCode.MEAN_BLOOD_PRESSURE),
|
|
96
|
+
heartRate: value(types_1.ObservationCode.BLOOD_PRESSURE_HEART_RATE),
|
|
97
|
+
};
|
|
98
|
+
case types_1.MeasurementTypeKey.bloodGlucose:
|
|
99
|
+
return { ...measurement, measurementTypeKey: type, bloodGlucose: value(types_1.ObservationCode.BLOOD_GLUCOSE) };
|
|
100
|
+
case types_1.MeasurementTypeKey.bodyWeight:
|
|
101
|
+
return { ...measurement, measurementTypeKey: type, bodyWeight: value(types_1.ObservationCode.BODY_WEIGHT) };
|
|
102
|
+
case types_1.MeasurementTypeKey.temperature:
|
|
103
|
+
return { ...measurement, measurementTypeKey: type, bodyTemp: value(types_1.ObservationCode.BODY_TEMPERATURE) };
|
|
104
|
+
case types_1.MeasurementTypeKey.bloodOxygenPulseRate:
|
|
105
|
+
return {
|
|
106
|
+
...measurement,
|
|
107
|
+
measurementTypeKey: type,
|
|
108
|
+
bloodOxygen: value(types_1.ObservationCode.OXYGEN_SATURATION),
|
|
109
|
+
pulseRate: value(types_1.ObservationCode.OXYGEN_SATURATION_HEART_RATE),
|
|
110
|
+
};
|
|
111
|
+
case types_1.MeasurementTypeKey.heartRate:
|
|
112
|
+
return {
|
|
113
|
+
...measurement,
|
|
114
|
+
measurementTypeKey: type,
|
|
115
|
+
heartRate: value(types_1.ObservationCode.HEART_RATE),
|
|
116
|
+
};
|
|
117
|
+
case types_1.MeasurementTypeKey.heartRateVariability:
|
|
118
|
+
return {
|
|
119
|
+
...measurement,
|
|
120
|
+
measurementTypeKey: type,
|
|
121
|
+
heartRateVariability: value(types_1.ObservationCode.HEART_RATE_VARIABILITY),
|
|
122
|
+
};
|
|
123
|
+
case types_1.MeasurementTypeKey.respiratoryRate:
|
|
124
|
+
return {
|
|
125
|
+
...measurement,
|
|
126
|
+
measurementTypeKey: type,
|
|
127
|
+
respiratoryRate: value(types_1.ObservationCode.RESPIRATORY_RATE),
|
|
128
|
+
};
|
|
129
|
+
case types_1.MeasurementTypeKey.restingHeartRate:
|
|
130
|
+
return {
|
|
131
|
+
...measurement,
|
|
132
|
+
measurementTypeKey: type,
|
|
133
|
+
restingHeartRate: value(types_1.ObservationCode.RESTING_HEART_RATE),
|
|
134
|
+
};
|
|
135
|
+
case types_1.MeasurementTypeKey.walkingHeartRateAverage:
|
|
136
|
+
return {
|
|
137
|
+
...measurement,
|
|
138
|
+
measurementTypeKey: type,
|
|
139
|
+
walkingHeartRateAverage: value(types_1.ObservationCode.WALKING_HEART_RATE_AVERAGE),
|
|
140
|
+
};
|
|
141
|
+
case types_1.MeasurementTypeKey.vo2Max:
|
|
142
|
+
return {
|
|
143
|
+
...measurement,
|
|
144
|
+
measurementTypeKey: type,
|
|
145
|
+
vo2Max: value(types_1.ObservationCode.VO2_MAX),
|
|
146
|
+
};
|
|
147
|
+
case types_1.MeasurementTypeKey.stepCount:
|
|
148
|
+
return {
|
|
149
|
+
...measurement,
|
|
150
|
+
measurementTypeKey: type,
|
|
151
|
+
stepCount: value(types_1.ObservationCode.STEPS),
|
|
152
|
+
...(((_a = first.effectivePeriod) === null || _a === void 0 ? void 0 : _a.start) ? { start: new Date(first.effectivePeriod.start) } : {}),
|
|
153
|
+
...(((_b = first.effectivePeriod) === null || _b === void 0 ? void 0 : _b.end) ? { end: new Date(first.effectivePeriod.end) } : {}),
|
|
154
|
+
};
|
|
155
|
+
case types_1.MeasurementTypeKey.ecg: {
|
|
156
|
+
const sampled = observations
|
|
157
|
+
.filter((observation) => codeOf(observation) === types_1.ObservationCode.ECG)
|
|
158
|
+
.sort((left, right) => { var _a, _b, _c, _d; return ((_b = (_a = left.effectivePeriod) === null || _a === void 0 ? void 0 : _a.start) !== null && _b !== void 0 ? _b : "").localeCompare((_d = (_c = right.effectivePeriod) === null || _c === void 0 ? void 0 : _c.start) !== null && _d !== void 0 ? _d : ""); });
|
|
159
|
+
const points = sampled.flatMap((observation) => {
|
|
160
|
+
var _a;
|
|
161
|
+
return typeof ((_a = observation.valueSampledData) === null || _a === void 0 ? void 0 : _a.data) === "string"
|
|
162
|
+
? observation.valueSampledData.data.split(/\s+/).filter(Boolean).map(Number)
|
|
163
|
+
: [];
|
|
164
|
+
});
|
|
165
|
+
const diagnosticResult = (_d = (_c = first.interpretation) === null || _c === void 0 ? void 0 : _c[0]) === null || _d === void 0 ? void 0 : _d.text;
|
|
166
|
+
const duration = (_g = (_f = (_e = first.component) === null || _e === void 0 ? void 0 : _e.find((component) => component.id === "measurement-duration")) === null || _f === void 0 ? void 0 : _f.valueQuantity) === null || _g === void 0 ? void 0 : _g.value;
|
|
167
|
+
return {
|
|
168
|
+
...measurement,
|
|
169
|
+
measurementTypeKey: type,
|
|
170
|
+
diagramPoints: points,
|
|
171
|
+
heartRate: (_h = value(types_1.ObservationCode.ECG_HEART_RATE)) !== null && _h !== void 0 ? _h : value(types_1.ObservationCode.HEART_RATE),
|
|
172
|
+
...(diagnosticResult ? { diagnosticResult } : {}),
|
|
173
|
+
...(typeof duration === "number" ? { duration } : {}),
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
case types_1.MeasurementTypeKey.urineAnalysis:
|
|
177
|
+
case types_1.MeasurementTypeKey.uricAcid: {
|
|
178
|
+
const analyteByCode = Object.fromEntries(types_1.URINE_ANALYTE_DEFINITIONS.map((analyte) => [analyte.code, analyte.key]));
|
|
179
|
+
const result = {
|
|
180
|
+
...measurement,
|
|
181
|
+
measurementTypeKey: type,
|
|
182
|
+
urineValues: [],
|
|
183
|
+
};
|
|
184
|
+
for (const observation of observations) {
|
|
185
|
+
const analyteCode = codeOf(observation);
|
|
186
|
+
const component = (_k = (_j = first.code) === null || _j === void 0 ? void 0 : _j.coding) === null || _k === void 0 ? void 0 : _k.find((coding) => coding.code === analyteCode);
|
|
187
|
+
const value = valueOf(observation);
|
|
188
|
+
const key = analyteByCode[analyteCode !== null && analyteCode !== void 0 ? analyteCode : ""];
|
|
189
|
+
if (key && typeof value === "number") {
|
|
190
|
+
result[key] = value;
|
|
191
|
+
}
|
|
192
|
+
if (component && value !== undefined) {
|
|
193
|
+
result.urineValues.push({ code: observation.code, value: { string: String(value) } });
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return result;
|
|
197
|
+
}
|
|
198
|
+
default:
|
|
199
|
+
return measurement;
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
const codesForType = {
|
|
203
|
+
[types_1.MeasurementTypeKey.bloodPressure]: [types_1.ObservationCode.SYSTOLIC_BLOOD_PRESSURE, types_1.ObservationCode.DIASTOLIC_BLOOD_PRESSURE, types_1.ObservationCode.MEAN_BLOOD_PRESSURE, types_1.ObservationCode.BLOOD_PRESSURE_HEART_RATE],
|
|
204
|
+
[types_1.MeasurementTypeKey.bloodGlucose]: [types_1.ObservationCode.BLOOD_GLUCOSE],
|
|
205
|
+
[types_1.MeasurementTypeKey.bodyWeight]: [types_1.ObservationCode.BODY_WEIGHT],
|
|
206
|
+
[types_1.MeasurementTypeKey.temperature]: [types_1.ObservationCode.BODY_TEMPERATURE],
|
|
207
|
+
[types_1.MeasurementTypeKey.bloodOxygenPulseRate]: [types_1.ObservationCode.OXYGEN_SATURATION, types_1.ObservationCode.OXYGEN_SATURATION_HEART_RATE],
|
|
208
|
+
[types_1.MeasurementTypeKey.stepCount]: [types_1.ObservationCode.STEPS],
|
|
209
|
+
[types_1.MeasurementTypeKey.heartRate]: [types_1.ObservationCode.HEART_RATE],
|
|
210
|
+
[types_1.MeasurementTypeKey.heartRateVariability]: [types_1.ObservationCode.HEART_RATE_VARIABILITY],
|
|
211
|
+
[types_1.MeasurementTypeKey.respiratoryRate]: [types_1.ObservationCode.RESPIRATORY_RATE],
|
|
212
|
+
[types_1.MeasurementTypeKey.restingHeartRate]: [types_1.ObservationCode.RESTING_HEART_RATE],
|
|
213
|
+
[types_1.MeasurementTypeKey.walkingHeartRateAverage]: [types_1.ObservationCode.WALKING_HEART_RATE_AVERAGE],
|
|
214
|
+
[types_1.MeasurementTypeKey.vo2Max]: [types_1.ObservationCode.VO2_MAX],
|
|
215
|
+
[types_1.MeasurementTypeKey.ecg]: [types_1.ObservationCode.ECG, types_1.ObservationCode.ECG_HEART_RATE, types_1.ObservationCode.HEART_RATE],
|
|
216
|
+
[types_1.MeasurementTypeKey.uricAcid]: [
|
|
217
|
+
types_1.ObservationCode.URIC_ACID,
|
|
218
|
+
...types_1.URINE_ANALYTE_DEFINITIONS.map((analyte) => analyte.code),
|
|
219
|
+
],
|
|
220
|
+
[types_1.MeasurementTypeKey.urineAnalysis]: [
|
|
221
|
+
types_1.ObservationCode.URIC_ACID,
|
|
222
|
+
...types_1.URINE_ANALYTE_DEFINITIONS.map((analyte) => analyte.code),
|
|
223
|
+
],
|
|
224
|
+
};
|
|
225
|
+
const observationCodesForTypes = (types) => [...new Set(types.flatMap((type) => { var _a; return (_a = codesForType[type]) !== null && _a !== void 0 ? _a : []; }))].join(",");
|
|
226
|
+
exports.observationCodesForTypes = observationCodesForTypes;
|
|
227
|
+
/** Converts FHIR observations back into SDK measurements without patient/app assumptions. */
|
|
228
|
+
const observationsToMeasurements = (observations, types) => {
|
|
229
|
+
const source = flattenMembers(observations);
|
|
230
|
+
const output = [];
|
|
231
|
+
types.forEach((type) => {
|
|
232
|
+
var _a;
|
|
233
|
+
const codes = (_a = codesForType[type]) !== null && _a !== void 0 ? _a : [];
|
|
234
|
+
const matching = source.filter((observation) => { var _a; return codes.includes((_a = codeOf(observation)) !== null && _a !== void 0 ? _a : ""); });
|
|
235
|
+
const groups = new Map();
|
|
236
|
+
matching.forEach((observation) => {
|
|
237
|
+
var _a, _b, _c;
|
|
238
|
+
const key = type === types_1.MeasurementTypeKey.ecg
|
|
239
|
+
? ecgGroupKey(observation, matching)
|
|
240
|
+
: (_b = (_a = recordedAtOf(observation)) === null || _a === void 0 ? void 0 : _a.toISOString()) !== null && _b !== void 0 ? _b : "unknown";
|
|
241
|
+
groups.set(key, [...((_c = groups.get(key)) !== null && _c !== void 0 ? _c : []), observation]);
|
|
242
|
+
});
|
|
243
|
+
groups.forEach((group) => output.push(inverseGroup(type, group)));
|
|
244
|
+
});
|
|
245
|
+
return output;
|
|
246
|
+
};
|
|
247
|
+
exports.observationsToMeasurements = observationsToMeasurements;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { OvokClient, SaveObservationsResult } from "../../ovok-client";
|
|
2
|
+
import { GenerateObservationBodyParams } from "../types/generate-observation-body/GenerateObservationBodyParams";
|
|
3
|
+
/** Framework-free save helper for apps that do not use React hooks. */
|
|
4
|
+
export declare const saveMeasurement: (client: OvokClient, params: GenerateObservationBodyParams) => Promise<SaveObservationsResult>;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.saveMeasurement = void 0;
|
|
4
|
+
/** Framework-free save helper for apps that do not use React hooks. */
|
|
5
|
+
const saveMeasurement = (client, params) => client.saveMeasurement(params);
|
|
6
|
+
exports.saveMeasurement = saveMeasurement;
|
|
@@ -41,6 +41,7 @@ class ObservationServiceCreator {
|
|
|
41
41
|
this.services.set(types_1.MeasurementTypeKey.heartRate, new heart_rate_1.HeartRateService());
|
|
42
42
|
this.services.set(types_1.MeasurementTypeKey.respiratoryRate, new respiratory_rate_1.RespiratoryRateService());
|
|
43
43
|
this.services.set(types_1.MeasurementTypeKey.vo2Max, new vo2_max_1.Vo2MaxService());
|
|
44
|
+
this.services.set(types_1.MeasurementTypeKey.urineAnalysis, new uric_acid_service_1.UricAcidService());
|
|
44
45
|
this.services.set(types_1.MeasurementTypeKey.uricAcid, new uric_acid_service_1.UricAcidService());
|
|
45
46
|
}
|
|
46
47
|
getService(type) {
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.generateEcgObservationFragments = void 0;
|
|
4
4
|
const MAX_SEGMENT_DURATION_SECONDS = 5 * 60;
|
|
5
|
+
const MAX_SAMPLED_DATA_CHARACTERS = 750000;
|
|
5
6
|
const generateEcgObservationFragments = (measurement, observationCodes) => {
|
|
6
7
|
const fragments = [];
|
|
7
8
|
const diagramPoints = measurement.diagramPoints;
|
|
@@ -14,9 +15,10 @@ const generateEcgObservationFragments = (measurement, observationCodes) => {
|
|
|
14
15
|
? measurement.duration
|
|
15
16
|
: undefined;
|
|
16
17
|
if (diagramPoints === null || diagramPoints === void 0 ? void 0 : diagramPoints.length) {
|
|
17
|
-
const
|
|
18
|
+
const maxSamplesForDuration = duration
|
|
18
19
|
? Math.max(1, Math.floor((MAX_SEGMENT_DURATION_SECONDS * diagramPoints.length) / duration))
|
|
19
20
|
: diagramPoints.length;
|
|
21
|
+
const maxSamples = Math.min(maxSamplesForDuration, Math.max(1, Math.floor(MAX_SAMPLED_DATA_CHARACTERS / 7)));
|
|
20
22
|
const recordingId = `ecg-${recordedAt.getTime()}-${diagramPoints.length}`;
|
|
21
23
|
const ecgCode = observationCodes[0];
|
|
22
24
|
for (let startIndex = 0, segmentIndex = 0; startIndex < diagramPoints.length; startIndex += maxSamples, segmentIndex++) {
|
|
@@ -9,6 +9,7 @@ export declare class UricAcidService extends BaseObservationService<UrineAnalyze
|
|
|
9
9
|
private static readonly THRESHOLDS;
|
|
10
10
|
protected measurementTypeKey: MeasurementTypeKey;
|
|
11
11
|
protected measurementTypeName: string;
|
|
12
|
+
protected getCastedMeasurement(measurement: Measurement): UrineAnalyzeMeasurement;
|
|
12
13
|
protected observationMetadata: ObservationMetadata<UrineAnalyzeMeasurement>[];
|
|
13
14
|
generateValues(measurement: Measurement): ObservationValue[];
|
|
14
15
|
getAdditionalProperties(measurement: Measurement): Partial<Observation>[];
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
/* eslint-disable max-lines */
|
|
2
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
4
|
exports.UricAcidService = void 0;
|
|
4
5
|
const types_1 = require("../../../types");
|
|
@@ -19,6 +20,13 @@ class UricAcidService extends base_observation_service_1.BaseObservationService
|
|
|
19
20
|
},
|
|
20
21
|
];
|
|
21
22
|
}
|
|
23
|
+
getCastedMeasurement(measurement) {
|
|
24
|
+
if (measurement.measurementTypeKey !== types_1.MeasurementTypeKey.urineAnalysis &&
|
|
25
|
+
measurement.measurementTypeKey !== types_1.MeasurementTypeKey.uricAcid) {
|
|
26
|
+
throw new Error("Measurement is not a uric acid measurement");
|
|
27
|
+
}
|
|
28
|
+
return measurement;
|
|
29
|
+
}
|
|
22
30
|
generateValues(measurement) {
|
|
23
31
|
const castedMeasurement = this.getCastedMeasurement(measurement);
|
|
24
32
|
const values = [];
|
|
@@ -7,6 +7,8 @@ export declare enum MeasurementTypeKey {
|
|
|
7
7
|
bodyWeight = "body-weight",
|
|
8
8
|
temperature = "body-temperature",
|
|
9
9
|
cosinussMultiParam = "cosinuss-multi-param",
|
|
10
|
+
urineAnalysis = "urine-analysis",
|
|
11
|
+
/** @deprecated Use urineAnalysis. */
|
|
10
12
|
uricAcid = "uric-acid",
|
|
11
13
|
heartRate = "heart-rate",
|
|
12
14
|
heartRateVariability = "heart-rate-variability",
|
|
@@ -11,6 +11,8 @@ var MeasurementTypeKey;
|
|
|
11
11
|
MeasurementTypeKey["bodyWeight"] = "body-weight";
|
|
12
12
|
MeasurementTypeKey["temperature"] = "body-temperature";
|
|
13
13
|
MeasurementTypeKey["cosinussMultiParam"] = "cosinuss-multi-param";
|
|
14
|
+
MeasurementTypeKey["urineAnalysis"] = "urine-analysis";
|
|
15
|
+
/** @deprecated Use urineAnalysis. */
|
|
14
16
|
MeasurementTypeKey["uricAcid"] = "uric-acid";
|
|
15
17
|
MeasurementTypeKey["heartRate"] = "heart-rate";
|
|
16
18
|
MeasurementTypeKey["heartRateVariability"] = "heart-rate-variability";
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { CodeableConcept } from "@medplum/fhirtypes";
|
|
2
|
+
import { MeasurementTypeKey } from "../MeasurementTypeKey";
|
|
2
3
|
import { Measurement } from "./Measurement";
|
|
3
4
|
export type UrineAnalyzeMeasurement = Measurement & {
|
|
5
|
+
measurementTypeKey: MeasurementTypeKey.urineAnalysis | MeasurementTypeKey.uricAcid;
|
|
4
6
|
uro?: number;
|
|
5
7
|
bil?: number;
|
|
6
8
|
ket?: number;
|
|
@@ -9,3 +9,9 @@ export * from "./UrineAnalyzeMeasurement";
|
|
|
9
9
|
export * from "./EcgMeasurement";
|
|
10
10
|
export * from "./SpirometryMeasurement";
|
|
11
11
|
export * from "./StepCountMeasurement";
|
|
12
|
+
export * from "./HeartRateMeasurement";
|
|
13
|
+
export * from "./HeartRateVariabilityMeasurement";
|
|
14
|
+
export * from "./RespiratoryRateMeasurement";
|
|
15
|
+
export * from "./RestingHeartRateMeasurement";
|
|
16
|
+
export * from "./Vo2MaxMeasurement";
|
|
17
|
+
export * from "./WalkingHeartRateAverageMeasurement";
|
|
@@ -25,3 +25,9 @@ __exportStar(require("./UrineAnalyzeMeasurement"), exports);
|
|
|
25
25
|
__exportStar(require("./EcgMeasurement"), exports);
|
|
26
26
|
__exportStar(require("./SpirometryMeasurement"), exports);
|
|
27
27
|
__exportStar(require("./StepCountMeasurement"), exports);
|
|
28
|
+
__exportStar(require("./HeartRateMeasurement"), exports);
|
|
29
|
+
__exportStar(require("./HeartRateVariabilityMeasurement"), exports);
|
|
30
|
+
__exportStar(require("./RespiratoryRateMeasurement"), exports);
|
|
31
|
+
__exportStar(require("./RestingHeartRateMeasurement"), exports);
|
|
32
|
+
__exportStar(require("./Vo2MaxMeasurement"), exports);
|
|
33
|
+
__exportStar(require("./WalkingHeartRateAverageMeasurement"), exports);
|
|
@@ -15,3 +15,4 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
__exportStar(require("./offline-measurement-queue"), exports);
|
|
18
|
+
__exportStar(require("./shared-measurement-queue"), exports);
|
|
@@ -21,6 +21,7 @@ export type OfflineMeasurementFlushResult = {
|
|
|
21
21
|
responses: Record<string, Bundle>;
|
|
22
22
|
};
|
|
23
23
|
type MeasurementTransport = (bundle: Bundle) => Promise<Bundle>;
|
|
24
|
+
declare const stableMeasurementId: (observation: Observation) => string;
|
|
24
25
|
declare const entryForObservation: (observation: Observation) => BundleEntry<Observation>;
|
|
25
26
|
export declare class OfflineMeasurementQueue {
|
|
26
27
|
private readonly storage;
|
|
@@ -41,4 +42,4 @@ export declare class OfflineMeasurementQueue {
|
|
|
41
42
|
private writeQueue;
|
|
42
43
|
private flushQueue;
|
|
43
44
|
}
|
|
44
|
-
export { DEFAULT_STORAGE_KEY as DEFAULT_OFFLINE_MEASUREMENT_QUEUE_KEY, IDENTIFIER_SYSTEM as OFFLINE_MEASUREMENT_IDENTIFIER_SYSTEM, entryForObservation, };
|
|
45
|
+
export { DEFAULT_STORAGE_KEY as DEFAULT_OFFLINE_MEASUREMENT_QUEUE_KEY, IDENTIFIER_SYSTEM as OFFLINE_MEASUREMENT_IDENTIFIER_SYSTEM, entryForObservation, stableMeasurementId, };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.entryForObservation = exports.OFFLINE_MEASUREMENT_IDENTIFIER_SYSTEM = exports.DEFAULT_OFFLINE_MEASUREMENT_QUEUE_KEY = exports.OfflineMeasurementQueue = void 0;
|
|
3
|
+
exports.stableMeasurementId = exports.entryForObservation = exports.OFFLINE_MEASUREMENT_IDENTIFIER_SYSTEM = exports.DEFAULT_OFFLINE_MEASUREMENT_QUEUE_KEY = exports.OfflineMeasurementQueue = void 0;
|
|
4
4
|
const DEFAULT_STORAGE_KEY = "@ovok/core/offline-measurements";
|
|
5
5
|
exports.DEFAULT_OFFLINE_MEASUREMENT_QUEUE_KEY = DEFAULT_STORAGE_KEY;
|
|
6
6
|
const DEFAULT_MAX_RETRIES = 8;
|
|
@@ -36,7 +36,16 @@ const hash = (value) => {
|
|
|
36
36
|
.toString(16)
|
|
37
37
|
.padStart(8, "0")}`;
|
|
38
38
|
};
|
|
39
|
-
|
|
39
|
+
/** RFC 4122-shaped deterministic UUID for canonical queue content. */
|
|
40
|
+
const stableValueId = (input) => {
|
|
41
|
+
const value = JSON.stringify(canonicalize(input));
|
|
42
|
+
const hex = `${hash(value)}${hash(`${value}\u0000`)}`.padEnd(32, "0").slice(0, 32).split("");
|
|
43
|
+
hex[12] = "5";
|
|
44
|
+
hex[16] = ((Number.parseInt(hex[16], 16) & 0x3) | 0x8).toString(16);
|
|
45
|
+
return `${hex.slice(0, 8).join("")}-${hex.slice(8, 12).join("")}-${hex.slice(12, 16).join("")}-${hex.slice(16, 20).join("")}-${hex.slice(20).join("")}`;
|
|
46
|
+
};
|
|
47
|
+
const stableMeasurementId = (observation) => stableValueId(observation);
|
|
48
|
+
exports.stableMeasurementId = stableMeasurementId;
|
|
40
49
|
const entryForObservation = (observation) => {
|
|
41
50
|
var _a;
|
|
42
51
|
const id = stableMeasurementId(observation);
|
|
@@ -77,7 +86,7 @@ class OfflineMeasurementQueue {
|
|
|
77
86
|
await ((_b = (_a = this.storage).getInitPromise) === null || _b === void 0 ? void 0 : _b.call(_a));
|
|
78
87
|
const queue = this.readQueue();
|
|
79
88
|
const entries = observations.map(entryForObservation);
|
|
80
|
-
const id =
|
|
89
|
+
const id = stableValueId(entries);
|
|
81
90
|
if (!queue.some((item) => item.id === id)) {
|
|
82
91
|
queue.push({
|
|
83
92
|
id,
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Observation } from "@medplum/fhirtypes";
|
|
2
|
+
/** Storage contract shared by native/background and React integrations. */
|
|
3
|
+
export declare const OVOK_SHARED_MEASUREMENT_QUEUE_KEY = "@ovok/shared/measurements";
|
|
4
|
+
export type SharedMeasurementQueueEntry = {
|
|
5
|
+
id: string;
|
|
6
|
+
kind: "observation" | "bluetooth-result";
|
|
7
|
+
queuedAt: string;
|
|
8
|
+
attempt: number;
|
|
9
|
+
payload: Observation | Record<string, unknown>;
|
|
10
|
+
};
|
|
11
|
+
export interface SharedMeasurementQueueStorage {
|
|
12
|
+
getItem: (key: string) => Promise<string | null>;
|
|
13
|
+
setItem: (key: string, value: string) => Promise<void>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OVOK_SHARED_MEASUREMENT_QUEUE_KEY = void 0;
|
|
4
|
+
/** Storage contract shared by native/background and React integrations. */
|
|
5
|
+
exports.OVOK_SHARED_MEASUREMENT_QUEUE_KEY = "@ovok/shared/measurements";
|
|
@@ -40,6 +40,7 @@ const aiFhirMethods = __importStar(require("./ai-fhir/methods"));
|
|
|
40
40
|
const authMethods = __importStar(require("./auth/methods"));
|
|
41
41
|
const botMethods = __importStar(require("./bot/methods"));
|
|
42
42
|
const aiMethods = __importStar(require("./chat/ai/methods"));
|
|
43
|
+
const rate_limit_error_1 = require("./errors/rate-limit-error");
|
|
43
44
|
const observationMethods = __importStar(require("./observation/methods"));
|
|
44
45
|
const offline_1 = require("./offline");
|
|
45
46
|
const questionnaireResponseMethods = __importStar(require("./questionnaire-response/methods"));
|
|
@@ -124,7 +125,15 @@ class OvokClient extends core_1.MedplumClient {
|
|
|
124
125
|
}
|
|
125
126
|
bindMethods(methods) {
|
|
126
127
|
for (const [name, method] of Object.entries(methods)) {
|
|
127
|
-
|
|
128
|
+
const boundMethod = method.bind(this);
|
|
129
|
+
this[name] = async (...args) => {
|
|
130
|
+
try {
|
|
131
|
+
return await boundMethod(...args);
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
throw (0, rate_limit_error_1.mapRateLimitError)(error);
|
|
135
|
+
}
|
|
136
|
+
};
|
|
128
137
|
}
|
|
129
138
|
}
|
|
130
139
|
/**
|
|
@@ -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.
|
|
51
|
+
version: "0.3.0",
|
|
52
52
|
},
|
|
53
53
|
fhirVersion: "4.0.1",
|
|
54
54
|
format: ["json"],
|
package/dist/hooks/index.d.ts
CHANGED
|
@@ -5,3 +5,4 @@ export declare const useClient: () => OvokClient;
|
|
|
5
5
|
export declare const OvokProvider: (props: React.PropsWithChildren<{
|
|
6
6
|
client: OvokClient;
|
|
7
7
|
}>) => React.JSX.Element;
|
|
8
|
+
export { observationsToMeasurements, useEcgRecording, useObservations, useSaveMeasurement, useUrineTests, } from "./observation-hooks";
|
package/dist/hooks/index.js
CHANGED
|
@@ -34,7 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
};
|
|
35
35
|
})();
|
|
36
36
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
-
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;
|
|
37
|
+
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
38
|
/*
|
|
39
39
|
* The only React-dependent module in the package.
|
|
40
40
|
*
|
|
@@ -70,3 +70,9 @@ const OvokProvider = (props) => {
|
|
|
70
70
|
return (React.createElement(react_hooks_1.MedplumProvider, { medplum: props.client }, props.children));
|
|
71
71
|
};
|
|
72
72
|
exports.OvokProvider = OvokProvider;
|
|
73
|
+
var observation_hooks_1 = require("./observation-hooks");
|
|
74
|
+
Object.defineProperty(exports, "observationsToMeasurements", { enumerable: true, get: function () { return observation_hooks_1.observationsToMeasurements; } });
|
|
75
|
+
Object.defineProperty(exports, "useEcgRecording", { enumerable: true, get: function () { return observation_hooks_1.useEcgRecording; } });
|
|
76
|
+
Object.defineProperty(exports, "useObservations", { enumerable: true, get: function () { return observation_hooks_1.useObservations; } });
|
|
77
|
+
Object.defineProperty(exports, "useSaveMeasurement", { enumerable: true, get: function () { return observation_hooks_1.useSaveMeasurement; } });
|
|
78
|
+
Object.defineProperty(exports, "useUrineTests", { enumerable: true, get: function () { return observation_hooks_1.useUrineTests; } });
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Observation } from "@medplum/fhirtypes";
|
|
2
|
+
import { observationsToMeasurements, SupportedMeasurement } from "../client/observation/methods/observationsToMeasurements";
|
|
3
|
+
import { GenerateObservationBodyParams } from "../client/observation/types/generate-observation-body/GenerateObservationBodyParams";
|
|
4
|
+
import { OvokClient } from "../client/ovok-client";
|
|
5
|
+
import { MeasurementTypeKey } from "../types";
|
|
6
|
+
type SupportedType = Exclude<MeasurementTypeKey, MeasurementTypeKey.symptomQuestionnaire>;
|
|
7
|
+
export interface ObservationPeriod {
|
|
8
|
+
start?: Date;
|
|
9
|
+
end?: Date;
|
|
10
|
+
}
|
|
11
|
+
export interface UseObservationsOptions<T extends readonly SupportedType[]> {
|
|
12
|
+
types: T;
|
|
13
|
+
period?: ObservationPeriod;
|
|
14
|
+
patientId?: string;
|
|
15
|
+
}
|
|
16
|
+
/** Plain inverse mapping for apps that already fetched their Observation resources. */
|
|
17
|
+
export { observationsToMeasurements };
|
|
18
|
+
export declare const useObservations: <T extends readonly SupportedType[]>({ types, period, patientId, }: UseObservationsOptions<T>) => {
|
|
19
|
+
measurements: Array<Extract<SupportedMeasurement, {
|
|
20
|
+
measurementTypeKey: T[number];
|
|
21
|
+
}>>;
|
|
22
|
+
loading: boolean;
|
|
23
|
+
error?: Error;
|
|
24
|
+
observations: readonly Observation[];
|
|
25
|
+
};
|
|
26
|
+
export declare const useSaveMeasurement: () => ((params: GenerateObservationBodyParams) => Promise<Awaited<ReturnType<OvokClient["saveMeasurement"]>>>);
|
|
27
|
+
export declare const useEcgRecording: (id?: string) => {
|
|
28
|
+
recording: import("../types").EcgMeasurement;
|
|
29
|
+
measurements: readonly Observation[];
|
|
30
|
+
loading: boolean;
|
|
31
|
+
error?: Error;
|
|
32
|
+
};
|
|
33
|
+
export declare const useUrineTests: () => {
|
|
34
|
+
measurements: never[];
|
|
35
|
+
loading: boolean;
|
|
36
|
+
error?: Error;
|
|
37
|
+
observations: readonly Observation[];
|
|
38
|
+
};
|
|
@@ -0,0 +1,89 @@
|
|
|
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.useUrineTests = exports.useEcgRecording = exports.useSaveMeasurement = exports.useObservations = exports.observationsToMeasurements = void 0;
|
|
38
|
+
const react_hooks_1 = require("@medplum/react-hooks");
|
|
39
|
+
const React = __importStar(require("react"));
|
|
40
|
+
const observationsToMeasurements_1 = require("../client/observation/methods/observationsToMeasurements");
|
|
41
|
+
Object.defineProperty(exports, "observationsToMeasurements", { enumerable: true, get: function () { return observationsToMeasurements_1.observationsToMeasurements; } });
|
|
42
|
+
const types_1 = require("../types");
|
|
43
|
+
const index_1 = require("./index");
|
|
44
|
+
const useObservations = ({ types, period, patientId, }) => {
|
|
45
|
+
var _a, _b, _c;
|
|
46
|
+
// A broad search is intentional: one measurement can expand into several
|
|
47
|
+
// observations, and the inverse mapper performs the type-specific grouping.
|
|
48
|
+
const codes = React.useMemo(() => (0, observationsToMeasurements_1.observationCodesForTypes)(types), [types]);
|
|
49
|
+
const query = React.useMemo(() => ({
|
|
50
|
+
...(codes ? { code: codes } : {}),
|
|
51
|
+
...(patientId ? { subject: `Patient/${patientId}` } : {}),
|
|
52
|
+
...((period === null || period === void 0 ? void 0 : period.start) || (period === null || period === void 0 ? void 0 : period.end)
|
|
53
|
+
? {
|
|
54
|
+
date: [
|
|
55
|
+
...(period.start ? [`ge${period.start.toISOString()}`] : []),
|
|
56
|
+
...(period.end ? [`le${period.end.toISOString()}`] : []),
|
|
57
|
+
],
|
|
58
|
+
}
|
|
59
|
+
: {}),
|
|
60
|
+
}), [codes, patientId, period === null || period === void 0 ? void 0 : period.end, period === null || period === void 0 ? void 0 : period.start]);
|
|
61
|
+
const [resources, loading, outcome] = (0, react_hooks_1.useSearchResources)("Observation", query);
|
|
62
|
+
const observations = resources !== null && resources !== void 0 ? resources : [];
|
|
63
|
+
const measurements = React.useMemo(() => (0, observationsToMeasurements_1.observationsToMeasurements)(observations, types), [observations, types]);
|
|
64
|
+
return {
|
|
65
|
+
measurements,
|
|
66
|
+
loading,
|
|
67
|
+
error: outcome ? new Error((_c = (_b = (_a = outcome.issue) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.diagnostics) !== null && _c !== void 0 ? _c : "Observation search failed") : undefined,
|
|
68
|
+
observations,
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
exports.useObservations = useObservations;
|
|
72
|
+
const useSaveMeasurement = () => {
|
|
73
|
+
const client = (0, index_1.useClient)();
|
|
74
|
+
return React.useCallback((params) => client.saveMeasurement(params), [client]);
|
|
75
|
+
};
|
|
76
|
+
exports.useSaveMeasurement = useSaveMeasurement;
|
|
77
|
+
const useEcgRecording = (id) => {
|
|
78
|
+
const { observations, ...state } = (0, exports.useObservations)({
|
|
79
|
+
types: [types_1.MeasurementTypeKey.ecg],
|
|
80
|
+
});
|
|
81
|
+
const recordingObservations = id
|
|
82
|
+
? observations.filter((observation) => { var _a; return observation.id === id || ((_a = observation.id) === null || _a === void 0 ? void 0 : _a.startsWith(`${id}-`)); })
|
|
83
|
+
: observations;
|
|
84
|
+
const recording = (0, observationsToMeasurements_1.observationsToMeasurements)(recordingObservations, [types_1.MeasurementTypeKey.ecg])[0];
|
|
85
|
+
return { ...state, recording, measurements: observations };
|
|
86
|
+
};
|
|
87
|
+
exports.useEcgRecording = useEcgRecording;
|
|
88
|
+
const useUrineTests = () => (0, exports.useObservations)({ types: [types_1.MeasurementTypeKey.urineAnalysis] });
|
|
89
|
+
exports.useUrineTests = useUrineTests;
|
package/dist/index.d.ts
CHANGED
|
@@ -923,9 +923,12 @@ declare const Medplum: {
|
|
|
923
923
|
};
|
|
924
924
|
export { Medplum };
|
|
925
925
|
export { OvokClient } from "./client/ovok-client";
|
|
926
|
+
export * from "./client/errors/rate-limit-error";
|
|
926
927
|
export type { SaveObservationsResult } from "./client/ovok-client";
|
|
927
928
|
export * from "./client/offline";
|
|
928
929
|
export * from "./hooks";
|
|
929
930
|
export * from "./utils";
|
|
930
931
|
export * from "./client/observation/services";
|
|
932
|
+
export * from "./client/observation/methods";
|
|
933
|
+
export { saveMeasurement } from "./client/observation/methods/saveMeasurement";
|
|
931
934
|
export * from "./conformance/capability-requirements";
|
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.OvokClient = exports.Medplum = void 0;
|
|
39
|
+
exports.saveMeasurement = 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
|
|
@@ -44,8 +44,12 @@ const { MedplumClient, ...Medplum } = MedplumCore;
|
|
|
44
44
|
exports.Medplum = Medplum;
|
|
45
45
|
var ovok_client_1 = require("./client/ovok-client");
|
|
46
46
|
Object.defineProperty(exports, "OvokClient", { enumerable: true, get: function () { return ovok_client_1.OvokClient; } });
|
|
47
|
+
__exportStar(require("./client/errors/rate-limit-error"), exports);
|
|
47
48
|
__exportStar(require("./client/offline"), exports);
|
|
48
49
|
__exportStar(require("./hooks"), exports);
|
|
49
50
|
__exportStar(require("./utils"), exports);
|
|
50
51
|
__exportStar(require("./client/observation/services"), exports);
|
|
52
|
+
__exportStar(require("./client/observation/methods"), exports);
|
|
53
|
+
var saveMeasurement_1 = require("./client/observation/methods/saveMeasurement");
|
|
54
|
+
Object.defineProperty(exports, "saveMeasurement", { enumerable: true, get: function () { return saveMeasurement_1.saveMeasurement; } });
|
|
51
55
|
__exportStar(require("./conformance/capability-requirements"), exports);
|
package/dist/types/index.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export * from "../client/auth/types/RegisterBody";
|
|
|
4
4
|
export * from "../client/auth/types/ChangePasswordBody";
|
|
5
5
|
export * from "../client/auth/types/RequestDeleteUserBody";
|
|
6
6
|
export * from "../client/auth/types/ResetPasswordBody";
|
|
7
|
+
export * from "../client/auth/types/Session";
|
|
7
8
|
export * from "../client/observation/types/MeasurementTypeKey";
|
|
8
9
|
export * from "../client/observation/types/ObservationCode";
|
|
9
10
|
export * from "../client/observation/types/UrineAnalytes";
|
package/dist/types/index.js
CHANGED
|
@@ -21,6 +21,7 @@ __exportStar(require("../client/auth/types/RegisterBody"), exports);
|
|
|
21
21
|
__exportStar(require("../client/auth/types/ChangePasswordBody"), exports);
|
|
22
22
|
__exportStar(require("../client/auth/types/RequestDeleteUserBody"), exports);
|
|
23
23
|
__exportStar(require("../client/auth/types/ResetPasswordBody"), exports);
|
|
24
|
+
__exportStar(require("../client/auth/types/Session"), exports);
|
|
24
25
|
// Measurement types
|
|
25
26
|
__exportStar(require("../client/observation/types/MeasurementTypeKey"), exports);
|
|
26
27
|
__exportStar(require("../client/observation/types/ObservationCode"), exports);
|