@anfenn/zync 0.2.0 → 0.3.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 +13 -4
- package/dist/index.cjs +181 -170
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.js +181 -170
- 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;
|
|
@@ -125,9 +161,9 @@ async function pull(set, get, stateKey, api, logger, conflictResolutionStrategy)
|
|
|
125
161
|
continue;
|
|
126
162
|
}
|
|
127
163
|
delete remote.deleted;
|
|
128
|
-
const pending = localItem && pendingChanges.some((p) => p.stateKey === stateKey && p.localId === localItem._localId);
|
|
129
164
|
if (localItem) {
|
|
130
|
-
|
|
165
|
+
const pendingChange = pendingChanges.find((p) => p.stateKey === stateKey && p.localId === localItem._localId);
|
|
166
|
+
if (pendingChange) {
|
|
131
167
|
switch (conflictResolutionStrategy) {
|
|
132
168
|
case "local-wins":
|
|
133
169
|
logger.debug(`[zync] pull:conflict-strategy:${conflictResolutionStrategy} stateKey=${stateKey} id=${remote.id}`);
|
|
@@ -158,11 +194,10 @@ async function pull(set, get, stateKey, api, logger, conflictResolutionStrategy)
|
|
|
158
194
|
} else {
|
|
159
195
|
const merged = {
|
|
160
196
|
...localItem,
|
|
161
|
-
...remote
|
|
162
|
-
_localId: localItem._localId
|
|
197
|
+
...remote
|
|
163
198
|
};
|
|
164
199
|
nextItems = nextItems.map((i) => i._localId === localItem._localId ? merged : i);
|
|
165
|
-
logger.debug(`[zync] pull:merge stateKey=${stateKey} id=${remote.id}`);
|
|
200
|
+
logger.debug(`[zync] pull:merge-remote stateKey=${stateKey} id=${remote.id}`);
|
|
166
201
|
}
|
|
167
202
|
} else {
|
|
168
203
|
nextItems = [...nextItems, { ...remote, _localId: nextLocalId() }];
|
|
@@ -183,96 +218,85 @@ async function pull(set, get, stateKey, api, logger, conflictResolutionStrategy)
|
|
|
183
218
|
}
|
|
184
219
|
|
|
185
220
|
// src/push.ts
|
|
186
|
-
var SYNC_FIELDS = ["id", "_localId", "updated_at", "deleted"];
|
|
187
221
|
async function pushOne(set, get, change, api, logger, setAndQueueToSync, missingStrategy, onMissingRemoteRecordDuringUpdate, onAfterRemoteAdd) {
|
|
188
222
|
logger.debug(`[zync] push:attempt action=${change.action} stateKey=${change.stateKey} localId=${change.localId}`);
|
|
189
|
-
const { action, stateKey, localId, id, version } = change;
|
|
223
|
+
const { action, stateKey, localId, id, version, changes } = change;
|
|
224
|
+
const changesClone = { ...changes };
|
|
190
225
|
switch (action) {
|
|
191
226
|
case "remove" /* Remove */:
|
|
192
227
|
if (!id) {
|
|
193
|
-
logger.warn(`[zync] push:remove:no-id
|
|
228
|
+
logger.warn(`[zync] push:remove:no-id stateKey=${stateKey} localId=${localId}`);
|
|
194
229
|
removeFromPendingChanges(set, localId, stateKey);
|
|
195
230
|
return;
|
|
196
231
|
}
|
|
197
232
|
await api.remove(id);
|
|
198
|
-
logger.debug(`[zync] push:remove:success
|
|
233
|
+
logger.debug(`[zync] push:remove:success stateKey=${stateKey} localId=${localId} id=${id}`);
|
|
199
234
|
removeFromPendingChanges(set, localId, stateKey);
|
|
200
235
|
break;
|
|
201
|
-
case "
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
localId
|
|
209
|
-
});
|
|
210
|
-
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
|
+
}
|
|
211
243
|
return;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
const
|
|
216
|
-
if (
|
|
217
|
-
logger.
|
|
218
|
-
|
|
219
|
-
localId,
|
|
220
|
-
id: item.id
|
|
221
|
-
});
|
|
222
|
-
if (samePendingVersion(get, stateKey, localId, version)) {
|
|
223
|
-
removeFromPendingChanges(set, localId, stateKey);
|
|
224
|
-
}
|
|
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);
|
|
225
251
|
return;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
default:
|
|
246
|
-
logger.error(`[zync] push:missing-remote:unknown stateKey=${stateKey} id=${item.id} strategy=${missingStrategy}`);
|
|
247
|
-
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;
|
|
248
271
|
}
|
|
249
|
-
|
|
250
|
-
|
|
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;
|
|
251
278
|
}
|
|
252
|
-
|
|
279
|
+
removeFromPendingChanges(set, localId, stateKey);
|
|
280
|
+
onMissingRemoteRecordDuringUpdate?.(missingStrategy, item);
|
|
253
281
|
}
|
|
254
|
-
|
|
282
|
+
break;
|
|
283
|
+
}
|
|
284
|
+
case "create" /* Create */: {
|
|
285
|
+
const result = await api.add(changesClone);
|
|
255
286
|
if (result) {
|
|
256
|
-
logger.debug(
|
|
257
|
-
stateKey,
|
|
258
|
-
localId,
|
|
259
|
-
id: result.id
|
|
260
|
-
});
|
|
287
|
+
logger.debug(`[zync] push:create:success stateKey=${stateKey} localId=${localId} id=${id}`);
|
|
261
288
|
set((s) => ({
|
|
262
289
|
[stateKey]: (s[stateKey] || []).map((i) => i._localId === localId ? { ...i, ...result } : i)
|
|
263
290
|
}));
|
|
264
291
|
if (samePendingVersion(get, stateKey, localId, version)) {
|
|
265
292
|
removeFromPendingChanges(set, localId, stateKey);
|
|
293
|
+
} else {
|
|
294
|
+
setPendingChangeToUpdate(get, stateKey, localId, result.id);
|
|
266
295
|
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
...result
|
|
270
|
-
});
|
|
296
|
+
const finalItem = { ...changes, ...result, _localId: localId };
|
|
297
|
+
onAfterRemoteAdd?.(set, get, setAndQueueToSync, stateKey, finalItem);
|
|
271
298
|
} else {
|
|
272
|
-
logger.warn(
|
|
273
|
-
stateKey,
|
|
274
|
-
localId
|
|
275
|
-
});
|
|
299
|
+
logger.warn(`[zync] push:create:no-result stateKey=${stateKey} localId=${localId} id=${id}`);
|
|
276
300
|
if (samePendingVersion(get, stateKey, localId, version)) {
|
|
277
301
|
removeFromPendingChanges(set, localId, stateKey);
|
|
278
302
|
}
|
|
@@ -282,6 +306,74 @@ async function pushOne(set, get, change, api, logger, setAndQueueToSync, missing
|
|
|
282
306
|
}
|
|
283
307
|
}
|
|
284
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
|
+
|
|
285
377
|
// src/indexedDBStorage.ts
|
|
286
378
|
function createIndexedDBStorage(options) {
|
|
287
379
|
const dbName = options.dbName;
|
|
@@ -362,7 +454,8 @@ function createIndexedDBStorage(options) {
|
|
|
362
454
|
|
|
363
455
|
// src/index.ts
|
|
364
456
|
var SyncAction = /* @__PURE__ */ ((SyncAction2) => {
|
|
365
|
-
SyncAction2["
|
|
457
|
+
SyncAction2["Create"] = "create";
|
|
458
|
+
SyncAction2["Update"] = "update";
|
|
366
459
|
SyncAction2["Remove"] = "remove";
|
|
367
460
|
return SyncAction2;
|
|
368
461
|
})(SyncAction || {});
|
|
@@ -434,13 +527,13 @@ function persistWithSync(stateCreator, persistOptions, syncApi, syncOptions = {}
|
|
|
434
527
|
status: "syncing"
|
|
435
528
|
}
|
|
436
529
|
}));
|
|
437
|
-
let
|
|
530
|
+
let firstSyncError;
|
|
438
531
|
for (const stateKey of Object.keys(syncApi)) {
|
|
439
532
|
try {
|
|
440
533
|
const api = findApi(stateKey, syncApi);
|
|
441
534
|
await pull(set, get, stateKey, api, logger, conflictResolutionStrategy);
|
|
442
535
|
} catch (err) {
|
|
443
|
-
|
|
536
|
+
firstSyncError = firstSyncError ?? err;
|
|
444
537
|
logger.error(`[zync] pull:error stateKey=${stateKey}`, err);
|
|
445
538
|
}
|
|
446
539
|
}
|
|
@@ -461,7 +554,7 @@ function persistWithSync(stateCreator, persistOptions, syncApi, syncOptions = {}
|
|
|
461
554
|
syncOptions.onAfterRemoteAdd
|
|
462
555
|
);
|
|
463
556
|
} catch (err) {
|
|
464
|
-
|
|
557
|
+
firstSyncError = firstSyncError ?? err;
|
|
465
558
|
logger.error(`[zync] push:error change=${change}`, err);
|
|
466
559
|
}
|
|
467
560
|
}
|
|
@@ -469,76 +562,13 @@ function persistWithSync(stateCreator, persistOptions, syncApi, syncOptions = {}
|
|
|
469
562
|
syncState: {
|
|
470
563
|
...state2.syncState || {},
|
|
471
564
|
status: "idle",
|
|
472
|
-
error:
|
|
565
|
+
error: firstSyncError
|
|
473
566
|
}
|
|
474
567
|
}));
|
|
475
|
-
if (get().syncState.pendingChanges.length > 0 && !
|
|
568
|
+
if (get().syncState.pendingChanges.length > 0 && !firstSyncError) {
|
|
476
569
|
await syncOnce();
|
|
477
570
|
}
|
|
478
571
|
}
|
|
479
|
-
async function startFirstLoad() {
|
|
480
|
-
let syncError;
|
|
481
|
-
for (const stateKey of Object.keys(syncApi)) {
|
|
482
|
-
try {
|
|
483
|
-
logger.info(`[zync] firstLoad:start stateKey=${stateKey}`);
|
|
484
|
-
const api = findApi(stateKey, syncApi);
|
|
485
|
-
let lastId;
|
|
486
|
-
while (true) {
|
|
487
|
-
const batch = await api.firstLoad(lastId);
|
|
488
|
-
if (!batch?.length) break;
|
|
489
|
-
set((state) => {
|
|
490
|
-
const local = state[stateKey] || [];
|
|
491
|
-
const localById = new Map(local.filter((l) => l.id).map((l) => [l.id, l]));
|
|
492
|
-
let newest = new Date(state.syncState.lastPulled[stateKey] || 0);
|
|
493
|
-
const next = [...local];
|
|
494
|
-
for (const remote of batch) {
|
|
495
|
-
const remoteUpdated = new Date(remote.updated_at || 0);
|
|
496
|
-
if (remoteUpdated > newest) newest = remoteUpdated;
|
|
497
|
-
if (remote.deleted) continue;
|
|
498
|
-
delete remote.deleted;
|
|
499
|
-
const localItem = remote.id ? localById.get(remote.id) : void 0;
|
|
500
|
-
if (localItem) {
|
|
501
|
-
const merged = {
|
|
502
|
-
...localItem,
|
|
503
|
-
...remote,
|
|
504
|
-
_localId: localItem._localId
|
|
505
|
-
};
|
|
506
|
-
const idx = next.findIndex((i) => i._localId === localItem._localId);
|
|
507
|
-
if (idx >= 0) next[idx] = merged;
|
|
508
|
-
} else {
|
|
509
|
-
next.push({
|
|
510
|
-
...remote,
|
|
511
|
-
_localId: nextLocalId()
|
|
512
|
-
});
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
return {
|
|
516
|
-
[stateKey]: next,
|
|
517
|
-
syncState: {
|
|
518
|
-
...state.syncState || {},
|
|
519
|
-
lastPulled: {
|
|
520
|
-
...state.syncState.lastPulled || {},
|
|
521
|
-
[stateKey]: newest.toISOString()
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
};
|
|
525
|
-
});
|
|
526
|
-
lastId = batch[batch.length - 1].id;
|
|
527
|
-
}
|
|
528
|
-
logger.info(`[zync] firstLoad:done stateKey=${stateKey}`);
|
|
529
|
-
} catch (err) {
|
|
530
|
-
syncError = syncError ?? err;
|
|
531
|
-
logger.error(`[zync] firstLoad:error stateKey=${stateKey}`, err);
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
set((state) => ({
|
|
535
|
-
syncState: {
|
|
536
|
-
...state.syncState || {},
|
|
537
|
-
firstLoadDone: true,
|
|
538
|
-
error: syncError
|
|
539
|
-
}
|
|
540
|
-
}));
|
|
541
|
-
}
|
|
542
572
|
function setAndSyncOnce(partial) {
|
|
543
573
|
if (typeof partial === "function") {
|
|
544
574
|
set((state) => ({ ...partial(state) }));
|
|
@@ -561,7 +591,7 @@ function persistWithSync(stateCreator, persistOptions, syncApi, syncOptions = {}
|
|
|
561
591
|
const current = state[stateKey];
|
|
562
592
|
const updated = partial[stateKey];
|
|
563
593
|
const changes = findChanges(current, updated);
|
|
564
|
-
|
|
594
|
+
tryAddToPendingChanges(pendingChanges, stateKey, changes);
|
|
565
595
|
});
|
|
566
596
|
return {
|
|
567
597
|
...partial,
|
|
@@ -571,25 +601,6 @@ function persistWithSync(stateCreator, persistOptions, syncApi, syncOptions = {}
|
|
|
571
601
|
}
|
|
572
602
|
};
|
|
573
603
|
}
|
|
574
|
-
function addToPendingChanges(pendingChanges, stateKey, changes) {
|
|
575
|
-
for (const [localId, change] of changes) {
|
|
576
|
-
const action = change.updatedItem === null ? "remove" /* Remove */ : "create-or-update" /* CreateOrUpdate */;
|
|
577
|
-
const queueItem = pendingChanges.find((p) => p.localId === localId && p.stateKey === stateKey);
|
|
578
|
-
if (queueItem) {
|
|
579
|
-
queueItem.version += 1;
|
|
580
|
-
if (queueItem.action === "create-or-update" /* CreateOrUpdate */ && action === "remove" /* Remove */ && change.currentItem.id) {
|
|
581
|
-
queueItem.action = "remove" /* Remove */;
|
|
582
|
-
queueItem.id = change.currentItem.id;
|
|
583
|
-
logger.debug(`[zync] addToPendingChanges:changed-to-remove action=${action} localId=${localId} v=${queueItem.version}`);
|
|
584
|
-
} else {
|
|
585
|
-
logger.debug(`[zync] addToPendingChanges:re-queued action=${action} localId=${localId} v=${queueItem.version}`);
|
|
586
|
-
}
|
|
587
|
-
} else {
|
|
588
|
-
pendingChanges.push({ action, stateKey, localId, id: change.currentItem?.id, version: 1 });
|
|
589
|
-
logger.debug(`[zync] addToPendingChanges:added action=${action} localId=${localId}`);
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
604
|
function enable(enabled) {
|
|
594
605
|
set((state) => ({
|
|
595
606
|
syncState: {
|
|
@@ -626,7 +637,7 @@ function persistWithSync(stateCreator, persistOptions, syncApi, syncOptions = {}
|
|
|
626
637
|
}
|
|
627
638
|
storeApi.sync = {
|
|
628
639
|
enable,
|
|
629
|
-
startFirstLoad
|
|
640
|
+
startFirstLoad: () => startFirstLoad(set, syncApi, logger)
|
|
630
641
|
};
|
|
631
642
|
const userState = stateCreator(setAndSyncOnce, get, setAndQueueToSync);
|
|
632
643
|
return {
|