import { Debouncer } from "@tanstack/react-pacer"; export interface StateStorage { getItem: (name: string) => string | null | Promise; setItem: (name: string, value: string) => R; removeItem: (name: string) => R; } export interface DeferredStorage { getItem: (name: string) => string | null | Promise; setItem: (name: string, value: TValue) => void; removeItem: (name: string) => void; flush: () => void; } export function createMemoryStorage(): StateStorage { const store = new Map(); return { getItem: (name) => store.get(name) ?? null, setItem: (name, value) => { store.set(name, value); }, removeItem: (name) => { store.delete(name); }, }; } function isStateStorage( storage: Partial | null | undefined, ): storage is StateStorage { return ( storage !== null && storage !== undefined && typeof storage.getItem === "function" && typeof storage.setItem === "function" && typeof storage.removeItem === "function" ); } export function resolveStorage(storage: Partial | null | undefined): StateStorage { return isStateStorage(storage) ? storage : createMemoryStorage(); } /** Keep the latest value and serialize it when the debounce fires or `flush` runs. */ export function createDeferredStorage( baseStorage: Partial | null | undefined, serialize: (value: TValue) => string, debounceMs: number = 300, ): DeferredStorage { const resolvedStorage = resolveStorage(baseStorage); const debouncedSetItem = new Debouncer( (name: string, value: TValue) => { resolvedStorage.setItem(name, serialize(value)); }, { wait: debounceMs }, ); return { getItem: (name) => resolvedStorage.getItem(name), setItem: (name, value) => { debouncedSetItem.maybeExecute(name, value); }, removeItem: (name) => { debouncedSetItem.cancel(); // cancel() leaves the captured value in Pacer's lastArgs. debouncedSetItem.reset(); resolvedStorage.removeItem(name); }, flush: () => { debouncedSetItem.flush(); }, }; }