/** * Analytics Cache Service * * Provides caching for analytics API calls to reduce redundant requests * and improve performance while managing cache invalidation. */ interface CacheEntry { data: T; timestamp: number; ttl: number; // Time to live in milliseconds } interface AnalyticsCacheConfig { defaultTTL: number; // Default TTL in milliseconds maxCacheSize: number; // Maximum number of entries to cache databaseDataTTL?: number; // Special TTL for database-stored data (longer) } class AnalyticsCacheService { private cache = new Map>(); private config: AnalyticsCacheConfig; constructor(config: Partial = {}) { this.config = { defaultTTL: 5 * 60 * 1000, // 5 minutes maxCacheSize: 100, databaseDataTTL: 2 * 60 * 60 * 1000, // 2 hours for database data ...config }; } /** * Generate cache key from parameters */ private generateKey(endpoint: string, params?: Record): string { const sortedParams = params ? Object.keys(params) .sort() .reduce((result, key) => { result[key] = params[key]; return result; }, {} as Record) : {}; return `${endpoint}:${JSON.stringify(sortedParams)}`; } /** * Get cached data if valid */ get(endpoint: string, params?: Record): T | null { const key = this.generateKey(endpoint, params); const entry = this.cache.get(key); if (!entry) { return null; } // Check if cache entry is still valid const now = Date.now(); if (now - entry.timestamp > entry.ttl) { this.cache.delete(key); return null; } console.log(`๐Ÿ“ฆ Analytics Cache HIT: ${key}`); return entry.data; } /** * Set cache entry */ set(endpoint: string, params: Record | undefined, data: T, ttl?: number): void { const key = this.generateKey(endpoint, params); const now = Date.now(); // Remove oldest entries if cache is full if (this.cache.size >= this.config.maxCacheSize) { this.evictOldest(); } this.cache.set(key, { data, timestamp: now, ttl: ttl || this.config.defaultTTL }); console.log(`๐Ÿ’พ Analytics Cache SET: ${key} (TTL: ${ttl || this.config.defaultTTL}ms)`); } /** * Set cache entry with database TTL (longer cache for DB-stored data) */ setDatabaseData(endpoint: string, params: Record | undefined, data: T): void { const ttl = this.config.databaseDataTTL || this.config.defaultTTL; this.set(endpoint, params, data, ttl); console.log(`๐Ÿ—„๏ธ Analytics Cache SET (DB): ${this.generateKey(endpoint, params)} (TTL: ${ttl}ms)`); } /** * Invalidate cache entries matching pattern */ invalidate(pattern?: string): void { if (!pattern) { this.cache.clear(); console.log('๐Ÿ—‘๏ธ Analytics Cache CLEARED: All entries'); return; } const keysToDelete: string[] = []; for (const key of this.cache.keys()) { if (key.includes(pattern)) { keysToDelete.push(key); } } keysToDelete.forEach(key => this.cache.delete(key)); console.log(`๐Ÿ—‘๏ธ Analytics Cache INVALIDATED: ${keysToDelete.length} entries matching "${pattern}"`); } /** * Invalidate entries older than specified time */ invalidateOlderThan(olderThanMs: number): void { const now = Date.now(); const keysToDelete: string[] = []; for (const [key, entry] of this.cache.entries()) { if (now - entry.timestamp > olderThanMs) { keysToDelete.push(key); } } keysToDelete.forEach(key => this.cache.delete(key)); console.log(`๐Ÿ—‘๏ธ Analytics Cache INVALIDATED: ${keysToDelete.length} entries older than ${olderThanMs}ms`); } /** * Get cache statistics */ getStats(): { size: number; keys: string[]; oldestEntry?: number; newestEntry?: number } { const keys = Array.from(this.cache.keys()); const timestamps = Array.from(this.cache.values()).map(entry => entry.timestamp); return { size: this.cache.size, keys, oldestEntry: timestamps.length > 0 ? Math.min(...timestamps) : undefined, newestEntry: timestamps.length > 0 ? Math.max(...timestamps) : undefined }; } /** * Remove oldest cache entry */ private evictOldest(): void { let oldestKey = ''; let oldestTime = Date.now(); for (const [key, entry] of this.cache.entries()) { if (entry.timestamp < oldestTime) { oldestTime = entry.timestamp; oldestKey = key; } } if (oldestKey) { this.cache.delete(oldestKey); console.log(`๐Ÿ—‘๏ธ Analytics Cache EVICTED: ${oldestKey}`); } } /** * Clean up expired entries */ cleanup(): void { const now = Date.now(); const keysToDelete: string[] = []; for (const [key, entry] of this.cache.entries()) { if (now - entry.timestamp > entry.ttl) { keysToDelete.push(key); } } keysToDelete.forEach(key => this.cache.delete(key)); if (keysToDelete.length > 0) { console.log(`๐Ÿงน Analytics Cache CLEANUP: Removed ${keysToDelete.length} expired entries`); } } } // Create singleton instance export const analyticsCache = new AnalyticsCacheService({ defaultTTL: 60 * 60 * 1000, // 60 minutes for analytics data (since it's stored in DB) maxCacheSize: 100, // Increased cache size since we're keeping data longer databaseDataTTL: 2 * 60 * 60 * 1000 // 2 hours for database-stored data }); // Cleanup expired entries every 5 minutes (since we have longer TTL) setInterval(() => { analyticsCache.cleanup(); }, 5 * 60 * 1000); export default analyticsCache;