// Tiny in-memory TTL cache keyed by string. Status pages serve the same payload // to many visitors during an outage; we don't want every page hit to fan out to // Postgres. The cache is per-process; behind a load balancer each replica fills // independently, which is fine - short TTLs converge quickly. interface Entry { value: T; expires: number } const store = new Map>(); export function cacheGet(key: string): T | null { const entry = store.get(key) as Entry | undefined; if (!entry) return null; if (Date.now() > entry.expires) { store.delete(key); return null; } return entry.value; } export function cacheSet(key: string, value: T, ttlSeconds: number): void { // Soft cap so a runaway path can't blow memory. LRU-ish: oldest entries get // dropped first by insertion order (Map preserves it). if (store.size > 5000) { const firstKey = store.keys().next().value; if (firstKey) store.delete(firstKey); } store.set(key, { value, expires: Date.now() + ttlSeconds * 1000 }); } // Convenience wrapper: get-or-fill. The producer runs at most once per key // during the TTL window across this process. export async function cached(key: string, ttlSeconds: number, producer: () => Promise): Promise { const hit = cacheGet(key); if (hit !== null) return hit; const value = await producer(); cacheSet(key, value, ttlSeconds); return value; }