@anfenn/zync 0.1.3 → 0.1.4
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 +152 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +38 -1
- package/dist/index.d.ts +38 -1
- package/dist/index.js +151 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -21,12 +21,163 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
SyncAction: () => SyncAction,
|
|
24
|
+
createIndexedDBStorage: () => createIndexedDBStorage,
|
|
24
25
|
nextLocalId: () => nextLocalId,
|
|
25
26
|
persistWithSync: () => persistWithSync
|
|
26
27
|
});
|
|
27
28
|
module.exports = __toCommonJS(index_exports);
|
|
28
29
|
var import_zustand = require("zustand");
|
|
29
30
|
var import_middleware = require("zustand/middleware");
|
|
31
|
+
|
|
32
|
+
// src/indexedDBStorage.ts
|
|
33
|
+
function createIndexedDBStorage(options = {}) {
|
|
34
|
+
if (typeof globalThis.localStorage === "undefined") {
|
|
35
|
+
const mem = {};
|
|
36
|
+
globalThis.localStorage = {
|
|
37
|
+
getItem: (k) => k in mem ? mem[k] : null,
|
|
38
|
+
setItem: (k, v) => {
|
|
39
|
+
mem[k] = v;
|
|
40
|
+
},
|
|
41
|
+
removeItem: (k) => {
|
|
42
|
+
delete mem[k];
|
|
43
|
+
},
|
|
44
|
+
clear: () => {
|
|
45
|
+
Object.keys(mem).forEach((k) => delete mem[k]);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
const dbName = options.dbName ?? "zustand-persist";
|
|
50
|
+
const storeName = options.storeName ?? "keyval";
|
|
51
|
+
const version = options.version ?? 1;
|
|
52
|
+
const log = options.logger ?? { warn: () => {
|
|
53
|
+
}, error: () => {
|
|
54
|
+
} };
|
|
55
|
+
const canUseIDB = typeof window !== "undefined" && "indexedDB" in window;
|
|
56
|
+
if (!canUseIDB) {
|
|
57
|
+
log.warn("[indexedDBStorage] IndexedDB not available \u2013 falling back to localStorage");
|
|
58
|
+
return localStorageFallback();
|
|
59
|
+
}
|
|
60
|
+
let dbPromise;
|
|
61
|
+
function openDB() {
|
|
62
|
+
if (dbPromise) return dbPromise;
|
|
63
|
+
dbPromise = new Promise((resolve, reject) => {
|
|
64
|
+
try {
|
|
65
|
+
const req = indexedDB.open(dbName, version);
|
|
66
|
+
req.onupgradeneeded = () => {
|
|
67
|
+
const db = req.result;
|
|
68
|
+
if (!db.objectStoreNames.contains(storeName)) {
|
|
69
|
+
db.createObjectStore(storeName);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
req.onsuccess = () => resolve(req.result);
|
|
73
|
+
req.onerror = () => reject(req.error);
|
|
74
|
+
req.onblocked = () => log.warn("[indexedDBStorage] open blocked");
|
|
75
|
+
} catch (e) {
|
|
76
|
+
reject(e);
|
|
77
|
+
}
|
|
78
|
+
}).catch((e) => {
|
|
79
|
+
log.error("[indexedDBStorage] Failed to open DB, falling back to localStorage", e);
|
|
80
|
+
return void 0;
|
|
81
|
+
});
|
|
82
|
+
return dbPromise;
|
|
83
|
+
}
|
|
84
|
+
async function withStore(mode, fn) {
|
|
85
|
+
const db = await openDB();
|
|
86
|
+
if (!db) {
|
|
87
|
+
return fn(null);
|
|
88
|
+
}
|
|
89
|
+
return new Promise((resolve, reject) => {
|
|
90
|
+
let tx;
|
|
91
|
+
try {
|
|
92
|
+
tx = db.transaction(storeName, mode);
|
|
93
|
+
} catch (_e) {
|
|
94
|
+
dbPromise = void 0;
|
|
95
|
+
openDB().then((fresh) => {
|
|
96
|
+
try {
|
|
97
|
+
if (!fresh) {
|
|
98
|
+
const result3 = fn(null);
|
|
99
|
+
Promise.resolve(result3).then(resolve, reject);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const retryTx = fresh.transaction(storeName, mode);
|
|
103
|
+
const store2 = retryTx.objectStore(storeName);
|
|
104
|
+
const result2 = fn(store2);
|
|
105
|
+
Promise.resolve(result2).then(resolve, reject);
|
|
106
|
+
} catch (err) {
|
|
107
|
+
reject(err);
|
|
108
|
+
}
|
|
109
|
+
}, reject);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const store = tx.objectStore(storeName);
|
|
113
|
+
let result;
|
|
114
|
+
try {
|
|
115
|
+
result = fn(store);
|
|
116
|
+
} catch (err) {
|
|
117
|
+
reject(err);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
tx.oncomplete = () => resolve(result);
|
|
121
|
+
tx.onerror = () => reject(tx.error);
|
|
122
|
+
tx.onabort = () => reject(tx.error || new Error("Transaction aborted"));
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
async function getItem(name) {
|
|
126
|
+
return withStore("readonly", (store) => {
|
|
127
|
+
console.log("READ STORE");
|
|
128
|
+
if (!store) return localStorage.getItem(name);
|
|
129
|
+
return new Promise((resolve, reject) => {
|
|
130
|
+
const req = store.get(name);
|
|
131
|
+
req.onsuccess = () => resolve(req.result ?? null);
|
|
132
|
+
req.onerror = () => reject(req.error);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
async function setItem(name, value) {
|
|
137
|
+
return withStore("readwrite", (store) => {
|
|
138
|
+
console.log("WRITE STORE");
|
|
139
|
+
if (!store) {
|
|
140
|
+
localStorage.setItem(name, value);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
return new Promise((resolve, reject) => {
|
|
144
|
+
const req = store.put(value, name);
|
|
145
|
+
req.onsuccess = () => resolve();
|
|
146
|
+
req.onerror = () => reject(req.error);
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
async function removeItem(name) {
|
|
151
|
+
return withStore("readwrite", (store) => {
|
|
152
|
+
if (!store) {
|
|
153
|
+
localStorage.removeItem(name);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
return new Promise((resolve, reject) => {
|
|
157
|
+
const req = store.delete(name);
|
|
158
|
+
req.onsuccess = () => resolve();
|
|
159
|
+
req.onerror = () => reject(req.error);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
return { getItem, setItem, removeItem };
|
|
164
|
+
}
|
|
165
|
+
function localStorageFallback() {
|
|
166
|
+
const ls = globalThis.localStorage;
|
|
167
|
+
return {
|
|
168
|
+
getItem: (n) => Promise.resolve(ls?.getItem ? ls.getItem(n) : null),
|
|
169
|
+
setItem: (n, v) => {
|
|
170
|
+
ls?.setItem?.(n, v);
|
|
171
|
+
return Promise.resolve();
|
|
172
|
+
},
|
|
173
|
+
removeItem: (n) => {
|
|
174
|
+
ls?.removeItem?.(n);
|
|
175
|
+
return Promise.resolve();
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// src/index.ts
|
|
30
181
|
var SyncAction = /* @__PURE__ */ ((SyncAction2) => {
|
|
31
182
|
SyncAction2["Create"] = "create";
|
|
32
183
|
SyncAction2["Update"] = "update";
|
|
@@ -476,6 +627,7 @@ function findApi(stateKey, syncApi) {
|
|
|
476
627
|
// Annotate the CommonJS export names for ESM import in node:
|
|
477
628
|
0 && (module.exports = {
|
|
478
629
|
SyncAction,
|
|
630
|
+
createIndexedDBStorage,
|
|
479
631
|
nextLocalId,
|
|
480
632
|
persistWithSync
|
|
481
633
|
});
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +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"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/indexedDBStorage.ts"],"sourcesContent":["import { type StateCreator } from 'zustand';\nimport { persist } from 'zustand/middleware';\n\nexport { createIndexedDBStorage, type IndexedDBStorageOptions } from './indexedDBStorage';\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","/**\n * IndexedDB storage adapter for Zustand's persist middleware.\n *\n * Usage:\n * import { persist, createJSONStorage } from 'zustand/middleware';\n * import { createIndexedDBStorage } from './indexedDBStorage';\n *\n * const storage = createJSONStorage(() => createIndexedDBStorage({ dbName: 'app', storeName: 'persist' }));\n *\n * persist(myCreator, { name: 'store', storage })\n *\n * Design goals:\n * - Non‑blocking: all operations are async and off the main microtask once IndexedDB request queued.\n * - Reuses a single opened IDBDatabase instance (lazy) to avoid repeated open costs.\n * - Graceful fallback to localStorage if IndexedDB unavailable or open fails (e.g. Safari private mode).\n * - Small, dependency free, typed.\n */\n\nexport interface IndexedDBStorageOptions {\n /** Database name (default: 'zustand-persist') */\n dbName?: string;\n /** Object store name (default: 'keyval') */\n storeName?: string;\n /** IndexedDB version (default: 1) */\n version?: number;\n /** Optional logger (console-like) */\n logger?: { warn: (...a: any[]) => void; error: (...a: any[]) => void };\n}\n\nexport type ZustandStateStorage = {\n getItem: (name: string) => Promise<string | null> | string | null;\n setItem: (name: string, value: string) => Promise<void> | void;\n removeItem: (name: string) => Promise<void> | void;\n};\n\nexport function createIndexedDBStorage(options: IndexedDBStorageOptions = {}): ZustandStateStorage {\n // Provide a minimal localStorage polyfill for non-browser (test / SSR) environments.\n if (typeof globalThis.localStorage === 'undefined') {\n const mem: Record<string, string> = {};\n (globalThis as any).localStorage = {\n getItem: (k: string) => (k in mem ? mem[k] : null),\n setItem: (k: string, v: string) => {\n mem[k] = v;\n },\n removeItem: (k: string) => {\n delete mem[k];\n },\n clear: () => {\n Object.keys(mem).forEach((k) => delete mem[k]);\n },\n } as Storage;\n }\n const dbName = options.dbName ?? 'zustand-persist';\n const storeName = options.storeName ?? 'keyval';\n const version = options.version ?? 1;\n const log = options.logger ?? { warn: () => {}, error: () => {} };\n\n // Fallback detection\n const canUseIDB = typeof window !== 'undefined' && 'indexedDB' in window;\n if (!canUseIDB) {\n log.warn('[indexedDBStorage] IndexedDB not available – falling back to localStorage');\n return localStorageFallback();\n }\n\n let dbPromise: Promise<IDBDatabase | undefined> | undefined;\n\n function openDB(): Promise<IDBDatabase | undefined> {\n if (dbPromise) return dbPromise;\n dbPromise = new Promise<IDBDatabase | undefined>((resolve, reject) => {\n try {\n const req = indexedDB.open(dbName, version);\n req.onupgradeneeded = () => {\n const db = req.result;\n if (!db.objectStoreNames.contains(storeName)) {\n db.createObjectStore(storeName);\n }\n };\n req.onsuccess = () => resolve(req.result);\n req.onerror = () => reject(req.error);\n req.onblocked = () => log.warn('[indexedDBStorage] open blocked');\n } catch (e) {\n reject(e);\n }\n }).catch((e) => {\n log.error('[indexedDBStorage] Failed to open DB, falling back to localStorage', e);\n return undefined; // handled below\n });\n return dbPromise;\n }\n\n async function withStore<T>(mode: IDBTransactionMode, fn: (store: IDBObjectStore) => T | Promise<T>): Promise<T> {\n const db = await openDB();\n if (!db) {\n // Fallback path (should be rare: open failed)\n return (fn as any)(null);\n }\n return new Promise<T>((resolve, reject) => {\n let tx: IDBTransaction;\n try {\n tx = db.transaction(storeName, mode);\n } catch (_e) {\n // Possibly closed / version change: clear cached promise & retry once\n dbPromise = undefined;\n openDB().then((fresh) => {\n try {\n if (!fresh) {\n // Fall back to invoking fn with null (localStorage path)\n const result = (fn as any)(null);\n Promise.resolve(result).then(resolve, reject);\n return;\n }\n const retryTx = fresh.transaction(storeName, mode);\n const store = retryTx.objectStore(storeName);\n const result = fn(store);\n Promise.resolve(result).then(resolve, reject);\n } catch (err) {\n reject(err);\n }\n }, reject);\n return;\n }\n const store = tx.objectStore(storeName);\n let result: any;\n try {\n result = fn(store);\n } catch (err) {\n reject(err);\n return;\n }\n tx.oncomplete = () => resolve(result);\n tx.onerror = () => reject(tx.error);\n tx.onabort = () => reject(tx.error || new Error('Transaction aborted'));\n });\n }\n\n async function getItem(name: string): Promise<string | null> {\n return withStore('readonly', (store) => {\n console.log('READ STORE');\n if (!store) return localStorage.getItem(name);\n return new Promise<string | null>((resolve, reject) => {\n const req = store.get(name);\n req.onsuccess = () => resolve(req.result ?? null);\n req.onerror = () => reject(req.error);\n });\n });\n }\n\n async function setItem(name: string, value: string): Promise<void> {\n return withStore('readwrite', (store) => {\n console.log('WRITE STORE');\n if (!store) {\n localStorage.setItem(name, value);\n return;\n }\n return new Promise<void>((resolve, reject) => {\n const req = store.put(value, name);\n req.onsuccess = () => resolve();\n req.onerror = () => reject(req.error);\n });\n });\n }\n\n async function removeItem(name: string): Promise<void> {\n return withStore('readwrite', (store) => {\n if (!store) {\n localStorage.removeItem(name);\n return;\n }\n return new Promise<void>((resolve, reject) => {\n const req = store.delete(name);\n req.onsuccess = () => resolve();\n req.onerror = () => reject(req.error);\n });\n });\n }\n\n return { getItem, setItem, removeItem };\n}\n\nfunction localStorageFallback(): ZustandStateStorage {\n // Capture reference now so if global is cleaned up (test teardown) we still work\n const ls: any = (globalThis as any).localStorage;\n return {\n getItem: (n) => Promise.resolve(ls?.getItem ? ls.getItem(n) : null),\n setItem: (n, v) => {\n ls?.setItem?.(n, v);\n return Promise.resolve();\n },\n removeItem: (n) => {\n ls?.removeItem?.(n);\n return Promise.resolve();\n },\n };\n}\n"],"mappings":"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","names":["result","store","SyncAction","state"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
import { StateCreator } from 'zustand';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* IndexedDB storage adapter for Zustand's persist middleware.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* import { persist, createJSONStorage } from 'zustand/middleware';
|
|
8
|
+
* import { createIndexedDBStorage } from './indexedDBStorage';
|
|
9
|
+
*
|
|
10
|
+
* const storage = createJSONStorage(() => createIndexedDBStorage({ dbName: 'app', storeName: 'persist' }));
|
|
11
|
+
*
|
|
12
|
+
* persist(myCreator, { name: 'store', storage })
|
|
13
|
+
*
|
|
14
|
+
* Design goals:
|
|
15
|
+
* - Non‑blocking: all operations are async and off the main microtask once IndexedDB request queued.
|
|
16
|
+
* - Reuses a single opened IDBDatabase instance (lazy) to avoid repeated open costs.
|
|
17
|
+
* - Graceful fallback to localStorage if IndexedDB unavailable or open fails (e.g. Safari private mode).
|
|
18
|
+
* - Small, dependency free, typed.
|
|
19
|
+
*/
|
|
20
|
+
interface IndexedDBStorageOptions {
|
|
21
|
+
/** Database name (default: 'zustand-persist') */
|
|
22
|
+
dbName?: string;
|
|
23
|
+
/** Object store name (default: 'keyval') */
|
|
24
|
+
storeName?: string;
|
|
25
|
+
/** IndexedDB version (default: 1) */
|
|
26
|
+
version?: number;
|
|
27
|
+
/** Optional logger (console-like) */
|
|
28
|
+
logger?: {
|
|
29
|
+
warn: (...a: any[]) => void;
|
|
30
|
+
error: (...a: any[]) => void;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
type ZustandStateStorage = {
|
|
34
|
+
getItem: (name: string) => Promise<string | null> | string | null;
|
|
35
|
+
setItem: (name: string, value: string) => Promise<void> | void;
|
|
36
|
+
removeItem: (name: string) => Promise<void> | void;
|
|
37
|
+
};
|
|
38
|
+
declare function createIndexedDBStorage(options?: IndexedDBStorageOptions): ZustandStateStorage;
|
|
39
|
+
|
|
3
40
|
type SyncedRecord = {
|
|
4
41
|
id?: any;
|
|
5
42
|
_localId: string;
|
|
@@ -107,4 +144,4 @@ type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'none';
|
|
|
107
144
|
declare function persistWithSync<TStore extends object>(stateCreator: SyncedStateCreator<TStore>, persistOptions: any, syncApi: Record<string, ApiFunctions>, syncOptions?: SyncOptions): StateCreator<TStore & SyncState, [], []>;
|
|
108
145
|
declare function nextLocalId(): string;
|
|
109
146
|
|
|
110
|
-
export { type ApiFunctions, type LogLevel, type Logger, type MissingRemoteRecordDuringUpdateStrategy, type QueueToSyncCallback, SyncAction, type SyncState, type SyncedRecord, nextLocalId, persistWithSync };
|
|
147
|
+
export { type ApiFunctions, type IndexedDBStorageOptions, type LogLevel, type Logger, type MissingRemoteRecordDuringUpdateStrategy, type QueueToSyncCallback, SyncAction, type SyncState, type SyncedRecord, createIndexedDBStorage, nextLocalId, persistWithSync };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
import { StateCreator } from 'zustand';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* IndexedDB storage adapter for Zustand's persist middleware.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* import { persist, createJSONStorage } from 'zustand/middleware';
|
|
8
|
+
* import { createIndexedDBStorage } from './indexedDBStorage';
|
|
9
|
+
*
|
|
10
|
+
* const storage = createJSONStorage(() => createIndexedDBStorage({ dbName: 'app', storeName: 'persist' }));
|
|
11
|
+
*
|
|
12
|
+
* persist(myCreator, { name: 'store', storage })
|
|
13
|
+
*
|
|
14
|
+
* Design goals:
|
|
15
|
+
* - Non‑blocking: all operations are async and off the main microtask once IndexedDB request queued.
|
|
16
|
+
* - Reuses a single opened IDBDatabase instance (lazy) to avoid repeated open costs.
|
|
17
|
+
* - Graceful fallback to localStorage if IndexedDB unavailable or open fails (e.g. Safari private mode).
|
|
18
|
+
* - Small, dependency free, typed.
|
|
19
|
+
*/
|
|
20
|
+
interface IndexedDBStorageOptions {
|
|
21
|
+
/** Database name (default: 'zustand-persist') */
|
|
22
|
+
dbName?: string;
|
|
23
|
+
/** Object store name (default: 'keyval') */
|
|
24
|
+
storeName?: string;
|
|
25
|
+
/** IndexedDB version (default: 1) */
|
|
26
|
+
version?: number;
|
|
27
|
+
/** Optional logger (console-like) */
|
|
28
|
+
logger?: {
|
|
29
|
+
warn: (...a: any[]) => void;
|
|
30
|
+
error: (...a: any[]) => void;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
type ZustandStateStorage = {
|
|
34
|
+
getItem: (name: string) => Promise<string | null> | string | null;
|
|
35
|
+
setItem: (name: string, value: string) => Promise<void> | void;
|
|
36
|
+
removeItem: (name: string) => Promise<void> | void;
|
|
37
|
+
};
|
|
38
|
+
declare function createIndexedDBStorage(options?: IndexedDBStorageOptions): ZustandStateStorage;
|
|
39
|
+
|
|
3
40
|
type SyncedRecord = {
|
|
4
41
|
id?: any;
|
|
5
42
|
_localId: string;
|
|
@@ -107,4 +144,4 @@ type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'none';
|
|
|
107
144
|
declare function persistWithSync<TStore extends object>(stateCreator: SyncedStateCreator<TStore>, persistOptions: any, syncApi: Record<string, ApiFunctions>, syncOptions?: SyncOptions): StateCreator<TStore & SyncState, [], []>;
|
|
108
145
|
declare function nextLocalId(): string;
|
|
109
146
|
|
|
110
|
-
export { type ApiFunctions, type LogLevel, type Logger, type MissingRemoteRecordDuringUpdateStrategy, type QueueToSyncCallback, SyncAction, type SyncState, type SyncedRecord, nextLocalId, persistWithSync };
|
|
147
|
+
export { type ApiFunctions, type IndexedDBStorageOptions, type LogLevel, type Logger, type MissingRemoteRecordDuringUpdateStrategy, type QueueToSyncCallback, SyncAction, type SyncState, type SyncedRecord, createIndexedDBStorage, nextLocalId, persistWithSync };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,156 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import "zustand";
|
|
3
3
|
import { persist } from "zustand/middleware";
|
|
4
|
+
|
|
5
|
+
// src/indexedDBStorage.ts
|
|
6
|
+
function createIndexedDBStorage(options = {}) {
|
|
7
|
+
if (typeof globalThis.localStorage === "undefined") {
|
|
8
|
+
const mem = {};
|
|
9
|
+
globalThis.localStorage = {
|
|
10
|
+
getItem: (k) => k in mem ? mem[k] : null,
|
|
11
|
+
setItem: (k, v) => {
|
|
12
|
+
mem[k] = v;
|
|
13
|
+
},
|
|
14
|
+
removeItem: (k) => {
|
|
15
|
+
delete mem[k];
|
|
16
|
+
},
|
|
17
|
+
clear: () => {
|
|
18
|
+
Object.keys(mem).forEach((k) => delete mem[k]);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
const dbName = options.dbName ?? "zustand-persist";
|
|
23
|
+
const storeName = options.storeName ?? "keyval";
|
|
24
|
+
const version = options.version ?? 1;
|
|
25
|
+
const log = options.logger ?? { warn: () => {
|
|
26
|
+
}, error: () => {
|
|
27
|
+
} };
|
|
28
|
+
const canUseIDB = typeof window !== "undefined" && "indexedDB" in window;
|
|
29
|
+
if (!canUseIDB) {
|
|
30
|
+
log.warn("[indexedDBStorage] IndexedDB not available \u2013 falling back to localStorage");
|
|
31
|
+
return localStorageFallback();
|
|
32
|
+
}
|
|
33
|
+
let dbPromise;
|
|
34
|
+
function openDB() {
|
|
35
|
+
if (dbPromise) return dbPromise;
|
|
36
|
+
dbPromise = new Promise((resolve, reject) => {
|
|
37
|
+
try {
|
|
38
|
+
const req = indexedDB.open(dbName, version);
|
|
39
|
+
req.onupgradeneeded = () => {
|
|
40
|
+
const db = req.result;
|
|
41
|
+
if (!db.objectStoreNames.contains(storeName)) {
|
|
42
|
+
db.createObjectStore(storeName);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
req.onsuccess = () => resolve(req.result);
|
|
46
|
+
req.onerror = () => reject(req.error);
|
|
47
|
+
req.onblocked = () => log.warn("[indexedDBStorage] open blocked");
|
|
48
|
+
} catch (e) {
|
|
49
|
+
reject(e);
|
|
50
|
+
}
|
|
51
|
+
}).catch((e) => {
|
|
52
|
+
log.error("[indexedDBStorage] Failed to open DB, falling back to localStorage", e);
|
|
53
|
+
return void 0;
|
|
54
|
+
});
|
|
55
|
+
return dbPromise;
|
|
56
|
+
}
|
|
57
|
+
async function withStore(mode, fn) {
|
|
58
|
+
const db = await openDB();
|
|
59
|
+
if (!db) {
|
|
60
|
+
return fn(null);
|
|
61
|
+
}
|
|
62
|
+
return new Promise((resolve, reject) => {
|
|
63
|
+
let tx;
|
|
64
|
+
try {
|
|
65
|
+
tx = db.transaction(storeName, mode);
|
|
66
|
+
} catch (_e) {
|
|
67
|
+
dbPromise = void 0;
|
|
68
|
+
openDB().then((fresh) => {
|
|
69
|
+
try {
|
|
70
|
+
if (!fresh) {
|
|
71
|
+
const result3 = fn(null);
|
|
72
|
+
Promise.resolve(result3).then(resolve, reject);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const retryTx = fresh.transaction(storeName, mode);
|
|
76
|
+
const store2 = retryTx.objectStore(storeName);
|
|
77
|
+
const result2 = fn(store2);
|
|
78
|
+
Promise.resolve(result2).then(resolve, reject);
|
|
79
|
+
} catch (err) {
|
|
80
|
+
reject(err);
|
|
81
|
+
}
|
|
82
|
+
}, reject);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const store = tx.objectStore(storeName);
|
|
86
|
+
let result;
|
|
87
|
+
try {
|
|
88
|
+
result = fn(store);
|
|
89
|
+
} catch (err) {
|
|
90
|
+
reject(err);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
tx.oncomplete = () => resolve(result);
|
|
94
|
+
tx.onerror = () => reject(tx.error);
|
|
95
|
+
tx.onabort = () => reject(tx.error || new Error("Transaction aborted"));
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
async function getItem(name) {
|
|
99
|
+
return withStore("readonly", (store) => {
|
|
100
|
+
console.log("READ STORE");
|
|
101
|
+
if (!store) return localStorage.getItem(name);
|
|
102
|
+
return new Promise((resolve, reject) => {
|
|
103
|
+
const req = store.get(name);
|
|
104
|
+
req.onsuccess = () => resolve(req.result ?? null);
|
|
105
|
+
req.onerror = () => reject(req.error);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
async function setItem(name, value) {
|
|
110
|
+
return withStore("readwrite", (store) => {
|
|
111
|
+
console.log("WRITE STORE");
|
|
112
|
+
if (!store) {
|
|
113
|
+
localStorage.setItem(name, value);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
return new Promise((resolve, reject) => {
|
|
117
|
+
const req = store.put(value, name);
|
|
118
|
+
req.onsuccess = () => resolve();
|
|
119
|
+
req.onerror = () => reject(req.error);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
async function removeItem(name) {
|
|
124
|
+
return withStore("readwrite", (store) => {
|
|
125
|
+
if (!store) {
|
|
126
|
+
localStorage.removeItem(name);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
return new Promise((resolve, reject) => {
|
|
130
|
+
const req = store.delete(name);
|
|
131
|
+
req.onsuccess = () => resolve();
|
|
132
|
+
req.onerror = () => reject(req.error);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
return { getItem, setItem, removeItem };
|
|
137
|
+
}
|
|
138
|
+
function localStorageFallback() {
|
|
139
|
+
const ls = globalThis.localStorage;
|
|
140
|
+
return {
|
|
141
|
+
getItem: (n) => Promise.resolve(ls?.getItem ? ls.getItem(n) : null),
|
|
142
|
+
setItem: (n, v) => {
|
|
143
|
+
ls?.setItem?.(n, v);
|
|
144
|
+
return Promise.resolve();
|
|
145
|
+
},
|
|
146
|
+
removeItem: (n) => {
|
|
147
|
+
ls?.removeItem?.(n);
|
|
148
|
+
return Promise.resolve();
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// src/index.ts
|
|
4
154
|
var SyncAction = /* @__PURE__ */ ((SyncAction2) => {
|
|
5
155
|
SyncAction2["Create"] = "create";
|
|
6
156
|
SyncAction2["Update"] = "update";
|
|
@@ -449,6 +599,7 @@ function findApi(stateKey, syncApi) {
|
|
|
449
599
|
}
|
|
450
600
|
export {
|
|
451
601
|
SyncAction,
|
|
602
|
+
createIndexedDBStorage,
|
|
452
603
|
nextLocalId,
|
|
453
604
|
persistWithSync
|
|
454
605
|
};
|
package/dist/index.js.map
CHANGED
|
@@ -1 +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"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/indexedDBStorage.ts"],"sourcesContent":["import { type StateCreator } from 'zustand';\nimport { persist } from 'zustand/middleware';\n\nexport { createIndexedDBStorage, type IndexedDBStorageOptions } from './indexedDBStorage';\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","/**\n * IndexedDB storage adapter for Zustand's persist middleware.\n *\n * Usage:\n * import { persist, createJSONStorage } from 'zustand/middleware';\n * import { createIndexedDBStorage } from './indexedDBStorage';\n *\n * const storage = createJSONStorage(() => createIndexedDBStorage({ dbName: 'app', storeName: 'persist' }));\n *\n * persist(myCreator, { name: 'store', storage })\n *\n * Design goals:\n * - Non‑blocking: all operations are async and off the main microtask once IndexedDB request queued.\n * - Reuses a single opened IDBDatabase instance (lazy) to avoid repeated open costs.\n * - Graceful fallback to localStorage if IndexedDB unavailable or open fails (e.g. Safari private mode).\n * - Small, dependency free, typed.\n */\n\nexport interface IndexedDBStorageOptions {\n /** Database name (default: 'zustand-persist') */\n dbName?: string;\n /** Object store name (default: 'keyval') */\n storeName?: string;\n /** IndexedDB version (default: 1) */\n version?: number;\n /** Optional logger (console-like) */\n logger?: { warn: (...a: any[]) => void; error: (...a: any[]) => void };\n}\n\nexport type ZustandStateStorage = {\n getItem: (name: string) => Promise<string | null> | string | null;\n setItem: (name: string, value: string) => Promise<void> | void;\n removeItem: (name: string) => Promise<void> | void;\n};\n\nexport function createIndexedDBStorage(options: IndexedDBStorageOptions = {}): ZustandStateStorage {\n // Provide a minimal localStorage polyfill for non-browser (test / SSR) environments.\n if (typeof globalThis.localStorage === 'undefined') {\n const mem: Record<string, string> = {};\n (globalThis as any).localStorage = {\n getItem: (k: string) => (k in mem ? mem[k] : null),\n setItem: (k: string, v: string) => {\n mem[k] = v;\n },\n removeItem: (k: string) => {\n delete mem[k];\n },\n clear: () => {\n Object.keys(mem).forEach((k) => delete mem[k]);\n },\n } as Storage;\n }\n const dbName = options.dbName ?? 'zustand-persist';\n const storeName = options.storeName ?? 'keyval';\n const version = options.version ?? 1;\n const log = options.logger ?? { warn: () => {}, error: () => {} };\n\n // Fallback detection\n const canUseIDB = typeof window !== 'undefined' && 'indexedDB' in window;\n if (!canUseIDB) {\n log.warn('[indexedDBStorage] IndexedDB not available – falling back to localStorage');\n return localStorageFallback();\n }\n\n let dbPromise: Promise<IDBDatabase | undefined> | undefined;\n\n function openDB(): Promise<IDBDatabase | undefined> {\n if (dbPromise) return dbPromise;\n dbPromise = new Promise<IDBDatabase | undefined>((resolve, reject) => {\n try {\n const req = indexedDB.open(dbName, version);\n req.onupgradeneeded = () => {\n const db = req.result;\n if (!db.objectStoreNames.contains(storeName)) {\n db.createObjectStore(storeName);\n }\n };\n req.onsuccess = () => resolve(req.result);\n req.onerror = () => reject(req.error);\n req.onblocked = () => log.warn('[indexedDBStorage] open blocked');\n } catch (e) {\n reject(e);\n }\n }).catch((e) => {\n log.error('[indexedDBStorage] Failed to open DB, falling back to localStorage', e);\n return undefined; // handled below\n });\n return dbPromise;\n }\n\n async function withStore<T>(mode: IDBTransactionMode, fn: (store: IDBObjectStore) => T | Promise<T>): Promise<T> {\n const db = await openDB();\n if (!db) {\n // Fallback path (should be rare: open failed)\n return (fn as any)(null);\n }\n return new Promise<T>((resolve, reject) => {\n let tx: IDBTransaction;\n try {\n tx = db.transaction(storeName, mode);\n } catch (_e) {\n // Possibly closed / version change: clear cached promise & retry once\n dbPromise = undefined;\n openDB().then((fresh) => {\n try {\n if (!fresh) {\n // Fall back to invoking fn with null (localStorage path)\n const result = (fn as any)(null);\n Promise.resolve(result).then(resolve, reject);\n return;\n }\n const retryTx = fresh.transaction(storeName, mode);\n const store = retryTx.objectStore(storeName);\n const result = fn(store);\n Promise.resolve(result).then(resolve, reject);\n } catch (err) {\n reject(err);\n }\n }, reject);\n return;\n }\n const store = tx.objectStore(storeName);\n let result: any;\n try {\n result = fn(store);\n } catch (err) {\n reject(err);\n return;\n }\n tx.oncomplete = () => resolve(result);\n tx.onerror = () => reject(tx.error);\n tx.onabort = () => reject(tx.error || new Error('Transaction aborted'));\n });\n }\n\n async function getItem(name: string): Promise<string | null> {\n return withStore('readonly', (store) => {\n console.log('READ STORE');\n if (!store) return localStorage.getItem(name);\n return new Promise<string | null>((resolve, reject) => {\n const req = store.get(name);\n req.onsuccess = () => resolve(req.result ?? null);\n req.onerror = () => reject(req.error);\n });\n });\n }\n\n async function setItem(name: string, value: string): Promise<void> {\n return withStore('readwrite', (store) => {\n console.log('WRITE STORE');\n if (!store) {\n localStorage.setItem(name, value);\n return;\n }\n return new Promise<void>((resolve, reject) => {\n const req = store.put(value, name);\n req.onsuccess = () => resolve();\n req.onerror = () => reject(req.error);\n });\n });\n }\n\n async function removeItem(name: string): Promise<void> {\n return withStore('readwrite', (store) => {\n if (!store) {\n localStorage.removeItem(name);\n return;\n }\n return new Promise<void>((resolve, reject) => {\n const req = store.delete(name);\n req.onsuccess = () => resolve();\n req.onerror = () => reject(req.error);\n });\n });\n }\n\n return { getItem, setItem, removeItem };\n}\n\nfunction localStorageFallback(): ZustandStateStorage {\n // Capture reference now so if global is cleaned up (test teardown) we still work\n const ls: any = (globalThis as any).localStorage;\n return {\n getItem: (n) => Promise.resolve(ls?.getItem ? ls.getItem(n) : null),\n setItem: (n, v) => {\n ls?.setItem?.(n, v);\n return Promise.resolve();\n },\n removeItem: (n) => {\n ls?.removeItem?.(n);\n return Promise.resolve();\n },\n };\n}\n"],"mappings":"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","names":["result","store","SyncAction","state"]}
|