Skip to content

Chaos game days

The problem. Retries, timeouts, breakers and fallbacks are configured once and then never exercised until a real outage. By then a timeout is longer than the load balancer’s, a fallback throws, or the breaker never opens because its minimumThroughput is higher than the traffic. A game day finds those gaps on purpose: you inject a failure at a time you choose, with people watching and a way to stop it, and check that the system behaves as you claimed.

The library gives you two ways to inject:

Chaos strategies (addChaosFault …) setGlobalChaos()
Where In one pipeline’s configuration Every pipeline in the process
Needs a deploy Once, to pre-wire them (off by default) No: call it at runtime
Scope Per pipeline, per execution (enabledGenerator, injectionRateGenerator) One filter over every callback attempt
Injects Fault, outcome (fake result), latency, any behaviour Fault and/or latency
Telemetry Chaos.OnFault, Chaos.OnLatency, … with the pipeline name Chaos.OnGlobal, without a pipeline name

Use pre-wired strategies for the experiments you’ll run again and again; use setGlobalChaos for one-off questions in staging (“what if every database call is 2 s slower?”).

  1. Write the hypothesis. One sentence you can check: “When pricing returns 503 for 20% of calls, checkout success stays above 99.5% and p99 latency below 1.5 s, because the retry hides single failures.”

  2. Define steady state and the abort condition. The metrics that show the hypothesis holds (checkout success rate, p99), and the line at which you stop at once (success below 98%). Look at them for a normal hour first.

  3. Limit the blast radius. Staging first. In production: synthetic traffic or an internal tenant only, one instance or one pipeline, a low rate, and a fixed end time.

  4. Make stopping trivial and automatic. A flag that turns injection off without a deploy, and a timer that turns it off even if nobody does.

  5. Run, watch, and write down what happened. Ramp the rate in steps (1%, 5%, 20%). Watch the Chaos.* events next to the steady-state metrics, so you always know which errors you caused.

  6. Review. Every surprise becomes a ticket: a timeout to shorten, a fallback to fix, a breaker threshold to lower. Then turn the experiment into an automated test so it can’t regress.

Add chaos strategies last (innermost), so the retry, breaker, timeout and fallback above them see the injected failure as if it came from the dependency. Gate them twice: a flag that enables the experiment, and a rate that is zero for everything outside its scope.

pricing.ts
/** Set on synthetic and internal-tenant requests: the only traffic a game day may touch. */
export const
const CHAOS_SCOPE: "chaos-scope"

Set on synthetic and internal-tenant requests: the only traffic a game day may touch.

CHAOS_SCOPE
= 'chaos-scope';
const
const rate: (flag: string) => ({ context }: {
context: {
properties: Map<string, unknown>;
};
}) => number
rate
= (
flag: string
flag
: string) => ({
context: {
properties: Map<string, unknown>;
}
context
}: {
context: {
properties: Map<string, unknown>;
}
context
: {
properties: Map<string, unknown>
properties
:
interface Map<K, V>
Map
<string, unknown> } }) =>
context: {
properties: Map<string, unknown>;
}
context
.
properties: Map<string, unknown>
properties
.
Map<string, unknown>.has(key: string): boolean

@returns ― boolean indicating whether an element with the specified key exists or not.

has
(
const CHAOS_SCOPE: "chaos-scope"

Set on synthetic and internal-tenant requests: the only traffic a game day may touch.

CHAOS_SCOPE
) ?
const flags: {
isOn(name: string): boolean;
number(name: string, fallback: number): number;
}
flags
.
function number(name: string, fallback: number): number
number
(
flag: string
flag
, 0) : 0;
export const
const pricing: ResiliencePipeline<number, never>
pricing
= new
new ResiliencePipelineBuilder<number, never, never>(options?: ResiliencePipelineBuilderOptions): ResiliencePipelineBuilder<number, never, never>

Polly-style builder with a shortcut for every strategy: addRetry(), addTimeout(), addCircuitBreaker(), … Convenient, but it bundles every strategy. For the smallest browser bundle use PipelineBuilder with retryStrategy(), timeoutStrategy(), … instead; the two are interchangeable.

