/** * Persists the React Query cache to IndexedDB so the dashboard is *viewable* * offline (last-synced data) and survives a reload with no connection. * * Uses `dehydrate`/`hydrate` from @tanstack/react-query directly — no extra * dependency. Writes are debounced; reads are guarded by a schema buster, a * max-age, and a tenant scope so one café never hydrates another's data. */ import { dehydrate, hydrate, type QueryClient } from "@tanstack/react-query"; import { kvGet, kvSet } from "@/lib/offline/offline-db"; const CACHE_KEY = "rq-cache"; /** Bump when cached shapes change so stale persisted data is dropped on deploy. */ const BUSTER = "v1"; const MAX_AGE_MS = 24 * 60 * 60 * 1000; // 24h const SAVE_DEBOUNCE_MS = 1000; type PersistedCache = { buster: string; timestamp: number; /** Tenant/user scope this cache belongs to (cafeId, or "anon"). */ scope: string; state: unknown; }; /** * Hydrate the query cache from IndexedDB if a valid snapshot exists for this * scope. Safe to call before or after queries mount. */ export async function restoreQueryCache(qc: QueryClient, scope: string): Promise { const saved = await kvGet(CACHE_KEY); if (!saved) return; if (saved.buster !== BUSTER) return; // schema changed if (saved.scope !== scope) return; // different tenant/user — do not leak if (Date.now() - saved.timestamp > MAX_AGE_MS) return; // too old try { hydrate(qc, saved.state as never); } catch { // corrupt snapshot — ignore, it will be overwritten on next save } } /** * Subscribe to cache changes and persist a debounced snapshot. Returns an * unsubscribe function. */ export function startPersisting(qc: QueryClient, scope: string): () => void { let timer: ReturnType | null = null; const save = () => { timer = null; const snapshot: PersistedCache = { buster: BUSTER, timestamp: Date.now(), scope, state: dehydrate(qc), }; void kvSet(CACHE_KEY, snapshot); }; const unsubscribe = qc.getQueryCache().subscribe(() => { if (timer) return; // a save is already scheduled timer = setTimeout(save, SAVE_DEBOUNCE_MS); }); return () => { if (timer) clearTimeout(timer); unsubscribe(); }; }