@anfenn/zync 0.1.22 → 0.2.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/README.md +19 -24
- package/dist/index.cjs +137 -61
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -3
- package/dist/index.d.ts +5 -3
- package/dist/index.js +137 -61
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,16 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@anfenn/zync)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Simple, bullet-proof, offline-first sync middleware for Zustand.
|
|
6
6
|
|
|
7
7
|
## Benefits
|
|
8
8
|
|
|
9
|
-
-
|
|
9
|
+
- Easy to sync non-nested array state with a backend (i.e. mirror remote database tables locally)
|
|
10
|
+
- **"It just works"** philosophy
|
|
11
|
+
- Batteries optionally included:
|
|
12
|
+
- IndexedDB helper (based on [idb](https://www.npmjs.com/package/idb))
|
|
10
13
|
- Uses the official persist middleware as the local storage (localStorage, IndexedDB, etc.)
|
|
11
14
|
- Zync's persistWithSync() is a drop-in replacement for Zustand's persist()
|
|
12
15
|
- Allows for idiomatic use of Zustand
|
|
13
16
|
- Leaves the api requests up to you (RESTful, GraphQL, etc.), just provide add(), update(), remove() and list()
|
|
14
|
-
- **_Coming soon_**: Customisable conflict resolution. Currently
|
|
17
|
+
- **_Coming soon_**: Customisable conflict resolution. Currently local-wins.
|
|
15
18
|
|
|
16
19
|
## Requirements
|
|
17
20
|
|
|
@@ -30,7 +33,7 @@ npm install @anfenn/zync
|
|
|
30
33
|
### Zustand store creation (store.ts):
|
|
31
34
|
|
|
32
35
|
```ts
|
|
33
|
-
import {
|
|
36
|
+
import { type UseStoreWithSync, persistWithSync } from '@anfenn/zync';
|
|
34
37
|
import { create } from 'zustand';
|
|
35
38
|
import { createJSONStorage } from 'zustand/middleware';
|
|
36
39
|
import { useShallow } from 'zustand/react/shallow';
|
|
@@ -45,32 +48,25 @@ type Store = {
|
|
|
45
48
|
|
|
46
49
|
export const useStore = create<any>()(
|
|
47
50
|
persistWithSync<Store>(
|
|
48
|
-
(set, get,
|
|
49
|
-
// Standard Zustand state and mutation functions with new
|
|
51
|
+
(set, get, setAndSync) => ({
|
|
52
|
+
// Standard Zustand state and mutation functions with new setAndSync()
|
|
50
53
|
|
|
51
54
|
facts: [],
|
|
52
55
|
addFact: (item: Fact) => {
|
|
53
56
|
const updated_at = new Date().toISOString();
|
|
54
57
|
const newItem = { ...item, created_at: updated_at, updated_at };
|
|
55
58
|
|
|
56
|
-
|
|
59
|
+
setAndSync((state: Store) => ({
|
|
57
60
|
facts: [...state.facts, newItem],
|
|
58
61
|
}));
|
|
59
|
-
|
|
60
|
-
// Never call queueToSync() inside Zustand set() due to itself calling set(), so may cause lost state changes
|
|
61
|
-
queueToSync(SyncAction.CreateOrUpdate, 'facts', item._localId);
|
|
62
62
|
},
|
|
63
63
|
updateFact: (localId: string, changes: Partial<Fact>) => {
|
|
64
|
-
|
|
64
|
+
setAndSync((state: Store) => ({
|
|
65
65
|
facts: state.facts.map((item) => (item._localId === localId ? { ...item, ...changes } : item)),
|
|
66
66
|
}));
|
|
67
|
-
|
|
68
|
-
queueToSync(SyncAction.CreateOrUpdate, 'facts', localId);
|
|
69
67
|
},
|
|
70
68
|
removeFact: (localId: string) => {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
set((state: Store) => ({
|
|
69
|
+
setAndSync((state: Store) => ({
|
|
74
70
|
facts: state.facts.filter((item) => item._localId !== localId),
|
|
75
71
|
}));
|
|
76
72
|
},
|
|
@@ -107,6 +103,9 @@ export const useFacts = () =>
|
|
|
107
103
|
);
|
|
108
104
|
```
|
|
109
105
|
|
|
106
|
+
**_NOTE_**: Zync uses an internal timer (setInterval) to sync, so it's advised to just have one store. You could have multiple, with different store names (see Zustand persist options above), but if both stores use Zync, although it would work fine, it wouldn't offer much advantage. If one store becomes large with many state keys and functions, then you could separate them into multiple files and import than with object spreading
|
|
107
|
+
`e.g. {...storeState1, ...storeState2}`
|
|
108
|
+
|
|
110
109
|
### In your component:
|
|
111
110
|
|
|
112
111
|
```ts
|
|
@@ -242,23 +241,19 @@ async function firstLoad(lastId: any) {
|
|
|
242
241
|
|
|
243
242
|
## Optional IndexedDB storage
|
|
244
243
|
|
|
245
|
-
|
|
244
|
+
Using async IndexedDB over sync localStorage gives the advantage of a responsive UI when reading/writing a very large store, as IndexedDB is running in it's own thread.
|
|
246
245
|
|
|
247
246
|
If you want to use the bundled `createIndexedDBStorage()` helper, install `idb` in your project. It's intentionally optional so projects that don't use IndexedDB won't pull the dependency into their bundles.
|
|
248
247
|
|
|
249
|
-
|
|
248
|
+
[idb](https://www.npmjs.com/package/idb) is an extremely popular and lightweight wrapper to simplify IndexedDB's verbose events based api into a simple Promise based one. It also handles the inconsistencies found when running in different browsers.
|
|
250
249
|
|
|
251
250
|
```bash
|
|
252
251
|
npm install idb
|
|
253
252
|
```
|
|
254
253
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
```bash
|
|
258
|
-
npm install --save-optional idb
|
|
259
|
-
```
|
|
254
|
+
When using IndexedDB Zustand saves the whole store under one key, which means indexes cannot be used to accelerate querying. However, if this becomes a performance issue due to the size of the store, then libraries like dexie.js instead of Zustand would be a better solution and provide the syntax for high performance queries.
|
|
260
255
|
|
|
261
|
-
|
|
256
|
+
From testing I've found Zustand and Zync are lightening fast even with 100,000 average sized state objects.
|
|
262
257
|
|
|
263
258
|
## Community
|
|
264
259
|
|
package/dist/index.cjs
CHANGED
|
@@ -349,9 +349,42 @@ function findApi(stateKey, syncApi) {
|
|
|
349
349
|
}
|
|
350
350
|
return api;
|
|
351
351
|
}
|
|
352
|
+
function findChanges(current, updated) {
|
|
353
|
+
const currentMap = /* @__PURE__ */ new Map();
|
|
354
|
+
for (const item of current) {
|
|
355
|
+
if (item && item._localId) {
|
|
356
|
+
currentMap.set(item._localId, item);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
const changesMap = /* @__PURE__ */ new Map();
|
|
360
|
+
for (const item of updated) {
|
|
361
|
+
if (item && item._localId) {
|
|
362
|
+
const curr = currentMap.get(item._localId);
|
|
363
|
+
if (curr) {
|
|
364
|
+
const diff = {};
|
|
365
|
+
for (const key in curr) {
|
|
366
|
+
if (key !== "_localId" && curr[key] !== item[key]) {
|
|
367
|
+
diff[key] = item[key];
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
if (Object.keys(diff).length > 0) {
|
|
371
|
+
changesMap.set(item._localId, { currentItem: curr, updatedItem: item, changes: diff });
|
|
372
|
+
}
|
|
373
|
+
} else {
|
|
374
|
+
changesMap.set(item._localId, { currentItem: null, updatedItem: item, changes: item });
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
for (const [localId, curr] of currentMap) {
|
|
379
|
+
if (!updated.some((u) => u && u._localId === localId)) {
|
|
380
|
+
changesMap.set(localId, { currentItem: curr, updatedItem: null, changes: null });
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
return changesMap;
|
|
384
|
+
}
|
|
352
385
|
|
|
353
386
|
// src/pull.ts
|
|
354
|
-
async function pull(set, get, stateKey, api, logger) {
|
|
387
|
+
async function pull(set, get, stateKey, api, logger, conflictResolutionStrategy) {
|
|
355
388
|
const lastPulled = get().syncState.lastPulled || {};
|
|
356
389
|
const lastPulledAt = new Date(lastPulled[stateKey] || /* @__PURE__ */ new Date(0));
|
|
357
390
|
logger.debug(`[zync] pull:start stateKey=${stateKey} since=${lastPulledAt.toISOString()}`);
|
|
@@ -381,15 +414,45 @@ async function pull(set, get, stateKey, api, logger) {
|
|
|
381
414
|
}
|
|
382
415
|
delete remote.deleted;
|
|
383
416
|
const pending = localItem && pendingChanges.some((p) => p.stateKey === stateKey && p.localId === localItem._localId);
|
|
384
|
-
if (localItem
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
417
|
+
if (localItem) {
|
|
418
|
+
if (pending) {
|
|
419
|
+
switch (conflictResolutionStrategy) {
|
|
420
|
+
case "local-wins":
|
|
421
|
+
logger.debug(`[zync] pull:conflict-strategy:${conflictResolutionStrategy} stateKey=${stateKey} id=${remote.id}`);
|
|
422
|
+
break;
|
|
423
|
+
case "remote-wins": {
|
|
424
|
+
const merged = {
|
|
425
|
+
...remote,
|
|
426
|
+
_localId: localItem._localId
|
|
427
|
+
};
|
|
428
|
+
nextItems = nextItems.map((i) => i._localId === localItem._localId ? merged : i);
|
|
429
|
+
logger.debug(`[zync] pull:conflict-strategy:${conflictResolutionStrategy} stateKey=${stateKey} id=${remote.id}`);
|
|
430
|
+
break;
|
|
431
|
+
}
|
|
432
|
+
// case 'try-shallow-merge':
|
|
433
|
+
// // Try and merge all fields, fail if not possible due to conflicts
|
|
434
|
+
// // throw new ConflictError('Details...');
|
|
435
|
+
// break;
|
|
436
|
+
// case 'custom':
|
|
437
|
+
// // Hook to allow custom userland logic
|
|
438
|
+
// // const error = onConflict(localItem, remote, stateKey, pending);
|
|
439
|
+
// // logger.debug(`[zync] pull:conflict-strategy:${conflictResolutionStrategy} stateKey=${stateKey} id=${remote.id} error=${error}`);
|
|
440
|
+
// // if (error) throw new ConflictError(error);
|
|
441
|
+
// break;
|
|
442
|
+
default:
|
|
443
|
+
logger.error(`[zync] pull:conflict-strategy:unknown stateKey=${stateKey} id=${remote.id} strategy=${conflictResolutionStrategy}`);
|
|
444
|
+
break;
|
|
445
|
+
}
|
|
446
|
+
} else {
|
|
447
|
+
const merged = {
|
|
448
|
+
...localItem,
|
|
449
|
+
...remote,
|
|
450
|
+
_localId: localItem._localId
|
|
451
|
+
};
|
|
452
|
+
nextItems = nextItems.map((i) => i._localId === localItem._localId ? merged : i);
|
|
453
|
+
logger.debug(`[zync] pull:merge stateKey=${stateKey} id=${remote.id}`);
|
|
454
|
+
}
|
|
455
|
+
} else {
|
|
393
456
|
nextItems = [...nextItems, { ...remote, _localId: nextLocalId() }];
|
|
394
457
|
logger.debug(`[zync] pull:add stateKey=${stateKey} id=${remote.id}`);
|
|
395
458
|
}
|
|
@@ -409,7 +472,7 @@ async function pull(set, get, stateKey, api, logger) {
|
|
|
409
472
|
|
|
410
473
|
// src/push.ts
|
|
411
474
|
var SYNC_FIELDS = ["id", "_localId", "updated_at", "deleted"];
|
|
412
|
-
async function pushOne(set, get, change, api, logger,
|
|
475
|
+
async function pushOne(set, get, change, api, logger, setAndQueueToSync, missingStrategy, onMissingRemoteRecordDuringUpdate, onAfterRemoteAdd) {
|
|
413
476
|
logger.debug(`[zync] push:attempt action=${change.action} stateKey=${change.stateKey} localId=${change.localId}`);
|
|
414
477
|
const { action, stateKey, localId, id, version } = change;
|
|
415
478
|
switch (action) {
|
|
@@ -449,26 +512,27 @@ async function pushOne(set, get, change, api, logger, queueToSync, missingStrate
|
|
|
449
512
|
}
|
|
450
513
|
return;
|
|
451
514
|
} else {
|
|
452
|
-
logger.warn("[zync] push:update:missing-remote", {
|
|
453
|
-
stateKey,
|
|
454
|
-
localId,
|
|
455
|
-
id: item.id
|
|
456
|
-
});
|
|
457
515
|
switch (missingStrategy) {
|
|
458
516
|
case "delete-local-record":
|
|
459
517
|
set((s) => ({
|
|
460
518
|
[stateKey]: (s[stateKey] || []).filter((i) => i._localId !== localId)
|
|
461
519
|
}));
|
|
520
|
+
logger.debug(`[zync] push:missing-remote:${missingStrategy} stateKey=${stateKey} id=${item.id}`);
|
|
462
521
|
break;
|
|
463
|
-
case "insert-remote-record":
|
|
522
|
+
case "insert-remote-record":
|
|
464
523
|
omittedItem._localId = crypto.randomUUID();
|
|
465
524
|
omittedItem.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
466
|
-
|
|
525
|
+
setAndQueueToSync((s) => ({
|
|
467
526
|
[stateKey]: (s[stateKey] || []).map((i) => i._localId === localId ? omittedItem : i)
|
|
468
527
|
}));
|
|
469
|
-
|
|
528
|
+
logger.debug(`[zync] push:missing-remote:${missingStrategy} stateKey=${stateKey} id=${item.id}`);
|
|
529
|
+
break;
|
|
530
|
+
case "ignore":
|
|
531
|
+
logger.debug(`[zync] push:missing-remote:${missingStrategy} stateKey=${stateKey} id=${item.id}`);
|
|
532
|
+
break;
|
|
533
|
+
default:
|
|
534
|
+
logger.error(`[zync] push:missing-remote:unknown stateKey=${stateKey} id=${item.id} strategy=${missingStrategy}`);
|
|
470
535
|
break;
|
|
471
|
-
}
|
|
472
536
|
}
|
|
473
537
|
removeFromPendingChanges(set, localId, stateKey);
|
|
474
538
|
onMissingRemoteRecordDuringUpdate?.(missingStrategy, item, omittedItem._localId);
|
|
@@ -488,7 +552,7 @@ async function pushOne(set, get, change, api, logger, queueToSync, missingStrate
|
|
|
488
552
|
if (samePendingVersion(get, stateKey, localId, version)) {
|
|
489
553
|
removeFromPendingChanges(set, localId, stateKey);
|
|
490
554
|
}
|
|
491
|
-
onAfterRemoteAdd?.(set, get,
|
|
555
|
+
onAfterRemoteAdd?.(set, get, setAndQueueToSync, stateKey, {
|
|
492
556
|
...item,
|
|
493
557
|
...result
|
|
494
558
|
});
|
|
@@ -588,6 +652,7 @@ var DEFAULT_SYNC_INTERVAL_MILLIS = 5e3;
|
|
|
588
652
|
var DEFAULT_LOGGER = console;
|
|
589
653
|
var DEFAULT_MIN_LOG_LEVEL = "debug";
|
|
590
654
|
var DEFAULT_MISSING_REMOTE_RECORD_STRATEGY = "ignore";
|
|
655
|
+
var DEFAULT_CONFLICT_RESOLUTION_STRATEGY = "local-wins";
|
|
591
656
|
function createWithSync(stateCreator, persistOptions, syncApi, syncOptions = {}) {
|
|
592
657
|
const store = (0, import_zustand.create)(persistWithSync(stateCreator, persistOptions, syncApi, syncOptions));
|
|
593
658
|
return new Promise((resolve) => {
|
|
@@ -599,6 +664,7 @@ function createWithSync(stateCreator, persistOptions, syncApi, syncOptions = {})
|
|
|
599
664
|
function persistWithSync(stateCreator, persistOptions, syncApi, syncOptions = {}) {
|
|
600
665
|
const syncInterval = syncOptions.syncInterval ?? DEFAULT_SYNC_INTERVAL_MILLIS;
|
|
601
666
|
const missingStrategy = syncOptions.missingRemoteRecordDuringUpdateStrategy ?? DEFAULT_MISSING_REMOTE_RECORD_STRATEGY;
|
|
667
|
+
const conflictResolutionStrategy = syncOptions.conflictResolutionStrategy ?? DEFAULT_CONFLICT_RESOLUTION_STRATEGY;
|
|
602
668
|
const logger = newLogger(syncOptions.logger ?? DEFAULT_LOGGER, syncOptions.minLogLevel ?? DEFAULT_MIN_LOG_LEVEL);
|
|
603
669
|
const baseOnRehydrate = persistOptions?.onRehydrateStorage;
|
|
604
670
|
const basePartialize = persistOptions?.partialize;
|
|
@@ -654,15 +720,15 @@ function persistWithSync(stateCreator, persistOptions, syncApi, syncOptions = {}
|
|
|
654
720
|
for (const stateKey of Object.keys(syncApi)) {
|
|
655
721
|
try {
|
|
656
722
|
const api = findApi(stateKey, syncApi);
|
|
657
|
-
await pull(set, get, stateKey, api, logger);
|
|
723
|
+
await pull(set, get, stateKey, api, logger, conflictResolutionStrategy);
|
|
658
724
|
} catch (err) {
|
|
659
725
|
syncError = syncError ?? err;
|
|
660
726
|
logger.error(`[zync] pull:error stateKey=${stateKey}`, err);
|
|
661
727
|
}
|
|
662
728
|
}
|
|
663
|
-
const
|
|
664
|
-
|
|
665
|
-
for (const change of
|
|
729
|
+
const changesSnapshot = [...get().syncState.pendingChanges || []];
|
|
730
|
+
changesSnapshot.sort((a, b) => orderFor(a.action) - orderFor(b.action));
|
|
731
|
+
for (const change of changesSnapshot) {
|
|
666
732
|
try {
|
|
667
733
|
const api = findApi(change.stateKey, syncApi);
|
|
668
734
|
await pushOne(
|
|
@@ -671,7 +737,7 @@ function persistWithSync(stateCreator, persistOptions, syncApi, syncOptions = {}
|
|
|
671
737
|
change,
|
|
672
738
|
api,
|
|
673
739
|
logger,
|
|
674
|
-
|
|
740
|
+
setAndQueueToSync,
|
|
675
741
|
missingStrategy,
|
|
676
742
|
syncOptions.onMissingRemoteRecordDuringUpdate,
|
|
677
743
|
syncOptions.onAfterRemoteAdd
|
|
@@ -755,40 +821,7 @@ function persistWithSync(stateCreator, persistOptions, syncApi, syncOptions = {}
|
|
|
755
821
|
}
|
|
756
822
|
}));
|
|
757
823
|
}
|
|
758
|
-
function
|
|
759
|
-
set((state) => {
|
|
760
|
-
const pendingChanges = state.syncState.pendingChanges || [];
|
|
761
|
-
for (const localId of localIds) {
|
|
762
|
-
const item = state[stateKey].find((i) => i._localId === localId);
|
|
763
|
-
if (!item) {
|
|
764
|
-
logger.error(`[zync] queueToSync:no-local-item localId=${localId}`);
|
|
765
|
-
continue;
|
|
766
|
-
}
|
|
767
|
-
const queueItem = pendingChanges.find((p) => p.localId === localId && p.stateKey === stateKey);
|
|
768
|
-
if (queueItem) {
|
|
769
|
-
queueItem.version += 1;
|
|
770
|
-
if (queueItem.action === "create-or-update" /* CreateOrUpdate */ && action === "remove" /* Remove */ && item.id) {
|
|
771
|
-
queueItem.action = "remove" /* Remove */;
|
|
772
|
-
queueItem.id = item.id;
|
|
773
|
-
logger.debug(`[zync] queueToSync:changed-to-remove action=${action} localId=${localId} v=${queueItem.version}`);
|
|
774
|
-
} else {
|
|
775
|
-
logger.debug(`[zync] queueToSync:re-queued action=${action} localId=${localId} v=${queueItem.version}`);
|
|
776
|
-
}
|
|
777
|
-
} else {
|
|
778
|
-
pendingChanges.push({ action, stateKey, localId, id: item.id, version: 1 });
|
|
779
|
-
logger.debug(`[zync] queueToSync:added action=${action} localId=${localId}`);
|
|
780
|
-
}
|
|
781
|
-
}
|
|
782
|
-
return {
|
|
783
|
-
syncState: {
|
|
784
|
-
...state.syncState || {},
|
|
785
|
-
pendingChanges
|
|
786
|
-
}
|
|
787
|
-
};
|
|
788
|
-
});
|
|
789
|
-
syncOnce();
|
|
790
|
-
}
|
|
791
|
-
function setAndSync(partial) {
|
|
824
|
+
function setAndSyncOnce(partial) {
|
|
792
825
|
if (typeof partial === "function") {
|
|
793
826
|
set((state) => ({ ...partial(state) }));
|
|
794
827
|
} else {
|
|
@@ -796,6 +829,49 @@ function persistWithSync(stateCreator, persistOptions, syncApi, syncOptions = {}
|
|
|
796
829
|
}
|
|
797
830
|
syncOnce();
|
|
798
831
|
}
|
|
832
|
+
function setAndQueueToSync(partial) {
|
|
833
|
+
if (typeof partial === "function") {
|
|
834
|
+
set((state) => newSyncState(state, partial(state)));
|
|
835
|
+
} else {
|
|
836
|
+
set((state) => newSyncState(state, partial));
|
|
837
|
+
}
|
|
838
|
+
syncOnce();
|
|
839
|
+
}
|
|
840
|
+
function newSyncState(state, partial) {
|
|
841
|
+
const pendingChanges = state.syncState.pendingChanges || [];
|
|
842
|
+
Object.keys(partial).map((stateKey) => {
|
|
843
|
+
const current = state[stateKey];
|
|
844
|
+
const updated = partial[stateKey];
|
|
845
|
+
const changes = findChanges(current, updated);
|
|
846
|
+
addToPendingChanges(pendingChanges, stateKey, changes);
|
|
847
|
+
});
|
|
848
|
+
return {
|
|
849
|
+
...partial,
|
|
850
|
+
syncState: {
|
|
851
|
+
...state.syncState || {},
|
|
852
|
+
pendingChanges
|
|
853
|
+
}
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
function addToPendingChanges(pendingChanges, stateKey, changes) {
|
|
857
|
+
for (const [localId, change] of changes) {
|
|
858
|
+
const action = change.updatedItem === null ? "remove" /* Remove */ : "create-or-update" /* CreateOrUpdate */;
|
|
859
|
+
const queueItem = pendingChanges.find((p) => p.localId === localId && p.stateKey === stateKey);
|
|
860
|
+
if (queueItem) {
|
|
861
|
+
queueItem.version += 1;
|
|
862
|
+
if (queueItem.action === "create-or-update" /* CreateOrUpdate */ && action === "remove" /* Remove */ && change.currentItem.id) {
|
|
863
|
+
queueItem.action = "remove" /* Remove */;
|
|
864
|
+
queueItem.id = change.currentItem.id;
|
|
865
|
+
logger.debug(`[zync] addToPendingChanges:changed-to-remove action=${action} localId=${localId} v=${queueItem.version}`);
|
|
866
|
+
} else {
|
|
867
|
+
logger.debug(`[zync] addToPendingChanges:re-queued action=${action} localId=${localId} v=${queueItem.version}`);
|
|
868
|
+
}
|
|
869
|
+
} else {
|
|
870
|
+
pendingChanges.push({ action, stateKey, localId, id: change.currentItem?.id, version: 1 });
|
|
871
|
+
logger.debug(`[zync] addToPendingChanges:added action=${action} localId=${localId}`);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
}
|
|
799
875
|
function enable(enabled) {
|
|
800
876
|
set((state) => ({
|
|
801
877
|
syncState: {
|
|
@@ -834,7 +910,7 @@ function persistWithSync(stateCreator, persistOptions, syncApi, syncOptions = {}
|
|
|
834
910
|
enable,
|
|
835
911
|
startFirstLoad
|
|
836
912
|
};
|
|
837
|
-
const userState = stateCreator(
|
|
913
|
+
const userState = stateCreator(setAndSyncOnce, get, setAndQueueToSync);
|
|
838
914
|
return {
|
|
839
915
|
...userState,
|
|
840
916
|
syncState: {
|