The type parameters track which errors the pipeline can raise, for pipeline.settle() + Outcome.match().

ResiliencePipelineBuilder
<number>({
ResiliencePipelineBuilderOptions.name?: string | undefined

Labels telemetry events.

name
: 'pricing' })
.
ResiliencePipelineBuilder<number, never, never>.addFallback(options: FallbackOptions<number> & {
shouldHandle?: undefined;
}): ResiliencePipelineBuilder<number, never, string> (+1 overload)

Without shouldHandle it catches every failure of the strategies added after it, so their errors leave the error type.

addFallback
({
FallbackOptions<number>.fallbackAction?: ((args: OutcomeArgs<number>) => MaybePromise<number>) | undefined

Produces the substitute value; throwing here makes the execution fail with that error. Give either this or fallbackValue.

fallbackAction
: ({
context: ResilienceContext
context
}) =>
const lastKnownPrice: (sku: string) => number
lastKnownPrice
(
context: ResilienceContext
context
.
ResilienceContext.operationKey: string | undefined
operationKey
?? '') })
.
ResiliencePipelineBuilder<number, never, string>.addRetry(options?: RetryOptions<number>): ResiliencePipelineBuilder<number, never, string>
addRetry
({
RetryOptions<number>.maxRetryAttempts?: number | undefined

Retries in addition to the original call. Default 3. Infinity retries until aborted.

maxRetryAttempts
: 2,
RetryOptions<number>.delay?: Duration | undefined

Base delay. Default 2000 ms.

delay
: '100ms',
RetryOptions<number>.useJitter?: Jitter | undefined
useJitter
: true })
.
ResiliencePipelineBuilder<number, never, string>.addCircuitBreaker(options?: CircuitBreakerOptions<number>): Next<number, never, string, BrokenCircuitError>
addCircuitBreaker
({
CircuitBreakerOptions<number>.failureRatio?: number | undefined

Failure ratio in the window that breaks the circuit. Default 0.1.

failureRatio
: 0.5,
CircuitBreakerOptions<number>.minimumThroughput?: number | undefined

Minimum recorded calls in the window before the ratio is considered. Default 100.

minimumThroughput
: 20,
CircuitBreakerOptions<number>.breakDuration?: Duration | undefined

How long the circuit stays open. Default 5 s.

breakDuration
: '15s' })
.
ResiliencePipelineBuilder<number, never, string>.addTimeout(options: Duration | TimeoutOptions): Next<number, never, string, TimeoutRejectedError>

addTimeout(5000), addTimeout('5s') or addTimeout({ timeout: '5s', onTimeout }).

