Files
clube67_newwhats.local/clube67/newwhats.local/backend/src/infra/cache/dragonfly.ts
T
VPS 4 Deploy Agent 2f8c04a0a7
continuous-integration/webhook Falha no deploy de clube67_newwhats.local (VPS 4)
chore(ops): restore all source files in newwhats.clube67.com
2026-05-18 03:28:29 +02:00

103 lines
3.3 KiB
TypeScript

import Redis from 'ioredis'
import { env } from '../../config/env'
import { logger } from '../../config/logger'
class DragonflyClient {
private client: Redis
private readonly ttl: number
constructor() {
this.ttl = parseInt(env.DRAGONFLY_TTL_SECONDS, 10)
this.client = new Redis(env.DRAGONFLY_URL, {
// null = não limita tentativas por comando; a reconexão é gerenciada pelo retryStrategy
maxRetriesPerRequest: null,
enableReadyCheck: false,
lazyConnect: true,
// Falha imediata quando desconectado — evita fila infinita em memória
enableOfflineQueue: false,
// Reconexão exponencial: 200ms → 400ms → ... → 30s
retryStrategy(times: number) {
const delay = Math.min(200 * 2 ** times, 30_000)
return delay
},
})
this.client.on('connect', () => logger.info('DragonflyDB conectado'))
this.client.on('ready', () => logger.info('DragonflyDB pronto'))
this.client.on('error', (err) => logger.error({ msg: 'DragonflyDB erro', code: (err as any).code }))
this.client.on('close', () => logger.warn('DragonflyDB conexão fechada — reconectando...'))
}
/** true se o cliente está conectado e pronto */
isAlive(): boolean {
return this.client.status === 'ready'
}
async connect(): Promise<void> {
// Inicia conexão em background — não bloqueia o startup se Redis estiver fora
// retryStrategy cuida da reconexão automática
this.client.connect().catch(() => { /* reconexão em background via retryStrategy */ })
}
async get(key: string): Promise<string | null> {
try { return await this.client.get(key) } catch { return null }
}
async getJson<T>(key: string): Promise<T | null> {
try {
const raw = await this.client.get(key)
if (!raw) return null
return JSON.parse(raw) as T
} catch { return null }
}
async set(key: string, value: string, ttlSeconds?: number): Promise<void> {
try {
const ttl = ttlSeconds ?? this.ttl
if (ttl > 0) {
await this.client.set(key, value, 'EX', ttl)
} else {
await this.client.set(key, value)
}
} catch { /* Redis indisponível — ignora silenciosamente */ }
}
async setJson(key: string, value: unknown, ttlSeconds?: number): Promise<void> {
try {
const ttl = ttlSeconds ?? this.ttl
if (ttl > 0) {
await this.client.set(key, JSON.stringify(value), 'EX', ttl)
} else {
await this.client.set(key, JSON.stringify(value))
}
} catch { /* Redis indisponível — ignora silenciosamente */ }
}
async psetex(key: string, ttlMs: number, value: string): Promise<void> {
try { await this.client.psetex(key, ttlMs, value) } catch { }
}
async del(key: string): Promise<void> {
try { await this.client.del(key) } catch { }
}
async exists(key: string): Promise<boolean> {
try {
const count = await this.client.exists(key)
return count > 0
} catch { return false }
}
/** Publica em um canal pub/sub (usado para broadcasting de QR/status) */
async publish(channel: string, message: string): Promise<void> {
try { await this.client.publish(channel, message) } catch { }
}
raw(): Redis {
return this.client
}
}
export const dragonfly = new DragonflyClient()