/** * Safe IDB storage interface. These try..catch are useful because * some browsers may block access to these APIs due to security policies * * Also, the stored value is lazy-loaded to avoid hydration mismatch * between server/browser. When state is 'hydrated', the value in the heap * is the same as the value in IDB */ import { get, set } from "idb-keyval"; import { Store } from "./base"; const getIDB = async (key: string) => { try { return await get(key); } catch (err) { return; } }; const setIDB = async (key: string, value: T) => { try { await set(key, value); } catch (err) { /** noop */ } }; const debounce = any>( func: T, delay: number ) => { let timeoutId: ReturnType return (...args: Parameters) => { clearTimeout(timeoutId) timeoutId = setTimeout(() => { func(...args) }, delay) } } export const persisted = (key: string) => (store: Store) => { const handler = async () => { const payload = await getIDB(key); if (typeof document !== 'undefined') { store.set(payload ?? store.readInitial()); } }; const debouncedHandler = debounce( handler, 500 ) // 500ms debounce debouncedHandler() globalThis.addEventListener?.('focus', () => debouncedHandler()) globalThis.document?.addEventListener( 'visibilitychange', () => document.visibilityState === 'visible' && debouncedHandler() ) store.subscribe((value) => { setIDB(key, value); }); return store; };