Something went wrong. Try again.
Something went wrong. Try again.
2.0 kB · 69 lines
TypeScript
at main
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970import { lt, eq } from 'drizzle-orm';import { db } from './db';import { cache } from './db/schema';
/** * Cache-aside helper backed by SQLite. * * - Fresh hit (row exists and not expired): returns the parsed cached value. * - Miss/stale: runs `fetcher`, upserts the result with a fresh TTL, returns it. * - Fetcher failure with a stale row present: logs and serves the stale value * (graceful degradation when the upstream wiki is unreachable). * - Fetcher failure with no row: rethrows. */export async function getOrFetch<T>( key: string, ttlSeconds: number, fetcher: () => Promise<T>): Promise<T> { const now = Date.now();
const rows = db.select().from(cache).where(eq(cache.key, key)).limit(1).all(); const existing = rows[0];
if (existing && existing.expiresAt > now) { return JSON.parse(existing.value) as T; }
try { const value = await fetcher(); const serialized = JSON.stringify(value); const expiresAt = now + ttlSeconds * 1000; db.insert(cache) .values({ key, value: serialized, expiresAt, createdAt: now }) .onConflictDoUpdate({ target: cache.key, set: { value: serialized, expiresAt, createdAt: now } }) .run(); return value; } catch (err) { if (existing) { console.warn( `[cache] fetcher failed for "${key}", serving stale value:`, err instanceof Error ? err.message : err ); return JSON.parse(existing.value) as T; } throw err; }}
function sweepExpired() { try { db.delete(cache).where(lt(cache.expiresAt, Date.now())).run(); } catch (err) { console.warn('[cache] expiry sweep failed:', err instanceof Error ? err.message : err); }}
// Register the expiry sweeper exactly once, even across Vite HMR reloads.const SWEEPER_FLAG = '__fandomClientCacheSweeper__';const g = globalThis as typeof globalThis & { [SWEEPER_FLAG]?: boolean };if (!g[SWEEPER_FLAG]) { g[SWEEPER_FLAG] = true; sweepExpired(); const interval = setInterval(sweepExpired, 15 * 60 * 1000); // Never keep the process alive just for the sweeper. interval.unref();}