Documentation Index
Fetch the complete documentation index at: https://mintlify.com/visible/cruel/llms.txt
Use this file to discover all available pages before exploring further.
Overview
cruel.wrap() provides a fluent, chainable API for applying chaos behaviors to functions. cruel.compose() (aliased as wrap()) combines chaos injection with resilience patterns like retry logic, circuit breakers, and fallbacks.
cruel.wrap()
Signature
cruel.wrap<T extends AnyFn>(fn: T): {
fail: (rate?: number) => T
slow: (delay?: number | [number, number]) => T
timeout: (rate?: number) => T
flaky: (intensity?: number) => T
unreliable: () => T
nightmare: () => T
with: (opts: ChaosOptions) => T
}
Parameters
The function to wrap with a fluent API for chaos injection.
Return Value
An object with chainable methods for applying chaos behaviors:Apply failure injection. Default rate: 0.1 (10%).
slow
(delay?: number | [number, number]) => T
Apply delay. Default: 500ms.
Apply timeout. Default rate: 0.1 (10%).
flaky
(intensity?: number) => T
Apply combined chaos. Default intensity: 0.2 (20%).
Apply high chaos (30% fail, 10% timeout, variable delays).
Apply extreme chaos (50% fail, 20% timeout, long delays).
with
(opts: ChaosOptions) => T
Apply custom chaos options.
Examples
Basic Fluent API
import { cruel } from 'cruel'
const apiCall = async (id: string) => {
return await fetch(`/api/users/${id}`).then(r => r.json())
}
// Fluent wrapping with preset
const flaky = cruel.wrap(apiCall).flaky()
// Fluent wrapping with custom options
const custom = cruel.wrap(apiCall).with({
fail: 0.15,
delay: [200, 800],
timeout: 0.05
})
Readable Test Setup
import { cruel } from 'cruel'
const api = {
// More readable than: cruel(getUser, { fail: 0.1 })
getUser: cruel.wrap(getUser).fail(0.1),
// Clear intent for slow operations
searchUsers: cruel.wrap(searchUsers).slow([500, 1500]),
// Easy to see timeout testing
uploadFile: cruel.wrap(uploadFile).timeout(0.2)
}
cruel.compose()
Signature
cruel.compose<T extends AnyFn>(fn: T, options: WrapOptions): T
Also available as the standalone wrap() export.
Parameters
The function to wrap with chaos and resilience patterns.
Configuration combining chaos options with resilience patterns.Chaos Options:Failure probability (0-1).
delay
number | [number, number]
Delay in milliseconds or range.
Timeout probability (0-1) or timeout duration in ms if > 1.
Maximum random jitter in milliseconds.
Corruption probability (0-1).
spike
number | [number, number]
Delay spike in milliseconds or range.
Resilience Patterns:Retry configuration.Number of retry attempts.
delay
number | [number, number]
Delay between retries.
backoff
'fixed' | 'linear' | 'exponential'
Backoff strategy. Default: ‘fixed’.
Maximum retry delay in milliseconds.
retryIf
(error: Error) => boolean
Predicate to determine if error should trigger retry.
onRetry
(attempt: number, error: Error) => void
Callback invoked on each retry.
Circuit breaker configuration.Number of failures before opening circuit.
Milliseconds to wait before attempting half-open.
Callback when circuit opens.
Callback when circuit closes.
Callback when circuit enters half-open state.
Bulkhead pattern for limiting concurrent executions.Maximum number of concurrent executions.
Maximum queue size for waiting executions.
Callback when execution is rejected.
Timeout duration in milliseconds (enforced timeout, not probabilistic).
Fallback value or function to use on error.
Caching configuration.Time-to-live for cached values in milliseconds.
key
(...args: unknown[]) => string
Function to generate cache key from arguments.
Rate limiting configuration.Number of requests allowed per interval.
Time window in milliseconds.
Callback when rate limit is hit.
Hedging configuration for parallel requests.Number of parallel requests to make.
Delay in milliseconds between starting each request.
Return Value
The wrapped function with all specified chaos and resilience patterns applied in the correct order:
- Cache (if configured)
- Rate limiter (if configured)
- Bulkhead (if configured)
- Circuit breaker (if configured)
- Retry logic (if configured)
- Timeout (if configured)
- Fallback (if configured)
- Hedging (if configured)
- Chaos injection
Examples
Retry with Chaos
import { cruel } from 'cruel'
const resilientApi = cruel.compose(apiCall, {
// Inject chaos
fail: 0.3,
delay: [100, 500],
// Handle with retry
retry: {
attempts: 3,
delay: 1000,
backoff: 'exponential',
maxDelay: 5000,
onRetry: (attempt, error) => {
console.log(`Retry ${attempt}: ${error.message}`)
}
}
})
try {
const result = await resilientApi()
} catch (error) {
// Only fails after 3 retry attempts
}
Circuit Breaker Pattern
import { cruel } from 'cruel'
const protected = cruel.compose(unreliableService, {
fail: 0.5, // 50% failure rate
circuitBreaker: {
threshold: 5, // Open after 5 failures
timeout: 10000, // Wait 10s before retry
onOpen: () => console.log('Circuit opened!'),
onClose: () => console.log('Circuit closed!'),
onHalfOpen: () => console.log('Circuit half-open, testing...')
}
})
// After 5 failures, circuit opens and rejects immediately
// After 10s, attempts one request to test if service recovered
Complete Resilience Stack
import { cruel } from 'cruel'
const productionApi = cruel.compose(criticalApiCall, {
// Chaos injection for testing
fail: 0.1,
delay: [50, 200],
timeout: 0.02,
// Rate limiting
rateLimiter: {
requests: 100,
interval: 60000, // 100 requests per minute
onLimit: () => console.warn('Rate limit exceeded')
},
// Caching
cache: {
ttl: 60000, // 1 minute cache
key: (userId) => `user:${userId}`,
onHit: (key) => console.log(`Cache hit: ${key}`),
onMiss: (key) => console.log(`Cache miss: ${key}`)
},
// Bulkhead isolation
bulkhead: {
maxConcurrent: 10,
maxQueue: 50,
onReject: () => console.error('Bulkhead full')
},
// Circuit breaker
circuitBreaker: {
threshold: 10,
timeout: 30000,
onOpen: () => console.error('Circuit opened')
},
// Retry logic
retry: {
attempts: 3,
delay: [500, 1000],
backoff: 'exponential',
retryIf: (error) => error.code !== 'CRUEL_CIRCUIT_OPEN'
},
// Timeout enforcement
timeoutMs: 5000,
// Fallback on total failure
fallback: () => ({ cached: true, data: [] })
})
const result = await productionApi('user-123')
Hedging for Low Latency
import { cruel } from 'cruel'
// Send parallel requests, use first successful response
const lowLatency = cruel.compose(apiCall, {
delay: [100, 1000], // Variable latency
hedge: {
count: 3, // Send 3 parallel requests
delay: 50 // 50ms between each
}
})
// Reduces tail latency by sending redundant requests
const result = await lowLatency()
Conditional Retry
import { cruel } from 'cruel'
const smart = cruel.compose(apiCall, {
fail: 0.3,
retry: {
attempts: 5,
delay: 1000,
backoff: 'exponential',
// Only retry on specific errors
retryIf: (error) => {
// Don't retry on client errors (400s)
if (error.status >= 400 && error.status < 500) {
return false
}
// Retry on server errors (500s) and network errors
return true
},
onRetry: (attempt, error) => {
console.log(`Attempt ${attempt} failed: ${error.message}`)
}
}
})
Graceful Degradation
import { cruel } from 'cruel'
const graceful = cruel.compose(fetchUserProfile, {
fail: 0.2,
delay: [100, 500],
timeoutMs: 3000,
retry: {
attempts: 2,
delay: 500
},
// Fallback to cached/default data
fallback: async (userId) => {
console.log('Using fallback for user', userId)
return {
id: userId,
name: 'Unknown User',
cached: true
}
}
})
// Always returns a result, even on failure
const profile = await graceful('user-123')
Standalone Resilience Functions
These can also be used independently without chaos:
cruel.retry()
import { cruel } from 'cruel'
const retried = cruel.retry(apiCall, {
attempts: 3,
delay: 1000,
backoff: 'exponential'
})
cruel.circuitBreaker()
import { cruel } from 'cruel'
const protected = cruel.circuitBreaker(apiCall, {
threshold: 5,
timeout: 10000,
onOpen: () => console.log('Circuit opened')
})
// Check state
const state = protected.getState()
console.log(state) // { failures: 0, state: 'closed', lastFailure: 0 }
// Reset circuit
protected.reset()
cruel.bulkhead()
import { cruel } from 'cruel'
const limited = cruel.bulkhead(apiCall, {
maxConcurrent: 5,
maxQueue: 10,
onReject: () => console.warn('Too many requests')
})
cruel.rateLimiter()
import { cruel } from 'cruel'
const ratelimited = cruel.rateLimiter(apiCall, {
requests: 100,
interval: 60000 // 100 requests per minute
})
cruel.cache()
import { cruel } from 'cruel'
const cached = cruel.cache(expensiveCall, {
ttl: 60000, // 1 minute
key: (...args) => JSON.stringify(args)
})
cruel.withTimeout()
import { cruel } from 'cruel'
const timeouted = cruel.withTimeout(slowCall, {
ms: 5000,
onTimeout: () => console.log('Operation timed out')
})
cruel.fallback()
import { cruel } from 'cruel'
const safe = cruel.fallback(riskyCall, {
fallback: { default: true },
onFallback: (error) => console.error('Used fallback:', error)
})
cruel.hedge()
import { cruel } from 'cruel'
const hedged = cruel.hedge(apiCall, {
count: 3,
delay: 50
})
- cruel() - Main chaos wrapper
- createCruel() - Factory for custom instances
- Individual resilience exports:
withRetry, createCircuitBreaker, createBulkhead, etc.