@ovok/core 0.4.21 → 0.4.22
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.
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { IClientStorage } from "@medplum/core";
|
|
2
2
|
import { Bundle, BundleEntry, Observation } from "@medplum/fhirtypes";
|
|
3
|
-
declare const
|
|
3
|
+
export declare const OFFLINE_MEASUREMENT_QUEUE_STORAGE_KEY = "ovok.core.offline-measurements";
|
|
4
4
|
declare const IDENTIFIER_SYSTEM = "https://api.ovok.com/fhir/StructureDefinition/offline-measurement-id";
|
|
5
5
|
export type OfflineMeasurementQueueOptions = {
|
|
6
6
|
enabled?: boolean;
|
|
@@ -8,6 +8,8 @@ export type OfflineMeasurementQueueOptions = {
|
|
|
8
8
|
maxRetries?: number;
|
|
9
9
|
retryBackoffMs?: number;
|
|
10
10
|
maxRetryBackoffMs?: number;
|
|
11
|
+
/** Returns the active account key. Queue items are delivered only to their owner. */
|
|
12
|
+
getOwnerKey?: () => string | undefined;
|
|
11
13
|
};
|
|
12
14
|
export type OfflineMeasurementFlushOptions = {
|
|
13
15
|
includeExhausted?: boolean;
|
|
@@ -30,6 +32,7 @@ export declare class OfflineMeasurementQueue {
|
|
|
30
32
|
private readonly maxRetries;
|
|
31
33
|
private readonly retryBackoffMs;
|
|
32
34
|
private readonly maxRetryBackoffMs;
|
|
35
|
+
private readonly getOwnerKey?;
|
|
33
36
|
private operation;
|
|
34
37
|
constructor(storage: IClientStorage, transport: MeasurementTransport, options?: OfflineMeasurementQueueOptions);
|
|
35
38
|
enqueueAndFlush: (observations: Observation[]) => Promise<{
|
|
@@ -42,4 +45,4 @@ export declare class OfflineMeasurementQueue {
|
|
|
42
45
|
private writeQueue;
|
|
43
46
|
private flushQueue;
|
|
44
47
|
}
|
|
45
|
-
export {
|
|
48
|
+
export { OFFLINE_MEASUREMENT_QUEUE_STORAGE_KEY as DEFAULT_OFFLINE_MEASUREMENT_QUEUE_KEY, IDENTIFIER_SYSTEM as OFFLINE_MEASUREMENT_IDENTIFIER_SYSTEM, entryForObservation, stableMeasurementId, };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.stableMeasurementId = exports.entryForObservation = exports.OFFLINE_MEASUREMENT_IDENTIFIER_SYSTEM = exports.DEFAULT_OFFLINE_MEASUREMENT_QUEUE_KEY = exports.OfflineMeasurementQueue = void 0;
|
|
4
|
-
|
|
5
|
-
exports.DEFAULT_OFFLINE_MEASUREMENT_QUEUE_KEY =
|
|
3
|
+
exports.stableMeasurementId = exports.entryForObservation = exports.OFFLINE_MEASUREMENT_IDENTIFIER_SYSTEM = exports.DEFAULT_OFFLINE_MEASUREMENT_QUEUE_KEY = exports.OfflineMeasurementQueue = exports.OFFLINE_MEASUREMENT_QUEUE_STORAGE_KEY = void 0;
|
|
4
|
+
exports.OFFLINE_MEASUREMENT_QUEUE_STORAGE_KEY = "ovok.core.offline-measurements";
|
|
5
|
+
exports.DEFAULT_OFFLINE_MEASUREMENT_QUEUE_KEY = exports.OFFLINE_MEASUREMENT_QUEUE_STORAGE_KEY;
|
|
6
6
|
const DEFAULT_MAX_RETRIES = 8;
|
|
7
7
|
const DEFAULT_RETRY_BACKOFF_MS = 5000;
|
|
8
8
|
const DEFAULT_MAX_RETRY_BACKOFF_MS = 60 * 60 * 1000;
|
|
@@ -82,14 +82,19 @@ class OfflineMeasurementQueue {
|
|
|
82
82
|
this.transport = transport;
|
|
83
83
|
this.operation = Promise.resolve();
|
|
84
84
|
this.enqueueAndFlush = (observations) => this.runExclusive(async () => {
|
|
85
|
-
var _a, _b;
|
|
85
|
+
var _a, _b, _c;
|
|
86
86
|
await ((_b = (_a = this.storage).getInitPromise) === null || _b === void 0 ? void 0 : _b.call(_a));
|
|
87
87
|
const queue = this.readQueue();
|
|
88
88
|
const entries = observations.map(entryForObservation);
|
|
89
|
-
const
|
|
89
|
+
const ownerKey = (_c = this.getOwnerKey) === null || _c === void 0 ? void 0 : _c.call(this);
|
|
90
|
+
if (this.getOwnerKey && !ownerKey) {
|
|
91
|
+
throw new Error("Offline measurements require an active account to prevent cross-account delivery.");
|
|
92
|
+
}
|
|
93
|
+
const id = stableValueId({ ownerKey, entries });
|
|
90
94
|
if (!queue.some((item) => item.id === id)) {
|
|
91
95
|
queue.push({
|
|
92
96
|
id,
|
|
97
|
+
ownerKey,
|
|
93
98
|
queuedAt: new Date().toISOString(),
|
|
94
99
|
attempt: 0,
|
|
95
100
|
nextAttemptAt: 0,
|
|
@@ -130,6 +135,7 @@ class OfflineMeasurementQueue {
|
|
|
130
135
|
}
|
|
131
136
|
};
|
|
132
137
|
this.flushQueue = async (queue, options = {}) => {
|
|
138
|
+
var _a;
|
|
133
139
|
const result = {
|
|
134
140
|
attempted: 0,
|
|
135
141
|
saved: 0,
|
|
@@ -138,6 +144,7 @@ class OfflineMeasurementQueue {
|
|
|
138
144
|
responses: {},
|
|
139
145
|
};
|
|
140
146
|
const now = Date.now();
|
|
147
|
+
const ownerKey = (_a = this.getOwnerKey) === null || _a === void 0 ? void 0 : _a.call(this);
|
|
141
148
|
let changed = false;
|
|
142
149
|
for (const item of [...queue]) {
|
|
143
150
|
if (item.exhausted && options.resetExhausted) {
|
|
@@ -156,6 +163,10 @@ class OfflineMeasurementQueue {
|
|
|
156
163
|
}
|
|
157
164
|
continue;
|
|
158
165
|
}
|
|
166
|
+
if (this.getOwnerKey && (!ownerKey || item.ownerKey !== ownerKey)) {
|
|
167
|
+
result.queued++;
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
159
170
|
result.attempted++;
|
|
160
171
|
const attempt = item.attempt + 1;
|
|
161
172
|
try {
|
|
@@ -172,7 +183,7 @@ class OfflineMeasurementQueue {
|
|
|
172
183
|
result.responses[item.id] = response;
|
|
173
184
|
changed = true;
|
|
174
185
|
}
|
|
175
|
-
catch (
|
|
186
|
+
catch (_b) {
|
|
176
187
|
item.attempt = attempt;
|
|
177
188
|
item.exhausted = attempt >= this.maxRetries;
|
|
178
189
|
item.nextAttemptAt = item.exhausted
|
|
@@ -193,10 +204,12 @@ class OfflineMeasurementQueue {
|
|
|
193
204
|
}
|
|
194
205
|
return result;
|
|
195
206
|
};
|
|
196
|
-
this.storageKey =
|
|
207
|
+
this.storageKey =
|
|
208
|
+
(_a = options.storageKey) !== null && _a !== void 0 ? _a : exports.OFFLINE_MEASUREMENT_QUEUE_STORAGE_KEY;
|
|
197
209
|
this.maxRetries = Math.max(1, (_b = options.maxRetries) !== null && _b !== void 0 ? _b : DEFAULT_MAX_RETRIES);
|
|
198
210
|
this.retryBackoffMs = Math.max(1, (_c = options.retryBackoffMs) !== null && _c !== void 0 ? _c : DEFAULT_RETRY_BACKOFF_MS);
|
|
199
211
|
this.maxRetryBackoffMs = Math.max(this.retryBackoffMs, (_d = options.maxRetryBackoffMs) !== null && _d !== void 0 ? _d : DEFAULT_MAX_RETRY_BACKOFF_MS);
|
|
212
|
+
this.getOwnerKey = options.getOwnerKey;
|
|
200
213
|
}
|
|
201
214
|
}
|
|
202
215
|
exports.OfflineMeasurementQueue = OfflineMeasurementQueue;
|
|
@@ -55,6 +55,11 @@ export declare class OvokClient extends MedplumClient {
|
|
|
55
55
|
/** Flushes measurements queued while offline. Call this on connectivity/app-resume events. */
|
|
56
56
|
flushOfflineMeasurementQueue(options?: OfflineMeasurementFlushOptions): Promise<OfflineMeasurementFlushResult>;
|
|
57
57
|
private getOfflineMeasurementQueue;
|
|
58
|
+
/**
|
|
59
|
+
* Medplum clears its entire client storage after an unrecoverable 401. Keep
|
|
60
|
+
* queued measurements so they can be retried only after their owner signs in.
|
|
61
|
+
*/
|
|
62
|
+
clear(): void;
|
|
58
63
|
private executeConditionalObservationCreates;
|
|
59
64
|
private executeObservationBundle;
|
|
60
65
|
private bindMethods;
|
|
@@ -148,9 +148,38 @@ class OvokClient extends core_1.MedplumClient {
|
|
|
148
148
|
if (!this.clientStorage) {
|
|
149
149
|
throw new Error("Offline measurement queue requires a durable client storage adapter.");
|
|
150
150
|
}
|
|
151
|
-
(_a = this.offlineMeasurementQueue) !== null && _a !== void 0 ? _a : (this.offlineMeasurementQueue = new offline_1.OfflineMeasurementQueue(this.clientStorage, (bundle) => this.executeObservationBundle(bundle),
|
|
151
|
+
(_a = this.offlineMeasurementQueue) !== null && _a !== void 0 ? _a : (this.offlineMeasurementQueue = new offline_1.OfflineMeasurementQueue(this.clientStorage, (bundle) => this.executeObservationBundle(bundle), {
|
|
152
|
+
...this.offlineQueueOptions,
|
|
153
|
+
getOwnerKey: () => {
|
|
154
|
+
var _a, _b, _c;
|
|
155
|
+
const login = this.getActiveLogin();
|
|
156
|
+
const profileReference = (_a = login === null || login === void 0 ? void 0 : login.profile) === null || _a === void 0 ? void 0 : _a.reference;
|
|
157
|
+
if (!profileReference) {
|
|
158
|
+
return undefined;
|
|
159
|
+
}
|
|
160
|
+
return [
|
|
161
|
+
this.getBaseUrl(),
|
|
162
|
+
(_c = (_b = login === null || login === void 0 ? void 0 : login.project) === null || _b === void 0 ? void 0 : _b.reference) !== null && _c !== void 0 ? _c : "",
|
|
163
|
+
profileReference,
|
|
164
|
+
].join("|");
|
|
165
|
+
},
|
|
166
|
+
}));
|
|
152
167
|
return this.offlineMeasurementQueue;
|
|
153
168
|
}
|
|
169
|
+
/**
|
|
170
|
+
* Medplum clears its entire client storage after an unrecoverable 401. Keep
|
|
171
|
+
* queued measurements so they can be retried only after their owner signs in.
|
|
172
|
+
*/
|
|
173
|
+
clear() {
|
|
174
|
+
var _a;
|
|
175
|
+
const storage = this.clientStorage;
|
|
176
|
+
const queueKey = (_a = this.offlineQueueOptions.storageKey) !== null && _a !== void 0 ? _a : offline_1.OFFLINE_MEASUREMENT_QUEUE_STORAGE_KEY;
|
|
177
|
+
const queuedMeasurements = storage === null || storage === void 0 ? void 0 : storage.getObject(queueKey);
|
|
178
|
+
super.clear();
|
|
179
|
+
if (Array.isArray(queuedMeasurements)) {
|
|
180
|
+
storage === null || storage === void 0 ? void 0 : storage.setObject(queueKey, queuedMeasurements);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
154
183
|
async executeConditionalObservationCreates(observations) {
|
|
155
184
|
const entries = observations.map(offline_1.entryForObservation);
|
|
156
185
|
return this.executeObservationBundle({
|
|
@@ -48,7 +48,7 @@ exports.OVOK_CORE_REQUIREMENTS = {
|
|
|
48
48
|
description: "The FHIR surface a server must provide for the @ovok/core client library's own methods to function.",
|
|
49
49
|
software: {
|
|
50
50
|
name: "@ovok/core",
|
|
51
|
-
version: "0.4.
|
|
51
|
+
version: "0.4.22",
|
|
52
52
|
},
|
|
53
53
|
fhirVersion: "4.0.1",
|
|
54
54
|
format: ["json"],
|