64c7712da7
- 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
754 lines
18 KiB
TypeScript
754 lines
18 KiB
TypeScript
/**
|
|
* Circuit Breaker Pattern Implementation
|
|
*
|
|
* 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'
|
|
|
|
/**
|
|
* Circuit breaker operational states
|
|
*/
|
|
export type CircuitState = 'closed' | 'open' | 'half-open'
|
|
|
|
/**
|
|
* Failure record with timestamp for sliding window tracking
|
|
*/
|
|
export interface FailureRecord {
|
|
timestamp: number
|
|
error: Error
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
isOpen: boolean
|
|
isClosed: boolean
|
|
isHalfOpen: boolean
|
|
}
|
|
|
|
/**
|
|
* Error thrown when circuit is OPEN and request is rejected
|
|
*/
|
|
export class CircuitOpenError extends Error {
|
|
constructor(
|
|
public readonly circuitName: string,
|
|
public readonly state: CircuitState
|
|
) {
|
|
super(`Circuit breaker "${circuitName}" is ${state}`)
|
|
this.name = 'CircuitOpenError'
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Error thrown when operation exceeds timeout
|
|
*/
|
|
export class CircuitTimeoutError extends Error {
|
|
constructor(
|
|
public readonly circuitName: string,
|
|
public readonly timeoutMs: number
|
|
) {
|
|
super(`Circuit breaker "${circuitName}" operation timed out after ${timeoutMs}ms`)
|
|
this.name = 'CircuitTimeoutError'
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 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 resetTimer?: ReturnType<typeof setTimeout>
|
|
private readonly options: Required<CircuitBreakerOptions>
|
|
|
|
constructor(options: CircuitBreakerOptions) {
|
|
super()
|
|
|
|
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,
|
|
volumeThreshold: options.volumeThreshold ?? 5,
|
|
shouldCountError: options.shouldCountError ?? (() => true),
|
|
collectMetrics: options.collectMetrics ?? true,
|
|
fallback: options.fallback ?? (() => {
|
|
throw new CircuitOpenError(this.options.name, this.state)
|
|
}),
|
|
onStateChange: options.onStateChange ?? (() => {}),
|
|
onFailure: options.onFailure ?? (() => {}),
|
|
onSuccess: options.onSuccess ?? (() => {}),
|
|
onOpen: options.onOpen ?? (() => {}),
|
|
onClose: options.onClose ?? (() => {}),
|
|
onHalfOpen: options.onHalfOpen ?? (() => {}),
|
|
}
|
|
|
|
this.lastStateChange = Date.now()
|
|
}
|
|
|
|
/**
|
|
* 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++
|
|
|
|
// Check if circuit allows execution
|
|
if (this.state === 'open') {
|
|
this.totalRejected++
|
|
|
|
if (this.options.collectMetrics) {
|
|
metrics.errors.inc({ category: 'circuit_breaker', code: 'rejected' })
|
|
}
|
|
|
|
return this.options.fallback() as T
|
|
}
|
|
|
|
// Execute with timeout protection
|
|
try {
|
|
const result = await this.executeWithTimeout(operation)
|
|
this.recordSuccess()
|
|
return result
|
|
} catch (error) {
|
|
this.recordFailure(error as Error)
|
|
throw error
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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: 'rejected' })
|
|
}
|
|
|
|
return this.options.fallback() as T
|
|
}
|
|
|
|
try {
|
|
const result = operation()
|
|
this.recordSuccess()
|
|
return result
|
|
} catch (error) {
|
|
this.recordFailure(error as Error)
|
|
throw error
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Execute operation with timeout wrapper
|
|
*/
|
|
private async executeWithTimeout<T>(operation: () => T | Promise<T>): Promise<T> {
|
|
return new Promise<T>((resolve, reject) => {
|
|
const timer = setTimeout(() => {
|
|
reject(new CircuitTimeoutError(this.options.name, this.options.timeout))
|
|
}, this.options.timeout)
|
|
|
|
Promise.resolve(operation())
|
|
.then((result) => {
|
|
clearTimeout(timer)
|
|
resolve(result)
|
|
})
|
|
.catch((error) => {
|
|
clearTimeout(timer)
|
|
reject(error)
|
|
})
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Record a successful operation
|
|
*/
|
|
private recordSuccess(): void {
|
|
this.totalSuccesses++
|
|
this.lastSuccessTime = Date.now()
|
|
this.consecutiveSuccesses++
|
|
this.consecutiveFailures = 0
|
|
|
|
if (this.options.collectMetrics) {
|
|
metrics.socketEvents.inc({ event: 'circuit_success' })
|
|
}
|
|
|
|
this.options.onSuccess()
|
|
this.emit('success')
|
|
|
|
// In HALF_OPEN state, check if we can close the circuit
|
|
if (this.state === 'half-open') {
|
|
if (this.consecutiveSuccesses >= this.options.successThreshold) {
|
|
this.transitionTo('closed')
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Record a failed operation
|
|
*/
|
|
private recordFailure(error: Error): void {
|
|
// Check if this error should count as a failure
|
|
if (!this.options.shouldCountError(error)) {
|
|
return
|
|
}
|
|
|
|
const now = Date.now()
|
|
this.totalFailures++
|
|
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' })
|
|
}
|
|
|
|
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') {
|
|
// 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')
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
|
|
if (oldState === newState) {
|
|
return
|
|
}
|
|
|
|
this.state = newState
|
|
this.lastStateChange = Date.now()
|
|
|
|
// Clear existing reset timer
|
|
if (this.resetTimer) {
|
|
clearTimeout(this.resetTimer)
|
|
this.resetTimer = undefined
|
|
}
|
|
|
|
// State-specific actions
|
|
switch (newState) {
|
|
case 'closed':
|
|
this.consecutiveFailures = 0
|
|
this.consecutiveSuccesses = 0
|
|
this.failureRecords = []
|
|
this.options.onClose()
|
|
this.emit('close')
|
|
break
|
|
|
|
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)
|
|
this.emit('state-change', { from: oldState, to: newState })
|
|
}
|
|
|
|
/**
|
|
* Manually trip the circuit to OPEN state
|
|
*/
|
|
trip(): void {
|
|
this.transitionTo('open')
|
|
}
|
|
|
|
/**
|
|
* Manually reset the circuit to CLOSED state
|
|
*/
|
|
reset(): void {
|
|
this.transitionTo('closed')
|
|
}
|
|
|
|
/**
|
|
* Get current circuit state
|
|
*/
|
|
getState(): CircuitState {
|
|
return this.state
|
|
}
|
|
|
|
/**
|
|
* Check if circuit is OPEN
|
|
*/
|
|
isOpen(): boolean {
|
|
return this.state === 'open'
|
|
}
|
|
|
|
/**
|
|
* Check if circuit is CLOSED
|
|
*/
|
|
isClosed(): boolean {
|
|
return this.state === 'closed'
|
|
}
|
|
|
|
/**
|
|
* Check if circuit is HALF_OPEN
|
|
*/
|
|
isHalfOpen(): boolean {
|
|
return this.state === 'half-open'
|
|
}
|
|
|
|
/**
|
|
* 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.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,
|
|
isOpen: this.isOpen(),
|
|
isClosed: this.isClosed(),
|
|
isHalfOpen: this.isHalfOpen(),
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get circuit breaker name
|
|
*/
|
|
getName(): string {
|
|
return this.options.name
|
|
}
|
|
|
|
/**
|
|
* Destroy circuit breaker and clean up resources
|
|
*/
|
|
destroy(): void {
|
|
if (this.resetTimer) {
|
|
clearTimeout(this.resetTimer)
|
|
}
|
|
this.failureRecords = []
|
|
this.removeAllListeners()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Factory function to create a circuit breaker
|
|
*/
|
|
export function createCircuitBreaker(options: CircuitBreakerOptions): CircuitBreaker {
|
|
return new CircuitBreaker(options)
|
|
}
|
|
|
|
/**
|
|
* Registry for managing multiple circuit breakers
|
|
*/
|
|
export class CircuitBreakerRegistry {
|
|
private breakers: Map<string, CircuitBreaker> = new Map()
|
|
|
|
/**
|
|
* Get or create a circuit breaker by name
|
|
*/
|
|
get(name: string, options?: Omit<CircuitBreakerOptions, 'name'>): CircuitBreaker {
|
|
if (!this.breakers.has(name)) {
|
|
const breaker = new CircuitBreaker({ ...options, name })
|
|
this.breakers.set(name, breaker)
|
|
}
|
|
return this.breakers.get(name)!
|
|
}
|
|
|
|
/**
|
|
* Check if a circuit breaker exists
|
|
*/
|
|
has(name: string): boolean {
|
|
return this.breakers.has(name)
|
|
}
|
|
|
|
/**
|
|
* Remove a circuit breaker
|
|
*/
|
|
remove(name: string): boolean {
|
|
const breaker = this.breakers.get(name)
|
|
if (breaker) {
|
|
breaker.destroy()
|
|
return this.breakers.delete(name)
|
|
}
|
|
return false
|
|
}
|
|
|
|
/**
|
|
* Get all circuit breakers
|
|
*/
|
|
getAll(): Map<string, CircuitBreaker> {
|
|
return new Map(this.breakers)
|
|
}
|
|
|
|
/**
|
|
* Get statistics for all circuit breakers
|
|
*/
|
|
getAllStats(): Record<string, CircuitBreakerStats> {
|
|
const stats: Record<string, CircuitBreakerStats> = {}
|
|
for (const [name, breaker] of this.breakers) {
|
|
stats[name] = breaker.getStats()
|
|
}
|
|
return stats
|
|
}
|
|
|
|
/**
|
|
* Reset all circuit breakers to CLOSED state
|
|
*/
|
|
resetAll(): void {
|
|
for (const breaker of this.breakers.values()) {
|
|
breaker.reset()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Destroy all circuit breakers
|
|
*/
|
|
destroyAll(): void {
|
|
for (const breaker of this.breakers.values()) {
|
|
breaker.destroy()
|
|
}
|
|
this.breakers.clear()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Global circuit breaker registry instance
|
|
*/
|
|
export const globalCircuitRegistry = new CircuitBreakerRegistry()
|
|
|
|
/**
|
|
* 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 (
|
|
_target: unknown,
|
|
propertyKey: string,
|
|
descriptor: TypedPropertyDescriptor<(...args: unknown[]) => unknown>
|
|
) {
|
|
const originalMethod = descriptor.value
|
|
if (!originalMethod) return descriptor
|
|
|
|
const name = options.name || propertyKey
|
|
const breaker = globalCircuitRegistry.get(name, options)
|
|
|
|
descriptor.value = async function (...args: unknown[]): Promise<unknown> {
|
|
return breaker.execute(() => originalMethod.apply(this, args))
|
|
}
|
|
|
|
return descriptor
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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,
|
|
options: CircuitBreakerOptions
|
|
): T {
|
|
const breaker = new CircuitBreaker(options)
|
|
|
|
return (async (...args: Parameters<T>): Promise<ReturnType<T>> => {
|
|
return breaker.execute(() => fn(...args)) as Promise<ReturnType<T>>
|
|
}) as unknown as T
|
|
}
|
|
|
|
/**
|
|
* Get health status of all circuit breakers
|
|
*/
|
|
export function getCircuitHealth(): {
|
|
healthy: boolean
|
|
openCircuits: string[]
|
|
stats: Record<string, CircuitBreakerStats>
|
|
} {
|
|
const stats = globalCircuitRegistry.getAllStats()
|
|
const openCircuits: string[] = []
|
|
|
|
for (const [name, stat] of Object.entries(stats)) {
|
|
if (stat.isOpen) {
|
|
openCircuits.push(name)
|
|
}
|
|
}
|
|
|
|
return {
|
|
healthy: openCircuits.length === 0,
|
|
openCircuits,
|
|
stats,
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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
|