103 lines
3.3 KiB
TypeScript
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()
|