addTimeout
('800ms')
// Experiment 1: pricing slower than our timeout.
.
ResiliencePipelineBuilder<number, never, string>.addChaosLatency(options: ChaosLatencyOptions): Next<number, never, string, TimeoutRejectedError>
addChaosLatency
({
ChaosOptions.enabledGenerator?: ((args: ContextArgs) => MaybePromise<boolean>) | undefined
enabledGenerator
: () =>
const flags: {
isOn(name: string): boolean;
number(name: string, fallback: number): number;
}
flags
.
function isOn(name: string): boolean
isOn
('chaos.pricing.latency'),
ChaosOptions.injectionRateGenerator?: ((args: ContextArgs) => MaybePromise<number>) | undefined
injectionRateGenerator
:
const rate: (flag: string) => ({ context }: {
context: {
properties: Map<string, unknown>;
};
}) => number
rate
('chaos.pricing.latency.rate'),
ChaosLatencyOptions.latency?: Duration | undefined

Default 30 s.

latency
: '2s',
})
// Experiment 2: pricing answering 503.
.
ResiliencePipelineBuilder<number, never, string>.addChaosFault(options: ChaosFaultOptions): Next<number, never, string, TimeoutRejectedError>
addChaosFault
({
ChaosOptions.enabledGenerator?: ((args: ContextArgs) => MaybePromise<boolean>) | undefined
enabledGenerator
: () =>
const flags: {
isOn(name: string): boolean;
number(name: string, fallback: number): number;
}
flags
.
function isOn(name: string): boolean
isOn
('chaos.pricing.errors'),
ChaosOptions.injectionRateGenerator?: ((args: ContextArgs) => MaybePromise<number>) | undefined
injectionRateGenerator
:
const rate: (flag: string) => ({ context }: {
context: {
properties: Map<string, unknown>;
};
}) => number
rate
('chaos.pricing.errors.rate'),
ChaosFaultOptions.faultGenerator?: ((args: ContextArgs) => MaybePromise<unknown>) | undefined
faultGenerator
: () =>
var Object: ObjectConstructor

Provides functionality common to all JavaScript objects.

Object
.
ObjectConstructor.assign<Error, {
status: number;
}>(target: Error, source: {
status: number;
}): Error & {
status: number;
} (+3 overloads)

Copy the values of all of the enumerable own properties from one or more source objects to a target object. Returns the target object.

@param ― target The target object to copy to.

@param ― source The source object from which to copy properties.

assign
(new
var Error: ErrorConstructor
new (message?: string, options?: ErrorOptions) => Error (+1 overload)
Error
('chaos: pricing 503'), {
status: number
status
: 503 }),
})
.
ResiliencePipelineBuilder<number, never, string>.build(): ResiliencePipeline<number, never>
build
();

With both flags off, the strategies cost one flag lookup per call. The fault carries status: 503, so the default retry and breaker predicates treat it exactly like a real 503. A fault with status: 400 would test something else: that client errors are not retried.

Experiments worth pre-wiring for each important dependency:

Experiment Inject You expect
Slow dependency addChaosLatency above the timeout TimeoutRejectedError, retry, then fallback; inbound p99 stays under its budget.
Partial failure addChaosFault, 5–20% Retries hide it; error rate seen by users barely moves.
Dependency down addChaosFault, 100% for scoped traffic Breaker opens within minimumThroughput calls; fallback answers; no retry storm.
Rate limited addChaosOutcome with a 429 result, or a fault with status: 429 and retryAfter Calls wait for retryAfter; nothing hammers the API.
Slow, then down latency at a low rate plus faults Adaptive limits shrink, shedding starts before the timeouts pile up.

setGlobalChaos adds latency and/or a fault before every callback attempt of every pipeline in the process, without touching their configuration. It returns a function that turns it off. Wrap it so it always turns itself off:

game-day.ts
let
let stop: (() => void) | undefined
stop
: (() => void) | undefined;
let
let timer: NodeJS.Timeout | undefined
timer
:
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any

Obtain the return type of a function type

ReturnType
<typeof
function setTimeout(handler: TimerHandler, timeout?: number, ...arguments: any[]): number (+2 overloads)
setTimeout
> | undefined;
/** Starts an experiment that ends by itself after `minutes`, even if nobody calls stopGameDay(). */
export function
function startGameDay(options: {
keyPrefix: string;
rate: number;
latency?: Duration;
fail?: boolean;
minutes: number;
}): void

Starts an experiment that ends by itself after minutes, even if nobody calls stopGameDay().

