@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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/client.mts +46 -23
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jcbuisson/express-x-client",
3
- "version": "3.1.13",
3
+ "version": "3.1.14",
4
4
  "type": "module",
5
5
  "description": "Client library for ExpressX framework",
6
6
  "main": "src/client.mts",
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 UPDATE RETURNING yielded 0 rows
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 UPDATE). Guard to avoid a TypeError crash that
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 DELETE RETURNING yielded 0 rows
219
- // (double-delete race). Guard before accessing .uid.
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. 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.
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(async result => {
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(async result => {
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(async result => {
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;