@jcbuisson/express-x-client 3.1.13 → 3.1.14
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/package.json +1 -1
- package/src/client.mts +46 -23
package/package.json
CHANGED
package/src/client.mts
CHANGED
|
@@ -205,9 +205,9 @@ export function offlinePlugin(app) {
|
|
|
205
205
|
|
|
206
206
|
app.service(modelName).on('updateWithMeta', async ([value, meta]) => {
|
|
207
207
|
console.log(`${modelName} EVENT updateWithMeta`, value);
|
|
208
|
-
// value may be undefined when the server's
|
|
208
|
+
// value may be undefined when the server's update yielded 0 rows
|
|
209
209
|
// (concurrent delete race: record was removed between the sync's findMany
|
|
210
|
-
// snapshot and the actual
|
|
210
|
+
// snapshot and the actual update). Guard to avoid a TypeError crash that
|
|
211
211
|
// would prevent db.metadata.put(meta) from running.
|
|
212
212
|
if (value?.uid) await db.values.put(value);
|
|
213
213
|
await db.metadata.put(meta);
|
|
@@ -215,12 +215,12 @@ export function offlinePlugin(app) {
|
|
|
215
215
|
|
|
216
216
|
app.service(modelName).on('deleteWithMeta', async ([value, meta]) => {
|
|
217
217
|
console.log(`${modelName} EVENT deleteWithMeta`, value)
|
|
218
|
-
// value may be undefined when the server's
|
|
219
|
-
// (double-delete race).
|
|
218
|
+
// value may be undefined when the server's delete yielded 0 rows
|
|
219
|
+
// (double-delete race).
|
|
220
220
|
if (value?.uid) await db.values.delete(value.uid)
|
|
221
221
|
// delete, not put: synchronize() step 2 also deletes idbMetadata for the same
|
|
222
|
-
// uid.
|
|
223
|
-
// metadata row as a permanent orphan.
|
|
222
|
+
// uid. If the pub/sub handler fires AFTER step 2, put() would re-create the
|
|
223
|
+
// metadata row as a permanent orphan. delete() is idempotent regardless of order.
|
|
224
224
|
await db.metadata.delete(meta.uid)
|
|
225
225
|
});
|
|
226
226
|
|
|
@@ -237,12 +237,7 @@ export function offlinePlugin(app) {
|
|
|
237
237
|
// execute on server, asynchronously, if connection is active
|
|
238
238
|
if (app.isConnected) {
|
|
239
239
|
app.service(modelName).createWithMeta(uid, data, now)
|
|
240
|
-
.then(
|
|
241
|
-
const [value, meta] = Array.isArray(result) ? result : []
|
|
242
|
-
if (value?.uid) await db.values.put(value)
|
|
243
|
-
if (meta?.uid) await db.metadata.put({ ...meta, __dirty__: false })
|
|
244
|
-
else await db.metadata.update(uid, { __dirty__: false })
|
|
245
|
-
})
|
|
240
|
+
.then(result => applyCreateAcknowledgement(uid, now, result))
|
|
246
241
|
.catch(async err => {
|
|
247
242
|
console.log(`*** err sync ${modelName} create`, err)
|
|
248
243
|
// rollback
|
|
@@ -253,6 +248,38 @@ export function offlinePlugin(app) {
|
|
|
253
248
|
return await db.values.get(uid)
|
|
254
249
|
}
|
|
255
250
|
|
|
251
|
+
async function applyCreateAcknowledgement(uid, requestCreatedAt, result) {
|
|
252
|
+
const currentMetadata = await db.metadata.get(uid)
|
|
253
|
+
if (!currentMetadata || !sameTimestamp(currentMetadata.created_at, requestCreatedAt)) return
|
|
254
|
+
const [value, meta] = Array.isArray(result) ? result : []
|
|
255
|
+
if (value?.uid) await db.values.put(value)
|
|
256
|
+
if (meta?.uid)
|
|
257
|
+
await db.metadata.put({ ...meta, __dirty__: false })
|
|
258
|
+
else
|
|
259
|
+
await db.metadata.update(uid, { __dirty__: false })
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
async function applyUpdateAcknowledgement(uid, requestUpdatedAt, result) {
|
|
263
|
+
const currentMetadata = await db.metadata.get(uid)
|
|
264
|
+
if (!currentMetadata || !sameTimestamp(currentMetadata.updated_at, requestUpdatedAt)) return
|
|
265
|
+
const [value, meta] = Array.isArray(result) ? result : []
|
|
266
|
+
if (value?.uid) await db.values.put(value)
|
|
267
|
+
if (meta?.uid)
|
|
268
|
+
await db.metadata.put({ ...meta, __dirty__: false })
|
|
269
|
+
else
|
|
270
|
+
await db.metadata.update(uid, { __dirty__: false })
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
async function applyDeleteAcknowledgement(uid, requestDeletedAt, result) {
|
|
274
|
+
const currentMetadata = await db.metadata.get(uid)
|
|
275
|
+
if (!currentMetadata || !sameTimestamp(currentMetadata.deleted_at, requestDeletedAt)) return
|
|
276
|
+
const [, meta] = Array.isArray(result) ? result : []
|
|
277
|
+
if (meta?.uid)
|
|
278
|
+
await db.metadata.put({ ...meta, __dirty__: false })
|
|
279
|
+
else
|
|
280
|
+
await db.metadata.update(uid, { __dirty__: false })
|
|
281
|
+
}
|
|
282
|
+
|
|
256
283
|
const update = async (uid: string, data: object) => {
|
|
257
284
|
const previousValue = { ...(await db.values.get(uid)) }
|
|
258
285
|
const previousMetadata = { ...(await db.metadata.get(uid)) }
|
|
@@ -263,12 +290,7 @@ export function offlinePlugin(app) {
|
|
|
263
290
|
// execute on server, asynchronously, if connection is active
|
|
264
291
|
if (app.isConnected) {
|
|
265
292
|
app.service(modelName).updateWithMeta(uid, data, now)
|
|
266
|
-
.then(
|
|
267
|
-
const [value, meta] = Array.isArray(result) ? result : []
|
|
268
|
-
if (value?.uid) await db.values.put(value)
|
|
269
|
-
if (meta?.uid) await db.metadata.put({ ...meta, __dirty__: false })
|
|
270
|
-
else await db.metadata.update(uid, { __dirty__: false })
|
|
271
|
-
})
|
|
293
|
+
.then(result => applyUpdateAcknowledgement(uid, now, result))
|
|
272
294
|
.catch(async err => {
|
|
273
295
|
console.log(`*** err sync ${modelName} update`, err)
|
|
274
296
|
// rollback
|
|
@@ -295,11 +317,7 @@ export function offlinePlugin(app) {
|
|
|
295
317
|
// and in database, if connected
|
|
296
318
|
if (app.isConnected) {
|
|
297
319
|
app.service(modelName).deleteWithMeta(uid, deleted_at)
|
|
298
|
-
.then(
|
|
299
|
-
const [, meta] = Array.isArray(result) ? result : []
|
|
300
|
-
if (meta?.uid) await db.metadata.put({ ...meta, __dirty__: false })
|
|
301
|
-
else await db.metadata.update(uid, { __dirty__: false })
|
|
302
|
-
})
|
|
320
|
+
.then(result => applyDeleteAcknowledgement(uid, deleted_at, result))
|
|
303
321
|
.catch(async err => {
|
|
304
322
|
console.log(`*** err sync ${modelName} remove`, err)
|
|
305
323
|
// rollback
|
|
@@ -641,6 +659,11 @@ function stringifyWithSortedKeys(obj, space = null) {
|
|
|
641
659
|
}
|
|
642
660
|
// console.log('stringifyWithSortedKeys({ age: 30, name: "Alice", data: { city: "Paris", color: "red" }})', stringifyWithSortedKeys({ age: 30, name: "Alice", data: { city: "Paris", color: "red" } }))
|
|
643
661
|
|
|
662
|
+
function sameTimestamp(a, b) {
|
|
663
|
+
if (!a || !b) return a === b
|
|
664
|
+
return new Date(a).getTime() === new Date(b).getTime()
|
|
665
|
+
}
|
|
666
|
+
|
|
644
667
|
export class Mutex {
|
|
645
668
|
constructor() {
|
|
646
669
|
this.locked = false;
|