startGameDay
(
options: {
keyPrefix: string;
rate: number;
latency?: Duration;
fail?: boolean;
minutes: number;
}
options
: {
keyPrefix: string
keyPrefix
: string;
rate: number
rate
: number;
latency?: Duration | undefined
latency
?:
type Duration = number | `${number}ms` | `${number}s` | `${number}m` | `${number}h` | `${number}d` | `${number}w`

Milliseconds, or a string like '250ms', '30s', '5m', '1h', '1d', '2w'.

Duration
;
fail?: boolean | undefined
fail
?: boolean;
minutes: number
minutes
: number }) {
function stopGameDay(): void
stopGameDay
();
let stop: (() => void) | undefined
stop
=
function setGlobalChaos(options: GlobalChaosOptions): () => void

Inject faults and/or latency into every pipeline in the process, without touching their configuration: game days, staging soak tests, "does my whole app survive a slow database?". Returns a function that turns it off.

setGlobalChaos
({
GlobalChaosOptions.injectionRate?: number | undefined

Probability per callback in [0, 1]. Default 0.05.

injectionRate
:
options: {
keyPrefix: string;
rate: number;
latency?: Duration;
fail?: boolean;
minutes: number;
}
options
.
rate: number
rate
,
GlobalChaosOptions.latency?: Duration | undefined

Delay added before the callback.

latency
:
options: {
keyPrefix: string;
rate: number;
latency?: Duration;
fail?: boolean;
minutes: number;
}
options
.
latency?: Duration | undefined
latency
,
GlobalChaosOptions.fault?: unknown

Error to fail with (or a function creating it). Omit to inject latency only.

fault
:
options: {
keyPrefix: string;
rate: number;
latency?: Duration;
fail?: boolean;
minutes: number;
}
options
.
fail?: boolean | undefined
fail
? () =>
var Object: ObjectConstructor

Provides functionality common to all JavaScript objects.

Object
.
ObjectConstructor.assign<Error, {
status: number;
}>(target: Error, source: {
status: number;
}): Error & {
status: number;
} (+3 overloads)

Copy the values of all of the enumerable own properties from one or more source objects to a target object. Returns the target object.

@param ― target The target object to copy to.

@param ― source The source object from which to copy properties.

assign
(new
var Error: ErrorConstructor
new (message?: string, options?: ErrorOptions) => Error (+1 overload)
Error
('chaos: game day'), {
status: number
status
: 503 }) :
var undefined
undefined
,
// Only calls that opted in through their operationKey. Never "everything".
GlobalChaosOptions.filter?: ((context: ResilienceContext) => boolean) | undefined

Only inject where this returns true (e.g. one pipeline, or one operationKey).

filter
: (
ctx: ResilienceContext
ctx
) =>
ctx: ResilienceContext
ctx
.
ResilienceContext.operationKey: string | undefined
operationKey
?.
String.startsWith(searchString: string, position?: number): boolean

Returns true if the sequence of elements of searchString converted to a String is the same as the corresponding elements of this object (converted to a String) starting at position. Otherwise returns false.

startsWith
(
options: {
keyPrefix: string;
rate: number;
latency?: Duration;
fail?: boolean;
minutes: number;
}
options
.
keyPrefix: string
keyPrefix
) ?? false,
});
let timer: NodeJS.Timeout | undefined
timer
=
function setTimeout<[]>(callback: () => void, delay?: number): NodeJS.Timeout (+2 overloads)

Schedules execution of a one-time callback after delay milliseconds.

The callback will likely not be invoked in precisely delay milliseconds. Node.js makes no guarantees about the exact timing of when callbacks will fire, nor of their ordering. The callback will be called as close as possible to the time specified.

When delay is larger than 2147483647 or less than 1 or NaN, the delay will be set to 1. Non-integer delays are truncated to an integer.

If callback is not a function, a TypeError will be thrown.

This method has a custom variant for promises that is available using timersPromises.setTimeout().

@since ― v0.0.1

@param ― callback The function to call when the timer elapses.

@param ― delay The number of milliseconds to wait before calling the callback. Default: 1.

@param ― args Optional arguments to pass when the callback is called.

@returns ― for use with clearTimeout()

setTimeout
(
function stopGameDay(): void
stopGameDay
,
options: {
keyPrefix: string;
rate: number;
latency?: Duration;
fail?: boolean;
minutes: number;
}
options
.
minutes: number
minutes
* 60_000);
}
export function
function stopGameDay(): void
stopGameDay
() {
function clearTimeout(timeout: NodeJS.Timeout | string | number | undefined): void (+1 overload)

Cancels a Timeout object created by setTimeout().

@since ― v0.0.1

@param ― timeout A Timeout object as returned by setTimeout() or the primitive of the Timeout object as a string or a number.

clearTimeout
(
let timer: NodeJS.Timeout | undefined
timer
);
let stop: (() => void) | undefined
stop
?.();
let stop: (() => void) | undefined
stop
=
var undefined
undefined
;
}
// From an authenticated admin endpoint, in staging:
function startGameDay(options: {
keyPrefix: string;
rate: number;
latency?: Duration;
fail?: boolean;
minutes: number;
}): void

