refactor(utils): remove circuit-breaker integration from helpers

- Utils/index.ts: re-export bounded-retry instead of circuit-breaker
- Utils/unified-session.ts: drop the optional circuit-breaker wrapping.
  Telemetry is non-critical; a single send attempt with the underlying
  sendNode timeout is sufficient. Removed enableCircuitBreaker option,
  circuitBreaker field, and CircuitOpenError handling.
- Utils/retry-utils.ts: drop the optional circuitBreaker option from
  withRetry. Retry mechanics are now self-contained; callers that need
  bounded retries should prefer withBoundedRetry directly.
- Utils/health-status.ts: stop importing globalCircuitRegistry. Keep the
  CircuitBreakerHealth shape for k8s probe schema stability — always
  reports an empty list now.

Note: src/Utils/structured-logger.ts has its own *private* CircuitBreaker
class for log-throttling — independent of the socket-level circuit breaker
being removed. Untouched.

baileys-logger.ts logCircuitBreaker() is a generic logging helper that
takes state strings as input — no import dependency. Untouched (no-op
for callers that no longer have circuit breakers).
This commit is contained in:
Renato Alcara
2026-04-26 22:46:30 -03:00
parent 3ece5f7446
commit b531b46999
4 changed files with 11 additions and 87 deletions
+6 -39
View File
@@ -6,11 +6,12 @@
* @module Utils/health-status
*/
import { globalCircuitRegistry } from './circuit-breaker.js'
import { getVersionCacheStatus } from './version-cache.js'
/**
* Circuit breaker health information
* Circuit breaker health information (kept for k8s probe schema stability —
* always reports an empty list now that circuit breakers were removed in
* favour of bounded-retry).
*/
export interface CircuitBreakerHealth {
name: string
@@ -105,44 +106,10 @@ export function getHealthStatus(): HealthStatus {
})
}
// 2. Check circuit breakers
// 2. Circuit breakers were removed in favour of bounded-retry.
// Keep the field for backward-compat with k8s probe schema, always empty.
const circuitBreakers: CircuitBreakerHealth[] = []
let openCircuits = 0
for (const [name, breaker] of globalCircuitRegistry.getAll()) {
const stats = breaker.getStats()
const state = breaker.getState()
circuitBreakers.push({
name,
state,
failures: stats.totalFailures,
successes: stats.totalSuccesses,
totalCalls: stats.totalCalls
})
if (state === 'open') {
openCircuits++
}
}
if (openCircuits > 0) {
checks.push({
name: 'circuit_breakers',
status: openCircuits > 2 ? 'fail' : 'warn',
message: `${openCircuits} circuit breaker(s) are open`
})
if (openCircuits > 2) {
overallStatus = 'unhealthy'
} else if (overallStatus === 'healthy') {
overallStatus = 'degraded'
}
} else {
checks.push({
name: 'circuit_breakers',
status: 'pass'
})
}
checks.push({ name: 'circuit_breakers', status: 'pass' })
// 3. Check memory usage (warn if > 90%)
const memUsage = process.memoryUsage()
+1 -1
View File
@@ -33,8 +33,8 @@ export * from './trace-context'
export * from './prometheus-metrics'
// Resilience and performance
export * from './bounded-retry'
export * from './cache-utils'
export * from './circuit-breaker'
export * from './retry-utils'
// Telemetry and detection mitigation
-9
View File
@@ -14,7 +14,6 @@
*/
import { EventEmitter } from 'events'
import type { CircuitBreaker } from './circuit-breaker.js'
import { metrics } from './prometheus-metrics.js'
/**
@@ -59,8 +58,6 @@ export interface RetryOptions {
operationName?: string
/** Collect metrics */
collectMetrics?: boolean
/** Circuit breaker for integration */
circuitBreaker?: CircuitBreaker
/** Callback before each retry */
onRetry?: (error: Error, attempt: number, delay: number) => void | Promise<void>
/** Callback on success */
@@ -283,7 +280,6 @@ export async function retry<T>(
timeout: options.timeout,
operationName: options.operationName ?? 'operation',
collectMetrics: options.collectMetrics ?? true,
circuitBreaker: options.circuitBreaker,
onRetry: options.onRetry ?? (() => {}),
onSuccess: options.onSuccess ?? (() => {}),
onFailure: options.onFailure ?? (() => {}),
@@ -313,11 +309,6 @@ export async function retry<T>(
throw new RetryAbortedError(attempt)
}
// Check circuit breaker
if (config.circuitBreaker?.isOpen()) {
throw new Error(`Circuit breaker "${config.circuitBreaker.getName()}" is open`)
}
try {
// Execute operation
let result: T
+2 -36
View File
@@ -15,7 +15,6 @@
*/
import type { BinaryNode } from '../WABinary/types.js'
import { CircuitBreaker, CircuitOpenError, createConnectionCircuitBreaker } from './circuit-breaker.js'
import type { ILogger } from './logger.js'
import { metrics } from './prometheus-metrics.js'
@@ -58,8 +57,6 @@ export interface UnifiedSessionOptions {
enabled?: boolean
/** Logger instance for debugging */
logger?: ILogger
/** Enable circuit breaker protection for send operations */
enableCircuitBreaker?: boolean
/** Function to send binary nodes to WhatsApp */
sendNode?: (node: BinaryNode) => Promise<void>
}
@@ -121,7 +118,6 @@ export class UnifiedSessionManager {
}
private readonly options: Required<UnifiedSessionOptions>
private circuitBreaker: CircuitBreaker | null = null
/** Minimum interval between unified_session sends (1 minute) */
private static readonly MIN_SEND_INTERVAL_MS = TimeMs.Minute
@@ -130,7 +126,6 @@ export class UnifiedSessionManager {
this.options = {
enabled: options.enabled ?? true,
logger: options.logger ?? (console as unknown as ILogger),
enableCircuitBreaker: options.enableCircuitBreaker ?? true,
sendNode:
options.sendNode ??
(async () => {
@@ -138,25 +133,6 @@ export class UnifiedSessionManager {
})
}
// Initialize circuit breaker if enabled
if (this.options.enableCircuitBreaker) {
this.circuitBreaker = createConnectionCircuitBreaker({
name: 'unified-session',
failureThreshold: 3,
failureWindow: 60000,
resetTimeout: 30000,
successThreshold: 1,
timeout: 10000,
onStateChange: (from, to) => {
this.options.logger.debug?.({ from, to }, 'Unified session circuit breaker state changed')
},
onOpen: () => {
this.options.logger.warn?.('Unified session circuit breaker OPENED')
metrics.circuitBreakerTrips?.inc({ name: 'unified-session' })
}
})
}
this.state.isInitialized = true
this.options.logger.debug?.('UnifiedSessionManager initialized')
}
@@ -292,20 +268,12 @@ export class UnifiedSessionManager {
}
try {
// Execute with circuit breaker if available
if (this.circuitBreaker) {
await this.circuitBreaker.execute(sendOperation)
} else {
// Telemetry is non-critical: a single send attempt with the
// underlying sendNode timeout is enough. No retry/circuit needed.
await sendOperation()
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
if (error instanceof CircuitOpenError) {
this.options.logger.warn?.({ trigger, circuitState: error.state }, 'Unified session blocked by circuit breaker')
} else {
this.options.logger.warn?.({ trigger, error: errorMessage }, 'Failed to send unified session telemetry')
}
// Record failure metric
metrics.errors?.inc({ category: 'unified_session', code: 'send_failed' })
@@ -331,7 +299,6 @@ export class UnifiedSessionManager {
sendCount: 0,
isInitialized: true
}
this.circuitBreaker?.reset()
this.options.logger.debug?.('UnifiedSessionManager reset')
}
@@ -339,7 +306,6 @@ export class UnifiedSessionManager {
* Destroy the manager and clean up resources.
*/
destroy(): void {
this.circuitBreaker?.destroy()
this.state.isInitialized = false
this.options.logger.debug?.('UnifiedSessionManager destroyed')
}