/** * Cache Management for Analytics * Provides caching for expensive analytics calculations */ interface CacheEntry { data: T; timestamp: number; expiresAt: number; } class AnalyticsCache { private cache: Map> = new Map(); private defaultTTL: number = 5 * 60 * 1000; // 5 minutes /** * Get cached data */ get(key: string): T | null { const entry = this.cache.get(key); if (!entry) return null; if (Date.now() > entry.expiresAt) { this.cache.delete(key); return null; } return entry.data as T; } /** * Set cached data */ set(key: string, data: T, ttlMs: number = this.defaultTTL): void { const now = Date.now(); this.cache.set(key, { data, timestamp: now, expiresAt: now + ttlMs, }); } /** * Check if key exists and is valid */ has(key: string): boolean { const entry = this.cache.get(key); if (!entry) return false; if (Date.now() > entry.expiresAt) { this.cache.delete(key); return false; } return true; } /** * Delete a specific key */ delete(key: string): boolean { return this.cache.delete(key); } /** * Clear all cached data */ clear(): void { this.cache.clear(); } /** * Clear expired entries */ cleanup(): number { const now = Date.now(); let cleaned = 0; for (const [key, entry] of this.cache.entries()) { if (now > entry.expiresAt) { this.cache.delete(key); cleaned++; } } return cleaned; } /** * Get cache statistics */ getStats(): { size: number; validEntries: number; expiredEntries: number; } { const now = Date.now(); let valid = 0; let expired = 0; for (const entry of this.cache.values()) { if (now > entry.expiresAt) { expired++; } else { valid++; } } return { size: this.cache.size, validEntries: valid, expiredEntries: expired, }; } /** * Generate cache key from object */ static generateKey(prefix: string, params: Record): string { const sortedParams = Object.keys(params) .sort() .map((key) => `${key}:${JSON.stringify(params[key])}`) .join('|'); return `${prefix}:${sortedParams}`; } } // Singleton instance export const analyticsCache = new AnalyticsCache(); /** * Cache decorator for async functions */ export function cached( keyGenerator: (...args: any[]) => string, ttlMs: number = 5 * 60 * 1000 ) { return function ( target: any, propertyKey: string, descriptor: PropertyDescriptor ) { const originalMethod = descriptor.value; descriptor.value = async function (...args: any[]) { const cacheKey = keyGenerator(...args); const cached = analyticsCache.get(cacheKey); if (cached !== null) { return cached; } const result = await originalMethod.apply(this, args); analyticsCache.set(cacheKey, result, ttlMs); return result; }; return descriptor; }; } /** * Higher-order function for caching async functions */ export function withCache( fn: (...args: A) => Promise, keyGenerator: (...args: A) => string, ttlMs: number = 5 * 60 * 1000 ): (...args: A) => Promise { return async (...args: A): Promise => { const cacheKey = keyGenerator(...args); const cached = analyticsCache.get(cacheKey); if (cached !== null) { return cached; } const result = await fn(...args); analyticsCache.set(cacheKey, result, ttlMs); return result; }; } // Schedule periodic cleanup if (typeof setInterval !== 'undefined') { setInterval(() => { analyticsCache.cleanup(); }, 60 * 1000); // Run cleanup every minute }