Files
pingql/apps/status/src/cache.ts
T
2026-04-09 21:07:28 +04:00

39 lines
1.4 KiB
TypeScript

// 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<T> { value: T; expires: number }
const store = new Map<string, Entry<unknown>>();
export function cacheGet<T>(key: string): T | null {
const entry = store.get(key) as Entry<T> | undefined;
if (!entry) return null;
if (Date.now() > entry.expires) {
store.delete(key);
return null;
}
return entry.value;
}
export function cacheSet<T>(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<T>(key: string, ttlSeconds: number, producer: () => Promise<T>): Promise<T> {
const hit = cacheGet<T>(key);
if (hit !== null) return hit;
const value = await producer();
cacheSet(key, value, ttlSeconds);
return value;
}