Starts an experiment that ends by itself after minutes, even if nobody calls stopGameDay().

startGameDay
({
keyPrefix: string
keyPrefix
: 'db:',
rate: number
rate
: 0.2,
latency?: Duration | undefined
latency
: '2s',
minutes: number
minutes
: 15 });

The filter sees the execution’s context: operationKey, operationName (the name execute option), properties and attemptNumber. It does not see which pipeline is running, so give the calls you want to target a recognisable operationKey or property. (operationKey is also the default cache key, so on cached pipelines use a property instead.)

Count what you inject next to what users see. onTelemetry gets every event in the process:

const
const off: () => void
off
=
function onTelemetry(listener: (event: TelemetryEvent) => void): () => void

Subscribe to every strategy event in the process. Works in every runtime (diagnostics_channel is Node-only). Returns an unsubscribe function.

onTelemetry
(({
event: string
event
,
source: TelemetrySource
source
,
context: ResilienceContext
context
}) => {
if (
event: string
event
.
String.startsWith(searchString: string, position?: number): boolean

Returns true if the sequence of elements of searchString converted to a String is the same as the corresponding elements of this object (converted to a String) starting at position. Otherwise returns false.

startsWith
('Chaos.')) {
const metrics: {
increment(name: string, labels: Record<string, string>): void;
}
metrics
.
function increment(name: string, labels: Record<string, string>): void
increment
('chaos_injected_total', {
event: string
event
,
target: string
target
:
source: TelemetrySource
source
.
TelemetrySource.pipelineName: string | undefined
pipelineName
??
context: ResilienceContext
context
.
ResilienceContext.operationKey: string | undefined
operationKey
?? 'unknown',
});
}
// The reactions you expect to see during the experiment.
if (
event: string
event
=== 'OnRetry' ||
event: string
event
=== 'OnTimeout' ||
event: string
event
=== 'OnCircuitOpened' ||
event: string
event
=== 'OnFallback') {
const metrics: {
increment(name: string, labels: Record<string, string>): void;
}
metrics
.
function increment(name: string, labels: Record<string, string>): void
increment
('resilience_reaction_total', {
event: string
event
,
pipeline: string
pipeline
:
source: TelemetrySource
source
.
TelemetrySource.pipelineName: string | undefined
pipelineName
?? 'unknown' });
}
});

If you use the OpenTelemetry or Prometheus integration, the Chaos.* events are already counted in the strategy events metric. Put that panel on the game-day dashboard.

What to look for:

  • Injected, but no reaction. Faults injected and no OnRetry: the retry predicate doesn’t match the error. Latency injected and no OnTimeout: the timeout is longer than the injected latency.
  • Reaction, but users still affected. Timeouts fire, yet inbound p99 climbs: the retries add up to more than the request budget. Shorten per-attempt timeouts or retry less.
  • Breaker never opens. Scoped traffic is below minimumThroughput within samplingDuration. Either the breaker is tuned for more traffic than you have, or the experiment’s scope is too small to test it.

Have the stop actions written down before you start, fastest first:

  1. Turn the experiment flag off (pre-wired strategies) or call stopGameDay() (global chaos).
  2. If an instance is stuck in a bad state (circuit open on a healthy dependency), close it through its CircuitBreakerManualControl, or restart the instance.
  3. The timer ends a global experiment by itself; a restart ends it too, since the hook lives in memory.

Every experiment that found something should become a test, so the fix can’t regress. In tests, injectionRate: 1 makes injection deterministic:

