Skip to main content

Overview

The configuration system provides fine-grained control over retry behavior, timeouts, backoff strategies, circuit breakers, and observability hooks.

ResilienceConfig

Main configuration object for withResilience.
type ResilienceConfig = {
  name?: string;
  timeoutMs?: number;
  retries?: number;
  backoff?: BackoffStrategy;
  retryOn?: (err: unknown) => boolean;
  circuitBreaker?: CircuitBreakerConfig;
  hooks?: ResilienceHooks;
  useAbortSignal?: boolean;
}
Defined in src/global.d.ts:34-43.

Configuration Options

name
string
Optional name for the wrapped function. Used in:
  • Hook callbacks for identification
  • Error messages (e.g., “CircuitOpenError: function-name”)
  • Metrics tracking
Default: Falls back to fn.name, then “anonymous”
withResilience(myFunction, { name: 'api-call' })
timeoutMs
number
Maximum execution time in milliseconds before timing out.When exceeded:
  • Execution is cancelled (if useAbortSignal is true)
  • Throws an Error('TimeoutError')
  • Counts as a failure for circuit breaker and retries
Default: No timeout
withResilience(myFunction, {
  timeoutMs: 5000 // 5 second timeout
})
retries
number
Number of retry attempts after the initial failure.
  • Total attempts = retries + 1 (initial + retries)
  • Each retry can have a backoff delay
  • Retries respect the retryOn predicate
Default: 0 (no retries)
withResilience(myFunction, {
  retries: 3 // Try up to 4 times total
})
backoff
BackoffStrategy
Delay strategy between retry attempts. See BackoffStrategy for details.Default: No delay between retries
withResilience(myFunction, {
  retries: 3,
  backoff: {
    type: 'exponential',
    baseDelayMs: 100,
    maxDelayMs: 5000,
    jitter: true
  }
})
retryOn
(err: unknown) => boolean
Predicate function to determine if an error should trigger a retry.
  • Called with the error object after each failure
  • Return true to retry, false to fail immediately
  • Only called if retries are remaining
Default: () => true (retry all errors)
withResilience(myFunction, {
  retries: 3,
  retryOn: (err) => {
    // Only retry network errors, not client errors
    if (err instanceof TypeError) return true;
    if (err.message.includes('5')) return true;
    return false;
  }
})
circuitBreaker
CircuitBreakerConfig
Circuit breaker configuration to prevent cascading failures. See CircuitBreakerConfig for details.Default: No circuit breaker
withResilience(myFunction, {
  circuitBreaker: {
    failureThreshold: 5,
    resetTimeoutMs: 60000 // 1 minute
  }
})
hooks
ResilienceHooks
Lifecycle hooks for observability, metrics, and logging. See Hooks for full reference.Default: No hooks
withResilience(myFunction, {
  hooks: {
    onAttempt: ({ name, attempt }) => {
      console.log(`[${name}] Attempt ${attempt}`);
    },
    onSuccess: ({ name, timeMs }) => {
      metrics.recordSuccess(name, timeMs);
    }
  }
})
useAbortSignal
boolean
Enable automatic abort signal management for cancellation support.When enabled:
  • Creates an AbortController for each execution
  • Makes the signal available to sleep and resilientFetch
  • Aborts the signal when timeout is reached
  • Enables automatic cancellation of async operations
Default: false
withResilience(
  async () => {
    await sleep(1000); // Auto-cancelled on timeout
    await resilientFetch('/api/data'); // Auto-cancelled
  },
  {
    timeoutMs: 5000,
    useAbortSignal: true // Enable cancellation
  }
)

BackoffStrategy

Defines the delay between retry attempts.
type BackoffStrategy =
  | { type: 'fixed'; delayMs: number }
  | { type: 'exponential'; baseDelayMs: number; maxDelayMs: number; jitter?: boolean }
Defined in src/global.d.ts:15-17.

Fixed Backoff

Constant delay between retries.
type
'fixed'
required
Specifies fixed backoff strategy.
delayMs
number
required
Delay in milliseconds between each retry attempt.
Example:
backoff: {
  type: 'fixed',
  delayMs: 1000 // Wait 1 second between each retry
}

// Retry timeline:
// Attempt 1: immediate
// Attempt 2: after 1000ms
// Attempt 3: after 1000ms
// Attempt 4: after 1000ms

Exponential Backoff

Delay increases exponentially with each retry.
type
'exponential'
required
Specifies exponential backoff strategy.
baseDelayMs
number
required
Base delay in milliseconds. The actual delay is calculated as:
delay = baseDelayMs * (2 ^ (attempt - 1))
maxDelayMs
number
required
Maximum delay in milliseconds. Caps the exponential growth.
jitter
boolean
default:"false"
Add random jitter to prevent thundering herd.When enabled, the delay is a random value between 0 and the calculated delay.
Example:
backoff: {
  type: 'exponential',
  baseDelayMs: 100,
  maxDelayMs: 5000,
  jitter: true
}

// Retry timeline (without jitter):
// Attempt 1: immediate
// Attempt 2: after 100ms (100 * 2^0)
// Attempt 3: after 200ms (100 * 2^1)
// Attempt 4: after 400ms (100 * 2^2)
// Attempt 5: after 800ms (100 * 2^3)
// Attempt 6: after 1600ms (100 * 2^4)
// Attempt 7: after 3200ms (100 * 2^5)
// Attempt 8: after 5000ms (capped at maxDelayMs)

