@anfenn/zync 0.1.9 → 0.1.11

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.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/indexedDBStorage.ts"],"sourcesContent":["import { create, type StateCreator, type StoreApi, type UseBoundStore } from 'zustand';\nimport { persist, type PersistOptions } from 'zustand/middleware';\n\nexport { createIndexedDBStorage } from './indexedDBStorage';\n\nexport type SyncedRecord = {\n id?: any;\n _localId: string;\n updated_at: string;\n deleted?: boolean;\n [k: string]: any;\n};\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 = (set: any, get: any, queue: QueueToSyncCallback, stateKey: string, item: SyncedRecord) => void;\n\ntype MissingRemoteRecordDuringUpdateCallback = (strategy: MissingRemoteRecordDuringUpdateStrategy, item: SyncedRecord, newLocalId?: string) => void;\n\nexport type MissingRemoteRecordDuringUpdateStrategy = 'ignore' | '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 firstLoadDone: boolean;\n pendingChanges: PendingChange[];\n lastPulled: Record<string, string>; // stateKey -> ISO timestamp of last successful pull\n };\n};\n\nexport enum SyncAction {\n CreateOrUpdate = 'createOrUpdate',\n Remove = 'remove',\n}\n\nexport type QueueToSyncCallback = (action: SyncAction, stateKey: string, ...localIds: string[]) => void;\n\ntype SyncedStateCreator<TStore> = (set: any, get: any, queue: QueueToSyncCallback) => TStore;\n\ninterface PendingChange {\n action: SyncAction;\n stateKey: string;\n localId: string;\n id?: any;\n version: number;\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 = 'ignore';\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's api is:\n * - `syncState` object for reactive access to internal state i.e.: const syncState = useStore((state) => state.syncState);\n * - `sync` object for control methods e.g.: useStore.sync.enable(true|false);\n *\n * Synced objects will have the client only field `_localId`, which provides a stable identifier for the object.\n * It is ideal for use as JSX keys.\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 * 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 *\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) {\n const syncInterval = syncOptions.syncInterval ?? DEFAULT_SYNC_INTERVAL_MILLIS;\n const missingStrategy = 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 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', state.syncState);\n }\n };\n },\n partialize: (s: any) => {\n // Select state to be persisted\n\n const base = basePartialize ? basePartialize(s) : s;\n const { syncState, ...rest } = base || {};\n return {\n ...rest,\n syncState: {\n firstLoadDone: syncState.firstLoadDone,\n pendingChanges: syncState.pendingChanges,\n lastPulled: syncState.lastPulled,\n },\n };\n },\n merge: (persisted: any, current: any) => {\n // Add unpersistable fields back e.g. functions or memory-only fields\n return { \n ...current, \n ...persisted, \n syncState: { \n ...current?.syncState, \n ...persisted?.syncState, \n status: 'idle' // this confirms 'hydrating' is done\n } \n };\n },\n };\n\n const creator: StateCreator<TStore & SyncState, [], []> = (set: any, get: any, storeApi: any) => {\n let syncIntervalId: any;\n\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 syncState: {\n ...(state.syncState || {}),\n status: 'syncing',\n },\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().syncState.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 syncState: {\n ...(state.syncState || {}),\n status: 'idle',\n error: syncError,\n },\n }));\n\n if (get().syncState.pendingChanges.length > 0 && !syncError) {\n // If there are pending changes and no sync error, we can sync again\n await syncOnce();\n }\n }\n\n function queueToSync(action: SyncAction, stateKey: string, ...localIds: string[]) {\n set((state: any) => {\n const queue: PendingChange[] = state.syncState.pendingChanges || [];\n\n for (const localId of localIds) {\n const item = state[stateKey].find((i: any) => i._localId === localId);\n if (!item) {\n logger.error('[persistWithSync] queueToSync:no-local-item', {\n stateKey,\n localId,\n });\n continue;\n }\n if (action === SyncAction.Remove && !item.id) {\n logger.warn('[persistWithSync] queueToSync:remove-no-id', {\n stateKey,\n localId,\n });\n continue;\n }\n\n const queueItem = queue.find((p) => p.localId === localId && p.stateKey === stateKey);\n if (queueItem) {\n queueItem.version += 1;\n\n if (queueItem.action === SyncAction.CreateOrUpdate && action === SyncAction.Remove && item.id) {\n // Convert pending Create -> Remove so the intent to\n // delete is preserved even if the create hasn't\n // been pushed yet. This prevents races where a\n // create is removed from the queue but still\n // ends up being pushed by an in-flight run.\n queueItem.action = SyncAction.Remove;\n queueItem.id = item.id;\n }\n } else {\n queue.push({ action, stateKey, localId, id: item.id, version: 1 });\n }\n }\n\n return {\n syncState: {\n ...(state.syncState || {}),\n pendingChanges: queue,\n },\n };\n });\n syncOnce(); // Unawaited async\n }\n\n function setAndSync(partial: any) {\n // Allow passing either a partial object or an updater function\n if (typeof partial === 'function') {\n set((state: any) => ({ ...(partial as Function)(state) }));\n } else {\n set(partial);\n }\n syncOnce(); // Unawaited async\n }\n\n function enable(enabled: boolean) {\n set((state: any) => ({\n syncState: {\n ...(state.syncState || {}),\n enabled,\n },\n }));\n\n enableSyncTimer(enabled);\n addVisibilityChangeListener(enabled);\n }\n\n function enableSyncTimer(enabled: boolean) {\n clearInterval(syncIntervalId);\n syncIntervalId = undefined;\n if (enabled) {\n syncIntervalId = setInterval(syncOnce, syncInterval);\n syncOnce(); // Unawaited async\n }\n }\n\n function addVisibilityChangeListener(add: boolean) {\n if (add) {\n document.addEventListener('visibilitychange', onVisibilityChange);\n } else {\n document.removeEventListener('visibilitychange', onVisibilityChange);\n }\n }\n\n function onVisibilityChange() {\n if (document.visibilityState === 'visible') {\n logger.debug('[persistWithSync] Sync started now app is in foreground');\n enableSyncTimer(true);\n } else {\n logger.debug('[persistWithSync] Sync paused now app is in background');\n enableSyncTimer(false);\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.syncState.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 = {\n ...localItem,\n ...remote,\n _localId: localItem._localId,\n };\n const idx = next.findIndex((i) => i._localId === localItem._localId);\n if (idx >= 0) next[idx] = merged;\n } else {\n next.push({\n ...remote,\n _localId: nextLocalId(),\n });\n }\n }\n\n return {\n [stateKey]: next,\n syncState: {\n ...(state.syncState || {}),\n lastPulled: {\n ...(state.syncState.lastPulled || {}),\n [stateKey]: newest.toISOString(),\n },\n },\n };\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 syncState: {\n ...(state.syncState || {}),\n firstLoadDone: true,\n error: syncError,\n },\n }));\n }\n\n storeApi.sync = {\n enable,\n startFirstLoad,\n };\n\n const userState = stateCreator(setAndSync, get, queueToSync) as TStore;\n\n return {\n ...userState,\n syncState: {\n // set defaults\n status: 'hydrating',\n error: undefined,\n enabled: false,\n firstLoadDone: false,\n pendingChanges: [],\n lastPulled: {},\n },\n } as TStore & SyncState;\n };\n\n return persist(creator, wrappedPersistOptions);\n}\n\nexport function createStoreWithSync<TStore extends object>(\n stateCreator: SyncedStateCreator<TStore>,\n persistOptions: any,\n syncApi: Record<string, ApiFunctions>,\n syncOptions: SyncOptions = {},\n): UseStoreWithSync<TStore> {\n return create(persistWithSync(stateCreator, persistOptions, syncApi, syncOptions)) as UseStoreWithSync<TStore>;\n}\n\nexport type UseStoreWithSync<T> = UseBoundStore<\n StoreApi<T & SyncState> & {\n sync: {\n enable: (e: boolean) => void;\n startFirstLoad: () => Promise<void>;\n };\n persist: {\n setOptions: (options: Partial<PersistOptions<T, any, any>>) => void;\n clearStorage: () => void;\n rehydrate: () => Promise<void> | void;\n hasHydrated: () => boolean;\n onHydrate: (fn: (state: T) => void) => () => void;\n onFinishHydration: (fn: (state: T) => void) => () => void;\n getOptions: () => Partial<PersistOptions<T, any, any>>;\n };\n }\n>;\n\nfunction orderFor(a: SyncAction): number {\n switch (a) {\n case SyncAction.CreateOrUpdate:\n return 1;\n case SyncAction.Remove:\n return 2;\n }\n}\n\nasync function pull(set: any, get: any, stateKey: string, api: ApiFunctions, logger: Logger) {\n const lastPulled: Record<string, string> = get().syncState.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.syncState.pendingChanges || [];\n const localItems: any[] = state[stateKey] || [];\n let nextItems = [...localItems];\n const localById = new Map<any, any>(localItems.filter((l) => l.id).map((l) => [l.id, l]));\n\n // Collect remote ids that have a pending local Remove so we don't resurrect them before push executes\n const pendingRemovalIds = new Set();\n for (const change of pendingChanges) {\n if (change.stateKey === stateKey && change.action === SyncAction.Remove) {\n const item = localItems.find((i: any) => i._localId === change.localId);\n if (item && item.id) pendingRemovalIds.add(item.id);\n }\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 (pendingRemovalIds.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 nextItems = nextItems.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 = localItem && pendingChanges.some((p: PendingChange) => p.stateKey === stateKey && p.localId === localItem._localId);\n if (localItem && !pending) {\n const merged = {\n ...localItem,\n ...remote,\n _localId: localItem._localId,\n };\n nextItems = nextItems.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 nextItems = [...nextItems, { ...remote, _localId: nextLocalId() }];\n logger.debug(`[persistWithSync] pull:add stateKey=${stateKey} id=${remote.id}`);\n }\n }\n\n return {\n [stateKey]: nextItems,\n syncState: {\n ...(state.syncState || {}),\n lastPulled: {\n ...(state.syncState.lastPulled || {}),\n [stateKey]: newest.toISOString(),\n },\n },\n };\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(`[persistWithSync] push:attempt action=${change.action} stateKey=${change.stateKey} localId=${change.localId}`);\n\n const { action, stateKey, localId, id, version } = change;\n\n switch (action) {\n case SyncAction.Remove:\n await api.remove(id);\n logger.debug(`[persistWithSync] push:remove:success ${stateKey} ${localId} ${id}`);\n removeFromPendingChanges(set, localId, stateKey);\n break;\n\n case SyncAction.CreateOrUpdate:\n const state = get();\n const items: SyncedRecord[] = state[stateKey] || [];\n const item = items.find((i) => i._localId === localId);\n if (!item) {\n logger.warn(`[persistWithSync] push:${action}:no-local-item`, {\n stateKey,\n localId,\n });\n removeFromPendingChanges(set, localId, stateKey);\n return;\n }\n\n let omittedItem = omitSyncFields(item);\n if (item.id) {\n // Update\n const changed = await api.update(item.id, omittedItem);\n if (changed) {\n logger.debug('[persistWithSync] push:update:success', {\n stateKey,\n localId,\n id: item.id,\n });\n if (samePendingVersion(get, stateKey, localId, version)) {\n removeFromPendingChanges(set, localId, stateKey);\n }\n return;\n } else {\n logger.warn('[persistWithSync] push:update:missingRemote', {\n stateKey,\n localId,\n id: item.id,\n });\n\n switch (missingStrategy) {\n case 'deleteLocalRecord':\n set((s: any) => ({\n [stateKey]: (s[stateKey] || []).filter((i: any) => i._localId !== localId),\n }));\n break;\n\n case 'insertNewRemoteRecord': {\n omittedItem._localId = nextLocalId();\n omittedItem.updated_at = new Date().toISOString();\n\n // replace old with new copy without id so it becomes a Create\n set((s: any) => ({\n [stateKey]: (s[stateKey] || []).map((i: any) => (i._localId === localId ? omittedItem : i)),\n }));\n\n queueToSync(SyncAction.CreateOrUpdate, stateKey, omittedItem._localId);\n break;\n }\n }\n removeFromPendingChanges(set, localId, stateKey);\n // Call hook so userland can alert the user etc.\n onMissingRemoteRecordDuringUpdate?.(missingStrategy, omittedItem, omittedItem._localId);\n }\n return;\n }\n\n // Create\n const result = await api.add(omittedItem);\n if (result) {\n logger.debug('[persistWithSync] push:create:success', {\n stateKey,\n localId,\n id: result.id,\n });\n\n // Merge server-assigned fields (id, updated_at, etc) directly into local entity\n set((s: any) => ({\n [stateKey]: (s[stateKey] || []).map((i: any) => (i._localId === localId ? { ...i, ...result } : i)),\n }));\n if (samePendingVersion(get, stateKey, localId, version)) {\n removeFromPendingChanges(set, localId, stateKey);\n }\n // Call hook so userland can perform any cascading adjustments\n onAfterRemoteAdd?.(set, get, queueToSync, stateKey, {\n ...item,\n ...result,\n });\n } else {\n logger.warn('[persistWithSync] push:create:no-result', {\n stateKey,\n localId,\n });\n if (samePendingVersion(get, stateKey, localId, version)) {\n removeFromPendingChanges(set, localId, stateKey);\n }\n }\n break;\n }\n}\n\nfunction samePendingVersion(get: any, stateKey: string, localId: string, version: number): boolean {\n // Check if the pending entry is still the same (i.e. wasn't modified while the request was in-flight)\n const q: PendingChange[] = get().syncState.pendingChanges || [];\n const curChange = q.find((p) => p.localId === localId && p.stateKey === stateKey);\n return curChange?.version === version;\n}\n\nfunction removeFromPendingChanges(set: any, localId: string, stateKey: string) {\n set((s: any) => {\n const queue: PendingChange[] = (s.syncState.pendingChanges || []).filter((p: PendingChange) => !(p.localId === localId && p.stateKey === stateKey));\n return {\n syncState: {\n ...(s.syncState || {}),\n pendingChanges: queue,\n },\n };\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> = {\n debug: 10,\n info: 20,\n warn: 30,\n error: 40,\n none: 100,\n };\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","import { openDB, type IDBPDatabase } from 'idb';\n\nexport function createIndexedDBStorage(options: { dbName: string; storeName: string }) {\n const dbName = options.dbName;\n const storeName = options.storeName;\n\n // Start with version 1; we may bump if the requested object store is missing\n let dbPromise: Promise<IDBPDatabase<any>> = openDB(dbName, 1, {\n upgrade(db) {\n if (!db.objectStoreNames.contains(storeName)) {\n db.createObjectStore(storeName);\n }\n },\n });\n\n async function ensureStore(): Promise<void> {\n const db = await dbPromise;\n if (db.objectStoreNames.contains(storeName)) return;\n const nextVersion = (db.version || 0) + 1;\n try {\n db.close();\n } catch (e) {\n // ignore\n }\n // Bump version to force onupgradeneeded and create the missing store\n dbPromise = openDB(dbName, nextVersion, {\n upgrade(upg) {\n if (!upg.objectStoreNames.contains(storeName)) upg.createObjectStore(storeName);\n },\n });\n await dbPromise;\n }\n\n async function withRetry<T>(fn: (db: IDBPDatabase<any>) => Promise<T>): Promise<T> {\n try {\n const db = await dbPromise;\n return await fn(db);\n } catch (err: any) {\n // If the object store is missing, reopen DB with bumped version and retry once\n const msg = String(err && err.message ? err.message : err);\n if (err && (err.name === 'NotFoundError' || /objectStore/i.test(msg))) {\n await ensureStore();\n const db2 = await dbPromise;\n return await fn(db2);\n }\n throw err;\n }\n }\n\n return {\n getItem: async (name: string): Promise<string | null> => {\n return withRetry(async (db) => {\n let v = await db.get(storeName, name);\n v = v ?? null;\n //console.log('getItem:', db.objectStoreNames, storeName, name, v);\n return v;\n });\n },\n setItem: async (name: string, value: string): Promise<void> => {\n return withRetry(async (db) => {\n //console.log('setItem', name, value);\n await db.put(storeName, value, name);\n });\n },\n removeItem: async (name: string): Promise<void> => {\n return withRetry(async (db) => {\n //console.log('removeItem', name);\n await db.delete(storeName, name);\n });\n },\n };\n}\n"],"mappings":"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","names":["SyncAction","state"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/logger.ts","../src/helpers.ts","../src/pull.ts","../src/push.ts","../src/indexedDBStorage.ts"],"sourcesContent":["import { create, type StateCreator } from 'zustand';\nimport { persist } from 'zustand/middleware';\nimport { newLogger, type Logger, type LogLevel } from './logger';\nimport { orderFor, findApi, nextLocalId } from './helpers';\nimport type {\n ApiFunctions,\n MissingRemoteRecordDuringUpdateStrategy,\n SyncOptions,\n SyncState,\n SyncedStateCreator,\n PendingChange,\n UseStoreWithSync,\n} from './types';\nimport { pull } from './pull';\nimport { pushOne } from './push';\n\nexport { createIndexedDBStorage } from './indexedDBStorage';\nexport { nextLocalId } from './helpers';\nexport type { ApiFunctions, UseStoreWithSync, SyncState } from './types';\n\nexport enum SyncAction {\n CreateOrUpdate = 'createOrUpdate',\n Remove = 'remove',\n}\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 = 'ignore';\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's api is:\n * - `syncState` object for reactive access to internal state i.e.: const syncState = useStore((state) => state.syncState);\n * - `sync` object for control methods e.g.: useStore.sync.enable(true|false);\n *\n * Synced objects will have the client only field `_localId`, which provides a stable identifier for the object.\n * It is ideal for use as JSX keys.\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 * 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 *\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 createWithSync<TStore extends object>(\n stateCreator: SyncedStateCreator<TStore>,\n persistOptions: any,\n syncApi: Record<string, ApiFunctions>,\n syncOptions: SyncOptions = {},\n): Promise<UseStoreWithSync<TStore>> {\n const store = create(persistWithSync(stateCreator, persistOptions, syncApi, syncOptions)) as UseStoreWithSync<TStore>;\n\n return new Promise<UseStoreWithSync<TStore>>((resolve) => {\n store.persist.onFinishHydration((_state) => {\n resolve(store);\n });\n });\n}\n\nexport function persistWithSync<TStore extends object>(\n stateCreator: SyncedStateCreator<TStore>,\n persistOptions: any,\n syncApi: Record<string, ApiFunctions>,\n syncOptions: SyncOptions = {},\n) {\n const syncInterval = syncOptions.syncInterval ?? DEFAULT_SYNC_INTERVAL_MILLIS;\n const missingStrategy = 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 const baseOnRehydrate = persistOptions?.onRehydrateStorage;\n const basePartialize = persistOptions?.partialize;\n\n const wrappedPersistOptions = {\n ...persistOptions,\n onRehydrateStorage: () => {\n logger.debug('[zync] rehydration:start');\n\n return (state: any, error: any) => {\n if (error) {\n logger.error('[zync] rehydration:failed', error);\n } else {\n baseOnRehydrate?.(state, error);\n logger.debug('[zync] rehydration:complete', state);\n }\n };\n },\n partialize: (s: any) => {\n // Select state to be persisted\n\n const base = basePartialize ? basePartialize(s) : s;\n const { syncState, ...rest } = base || {};\n return {\n ...rest,\n syncState: {\n firstLoadDone: syncState.firstLoadDone,\n pendingChanges: syncState.pendingChanges,\n lastPulled: syncState.lastPulled,\n },\n };\n },\n merge: (persisted: any, current: any) => {\n // Here after hydration.\n // `persisted` is state from storage that's just loaded (possibly ascynchronously e.g. IndexedDB)\n // `current` is what the user has defined (they may have added or removed state keys)\n // Zync is designed to not be used until hydration is complete, so we don't expect to have to\n // merge user mutated state (i.e. current) into persisted. So we do the Zustand recommended pattern of\n // shallow copy where persisted keys win:\n const state = { ...current, ...persisted };\n\n return {\n ...state,\n syncState: {\n ...state.syncState,\n status: 'idle', // this confirms 'hydrating' is done\n },\n };\n },\n };\n\n const creator: StateCreator<TStore & SyncState, [], []> = (set: any, get: any, storeApi: any) => {\n let syncIntervalId: any;\n\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 syncState: {\n ...(state.syncState || {}),\n status: 'syncing',\n },\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(`[zync] pull:error stateKey=${stateKey}`, err);\n }\n }\n\n // 2) PUSH queued changes\n const snapshot: PendingChange[] = [...(get().syncState.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(`[zync] push:error change=${change}`, err);\n }\n }\n\n set((state: any) => ({\n syncState: {\n ...(state.syncState || {}),\n status: 'idle',\n error: syncError,\n },\n }));\n\n if (get().syncState.pendingChanges.length > 0 && !syncError) {\n // If there are pending changes and no sync error, we can sync again\n await syncOnce();\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(`[zync] firstLoad:start 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.syncState.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 = {\n ...localItem,\n ...remote,\n _localId: localItem._localId,\n };\n const idx = next.findIndex((i) => i._localId === localItem._localId);\n if (idx >= 0) next[idx] = merged;\n } else {\n next.push({\n ...remote,\n _localId: nextLocalId(),\n });\n }\n }\n\n return {\n [stateKey]: next,\n syncState: {\n ...(state.syncState || {}),\n lastPulled: {\n ...(state.syncState.lastPulled || {}),\n [stateKey]: newest.toISOString(),\n },\n },\n };\n });\n\n lastId = batch[batch.length - 1].id;\n }\n\n logger.info(`[zync] firstLoad:done stateKey=${stateKey}`);\n } catch (err) {\n syncError = syncError ?? (err as Error);\n logger.error(`[zync] firstLoad:error stateKey=${stateKey}`, err);\n }\n }\n\n set((state: any) => ({\n syncState: {\n ...(state.syncState || {}),\n firstLoadDone: true,\n error: syncError,\n },\n }));\n }\n\n function queueToSync(action: any, stateKey: string, ...localIds: string[]) {\n set((state: any) => {\n const pendingChanges: any[] = state.syncState.pendingChanges || [];\n\n for (const localId of localIds) {\n const item = state[stateKey].find((i: any) => i._localId === localId);\n if (!item) {\n logger.error(`[zync] queueToSync:no-local-item localId=${localId}`);\n continue;\n }\n\n const queueItem = pendingChanges.find((p) => p.localId === localId && p.stateKey === stateKey);\n if (queueItem) {\n queueItem.version += 1;\n\n if (queueItem.action === SyncAction.CreateOrUpdate && action === SyncAction.Remove && item.id) {\n queueItem.action = SyncAction.Remove;\n queueItem.id = item.id;\n }\n logger.debug(`[zync] queueToSync:adjusted v=${queueItem.version} action=${action} localId=${localId}`);\n } else {\n pendingChanges.push({ action, stateKey, localId, id: item.id, version: 1 });\n logger.debug(`[zync] queueToSync:added action=${action} localId=${localId}`);\n }\n }\n\n return {\n syncState: {\n ...(state.syncState || {}),\n pendingChanges,\n },\n };\n });\n syncOnce();\n }\n\n function setAndSync(partial: any) {\n if (typeof partial === 'function') {\n set((state: any) => ({ ...partial(state) }));\n } else {\n set(partial);\n }\n syncOnce();\n }\n\n function enable(enabled: boolean) {\n set((state: any) => ({\n syncState: {\n ...(state.syncState || {}),\n enabled,\n },\n }));\n\n enableSyncTimer(enabled);\n addVisibilityChangeListener(enabled);\n }\n\n function enableSyncTimer(enabled: boolean) {\n clearInterval(syncIntervalId);\n syncIntervalId = undefined;\n if (enabled) {\n syncIntervalId = setInterval(syncOnce, syncInterval);\n syncOnce();\n }\n }\n\n function addVisibilityChangeListener(add: boolean) {\n if (add) {\n document.addEventListener('visibilitychange', onVisibilityChange);\n } else {\n document.removeEventListener('visibilitychange', onVisibilityChange);\n }\n }\n\n function onVisibilityChange() {\n if (document.visibilityState === 'visible') {\n logger.debug('[zync] sync:start-in-foreground');\n enableSyncTimer(true);\n } else {\n logger.debug('[zync] sync:pause-in-background');\n enableSyncTimer(false);\n }\n }\n\n // public useStore.sync api, similar in principle to useStore.persist\n storeApi.sync = {\n enable,\n startFirstLoad,\n };\n\n const userState = stateCreator(setAndSync, get, queueToSync) as TStore;\n\n return {\n ...userState,\n syncState: {\n // set defaults\n status: 'hydrating',\n error: undefined,\n enabled: false,\n firstLoadDone: false,\n pendingChanges: [],\n lastPulled: {},\n },\n } as TStore & SyncState;\n };\n\n return persist(creator, wrappedPersistOptions);\n}\n","export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'none';\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 function newLogger(base: Logger, min: LogLevel): Logger {\n const order: Record<LogLevel, number> = {\n debug: 10,\n info: 20,\n warn: 30,\n error: 40,\n none: 100,\n };\n const threshold = order[min];\n const enabled = (lvl: LogLevel) => order[lvl] >= threshold;\n return {\n debug: (...a: any[]) => enabled('debug') && base.debug?.(...a),\n info: (...a: any[]) => enabled('info') && base.info?.(...a),\n warn: (...a: any[]) => enabled('warn') && base.warn?.(...a),\n error: (...a: any[]) => enabled('error') && base.error?.(...a),\n };\n}\n","import { SyncAction } from './index';\nimport type { ApiFunctions, PendingChange } from './types';\n\nexport function nextLocalId(): string {\n return crypto.randomUUID();\n}\n\nexport function orderFor(a: SyncAction): number {\n switch (a) {\n case SyncAction.CreateOrUpdate:\n return 1;\n case SyncAction.Remove:\n return 2;\n }\n}\n\nexport function omitSyncFields(item: any, fields: readonly string[]) {\n const result = { ...item };\n for (const k of fields) delete result[k];\n return result;\n}\n\nexport function samePendingVersion(get: any, stateKey: string, localId: string, version: number): boolean {\n const q: PendingChange[] = get().syncState.pendingChanges || [];\n const curChange = q.find((p) => p.localId === localId && p.stateKey === stateKey);\n return curChange?.version === version;\n}\n\nexport function removeFromPendingChanges(set: any, localId: string, stateKey: string) {\n set((s: any) => {\n const queue: PendingChange[] = (s.syncState.pendingChanges || []).filter((p: PendingChange) => !(p.localId === localId && p.stateKey === stateKey));\n return {\n syncState: {\n ...(s.syncState || {}),\n pendingChanges: queue,\n },\n };\n });\n}\n\nexport function 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","import type { ApiFunctions, PendingChange, SyncedRecord } from './types';\nimport { SyncAction } from './index';\nimport { nextLocalId } from './helpers';\n\nexport async function pull(set: any, get: any, stateKey: string, api: ApiFunctions, logger: any) {\n const lastPulled: Record<string, string> = get().syncState.lastPulled || {};\n const lastPulledAt = new Date(lastPulled[stateKey] || new Date(0));\n\n logger.debug(`[zync] pull:start stateKey=${stateKey} since=${lastPulledAt.toISOString()}`);\n\n const serverData = (await api.list(lastPulledAt)) as SyncedRecord[];\n if (!serverData?.length) return;\n\n let newest = lastPulledAt;\n set((state: any) => {\n const pendingChanges: PendingChange[] = state.syncState.pendingChanges || [];\n const localItems: any[] = state[stateKey] || [];\n let nextItems = [...localItems];\n const localById = new Map<any, any>(localItems.filter((l) => l.id).map((l) => [l.id, l]));\n // prevent resurrecting deleted items by pulling them again\n const pendingRemovalById = new Set(pendingChanges.filter((p) => p.stateKey === stateKey && p.action === SyncAction.Remove).map((p) => p.id));\n\n for (const remote of serverData) {\n const remoteUpdated = new Date(remote.updated_at);\n if (remoteUpdated > newest) newest = remoteUpdated;\n\n // If a Remove is pending for this localId, skip merging/adding to avoid flicker\n if (pendingRemovalById.has(remote.id)) {\n logger.debug(`[zync] pull:skip-pending-remove stateKey=${stateKey} id=${remote.id}`);\n continue;\n }\n\n const localItem = localById.get(remote.id);\n if (remote.deleted) {\n if (localItem) {\n nextItems = nextItems.filter((i: any) => i.id !== remote.id);\n logger.debug(`[zync] pull:remove stateKey=${stateKey} id=${remote.id}`);\n }\n continue;\n }\n\n const pending = localItem && pendingChanges.some((p: any) => p.stateKey === stateKey && p.localId === localItem._localId);\n if (localItem && !pending) {\n const merged = {\n ...localItem,\n ...remote,\n _localId: localItem._localId,\n };\n nextItems = nextItems.map((i: any) => (i._localId === localItem._localId ? merged : i));\n logger.debug(`[zync] pull:merge stateKey=${stateKey} id=${remote.id}`);\n } else if (!localItem) {\n // Add remote item (no local or pending collisions)\n nextItems = [...nextItems, { ...remote, _localId: nextLocalId() }];\n logger.debug(`[zync] pull:add stateKey=${stateKey} id=${remote.id}`);\n }\n }\n\n return {\n [stateKey]: nextItems,\n syncState: {\n ...(state.syncState || {}),\n lastPulled: {\n ...(state.syncState.lastPulled || {}),\n [stateKey]: newest.toISOString(),\n },\n },\n };\n });\n}\n","import { removeFromPendingChanges, omitSyncFields, samePendingVersion } from './helpers';\nimport { SyncAction } from './index';\nimport type { ApiFunctions } from './types';\n\nconst SYNC_FIELDS = ['id', '_localId', 'updated_at', 'deleted'] as const;\n\nexport async function pushOne(\n set: any,\n get: any,\n change: any,\n api: ApiFunctions,\n logger: any,\n queueToSync: any,\n missingStrategy: string,\n onMissingRemoteRecordDuringUpdate?: any,\n onAfterRemoteAdd?: any,\n) {\n logger.debug(`[zync] push:attempt action=${change.action} stateKey=${change.stateKey} localId=${change.localId}`);\n\n const { action, stateKey, localId, id, version } = change;\n\n switch (action) {\n case SyncAction.Remove:\n await api.remove(id);\n logger.debug(`[zync] push:remove:success ${stateKey} ${localId} ${id}`);\n removeFromPendingChanges(set, localId, stateKey);\n break;\n\n case SyncAction.CreateOrUpdate: {\n const state = get();\n const items: any[] = state[stateKey] || [];\n const item = items.find((i) => i._localId === localId);\n if (!item) {\n logger.warn(`[zync] push:create-or-update:no-local-item`, {\n stateKey,\n localId,\n });\n removeFromPendingChanges(set, localId, stateKey);\n return;\n }\n\n const omittedItem = omitSyncFields(item, SYNC_FIELDS);\n if (item.id) {\n // Update\n const changed = await api.update(item.id, omittedItem);\n if (changed) {\n logger.debug('[zync] push:update:success', {\n stateKey,\n localId,\n id: item.id,\n });\n if (samePendingVersion(get, stateKey, localId, version)) {\n removeFromPendingChanges(set, localId, stateKey);\n }\n return;\n } else {\n logger.warn('[zync] push:update:missing-remote', {\n stateKey,\n localId,\n id: item.id,\n });\n\n switch (missingStrategy) {\n case 'deleteLocalRecord':\n set((s: any) => ({\n [stateKey]: (s[stateKey] || []).filter((i: any) => i._localId !== localId),\n }));\n break;\n\n case 'insertNewRemoteRecord': {\n omittedItem._localId = crypto.randomUUID();\n omittedItem.updated_at = new Date().toISOString();\n\n // replace old with new copy without id so it becomes a Create\n set((s: any) => ({\n [stateKey]: (s[stateKey] || []).map((i: any) => (i._localId === localId ? omittedItem : i)),\n }));\n\n queueToSync(SyncAction.CreateOrUpdate, stateKey, omittedItem._localId);\n break;\n }\n }\n removeFromPendingChanges(set, localId, stateKey);\n // Call hook so userland can alert the user etc.\n onMissingRemoteRecordDuringUpdate?.(missingStrategy, omittedItem, omittedItem._localId);\n }\n return;\n }\n\n // Create\n const result = await api.add(omittedItem);\n if (result) {\n logger.debug('[zync] push:create:success', {\n stateKey,\n localId,\n id: result.id,\n });\n\n // Merge server-assigned fields (id, updated_at, etc) directly into local entity\n set((s: any) => ({\n [stateKey]: (s[stateKey] || []).map((i: any) => (i._localId === localId ? { ...i, ...result } : i)),\n }));\n if (samePendingVersion(get, stateKey, localId, version)) {\n removeFromPendingChanges(set, localId, stateKey);\n }\n // Call hook so userland can perform any cascading adjustments\n onAfterRemoteAdd?.(set, get, queueToSync, stateKey, {\n ...item,\n ...result,\n });\n } else {\n logger.warn('[zync] push:create:no-result', {\n stateKey,\n localId,\n });\n if (samePendingVersion(get, stateKey, localId, version)) {\n removeFromPendingChanges(set, localId, stateKey);\n }\n }\n break;\n }\n }\n}\n","import type { IDBPDatabase } from 'idb';\n\nexport function createIndexedDBStorage(options: { dbName: string; storeName: string }) {\n const dbName = options.dbName;\n const storeName = options.storeName;\n\n // dbPromise is created lazily by initDB() to avoid pulling `idb` into bundles\n let dbPromise: Promise<IDBPDatabase<any>> | null = null;\n\n async function initDB(): Promise<IDBPDatabase<any>> {\n if (dbPromise) return dbPromise;\n try {\n const idb = await import(/* webpackChunkName: \"idb\" */ 'idb');\n dbPromise = idb.openDB(dbName, 1, {\n upgrade(db: IDBPDatabase<any>) {\n if (!db.objectStoreNames.contains(storeName)) {\n db.createObjectStore(storeName);\n }\n },\n });\n return dbPromise;\n } catch (_e) {\n throw new Error('Missing optional dependency \"idb\". Install it to use IndexedDB storage: npm install idb');\n }\n }\n\n async function ensureStore(): Promise<void> {\n const db = await initDB();\n if (db.objectStoreNames.contains(storeName)) return;\n const nextVersion = (db.version || 0) + 1;\n try {\n db.close();\n } catch (_e) {\n // ignore\n }\n const idb = await import(/* webpackChunkName: \"idb\" */ 'idb');\n dbPromise = idb.openDB(dbName, nextVersion, {\n upgrade(upg: IDBPDatabase<any>) {\n if (!upg.objectStoreNames.contains(storeName)) upg.createObjectStore(storeName);\n },\n });\n await dbPromise;\n }\n\n async function withRetry<T>(fn: (db: IDBPDatabase<any>) => Promise<T>): Promise<T> {\n try {\n const db = await initDB();\n return await fn(db);\n } catch (err: any) {\n const msg = String(err && err.message ? err.message : err);\n if (err && (err.name === 'NotFoundError' || /objectStore/i.test(msg))) {\n await ensureStore();\n const db2 = await initDB();\n return await fn(db2);\n }\n throw err;\n }\n }\n\n return {\n getItem: async (name: string): Promise<string | null> => {\n return withRetry(async (db) => {\n let v = await db.get(storeName, name);\n v = v ?? null; // Zustand expects null for missing keys\n return v;\n });\n },\n setItem: async (name: string, value: string): Promise<void> => {\n return withRetry(async (db) => {\n await db.put(storeName, value, name);\n });\n },\n removeItem: async (name: string): Promise<void> => {\n return withRetry(async (db) => {\n await db.delete(storeName, name);\n });\n },\n };\n}\n"],"mappings":"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","names":["SyncAction","state"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anfenn/zync",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "private": false,
5
5
  "description": "Sync middleware for zustand",
6
6
  "keywords": [],
@@ -25,30 +25,33 @@
25
25
  "package.json"
26
26
  ],
