"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; var RedisService_1; Object.defineProperty(exports, "__esModule", { value: true }); exports.RedisService = void 0; const common_1 = require("@nestjs/common"); const ioredis_1 = __importDefault(require("ioredis")); let RedisService = RedisService_1 = class RedisService { // Keep client typed as any to avoid tight coupling to ioredis types in tests client = null; logger = new common_1.Logger(RedisService_1.name); lastLogAt = 0; LOG_THROTTLE_MS = 5000; // throttle repeated error logs onModuleInit() { const enabled = (process.env.REDIS_ENABLED || 'true').toLowerCase() === 'true'; if (!enabled) { this.logger.log('Redis disabled via REDIS_ENABLED=false'); return; } const url = process.env.REDIS_URL || 'redis://localhost:6379'; // Use lazyConnect so application can start even if Redis is unavailable temporarily this.client = new ioredis_1.default(url, { lazyConnect: true, // limit retries to avoid infinite reconnect storms maxRetriesPerRequest: 5, // automatic reconnection strategy reconnectOnError: (err) => { return true; }, enableOfflineQueue: true, // optional reconnect strategy retryStrategy: (times) => { // exponential backoff capped at 5s const delay = Math.min(50 * Math.pow(2, times), 5000); return delay; }, }); this.client.on('connect', () => this.logger.log('Connected to Redis')); this.client.on('ready', () => this.logger.log('Redis ready')); this.client.on('error', (err) => this.handleError(err)); this.client.on('close', () => this.logger.warn('Redis connection closed')); this.client.on('reconnecting', () => this.logger.log('Redis reconnecting')); // attempt to connect but do not throw if it fails this.client.connect().catch((err) => { this.handleError(err); }); } handleError(err) { const now = Date.now(); if (now - this.lastLogAt > this.LOG_THROTTLE_MS) { this.logger.error('Redis error', err instanceof Error ? err.message : err); this.lastLogAt = now; } } onModuleDestroy() { if (this.client) { try { this.client.disconnect(); } catch (e) { // ignore } } } getClient() { return this.client; } ensureClient() { // returns true if client is connected/usable return this.client && this.client.status && this.client.status !== 'end' && this.client.status !== 'close'; } async get(key) { if (!this.ensureClient()) return null; try { return await this.client.get(key); } catch (e) { this.handleError(e); return null; } } async set(key, value, ttlSeconds) { if (!this.ensureClient()) return; try { if (ttlSeconds) { await this.client.set(key, value, 'EX', ttlSeconds); } else { await this.client.set(key, value); } } catch (e) { this.handleError(e); } } async del(key) { if (!this.ensureClient()) return; try { await this.client.del(key); } catch (e) { this.handleError(e); } } }; exports.RedisService = RedisService; exports.RedisService = RedisService = RedisService_1 = __decorate([ (0, common_1.Injectable)() ], RedisService); //# sourceMappingURL=redis.service.js.map