import Redis from 'ioredis'; import { logger } from '../utils/logger.js'; export class RedisClient { constructor() { this.client = null; } static getInstance() { if (!RedisClient.instance) { RedisClient.instance = new RedisClient(); } return RedisClient.instance; } async connect() { const config = { host: process.env.REDIS_HOST || 'localhost', port: process.env.REDIS_PORT || 6379, password: process.env.REDIS_PASSWORD || undefined, db: parseInt(process.env.REDIS_DB) || 2, // Different DB for safety guard retryStrategy: (times) => { const delay = Math.min(times * 50, 2000); return delay; }, enableOfflineQueue: false }; try { this.client = new Redis(config); this.client.on('connect', () => { logger.info('Redis connection established'); }); this.client.on('error', (err) => { logger.error('Redis error:', err); }); this.client.on('close', () => { logger.warn('Redis connection closed'); }); // Wait for connection await this.client.ping(); return this.client; } catch (error) { logger.error('Failed to connect to Redis:', error); throw error; } } async checkHealth() { try { const result = await this.client.ping(); return result === 'PONG'; } catch (error) { logger.error('Redis health check failed:', error); return false; } } async disconnect() { if (this.client) { await this.client.quit(); logger.info('Redis connection closed'); } } // Cache methods with JSON serialization async setWithExpiry(key, value, ttl) { return await this.client.setex(key, ttl, JSON.stringify(value)); } async get(key) { const value = await this.client.get(key); return value ? JSON.parse(value) : null; } async del(key) { return await this.client.del(key); } async exists(key) { return await this.client.exists(key); } // Hash operations async hset(key, field, value) { return await this.client.hset(key, field, JSON.stringify(value)); } async hget(key, field) { const value = await this.client.hget(key, field); return value ? JSON.parse(value) : null; } async hdel(key, field) { return await this.client.hdel(key, field); } async hgetall(key) { const data = await this.client.hgetall(key); const result = {}; for (const [field, value] of Object.entries(data)) { result[field] = JSON.parse(value); } return result; } // List operations async lpush(key, value) { return await this.client.lpush(key, JSON.stringify(value)); } async rpush(key, value) { return await this.client.rpush(key, JSON.stringify(value)); } async lrange(key, start, stop) { const items = await this.client.lrange(key, start, stop); return items.map(item => JSON.parse(item)); } async ltrim(key, start, stop) { return await this.client.ltrim(key, start, stop); } // Set operations async sadd(key, member) { return await this.client.sadd(key, JSON.stringify(member)); } async srem(key, member) { return await this.client.srem(key, JSON.stringify(member)); } async smembers(key) { const members = await this.client.smembers(key); return members.map(member => JSON.parse(member)); } async sismember(key, member) { return await this.client.sismember(key, JSON.stringify(member)); } // Expiry operations async expire(key, seconds) { return await this.client.expire(key, seconds); } async ttl(key) { return await this.client.ttl(key); } }