import { Redis } from "ioredis"; import { getEnv } from "./env.js"; let cacheClient: Redis | null = null; let queueClient: Redis | null = null; let cacheDown = false; /** Redis client for the API cache: fail fast (no offline queue) so a Redis outage never blocks a request. */ export function getCacheRedis(): Redis | null { if (cacheDown) return null; if (!cacheClient) { const c = new Redis(getEnv().redisUrl, { lazyConnect: true, enableOfflineQueue: false, maxRetriesPerRequest: 1, connectTimeout: 2_000, retryStrategy: (n: number) => (n > 20 ? null : Math.min(30_000, 500 * n)) }); c.on("error", () => { /* handled by callers: null/undefined results */ }); c.on("end", () => { cacheDown = true; }); c.connect().catch(() => { /* retried by ioredis */ }); cacheClient = c; } return cacheClient; } /** Redis connection for BullMQ (requires maxRetriesPerRequest: null) and worker status keys. */ export function getQueueRedis(): Redis { if (!queueClient) { const c = new Redis(getEnv().redisUrl, { lazyConnect: true, maxRetriesPerRequest: null, enableOfflineQueue: true, connectTimeout: 3_000 }); c.on("error", () => { /* surfaced through BullMQ / ops calls */ }); queueClient = c; } return queueClient; } export async function redisPing(): Promise { try { const r = getQueueRedis(); if (r.status === "wait") await r.connect().catch(() => {}); const pong = await Promise.race([r.ping(), new Promise((_, rej) => setTimeout(() => rej(new Error("timeout")), 1500))]); return pong === "PONG"; } catch { return false; } } export async function closeRedis(): Promise { await Promise.allSettled([cacheClient?.quit(), queueClient?.quit()]); cacheClient = null; queueClient = null; }