import Redis from 'ioredis'; import crypto from 'crypto'; import { config } from '../config/env.js'; import { logger } from '../utils/logger.js'; class CacheService { private redis: Redis | null = null; private connected = false; constructor() { try { this.redis = new Redis(config.REDIS_URL, { maxRetriesPerRequest: 3, retryStrategy: (times: number) => { // Do not retry. Fail immediately to gracefully disable caching and prevent log spam. return null; }, lazyConnect: true, }); this.redis.on('connect', () => { this.connected = true; logger.info('Redis connected'); }); this.redis.on('error', (err) => { this.connected = false; logger.warn('Redis error', { error: err.message }); }); this.redis.connect().catch(() => { logger.warn('Redis connection failed — caching disabled'); }); } catch { logger.warn('Redis initialization failed — caching disabled'); } } async get(key: string): Promise { if (!this.connected || !this.redis) return null; try { const data = await this.redis.get(key); return data ? JSON.parse(data) as T : null; } catch (err) { logger.warn('Cache get failed', { key, error: (err as Error).message }); return null; } } async set(key: string, value: unknown, ttlSeconds: number): Promise { if (!this.connected || !this.redis) return; try { await this.redis.setex(key, ttlSeconds, JSON.stringify(value)); } catch (err) { logger.warn('Cache set failed', { key, error: (err as Error).message }); } } async delete(key: string): Promise { if (!this.connected || !this.redis) return; try { await this.redis.del(key); } catch (err) { logger.warn('Cache delete failed', { key, error: (err as Error).message }); } } async getCachedOrCompute(key: string, computeFn: () => Promise, ttlSeconds: number): Promise { const cached = await this.get(key); if (cached !== null) { logger.debug('Cache hit', { key }); return cached; } logger.debug('Cache miss', { key }); const result = await computeFn(); await this.set(key, result, ttlSeconds); return result; } static hashKey(...parts: string[]): string { return crypto.createHash('sha256').update(parts.join('|')).digest('hex'); } } export const cache = new CacheService(); export { CacheService };