@anfenn/zync 0.2.1 → 0.3.1
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 +8 -3
- package/dist/index.cjs +178 -174
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +178 -174
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -22,20 +22,23 @@ function newLogger(base, min) {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
// src/helpers.ts
|
|
25
|
+
var SYNC_FIELDS = ["_localId", "updated_at", "deleted"];
|
|
25
26
|
function nextLocalId() {
|
|
26
27
|
return crypto.randomUUID();
|
|
27
28
|
}
|
|
28
29
|
function orderFor(a) {
|
|
29
30
|
switch (a) {
|
|
30
|
-
case "create
|
|
31
|
+
case "create" /* Create */:
|
|
31
32
|
return 1;
|
|
32
|
-
case "
|
|
33
|
+
case "update" /* Update */:
|
|
33
34
|
return 2;
|
|
35
|
+
case "remove" /* Remove */:
|
|
36
|
+
return 3;
|
|
34
37
|
}
|
|
35
38
|
}
|
|
36
|
-
function omitSyncFields(item
|
|
39
|
+
function omitSyncFields(item) {
|
|
37
40
|
const result = { ...item };
|
|
38
|
-
for (const k of
|
|
41
|
+
for (const k of SYNC_FIELDS) delete result[k];
|
|
39
42
|
return result;
|
|
40
43
|
}
|
|
41
44
|
function samePendingVersion(get, stateKey, localId, version) {
|
|
@@ -54,6 +57,38 @@ function removeFromPendingChanges(set, localId, stateKey) {
|
|
|
54
57
|
};
|
|
55
58
|
});
|
|
56
59
|
}
|
|
60
|
+
function tryAddToPendingChanges(pendingChanges, stateKey, changes) {
|
|
61
|
+
for (const [localId, change] of changes) {
|
|
62
|
+
let omittedItem = omitSyncFields(change.changes);
|
|
63
|
+
const queueItem = pendingChanges.find((p) => p.localId === localId && p.stateKey === stateKey);
|
|
64
|
+
const hasChanges = Object.keys(omittedItem).length > 0;
|
|
65
|
+
const action = change.updatedItem === null ? "remove" /* Remove */ : change.currentItem === null ? "create" /* Create */ : "update" /* Update */;
|
|
66
|
+
if (action === "update" /* Update */ && change.updatedItem && change.currentItem && change.currentItem._localId !== change.updatedItem._localId) {
|
|
67
|
+
omittedItem = omitSyncFields(change.updatedItem);
|
|
68
|
+
}
|
|
69
|
+
if (queueItem) {
|
|
70
|
+
if (queueItem.action === "remove" /* Remove */) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
queueItem.version += 1;
|
|
74
|
+
if (action === "remove" /* Remove */) {
|
|
75
|
+
queueItem.action = "remove" /* Remove */;
|
|
76
|
+
} else if (hasChanges) {
|
|
77
|
+
queueItem.changes = { ...queueItem.changes, ...omittedItem };
|
|
78
|
+
}
|
|
79
|
+
} else if (action === "remove" /* Remove */ || hasChanges) {
|
|
80
|
+
pendingChanges.push({ action, stateKey, localId, id: change.id, version: 1, changes: omittedItem });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function setPendingChangeToUpdate(get, stateKey, localId, id) {
|
|
85
|
+
const pendingChanges = get().syncState.pendingChanges || [];
|
|
86
|
+
const change = pendingChanges.find((p) => p.stateKey === stateKey && p.localId === localId);
|
|
87
|
+
if (change) {
|
|
88
|
+
change.action = "update" /* Update */;
|
|
89
|
+
if (id) change.id = id;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
57
92
|
function findApi(stateKey, syncApi) {
|
|
58
93
|
const api = syncApi[stateKey];
|
|
59
94
|
if (!api || !api.add || !api.update || !api.remove || !api.list || !api.firstLoad) {
|
|
@@ -65,11 +100,12 @@ function findChanges(current, updated) {
|
|
|
65
100
|
const currentMap = /* @__PURE__ */ new Map();
|
|
66
101
|
for (const item of current) {
|
|
67
102
|
if (item && item._localId) {
|
|
68
|
-
currentMap.set(item._localId, item);
|
|
103
|
+
currentMap.set(item._localId, { ...item });
|
|
69
104
|
}
|
|
70
105
|
}
|
|
71
106
|
const changesMap = /* @__PURE__ */ new Map();
|
|
72
|
-
for (const
|
|
107
|
+
for (const update of updated) {
|
|
108
|
+
const item = { ...update };
|
|
73
109
|
if (item && item._localId) {
|
|
74
110
|
const curr = currentMap.get(item._localId);
|
|
75
111
|
if (curr) {
|
|
@@ -80,16 +116,16 @@ function findChanges(current, updated) {
|
|
|
80
116
|
}
|
|
81
117
|
}
|
|
82
118
|
if (Object.keys(diff).length > 0) {
|
|
83
|
-
changesMap.set(item._localId, { currentItem: curr, updatedItem: item, changes: diff });
|
|
119
|
+
changesMap.set(item._localId, { currentItem: curr, updatedItem: item, changes: diff, id: curr.id ?? item.id });
|
|
84
120
|
}
|
|
85
121
|
} else {
|
|
86
|
-
changesMap.set(item._localId, { currentItem: null, updatedItem: item, changes: item });
|
|
122
|
+
changesMap.set(item._localId, { currentItem: null, updatedItem: item, changes: item, id: item.id });
|
|
87
123
|
}
|
|
88
124
|
}
|
|
89
125
|
}
|
|
90
126
|
for (const [localId, curr] of currentMap) {
|
|
91
127
|
if (!updated.some((u) => u && u._localId === localId)) {
|
|
92
|
-
changesMap.set(localId, { currentItem: curr, updatedItem: null, changes: null });
|
|
128
|
+
changesMap.set(localId, { currentItem: curr, updatedItem: null, changes: null, id: curr.id });
|
|
93
129
|
}
|
|
94
130
|
}
|
|
95
131
|
return changesMap;
|
|
@@ -144,13 +180,6 @@ async function pull(set, get, stateKey, api, logger, conflictResolutionStrategy)
|
|
|
144
180
|
// case 'try-shallow-merge':
|
|
145
181
|
// // Try and merge all fields, fail if not possible due to conflicts
|
|
146
182
|
// // throw new ConflictError('Details...');
|
|
147
|
-
// Object.entries(pendingChange.changes || {}).map(([key, value]) => {
|
|
148
|
-
// const localValue = localItem[key];
|
|
149
|
-
// const remoteValue = remote[key];
|
|
150
|
-
// if (localValue !== undefined && localValue !== value) {
|
|
151
|
-
// //throw new ConflictError(`Conflict on ${key}: local=${localValue} remote=${value}`);
|
|
152
|
-
// }
|
|
153
|
-
// });
|
|
154
183
|
// break;
|
|
155
184
|
// case 'custom':
|
|
156
185
|
// // Hook to allow custom userland logic
|
|
@@ -165,8 +194,7 @@ async function pull(set, get, stateKey, api, logger, conflictResolutionStrategy)
|
|
|
165
194
|
} else {
|
|
166
195
|
const merged = {
|
|
167
196
|
...localItem,
|
|
168
|
-
...remote
|
|
169
|
-
_localId: localItem._localId
|
|
197
|
+
...remote
|
|
170
198
|
};
|
|
171
199
|
nextItems = nextItems.map((i) => i._localId === localItem._localId ? merged : i);
|
|
172
200
|
logger.debug(`[zync] pull:merge-remote stateKey=${stateKey} id=${remote.id}`);
|
|
@@ -190,96 +218,85 @@ async function pull(set, get, stateKey, api, logger, conflictResolutionStrategy)
|
|
|
190
218
|
}
|
|
191
219
|
|
|
192
220
|
// src/push.ts
|
|
193
|
-
var SYNC_FIELDS = ["id", "_localId", "updated_at", "deleted"];
|
|
194
221
|
async function pushOne(set, get, change, api, logger, setAndQueueToSync, missingStrategy, onMissingRemoteRecordDuringUpdate, onAfterRemoteAdd) {
|
|
195
222
|
logger.debug(`[zync] push:attempt action=${change.action} stateKey=${change.stateKey} localId=${change.localId}`);
|
|
196
|
-
const { action, stateKey, localId, id, version } = change;
|
|
223
|
+
const { action, stateKey, localId, id, version, changes } = change;
|
|
224
|
+
const changesClone = { ...changes };
|
|
197
225
|
switch (action) {
|
|
198
226
|
case "remove" /* Remove */:
|
|
199
227
|
if (!id) {
|
|
200
|
-
logger.warn(`[zync] push:remove:no-id
|
|
228
|
+
logger.warn(`[zync] push:remove:no-id stateKey=${stateKey} localId=${localId}`);
|
|
201
229
|
removeFromPendingChanges(set, localId, stateKey);
|
|
202
230
|
return;
|
|
203
231
|
}
|
|
204
232
|
await api.remove(id);
|
|
205
|
-
logger.debug(`[zync] push:remove:success
|
|
233
|
+
logger.debug(`[zync] push:remove:success stateKey=${stateKey} localId=${localId} id=${id}`);
|
|
206
234
|
removeFromPendingChanges(set, localId, stateKey);
|
|
207
235
|
break;
|
|
208
|
-
case "
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
localId
|
|
216
|
-
});
|
|
217
|
-
removeFromPendingChanges(set, localId, stateKey);
|
|
236
|
+
case "update" /* Update */: {
|
|
237
|
+
const changed = await api.update(id, changesClone);
|
|
238
|
+
if (changed) {
|
|
239
|
+
logger.debug(`[zync] push:update:success stateKey=${stateKey} localId=${localId} id=${id}`);
|
|
240
|
+
if (samePendingVersion(get, stateKey, localId, version)) {
|
|
241
|
+
removeFromPendingChanges(set, localId, stateKey);
|
|
242
|
+
}
|
|
218
243
|
return;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
const
|
|
223
|
-
if (
|
|
224
|
-
logger.
|
|
225
|
-
|
|
226
|
-
localId,
|
|
227
|
-
id: item.id
|
|
228
|
-
});
|
|
229
|
-
if (samePendingVersion(get, stateKey, localId, version)) {
|
|
230
|
-
removeFromPendingChanges(set, localId, stateKey);
|
|
231
|
-
}
|
|
244
|
+
} else {
|
|
245
|
+
const state = get();
|
|
246
|
+
const items = state[stateKey] || [];
|
|
247
|
+
const item = items.find((i) => i._localId === localId);
|
|
248
|
+
if (!item) {
|
|
249
|
+
logger.warn(`[zync] push:missing-remote:no-local-item stateKey=${stateKey} localId=${localId}`);
|
|
250
|
+
removeFromPendingChanges(set, localId, stateKey);
|
|
232
251
|
return;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
default:
|
|
253
|
-
logger.error(`[zync] push:missing-remote:unknown stateKey=${stateKey} id=${item.id} strategy=${missingStrategy}`);
|
|
254
|
-
break;
|
|
252
|
+
}
|
|
253
|
+
switch (missingStrategy) {
|
|
254
|
+
case "delete-local-record":
|
|
255
|
+
set((s) => ({
|
|
256
|
+
[stateKey]: (s[stateKey] || []).filter((i) => i._localId !== localId)
|
|
257
|
+
}));
|
|
258
|
+
logger.debug(`[zync] push:missing-remote:${missingStrategy} stateKey=${stateKey} id=${item.id}`);
|
|
259
|
+
break;
|
|
260
|
+
case "insert-remote-record": {
|
|
261
|
+
const newItem = {
|
|
262
|
+
...item,
|
|
263
|
+
_localId: nextLocalId(),
|
|
264
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
265
|
+
};
|
|
266
|
+
setAndQueueToSync((s) => ({
|
|
267
|
+
[stateKey]: (s[stateKey] || []).map((i) => i._localId === localId ? newItem : i)
|
|
268
|
+
}));
|
|
269
|
+
logger.debug(`[zync] push:missing-remote:${missingStrategy} stateKey=${stateKey} id=${newItem.id}`);
|
|
270
|
+
break;
|
|
255
271
|
}
|
|
256
|
-
|
|
257
|
-
|
|
272
|
+
case "ignore":
|
|
273
|
+
logger.debug(`[zync] push:missing-remote:${missingStrategy} stateKey=${stateKey} id=${item.id}`);
|
|
274
|
+
break;
|
|
275
|
+
default:
|
|
276
|
+
logger.error(`[zync] push:missing-remote:unknown-strategy stateKey=${stateKey} id=${item.id} strategy=${missingStrategy}`);
|
|
277
|
+
break;
|
|
258
278
|
}
|
|
259
|
-
|
|
279
|
+
removeFromPendingChanges(set, localId, stateKey);
|
|
280
|
+
onMissingRemoteRecordDuringUpdate?.(missingStrategy, item);
|
|
260
281
|
}
|
|
261
|
-
|
|
282
|
+
break;
|
|
283
|
+
}
|
|
284
|
+
case "create" /* Create */: {
|
|
285
|
+
const result = await api.add(changesClone);
|
|
262
286
|
if (result) {
|
|
263
|
-
logger.debug(
|
|
264
|
-
stateKey,
|
|
265
|
-
localId,
|
|
266
|
-
id: result.id
|
|
267
|
-
});
|
|
287
|
+
logger.debug(`[zync] push:create:success stateKey=${stateKey} localId=${localId} id=${id}`);
|
|
268
288
|
set((s) => ({
|
|
269
289
|
[stateKey]: (s[stateKey] || []).map((i) => i._localId === localId ? { ...i, ...result } : i)
|
|
270
290
|
}));
|
|
271
291
|
if (samePendingVersion(get, stateKey, localId, version)) {
|
|
272
292
|
removeFromPendingChanges(set, localId, stateKey);
|
|
293
|
+
} else {
|
|
294
|
+
setPendingChangeToUpdate(get, stateKey, localId, result.id);
|
|
273
295
|
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
...result
|
|
277
|
-
});
|
|
296
|
+
const finalItem = { ...changes, ...result, _localId: localId };
|
|
297
|
+
onAfterRemoteAdd?.(set, get, setAndQueueToSync, stateKey, finalItem);
|
|
278
298
|
} else {
|
|
279
|
-
logger.warn(
|
|
280
|
-
stateKey,
|
|
281
|
-
localId
|
|
282
|
-
});
|
|
299
|
+
logger.warn(`[zync] push:create:no-result stateKey=${stateKey} localId=${localId} id=${id}`);
|
|
283
300
|
if (samePendingVersion(get, stateKey, localId, version)) {
|
|
284
301
|
removeFromPendingChanges(set, localId, stateKey);
|
|
285
302
|
}
|
|
@@ -289,6 +306,74 @@ async function pushOne(set, get, change, api, logger, setAndQueueToSync, missing
|
|
|
289
306
|
}
|
|
290
307
|
}
|
|
291
308
|
|
|
309
|
+
// src/firstLoad.ts
|
|
310
|
+
async function startFirstLoad(set, syncApi, logger) {
|
|
311
|
+
let syncError;
|
|
312
|
+
for (const stateKey of Object.keys(syncApi)) {
|
|
313
|
+
try {
|
|
314
|
+
logger.info(`[zync] firstLoad:start stateKey=${stateKey}`);
|
|
315
|
+
const api = findApi(stateKey, syncApi);
|
|
316
|
+
let lastId;
|
|
317
|
+
while (true) {
|
|
318
|
+
const batch = await api.firstLoad(lastId);
|
|
319
|
+
if (!batch?.length) break;
|
|
320
|
+
set((state) => {
|
|
321
|
+
const local = state[stateKey] || [];
|
|
322
|
+
const localById = new Map(local.filter((l) => l.id).map((l) => [l.id, l]));
|
|
323
|
+
let newest = new Date(state.syncState.lastPulled[stateKey] || 0);
|
|
324
|
+
const next = [...local];
|
|
325
|
+
for (const remote of batch) {
|
|
326
|
+
const remoteUpdated = new Date(remote.updated_at || 0);
|
|
327
|
+
if (remoteUpdated > newest) newest = remoteUpdated;
|
|
328
|
+
if (remote.deleted) continue;
|
|
329
|
+
delete remote.deleted;
|
|
330
|
+
const localItem = remote.id ? localById.get(remote.id) : void 0;
|
|
331
|
+
if (localItem) {
|
|
332
|
+
const merged = {
|
|
333
|
+
...localItem,
|
|
334
|
+
...remote,
|
|
335
|
+
_localId: localItem._localId
|
|
336
|
+
};
|
|
337
|
+
const idx = next.findIndex((i) => i._localId === localItem._localId);
|
|
338
|
+
if (idx >= 0) next[idx] = merged;
|
|
339
|
+
} else {
|
|
340
|
+
next.push({
|
|
341
|
+
...remote,
|
|
342
|
+
_localId: nextLocalId()
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
return {
|
|
347
|
+
[stateKey]: next,
|
|
348
|
+
syncState: {
|
|
349
|
+
...state.syncState || {},
|
|
350
|
+
lastPulled: {
|
|
351
|
+
...state.syncState.lastPulled || {},
|
|
352
|
+
[stateKey]: newest.toISOString()
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
});
|
|
357
|
+
if (lastId !== void 0 && lastId === batch[batch.length - 1].id) {
|
|
358
|
+
throw new Error(`Duplicate records downloaded, stopping to prevent infinite loop`);
|
|
359
|
+
}
|
|
360
|
+
lastId = batch[batch.length - 1].id;
|
|
361
|
+
}
|
|
362
|
+
logger.info(`[zync] firstLoad:done stateKey=${stateKey}`);
|
|
363
|
+
} catch (err) {
|
|
364
|
+
syncError = syncError ?? err;
|
|
365
|
+
logger.error(`[zync] firstLoad:error stateKey=${stateKey}`, err);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
set((state) => ({
|
|
369
|
+
syncState: {
|
|
370
|
+
...state.syncState || {},
|
|
371
|
+
firstLoadDone: true,
|
|
372
|
+
error: syncError
|
|
373
|
+
}
|
|
374
|
+
}));
|
|
375
|
+
}
|
|
376
|
+
|
|
292
377
|
// src/indexedDBStorage.ts
|
|
293
378
|
function createIndexedDBStorage(options) {
|
|
294
379
|
const dbName = options.dbName;
|
|
@@ -369,7 +454,8 @@ function createIndexedDBStorage(options) {
|
|
|
369
454
|
|
|
370
455
|
// src/index.ts
|
|
371
456
|
var SyncAction = /* @__PURE__ */ ((SyncAction2) => {
|
|
372
|
-
SyncAction2["
|
|
457
|
+
SyncAction2["Create"] = "create";
|
|
458
|
+
SyncAction2["Update"] = "update";
|
|
373
459
|
SyncAction2["Remove"] = "remove";
|
|
374
460
|
return SyncAction2;
|
|
375
461
|
})(SyncAction || {});
|
|
@@ -441,13 +527,13 @@ function persistWithSync(stateCreator, persistOptions, syncApi, syncOptions = {}
|
|
|
441
527
|
status: "syncing"
|
|
442
528
|
}
|
|
443
529
|
}));
|
|
444
|
-
let
|
|
530
|
+
let firstSyncError;
|
|
445
531
|
for (const stateKey of Object.keys(syncApi)) {
|
|
446
532
|
try {
|
|
447
533
|
const api = findApi(stateKey, syncApi);
|
|
448
534
|
await pull(set, get, stateKey, api, logger, conflictResolutionStrategy);
|
|
449
535
|
} catch (err) {
|
|
450
|
-
|
|
536
|
+
firstSyncError = firstSyncError ?? err;
|
|
451
537
|
logger.error(`[zync] pull:error stateKey=${stateKey}`, err);
|
|
452
538
|
}
|
|
453
539
|
}
|
|
@@ -468,7 +554,7 @@ function persistWithSync(stateCreator, persistOptions, syncApi, syncOptions = {}
|
|
|
468
554
|
syncOptions.onAfterRemoteAdd
|
|
469
555
|
);
|
|
470
556
|
} catch (err) {
|
|
471
|
-
|
|
557
|
+
firstSyncError = firstSyncError ?? err;
|
|
472
558
|
logger.error(`[zync] push:error change=${change}`, err);
|
|
473
559
|
}
|
|
474
560
|
}
|
|
@@ -476,76 +562,13 @@ function persistWithSync(stateCreator, persistOptions, syncApi, syncOptions = {}
|
|
|
476
562
|
syncState: {
|
|
477
563
|
...state2.syncState || {},
|
|
478
564
|
status: "idle",
|
|
479
|
-
error:
|
|
565
|
+
error: firstSyncError
|
|
480
566
|
}
|
|
481
567
|
}));
|
|
482
|
-
if (get().syncState.pendingChanges.length > 0 && !
|
|
568
|
+
if (get().syncState.pendingChanges.length > 0 && !firstSyncError) {
|
|
483
569
|
await syncOnce();
|
|
484
570
|
}
|
|
485
571
|
}
|
|
486
|
-
async function startFirstLoad() {
|
|
487
|
-
let syncError;
|
|
488
|
-
for (const stateKey of Object.keys(syncApi)) {
|
|
489
|
-
try {
|
|
490
|
-
logger.info(`[zync] firstLoad:start stateKey=${stateKey}`);
|
|
491
|
-
const api = findApi(stateKey, syncApi);
|
|
492
|
-
let lastId;
|
|
493
|
-
while (true) {
|
|
494
|
-
const batch = await api.firstLoad(lastId);
|
|
495
|
-
if (!batch?.length) break;
|
|
496
|
-
set((state) => {
|
|
497
|
-
const local = state[stateKey] || [];
|
|
498
|
-
const localById = new Map(local.filter((l) => l.id).map((l) => [l.id, l]));
|
|
499
|
-
let newest = new Date(state.syncState.lastPulled[stateKey] || 0);
|
|
500
|
-
const next = [...local];
|
|
501
|
-
for (const remote of batch) {
|
|
502
|
-
const remoteUpdated = new Date(remote.updated_at || 0);
|
|
503
|
-
if (remoteUpdated > newest) newest = remoteUpdated;
|
|
504
|
-
if (remote.deleted) continue;
|
|
505
|
-
delete remote.deleted;
|
|
506
|
-
const localItem = remote.id ? localById.get(remote.id) : void 0;
|
|
507
|
-
if (localItem) {
|
|
508
|
-
const merged = {
|
|
509
|
-
...localItem,
|
|
510
|
-
...remote,
|
|
511
|
-
_localId: localItem._localId
|
|
512
|
-
};
|
|
513
|
-
const idx = next.findIndex((i) => i._localId === localItem._localId);
|
|
514
|
-
if (idx >= 0) next[idx] = merged;
|
|
515
|
-
} else {
|
|
516
|
-
next.push({
|
|
517
|
-
...remote,
|
|
518
|
-
_localId: nextLocalId()
|
|
519
|
-
});
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
return {
|
|
523
|
-
[stateKey]: next,
|
|
524
|
-
syncState: {
|
|
525
|
-
...state.syncState || {},
|
|
526
|
-
lastPulled: {
|
|
527
|
-
...state.syncState.lastPulled || {},
|
|
528
|
-
[stateKey]: newest.toISOString()
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
};
|
|
532
|
-
});
|
|
533
|
-
lastId = batch[batch.length - 1].id;
|
|
534
|
-
}
|
|
535
|
-
logger.info(`[zync] firstLoad:done stateKey=${stateKey}`);
|
|
536
|
-
} catch (err) {
|
|
537
|
-
syncError = syncError ?? err;
|
|
538
|
-
logger.error(`[zync] firstLoad:error stateKey=${stateKey}`, err);
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
set((state) => ({
|
|
542
|
-
syncState: {
|
|
543
|
-
...state.syncState || {},
|
|
544
|
-
firstLoadDone: true,
|
|
545
|
-
error: syncError
|
|
546
|
-
}
|
|
547
|
-
}));
|
|
548
|
-
}
|
|
549
572
|
function setAndSyncOnce(partial) {
|
|
550
573
|
if (typeof partial === "function") {
|
|
551
574
|
set((state) => ({ ...partial(state) }));
|
|
@@ -568,7 +591,7 @@ function persistWithSync(stateCreator, persistOptions, syncApi, syncOptions = {}
|
|
|
568
591
|
const current = state[stateKey];
|
|
569
592
|
const updated = partial[stateKey];
|
|
570
593
|
const changes = findChanges(current, updated);
|
|
571
|
-
|
|
594
|
+
tryAddToPendingChanges(pendingChanges, stateKey, changes);
|
|
572
595
|
});
|
|
573
596
|
return {
|
|
574
597
|
...partial,
|
|
@@ -578,25 +601,6 @@ function persistWithSync(stateCreator, persistOptions, syncApi, syncOptions = {}
|
|
|
578
601
|
}
|
|
579
602
|
};
|
|
580
603
|
}
|
|
581
|
-
function addToPendingChanges(pendingChanges, stateKey, changes) {
|
|
582
|
-
for (const [localId, change] of changes) {
|
|
583
|
-
const action = change.updatedItem === null ? "remove" /* Remove */ : "create-or-update" /* CreateOrUpdate */;
|
|
584
|
-
const queueItem = pendingChanges.find((p) => p.localId === localId && p.stateKey === stateKey);
|
|
585
|
-
if (queueItem) {
|
|
586
|
-
queueItem.version += 1;
|
|
587
|
-
if (queueItem.action === "create-or-update" /* CreateOrUpdate */ && action === "remove" /* Remove */ && change.currentItem.id) {
|
|
588
|
-
queueItem.action = "remove" /* Remove */;
|
|
589
|
-
queueItem.id = change.currentItem.id;
|
|
590
|
-
logger.debug(`[zync] addToPendingChanges:changed-to-remove action=${action} localId=${localId} v=${queueItem.version}`);
|
|
591
|
-
} else {
|
|
592
|
-
logger.debug(`[zync] addToPendingChanges:re-queued action=${action} localId=${localId} v=${queueItem.version}`);
|
|
593
|
-
}
|
|
594
|
-
} else {
|
|
595
|
-
pendingChanges.push({ action, stateKey, localId, id: change.currentItem?.id, version: 1, changes: change.changes });
|
|
596
|
-
logger.debug(`[zync] addToPendingChanges:added action=${action} localId=${localId}`);
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
604
|
function enable(enabled) {
|
|
601
605
|
set((state) => ({
|
|
602
606
|
syncState: {
|
|
@@ -633,7 +637,7 @@ function persistWithSync(stateCreator, persistOptions, syncApi, syncOptions = {}
|
|
|
633
637
|
}
|
|
634
638
|
storeApi.sync = {
|
|
635
639
|
enable,
|
|
636
|
-
startFirstLoad
|
|
640
|
+
startFirstLoad: () => startFirstLoad(set, syncApi, logger)
|
|
637
641
|
};
|
|
638
642
|
const userState = stateCreator(setAndSyncOnce, get, setAndQueueToSync);
|
|
639
643
|
return {
|