27
27
  "scripts": {
28
- "build": "tsup",
28
+ "build": "npm run format && npm run lint && tsup",
29
29
  "dev": "tsup --watch",
30
+ "lint": "eslint . --fix \"src/**/*.{js,jsx,ts,tsx,json}\"",
30
31
  "format": "prettier --write \"**/*.{ts,tsx,md}\"",
31
32
  "size": "pnpm --filter size size",
32
33
  "test": "pnpm --filter tests test",
33
34
  "test:ci": "pnpm --filter tests test:ci"
34
35
  },
35
36
  "devDependencies": {
37
+ "@eslint/js": "9.21.0",
38
+ "eslint": "9.35.0",
39
+ "idb": "8.0.3",
36
40
  "prettier": "3.4.2",
37
41
  "tsup": "8.3.5",
38
42
  "typescript": "5.7.3",
39
- "zustand": "5.0.8",
40
- "idb": "8.0.3"
43
+ "typescript-eslint": "8.24.1",
44
+ "zustand": "5.0.8"
41
45
  },
42
46
  "peerDependencies": {
43
- "zustand": "^5.0.0",
44
- "idb": "8.0.3"
47
+ "zustand": "^5.0.0"
45
48
  },
46
49
  "peerDependenciesMeta": {
47
50
  "zustand": {
48
51
  "optional": false
49
- },
50
- "idb": {
51
- "optional": false
52
52
  }
53
+ },
54
+ "optionalDependencies": {
55
+ "idb": "8.0.3"
53
56
  }
54
57
  }