@jcbuisson/express-x-client 3.1.14 → 3.1.15

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 +30 -5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jcbuisson/express-x-client",
3
- "version": "3.1.14",
3
+ "version": "3.1.15",
4
4
  "type": "module",
5
5
  "description": "Client library for ExpressX framework",
6
6
  "main": "src/client.mts",
package/src/client.mts CHANGED
@@ -199,8 +199,9 @@ export function offlinePlugin(app) {
199
199
 
200
200
  app.service(modelName).on('createWithMeta', async ([value, meta]) => {
201
201
  console.log(`${modelName} EVENT createWithMeta`, value);
202
- await db.values.put(value);
203
- await db.metadata.put(meta);
202
+ if (await isIncomingEventStale(value?.uid ?? meta?.uid, meta)) return
203
+ if (value?.uid) await db.values.put(value);
204
+ if (meta?.uid) await db.metadata.put({ ...meta, __dirty__: false });
204
205
  });
205
206
 
206
207
  app.service(modelName).on('updateWithMeta', async ([value, meta]) => {
@@ -209,21 +210,31 @@ export function offlinePlugin(app) {
209
210
  // (concurrent delete race: record was removed between the sync's findMany
210
211
  // snapshot and the actual update). Guard to avoid a TypeError crash that
211
212
  // would prevent db.metadata.put(meta) from running.
213
+ if (await isIncomingEventStale(value?.uid ?? meta?.uid, meta)) return
212
214
  if (value?.uid) await db.values.put(value);
213
- await db.metadata.put(meta);
215
+ if (meta?.uid) await db.metadata.put({ ...meta, __dirty__: false });
214
216
  });
215
217
 
216
218
  app.service(modelName).on('deleteWithMeta', async ([value, meta]) => {
217
219
  console.log(`${modelName} EVENT deleteWithMeta`, value)
218
220
  // value may be undefined when the server's delete yielded 0 rows
219
221
  // (double-delete race).
220
- if (value?.uid) await db.values.delete(value.uid)
221
222
  // delete, not put: synchronize() step 2 also deletes idbMetadata for the same
222
223
  // uid. If the pub/sub handler fires AFTER step 2, put() would re-create the
223
224
  // metadata row as a permanent orphan. delete() is idempotent regardless of order.
224
- await db.metadata.delete(meta.uid)
225
+ const uid = value?.uid ?? meta?.uid
226
+ if (await isIncomingEventStale(uid, meta)) return
227
+ if (value?.uid) await db.values.delete(value.uid)
228
+ if (uid) await db.metadata.delete(uid)
225
229
  });
226
230
 
231
+ async function isIncomingEventStale(uid, incomingMeta) {
232
+ if (!uid || !incomingMeta) return false
233
+ const currentMeta = await db.metadata.get(uid)
234
+ if (!currentMeta) return false
235
+ return compareMetadataTime(currentMeta, incomingMeta) > 0
236
+ }
237
+
227
238
 
228
239
  ///////////// CREATE/UPDATE/REMOVE /////////////
229
240
 
@@ -664,6 +675,20 @@ function sameTimestamp(a, b) {
664
675
  return new Date(a).getTime() === new Date(b).getTime()
665
676
  }
666
677
 
678
+ function compareMetadataTime(a, b) {
679
+ const aTime = metadataTime(a)
680
+ const bTime = metadataTime(b)
681
+ if (aTime == null || bTime == null) return 0
682
+ return aTime - bTime
683
+ }
684
+
685
+ function metadataTime(meta) {
686
+ const value = meta?.deleted_at ?? meta?.updated_at ?? meta?.created_at
687
+ if (!value) return null
688
+ const time = new Date(value).getTime()
689
+ return Number.isNaN(time) ? null : time
690
+ }
691
+
667
692
  export class Mutex {
668
693
  constructor() {
669
694
  this.locked = false;