@jcbuisson/express-x-client 3.1.11 → 3.1.12
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/CLAUDE.md +2 -2
- package/package.json +1 -1
- package/src/client.mts +32 -8
package/CLAUDE.md
CHANGED
|
@@ -35,7 +35,7 @@ Enriches `app` with offline-first IndexedDB CRUD via Dexie. Call `app.createOffl
|
|
|
35
35
|
This plugin also adds three dynamic attributes to `app`:
|
|
36
36
|
- `app.isConnected` — boolean, updated on socket connect/disconnect
|
|
37
37
|
- `app.connectedDate` — `Date` of the last connection, or `null`
|
|
38
|
-
- `app.disconnectedDate` — `Date` of the last disconnection, or `null`
|
|
38
|
+
- `app.disconnectedDate` — `Date` of the last disconnection, or `null`
|
|
39
39
|
|
|
40
40
|
Each model maintains three Dexie stores under the same DB name:
|
|
41
41
|
- `values` — the actual records (indexed on `uid`, `__deleted__`)
|
|
@@ -47,7 +47,7 @@ Each model maintains three Dexie stores under the same DB name:
|
|
|
47
47
|
|
|
48
48
|
**Optimistic writes**: `create`, `update`, `remove` write to IndexedDB immediately, then call the server service method. On server error they roll back the local change.
|
|
49
49
|
|
|
50
|
-
**Sync on reconnect**: When the socket reconnects, every registered model calls `synchronizeAll`, which iterates its `whereList` and calls the server's `sync.go(modelName, where,
|
|
50
|
+
**Sync on reconnect**: When the socket reconnects, every registered model calls `synchronizeAll`, which iterates its `whereList` and calls the server's `sync.go(modelName, where, clientMetadataDict)` service. The response contains five buckets (`addClient`, `updateClient`, `deleteClient`, `addDatabase`, `updateDatabase`) that are applied in order.
|
|
51
51
|
|
|
52
52
|
**Real-time observables**: `getObservable(where)` returns an RxJS `Observable` backed by Dexie's `liveQuery`. It also registers the `where` in `whereList` and triggers a sync if it is a new, unregistered filter. Vue component lifecycle cleanup is handled by `tryOnScopeDispose`.
|
|
53
53
|
|
package/package.json
CHANGED
package/src/client.mts
CHANGED
|
@@ -184,6 +184,8 @@ export function offlinePlugin(app) {
|
|
|
184
184
|
|
|
185
185
|
const dbName = modelName;
|
|
186
186
|
const db = getOrCreateDB(dbName, fields);
|
|
187
|
+
const synchronizedWhereKeys = new Set();
|
|
188
|
+
const synchronizeWherePromises = new Map();
|
|
187
189
|
|
|
188
190
|
const reset = async () => {
|
|
189
191
|
console.log('reset', modelName);
|
|
@@ -322,8 +324,9 @@ export function offlinePlugin(app) {
|
|
|
322
324
|
// behavior as before.
|
|
323
325
|
return defer(() => {
|
|
324
326
|
const ready = addSynchroWhere(where).then((isNew: boolean) => {
|
|
325
|
-
|
|
326
|
-
|
|
327
|
+
const whereKey = stringifyWithSortedKeys(where)
|
|
328
|
+
if (app.isConnected && (isNew || !synchronizedWhereKeys.has(whereKey))) {
|
|
329
|
+
return synchronizeWhere(where)
|
|
327
330
|
}
|
|
328
331
|
})
|
|
329
332
|
return from(ready).pipe(switchMap(() => liveQuery$))
|
|
@@ -341,11 +344,27 @@ export function offlinePlugin(app) {
|
|
|
341
344
|
function removeSynchroWhere(where: object) {
|
|
342
345
|
console.log('removeSynchroWhere', dbName, modelName, where)
|
|
343
346
|
count -= 1
|
|
347
|
+
synchronizedWhereKeys.delete(stringifyWithSortedKeys(where))
|
|
344
348
|
return removeSynchroDBWhere(where, db.whereList)
|
|
345
349
|
}
|
|
346
350
|
|
|
347
351
|
async function synchronizeAll() {
|
|
348
|
-
await synchronizeModelWhereList(modelName, db.values, db.metadata,
|
|
352
|
+
await synchronizeModelWhereList(modelName, db.values, db.metadata, db.whereList, synchronizeWhere)
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
async function synchronizeWhere(where) {
|
|
356
|
+
const whereKey = stringifyWithSortedKeys(where)
|
|
357
|
+
if (!synchronizeWherePromises.has(whereKey)) {
|
|
358
|
+
const promise = synchronize(modelName, db.values, db.metadata, where)
|
|
359
|
+
.then(() => {
|
|
360
|
+
synchronizedWhereKeys.add(whereKey)
|
|
361
|
+
})
|
|
362
|
+
.finally(() => {
|
|
363
|
+
synchronizeWherePromises.delete(whereKey)
|
|
364
|
+
})
|
|
365
|
+
synchronizeWherePromises.set(whereKey, promise)
|
|
366
|
+
}
|
|
367
|
+
return synchronizeWherePromises.get(whereKey)
|
|
349
368
|
}
|
|
350
369
|
|
|
351
370
|
// Automatically clean up when the component using this composable unmounts
|
|
@@ -369,12 +388,16 @@ export function offlinePlugin(app) {
|
|
|
369
388
|
}
|
|
370
389
|
}
|
|
371
390
|
|
|
391
|
+
let hasConnected = false
|
|
392
|
+
|
|
372
393
|
app.addConnectListener(async (_socket) => {
|
|
373
394
|
app.connectedDate = new Date()
|
|
374
395
|
console.log('onConnect', app.connectedDate)
|
|
375
396
|
app.isConnected = true
|
|
376
397
|
const disconnectedDate = app.disconnectedDate
|
|
377
|
-
|
|
398
|
+
const isInitialConnect = !hasConnected
|
|
399
|
+
hasConnected = true
|
|
400
|
+
if (disconnectedDate || isInitialConnect) {
|
|
378
401
|
const results = await Promise.allSettled(modelSyncFunctions.map(sync => sync()))
|
|
379
402
|
const failures = results.filter(result => result.status === 'rejected')
|
|
380
403
|
if (failures.length > 0) {
|
|
@@ -396,7 +419,7 @@ export function offlinePlugin(app) {
|
|
|
396
419
|
const mutex = new Mutex()
|
|
397
420
|
|
|
398
421
|
// ex: where = { uid: 'azer' }
|
|
399
|
-
async function synchronize(modelName, idbValues, idbMetadata, where
|
|
422
|
+
async function synchronize(modelName, idbValues, idbMetadata, where) {
|
|
400
423
|
await mutex.acquire()
|
|
401
424
|
console.log('synchronize', modelName, where)
|
|
402
425
|
|
|
@@ -419,7 +442,7 @@ export function offlinePlugin(app) {
|
|
|
419
442
|
|
|
420
443
|
// call sync service on `where` perimeter
|
|
421
444
|
const { addClient, updateClient, deleteClient, addDatabase, updateDatabase } =
|
|
422
|
-
await app.service('sync').go(modelName, where,
|
|
445
|
+
await app.service('sync').go(modelName, where, clientMetadataDict)
|
|
423
446
|
console.log('-> service.sync', modelName, where, addClient, updateClient, deleteClient, addDatabase, updateDatabase)
|
|
424
447
|
|
|
425
448
|
// 1- add missing elements in indexedDB cache
|
|
@@ -550,10 +573,11 @@ export function offlinePlugin(app) {
|
|
|
550
573
|
}
|
|
551
574
|
}
|
|
552
575
|
|
|
553
|
-
async function synchronizeModelWhereList(modelName, idbValues, idbMetadata,
|
|
576
|
+
async function synchronizeModelWhereList(modelName, idbValues, idbMetadata, whereDb, syncWhere = null) {
|
|
554
577
|
const whereList = await getWhereList(whereDb)
|
|
555
578
|
for (const where of whereList) {
|
|
556
|
-
|
|
579
|
+
if (syncWhere) await syncWhere(where)
|
|
580
|
+
else await synchronize(modelName, idbValues, idbMetadata, where)
|
|
557
581
|
}
|
|
558
582
|
}
|
|
559
583
|
|