@anfenn/zync 0.1.0 → 0.1.2

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/dist/index.cjs ADDED
@@ -0,0 +1,482 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ SyncAction: () => SyncAction,
24
+ nextLocalId: () => nextLocalId,
25
+ persistWithSync: () => persistWithSync
26
+ });
27
+ module.exports = __toCommonJS(index_exports);
28
+ var import_zustand = require("zustand");
29
+ var import_middleware = require("zustand/middleware");
30
+ var SyncAction = /* @__PURE__ */ ((SyncAction2) => {
31
+ SyncAction2["Create"] = "create";
32
+ SyncAction2["Update"] = "update";
33
+ SyncAction2["Remove"] = "remove";
34
+ return SyncAction2;
35
+ })(SyncAction || {});
36
+ var DEFAULT_SYNC_INTERVAL_MILLIS = 5e3;
37
+ var DEFAULT_LOGGER = console;
38
+ var DEFAULT_MIN_LOG_LEVEL = "debug";
39
+ var DEFAULT_MISSING_REMOTE_RECORD_ON_UPDATE_STRATEGY = "insertNewRemoteRecord";
40
+ var SYNC_FIELDS = ["id", "_localId", "updated_at", "deleted"];
41
+ function persistWithSync(stateCreator, persistOptions, syncApi, syncOptions = {}) {
42
+ const syncInterval = syncOptions.syncInterval ?? DEFAULT_SYNC_INTERVAL_MILLIS;
43
+ const missingStrategy = syncOptions.missingRemoteRecordDuringUpdateStrategy ?? DEFAULT_MISSING_REMOTE_RECORD_ON_UPDATE_STRATEGY;
44
+ const logger = newLogger(syncOptions.logger ?? DEFAULT_LOGGER, syncOptions.minLogLevel ?? DEFAULT_MIN_LOG_LEVEL);
45
+ let startSync;
46
+ let syncIntervalId;
47
+ const baseOnRehydrate = persistOptions?.onRehydrateStorage;
48
+ const basePartialize = persistOptions?.partialize;
49
+ const wrappedPersistOptions = {
50
+ ...persistOptions,
51
+ onRehydrateStorage: () => {
52
+ logger.debug("[persistWithSync] Rehydration started");
53
+ return (state, error) => {
54
+ if (error) {
55
+ logger.error("[persistWithSync] Rehydration failed", error);
56
+ } else {
57
+ baseOnRehydrate?.(state, error);
58
+ logger.debug("[persistWithSync] Rehydration complete");
59
+ startSync?.();
60
+ }
61
+ };
62
+ },
63
+ partialize: (s) => {
64
+ const base = basePartialize ? basePartialize(s) : s;
65
+ const { syncState: _sync, ...rest } = base || {};
66
+ return {
67
+ ...rest,
68
+ syncState: {
69
+ firstLoadDone: _sync?.firstLoadDone ?? false
70
+ }
71
+ };
72
+ },
73
+ merge: (persisted, current) => {
74
+ const p = persisted || {};
75
+ const c = current || {};
76
+ return {
77
+ ...c,
78
+ ...p,
79
+ syncState: {
80
+ ...c.syncState,
81
+ firstLoadDone: p.syncState?.firstLoadDone ?? c.syncState?.firstLoadDone ?? false,
82
+ status: "idle"
83
+ }
84
+ };
85
+ }
86
+ };
87
+ const creator = (set, get) => {
88
+ async function syncOnce() {
89
+ const state = get();
90
+ if (!state.syncState.enabled || state.syncState.status !== "idle") return;
91
+ set((state2) => {
92
+ return {
93
+ syncState: {
94
+ ...state2.syncState,
95
+ status: "syncing"
96
+ }
97
+ };
98
+ });
99
+ let syncError;
100
+ for (const stateKey of Object.keys(syncApi)) {
101
+ try {
102
+ const api = findApi(stateKey, syncApi);
103
+ await pull(set, get, stateKey, api, logger);
104
+ } catch (err) {
105
+ syncError = syncError ?? err;
106
+ logger.error(`[persistWithSync] Pull error for stateKey: ${stateKey}`, err);
107
+ }
108
+ }
109
+ const snapshot = [...get()._pendingChanges || []];
110
+ snapshot.sort((a, b) => orderFor(a.action) - orderFor(b.action));
111
+ for (const change of snapshot) {
112
+ try {
113
+ const api = findApi(change.stateKey, syncApi);
114
+ await pushOne(
115
+ set,
116
+ get,
117
+ change,
118
+ api,
119
+ logger,
120
+ queueToSync,
121
+ missingStrategy,
122
+ syncOptions.onMissingRemoteRecordDuringUpdate,
123
+ syncOptions.onAfterRemoteAdd
124
+ );
125
+ } catch (err) {
126
+ syncError = syncError ?? err;
127
+ logger.error(`[persistWithSync] Push error for change: ${change}`, err);
128
+ }
129
+ }
130
+ set((state2) => {
131
+ return {
132
+ syncState: {
133
+ ...state2.syncState,
134
+ status: "idle",
135
+ error: syncError
136
+ }
137
+ };
138
+ });
139
+ }
140
+ startSync = async () => {
141
+ clearInterval(syncIntervalId);
142
+ syncIntervalId = void 0;
143
+ await syncOnce();
144
+ syncIntervalId = setInterval(syncOnce, syncInterval);
145
+ };
146
+ function queueToSync(action, localId, stateKey, changes) {
147
+ set((state) => {
148
+ const queue = state._pendingChanges || [];
149
+ const idx = queue.findIndex((p) => p.localId === localId && p.stateKey === stateKey);
150
+ if (idx >= 0) {
151
+ const existing = queue[idx];
152
+ switch (existing?.action) {
153
+ case "create" /* Create */:
154
+ if (action === "update" /* Update */) {
155
+ existing.changes = { ...existing.changes, ...changes };
156
+ } else if (action === "remove" /* Remove */) {
157
+ queue.splice(idx, 1);
158
+ }
159
+ break;
160
+ case "update" /* Update */:
161
+ if (action === "update" /* Update */) {
162
+ existing.changes = { ...existing.changes, ...changes };
163
+ } else if (action === "remove" /* Remove */) {
164
+ existing.action = "remove" /* Remove */;
165
+ delete existing.changes;
166
+ }
167
+ break;
168
+ case "remove" /* Remove */:
169
+ break;
170
+ }
171
+ } else {
172
+ if (action === "remove" /* Remove */) {
173
+ const item = state[stateKey].find((i) => i._localId === localId);
174
+ if (item) changes = { id: item.id };
175
+ }
176
+ queue.push({ stateKey, localId, action, changes });
177
+ }
178
+ state._pendingChanges = queue;
179
+ return state;
180
+ });
181
+ syncOnce();
182
+ }
183
+ function setAndSync(partial) {
184
+ set(partial);
185
+ syncOnce();
186
+ }
187
+ function enableSync(enabled) {
188
+ set((state) => {
189
+ return {
190
+ syncState: {
191
+ ...state.syncState,
192
+ enabled
193
+ }
194
+ };
195
+ });
196
+ syncOnce();
197
+ }
198
+ document.addEventListener("visibilitychange", async () => {
199
+ clearInterval(syncIntervalId);
200
+ if (document.visibilityState === "visible") {
201
+ logger.debug("[persistWithSync] Sync starting now app is in foreground");
202
+ await startSync?.();
203
+ } else {
204
+ logger.debug("[persistWithSync] Sync paused now app is in background");
205
+ }
206
+ });
207
+ async function startFirstLoad() {
208
+ let syncError;
209
+ for (const stateKey of Object.keys(syncApi)) {
210
+ try {
211
+ logger.info(`[persistWithSync] firstLoad:start for stateKey: ${stateKey}`);
212
+ const api = findApi(stateKey, syncApi);
213
+ let lastId;
214
+ while (true) {
215
+ const batch = await api.firstLoad(lastId);
216
+ if (!batch?.length) break;
217
+ set((state) => {
218
+ const local = state[stateKey] || [];
219
+ const localById = new Map(local.filter((l) => l.id).map((l) => [l.id, l]));
220
+ let newest = new Date(state._lastPulled?.[stateKey] || 0);
221
+ const next = [...local];
222
+ for (const remote of batch) {
223
+ const remoteUpdated = new Date(remote.updated_at || 0);
224
+ if (remoteUpdated > newest) newest = remoteUpdated;
225
+ if (remote.deleted) continue;
226
+ const localItem = remote.id ? localById.get(remote.id) : void 0;
227
+ if (localItem) {
228
+ const merged = { ...localItem, ...remote, _localId: localItem._localId };
229
+ const idx = next.findIndex((i) => i._localId === localItem._localId);
230
+ if (idx >= 0) next[idx] = merged;
231
+ } else {
232
+ next.push({ ...remote, _localId: nextLocalId() });
233
+ }
234
+ }
235
+ state[stateKey] = next;
236
+ state._lastPulled = {
237
+ ...state._lastPulled || {},
238
+ [stateKey]: newest.toISOString()
239
+ };
240
+ return state;
241
+ });
242
+ lastId = batch[batch.length - 1].id;
243
+ }
244
+ logger.info(`[persistWithSync] firstLoad:done for stateKey: ${stateKey}`);
245
+ } catch (err) {
246
+ syncError = syncError ?? err;
247
+ logger.error(`[persistWithSync] First load pull error for stateKey: ${stateKey}`, err);
248
+ }
249
+ }
250
+ set((state) => {
251
+ return {
252
+ syncState: { ...state.syncState, firstLoadDone: true },
253
+ syncError
254
+ };
255
+ });
256
+ }
257
+ const userState = stateCreator(setAndSync, get, queueToSync);
258
+ const syncState = userState.syncState || {};
259
+ return {
260
+ ...userState,
261
+ syncState: {
262
+ status: syncState.status ?? "hydrating",
263
+ error: syncState.error,
264
+ enabled: syncState.enabled ?? false,
265
+ firstLoadDone: syncState.firstLoadDone ?? false,
266
+ enableSync,
267
+ startFirstLoad
268
+ },
269
+ _pendingChanges: userState._pendingChanges ?? [],
270
+ _lastPulled: userState._lastPulled ?? {}
271
+ };
272
+ };
273
+ return (0, import_middleware.persist)(creator, wrappedPersistOptions);
274
+ }
275
+ function orderFor(a) {
276
+ switch (a) {
277
+ case "create" /* Create */:
278
+ return 1;
279
+ case "update" /* Update */:
280
+ return 2;
281
+ case "remove" /* Remove */:
282
+ return 3;
283
+ }
284
+ }
285
+ async function pull(set, get, stateKey, api, logger) {
286
+ const lastPulled = get()._lastPulled || {};
287
+ const lastPulledAt = new Date(lastPulled[stateKey] || /* @__PURE__ */ new Date(0));
288
+ logger.debug(`[persistWithSync] pull:start stateKey=${stateKey} since=${lastPulledAt.toISOString()}`);
289
+ const serverData = await api.list(lastPulledAt);
290
+ if (!serverData?.length) return;
291
+ let newest = lastPulledAt;
292
+ set((state) => {
293
+ const _pendingChanges = state._pendingChanges || [];
294
+ const local = state[stateKey] || [];
295
+ const localById = new Map(local.filter((l) => l.id).map((l) => [l.id, l]));
296
+ const pendingRemovals = new Set(
297
+ _pendingChanges.filter(
298
+ (p) => p.stateKey === stateKey && p.action === "remove" /* Remove */ && p.changes && typeof p.changes.id !== "undefined"
299
+ ).map((p) => p.changes.id)
300
+ );
301
+ for (const remote of serverData) {
302
+ const remoteUpdated = new Date(remote.updated_at);
303
+ if (remoteUpdated > newest) newest = remoteUpdated;
304
+ const localItem = localById.get(remote.id);
305
+ if (pendingRemovals.has(remote.id)) {
306
+ logger.debug(`[persistWithSync] pull:skip-pending-remove stateKey=${stateKey} id=${remote.id}`);
307
+ continue;
308
+ }
309
+ if (remote.deleted) {
310
+ if (localItem) {
311
+ state[stateKey] = state[stateKey].filter((i) => i.id !== remote.id);
312
+ logger.debug(`[persistWithSync] pull:remove stateKey=${stateKey} id=${remote.id}`);
313
+ }
314
+ continue;
315
+ }
316
+ const pending = _pendingChanges.some(
317
+ (p) => p.stateKey === stateKey && localItem && p.localId === localItem._localId
318
+ );
319
+ if (localItem && !pending) {
320
+ const merged = { ...localItem, ...remote, _localId: localItem._localId };
321
+ state[stateKey] = state[stateKey].map((i) => i._localId === localItem._localId ? merged : i);
322
+ logger.debug(`[persistWithSync] pull:merge stateKey=${stateKey} id=${remote.id}`);
323
+ } else if (!localItem) {
324
+ state[stateKey] = [...state[stateKey], { ...remote, _localId: nextLocalId() }];
325
+ logger.debug(`[persistWithSync] pull:add stateKey=${stateKey} id=${remote.id}`);
326
+ }
327
+ }
328
+ state._lastPulled = {
329
+ ...state._lastPulled,
330
+ [stateKey]: newest.toISOString()
331
+ };
332
+ return state;
333
+ });
334
+ }
335
+ async function pushOne(set, get, change, api, logger, queueToSync, missingStrategy, onMissingRemoteRecordDuringUpdate, onAfterRemoteAdd) {
336
+ logger.debug(
337
+ `[persistWithSync] push:attempt action=${change.action} stateKey=${change.stateKey} localId=${change.localId}`
338
+ );
339
+ const { stateKey, localId, action, changes } = change;
340
+ const state = get();
341
+ const items = state[stateKey] || [];
342
+ const item = items.find((i) => i._localId === localId);
343
+ switch (action) {
344
+ case "create" /* Create */: {
345
+ if (!item) {
346
+ removeFromPendingChanges(set, localId, stateKey);
347
+ return;
348
+ }
349
+ if (item.id) {
350
+ if (changes && Object.keys(changes).length) {
351
+ await api.update(item.id, omitSyncFields({ ...item, ...changes }));
352
+ }
353
+ removeFromPendingChanges(set, localId, stateKey);
354
+ return;
355
+ }
356
+ const payload = changes ? { ...item, ...changes } : item;
357
+ const result = await api.add(omitSyncFields(payload));
358
+ if (result) {
359
+ logger.debug("[persistWithSync] push:create:success", { stateKey, localId, id: result.id });
360
+ set((s) => {
361
+ s[stateKey] = (s[stateKey] || []).map(
362
+ (i) => i._localId === localId ? { ...i, ...result } : i
363
+ );
364
+ return s;
365
+ });
366
+ onAfterRemoteAdd?.(set, get, queueToSync, stateKey, { ...item, ...result });
367
+ } else {
368
+ logger.warn("[persistWithSync] push:create:no-result", { stateKey, localId });
369
+ }
370
+ removeFromPendingChanges(set, localId, stateKey);
371
+ break;
372
+ }
373
+ case "update" /* Update */: {
374
+ if (!item) {
375
+ removeFromPendingChanges(set, localId, stateKey);
376
+ return;
377
+ }
378
+ if (!item.id) {
379
+ set((s) => {
380
+ const q = s._pendingChanges || [];
381
+ const e = q.find((p) => p.localId === localId && p.stateKey === stateKey);
382
+ if (e) e.action = "create" /* Create */;
383
+ return s;
384
+ });
385
+ return;
386
+ }
387
+ const changed = await api.update(item.id, omitSyncFields({ ...changes }));
388
+ if (!changed) {
389
+ logger.warn("[persistWithSync] push:update:missingRemote", { stateKey, localId, id: item.id });
390
+ const oldRecord = { ...item };
391
+ let newLocalId;
392
+ switch (missingStrategy) {
393
+ case "deleteLocalRecord":
394
+ set((s) => {
395
+ s[stateKey] = (s[stateKey] || []).filter((i) => i._localId !== localId);
396
+ return s;
397
+ });
398
+ removeFromPendingChanges(set, localId, stateKey);
399
+ break;
400
+ case "insertNewRemoteRecord": {
401
+ const freshLocalId = nextLocalId();
402
+ newLocalId = freshLocalId;
403
+ set((s) => {
404
+ s[stateKey] = (s[stateKey] || []).filter((i) => i._localId !== localId);
405
+ s[stateKey].push({
406
+ ...omitSyncFields(oldRecord),
407
+ _localId: freshLocalId,
408
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
409
+ });
410
+ const q = s._pendingChanges || [];
411
+ const e = q.find((p) => p.localId === localId && p.stateKey === stateKey);
412
+ if (e) {
413
+ e.localId = freshLocalId;
414
+ e.action = "create" /* Create */;
415
+ } else {
416
+ q.push({ stateKey, localId: freshLocalId, action: "create" /* Create */, changes });
417
+ }
418
+ s._pendingChanges = q;
419
+ return s;
420
+ });
421
+ break;
422
+ }
423
+ }
424
+ onMissingRemoteRecordDuringUpdate?.(missingStrategy, oldRecord, newLocalId);
425
+ } else {
426
+ logger.debug("[persistWithSync] push:update:success", { stateKey, localId, id: item.id });
427
+ removeFromPendingChanges(set, localId, stateKey);
428
+ }
429
+ break;
430
+ }
431
+ case "remove" /* Remove */: {
432
+ const id = changes?.id;
433
+ if (id) {
434
+ await api.remove(id);
435
+ logger.debug("[persistWithSync] push:remove:success", { stateKey, localId, id });
436
+ }
437
+ removeFromPendingChanges(set, localId, stateKey);
438
+ break;
439
+ }
440
+ }
441
+ }
442
+ function removeFromPendingChanges(set, localId, stateKey) {
443
+ set((s) => {
444
+ s._pendingChanges = (s._pendingChanges || []).filter(
445
+ (p) => !(p.localId === localId && p.stateKey === stateKey)
446
+ );
447
+ return s;
448
+ });
449
+ }
450
+ function omitSyncFields(item) {
451
+ const result = { ...item };
452
+ for (const k of SYNC_FIELDS) delete result[k];
453
+ return result;
454
+ }
455
+ function nextLocalId() {
456
+ return crypto.randomUUID();
457
+ }
458
+ function newLogger(logger, min) {
459
+ const order = { debug: 10, info: 20, warn: 30, error: 40, none: 100 };
460
+ const threshold = order[min];
461
+ const enabled = (lvl) => order[lvl] >= threshold;
462
+ return {
463
+ debug: (...a) => enabled("debug") && logger.debug?.(...a),
464
+ info: (...a) => enabled("info") && logger.info?.(...a),
465
+ warn: (...a) => enabled("warn") && logger.warn?.(...a),
466
+ error: (...a) => enabled("error") && logger.error?.(...a)
467
+ };
468
+ }
469
+ function findApi(stateKey, syncApi) {
470
+ const api = syncApi[stateKey];
471
+ if (!api || !api.add || !api.update || !api.remove || !api.list || !api.firstLoad) {
472
+ throw new Error(`Missing API function(s) for state key: ${stateKey}.`);
473
+ }
474
+ return api;
475
+ }
476
+ // Annotate the CommonJS export names for ESM import in node:
477
+ 0 && (module.exports = {
478
+ SyncAction,
479
+ nextLocalId,
480
+ persistWithSync
481
+ });
482
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { type StateCreator } from 'zustand';\nimport { persist } from 'zustand/middleware';\n\nexport type SyncedRecord = { id?: any; _localId: string; updated_at: string; deleted?: boolean; [k: string]: any };\n\nexport interface ApiFunctions {\n add: (item: any) => Promise<any | undefined>;\n update: (id: any, changes: any) => Promise<boolean>; // returns true if applied, false if remote missing\n remove: (id: any) => Promise<void>;\n list: (lastUpdatedAt: Date) => Promise<any[]>; // returns changed records since timestamp (including records with deleted flag)\n firstLoad: (lastId: any) => Promise<any[]>; // returns all records with id > lastId\n}\n\ntype AfterRemoteAddCallback = (\n set: any,\n get: any,\n queue: QueueToSyncCallback,\n stateKey: string,\n item: SyncedRecord,\n) => void;\n\ntype MissingRemoteRecordDuringUpdateCallback = (\n strategy: MissingRemoteRecordDuringUpdateStrategy,\n item: SyncedRecord,\n newLocalId?: string,\n) => void;\n\nexport type MissingRemoteRecordDuringUpdateStrategy = 'deleteLocalRecord' | 'insertNewRemoteRecord';\n\ninterface SyncOptions {\n syncInterval?: number;\n logger?: Logger;\n minLogLevel?: LogLevel;\n onAfterRemoteAdd?: AfterRemoteAddCallback;\n missingRemoteRecordDuringUpdateStrategy?: MissingRemoteRecordDuringUpdateStrategy;\n onMissingRemoteRecordDuringUpdate?: MissingRemoteRecordDuringUpdateCallback;\n}\n\nexport type SyncState = {\n syncState: {\n status: 'hydrating' | 'syncing' | 'idle';\n error?: Error;\n enabled: boolean;\n enableSync: (enabled: boolean) => void;\n firstLoadDone: boolean;\n startFirstLoad: () => Promise<void>;\n };\n};\n\ntype _SyncState = {\n _pendingChanges: PendingChange[];\n _lastPulled: Record<string, string>; // stateKey -> ISO timestamp of last successful pull\n};\n\nexport enum SyncAction {\n Create = 'create',\n Update = 'update',\n Remove = 'remove',\n}\n\nexport type QueueToSyncCallback = (action: SyncAction, localId: string, stateKey: string, changes?: any) => void;\n\ntype SyncedStateCreator<TStore> = (set: any, get: any, queue: QueueToSyncCallback) => TStore;\n\ninterface PendingChange {\n stateKey: string;\n localId: string;\n action: SyncAction;\n changes?: any; // merged change set (for create/update)\n}\n\nexport interface Logger {\n debug: (...args: any[]) => void;\n info: (...args: any[]) => void;\n warn: (...args: any[]) => void;\n error: (...args: any[]) => void;\n}\n\nexport type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'none';\n\nconst DEFAULT_SYNC_INTERVAL_MILLIS = 5000;\nconst DEFAULT_LOGGER: Logger = console;\nconst DEFAULT_MIN_LOG_LEVEL: LogLevel = 'debug';\nconst DEFAULT_MISSING_REMOTE_RECORD_ON_UPDATE_STRATEGY: MissingRemoteRecordDuringUpdateStrategy =\n 'insertNewRemoteRecord';\nconst SYNC_FIELDS = ['id', '_localId', 'updated_at', 'deleted'] as const;\n\n/**\n * Zync creates a standard persisted Zustand store with optional background sync (e.g. via RESTful, GraphQL, etc.).\n * It provides a `queueToSync()` method to enqueue changes for syncing, which are processed on an interval.\n *\n * This is a drop-in replacement for Zustand's `persist()` middleware that wires in background sync. It's usage mirrors\n * `persist(stateCreator, options)` with optional syncing per state key.\n *\n * Can be used with any storage that Zustand Persist supports. Options include localStorage if its syncronous access doesn't\n * cause blocking issues like UI freezes, or IndexedDB with its asynchronous access for improved performance.\n *\n * When using IndexedDB the whole store is saved under one key, which means indexes cannot be used to accelerate querying. However, if this\n * 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\n * provide the syntax for high performance queries.\n *\n * Zync maintains the following additional state:\n *\n * [Private]\n * - _pendingChanges: A queue of changes to be synced with the backend.\n * - _lastPulled: A timestamp of the last successful pull from the backend.\n * [Public]\n * - syncState: Access to sync status, errors, and a method to enable/disable syncing.\n * i.e. const syncState = useStore((state) => state.syncState);\n *\n * Design principles:\n *\n * - Always pull (list) first each sync cycle to enable future conflict resolution. Currently last-write-wins, although any queued client changes\n * for an item will prevent it being overwritten during a pull, even if the server has a newer version.\n * - Then push queued changes in order (Create -> Update -> Remove).\n * - Queue coalescing: (Create + Update*) => single Create (merged changes); (Create + Remove) => drop both; (Update + Update) => merge; (Update + Remove) => Remove.\n *\n * Synced objects are expected to have the following server fields:\n *\n * - id: The unique identifier from the server.\n * - updated_at: A timestamp indicating when the object was last updated.\n * This field is used to determine if the object needs to be re-fetched from the server.\n * It must be set at the server (e.g. via sql trigger or in the api code).\n * Ensure the server sets a timestamp with millisecond precision, not microsecond like PostgreSQL's timestampz,\n * as Javascript's Date object is based on milliseconds, and this will be used during sync.\n * Although the client can set this locally, it is only to give a good UX, as it won't be sent\n * to the server and will be overwritten on the client during sync. The client clock is never\n * used to check for changes as it can't be guaranteed to be in sync with the server clock. Instead any item\n * that is added, updated or deleted locally is added to a queue.\n * - deleted: A boolean flag indicating whether the object has been deleted. This use of soft deletes or similar\n * is how all clients are told about deletions during sync.\n *\n * Synced objects will have the field `_localId` on the client only, which provides a stable identifier for the object.\n * It is ideal for use as JSX keys.\n *\n * @param stateCreator - The function to create the initial state.\n * @param persistOptions - Standard Zustand options for persisting the store.\n * @param syncApi - Remote API functions for syncing state. Use the same key name as the state key.\n * e.g. if your state key is called `fish`, the syncApi should be `fish: { list, add, update, remove }`\n * If you don't provide a key for a state field, it won't be synced, but will be persisted as expected.\n * @param syncOptions - Syncing options (Optional).\n */\nexport function persistWithSync<TStore extends object>(\n stateCreator: SyncedStateCreator<TStore>,\n persistOptions: any,\n syncApi: Record<string, ApiFunctions>,\n syncOptions: SyncOptions = {},\n): StateCreator<TStore & SyncState, [], []> {\n const syncInterval = syncOptions.syncInterval ?? DEFAULT_SYNC_INTERVAL_MILLIS;\n const missingStrategy =\n syncOptions.missingRemoteRecordDuringUpdateStrategy ?? DEFAULT_MISSING_REMOTE_RECORD_ON_UPDATE_STRATEGY;\n const logger = newLogger(syncOptions.logger ?? DEFAULT_LOGGER, syncOptions.minLogLevel ?? DEFAULT_MIN_LOG_LEVEL);\n\n let startSync: (() => Promise<void>) | undefined;\n let syncIntervalId: any;\n\n const baseOnRehydrate = persistOptions?.onRehydrateStorage;\n const basePartialize = persistOptions?.partialize;\n\n const wrappedPersistOptions = {\n ...persistOptions,\n onRehydrateStorage: () => {\n logger.debug('[persistWithSync] Rehydration started');\n\n return (state: any, error: any) => {\n if (error) {\n logger.error('[persistWithSync] Rehydration failed', error);\n } else {\n baseOnRehydrate?.(state, error);\n logger.debug('[persistWithSync] Rehydration complete');\n startSync?.();\n }\n };\n },\n partialize: (s: any) => {\n // Select state to be persisted\n\n const base = basePartialize ? basePartialize(s) : s;\n const { syncState: _sync, ...rest } = base || {};\n return {\n ...rest,\n syncState: {\n firstLoadDone: _sync?.firstLoadDone ?? false,\n },\n };\n },\n merge: (persisted: any, current: any) => {\n // Add unpersistable fields back e.g. functions or memory-only fields\n\n const p = persisted || {};\n const c = current || {};\n return {\n ...c,\n ...p,\n syncState: {\n ...c.syncState,\n firstLoadDone: p.syncState?.firstLoadDone ?? c.syncState?.firstLoadDone ?? false,\n status: 'idle',\n },\n };\n },\n };\n\n const creator: StateCreator<TStore & SyncState, [], []> = (set: any, get: any) => {\n async function syncOnce() {\n const state: SyncState = get();\n if (!state.syncState.enabled || state.syncState.status !== 'idle') return;\n\n set((state: any) => {\n return {\n syncState: {\n ...state.syncState,\n status: 'syncing',\n },\n } as SyncState;\n });\n\n let syncError: Error | undefined;\n\n // 1) PULL for each stateKey\n for (const stateKey of Object.keys(syncApi)) {\n try {\n const api = findApi(stateKey, syncApi);\n await pull(set, get, stateKey, api, logger);\n } catch (err) {\n syncError = syncError ?? (err as Error);\n logger.error(`[persistWithSync] Pull error for stateKey: ${stateKey}`, err);\n }\n }\n\n // 2) PUSH queued changes\n const snapshot: PendingChange[] = [...(get()._pendingChanges || [])];\n\n // Deterministic ordering: Create -> Update -> Remove so dependencies (e.g. id assignment) happen early\n snapshot.sort((a, b) => orderFor(a.action) - orderFor(b.action));\n\n for (const change of snapshot) {\n try {\n const api = findApi(change.stateKey, syncApi); \n await pushOne(\n set,\n get,\n change,\n api,\n logger,\n queueToSync,\n missingStrategy,\n syncOptions.onMissingRemoteRecordDuringUpdate,\n syncOptions.onAfterRemoteAdd,\n );\n } catch (err) {\n syncError = syncError ?? (err as Error);\n logger.error(`[persistWithSync] Push error for change: ${change}`, err);\n }\n }\n\n set((state: any) => {\n return {\n syncState: {\n ...state.syncState,\n status: 'idle',\n error: syncError,\n },\n } as SyncState;\n });\n }\n\n startSync = async () => {\n clearInterval(syncIntervalId);\n syncIntervalId = undefined;\n await syncOnce();\n syncIntervalId = setInterval(syncOnce, syncInterval);\n };\n\n function queueToSync(action: SyncAction, localId: string, stateKey: string, changes?: any) {\n set((state: any) => {\n const queue: PendingChange[] = state._pendingChanges || [];\n const idx = queue.findIndex((p) => p.localId === localId && p.stateKey === stateKey);\n if (idx >= 0) {\n const existing = queue[idx];\n switch (existing?.action) {\n case SyncAction.Create:\n if (action === SyncAction.Update) {\n existing.changes = { ...existing.changes, ...changes };\n } else if (action === SyncAction.Remove) {\n queue.splice(idx, 1); // cancel create\n }\n break;\n case SyncAction.Update:\n if (action === SyncAction.Update) {\n existing.changes = { ...existing.changes, ...changes };\n } else if (action === SyncAction.Remove) {\n existing.action = SyncAction.Remove;\n delete existing.changes;\n }\n break;\n case SyncAction.Remove:\n // terminal; ignore further updates\n break;\n }\n } else {\n if (action === SyncAction.Remove) {\n // Add id to changes as when pushOne() processes the queue it may not find the item if using IndexedDB,\n // as it's async and so may have deleted the item already\n const item = state[stateKey].find((i: any) => i._localId === localId);\n if (item) changes = { id: item.id };\n }\n\n queue.push({ stateKey, localId, action, changes });\n }\n state._pendingChanges = queue;\n return state;\n });\n syncOnce();\n }\n\n function setAndSync(partial: any) {\n set(partial);\n syncOnce();\n }\n\n function enableSync(enabled: boolean) {\n set((state: any) => {\n return {\n syncState: {\n ...state.syncState,\n enabled,\n },\n } as SyncState;\n });\n syncOnce();\n }\n\n document.addEventListener('visibilitychange', async () => {\n clearInterval(syncIntervalId);\n if (document.visibilityState === 'visible') {\n logger.debug('[persistWithSync] Sync starting now app is in foreground');\n await startSync?.();\n } else {\n logger.debug('[persistWithSync] Sync paused now app is in background');\n }\n });\n\n async function startFirstLoad() {\n let syncError: Error | undefined;\n\n for (const stateKey of Object.keys(syncApi)) {\n try {\n logger.info(`[persistWithSync] firstLoad:start for stateKey: ${stateKey}`);\n\n const api = findApi(stateKey, syncApi);\n let lastId; // Start as undefined to allow the userland api code to set the initial value+type\n\n // Batch until empty\n while (true) {\n const batch = await api.firstLoad(lastId);\n if (!batch?.length) break;\n\n // Merge batch\n set((state: any) => {\n const local: any[] = state[stateKey] || [];\n const localById = new Map<any, any>(local.filter((l) => l.id).map((l) => [l.id, l]));\n\n let newest = new Date(state._lastPulled?.[stateKey] || 0);\n const next = [...local];\n for (const remote of batch) {\n const remoteUpdated = new Date(remote.updated_at || 0);\n if (remoteUpdated > newest) newest = remoteUpdated;\n\n if (remote.deleted) continue;\n\n const localItem = remote.id ? localById.get(remote.id) : undefined;\n if (localItem) {\n const merged = { ...localItem, ...remote, _localId: localItem._localId };\n const idx = next.findIndex((i) => i._localId === localItem._localId);\n if (idx >= 0) next[idx] = merged;\n } else {\n next.push({ ...remote, _localId: nextLocalId() });\n }\n }\n\n state[stateKey] = next;\n state._lastPulled = {\n ...(state._lastPulled || {}),\n [stateKey]: newest.toISOString(),\n };\n return state;\n });\n\n lastId = batch[batch.length - 1].id;\n }\n\n logger.info(`[persistWithSync] firstLoad:done for stateKey: ${stateKey}`);\n } catch (err) {\n syncError = syncError ?? (err as Error);\n logger.error(`[persistWithSync] First load pull error for stateKey: ${stateKey}`, err);\n }\n }\n\n set((state: any) => {\n return {\n syncState: { ...state.syncState, firstLoadDone: true },\n syncError,\n };\n });\n }\n\n const userState = stateCreator(setAndSync, get, queueToSync) as TStore & SyncState & _SyncState;\n\n // Always inject sync methods and sensible defaults even if a persisted syncState object exists\n const syncState: Partial<SyncState['syncState']> = userState.syncState || {};\n return {\n ...userState,\n syncState: {\n status: syncState.status ?? 'hydrating',\n error: syncState.error,\n enabled: syncState.enabled ?? false,\n firstLoadDone: syncState.firstLoadDone ?? false,\n enableSync,\n startFirstLoad,\n },\n _pendingChanges: userState._pendingChanges ?? [],\n _lastPulled: userState._lastPulled ?? {},\n } as TStore & SyncState & _SyncState;\n };\n\n return persist(creator as any, wrappedPersistOptions) as unknown as StateCreator<TStore & SyncState, [], []>;\n}\n\nfunction orderFor(a: SyncAction): number {\n switch (a) {\n case SyncAction.Create:\n return 1;\n case SyncAction.Update:\n return 2;\n case SyncAction.Remove:\n return 3;\n }\n}\n\nasync function pull(set: any, get: any, stateKey: string, api: ApiFunctions, logger: Logger) {\n const lastPulled: Record<string, string> = get()._lastPulled || {};\n const lastPulledAt = new Date(lastPulled[stateKey] || new Date(0));\n\n logger.debug(`[persistWithSync] pull:start stateKey=${stateKey} since=${lastPulledAt.toISOString()}`);\n\n const serverData = await api.list(lastPulledAt);\n if (!serverData?.length) return;\n\n let newest = lastPulledAt;\n set((state: any) => {\n const _pendingChanges: PendingChange[] = state._pendingChanges || [];\n const local: any[] = state[stateKey] || [];\n const localById = new Map<any, any>(local.filter((l) => l.id).map((l) => [l.id, l]));\n // Collect remote ids that have a pending local Remove so we don't resurrect them before push executes\n const pendingRemovals = new Set(\n _pendingChanges\n .filter(\n (p: PendingChange) =>\n p.stateKey === stateKey &&\n p.action === SyncAction.Remove &&\n p.changes &&\n typeof p.changes.id !== 'undefined',\n )\n .map((p: PendingChange) => p.changes.id),\n );\n\n for (const remote of serverData) {\n const remoteUpdated = new Date(remote.updated_at);\n if (remoteUpdated > newest) newest = remoteUpdated;\n\n const localItem = localById.get(remote.id);\n // If a Remove is pending for this id, skip merging/adding to avoid flicker\n if (pendingRemovals.has(remote.id)) {\n logger.debug(`[persistWithSync] pull:skip-pending-remove stateKey=${stateKey} id=${remote.id}`);\n continue;\n }\n if (remote.deleted) {\n if (localItem) {\n state[stateKey] = state[stateKey].filter((i: any) => i.id !== remote.id);\n logger.debug(`[persistWithSync] pull:remove stateKey=${stateKey} id=${remote.id}`);\n }\n continue;\n }\n\n const pending = _pendingChanges.some(\n (p: PendingChange) => p.stateKey === stateKey && localItem && p.localId === localItem._localId,\n );\n if (localItem && !pending) {\n const merged = { ...localItem, ...remote, _localId: localItem._localId };\n state[stateKey] = state[stateKey].map((i: any) => (i._localId === localItem._localId ? merged : i));\n logger.debug(`[persistWithSync] pull:merge stateKey=${stateKey} id=${remote.id}`);\n } else if (!localItem) {\n // Add remote item (no local or pending collisions)\n state[stateKey] = [...state[stateKey], { ...remote, _localId: nextLocalId() }];\n logger.debug(`[persistWithSync] pull:add stateKey=${stateKey} id=${remote.id}`);\n }\n }\n\n state._lastPulled = {\n ...state._lastPulled,\n [stateKey]: newest.toISOString(),\n };\n return state;\n });\n}\n\nasync function pushOne(\n set: any,\n get: any,\n change: PendingChange,\n api: ApiFunctions,\n logger: Logger,\n queueToSync: QueueToSyncCallback,\n missingStrategy: MissingRemoteRecordDuringUpdateStrategy,\n onMissingRemoteRecordDuringUpdate?: MissingRemoteRecordDuringUpdateCallback,\n onAfterRemoteAdd?: AfterRemoteAddCallback,\n) {\n logger.debug(\n `[persistWithSync] push:attempt action=${change.action} stateKey=${change.stateKey} localId=${change.localId}`,\n );\n\n const { stateKey, localId, action, changes } = change;\n const state = get();\n const items: SyncedRecord[] = state[stateKey] || [];\n const item = items.find((i) => i._localId === localId);\n\n switch (action) {\n case SyncAction.Create: {\n if (!item) {\n removeFromPendingChanges(set, localId, stateKey);\n return;\n }\n if (item.id) {\n if (changes && Object.keys(changes).length) {\n await api.update(item.id, omitSyncFields({ ...item, ...changes }));\n }\n removeFromPendingChanges(set, localId, stateKey);\n return;\n }\n const payload = changes ? { ...item, ...changes } : item;\n const result = await api.add(omitSyncFields(payload));\n if (result) {\n logger.debug('[persistWithSync] push:create:success', { stateKey, localId, id: result.id });\n // Merge server-assigned fields (id, updated_at, etc) directly into local entity\n set((s: any) => {\n s[stateKey] = (s[stateKey] || []).map((i: any) =>\n i._localId === localId ? { ...i, ...result } : i,\n );\n return s;\n });\n // Call hook so userland can perform any cascading adjustments\n onAfterRemoteAdd?.(set, get, queueToSync, stateKey, { ...item, ...result });\n } else {\n logger.warn('[persistWithSync] push:create:no-result', { stateKey, localId });\n }\n removeFromPendingChanges(set, localId, stateKey);\n break;\n }\n case SyncAction.Update: {\n if (!item) {\n removeFromPendingChanges(set, localId, stateKey);\n return;\n }\n if (!item.id) {\n // promote to create\n set((s: any) => {\n const q: PendingChange[] = s._pendingChanges || [];\n const e = q.find((p) => p.localId === localId && p.stateKey === stateKey);\n if (e) e.action = SyncAction.Create;\n return s;\n });\n return;\n }\n const changed = await api.update(item.id, omitSyncFields({ ...changes }));\n if (!changed) {\n logger.warn('[persistWithSync] push:update:missingRemote', { stateKey, localId, id: item.id });\n const oldRecord = { ...item } as SyncedRecord;\n let newLocalId: string | undefined;\n switch (missingStrategy) {\n case 'deleteLocalRecord':\n set((s: any) => {\n s[stateKey] = (s[stateKey] || []).filter((i: any) => i._localId !== localId);\n return s;\n });\n removeFromPendingChanges(set, localId, stateKey);\n break;\n case 'insertNewRemoteRecord': {\n const freshLocalId = nextLocalId();\n newLocalId = freshLocalId;\n set((s: any) => {\n // remove old, add new copy without id so it becomes a Create\n s[stateKey] = (s[stateKey] || []).filter((i: any) => i._localId !== localId);\n s[stateKey].push({\n ...omitSyncFields(oldRecord),\n _localId: freshLocalId,\n updated_at: new Date().toISOString(),\n });\n // update queue entry\n const q: PendingChange[] = s._pendingChanges || [];\n const e = q.find((p) => p.localId === localId && p.stateKey === stateKey);\n if (e) {\n e.localId = freshLocalId;\n e.action = SyncAction.Create;\n } else {\n q.push({ stateKey, localId: freshLocalId, action: SyncAction.Create, changes });\n }\n s._pendingChanges = q;\n return s;\n });\n break;\n }\n }\n // Call hook so userland can alert the user etc.\n onMissingRemoteRecordDuringUpdate?.(missingStrategy, oldRecord, newLocalId);\n } else {\n logger.debug('[persistWithSync] push:update:success', { stateKey, localId, id: item.id });\n removeFromPendingChanges(set, localId, stateKey);\n }\n break;\n }\n case SyncAction.Remove: {\n const id = changes?.id;\n if (id) {\n await api.remove(id);\n logger.debug('[persistWithSync] push:remove:success', { stateKey, localId, id });\n }\n removeFromPendingChanges(set, localId, stateKey);\n break;\n }\n }\n}\n\nfunction removeFromPendingChanges(set: any, localId: string, stateKey: string) {\n set((s: any) => {\n s._pendingChanges = (s._pendingChanges || []).filter(\n (p: PendingChange) => !(p.localId === localId && p.stateKey === stateKey),\n );\n return s;\n });\n}\n\nfunction omitSyncFields(item: any) {\n const result = { ...item };\n for (const k of SYNC_FIELDS) delete result[k];\n return result;\n}\n\nexport function nextLocalId(): string {\n return crypto.randomUUID();\n}\n\nfunction newLogger(logger: Logger, min: LogLevel): Logger {\n const order: Record<LogLevel, number> = { debug: 10, info: 20, warn: 30, error: 40, none: 100 };\n const threshold = order[min];\n const enabled = (lvl: LogLevel) => order[lvl] >= threshold;\n return {\n debug: (...a) => enabled('debug') && logger.debug?.(...a),\n info: (...a) => enabled('info') && logger.info?.(...a),\n warn: (...a) => enabled('warn') && logger.warn?.(...a),\n error: (...a) => enabled('error') && logger.error?.(...a),\n };\n}\n\nfunction findApi(stateKey: string, syncApi: Record<string, ApiFunctions>) {\n const api = syncApi[stateKey];\n if (!api || !api.add || !api.update || !api.remove || !api.list || !api.firstLoad) {\n throw new Error(`Missing API function(s) for state key: ${stateKey}.`);\n }\n return api;\n}\n\n"],"mappings":"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","names":["SyncAction","state"]}