@openmrs/esm-offline 3.3.2-pre.9 → 3.4.0
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/.turbo/turbo-build.log +7 -7
- package/dist/openmrs-esm-offline.js +1 -1
- package/dist/openmrs-esm-offline.js.map +1 -1
- package/package.json +6 -6
- package/src/dynamic-offline-data.test.ts +70 -0
- package/src/dynamic-offline-data.ts +348 -0
- package/src/index.ts +2 -0
- package/src/offline-db.ts +35 -0
- package/src/offline-patient-data.ts +31 -191
- package/src/public.ts +2 -1
- package/src/service-worker.ts +1 -1
- package/src/sync.test.ts +2 -2
- package/src/sync.ts +2 -22
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openmrs/esm-offline",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.4.0",
|
|
4
4
|
"license": "MPL-2.0",
|
|
5
5
|
"description": "Helper utilities for OpenMRS",
|
|
6
6
|
"browser": "dist/openmrs-esm-offline.js",
|
|
@@ -36,10 +36,10 @@
|
|
|
36
36
|
"access": "public"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"@openmrs/esm-api": "^3.
|
|
40
|
-
"@openmrs/esm-globals": "^3.
|
|
41
|
-
"@openmrs/esm-state": "^3.
|
|
42
|
-
"@openmrs/esm-styleguide": "^3.
|
|
39
|
+
"@openmrs/esm-api": "^3.4.0",
|
|
40
|
+
"@openmrs/esm-globals": "^3.4.0",
|
|
41
|
+
"@openmrs/esm-state": "^3.4.0",
|
|
42
|
+
"@openmrs/esm-styleguide": "^3.4.0",
|
|
43
43
|
"@types/uuid": "^8.3.0",
|
|
44
44
|
"rxjs": "^6.5.3"
|
|
45
45
|
},
|
|
@@ -56,5 +56,5 @@
|
|
|
56
56
|
"uuid": "^8.3.2",
|
|
57
57
|
"workbox-window": "^6.1.5"
|
|
58
58
|
},
|
|
59
|
-
"gitHead": "
|
|
59
|
+
"gitHead": "51e0d8495e4dbd18d18da19267ed4a792ab4fc6c"
|
|
60
60
|
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import "fake-indexeddb/auto";
|
|
2
|
+
import { getLoggedInUser } from "@openmrs/esm-api";
|
|
3
|
+
import { OfflineDb } from "./offline-db";
|
|
4
|
+
import {
|
|
5
|
+
getDynamicOfflineDataEntries,
|
|
6
|
+
getDynamicOfflineDataEntriesFor,
|
|
7
|
+
putDynamicOfflineData,
|
|
8
|
+
putDynamicOfflineDataFor,
|
|
9
|
+
removeDynamicOfflineData,
|
|
10
|
+
removeDynamicOfflineDataFor,
|
|
11
|
+
} from "./dynamic-offline-data";
|
|
12
|
+
|
|
13
|
+
const mockUserId = "00000000-0000-0000-0000-000000000000";
|
|
14
|
+
|
|
15
|
+
jest.mock("@openmrs/esm-api", () => ({
|
|
16
|
+
getLoggedInUser: jest.fn(async () => ({ uuid: mockUserId })),
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
afterEach(async () => {
|
|
20
|
+
// We want each test case to start fresh with a clean sync queue.
|
|
21
|
+
await new OfflineDb().dynamicOfflineData.clear();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe("putDynamicOfflineData", () => {
|
|
25
|
+
it("creates new entry if none exists yet", async () => {
|
|
26
|
+
await putDynamicOfflineData("test", "123");
|
|
27
|
+
|
|
28
|
+
const entries = await getDynamicOfflineDataEntries("test");
|
|
29
|
+
expect(entries).toHaveLength(1);
|
|
30
|
+
expect(entries[0].type).toBe("test");
|
|
31
|
+
expect(entries[0].identifier).toBe("123");
|
|
32
|
+
expect(entries[0].users).toStrictEqual([mockUserId]);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("does not create new entry if type+identifier combination already exists", async () => {
|
|
36
|
+
await putDynamicOfflineData("test", "123");
|
|
37
|
+
await putDynamicOfflineData("test", "123");
|
|
38
|
+
|
|
39
|
+
const entries = await getDynamicOfflineDataEntries("test");
|
|
40
|
+
expect(entries).toHaveLength(1);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("combines users if entry is already registered for other user", async () => {
|
|
44
|
+
await putDynamicOfflineDataFor("user-id-1", "test", "123");
|
|
45
|
+
await putDynamicOfflineDataFor("user-id-2", "test", "123");
|
|
46
|
+
|
|
47
|
+
const entries = await getDynamicOfflineDataEntriesFor("user-id-1", "test");
|
|
48
|
+
expect(entries).toHaveLength(1);
|
|
49
|
+
expect(entries[0].users).toStrictEqual(["user-id-1", "user-id-2"]);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe("removeDynamicOfflineData", () => {
|
|
54
|
+
it("removes entry of single user", async () => {
|
|
55
|
+
await putDynamicOfflineData("test", "123");
|
|
56
|
+
await removeDynamicOfflineData("test", "123");
|
|
57
|
+
const entries = await getDynamicOfflineDataEntries("test");
|
|
58
|
+
expect(entries).toHaveLength(0);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("removes calling user of entry with multiple users", async () => {
|
|
62
|
+
await putDynamicOfflineDataFor("user-id-1", "test", "123");
|
|
63
|
+
await putDynamicOfflineDataFor("user-id-2", "test", "123");
|
|
64
|
+
await removeDynamicOfflineDataFor("user-id-1", "test", "123");
|
|
65
|
+
|
|
66
|
+
const entries = await getDynamicOfflineDataEntriesFor("user-id-2", "test");
|
|
67
|
+
expect(entries).toHaveLength(1);
|
|
68
|
+
expect(entries[0].users).toStrictEqual(["user-id-2"]);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
/** @module @category Offline */
|
|
2
|
+
import { getLoggedInUser } from "@openmrs/esm-api";
|
|
3
|
+
import Dexie from "dexie";
|
|
4
|
+
import { OfflineDb } from "./offline-db";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A handler for synchronizing dynamically declared offline data.
|
|
8
|
+
* Can be setup using the {@link setupDynamicOfflineDataHandler} function.
|
|
9
|
+
*/
|
|
10
|
+
export interface DynamicOfflineDataHandler {
|
|
11
|
+
/**
|
|
12
|
+
* A string uniquely identifying the handler.
|
|
13
|
+
*/
|
|
14
|
+
id: string;
|
|
15
|
+
/**
|
|
16
|
+
* The type of offline data handled by this handler.
|
|
17
|
+
* See {@link DynamicOfflineData.type} for details.
|
|
18
|
+
*/
|
|
19
|
+
type: string;
|
|
20
|
+
/**
|
|
21
|
+
* A human-readable string representing the handler.
|
|
22
|
+
* If provided, the handler can be rendered in the UI using that string.
|
|
23
|
+
*/
|
|
24
|
+
displayName?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Evaluates whether the given offline data is correctly synced at this point in time from the perspective
|
|
27
|
+
* of this single handler.
|
|
28
|
+
* If `false`, the handler would have to (re-)sync the data in order for offline mode to properly work.
|
|
29
|
+
* @param identifier The identifier of the offline data. See {@link DynamicOfflineData} for details.
|
|
30
|
+
* @param abortSignal An {@link AbortSignal} which can be used to cancel the operation.
|
|
31
|
+
*/
|
|
32
|
+
isSynced(identifier: string, abortSignal?: AbortSignal): Promise<boolean>;
|
|
33
|
+
/**
|
|
34
|
+
* Synchronizes the given offline data.
|
|
35
|
+
* @param identifier The identifier of the offline data. See {@link DynamicOfflineData} for details.
|
|
36
|
+
* @param abortSignal An {@link AbortSignal} which can be used to cancel the operation.
|
|
37
|
+
*/
|
|
38
|
+
sync(identifier: string, abortSignal?: AbortSignal): Promise<void>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Represents the registration of a single dynamic offline data entry.
|
|
43
|
+
*/
|
|
44
|
+
export interface DynamicOfflineData {
|
|
45
|
+
/**
|
|
46
|
+
* The internal ID of the data entry, as assigned by the IndexedDB where it is stored.
|
|
47
|
+
*/
|
|
48
|
+
id?: number;
|
|
49
|
+
/**
|
|
50
|
+
* The underlying type used for categorizing the data entry.
|
|
51
|
+
* Examples could be `"patient"` or `"form"`.
|
|
52
|
+
*/
|
|
53
|
+
type: string;
|
|
54
|
+
/**
|
|
55
|
+
* The externally provided identifier of the data entry.
|
|
56
|
+
* This is typically the ID of the resource as assigned by a remote API.
|
|
57
|
+
*/
|
|
58
|
+
identifier: string;
|
|
59
|
+
/**
|
|
60
|
+
* The UUIDs of the users who need this data entry available offline.
|
|
61
|
+
*/
|
|
62
|
+
users: Array<string>;
|
|
63
|
+
/**
|
|
64
|
+
* If this entry has already been synced, returns the result of that last sync attempt.
|
|
65
|
+
* Otherwise this is `undefined`.
|
|
66
|
+
*/
|
|
67
|
+
syncState?: DynamicOfflineDataSyncState;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Represents the result of syncing a given {@link DynamicOfflineData} entry.
|
|
72
|
+
*/
|
|
73
|
+
export interface DynamicOfflineDataSyncState {
|
|
74
|
+
/**
|
|
75
|
+
* The time when the entry has been synced the last time.
|
|
76
|
+
*/
|
|
77
|
+
syncedOn: Date;
|
|
78
|
+
/**
|
|
79
|
+
* The ID of the user who has triggered the data synchronization.
|
|
80
|
+
*/
|
|
81
|
+
syncedBy: string;
|
|
82
|
+
/**
|
|
83
|
+
* The IDs of the handlers which successfully synchronized their data.
|
|
84
|
+
*/
|
|
85
|
+
succeededHandlers: Array<string>;
|
|
86
|
+
/**
|
|
87
|
+
* The IDs of the handlers which failed to synchronize their data.
|
|
88
|
+
*/
|
|
89
|
+
erroredHandlers: Array<string>;
|
|
90
|
+
/**
|
|
91
|
+
* A collection of the errors caught while synchronizing, per handler.
|
|
92
|
+
*/
|
|
93
|
+
errors: Array<{
|
|
94
|
+
handlerId: string;
|
|
95
|
+
message: string;
|
|
96
|
+
}>;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const dynamicOfflineDataHandlers: Record<string, DynamicOfflineDataHandler> =
|
|
100
|
+
{};
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Returns all handlers which have been setup using the {@link setupDynamicOfflineDataHandler} function.
|
|
104
|
+
*/
|
|
105
|
+
export function getDynamicOfflineDataHandlers() {
|
|
106
|
+
return Object.values(dynamicOfflineDataHandlers);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Sets up a handler for synchronizing dynamic offline data.
|
|
111
|
+
* See {@link DynamicOfflineDataHandler} for details.
|
|
112
|
+
* @param handler The handler to be setup.
|
|
113
|
+
*/
|
|
114
|
+
export function setupDynamicOfflineDataHandler(
|
|
115
|
+
handler: DynamicOfflineDataHandler
|
|
116
|
+
) {
|
|
117
|
+
if (dynamicOfflineDataHandlers[handler.id]) {
|
|
118
|
+
console.warn(
|
|
119
|
+
`[setupDynamicOfflineDataHandler] Another second handler with the ID "${handler.id}" was registered. This handler will override the previous one. This could be unintended as the previous handler might run different flows than the newly registered one. If this is the case, ensure that you are setting up the handlers with different IDs.`
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
dynamicOfflineDataHandlers[handler.id] = handler;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Returns all {@link DynamicOfflineData} entries which registered for the currently logged in user.
|
|
128
|
+
* Optionally returns only entries of a given type.
|
|
129
|
+
* @param type The type of the entries to be returned. If `undefined`, returns all types.
|
|
130
|
+
*/
|
|
131
|
+
export async function getDynamicOfflineDataEntries(
|
|
132
|
+
type?: string
|
|
133
|
+
): Promise<Array<DynamicOfflineData>> {
|
|
134
|
+
const userId = await getCurrentUserId();
|
|
135
|
+
return await getDynamicOfflineDataEntriesFor(userId, type);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Returns all {@link DynamicOfflineData} entries which registered for the given user.
|
|
140
|
+
* Optionally returns only entries of a given type.
|
|
141
|
+
* @param userId The ID of the user whose entries are to be retrieved.
|
|
142
|
+
* @param type The type of the entries to be returned. If `undefined`, returns all types.
|
|
143
|
+
*/
|
|
144
|
+
export async function getDynamicOfflineDataEntriesFor(
|
|
145
|
+
userId: string,
|
|
146
|
+
type?: string
|
|
147
|
+
): Promise<Array<DynamicOfflineData>> {
|
|
148
|
+
const filter = type ? { type, users: userId } : { users: userId };
|
|
149
|
+
const db = new OfflineDb();
|
|
150
|
+
return await db.dynamicOfflineData
|
|
151
|
+
.where(filter)
|
|
152
|
+
.toArray()
|
|
153
|
+
.catch(Dexie.errnames.DatabaseClosed, () => []);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Declares that dynamic offline data of the given {@link type} with the given {@link identifier}
|
|
158
|
+
* should be made available offline for the currently logged in user.
|
|
159
|
+
* @param type The type of the offline data. See {@link DynamicOfflineData} for details.
|
|
160
|
+
* @param identifier The identifier of the offline data. See {@link DynamicOfflineData} for details.
|
|
161
|
+
*/
|
|
162
|
+
export async function putDynamicOfflineData(
|
|
163
|
+
type: string,
|
|
164
|
+
identifier: string
|
|
165
|
+
): Promise<void> {
|
|
166
|
+
const userId = await getCurrentUserId();
|
|
167
|
+
return await putDynamicOfflineDataFor(userId, type, identifier);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Declares that dynamic offline data of the given {@link type} with the given {@link identifier}
|
|
172
|
+
* should be made available offline for the user with the given ID.
|
|
173
|
+
* @param userId The ID of the user for whom the dynamic offline data should be made available.
|
|
174
|
+
* @param type The type of the offline data. See {@link DynamicOfflineData} for details.
|
|
175
|
+
* @param identifier The identifier of the offline data. See {@link DynamicOfflineData} for details.
|
|
176
|
+
*/
|
|
177
|
+
export async function putDynamicOfflineDataFor(
|
|
178
|
+
userId: string,
|
|
179
|
+
type: string,
|
|
180
|
+
identifier: string
|
|
181
|
+
): Promise<void> {
|
|
182
|
+
const db = new OfflineDb();
|
|
183
|
+
const existingEntry = await db.dynamicOfflineData
|
|
184
|
+
.get({
|
|
185
|
+
type,
|
|
186
|
+
identifier,
|
|
187
|
+
})
|
|
188
|
+
.catch(Dexie.errnames.DatabaseClosed, () => undefined);
|
|
189
|
+
|
|
190
|
+
if (!existingEntry) {
|
|
191
|
+
await db.dynamicOfflineData
|
|
192
|
+
.add({
|
|
193
|
+
users: [userId],
|
|
194
|
+
type,
|
|
195
|
+
identifier,
|
|
196
|
+
})
|
|
197
|
+
.catch(Dexie.errnames.DatabaseClosed);
|
|
198
|
+
} else if (!existingEntry.users.includes(userId)) {
|
|
199
|
+
await db.dynamicOfflineData
|
|
200
|
+
.update(existingEntry.id!, {
|
|
201
|
+
users: [...existingEntry.users, userId],
|
|
202
|
+
})
|
|
203
|
+
.catch(Dexie.errnames.DatabaseClosed);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Declares that dynamic offline data of the given {@link type} with the given {@link identifier}
|
|
209
|
+
* no longer needs to be available offline for the currently logged in user.
|
|
210
|
+
* @param type The type of the offline data. See {@link DynamicOfflineData} for details.
|
|
211
|
+
* @param identifier The identifier of the offline data. See {@link DynamicOfflineData} for details.
|
|
212
|
+
*/
|
|
213
|
+
export async function removeDynamicOfflineData(
|
|
214
|
+
type: string,
|
|
215
|
+
identifier: string
|
|
216
|
+
): Promise<void> {
|
|
217
|
+
const userId = await getCurrentUserId();
|
|
218
|
+
return await removeDynamicOfflineDataFor(userId, type, identifier);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Declares that dynamic offline data of the given {@link type} with the given {@link identifier}
|
|
223
|
+
* no longer needs to be available offline for the user with the given ID.
|
|
224
|
+
* @param userId The ID of the user who doesn't require the specified offline data.
|
|
225
|
+
* @param type The type of the offline data. See {@link DynamicOfflineData} for details.
|
|
226
|
+
* @param identifier The identifier of the offline data. See {@link DynamicOfflineData} for details.
|
|
227
|
+
*/
|
|
228
|
+
export async function removeDynamicOfflineDataFor(
|
|
229
|
+
userId: string,
|
|
230
|
+
type: string,
|
|
231
|
+
identifier: string
|
|
232
|
+
): Promise<void> {
|
|
233
|
+
const db = new OfflineDb();
|
|
234
|
+
const existingEntry = await db.dynamicOfflineData
|
|
235
|
+
.get({
|
|
236
|
+
type,
|
|
237
|
+
identifier,
|
|
238
|
+
users: userId,
|
|
239
|
+
})
|
|
240
|
+
.catch(Dexie.errnames.DatabaseClosed, () => undefined);
|
|
241
|
+
|
|
242
|
+
if (existingEntry) {
|
|
243
|
+
if (existingEntry.users.length > 1) {
|
|
244
|
+
await db.dynamicOfflineData
|
|
245
|
+
.update(existingEntry.id!, {
|
|
246
|
+
users: existingEntry.users.filter((x) => x !== userId),
|
|
247
|
+
})
|
|
248
|
+
.catch(Dexie.errnames.DatabaseClosed);
|
|
249
|
+
} else {
|
|
250
|
+
await db.dynamicOfflineData
|
|
251
|
+
.delete(existingEntry.id!)
|
|
252
|
+
.catch(Dexie.errnames.DatabaseClosed);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Synchronizes all offline data entries of the given {@link type} for the currently logged in user.
|
|
259
|
+
* @param type The type of the offline data. See {@link DynamicOfflineData} for details.
|
|
260
|
+
* @param abortSignal An {@link AbortSignal} which can be used to cancel the operation.
|
|
261
|
+
*/
|
|
262
|
+
export async function syncAllDynamicOfflineData(
|
|
263
|
+
type: string,
|
|
264
|
+
abortSignal?: AbortSignal
|
|
265
|
+
): Promise<void> {
|
|
266
|
+
const dataEntriesToSync = await getDynamicOfflineDataEntries(type);
|
|
267
|
+
await Promise.all(
|
|
268
|
+
dataEntriesToSync.map(async (entry) =>
|
|
269
|
+
syncDynamicOfflineData(entry.type, entry.identifier, abortSignal)
|
|
270
|
+
)
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Synchronizes a single offline data entry of the given {@link type} for the currently logged in user.
|
|
276
|
+
* @param type The type of the offline data. See {@link DynamicOfflineData} for details.
|
|
277
|
+
* @param identifier The identifier of the offline data. See {@link DynamicOfflineData} for details.
|
|
278
|
+
* @param abortSignal An {@link AbortSignal} which can be used to cancel the operation.
|
|
279
|
+
*/
|
|
280
|
+
export async function syncDynamicOfflineData(
|
|
281
|
+
type: string,
|
|
282
|
+
identifier: string,
|
|
283
|
+
abortSignal?: AbortSignal
|
|
284
|
+
): Promise<void> {
|
|
285
|
+
// If this function is called without the offline data being registered, we're implicitly doing
|
|
286
|
+
// that instead of throwing. This mitigates race conditions with user input and generally
|
|
287
|
+
// prevents errors that might occur otherwise (e.g. the sync state not being accessible without
|
|
288
|
+
// an entry being present).
|
|
289
|
+
await putDynamicOfflineData(type, identifier);
|
|
290
|
+
|
|
291
|
+
const db = new OfflineDb();
|
|
292
|
+
const userId = await getCurrentUserId();
|
|
293
|
+
const entry = await db.dynamicOfflineData
|
|
294
|
+
.get({ type, identifier })
|
|
295
|
+
.catch(Dexie.errnames.DatabaseClosed, () => undefined);
|
|
296
|
+
const handlers = getDynamicOfflineDataHandlers().filter(
|
|
297
|
+
(handler) => handler.type === type
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
if (!entry) {
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const results = await Promise.all(
|
|
305
|
+
handlers.map(async (handler) => {
|
|
306
|
+
try {
|
|
307
|
+
handler.sync(identifier, abortSignal);
|
|
308
|
+
return { id: handler.id, error: undefined };
|
|
309
|
+
} catch (e: any) {
|
|
310
|
+
const errorMessage: string = e["message"]?.toString() ?? e.toString();
|
|
311
|
+
return { id: handler.id, error: errorMessage };
|
|
312
|
+
}
|
|
313
|
+
})
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
const succeededHandlers = results.filter((x) => !x.error).map((x) => x.id);
|
|
317
|
+
const erroredHandlers = results.filter((x) => x.error).map((x) => x.id);
|
|
318
|
+
const errors = results
|
|
319
|
+
.filter((x) => x.error)
|
|
320
|
+
.map((x) => ({ handlerId: x.id, message: x.error! }));
|
|
321
|
+
const newSyncState: DynamicOfflineDataSyncState = {
|
|
322
|
+
syncedOn: new Date(),
|
|
323
|
+
syncedBy: userId,
|
|
324
|
+
succeededHandlers,
|
|
325
|
+
erroredHandlers,
|
|
326
|
+
errors,
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
if (entry.id) {
|
|
330
|
+
await db.dynamicOfflineData
|
|
331
|
+
.update(entry.id!, {
|
|
332
|
+
syncState: newSyncState,
|
|
333
|
+
})
|
|
334
|
+
.catch(Dexie.errnames.DatabaseClosed);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
async function getCurrentUserId() {
|
|
339
|
+
const id = (await getLoggedInUser()).uuid;
|
|
340
|
+
|
|
341
|
+
if (!id) {
|
|
342
|
+
throw new Error(
|
|
343
|
+
"Using the dynamic offline data API requires a logged in user."
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return id;
|
|
348
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import Dexie, { Table } from "dexie";
|
|
2
|
+
import { DynamicOfflineData } from "./dynamic-offline-data";
|
|
3
|
+
import { SyncItem } from "./sync";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Accesses the central IndexedDB used by the `esm-offline` module to persist offline related state.
|
|
7
|
+
* Leverages the `dexie` library for IndexedDB management.
|
|
8
|
+
*/
|
|
9
|
+
export class OfflineDb extends Dexie {
|
|
10
|
+
/**
|
|
11
|
+
* The table used to store the data of the offline synchronization queue (aka "sync queue" / "offline actions").
|
|
12
|
+
*/
|
|
13
|
+
syncQueue: Table<SyncItem, number>;
|
|
14
|
+
dynamicOfflineData: Table<DynamicOfflineData, number>;
|
|
15
|
+
|
|
16
|
+
constructor() {
|
|
17
|
+
super("EsmOffline");
|
|
18
|
+
|
|
19
|
+
this.version(4).stores({
|
|
20
|
+
syncQueue: "++id,userId,type,[userId+type]",
|
|
21
|
+
dynamicOfflineData: "++id,type,identifier,*users,&[type+identifier]",
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
this.syncQueue = this.table("syncQueue");
|
|
25
|
+
this.dynamicOfflineData = this.table("dynamicOfflineData");
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @internal Temporarily added for esm-offline-tools-app and workarounds. Please don't use elsewhere.
|
|
31
|
+
* @deprecated Should/Will be removed in the future per the above reason.
|
|
32
|
+
*/
|
|
33
|
+
export function getOfflineDb() {
|
|
34
|
+
return new OfflineDb();
|
|
35
|
+
}
|