shohan
Version:
Production-grade utilities and tools by Shohan - Starting with Smart Cache System for Next.js applications
1 lines • 87.8 kB
Source Map (JSON)
{"version":3,"sources":["../../src/cache/config.ts","../../src/cache/memory.ts","../../src/cache/traffic.ts","../../src/cache/redis.ts","../../src/cache/metrics.ts","../../src/cache/Cache.ts","../../src/cache/index.ts"],"sourcesContent":["/**\n * 🔧 EZ Cache Configuration - Production Grade\n * \n * Flexible configuration that handles all scenarios:\n * - With Redis (full 3-layer: Memory → Redis → DB)\n * - Without Redis (2-layer: Memory → DB) \n * - Memory-only mode\n * - Configurable logging, metrics, traffic detection\n */\n\n// Auto-detect environment\nconst ENV = process.env.NODE_ENV || 'production';\nconst IS_DEV = ENV === 'development';\nconst IS_PROD = ENV === 'production';\nconst IS_TEST = ENV === 'test';\n\n// User configurable options via environment variables\ninterface UserCacheConfig {\n // Redis Configuration (completely optional)\n enableRedis?: boolean;\n redisUrl?: string;\n redisToken?: string;\n\n // Memory Configuration\n enableMemory?: boolean;\n memorySize?: number;\n\n // Feature toggles\n enableLogging?: boolean;\n enableMetrics?: boolean;\n enableTrafficDetection?: boolean;\n enableCircuitBreaker?: boolean;\n\n // Performance tuning\n trafficThreshold?: number;\n defaultTtl?: number;\n cleanupInterval?: number;\n\n // Cache strategy\n cacheStrategy?: 'aggressive' | 'balanced' | 'conservative' | 'memory-only' | 'custom';\n}\n\n// Check if Redis dependencies are available (graceful fallback if @upstash/redis is not installed)\nfunction checkRedisAvailability(): boolean {\n try {\n // Check if Redis package is available\n require.resolve('@upstash/redis');\n\n // Check if Redis environment variables are provided\n const hasRedisUrl = !!process.env.UPSTASH_REDIS_REST_URL;\n const hasRedisToken = !!process.env.UPSTASH_REDIS_REST_TOKEN;\n\n // Check user preference (can disable Redis even if available)\n const userDisabled = process.env.CACHE_DISABLE_REDIS === 'true';\n\n return hasRedisUrl && hasRedisToken && !userDisabled;\n } catch {\n // @upstash/redis package not installed - graceful fallback\n return false;\n }\n}\n\n// Smart defaults based on environment and availability\nfunction getSmartDefaults(): Required<UserCacheConfig> {\n const redisAvailable = checkRedisAvailability();\n\n return {\n // Redis settings (auto-detect but allow override)\n enableRedis: redisAvailable && (process.env.CACHE_ENABLE_REDIS !== 'false'),\n redisUrl: process.env.UPSTASH_REDIS_REST_URL || '',\n redisToken: process.env.UPSTASH_REDIS_REST_TOKEN || '',\n\n // Memory settings\n enableMemory: process.env.CACHE_ENABLE_MEMORY !== 'false', // Default true\n memorySize: IS_DEV ? 200 : IS_PROD ? 5000 : 1000,\n\n // Feature flags with smart defaults\n enableLogging: process.env.CACHE_ENABLE_LOGGING === 'true' || IS_DEV,\n enableMetrics: process.env.CACHE_ENABLE_METRICS !== 'false' && (IS_PROD || IS_TEST),\n enableTrafficDetection: process.env.CACHE_ENABLE_TRAFFIC !== 'false',\n enableCircuitBreaker: process.env.CACHE_ENABLE_CIRCUIT_BREAKER !== 'false' && redisAvailable,\n\n // Performance settings\n trafficThreshold: IS_DEV ? 3 : IS_PROD ? 100 : 20,\n defaultTtl: IS_DEV ? 60 : IS_PROD ? 600 : 180,\n cleanupInterval: IS_DEV ? 2 * 60 * 1000 : IS_PROD ? 10 * 60 * 1000 : 5 * 60 * 1000,\n\n // Cache strategy\n cacheStrategy: (process.env.CACHE_STRATEGY as 'aggressive' | 'balanced' | 'conservative' | 'memory-only' | 'custom') ||\n (IS_PROD ? 'balanced' : IS_DEV ? 'aggressive' : 'conservative')\n };\n}\n\n// Apply strategy-based configurations\nfunction applyCacheStrategy(config: Required<UserCacheConfig>): Required<UserCacheConfig> {\n switch (config.cacheStrategy) {\n case 'aggressive':\n return {\n ...config,\n trafficThreshold: 1, // Cache almost everything\n defaultTtl: IS_PROD ? 900 : 300, // Longer TTL\n memorySize: config.memorySize * 1.5,\n enableMetrics: true\n };\n\n case 'conservative':\n return {\n ...config,\n trafficThreshold: IS_PROD ? 200 : 50, // Cache only high traffic\n defaultTtl: IS_PROD ? 300 : 60, // Shorter TTL\n memorySize: Math.floor(config.memorySize * 0.7),\n enableMetrics: IS_PROD\n };\n\n case 'memory-only':\n return {\n ...config,\n enableRedis: false, // Force disable Redis\n memorySize: config.memorySize * 2, // Larger memory\n trafficThreshold: 5 // Lower threshold since no Redis\n };\n\n case 'balanced':\n default:\n return config; // Use defaults\n }\n}\n\n// Build final configuration\nconst userConfig = getSmartDefaults();\nconst finalConfig = applyCacheStrategy(userConfig);\n\nexport const CACHE_CONFIG = {\n // Environment info\n ENVIRONMENT: ENV,\n IS_DEV,\n IS_PROD,\n IS_TEST,\n\n // Cache layers configuration\n ENABLE_MEMORY: finalConfig.enableMemory,\n ENABLE_REDIS: finalConfig.enableRedis,\n\n // Redis settings (safe even if Redis not available)\n REDIS_URL: finalConfig.redisUrl,\n REDIS_TOKEN: finalConfig.redisToken,\n REDIS_TIMEOUT: parseInt(process.env.CACHE_REDIS_TIMEOUT || '5000'),\n REDIS_RETRY_ATTEMPTS: parseInt(process.env.CACHE_REDIS_RETRIES || '3'),\n\n // Circuit breaker settings\n ENABLE_CIRCUIT_BREAKER: finalConfig.enableCircuitBreaker,\n CIRCUIT_FAILURE_THRESHOLD: parseInt(process.env.CACHE_CIRCUIT_THRESHOLD || '3'),\n CIRCUIT_RESET_TIMEOUT: parseInt(process.env.CACHE_CIRCUIT_RESET || '30000'),\n\n // Memory settings\n MEMORY_SIZE: finalConfig.memorySize,\n MEMORY_TTL_MAX: IS_DEV ? 120 : IS_PROD ? 300 : 180,\n\n // Traffic and performance\n TRAFFIC_THRESHOLD: finalConfig.trafficThreshold,\n DEFAULT_TTL: finalConfig.defaultTtl,\n CLEANUP_INTERVAL: finalConfig.cleanupInterval,\n\n // Feature flags\n ENABLE_LOGGING: finalConfig.enableLogging,\n ENABLE_METRICS: finalConfig.enableMetrics,\n ENABLE_TRAFFIC_DETECTION: finalConfig.enableTrafficDetection,\n\n // Performance limits\n MAX_VALUE_SIZE: parseInt(process.env.CACHE_MAX_VALUE_SIZE || '1048576'), // 1MB\n WINDOW_MS: parseInt(process.env.CACHE_WINDOW_MS || '60000'), // 1 minute\n TRACKER_CLEANUP_MS: parseInt(process.env.CACHE_TRACKER_CLEANUP || '300000'), // 5 minutes\n\n // Cache strategy info\n CACHE_STRATEGY: finalConfig.cacheStrategy,\n\n // Cache mode based on what's enabled\n CACHE_MODE: finalConfig.enableRedis && finalConfig.enableMemory ? 'HYBRID' :\n finalConfig.enableMemory && !finalConfig.enableRedis ? 'MEMORY_ONLY' :\n !finalConfig.enableMemory && finalConfig.enableRedis ? 'REDIS_ONLY' :\n 'DISABLED'\n} as const;\n\n// Export types for other modules\nexport type CacheConfig = typeof CACHE_CONFIG;\nexport type CacheMode = typeof CACHE_CONFIG.CACHE_MODE;\nexport type CacheStrategy = Required<UserCacheConfig>['cacheStrategy'];\n\n// Helper functions for configuration\nexport const CONFIG_HELPERS = {\n /**\n * Check if Redis is available and enabled\n */\n isRedisEnabled(): boolean {\n return CACHE_CONFIG.ENABLE_REDIS && !!CACHE_CONFIG.REDIS_URL;\n },\n\n /**\n * Check if memory cache is enabled\n */\n isMemoryEnabled(): boolean {\n return CACHE_CONFIG.ENABLE_MEMORY;\n },\n\n /**\n * Get current cache mode description\n */\n getCacheModeDescription(): string {\n switch (CACHE_CONFIG.CACHE_MODE) {\n case 'HYBRID':\n return 'Full 3-layer caching: Memory → Redis → Database';\n case 'MEMORY_ONLY':\n return '2-layer caching: Memory → Database (Redis disabled/unavailable)';\n case 'REDIS_ONLY':\n return '2-layer caching: Redis → Database (Memory disabled)';\n case 'DISABLED':\n return 'Direct database access (All caching disabled)';\n default:\n return 'Unknown cache mode';\n }\n },\n\n /**\n * Get configuration summary for logging\n */\n getConfigSummary() {\n return {\n mode: CACHE_CONFIG.CACHE_MODE,\n strategy: CACHE_CONFIG.CACHE_STRATEGY,\n environment: CACHE_CONFIG.ENVIRONMENT,\n redisEnabled: this.isRedisEnabled(),\n memoryEnabled: this.isMemoryEnabled(),\n loggingEnabled: CACHE_CONFIG.ENABLE_LOGGING,\n metricsEnabled: CACHE_CONFIG.ENABLE_METRICS,\n trafficDetection: CACHE_CONFIG.ENABLE_TRAFFIC_DETECTION,\n trafficThreshold: CACHE_CONFIG.TRAFFIC_THRESHOLD,\n defaultTtl: CACHE_CONFIG.DEFAULT_TTL,\n memorySize: CACHE_CONFIG.MEMORY_SIZE\n };\n }\n};\n\n/**\n * 📚 CONFIGURATION GUIDE\n * \n * Environment Variables for customization:\n * \n * === Redis Configuration ===\n * UPSTASH_REDIS_REST_URL=your_redis_url # Redis URL (if available)\n * UPSTASH_REDIS_REST_TOKEN=your_redis_token # Redis token (if available)\n * CACHE_ENABLE_REDIS=true|false # Force enable/disable Redis\n * CACHE_DISABLE_REDIS=true # Force disable Redis (overrides enable)\n * CACHE_REDIS_TIMEOUT=5000 # Redis timeout in ms\n * CACHE_REDIS_RETRIES=3 # Redis retry attempts\n * \n * === Memory Configuration ===\n * CACHE_ENABLE_MEMORY=true|false # Enable/disable memory cache\n * \n * === Feature Toggles ===\n * CACHE_ENABLE_LOGGING=true|false # Enable detailed logging\n * CACHE_ENABLE_METRICS=true|false # Enable performance metrics\n * CACHE_ENABLE_TRAFFIC=true|false # Enable traffic detection\n * CACHE_ENABLE_CIRCUIT_BREAKER=true|false # Enable Redis circuit breaker\n * \n * === Performance Tuning ===\n * CACHE_STRATEGY=aggressive|balanced|conservative|memory-only # Cache strategy\n * CACHE_MAX_VALUE_SIZE=1048576 # Max cache value size (bytes)\n * CACHE_WINDOW_MS=60000 # Traffic window (ms)\n * CACHE_TRACKER_CLEANUP=300000 # Tracker cleanup interval (ms)\n * \n * === Cache Strategies ===\n * - aggressive: Cache almost everything (dev/demo)\n * - balanced: Smart caching based on traffic (production default)\n * - conservative: Cache only high-traffic endpoints\n * - memory-only: Use only memory cache, no Redis\n * \n * === Cache Modes (Auto-detected) ===\n * - HYBRID: Memory → Redis → Database (best performance)\n * - MEMORY_ONLY: Memory → Database (Redis unavailable/disabled)\n * - REDIS_ONLY: Redis → Database (memory disabled)\n * - DISABLED: Direct database access (all caching disabled)\n * \n * === Per-Item Cache Options (NEW!) ===\n * \n * The cache now supports per-item configuration for production-grade control:\n * \n * // Basic usage (backward compatible)\n * await ezCache.fetch('key', fetcher, 300); // TTL only\n * \n * // Advanced usage with per-item control\n * await ezCache.fetch('key', fetcher, {\n * ttl: 300, // Memory: 300s, Redis: 3000s (auto 10x)\n * minTrafficCount: 50, // Cache after 50 requests/min (overrides global setting)\n * forceCaching: false // Respect traffic threshold (true = always cache)\n * });\n * \n * Examples:\n * - Hot data: { ttl: 30, minTrafficCount: 5, forceCaching: false }\n * - Warm data: { ttl: 300, minTrafficCount: 25, forceCaching: false } \n * - Cold data: { ttl: 1800, minTrafficCount: 100, forceCaching: false }\n * - Critical data: { ttl: 600, minTrafficCount: 1, forceCaching: true }\n * \n * This allows you to fine-tune caching behavior per endpoint for optimal performance!\n */\n\n// Export configuration for use in other modules\nexport default CACHE_CONFIG;\n","/**\n * 🧠 Production Memory Cache\n * \n * LRU cache with smart eviction, size tracking, and automatic cleanup\n */\n\nimport { CACHE_CONFIG } from './config';\nimport type { CacheItem, MemoryStats } from './types';\n\nexport class ProductionMemoryCache {\n private cache = new Map<string, CacheItem>();\n private totalSize = 0;\n private lastCleanup = Date.now();\n\n /**\n * Store data in memory cache\n * @param key - Cache key\n * @param data - Data to cache\n * @param ttlSeconds - Time to live in seconds\n * @returns Success status\n */\n set(key: string, data: unknown, ttlSeconds: number): boolean {\n try {\n // Estimate data size\n const dataSize = this.estimateSize(data);\n\n // Skip if data is too large\n if (dataSize > CACHE_CONFIG.MAX_VALUE_SIZE) {\n return false;\n }\n\n // Cleanup expired items first\n this.cleanupExpired();\n\n // Make room if needed\n while (this.cache.size >= CACHE_CONFIG.MEMORY_SIZE) {\n if (!this.evictLRU()) break;\n }\n\n // Store new item\n const item: CacheItem = {\n data,\n expires: Date.now() + (Math.min(ttlSeconds, CACHE_CONFIG.MEMORY_TTL_MAX) * 1000),\n lastAccess: Date.now(),\n hitCount: 1,\n size: dataSize,\n createdAt: Date.now()\n };\n\n // Remove old item if exists\n const existingItem = this.cache.get(key);\n if (existingItem) {\n this.totalSize -= existingItem.size;\n }\n\n this.cache.set(key, item);\n this.totalSize += dataSize;\n\n return true;\n } catch {\n return false;\n }\n }\n\n /**\n * Get data from memory cache\n * @param key - Cache key\n * @returns Cached data or null\n */\n get<T>(key: string): T | null {\n const item = this.cache.get(key);\n if (!item) return null;\n\n const now = Date.now();\n\n // Check expiry\n if (now > item.expires) {\n this.delete(key);\n return null;\n }\n\n // Update access stats\n item.lastAccess = now;\n item.hitCount++;\n\n return item.data as T;\n }\n\n /**\n * Delete specific cache entry\n * @param key - Cache key\n * @returns Success status\n */\n delete(key: string): boolean {\n const item = this.cache.get(key);\n if (item) {\n this.totalSize -= item.size;\n return this.cache.delete(key);\n }\n return false;\n }\n\n /**\n * Clear all cache entries\n */\n clear(): void {\n this.cache.clear();\n this.totalSize = 0;\n }\n\n /**\n * Get current cache size\n */\n size(): number {\n return this.cache.size;\n }\n\n /**\n * Smart LRU eviction based on multiple factors\n * @returns Success status\n */\n private evictLRU(): boolean {\n if (this.cache.size === 0) return false;\n\n let evictKey = '';\n let lowestScore = Number.MAX_SAFE_INTEGER;\n const now = Date.now();\n\n for (const [key, item] of this.cache.entries()) {\n // Scoring algorithm: lower score = more likely to evict\n // Factors: age, access frequency, last access time, size\n const age = now - item.createdAt;\n const timeSinceAccess = now - item.lastAccess;\n const accessFrequency = item.hitCount;\n const sizePenalty = item.size / 1024; // Size in KB\n\n const score = (timeSinceAccess / 1000) + (age / 10000) + sizePenalty - (accessFrequency * 100);\n\n if (score < lowestScore) {\n lowestScore = score;\n evictKey = key;\n }\n }\n\n if (evictKey) {\n this.delete(evictKey);\n return true;\n }\n\n return false;\n }\n\n /**\n * Cleanup expired items\n */\n private cleanupExpired(): void {\n const now = Date.now();\n\n // Throttle cleanup to avoid performance impact\n if (now - this.lastCleanup < 10000) return; // Max once per 10 seconds\n\n const expiredKeys: string[] = [];\n\n for (const [key, item] of this.cache.entries()) {\n if (now > item.expires) {\n expiredKeys.push(key);\n }\n }\n\n expiredKeys.forEach(key => this.delete(key));\n this.lastCleanup = now;\n }\n\n /**\n * Estimate data size in bytes\n * @param data - Data to estimate\n * @returns Size in bytes\n */\n private estimateSize(data: unknown): number {\n try {\n return JSON.stringify(data).length * 2; // Rough UTF-16 estimate\n } catch {\n return 1024; // Default 1KB if can't stringify\n }\n }\n\n /**\n * Get cache statistics\n */\n getStats(): MemoryStats {\n const now = Date.now();\n const items = Array.from(this.cache.values());\n\n return {\n size: this.cache.size,\n maxSize: CACHE_CONFIG.MEMORY_SIZE,\n totalSizeKB: Math.round(this.totalSize / 1024),\n utilization: Math.round((this.cache.size / CACHE_CONFIG.MEMORY_SIZE) * 100),\n expiredItems: items.filter(item => now > item.expires).length,\n avgHitCount: items.length > 0 ?\n Math.round(items.reduce((sum, item) => sum + item.hitCount, 0) / items.length) : 0,\n oldestItem: items.length > 0 ?\n Math.round((now - Math.min(...items.map(item => item.createdAt))) / 1000) : 0\n };\n }\n}\n","/**\n * 📊 Production Traffic Tracker\n * \n * Tracks API request patterns with memory leak prevention and automatic cleanup\n */\n\nimport { CACHE_CONFIG } from './config';\nimport type { TrafficData, TrafficStats } from './types';\n\nexport class ProductionTrafficTracker {\n private requests = new Map<string, TrafficData>();\n private lastGlobalCleanup = Date.now();\n\n /**\n * Track a request and return current count\n * @param key - Endpoint identifier\n * @returns Current request count in window\n */\n track(key: string): number {\n const now = Date.now();\n\n // Get or create tracking data\n let data = this.requests.get(key);\n if (!data) {\n data = {\n timestamps: [],\n lastCleanup: now,\n totalRequests: 0\n };\n this.requests.set(key, data);\n }\n\n // Periodic cleanup to prevent memory leaks\n if (now - data.lastCleanup > CACHE_CONFIG.TRACKER_CLEANUP_MS) {\n this.cleanupEndpoint(key, data);\n data.lastCleanup = now;\n }\n\n // Global cleanup periodically\n if (now - this.lastGlobalCleanup > CACHE_CONFIG.TRACKER_CLEANUP_MS * 2) {\n this.globalCleanup();\n this.lastGlobalCleanup = now;\n }\n\n // Filter valid requests within window\n data.timestamps = data.timestamps.filter(time =>\n now - time < CACHE_CONFIG.WINDOW_MS\n );\n\n // Add current request\n data.timestamps.push(now);\n data.totalRequests++;\n\n return data.timestamps.length;\n }\n\n /**\n * Check if endpoint has high traffic\n * @param key - Endpoint identifier\n * @param customThreshold - Custom traffic threshold (optional, uses config default)\n * @returns True if high traffic detected\n */\n isHighTraffic(key: string, customThreshold?: number): boolean {\n const count = this.track(key);\n const threshold = customThreshold || CACHE_CONFIG.TRAFFIC_THRESHOLD;\n return count >= threshold;\n }\n\n /**\n * Get current request count for endpoint\n * @param key - Endpoint identifier\n * @returns Current request count\n */\n getCurrentCount(key: string): number {\n const data = this.requests.get(key);\n if (!data) return 0;\n\n const now = Date.now();\n const validRequests = data.timestamps.filter(time =>\n now - time < CACHE_CONFIG.WINDOW_MS\n );\n\n return validRequests.length;\n }\n\n /**\n * Clean up old timestamps for specific endpoint\n * @param key - Endpoint identifier\n * @param data - Traffic data to clean\n */\n private cleanupEndpoint(key: string, data: TrafficData): void {\n const now = Date.now();\n data.timestamps = data.timestamps.filter(time =>\n now - time < CACHE_CONFIG.WINDOW_MS\n );\n }\n\n /**\n * Global cleanup of inactive endpoints\n */\n private globalCleanup(): void {\n const now = Date.now();\n const inactiveKeys: string[] = [];\n\n for (const [key, data] of this.requests.entries()) {\n // Remove endpoints with no recent activity\n if (data.timestamps.length === 0 ||\n (data.timestamps.length > 0 && now - data.timestamps[data.timestamps.length - 1] > CACHE_CONFIG.TRACKER_CLEANUP_MS)) {\n inactiveKeys.push(key);\n }\n }\n\n inactiveKeys.forEach(key => this.requests.delete(key));\n }\n\n /**\n * Get comprehensive traffic statistics\n */\n getStats(): TrafficStats {\n const now = Date.now();\n const stats: Record<string, unknown> = {};\n\n for (const [key, data] of this.requests.entries()) {\n const recentRequests = data.timestamps.filter(time =>\n now - time < CACHE_CONFIG.WINDOW_MS\n );\n\n stats[key] = {\n currentRequests: recentRequests.length,\n totalRequests: data.totalRequests,\n isHighTraffic: recentRequests.length >= CACHE_CONFIG.TRAFFIC_THRESHOLD,\n lastActivity: data.timestamps.length > 0 ?\n new Date(data.timestamps[data.timestamps.length - 1]).toISOString() : null\n };\n }\n\n return {\n endpoints: stats,\n trackedEndpoints: this.requests.size,\n trafficThreshold: CACHE_CONFIG.TRAFFIC_THRESHOLD\n };\n }\n\n /**\n * Clear all traffic data\n */\n clear(): void {\n this.requests.clear();\n this.lastGlobalCleanup = Date.now();\n }\n\n /**\n * Get number of tracked endpoints\n */\n getTrackedEndpointsCount(): number {\n return this.requests.size;\n }\n}\n","/* eslint-disable @typescript-eslint/no-unused-vars */\n/**\n * 💾 Resilient Redis Client - Production Grade\n * \n * Features:\n * - Graceful fallback when Redis is not available\n * - Circuit breaker pattern for fault tolerance\n * - Handles missing @upstash/redis package\n * - Safe for projects that don't use Redis\n * - Connection pooling and retry logic\n * - Health monitoring and metrics\n */\n\nimport { CACHE_CONFIG } from './config';\nimport type { RedisStatus } from './types';\n\n// Type for Redis client (to avoid import errors if package not installed)\ninterface RedisClient {\n ping(): Promise<string>;\n get(key: string): Promise<unknown>;\n setex(key: string, ttl: number, value: unknown): Promise<string>;\n del(key: string): Promise<number>;\n // Add more Redis methods as needed\n exists(key: string): Promise<number>;\n ttl(key: string): Promise<number>;\n keys(pattern: string): Promise<string[]>;\n}\n\nexport class ResilientRedis {\n private redis: RedisClient | null = null;\n private isConnected = false;\n private circuitOpen = false;\n private failureCount = 0;\n private lastFailure = 0;\n private connectionAttempts = 0;\n private lastConnectionAttempt = 0;\n private redisAvailable = false;\n private healthCheckInterval: ReturnType<typeof setInterval> | null = null;\n private reconnectTimeout: ReturnType<typeof setTimeout> | null = null;\n private operationMetrics = {\n totalOperations: 0,\n successfulOperations: 0,\n failedOperations: 0,\n avgResponseTime: 0,\n lastOperationTime: 0\n };\n\n private status: RedisStatus = {\n available: false,\n connected: false,\n circuitOpen: false,\n failures: 0,\n lastFailure: 0,\n mode: 'unavailable'\n };\n\n constructor() {\n this.checkRedisAvailability();\n if (this.redisAvailable) {\n this.initializeRedis();\n this.startHealthMonitoring();\n }\n\n // Cleanup on process exit\n process.on('exit', () => this.cleanup());\n process.on('SIGINT', () => this.cleanup());\n process.on('SIGTERM', () => this.cleanup());\n }\n\n /**\n * Check if Redis package and configuration are available\n */\n private checkRedisAvailability(): void {\n try {\n // Check if @upstash/redis package is installed\n require.resolve('@upstash/redis');\n\n // Check if Redis is enabled in config\n if (!CACHE_CONFIG.ENABLE_REDIS) {\n if (CACHE_CONFIG.ENABLE_LOGGING) {\n console.log('[REDIS] Disabled in configuration');\n }\n return;\n }\n\n // Check if Redis credentials are provided\n if (!CACHE_CONFIG.REDIS_URL || !CACHE_CONFIG.REDIS_TOKEN) {\n if (CACHE_CONFIG.ENABLE_LOGGING) {\n console.log('[REDIS] Credentials not provided - running in memory-only mode');\n }\n return;\n }\n\n this.redisAvailable = true;\n\n } catch (error) {\n if (CACHE_CONFIG.ENABLE_LOGGING) {\n console.error('[REDIS] Error checking availability:', error);\n console.log('[REDIS] Package not installed - graceful fallback to memory-only mode');\n }\n this.redisAvailable = false;\n }\n }\n\n /**\n * Initialize Redis connection (only if available)\n */\n private async initializeRedis(): Promise<void> {\n if (!this.redisAvailable) {\n return;\n }\n\n // Prevent concurrent initialization attempts\n if (this.lastConnectionAttempt && Date.now() - this.lastConnectionAttempt < 5000) {\n return;\n }\n\n this.lastConnectionAttempt = Date.now();\n this.connectionAttempts++;\n\n try {\n // Try to dynamically import Redis - this should work at runtime if package is installed\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let redisModule: any = null;\n try {\n redisModule = await import('@upstash/redis');\n } catch (importError) {\n this.log('[REDIS] @upstash/redis package not found - running in memory-only mode');\n this.redis = null;\n this.status.available = false;\n this.status.mode = 'unavailable';\n this.redisAvailable = false;\n return;\n }\n\n const { Redis } = redisModule;\n\n // Create Redis instance with timeout and retry configuration\n this.redis = new Redis({\n url: CACHE_CONFIG.REDIS_URL,\n token: CACHE_CONFIG.REDIS_TOKEN,\n retry: {\n retries: 3,\n backoff: (retryCount: number) => Math.exp(retryCount) * 50,\n },\n // Set connection timeout\n automaticDeserialization: true,\n });\n\n // Test connection with timeout\n const connectionPromise = this.redis!.ping();\n const timeoutPromise = new Promise((_, reject) =>\n setTimeout(() => reject(new Error('Connection timeout')), CACHE_CONFIG.REDIS_TIMEOUT)\n );\n\n await Promise.race([connectionPromise, timeoutPromise]);\n\n this.isConnected = true;\n this.resetCircuit();\n this.updateStatus();\n\n this.log('[REDIS] Connected successfully');\n\n } catch (error) {\n this.redis = null;\n this.isConnected = false;\n this.recordFailure();\n this.updateStatus();\n\n this.log(`[REDIS] Connection failed (attempt ${this.connectionAttempts}): ${error instanceof Error ? error.message : 'Unknown error'}`);\n\n // Schedule reconnection if under max attempts\n if (this.connectionAttempts < 5) {\n this.scheduleReconnection();\n }\n }\n }\n\n /**\n * Get value from Redis with enhanced error handling and metrics\n */\n async get(key: string): Promise<unknown> {\n if (!this.isAvailable()) {\n return null;\n }\n\n const startTime = Date.now();\n this.operationMetrics.totalOperations++;\n\n try {\n const result = await Promise.race([\n this.redis!.get(key),\n new Promise((_, reject) =>\n setTimeout(() => reject(new Error('Timeout')), CACHE_CONFIG.REDIS_TIMEOUT)\n )\n ]);\n\n this.recordSuccessfulOperation(startTime);\n return result;\n\n } catch (error) {\n this.recordFailedOperation(startTime);\n this.recordFailure();\n this.log(`[REDIS] Get operation failed for key \"${key}\": ${error instanceof Error ? error.message : 'Unknown error'}`);\n return null;\n }\n }\n\n /**\n * Set value in Redis with expiration\n */\n async setex(key: string, ttl: number, data: unknown): Promise<boolean> {\n if (!this.isAvailable()) {\n return false;\n }\n\n try {\n await Promise.race([\n this.redis!.setex(key, ttl, data),\n new Promise((_, reject) =>\n setTimeout(() => reject(new Error('Timeout')), CACHE_CONFIG.REDIS_TIMEOUT)\n )\n ]);\n\n this.resetCircuit();\n return true;\n\n } catch (error) {\n this.recordFailure();\n return false;\n }\n }\n\n /**\n * Delete key from Redis\n */\n async del(key: string): Promise<boolean> {\n if (!this.isAvailable()) {\n return false;\n }\n\n try {\n await this.redis!.del(key);\n this.resetCircuit();\n return true;\n } catch (error) {\n this.recordFailure();\n return false;\n }\n }\n\n /**\n * Check if Redis is available for operations\n */\n private isAvailable(): boolean {\n // If Redis was never initialized, it's not available\n if (!this.redisAvailable || !this.redis) {\n return false;\n }\n\n // If circuit breaker is disabled, always try\n if (!CACHE_CONFIG.ENABLE_CIRCUIT_BREAKER) {\n return true;\n }\n\n // Circuit breaker logic\n if (this.circuitOpen) {\n const now = Date.now();\n if (now - this.lastFailure < CACHE_CONFIG.CIRCUIT_RESET_TIMEOUT) {\n return false; // Circuit still open\n } else {\n // Try to reset circuit\n this.circuitOpen = false;\n this.failureCount = Math.max(0, this.failureCount - 1);\n }\n }\n\n return true;\n }\n\n /**\n * Record a failure and potentially open circuit\n */\n private recordFailure(): void {\n this.failureCount++;\n this.lastFailure = Date.now();\n\n if (this.failureCount >= CACHE_CONFIG.CIRCUIT_FAILURE_THRESHOLD) {\n this.circuitOpen = true;\n this.isConnected = false;\n\n if (CACHE_CONFIG.ENABLE_LOGGING) {\n console.log('[REDIS] Circuit breaker opened - too many failures');\n }\n }\n }\n\n /**\n * Reset circuit breaker\n */\n private resetCircuit(): void {\n if (this.failureCount > 0 || this.circuitOpen) {\n this.failureCount = 0;\n this.circuitOpen = false;\n this.isConnected = true;\n\n if (CACHE_CONFIG.ENABLE_LOGGING) {\n console.log('[REDIS] Circuit breaker reset - connection restored');\n }\n }\n }\n\n /**\n * Get Redis connection status\n */\n getConnectionStatus(): RedisStatus {\n return {\n available: this.redisAvailable,\n connected: this.isConnected,\n circuitOpen: this.circuitOpen,\n failures: this.failureCount,\n lastFailure: this.lastFailure,\n mode: !this.redisAvailable ? 'unavailable' :\n this.circuitOpen ? 'circuit-open' :\n this.isConnected ? 'connected' : 'disconnected'\n };\n }\n\n /**\n * Force retry connection (for admin/debugging)\n */\n async forceReconnect(): Promise<boolean> {\n if (!this.redisAvailable) {\n return false;\n }\n\n this.resetCircuit();\n await this.initializeRedis();\n return this.isConnected;\n }\n\n /**\n * Get Redis health info\n */\n async getHealthInfo(): Promise<{\n available: boolean;\n connected: boolean;\n latency?: number;\n error?: string;\n }> {\n if (!this.isAvailable()) {\n return {\n available: this.redisAvailable,\n connected: false,\n error: this.redisAvailable ? 'Circuit breaker open' : 'Redis not available'\n };\n }\n\n try {\n const start = Date.now();\n await this.redis!.ping();\n const latency = Date.now() - start;\n\n return {\n available: true,\n connected: true,\n latency\n };\n\n } catch (error) {\n return {\n available: this.redisAvailable,\n connected: false,\n error: error instanceof Error ? error.message : 'Unknown error'\n };\n }\n }\n\n /**\n * Log messages (internal logging method)\n */\n private log(message: string): void {\n if (CACHE_CONFIG.ENABLE_LOGGING) {\n console.log(`[${new Date().toISOString()}] ${message}`);\n }\n }\n\n /**\n * Update internal status object\n */\n private updateStatus(): void {\n this.status = {\n available: this.redisAvailable,\n connected: this.isConnected,\n circuitOpen: this.circuitOpen,\n failures: this.failureCount,\n lastFailure: this.lastFailure,\n mode: !this.redisAvailable ? 'unavailable' :\n this.circuitOpen ? 'circuit-open' :\n this.isConnected ? 'connected' : 'disconnected'\n };\n }\n\n /**\n * Schedule reconnection attempt\n */\n private scheduleReconnection(): void {\n if (this.reconnectTimeout) {\n clearTimeout(this.reconnectTimeout);\n }\n\n const delay = Math.min(5000 * Math.pow(2, this.connectionAttempts - 1), 30000); // Exponential backoff, max 30s\n\n this.reconnectTimeout = setTimeout(() => {\n this.log(`[REDIS] Attempting reconnection (${this.connectionAttempts + 1}/5)`);\n this.initializeRedis();\n }, delay);\n }\n\n /**\n * Record successful operation for metrics\n */\n private recordSuccessfulOperation(startTime: number): void {\n this.operationMetrics.successfulOperations++;\n const responseTime = Date.now() - startTime;\n this.operationMetrics.lastOperationTime = responseTime;\n\n // Update average response time (rolling average)\n this.operationMetrics.avgResponseTime =\n (this.operationMetrics.avgResponseTime * (this.operationMetrics.successfulOperations - 1) + responseTime) /\n this.operationMetrics.successfulOperations;\n }\n\n /**\n * Record failed operation for metrics\n */\n private recordFailedOperation(startTime: number): void {\n this.operationMetrics.failedOperations++;\n this.operationMetrics.lastOperationTime = Date.now() - startTime;\n }\n\n /**\n * Start health monitoring (periodic health checks)\n */\n private startHealthMonitoring(): void {\n if (!this.redisAvailable || this.healthCheckInterval) {\n return;\n }\n\n // Health check every 30 seconds\n this.healthCheckInterval = setInterval(async () => {\n if (this.isConnected && this.redis) {\n try {\n await Promise.race([\n this.redis.ping(),\n new Promise((_, reject) =>\n setTimeout(() => reject(new Error('Health check timeout')), 5000)\n )\n ]);\n\n // If we were disconnected, log reconnection\n if (!this.isConnected) {\n this.log('[REDIS] Health check passed - connection restored');\n this.isConnected = true;\n this.resetCircuit();\n this.updateStatus();\n }\n\n } catch (error) {\n this.log(`[REDIS] Health check failed: ${error instanceof Error ? error.message : 'Unknown error'}`);\n this.isConnected = false;\n this.recordFailure();\n this.updateStatus();\n }\n } else if (!this.isConnected && this.redisAvailable && !this.circuitOpen) {\n // Try to reconnect if we're not connected but Redis should be available\n this.log('[REDIS] Health check: attempting reconnection');\n this.initializeRedis();\n }\n }, 30000);\n }\n\n /**\n * Cleanup resources\n */\n private cleanup(): void {\n if (this.healthCheckInterval) {\n clearInterval(this.healthCheckInterval);\n this.healthCheckInterval = null;\n }\n\n if (this.reconnectTimeout) {\n clearTimeout(this.reconnectTimeout);\n this.reconnectTimeout = null;\n }\n\n if (this.redis && this.isConnected) {\n this.log('[REDIS] Cleaning up connection');\n // Note: Upstash Redis doesn't have explicit close method\n this.redis = null;\n this.isConnected = false;\n }\n }\n\n /**\n * Get operation metrics\n */\n getMetrics(): {\n totalOperations: number;\n successfulOperations: number;\n failedOperations: number;\n successRate: number;\n avgResponseTime: number;\n lastOperationTime: number;\n } {\n const successRate = this.operationMetrics.totalOperations > 0 ?\n (this.operationMetrics.successfulOperations / this.operationMetrics.totalOperations) * 100 : 0;\n\n return {\n ...this.operationMetrics,\n successRate: Math.round(successRate * 100) / 100 // Round to 2 decimal places\n };\n }\n\n /**\n * Additional Redis operations for completeness\n */\n async exists(key: string): Promise<boolean> {\n if (!this.isAvailable()) {\n return false;\n }\n\n try {\n const result = await this.redis!.exists(key);\n return result === 1;\n } catch (error) {\n this.recordFailure();\n return false;\n }\n }\n\n async ttl(key: string): Promise<number> {\n if (!this.isAvailable()) {\n return -1;\n }\n\n try {\n const result = await this.redis!.ttl(key);\n return typeof result === 'number' ? result : -1;\n } catch (error) {\n this.recordFailure();\n return -1;\n }\n }\n\n async keys(pattern: string): Promise<string[]> {\n if (!this.isAvailable()) {\n return [];\n }\n\n try {\n const result = await this.redis!.keys(pattern);\n return Array.isArray(result) ? result : [];\n } catch (error) {\n this.recordFailure();\n return [];\n }\n }\n}\n\n// Singleton instance for easy usage\nlet redisInstance: ResilientRedis | null = null;\n\nexport function getRedisClient(): ResilientRedis {\n if (!redisInstance) {\n redisInstance = new ResilientRedis();\n }\n return redisInstance;\n}\n\n// Default export for convenience\nexport const redis = getRedisClient();\n","/**\n * 📈 Performance Metrics Tracker\n * \n * Tracks cache performance metrics including hit rates and response times\n */\n\nimport { CACHE_CONFIG } from './config';\nimport type { MetricsData, PerformanceStats } from './types';\n\nexport class PerformanceMetrics {\n private metrics: MetricsData = {\n hits: 0,\n misses: 0,\n errors: 0,\n totalRequests: 0,\n avgResponseTime: 0,\n lastReset: Date.now()\n };\n\n private responseTimes: number[] = [];\n\n /**\n * Record a cache hit\n * @param responseTime - Response time in milliseconds\n */\n recordHit(responseTime: number): void {\n if (!CACHE_CONFIG.ENABLE_METRICS) return;\n\n this.metrics.hits++;\n this.metrics.totalRequests++;\n this.recordResponseTime(responseTime);\n }\n\n /**\n * Record a cache miss\n * @param responseTime - Response time in milliseconds\n */\n recordMiss(responseTime: number): void {\n if (!CACHE_CONFIG.ENABLE_METRICS) return;\n\n this.metrics.misses++;\n this.metrics.totalRequests++;\n this.recordResponseTime(responseTime);\n }\n\n /**\n * Record an error\n */\n recordError(): void {\n if (!CACHE_CONFIG.ENABLE_METRICS) return;\n\n this.metrics.errors++;\n this.metrics.totalRequests++;\n }\n\n /**\n * Record response time and update average\n * @param time - Response time in milliseconds\n */\n private recordResponseTime(time: number): void {\n this.responseTimes.push(time);\n\n // Keep only last 1000 response times to prevent memory bloat\n if (this.responseTimes.length > 1000) {\n this.responseTimes = this.responseTimes.slice(-1000);\n }\n\n // Update rolling average\n this.metrics.avgResponseTime = this.responseTimes.reduce((a, b) => a + b, 0) / this.responseTimes.length;\n }\n\n /**\n * Get comprehensive performance statistics\n */\n getStats(): PerformanceStats {\n const hitRate = this.metrics.totalRequests > 0 ?\n Math.round((this.metrics.hits / this.metrics.totalRequests) * 100) : 0;\n\n const errorRate = this.metrics.totalRequests > 0 ?\n Math.round((this.metrics.errors / this.metrics.totalRequests) * 100) : 0;\n\n return {\n ...this.metrics,\n hitRate,\n errorRate,\n avgResponseTime: Math.round(this.metrics.avgResponseTime * 100) / 100\n };\n }\n\n /**\n * Get detailed response time statistics\n */\n getResponseTimeStats(): {\n min: number;\n max: number;\n median: number;\n p95: number;\n p99: number;\n } {\n if (this.responseTimes.length === 0) {\n return { min: 0, max: 0, median: 0, p95: 0, p99: 0 };\n }\n\n const sorted = [...this.responseTimes].sort((a, b) => a - b);\n const len = sorted.length;\n\n return {\n min: sorted[0],\n max: sorted[len - 1],\n median: sorted[Math.floor(len / 2)],\n p95: sorted[Math.floor(len * 0.95)],\n p99: sorted[Math.floor(len * 0.99)]\n };\n }\n\n /**\n * Reset all metrics\n */\n reset(): void {\n this.metrics = {\n hits: 0,\n misses: 0,\n errors: 0,\n totalRequests: 0,\n avgResponseTime: 0,\n lastReset: Date.now()\n };\n this.responseTimes = [];\n }\n\n /**\n * Get cache efficiency score (0-100)\n */\n getEfficiencyScore(): number {\n if (this.metrics.totalRequests === 0) return 0;\n\n const hitRate = (this.metrics.hits / this.metrics.totalRequests) * 100;\n const errorRate = (this.metrics.errors / this.metrics.totalRequests) * 100;\n const responseTimeScore = Math.max(0, 100 - (this.metrics.avgResponseTime / 10)); // Lower is better\n\n // Weighted score: 60% hit rate, 30% low error rate, 10% response time\n return Math.round(\n (hitRate * 0.6) +\n ((100 - errorRate) * 0.3) +\n (responseTimeScore * 0.1)\n );\n }\n\n /**\n * Check if metrics collection is enabled\n */\n isEnabled(): boolean {\n return CACHE_CONFIG.ENABLE_METRICS;\n }\n}\n","/**\n * 🚀 Production EZ Cache - Main Controller\n * \n * The main cache orchestrator that coordinates all components\n * Supports multiple cache modes: HYBRID, MEMORY_ONLY, REDIS_ONLY, DISABLED\n */\n\nimport { CACHE_CONFIG, CONFIG_HELPERS } from './config';\nimport { ProductionMemoryCache } from './memory';\nimport { ProductionTrafficTracker } from './traffic';\nimport { ResilientRedis } from './redis';\nimport { PerformanceMetrics } from './metrics';\nimport type { SystemStats, CacheOptions, SimpleCacheOptions } from './types';\n\nexport class ProductionEZCache {\n private memory: ProductionMemoryCache | null = null;\n private traffic: ProductionTrafficTracker | null = null;\n private redis: ResilientRedis | null = null;\n private metrics: PerformanceMetrics | null = null;\n private cleanupInterval: ReturnType<typeof setInterval> | null = null;\n\n constructor() {\n this.log(`🚀 Production EZ Cache v3 starting (${CACHE_CONFIG.ENVIRONMENT})...`);\n this.log(`📋 Cache Mode: ${CACHE_CONFIG.CACHE_MODE}`);\n this.log(`🎯 Strategy: ${CACHE_CONFIG.CACHE_STRATEGY}`);\n\n // Initialize components based on configuration\n this.initializeComponents();\n\n // Setup automatic cleanup\n this.setupCleanup();\n\n // Log configuration\n this.logConfiguration();\n\n this.log(`✅ Production EZ Cache ready! ${CONFIG_HELPERS.getCacheModeDescription()}`);\n }\n\n /**\n * Initialize cache components based on configuration\n */\n private initializeComponents(): void {\n // Initialize memory cache if enabled\n if (CACHE_CONFIG.ENABLE_MEMORY) {\n this.memory = new ProductionMemoryCache();\n this.log(`🧠 Memory cache initialized (${CACHE_CONFIG.MEMORY_SIZE} items)`);\n }\n\n // Initialize Redis if enabled\n if (CACHE_CONFIG.ENABLE_REDIS) {\n this.redis = new ResilientRedis();\n this.log(`💾 Redis client initialized`);\n }\n\n // Initialize traffic tracker if needed\n if (CACHE_CONFIG.ENABLE_TRAFFIC_DETECTION) {\n this.traffic = new ProductionTrafficTracker();\n this.log(`📊 Traffic tracking enabled`);\n }\n\n // Initialize metrics if enabled\n if (CACHE_CONFIG.ENABLE_METRICS) {\n this.metrics = new PerformanceMetrics();\n this.log(`📈 Performance metrics enabled`);\n }\n }\n\n /**\n * Helper function to calculate smart defaults based on TTL\n * Shorter TTL = More aggressive caching (lower traffic threshold)\n * Longer TTL = More conservative caching (higher traffic threshold)\n */\n private getSmartTrafficThreshold(ttl: number): number {\n if (ttl <= 60) {\n // Hot data (≤1 min) - very aggressive caching\n return Math.max(3, Math.floor(CACHE_CONFIG.TRAFFIC_THRESHOLD * 0.1));\n } else if (ttl <= 600) {\n // Warm data (≤10 min) - balanced caching \n return Math.max(10, Math.floor(CACHE_CONFIG.TRAFFIC_THRESHOLD * 0.3));\n } else {\n // Cold data (>10 min) - conservative caching\n return CACHE_CONFIG.TRAFFIC_THRESHOLD;\n }\n }\n\n /**\n * 🎯 Main cache method - TTL-focused with smart defaults\n * \n * Features intelligent defaults and flexible options:\n * - Pass just a number for TTL (gets smart traffic threshold)\n * - Pass an object for full control with TypeScript suggestions\n * - Smart traffic thresholds: Short TTL = aggressive, Long TTL = conservative\n * - Redis TTL automatically set to 10x memory TTL for optimal performance\n * \n * @param key - Cache key (auto-prefixed)\n * @param fetcher - Function to get data from database\n * @param optionsOrTtl - TTL number (simple) or options object (advanced) - optional\n * @returns Cached or fresh data\n * \n * @example\n * // Simple TTL (gets smart defaults)\n * await ezCache.fetch('products', fetcher, 300);\n * \n * @example \n * // Advanced with suggestions\n * await ezCache.fetch('products', fetcher, {\n *