interface CacheEntry { data: unknown; expiresAt: number; } const store = new Map(); const inflight = new Map>(); export function cacheGet(key: string): unknown | undefined { const entry = store.get(key); if (!entry) return undefined; if (Date.now() > entry.expiresAt) { store.delete(key); return undefined; } return entry.data; } export function cacheSet(key: string, data: unknown, ttlMs: number): void { store.set(key, { data, expiresAt: Date.now() + ttlMs }); } export function cacheInvalidateAll(): void { store.clear(); inflight.clear(); } export function cacheInvalidateByPrefix(prefix: string): void { for (const key of store.keys()) { if (key.startsWith(prefix)) store.delete(key); } for (const key of inflight.keys()) { if (key.startsWith(prefix)) inflight.delete(key); } } export function cacheInvalidateKey(key: string): void { store.delete(key); inflight.delete(key); } export async function cachedQuery(key: string, ttlMs: number, fn: () => Promise): Promise { const cached = cacheGet(key); if (cached !== undefined) return cached as T; const pending = inflight.get(key); if (pending) return pending as Promise; const promise = fn().then(result => { cacheSet(key, result, ttlMs); inflight.delete(key); return result; }).catch(err => { inflight.delete(key); throw err; }); inflight.set(key, promise); return promise as Promise; } export function stableKey(prefix: string, query: Record): string { const sorted = Object.keys(query).sort().map(k => `${k}=${query[k]}`).join("&"); return `${prefix}:${sorted}`; } export function cacheStats(): { size: number; keys: string[] } { return { size: store.size, keys: Array.from(store.keys()) }; }