Chaos
Chaos injects faults into the steps added before it: latency, thrown errors, typed errors, or values. Use it in tests and game days to check what the rest of the policy and the application do when a dependency misbehaves.
import { Tenace } from '@julr/tenace'
// one call in five takes 2s longer, so the timeout firesconst policy = Tenace.policy().withChaos({ rate: 0.2, latency: '2s' }).withTimeout('1s')policy.withChaos(chaos?: Chaos | ChaosOptions)new Chaos(options?: ChaosOptions)Tenace.chaos(options): () => void| Option | Type | Default | Description |
|---|---|---|---|
rate |
number |
1 |
Share of calls affected, from 0 to 1. |
latency |
Duration | () => Duration |
Delay added before the call. | |
throws |
() => unknown |
Simulates a throw of the function. Becomes an UnexpectedError. |
|
error |
() => E |
Returns this error instead of calling the function. | |
value |
() => V |
Returns this value instead of calling the function. | |
enabled |
boolean | () => boolean |
true |
Turns the chaos on and off without rebuilding the policy. |
random |
() => number |
Math.random |
Source of randomness, for tests. |
A rate outside 0 to 1 throws a RangeError when the policy is built.
Behavior
Section titled “Behavior”For each call, chaos checks enabled, then draws a random number. The call is affected when the number is below rate. An affected call goes through these steps in order:
- With
latency, it waits. An abort during the wait ends the call. - With
error, it returnserr(error()). - Otherwise, with
value, it returnsok(value()). - Otherwise, with
throws, it fails as if the function threwthrows(). - Otherwise, without
latency, it fails as if the function threw aChaosError. - Otherwise, it calls the function after the latency.
So {} makes every call throw a ChaosError, and { latency: '200ms' } delays every call without failing it.
An error, value, or throws factory that throws fails the call as if the function threw that value: an UnexpectedError.
Error channel
Section titled “Error channel”An error factory adds its error type to the channel, and a value factory adds its value type to the result. Thrown faults, including the default ChaosError, are UnexpectedError like any throw, so they do not change the types.
import { Tenace, TenaceError } from '@julr/tenace'
class Overloaded extends TenaceError { readonly _tag = 'Overloaded' as const readonly code = 'E_OVERLOADED' as const}
const policy = Tenace.policy() .withChaos({ rate: 0.1, error: () => new Overloaded('chaos') }) .withFallbackOn(['Overloaded'], () => null)Chaos around every call
Section titled “Chaos around every call”Tenace.chaos(options) injects faults around the function of every policy, at every attempt, until you call the function it returns. It takes every option except error and value, so the types of your policies stay accurate.
import { Tenace } from '@julr/tenace'
const restore = Tenace.chaos({ rate: 0.5, latency: '200ms' })// ... run the scenariorestore()See Testing for how to use it in a test suite.