refactor(utils): translate all comments to English and enhance circuit breaker

- Translate all Portuguese comments to English in utility modules
- Enhance circuit-breaker.ts with sliding window failure tracking
- Add factory functions for WhatsApp-specific circuit breakers
  (createPreKeyCircuitBreaker, createConnectionCircuitBreaker, createMessageCircuitBreaker)
- Improve volume threshold support for circuit breaker decisions
- Standardize documentation across all utility files

Files updated:
- structured-logger.ts: English comments
- baileys-logger.ts: English comments
- trace-context.ts: English comments
- prometheus-metrics.ts: English comments
- cache-utils.ts: English comments
- circuit-breaker.ts: Enhanced with sliding window + English comments
- retry-utils.ts: English comments
- baileys-event-stream.ts: English comments
- index.ts: English comments
This commit is contained in:
Claude
2026-01-20 01:48:23 +00:00
parent 882a1f57cf
commit 64c7712da7
9 changed files with 865 additions and 614 deletions
+332 -111
View File
@@ -1,66 +1,85 @@
/**
* @fileoverview Proteção contra falhas em cascata - Circuit Breaker
* @module Utils/circuit-breaker
* Circuit Breaker Pattern Implementation
*
* Fornece:
* - Estados: Closed, Open, Half-Open
* - Thresholds configuráveis
* - Recuperação automática
* - Callbacks de estado
* - Integração com métricas
* - Fallback handlers
* Provides protection against cascading failures by monitoring operation outcomes
* and temporarily blocking requests when failure thresholds are exceeded.
*
* States:
* - CLOSED: Normal operation, requests pass through
* - OPEN: Failures exceeded threshold, requests are blocked
* - HALF_OPEN: Testing recovery, limited requests allowed
*
* @module Utils/circuit-breaker
*/
import { EventEmitter } from 'events'
import { metrics } from './prometheus-metrics.js'
/**
* Estados do Circuit Breaker
* Circuit breaker operational states
*/
export type CircuitState = 'closed' | 'open' | 'half-open'
/**
* Opções de configuração do Circuit Breaker
* Failure record with timestamp for sliding window tracking
*/
export interface CircuitBreakerOptions {
/** Nome do circuit breaker (para métricas) */
name: string
/** Número de falhas para abrir o circuito */
failureThreshold?: number
/** Número de sucessos para fechar o circuito (em half-open) */
successThreshold?: number
/** Tempo em ms para tentar half-open após open */
resetTimeout?: number
/** Timeout para operações em ms */
timeout?: number
/** Função para determinar se erro deve contar como falha */
isFailure?: (error: Error) => boolean
/** Coletar métricas */
collectMetrics?: boolean
/** Função de fallback quando circuito está aberto */
fallback?: <T>() => T | Promise<T>
/** Callback quando estado muda */
onStateChange?: (from: CircuitState, to: CircuitState) => void
/** Callback em falha */
onFailure?: (error: Error) => void
/** Callback em sucesso */
onSuccess?: () => void
/** Callback quando circuito abre */
onOpen?: () => void
/** Callback quando circuito fecha */
onClose?: () => void
export interface FailureRecord {
timestamp: number
error: Error
}
/**
* Estatísticas do Circuit Breaker
* Circuit breaker configuration options
*/
export interface CircuitBreakerOptions {
/** Unique identifier for this circuit breaker (used in metrics/logging) */
name: string
/** Number of failures within the window to trigger OPEN state (default: 5) */
failureThreshold?: number
/** Time window in ms for counting failures (default: 60000) */
failureWindow?: number
/** Number of successes required in HALF_OPEN to return to CLOSED (default: 2) */
successThreshold?: number
/** Time in ms to wait before transitioning from OPEN to HALF_OPEN (default: 30000) */
resetTimeout?: number
/** Timeout for individual operations in ms (default: 10000) */
timeout?: number
/** Minimum number of requests before circuit can trip (default: 5) */
volumeThreshold?: number
/** Predicate to determine if an error should count as a failure */
shouldCountError?: (error: Error) => boolean
/** Whether to collect Prometheus metrics (default: true) */
collectMetrics?: boolean
/** Fallback function when circuit is OPEN */
fallback?: <T>() => T | Promise<T>
/** Callback when state changes */
onStateChange?: (from: CircuitState, to: CircuitState) => void
/** Callback on failure */
onFailure?: (error: Error) => void
/** Callback on success */
onSuccess?: () => void
/** Callback when circuit opens */
onOpen?: () => void
/** Callback when circuit closes */
onClose?: () => void
/** Callback when circuit enters half-open */
onHalfOpen?: () => void
}
/**
* Circuit breaker statistics
*/
export interface CircuitBreakerStats {
state: CircuitState
failures: number
successes: number
consecutiveFailures: number
consecutiveSuccesses: number
totalCalls: number
totalFailures: number
totalSuccesses: number
totalRejected: number
failureRate: number
lastFailureTime?: number
lastSuccessTime?: number
lastStateChange?: number
@@ -70,7 +89,7 @@ export interface CircuitBreakerStats {
}
/**
* Erro lançado quando circuito está aberto
* Error thrown when circuit is OPEN and request is rejected
*/
export class CircuitOpenError extends Error {
constructor(
@@ -83,7 +102,7 @@ export class CircuitOpenError extends Error {
}
/**
* Erro de timeout
* Error thrown when operation exceeds timeout
*/
export class CircuitTimeoutError extends Error {
constructor(
@@ -96,20 +115,39 @@ export class CircuitTimeoutError extends Error {
}
/**
* Classe principal do Circuit Breaker
* Circuit Breaker implementation with sliding window failure tracking
*
* @example
* ```typescript
* const breaker = new CircuitBreaker({
* name: 'whatsapp-api',
* failureThreshold: 5,
* resetTimeout: 30000
* })
*
* try {
* const result = await breaker.execute(() => sendMessage(msg))
* } catch (error) {
* if (error instanceof CircuitOpenError) {
* // Circuit is open, use fallback
* }
* }
* ```
*/
export class CircuitBreaker extends EventEmitter {
private state: CircuitState = 'closed'
private failures = 0
private successes = 0
private failureRecords: FailureRecord[] = []
private consecutiveFailures = 0
private consecutiveSuccesses = 0
private totalCalls = 0
private totalFailures = 0
private totalSuccesses = 0
private totalRejected = 0
private lastFailureTime?: number
private lastSuccessTime?: number
private lastStateChange?: number
private lastStateChange: number
private resetTimer?: ReturnType<typeof setTimeout>
private options: Required<CircuitBreakerOptions>
private readonly options: Required<CircuitBreakerOptions>
constructor(options: CircuitBreakerOptions) {
super()
@@ -117,10 +155,12 @@ export class CircuitBreaker extends EventEmitter {
this.options = {
name: options.name,
failureThreshold: options.failureThreshold ?? 5,
failureWindow: options.failureWindow ?? 60000,
successThreshold: options.successThreshold ?? 2,
resetTimeout: options.resetTimeout ?? 30000,
timeout: options.timeout ?? 10000,
isFailure: options.isFailure ?? (() => true),
volumeThreshold: options.volumeThreshold ?? 5,
shouldCountError: options.shouldCountError ?? (() => true),
collectMetrics: options.collectMetrics ?? true,
fallback: options.fallback ?? (() => {
throw new CircuitOpenError(this.options.name, this.state)
@@ -130,27 +170,46 @@ export class CircuitBreaker extends EventEmitter {
onSuccess: options.onSuccess ?? (() => {}),
onOpen: options.onOpen ?? (() => {}),
onClose: options.onClose ?? (() => {}),
onHalfOpen: options.onHalfOpen ?? (() => {}),
}
this.lastStateChange = Date.now()
}
/**
* Executa operação protegida pelo circuit breaker
* Check if the circuit allows execution
*/
canExecute(): boolean {
if (this.state === 'closed') {
return true
}
if (this.state === 'open') {
return false
}
// HALF_OPEN: allow limited requests for testing
return true
}
/**
* Execute an async operation with circuit breaker protection
*/
async execute<T>(operation: () => T | Promise<T>): Promise<T> {
this.totalCalls++
// Verificar estado
// Check if circuit allows execution
if (this.state === 'open') {
this.totalRejected++
if (this.options.collectMetrics) {
metrics.errors.inc({ category: 'circuit_breaker', code: 'open' })
metrics.errors.inc({ category: 'circuit_breaker', code: 'rejected' })
}
return this.options.fallback() as T
}
// Executar com timeout
// Execute with timeout protection
try {
const result = await this.executeWithTimeout(operation)
this.recordSuccess()
@@ -162,14 +221,16 @@ export class CircuitBreaker extends EventEmitter {
}
/**
* Executa operação síncrona protegida
* Execute a synchronous operation with circuit breaker protection
*/
executeSync<T>(operation: () => T): T {
this.totalCalls++
if (this.state === 'open') {
this.totalRejected++
if (this.options.collectMetrics) {
metrics.errors.inc({ category: 'circuit_breaker', code: 'open' })
metrics.errors.inc({ category: 'circuit_breaker', code: 'rejected' })
}
return this.options.fallback() as T
@@ -186,7 +247,7 @@ export class CircuitBreaker extends EventEmitter {
}
/**
* Executa com timeout
* Execute operation with timeout wrapper
*/
private async executeWithTimeout<T>(operation: () => T | Promise<T>): Promise<T> {
return new Promise<T>((resolve, reject) => {
@@ -207,12 +268,13 @@ export class CircuitBreaker extends EventEmitter {
}
/**
* Registra sucesso
* Record a successful operation
*/
private recordSuccess(): void {
this.totalSuccesses++
this.lastSuccessTime = Date.now()
this.failures = 0
this.consecutiveSuccesses++
this.consecutiveFailures = 0
if (this.options.collectMetrics) {
metrics.socketEvents.inc({ event: 'circuit_success' })
@@ -221,28 +283,34 @@ export class CircuitBreaker extends EventEmitter {
this.options.onSuccess()
this.emit('success')
// In HALF_OPEN state, check if we can close the circuit
if (this.state === 'half-open') {
this.successes++
if (this.successes >= this.options.successThreshold) {
if (this.consecutiveSuccesses >= this.options.successThreshold) {
this.transitionTo('closed')
}
}
}
/**
* Registra falha
* Record a failed operation
*/
private recordFailure(error: Error): void {
// Verificar se erro deve contar como falha
if (!this.options.isFailure(error)) {
// Check if this error should count as a failure
if (!this.options.shouldCountError(error)) {
return
}
const now = Date.now()
this.totalFailures++
this.lastFailureTime = Date.now()
this.failures++
this.successes = 0
this.lastFailureTime = now
this.consecutiveFailures++
this.consecutiveSuccesses = 0
// Add to failure records for sliding window
this.failureRecords.push({ timestamp: now, error })
// Clean old failures outside the window
this.cleanOldFailures()
if (this.options.collectMetrics) {
metrics.errors.inc({ category: 'circuit_breaker', code: 'failure' })
@@ -251,15 +319,35 @@ export class CircuitBreaker extends EventEmitter {
this.options.onFailure(error)
this.emit('failure', error)
// State transition logic
if (this.state === 'half-open') {
// Any failure in HALF_OPEN immediately reopens the circuit
this.transitionTo('open')
} else if (this.state === 'closed' && this.failures >= this.options.failureThreshold) {
this.transitionTo('open')
} else if (this.state === 'closed') {
// Check if we should trip the circuit
const recentFailures = this.failureRecords.length
if (
this.totalCalls >= this.options.volumeThreshold &&
recentFailures >= this.options.failureThreshold
) {
this.transitionTo('open')
}
}
}
/**
* Transiciona para novo estado
* Remove failure records outside the sliding window
*/
private cleanOldFailures(): void {
const cutoff = Date.now() - this.options.failureWindow
this.failureRecords = this.failureRecords.filter(
(record) => record.timestamp > cutoff
)
}
/**
* Transition to a new state
*/
private transitionTo(newState: CircuitState): void {
const oldState = this.state
@@ -271,31 +359,39 @@ export class CircuitBreaker extends EventEmitter {
this.state = newState
this.lastStateChange = Date.now()
// Limpar timer existente
// Clear existing reset timer
if (this.resetTimer) {
clearTimeout(this.resetTimer)
this.resetTimer = undefined
}
// Resetar contadores baseado no novo estado
if (newState === 'closed') {
this.failures = 0
this.successes = 0
this.options.onClose()
this.emit('close')
} else if (newState === 'open') {
this.successes = 0
this.options.onOpen()
this.emit('open')
// State-specific actions
switch (newState) {
case 'closed':
this.consecutiveFailures = 0
this.consecutiveSuccesses = 0
this.failureRecords = []
this.options.onClose()
this.emit('close')
break
// Agendar tentativa de half-open
this.resetTimer = setTimeout(() => {
this.transitionTo('half-open')
}, this.options.resetTimeout)
} else if (newState === 'half-open') {
this.successes = 0
this.failures = 0
this.emit('half-open')
case 'open':
this.consecutiveSuccesses = 0
this.options.onOpen()
this.emit('open')
// Schedule transition to HALF_OPEN
this.resetTimer = setTimeout(() => {
this.transitionTo('half-open')
}, this.options.resetTimeout)
break
case 'half-open':
this.consecutiveSuccesses = 0
this.consecutiveFailures = 0
this.options.onHalfOpen()
this.emit('half-open')
break
}
this.options.onStateChange(oldState, newState)
@@ -303,58 +399,68 @@ export class CircuitBreaker extends EventEmitter {
}
/**
* Força abertura do circuito
* Manually trip the circuit to OPEN state
*/
trip(): void {
this.transitionTo('open')
}
/**
* Força fechamento do circuito
* Manually reset the circuit to CLOSED state
*/
reset(): void {
this.transitionTo('closed')
}
/**
* Retorna estado atual
* Get current circuit state
*/
getState(): CircuitState {
return this.state
}
/**
* Verifica se circuito está aberto
* Check if circuit is OPEN
*/
isOpen(): boolean {
return this.state === 'open'
}
/**
* Verifica se circuito está fechado
* Check if circuit is CLOSED
*/
isClosed(): boolean {
return this.state === 'closed'
}
/**
* Verifica se circuito está half-open
* Check if circuit is HALF_OPEN
*/
isHalfOpen(): boolean {
return this.state === 'half-open'
}
/**
* Retorna estatísticas
* Get circuit breaker statistics
*/
getStats(): CircuitBreakerStats {
this.cleanOldFailures()
const failureRate = this.totalCalls > 0
? (this.totalFailures / this.totalCalls) * 100
: 0
return {
state: this.state,
failures: this.failures,
successes: this.successes,
failures: this.failureRecords.length,
successes: this.consecutiveSuccesses,
consecutiveFailures: this.consecutiveFailures,
consecutiveSuccesses: this.consecutiveSuccesses,
totalCalls: this.totalCalls,
totalFailures: this.totalFailures,
totalSuccesses: this.totalSuccesses,
totalRejected: this.totalRejected,
failureRate,
lastFailureTime: this.lastFailureTime,
lastSuccessTime: this.lastSuccessTime,
lastStateChange: this.lastStateChange,
@@ -365,38 +471,39 @@ export class CircuitBreaker extends EventEmitter {
}
/**
* Retorna nome do circuit breaker
* Get circuit breaker name
*/
getName(): string {
return this.options.name
}
/**
* Destroy e limpa recursos
* Destroy circuit breaker and clean up resources
*/
destroy(): void {
if (this.resetTimer) {
clearTimeout(this.resetTimer)
}
this.failureRecords = []
this.removeAllListeners()
}
}
/**
* Factory para criar circuit breaker
* Factory function to create a circuit breaker
*/
export function createCircuitBreaker(options: CircuitBreakerOptions): CircuitBreaker {
return new CircuitBreaker(options)
}
/**
* Registry de circuit breakers
* Registry for managing multiple circuit breakers
*/
export class CircuitBreakerRegistry {
private breakers: Map<string, CircuitBreaker> = new Map()
/**
* Obtém ou cria circuit breaker
* Get or create a circuit breaker by name
*/
get(name: string, options?: Omit<CircuitBreakerOptions, 'name'>): CircuitBreaker {
if (!this.breakers.has(name)) {
@@ -407,14 +514,14 @@ export class CircuitBreakerRegistry {
}
/**
* Verifica se circuit breaker existe
* Check if a circuit breaker exists
*/
has(name: string): boolean {
return this.breakers.has(name)
}
/**
* Remove circuit breaker
* Remove a circuit breaker
*/
remove(name: string): boolean {
const breaker = this.breakers.get(name)
@@ -426,14 +533,14 @@ export class CircuitBreakerRegistry {
}
/**
* Retorna todos os circuit breakers
* Get all circuit breakers
*/
getAll(): Map<string, CircuitBreaker> {
return new Map(this.breakers)
}
/**
* Retorna estatísticas de todos os circuit breakers
* Get statistics for all circuit breakers
*/
getAllStats(): Record<string, CircuitBreakerStats> {
const stats: Record<string, CircuitBreakerStats> = {}
@@ -444,7 +551,7 @@ export class CircuitBreakerRegistry {
}
/**
* Reseta todos os circuit breakers
* Reset all circuit breakers to CLOSED state
*/
resetAll(): void {
for (const breaker of this.breakers.values()) {
@@ -453,7 +560,7 @@ export class CircuitBreakerRegistry {
}
/**
* Destroy todos os circuit breakers
* Destroy all circuit breakers
*/
destroyAll(): void {
for (const breaker of this.breakers.values()) {
@@ -464,12 +571,22 @@ export class CircuitBreakerRegistry {
}
/**
* Registry global
* Global circuit breaker registry instance
*/
export const globalCircuitRegistry = new CircuitBreakerRegistry()
/**
* Decorator para proteger método com circuit breaker
* Decorator to protect a method with circuit breaker
*
* @example
* ```typescript
* class MyService {
* @circuitBreaker({ failureThreshold: 3 })
* async fetchData() {
* return await api.getData()
* }
* }
* ```
*/
export function circuitBreaker(options: Omit<CircuitBreakerOptions, 'name'> & { name?: string } = {}) {
return function (
@@ -492,7 +609,15 @@ export function circuitBreaker(options: Omit<CircuitBreakerOptions, 'name'> & {
}
/**
* Wrapper para função com circuit breaker
* Wrap a function with circuit breaker protection
*
* @example
* ```typescript
* const protectedFetch = withCircuitBreaker(
* fetchData,
* { name: 'api-fetch', failureThreshold: 5 }
* )
* ```
*/
export function withCircuitBreaker<T extends (...args: unknown[]) => unknown>(
fn: T,
@@ -506,7 +631,7 @@ export function withCircuitBreaker<T extends (...args: unknown[]) => unknown>(
}
/**
* Verifica saúde de todos os circuit breakers
* Get health status of all circuit breakers
*/
export function getCircuitHealth(): {
healthy: boolean
@@ -529,4 +654,100 @@ export function getCircuitHealth(): {
}
}
/**
* Create a pre-configured circuit breaker for WhatsApp PreKey operations
*
* This circuit breaker is optimized for handling encryption/session errors
* that commonly occur with WhatsApp's Signal protocol implementation.
*
* @example
* ```typescript
* const preKeyBreaker = createPreKeyCircuitBreaker()
*
* async function sendEncryptedMessage(msg) {
* return preKeyBreaker.execute(async () => {
* return await encryptAndSend(msg)
* })
* }
* ```
*/
export function createPreKeyCircuitBreaker(
customOptions?: Partial<CircuitBreakerOptions>
): CircuitBreaker {
const preKeyErrorPatterns = [
'prekey',
'pre-key',
'session',
'signal',
'encrypt',
'decrypt',
'cipher',
'key',
]
return new CircuitBreaker({
name: 'prekey-operations',
failureThreshold: 5,
failureWindow: 60000,
resetTimeout: 30000,
successThreshold: 2,
shouldCountError: (error: Error) => {
const message = error.message.toLowerCase()
return preKeyErrorPatterns.some((pattern) => message.includes(pattern))
},
...customOptions,
})
}
/**
* Create a circuit breaker for WebSocket connection operations
*/
export function createConnectionCircuitBreaker(
customOptions?: Partial<CircuitBreakerOptions>
): CircuitBreaker {
const connectionErrorPatterns = [
'econnrefused',
'econnreset',
'etimedout',
'enotfound',
'socket',
'websocket',
'connection',
'network',
]
return new CircuitBreaker({
name: 'connection-operations',
failureThreshold: 3,
failureWindow: 30000,
resetTimeout: 60000,
successThreshold: 1,
shouldCountError: (error: Error) => {
const message = error.message.toLowerCase()
const code = (error as NodeJS.ErrnoException).code?.toLowerCase() || ''
return connectionErrorPatterns.some(
(pattern) => message.includes(pattern) || code.includes(pattern)
)
},
...customOptions,
})
}
/**
* Create a circuit breaker for message sending operations
*/
export function createMessageCircuitBreaker(
customOptions?: Partial<CircuitBreakerOptions>
): CircuitBreaker {
return new CircuitBreaker({
name: 'message-operations',
failureThreshold: 5,
failureWindow: 60000,
resetTimeout: 15000,
successThreshold: 2,
timeout: 30000,
...customOptions,
})
}
export default CircuitBreaker