import {
function test(name?: string, fn?: test.TestFn): Promise<void> (+3 overloads)

The test() function is the value imported from the test module. Each invocation of this function results in reporting the test to the TestsStream.

The TestContext object passed to the fn argument can be used to perform actions related to the current test. Examples include skipping the test, adding additional diagnostic information, or creating subtests.

test() returns a Promise that fulfills once the test completes. if test() is called within a suite, it fulfills immediately. The return value can usually be discarded for top level tests. However, the return value from subtests should be used to prevent the parent test from finishing first and cancelling the subtest as shown in the following example.

test('top level test', async (t) => {
// The setTimeout() in the following subtest would cause it to outlive its
// parent test if 'await' is removed on the next line. Once the parent test
// completes, it will cancel any outstanding subtests.
await t.test('longer running subtest', async (t) => {
return new Promise((resolve, reject) => {
setTimeout(resolve, 1000);
});
});
});

The timeout option can be used to fail the test if it takes longer than timeout milliseconds to complete. However, it is not a reliable mechanism for canceling tests because a running test might block the application thread and thus prevent the scheduled cancellation.

@since ― v18.0.0, v16.17.0

@param ― name The name of the test, which is displayed when reporting test results. Defaults to the name property of fn, or '<anonymous>' if fn does not have a name.

@param ― options Configuration options for the test.

@param ― fn The function under test. The first argument to this function is a TestContext object. If the test uses callbacks, the callback function is passed as the second argument.

@return ― Fulfilled with undefined once the test completes, or immediately if the test runs within a suite.

test
} from 'node:test';
import
function assert(value: unknown, message?: string | Error): asserts value
assert
from 'node:assert/strict';
import {
class ResiliencePipelineBuilder<T = any, E = never, C extends string = never>

Polly-style builder with a shortcut for every strategy: addRetry(), addTimeout(), addCircuitBreaker(), … Convenient, but it bundles every strategy. For the smallest browser bundle use PipelineBuilder with retryStrategy(), timeoutStrategy(), … instead; the two are interchangeable.

The type parameters track which errors the pipeline can raise, for pipeline.settle() + Outcome.match().

ResiliencePipelineBuilder
} from 'resilience-pipeline';
function test(name?: string, fn?: test.TestFn): Promise<void> (+3 overloads)

The test() function is the value imported from the test module. Each invocation of this function results in reporting the test to the TestsStream.

The TestContext object passed to the fn argument can be used to perform actions related to the current test. Examples include skipping the test, adding additional diagnostic information, or creating subtests.

test() returns a Promise that fulfills once the test completes. if test() is called within a suite, it fulfills immediately. The return value can usually be discarded for top level tests. However, the return value from subtests should be used to prevent the parent test from finishing first and cancelling the subtest as shown in the following example.

test('top level test', async (t) => {
// The setTimeout() in the following subtest would cause it to outlive its
// parent test if 'await' is removed on the next line. Once the parent test
// completes, it will cancel any outstanding subtests.
await t.test('longer running subtest', async (t) => {
return new Promise((resolve, reject) => {
setTimeout(resolve, 1000);
});
});
});

The timeout option can be used to fail the test if it takes longer than timeout milliseconds to complete. However, it is not a reliable mechanism for canceling tests because a running test might block the application thread and thus prevent the scheduled cancellation.

@since ― v18.0.0, v16.17.0

@param ― name The name of the test, which is displayed when reporting test results. Defaults to the name property of fn, or '<anonymous>' if fn does not have a name.

@param ― options Configuration options for the test.

@param ― fn The function under test. The first argument to this function is a TestContext object. If the test uses callbacks, the callback function is passed as the second argument.

@return ― Fulfilled with undefined once the test completes, or immediately if the test runs within a suite.

