import { Debouncer } from "@tanstack/react-pacer"; export interface StateStorage<R = unknown> { getItem: (name: string) => string | null | Promise<string | null>; setItem: (name: string, value: string) => R; removeItem: (name: string) => R;} export interface DeferredStorage<TValue> { getItem: (name: string) => string | null | Promise<string | null>; setItem: (name: string, value: TValue) => void; removeItem: (name: string) => void; flush: () => void;} export function createMemoryStorage(): StateStorage { const store = new Map<string, string>(); return { getItem: (name) => store.get(name) ?? null, setItem: (name, value) => { store.set(name, value); }, removeItem: (name) => { store.delete(name); }, };} function isStateStorage( storage: Partial<StateStorage> | 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<StateStorage> | 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<TValue>( baseStorage: Partial<StateStorage> | null | undefined, serialize: (value: TValue) => string, debounceMs: number = 300,): DeferredStorage<TValue> { 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(); }, };}
import { Debouncer } from "@tanstack/react-pacer";
export interface StateStorage<R = unknown> {
getItem: (name: string) => string | null | Promise<string | null>;
setItem: (name: string, value: string) => R;
removeItem: (name: string) => R;
}
export interface DeferredStorage<TValue> {
getItem: (name: string) => string | null | Promise<string | null>;
setItem: (name: string, value: TValue) => void;
removeItem: (name: string) => void;
flush: () => void;
}
export function createMemoryStorage(): StateStorage {
const store = new Map<string, string>();
return {
getItem: (name) => store.get(name) ?? null,
setItem: (name, value) => {
store.set(name, value);
},
removeItem: (name) => {
store.delete(name);
},
};
}
function isStateStorage(
storage: Partial<StateStorage> | 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<StateStorage> | 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<TValue>(
baseStorage: Partial<StateStorage> | null | undefined,
serialize: (value: TValue) => string,
debounceMs: number = 300,
): DeferredStorage<TValue> {
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();
},
};
}