@ovok/core 0.3.2 → 0.3.4
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 +14 -0
- package/dist/client/auth/methods/index.d.ts +1 -0
- package/dist/client/auth/methods/index.js +1 -0
- package/dist/client/auth/methods/login.js +20 -9
- package/dist/client/auth/methods/setPasswordFromReset.d.ts +4 -0
- package/dist/client/auth/methods/setPasswordFromReset.js +6 -0
- package/dist/client/auth/types/CompletePasswordResetBody.d.ts +5 -0
- package/dist/client/auth/types/CompletePasswordResetBody.js +2 -0
- package/dist/client/auth/types/Session.d.ts +2 -5
- package/dist/client/errors/rate-limit-error.js +25 -1
- package/dist/client/observation/methods/generateObservationBodiesByMeasurement.js +9 -2
- package/dist/client/observation/observation-bundle-utils.d.ts +6 -0
- package/dist/client/observation/observation-bundle-utils.js +90 -0
- package/dist/client/observation/types/generate-observation-body/GenerateObservationBodyParams.d.ts +1 -0
- package/dist/client/observation/types/measurement/MeasurementDevice.d.ts +2 -0
- package/dist/client/observation/utils/getDeviceObject.js +10 -0
- package/dist/client/ovok-client.d.ts +3 -1
- package/dist/client/ovok-client.js +13 -14
- package/dist/conformance/capability-requirements.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +4 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -103,3 +103,17 @@ if ("nextStep" in result && result.nextStep === "mfa") {
|
|
|
103
103
|
const authenticated = await result.verify(oneTimeCode);
|
|
104
104
|
}
|
|
105
105
|
```
|
|
106
|
+
|
|
107
|
+
## Large measurements
|
|
108
|
+
|
|
109
|
+
`saveMeasurement` and `saveObservations` use conditional creates for idempotent saves. Medplum
|
|
110
|
+
limits a serializable transaction with conditional operations to eight entries, so the SDK keeps
|
|
111
|
+
the first observation in a transaction and sends additional observations in a FHIR batch request.
|
|
112
|
+
This supports measurements such as urine strips and long ECG recordings without changing retry
|
|
113
|
+
behavior. The first observation should remain first when supplying observations manually.
|
|
114
|
+
|
|
115
|
+
The offline queue applies the same split when it flushes a measurement. A partial retry is safe:
|
|
116
|
+
the first conditional create finds the existing observation and the batch only creates missing
|
|
117
|
+
members. Applications building their own bundles can use the exported
|
|
118
|
+
`MAX_CONDITIONAL_OBSERVATION_ENTRIES` limit. See [FHIR batch requests](https://www.medplum.com/docs/fhir-datastore/fhir-batch-requests)
|
|
119
|
+
for the transaction and batch semantics.
|
|
@@ -6,6 +6,7 @@ export * from "./requestDeleteUser";
|
|
|
6
6
|
export * from "./googleLogin";
|
|
7
7
|
export * from "./appleLogin";
|
|
8
8
|
export * from "./resetPassword";
|
|
9
|
+
export * from "./setPasswordFromReset";
|
|
9
10
|
export * from "./logout";
|
|
10
11
|
export * from "./getSessions";
|
|
11
12
|
export * from "./revokeSessions";
|
|
@@ -22,6 +22,7 @@ __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("./setPasswordFromReset"), exports);
|
|
25
26
|
__exportStar(require("./logout"), exports);
|
|
26
27
|
__exportStar(require("./getSessions"), exports);
|
|
27
28
|
__exportStar(require("./revokeSessions"), exports);
|
|
@@ -12,18 +12,21 @@ async function login(body) {
|
|
|
12
12
|
body,
|
|
13
13
|
codeChallenge,
|
|
14
14
|
});
|
|
15
|
-
const tenantCode = (0, handle_tenant_code_1.handleTenantCode)({
|
|
16
|
-
profiles: startResponse.profiles,
|
|
17
|
-
tenantCode: body.tenantCode,
|
|
18
|
-
type: body.type,
|
|
19
|
-
});
|
|
20
|
-
if (!tenantCode) {
|
|
21
|
-
throw new Error("No tenant code found");
|
|
22
|
-
}
|
|
23
15
|
if (startResponse.nextStep === "mfa") {
|
|
24
16
|
return {
|
|
25
17
|
nextStep: "mfa",
|
|
26
18
|
verify: async (code) => {
|
|
19
|
+
const tenantCode = body.type === "Practitioner" && !startResponse.profiles
|
|
20
|
+
? body.tenantCode
|
|
21
|
+
: (0, handle_tenant_code_1.handleTenantCode)({
|
|
22
|
+
profiles: startResponse.profiles,
|
|
23
|
+
tenantCode: body.tenantCode,
|
|
24
|
+
type: body.type,
|
|
25
|
+
});
|
|
26
|
+
const practitionerTenantCode = tenantCode;
|
|
27
|
+
if (body.type === "Practitioner" && !practitionerTenantCode) {
|
|
28
|
+
throw new Error("No tenant code found");
|
|
29
|
+
}
|
|
27
30
|
const mfaResponse = await this.post(`auth/tenant/${body.type}/login/mfa`, {
|
|
28
31
|
loginId: startResponse.loginId,
|
|
29
32
|
mfaToken: code,
|
|
@@ -34,7 +37,7 @@ async function login(body) {
|
|
|
34
37
|
type: "Practitioner",
|
|
35
38
|
sessionCode: mfaResponse.sessionCode,
|
|
36
39
|
codeVerifier,
|
|
37
|
-
tenantCode,
|
|
40
|
+
tenantCode: practitionerTenantCode,
|
|
38
41
|
});
|
|
39
42
|
}
|
|
40
43
|
return (0, exchange_code_1.exchangeCode)({
|
|
@@ -46,6 +49,14 @@ async function login(body) {
|
|
|
46
49
|
},
|
|
47
50
|
};
|
|
48
51
|
}
|
|
52
|
+
const tenantCode = (0, handle_tenant_code_1.handleTenantCode)({
|
|
53
|
+
profiles: startResponse.profiles,
|
|
54
|
+
tenantCode: body.tenantCode,
|
|
55
|
+
type: body.type,
|
|
56
|
+
});
|
|
57
|
+
if (!tenantCode) {
|
|
58
|
+
throw new Error("No tenant code found");
|
|
59
|
+
}
|
|
49
60
|
const exchangeCodeProps = body.type === "Practitioner"
|
|
50
61
|
? {
|
|
51
62
|
client: this,
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { OperationOutcome } from "@medplum/fhirtypes";
|
|
2
|
+
import { OvokClient } from "../../ovok-client";
|
|
3
|
+
import { CompletePasswordResetBody } from "../types/CompletePasswordResetBody";
|
|
4
|
+
export declare function setPasswordFromReset(this: OvokClient, body: CompletePasswordResetBody): Promise<OperationOutcome>;
|
|
@@ -2,11 +2,8 @@
|
|
|
2
2
|
export interface OvokSession {
|
|
3
3
|
id: string;
|
|
4
4
|
authMethod?: string;
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
os?: string;
|
|
8
|
-
createdAt?: string;
|
|
9
|
-
lastActiveAt?: string;
|
|
5
|
+
remoteAddress?: string;
|
|
6
|
+
lastUpdated?: string;
|
|
10
7
|
}
|
|
11
8
|
/** Session selector accepted by the session revocation endpoint. */
|
|
12
9
|
export type SessionRevokeOption = "current" | "other" | "all" | (string & {});
|
|
@@ -4,6 +4,7 @@ exports.RateLimitError = void 0;
|
|
|
4
4
|
exports.isRateLimitError = isRateLimitError;
|
|
5
5
|
exports.mapRateLimitError = mapRateLimitError;
|
|
6
6
|
const core_1 = require("@medplum/core");
|
|
7
|
+
const RATE_LIMIT_RESET_EXTENSION_URL = "https://medplum.com/fhir/StructureDefinition/rate-limit-reset";
|
|
7
8
|
/** A typed error returned when the server asks the client to slow down. */
|
|
8
9
|
class RateLimitError extends Error {
|
|
9
10
|
constructor(retryAfterMs, options) {
|
|
@@ -31,10 +32,33 @@ function mapRateLimitError(error) {
|
|
|
31
32
|
return error;
|
|
32
33
|
}
|
|
33
34
|
if (error instanceof core_1.OperationOutcomeError && isRateLimitOutcome(error.outcome)) {
|
|
34
|
-
return new RateLimitError((
|
|
35
|
+
return new RateLimitError(rateLimitResetFromOutcome(error.outcome), { cause: error });
|
|
35
36
|
}
|
|
36
37
|
return error;
|
|
37
38
|
}
|
|
39
|
+
function rateLimitResetFromOutcome(outcome) {
|
|
40
|
+
var _a, _b, _c;
|
|
41
|
+
if (!outcome || typeof outcome !== "object") {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
const record = outcome;
|
|
45
|
+
const extension = (_a = record.extension) === null || _a === void 0 ? void 0 : _a.find((candidate) => candidate.url === RATE_LIMIT_RESET_EXTENSION_URL);
|
|
46
|
+
if (typeof (extension === null || extension === void 0 ? void 0 : extension.valueUnsignedInt) === "number") {
|
|
47
|
+
return extension.valueUnsignedInt * 1000;
|
|
48
|
+
}
|
|
49
|
+
for (const issue of (_b = record.issue) !== null && _b !== void 0 ? _b : []) {
|
|
50
|
+
try {
|
|
51
|
+
const diagnostics = JSON.parse((_c = issue.diagnostics) !== null && _c !== void 0 ? _c : "");
|
|
52
|
+
if (typeof diagnostics.msBeforeNext === "number") {
|
|
53
|
+
return diagnostics.msBeforeNext;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch (_d) {
|
|
57
|
+
// Diagnostics are optional and may be ordinary human-readable text.
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
38
62
|
function isRateLimitOutcome(outcome) {
|
|
39
63
|
if (!(0, core_1.isOperationOutcome)(outcome)) {
|
|
40
64
|
return false;
|
|
@@ -19,6 +19,12 @@ async function generateObservationBodiesByMeasurement(params) {
|
|
|
19
19
|
const observationService = creator_1.ObservationServiceCreator.getInstance().getService(measurementTypeKey);
|
|
20
20
|
const deviceObject = (0, getDeviceObject_1.getDeviceObject)(device);
|
|
21
21
|
const observationFragments = (_a = observationService.generateObservationFragments) === null || _a === void 0 ? void 0 : _a.call(observationService, measurement);
|
|
22
|
+
const applyEffectiveDateTime = (observation) => params.effectiveDateTime && !observation.effectivePeriod
|
|
23
|
+
? {
|
|
24
|
+
...observation,
|
|
25
|
+
effectiveDateTime: params.effectiveDateTime.toISOString(),
|
|
26
|
+
}
|
|
27
|
+
: observation;
|
|
22
28
|
if (observationFragments) {
|
|
23
29
|
return observationFragments
|
|
24
30
|
.map((fragment) => ({
|
|
@@ -32,7 +38,8 @@ async function generateObservationBodiesByMeasurement(params) {
|
|
|
32
38
|
code: fragment.code,
|
|
33
39
|
...(fragment.additionalProperties || {}),
|
|
34
40
|
}))
|
|
35
|
-
.filter(hasObservationValue)
|
|
41
|
+
.filter(hasObservationValue)
|
|
42
|
+
.map(applyEffectiveDateTime);
|
|
36
43
|
}
|
|
37
44
|
const observationCodes = observationService.getObservationCodes();
|
|
38
45
|
const values = observationService.generateValues(measurement);
|
|
@@ -48,7 +55,7 @@ async function generateObservationBodiesByMeasurement(params) {
|
|
|
48
55
|
code,
|
|
49
56
|
...(additionalProperties[index] || {}),
|
|
50
57
|
}));
|
|
51
|
-
return observationBodies.filter(hasObservationValue);
|
|
58
|
+
return observationBodies.filter(hasObservationValue).map(applyEffectiveDateTime);
|
|
52
59
|
}
|
|
53
60
|
const hasObservationValue = (observation) => {
|
|
54
61
|
var _a, _b, _c, _d, _e;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { Bundle, BundleEntry, Observation } from "@medplum/fhirtypes";
|
|
2
|
+
export declare const MAX_OBSERVATION_BUNDLE_BYTES = 900000;
|
|
3
|
+
export declare const MAX_CONDITIONAL_OBSERVATION_ENTRIES = 8;
|
|
4
|
+
export declare const linkObservationMembers: (observations: Observation[]) => Observation[];
|
|
5
|
+
export declare const splitObservationBundleEntries: (bundle: Bundle) => BundleEntry[][] | undefined;
|
|
6
|
+
export declare const executeObservationBundle: (bundle: Bundle, executeBatch: (bundle: Bundle) => Promise<Bundle>) => Promise<Bundle>;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.executeObservationBundle = exports.splitObservationBundleEntries = exports.linkObservationMembers = exports.MAX_CONDITIONAL_OBSERVATION_ENTRIES = exports.MAX_OBSERVATION_BUNDLE_BYTES = void 0;
|
|
4
|
+
const offline_1 = require("../offline");
|
|
5
|
+
exports.MAX_OBSERVATION_BUNDLE_BYTES = 900000;
|
|
6
|
+
exports.MAX_CONDITIONAL_OBSERVATION_ENTRIES = 8;
|
|
7
|
+
const linkObservationMembers = (observations) => {
|
|
8
|
+
const main = observations[0];
|
|
9
|
+
if (!main) {
|
|
10
|
+
return observations;
|
|
11
|
+
}
|
|
12
|
+
const mainReference = `urn:uuid:${(0, offline_1.stableMeasurementId)(main)}`;
|
|
13
|
+
return observations.map((observation, index) => {
|
|
14
|
+
var _a;
|
|
15
|
+
return index === 0
|
|
16
|
+
? observation
|
|
17
|
+
: {
|
|
18
|
+
...observation,
|
|
19
|
+
hasMember: [
|
|
20
|
+
...((_a = observation.hasMember) !== null && _a !== void 0 ? _a : []),
|
|
21
|
+
{ reference: mainReference },
|
|
22
|
+
],
|
|
23
|
+
};
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
exports.linkObservationMembers = linkObservationMembers;
|
|
27
|
+
const splitObservationBundleEntries = (bundle) => {
|
|
28
|
+
var _a;
|
|
29
|
+
const entries = (_a = bundle.entry) !== null && _a !== void 0 ? _a : [];
|
|
30
|
+
if (entries.length <= 1 || JSON.stringify(bundle).length <= exports.MAX_OBSERVATION_BUNDLE_BYTES) {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
const chunks = [];
|
|
34
|
+
let chunk = [];
|
|
35
|
+
for (const entry of entries) {
|
|
36
|
+
const candidate = [...chunk, entry];
|
|
37
|
+
if (chunk.length > 0 &&
|
|
38
|
+
JSON.stringify({ ...bundle, entry: candidate }).length > exports.MAX_OBSERVATION_BUNDLE_BYTES) {
|
|
39
|
+
chunks.push(chunk);
|
|
40
|
+
chunk = [entry];
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
chunk = candidate;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (chunk.length > 0) {
|
|
47
|
+
chunks.push(chunk);
|
|
48
|
+
}
|
|
49
|
+
return chunks;
|
|
50
|
+
};
|
|
51
|
+
exports.splitObservationBundleEntries = splitObservationBundleEntries;
|
|
52
|
+
const executeObservationBundle = async (bundle, executeBatch) => {
|
|
53
|
+
var _a, _b, _c, _d;
|
|
54
|
+
const entries = (_a = bundle.entry) !== null && _a !== void 0 ? _a : [];
|
|
55
|
+
if (bundle.type === "transaction" &&
|
|
56
|
+
entries.length > exports.MAX_CONDITIONAL_OBSERVATION_ENTRIES) {
|
|
57
|
+
const transactionResponse = await (0, exports.executeObservationBundle)({ ...bundle, entry: entries.slice(0, 1) }, executeBatch);
|
|
58
|
+
const batchResponse = await (0, exports.executeObservationBundle)({ ...bundle, type: "batch", entry: entries.slice(1) }, executeBatch);
|
|
59
|
+
return {
|
|
60
|
+
resourceType: "Bundle",
|
|
61
|
+
type: "batch-response",
|
|
62
|
+
entry: [
|
|
63
|
+
...((_b = transactionResponse.entry) !== null && _b !== void 0 ? _b : []),
|
|
64
|
+
...((_c = batchResponse.entry) !== null && _c !== void 0 ? _c : []),
|
|
65
|
+
],
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
const chunks = (0, exports.splitObservationBundleEntries)(bundle);
|
|
69
|
+
if (chunks) {
|
|
70
|
+
const responses = [];
|
|
71
|
+
for (const entry of chunks) {
|
|
72
|
+
responses.push(await (0, exports.executeObservationBundle)({ ...bundle, entry }, executeBatch));
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
resourceType: "Bundle",
|
|
76
|
+
type: "batch-response",
|
|
77
|
+
entry: responses.flatMap((response) => { var _a; return (_a = response.entry) !== null && _a !== void 0 ? _a : []; }),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
const response = await executeBatch(bundle);
|
|
81
|
+
if ((_d = response.entry) === null || _d === void 0 ? void 0 : _d.some((entry) => {
|
|
82
|
+
var _a, _b;
|
|
83
|
+
const status = (_b = (_a = entry.response) === null || _a === void 0 ? void 0 : _a.status) !== null && _b !== void 0 ? _b : "";
|
|
84
|
+
return status.startsWith("4") || status.startsWith("5");
|
|
85
|
+
})) {
|
|
86
|
+
throw response;
|
|
87
|
+
}
|
|
88
|
+
return response;
|
|
89
|
+
};
|
|
90
|
+
exports.executeObservationBundle = executeObservationBundle;
|
package/dist/client/observation/types/generate-observation-body/GenerateObservationBodyParams.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { Measurement } from "../../../../types";
|
|
|
2
2
|
import { MeasurementDevice } from "../measurement/MeasurementDevice";
|
|
3
3
|
export type GenerateObservationBodyParams = {
|
|
4
4
|
device?: MeasurementDevice;
|
|
5
|
+
effectiveDateTime?: Date;
|
|
5
6
|
patientId?: string;
|
|
6
7
|
measurement: Measurement;
|
|
7
8
|
};
|
|
@@ -4,6 +4,8 @@ exports.getDeviceObject = void 0;
|
|
|
4
4
|
const EXTENSION_BASE_URL = "http://ovok.com/fhir/StructureDefinition";
|
|
5
5
|
const LOCAL_NAME_URL = `${EXTENSION_BASE_URL}/device-local-name`;
|
|
6
6
|
const MANUFACTURER_DATA_URL = `${EXTENSION_BASE_URL}/device-manufacturer-data`;
|
|
7
|
+
const MANUFACTURER_NAME_URL = `${EXTENSION_BASE_URL}/device-manufacturer-name`;
|
|
8
|
+
const MODEL_URL = `${EXTENSION_BASE_URL}/device-model`;
|
|
7
9
|
const SERIAL_NUMBER_URL = `${EXTENSION_BASE_URL}/device-serial-number`;
|
|
8
10
|
const BATTERY_PERCENTAGE_URL = `${EXTENSION_BASE_URL}/device-battery-percentage`;
|
|
9
11
|
/**
|
|
@@ -21,6 +23,12 @@ const getBatteryExtension = (batteryPercentage) => {
|
|
|
21
23
|
},
|
|
22
24
|
];
|
|
23
25
|
};
|
|
26
|
+
const getOptionalStringExtension = (url, value) => {
|
|
27
|
+
if (!value) {
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
return [{ url, valueString: value }];
|
|
31
|
+
};
|
|
24
32
|
const getDeviceObject = (measurementDevice) => {
|
|
25
33
|
var _a, _b;
|
|
26
34
|
if (!measurementDevice) {
|
|
@@ -44,6 +52,8 @@ const getDeviceObject = (measurementDevice) => {
|
|
|
44
52
|
url: MANUFACTURER_DATA_URL,
|
|
45
53
|
valueString: measurementDevice.manufacturerData || "no-manufacturer-data",
|
|
46
54
|
},
|
|
55
|
+
...getOptionalStringExtension(MANUFACTURER_NAME_URL, measurementDevice.manufacturerName),
|
|
56
|
+
...getOptionalStringExtension(MODEL_URL, measurementDevice.model),
|
|
47
57
|
{
|
|
48
58
|
url: SERIAL_NUMBER_URL,
|
|
49
59
|
valueString: measurementDevice.sn || "no-serial-number",
|
|
@@ -8,6 +8,8 @@ import * as observationMethods from "./observation/methods";
|
|
|
8
8
|
import { GenerateObservationBodyParams } from "./observation/types/generate-observation-body/GenerateObservationBodyParams";
|
|
9
9
|
import { OfflineMeasurementFlushOptions, OfflineMeasurementFlushResult, OfflineMeasurementQueueOptions } from "./offline";
|
|
10
10
|
import * as questionnaireResponseMethods from "./questionnaire-response/methods";
|
|
11
|
+
export declare const MAX_CONDITIONAL_OBSERVATION_ENTRIES = 8;
|
|
12
|
+
export { MAX_OBSERVATION_BUNDLE_BYTES } from "./observation/observation-bundle-utils";
|
|
11
13
|
export type SaveObservationsResult = {
|
|
12
14
|
status: "saved";
|
|
13
15
|
response: Bundle;
|
|
@@ -36,6 +38,7 @@ export declare class OvokClient extends MedplumClient {
|
|
|
36
38
|
flushOfflineMeasurementQueue(options?: OfflineMeasurementFlushOptions): Promise<OfflineMeasurementFlushResult>;
|
|
37
39
|
private getOfflineMeasurementQueue;
|
|
38
40
|
private executeConditionalObservationCreates;
|
|
41
|
+
private executeObservationBundle;
|
|
39
42
|
private bindMethods;
|
|
40
43
|
/**
|
|
41
44
|
* Gets the `SubscriptionManager` for WebSocket subscriptions.
|
|
@@ -53,4 +56,3 @@ declare module "./ovok-client" {
|
|
|
53
56
|
clientStorage: IClientStorage | undefined;
|
|
54
57
|
}
|
|
55
58
|
}
|
|
56
|
-
export {};
|
|
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.OvokClient = void 0;
|
|
36
|
+
exports.OvokClient = exports.MAX_OBSERVATION_BUNDLE_BYTES = exports.MAX_CONDITIONAL_OBSERVATION_ENTRIES = void 0;
|
|
37
37
|
/* eslint-disable @typescript-eslint/no-unsafe-function-type */ // TODO: fix this whenever find time
|
|
38
38
|
const core_1 = require("@medplum/core");
|
|
39
39
|
const aiFhirMethods = __importStar(require("./ai-fhir/methods"));
|
|
@@ -42,8 +42,12 @@ const botMethods = __importStar(require("./bot/methods"));
|
|
|
42
42
|
const aiMethods = __importStar(require("./chat/ai/methods"));
|
|
43
43
|
const rate_limit_error_1 = require("./errors/rate-limit-error");
|
|
44
44
|
const observationMethods = __importStar(require("./observation/methods"));
|
|
45
|
+
const observation_bundle_utils_1 = require("./observation/observation-bundle-utils");
|
|
45
46
|
const offline_1 = require("./offline");
|
|
46
47
|
const questionnaireResponseMethods = __importStar(require("./questionnaire-response/methods"));
|
|
48
|
+
exports.MAX_CONDITIONAL_OBSERVATION_ENTRIES = 8;
|
|
49
|
+
var observation_bundle_utils_2 = require("./observation/observation-bundle-utils");
|
|
50
|
+
Object.defineProperty(exports, "MAX_OBSERVATION_BUNDLE_BYTES", { enumerable: true, get: function () { return observation_bundle_utils_2.MAX_OBSERVATION_BUNDLE_BYTES; } });
|
|
47
51
|
class OvokClient extends core_1.MedplumClient {
|
|
48
52
|
/**
|
|
49
53
|
* @param config - The configuration for the client.
|
|
@@ -70,15 +74,16 @@ class OvokClient extends core_1.MedplumClient {
|
|
|
70
74
|
}
|
|
71
75
|
/** Saves observations with conditional creates so a retry cannot create a duplicate. */
|
|
72
76
|
async saveObservations(observations) {
|
|
77
|
+
const linkedObservations = (0, observation_bundle_utils_1.linkObservationMembers)(observations);
|
|
73
78
|
const queue = this.getOfflineMeasurementQueue();
|
|
74
79
|
if (queue) {
|
|
75
|
-
const { id, flush } = await queue.enqueueAndFlush(
|
|
80
|
+
const { id, flush } = await queue.enqueueAndFlush(linkedObservations);
|
|
76
81
|
const response = flush.responses[id];
|
|
77
82
|
return response
|
|
78
83
|
? { status: "saved", response }
|
|
79
84
|
: { status: "queued", queueId: id };
|
|
80
85
|
}
|
|
81
|
-
const response = await this.executeConditionalObservationCreates(
|
|
86
|
+
const response = await this.executeConditionalObservationCreates(linkedObservations);
|
|
82
87
|
return { status: "saved", response };
|
|
83
88
|
}
|
|
84
89
|
/** Flushes measurements queued while offline. Call this on connectivity/app-resume events. */
|
|
@@ -103,25 +108,19 @@ class OvokClient extends core_1.MedplumClient {
|
|
|
103
108
|
if (!this.clientStorage) {
|
|
104
109
|
throw new Error("Offline measurement queue requires a durable client storage adapter.");
|
|
105
110
|
}
|
|
106
|
-
(_a = this.offlineMeasurementQueue) !== null && _a !== void 0 ? _a : (this.offlineMeasurementQueue = new offline_1.OfflineMeasurementQueue(this.clientStorage, (bundle) => this.
|
|
111
|
+
(_a = this.offlineMeasurementQueue) !== null && _a !== void 0 ? _a : (this.offlineMeasurementQueue = new offline_1.OfflineMeasurementQueue(this.clientStorage, (bundle) => this.executeObservationBundle(bundle), this.offlineQueueOptions));
|
|
107
112
|
return this.offlineMeasurementQueue;
|
|
108
113
|
}
|
|
109
114
|
async executeConditionalObservationCreates(observations) {
|
|
110
|
-
var _a;
|
|
111
115
|
const entries = observations.map(offline_1.entryForObservation);
|
|
112
|
-
|
|
116
|
+
return this.executeObservationBundle({
|
|
113
117
|
resourceType: "Bundle",
|
|
114
118
|
type: "transaction",
|
|
115
119
|
entry: entries,
|
|
116
120
|
});
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
return status.startsWith("4") || status.startsWith("5");
|
|
121
|
-
})) {
|
|
122
|
-
throw bundle;
|
|
123
|
-
}
|
|
124
|
-
return bundle;
|
|
121
|
+
}
|
|
122
|
+
async executeObservationBundle(bundle) {
|
|
123
|
+
return (0, observation_bundle_utils_1.executeObservationBundle)(bundle, (request) => this.executeBatch(request));
|
|
125
124
|
}
|
|
126
125
|
bindMethods(methods) {
|
|
127
126
|
for (const [name, method] of Object.entries(methods)) {
|
|
@@ -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.3.
|
|
51
|
+
version: "0.3.3",
|
|
52
52
|
},
|
|
53
53
|
fhirVersion: "4.0.1",
|
|
54
54
|
format: ["json"],
|
package/dist/index.d.ts
CHANGED
|
@@ -924,6 +924,7 @@ declare const Medplum: {
|
|
|
924
924
|
export { Medplum };
|
|
925
925
|
export { OvokClient } from "./client/ovok-client";
|
|
926
926
|
export * from "./client/errors/rate-limit-error";
|
|
927
|
+
export { MAX_CONDITIONAL_OBSERVATION_ENTRIES, MAX_OBSERVATION_BUNDLE_BYTES, } from "./client/ovok-client";
|
|
927
928
|
export type { SaveObservationsResult } from "./client/ovok-client";
|
|
928
929
|
export * from "./client/offline";
|
|
929
930
|
export * from "./hooks";
|
package/dist/index.js
CHANGED
|
@@ -36,7 +36,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
36
36
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.saveMeasurement = exports.OvokClient = exports.Medplum = void 0;
|
|
39
|
+
exports.saveMeasurement = exports.MAX_OBSERVATION_BUNDLE_BYTES = exports.MAX_CONDITIONAL_OBSERVATION_ENTRIES = exports.OvokClient = exports.Medplum = void 0;
|
|
40
40
|
const MedplumCore = __importStar(require("@medplum/core"));
|
|
41
41
|
__exportStar(require("./types"), exports);
|
|
42
42
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
@@ -45,6 +45,9 @@ 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
47
|
__exportStar(require("./client/errors/rate-limit-error"), exports);
|
|
48
|
+
var ovok_client_2 = require("./client/ovok-client");
|
|
49
|
+
Object.defineProperty(exports, "MAX_CONDITIONAL_OBSERVATION_ENTRIES", { enumerable: true, get: function () { return ovok_client_2.MAX_CONDITIONAL_OBSERVATION_ENTRIES; } });
|
|
50
|
+
Object.defineProperty(exports, "MAX_OBSERVATION_BUNDLE_BYTES", { enumerable: true, get: function () { return ovok_client_2.MAX_OBSERVATION_BUNDLE_BYTES; } });
|
|
48
51
|
__exportStar(require("./client/offline"), exports);
|
|
49
52
|
__exportStar(require("./hooks"), exports);
|
|
50
53
|
__exportStar(require("./utils"), exports);
|