// With jitter: each delay is randomized between 0 and the calculated value
Implementation (from src/index.ts:72-81):
function computeBackoffMs(strategy: Resilience.BackoffStrategy | undefined, attempt: number): number {
  if (!strategy) return 0;
  if (strategy.type === 'fixed') return strategy.delayMs;

  const raw = strategy.baseDelayMs * Math.pow(2, Math.max(0, attempt - 1));
  const capped = Math.min(raw, strategy.maxDelayMs);
  if (!strategy.jitter) return capped;

  return Math.floor(Math.random() * capped);
}

CircuitBreakerConfig

Configuration for circuit breaker pattern to prevent cascading failures.
type CircuitBreakerConfig = {
  failureThreshold: number;
  resetTimeoutMs: number;
}
Defined in src/global.d.ts:19-22.
failureThreshold
number
required
Number of consecutive failures before opening the circuit.Once the threshold is reached:
  • Circuit state changes to “OPEN”
  • All attempts are rejected immediately with CircuitOpenError
  • No actual function execution occurs
resetTimeoutMs
number
required
Time in milliseconds to wait before attempting to close the circuit.After this timeout:
  • Circuit state changes to “HALF_OPEN”
  • Next attempt is allowed (probe)
  • If probe succeeds: circuit closes
  • If probe fails: circuit reopens

Circuit States

type CircuitState = 'CLOSED' | 'OPEN' | 'HALF_OPEN'
Defined in src/global.d.ts:3.
CLOSED
'CLOSED'
Normal operation. All requests are allowed. Failures are counted.
OPEN
'OPEN'
Circuit is tripped. All requests are rejected immediately with CircuitOpenError. No function execution occurs.
HALF_OPEN
'HALF_OPEN'
Testing if service recovered. One request is allowed as a probe. Success closes the circuit, failure reopens it.
Example:
withResilience(callExternalAPI, {
  circuitBreaker: {
    failureThreshold: 5,
    resetTimeoutMs: 60000 // 1 minute
  }
})

// Timeline:
// Failures 1-4: Normal execution, counted
// Failure 5: Circuit opens, onCircuitOpen hook called
// Next 60 seconds: All attempts rejected with CircuitOpenError
// After 60 seconds: Circuit enters HALF_OPEN, onCircuitHalfOpen hook called
// Next attempt: If succeeds, circuit closes (onCircuitClosed hook)
//               If fails, circuit reopens for another 60 seconds

Complete Configuration Example

import { withResilience } from '@oldwhisper/resilience';

const resilientAPICall = withResilience(
  async (endpoint: string, data: any) => {
    const response = await resilientFetch(`/api/${endpoint}`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(data)
    });
    return await response.json();
  },
  {
    // Identification
    name: 'api-call',
    
    // Timeout
    timeoutMs: 5000,
    
    // Retry configuration
    retries: 3,
    retryOn: (err) => {
      // Only retry network errors and 5xx responses
      return err instanceof TypeError || 
             (err.message && err.message.includes('5'));
    },
    
    // Backoff strategy
    backoff: {
      type: 'exponential',
      baseDelayMs: 100,
      maxDelayMs: 5000,
      jitter: true
    },
    
    // Circuit breaker
    circuitBreaker: {
      failureThreshold: 5,
      resetTimeoutMs: 60000
    },
    
    // Abort signal support
    useAbortSignal: true,
    
    // Observability hooks
    hooks: {
      onAttempt: ({ name, attempt }) => {
        logger.debug(`[${name}] Attempt ${attempt}`);
      },
      onSuccess: ({ name, attempt, timeMs }) => {
        metrics.recordSuccess(name, timeMs, attempt);
      },
      onFailure: ({ name, attempt, timeMs, error }) => {
        metrics.recordFailure(name, timeMs, attempt);
        logger.error(`[${name}] Failure:`, error);
      },
      onRetry: ({ name, attempt, delayMs, error }) => {
        logger.warn(`[${name}] Retrying after ${delayMs}ms`);
      },
      onCircuitOpen: ({ name }) => {
        alerts.send(`Circuit breaker opened for ${name}`);
      },
      onCircuitHalfOpen: ({ name }) => {
        logger.info(`Circuit breaker testing ${name}`);
      },
      onCircuitClosed: ({ name }) => {
        logger.info(`Circuit breaker closed for ${name}`);
      }
    }
  }
);

Configuration Patterns

Aggressive Retries for Critical Operations

{
  retries: 5,
  backoff: {
    type: 'exponential',
    baseDelayMs: 50,
    maxDelayMs: 2000,
    jitter: true
  }
}

Conservative Retries for External APIs

{
  retries: 2,
  timeoutMs: 10000,
  backoff: {
    type: 'fixed',
    delayMs: 1000
  },
  circuitBreaker: {
    failureThreshold: 3,
    resetTimeoutMs: 30000
  }
}

Fast Fail for User-Facing Operations

{
  timeoutMs: 2000,
  retries: 1,
  backoff: {
    type: 'fixed',
    delayMs: 500
  }
}

Background Job Processing

{
  retries: 10,
  backoff: {
    type: 'exponential',
    baseDelayMs: 1000,
    maxDelayMs: 60000,
    jitter: true
  },
  retryOn: (err) => {
    // Don't retry validation errors
    if (err instanceof ValidationError) return false;
    return true;
  }
}
  • withResilience - Main function using this configuration
  • Hooks - Full hooks API reference
  • WrapperInit - Helper for managing configuration with metrics

Build docs developers (and LLMs) love