refactor tier 3
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
// 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;
|
||||
}
|
||||
Reference in New Issue
Block a user