export interface ICacheEntry { data: T; timestamp: number; ttl: number; } export class MetricsCache { private cache = new Map>(); private inFlight = new Map>(); private readonly defaultTTL: number; constructor(defaultTTL: number = 500) { this.defaultTTL = defaultTTL; } /** * Get cached data or compute and cache it */ public get(key: string, computeFn: () => T | Promise, ttl?: number): T | Promise { const cached = this.cache.get(key); const now = Date.now(); const actualTTL = ttl ?? this.defaultTTL; if (cached && (now - cached.timestamp) < cached.ttl) { return cached.data; } if (cached) { this.cache.delete(key); } const pending = this.inFlight.get(key) as Promise | undefined; if (pending) { return pending; } const result = computeFn(); // Handle both sync and async compute functions if (result instanceof Promise) { let inFlightPromise: Promise; inFlightPromise = result.then( (data) => { if (this.inFlight.get(key) === inFlightPromise) { this.inFlight.delete(key); this.cache.set(key, { data, timestamp: Date.now(), ttl: actualTTL }); } return data; }, (error: unknown) => { if (this.inFlight.get(key) === inFlightPromise) { this.inFlight.delete(key); this.cache.delete(key); } throw error; }, ); this.inFlight.set(key, inFlightPromise); return inFlightPromise; } else { this.cache.set(key, { data: result, timestamp: now, ttl: actualTTL }); return result; } } /** * Invalidate a specific cache entry */ public invalidate(key: string): void { this.cache.delete(key); this.inFlight.delete(key); } /** * Clear all cache entries */ public clear(): void { this.cache.clear(); this.inFlight.clear(); } /** * Get cache statistics */ public getStats(): { size: number; keys: string[] } { return { size: this.cache.size, keys: Array.from(this.cache.keys()) }; } /** * Clean up expired entries */ public cleanup(): void { const now = Date.now(); for (const [key, entry] of this.cache.entries()) { if (now - entry.timestamp > entry.ttl) { this.cache.delete(key); } } } }