68 lines · 2.4 KB · TypeScript
RawDownload
/** * Stale-while-revalidate store for page data. * * Page hooks seed their state from here so client-side navigation paints the * last-known data instantly while the fresh fetch runs in the background. * Controller round-trips go through a tunnel and can take seconds — without * this every route switch stares at a spinner for the full fetch. * * Two layers: *  - Module memory (`cache`): survives client-side route switches (same JS *    context), instant reads, no serialization cost. *  - `sessionStorage` (write-through): survives a hard reload so the very first *    paint after refresh still shows last-known data instead of a cold spinner. *    Guarded for SSR/no-window and quota errors; large values are kept in module *    memory only so we never blow the storage quota on a big config blob. */const cache = new Map<string, unknown>(); const STORAGE_PREFIX = "page-cache:";// Skip persisting anything larger than this (serialized length). Big blobs stay// in module memory only — sessionStorage quota is small and shared.const MAX_PERSISTED_CHARS = 200_000; const getSessionStorage = (): Storage | null => {  if (typeof window === "undefined") return null;  try {    return window.sessionStorage;  } catch {    // Access can throw in sandboxed/blocked-storage contexts.    return null;  }}; export function readPageCache<T>(key: string): T | null {  if (cache.has(key)) {    return (cache.get(key) as T | undefined) ?? null;  }  const store = getSessionStorage();  if (!store) return null;  try {    const raw = store.getItem(STORAGE_PREFIX + key);    if (raw == null) return null;    const value = JSON.parse(raw) as T;    // Warm the module layer so subsequent reads skip parsing.    cache.set(key, value);    return value;  } catch {    return null;  }} export function writePageCache<T>(key: string, value: T): void {  cache.set(key, value);  const store = getSessionStorage();  if (!store) return;  try {    const serialized = JSON.stringify(value);    if (serialized.length > MAX_PERSISTED_CHARS) {      // Too big to persist — drop any stale copy and keep module memory only.      store.removeItem(STORAGE_PREFIX + key);      return;    }    store.setItem(STORAGE_PREFIX + key, serialized);  } catch {    // Quota exceeded, serialization failure, or blocked storage — module memory    // still holds the value, so reads within this session keep working.  }}