test
('pricing answers from the fallback when pricing is down', async () => {
const
const pricing: ResiliencePipeline<number, never>
pricing
= new
new ResiliencePipelineBuilder<number, never, never>(options?: ResiliencePipelineBuilderOptions): ResiliencePipelineBuilder<number, never, never>

Polly-style builder with a shortcut for every strategy: addRetry(), addTimeout(), addCircuitBreaker(), … Convenient, but it bundles every strategy. For the smallest browser bundle use PipelineBuilder with retryStrategy(), timeoutStrategy(), … instead; the two are interchangeable.

The type parameters track which errors the pipeline can raise, for pipeline.settle() + Outcome.match().

ResiliencePipelineBuilder
<number>()
.
ResiliencePipelineBuilder<number, never, never>.addFallback(options: FallbackOptions<number> & {
shouldHandle?: undefined;
}): ResiliencePipelineBuilder<number, never, string> (+1 overload)

Without shouldHandle it catches every failure of the strategies added after it, so their errors leave the error type.

addFallback
({
FallbackOptions<number>.fallbackValue?: number | undefined

A fixed substitute value (cockatiel's fallback(policy, value)).

fallbackValue
: -1 })
.
ResiliencePipelineBuilder<number, never, string>.addRetry(options?: RetryOptions<number>): ResiliencePipelineBuilder<number, never, string>
addRetry
({
RetryOptions<number>.maxRetryAttempts?: number | undefined

Retries in addition to the original call. Default 3. Infinity retries until aborted.

maxRetryAttempts
: 2,
RetryOptions<number>.delay?: Duration | undefined

Base delay. Default 2000 ms.

delay
: 0 })
.
ResiliencePipelineBuilder<number, never, string>.addChaosFault(options: ChaosFaultOptions): ResiliencePipelineBuilder<number, never, string>
addChaosFault
({
ChaosOptions.injectionRate?: number | undefined

Probability in [0, 1]. Default 0.001. Ignored when injectionRateGenerator is set.

injectionRate
: 1,
ChaosFaultOptions.fault?: unknown
fault
:
var Object: ObjectConstructor

Provides functionality common to all JavaScript objects.

Object
.
ObjectConstructor.assign<Error, {
status: number;
}>(target: Error, source: {
status: number;
}): Error & {
status: number;
} (+3 overloads)

Copy the values of all of the enumerable own properties from one or more source objects to a target object. Returns the target object.

@param ― target The target object to copy to.

@param ― source The source object from which to copy properties.

assign
(new
var Error: ErrorConstructor
new (message?: string, options?: ErrorOptions) => Error (+1 overload)
Error
('chaos: pricing 503'), {
status: number
status
: 503 }) })
.
ResiliencePipelineBuilder<number, never, string>.build(): ResiliencePipeline<number, never>
build
();
function assert(value: unknown, message?: string | Error): asserts value
assert
.
strict.equal<-1>(actual: unknown, expected: -1, message?: string | Error): asserts actual is -1
export strict.equal

Tests strict equality between the actual and expected parameters as determined by Object.is().

import assert from 'node:assert/strict';
assert.strictEqual(1, 2);
// AssertionError [ERR_ASSERTION]: Expected inputs to be strictly equal:
//
// 1 !== 2
assert.strictEqual(1, 1);
// OK
assert.strictEqual('Hello foobar', 'Hello World!');
// AssertionError [ERR_ASSERTION]: Expected inputs to be strictly equal:
// + actual - expected
//
// + 'Hello foobar'
// - 'Hello World!'
// ^
const apples = 1;
const oranges = 2;
assert.strictEqual(apples, oranges, `apples ${apples} !== oranges ${oranges}`);
// AssertionError [ERR_ASSERTION]: apples 1 !== oranges 2
assert.strictEqual(1, '1', new TypeError('Inputs are not identical'));
// TypeError: Inputs are not identical

If the values are not strictly equal, an AssertionError is thrown with a message property set equal to the value of the message parameter. If the message parameter is undefined, a default error message is assigned. If the message parameter is an instance of an Error then it will be thrown instead of the AssertionError.

@since ― v0.1.21

equal
(await
const pricing: ResiliencePipeline<number, never>
pricing
.
ResiliencePipeline<number, never>.execute<42>(callback: (context: ResilienceContext) => MaybePromise<42>, options?: ExecuteOptions): Promise<42>

Runs the callback through the pipeline; throws the final error.

execute
(() => 42), -1);
});

Prefer the per-pipeline strategies in tests over setGlobalChaos: test runners run files in parallel, and the global hook is shared by everything in the process. For injected latency, use a FakeTimeProvider so a 2 s delay takes no time (see testing).