Comparing version 3.1.3 to 3.2.0
@@ -6,4 +6,3 @@ /** | ||
/** | ||
* Returns the first backoff duration. Can return "undefined" to signal | ||
* that we should not back off. | ||
* Returns the first backoff duration. | ||
*/ | ||
@@ -10,0 +9,0 @@ next(context: T): IBackoff<T>; |
import { IBackoff, IBackoffFactory } from './Backoff'; | ||
export declare type DelegateBackoffFn<T, S = void> = (context: T, state?: S) => { | ||
export type DelegateBackoffFn<T, S = void> = (context: T, state?: S) => { | ||
delay: number; | ||
@@ -4,0 +4,0 @@ state: S; |
@@ -5,3 +5,3 @@ import { IExponentialBackoffOptions } from '../backoff/ExponentialBackoff'; | ||
*/ | ||
export declare type GeneratorFn<S> = (state: S | undefined, options: IExponentialBackoffOptions<S>) => [number, S]; | ||
export type GeneratorFn<S> = (state: S | undefined, options: IExponentialBackoffOptions<S>) => [number, S]; | ||
/** | ||
@@ -8,0 +8,0 @@ * Generator that creates a backoff with no jitter. |
@@ -11,3 +11,3 @@ import { IBackoff, IBackoffFactory } from './Backoff'; | ||
*/ | ||
next(): IBackoff<unknown>; | ||
next(_context: unknown): IBackoff<unknown>; | ||
} |
@@ -14,3 +14,3 @@ "use strict"; | ||
*/ | ||
next() { | ||
next(_context) { | ||
return instance(this.durations, 0); | ||
@@ -17,0 +17,0 @@ } |
@@ -17,1 +17,2 @@ import { CircuitState } from '../CircuitBreakerPolicy'; | ||
export * from './ConsecutiveBreaker'; | ||
export * from './CountBreaker'; |
@@ -19,2 +19,3 @@ "use strict"; | ||
__exportStar(require("./ConsecutiveBreaker"), exports); | ||
__exportStar(require("./CountBreaker"), exports); | ||
//# sourceMappingURL=Breaker.js.map |
@@ -31,3 +31,3 @@ import { CircuitState } from '../CircuitBreakerPolicy'; | ||
* last `samplingDuration`, so long as there's at least `minimumRps` (to avoid | ||
* closing unnecessarily under low RPS). | ||
* opening unnecessarily under low RPS). | ||
*/ | ||
@@ -34,0 +34,0 @@ constructor({ threshold, duration: samplingDuration, minimumRps }: ISamplingBreakerOptions); |
@@ -9,3 +9,3 @@ "use strict"; | ||
* last `samplingDuration`, so long as there's at least `minimumRps` (to avoid | ||
* closing unnecessarily under low RPS). | ||
* opening unnecessarily under low RPS). | ||
*/ | ||
@@ -12,0 +12,0 @@ constructor({ threshold, duration: samplingDuration, minimumRps }) { |
@@ -1,2 +0,1 @@ | ||
/// <reference types="node" /> | ||
import { IDefaultPolicyContext, IPolicy } from './Policy'; | ||
@@ -3,0 +2,0 @@ export declare class BulkheadPolicy implements IPolicy { |
@@ -12,2 +12,14 @@ "use strict"; | ||
/** | ||
* Returns the number of available execution slots at this point in time. | ||
*/ | ||
get executionSlots() { | ||
return this.capacity - this.active; | ||
} | ||
/** | ||
* Returns the number of queue slots at this point in time. | ||
*/ | ||
get queueSlots() { | ||
return this.queueCapacity - this.queue.length; | ||
} | ||
/** | ||
* Bulkhead limits concurrent requests made. | ||
@@ -36,14 +48,2 @@ */ | ||
/** | ||
* Returns the number of available execution slots at this point in time. | ||
*/ | ||
get executionSlots() { | ||
return this.capacity - this.active; | ||
} | ||
/** | ||
* Returns the number of queue slots at this point in time. | ||
*/ | ||
get queueSlots() { | ||
return this.queueCapacity - this.queue.length; | ||
} | ||
/** | ||
* Executes the given function. | ||
@@ -50,0 +50,0 @@ * @param fn Function to execute |
@@ -1,2 +0,2 @@ | ||
/// <reference types="node" /> | ||
import { IBackoffFactory } from './backoff/Backoff'; | ||
import { IBreaker } from './breaker/Breaker'; | ||
@@ -25,5 +25,30 @@ import { ExecuteWrapper } from './common/Executor'; | ||
} | ||
/** | ||
* Context passed into halfOpenAfter backoff delegate. | ||
*/ | ||
export interface IHalfOpenAfterBackoffContext extends IDefaultPolicyContext { | ||
/** | ||
* The consecutive number of times the circuit has entered the | ||
* {@link CircuitState.Open} state. | ||
*/ | ||
attempt: number; | ||
/** | ||
* The result of the last method call that caused the circuit to enter the | ||
* {@link CircuitState.Open} state. Either a thrown error, or a value that we | ||
* determined should open the circuit. | ||
*/ | ||
result: FailureReason<unknown>; | ||
} | ||
export interface ICircuitBreakerOptions { | ||
breaker: IBreaker; | ||
halfOpenAfter: number; | ||
/** | ||
* When to (potentially) enter the {@link CircuitState.HalfOpen} state from | ||
* the {@link CircuitState.Open} state. Either a duration in milliseconds or a | ||
* backoff factory. | ||
*/ | ||
halfOpenAfter: number | IBackoffFactory<IHalfOpenAfterBackoffContext>; | ||
/** | ||
* Initial state from a previous call to {@link CircuitBreakerPolicy.toJSON}. | ||
*/ | ||
initialState?: unknown; | ||
} | ||
@@ -38,2 +63,3 @@ export declare class CircuitBreakerPolicy implements IPolicy { | ||
private readonly stateChangeEmitter; | ||
private readonly halfOpenAfterBackoffFactory; | ||
private innerLastFailure?; | ||
@@ -93,2 +119,9 @@ private innerState; | ||
execute<T>(fn: (context: IDefaultPolicyContext) => PromiseLike<T> | T, signal?: AbortSignal): Promise<T>; | ||
/** | ||
* Captures circuit breaker state that can later be used to recreate the | ||
* breaker by passing `state` to the `circuitBreaker` function. This is | ||
* useful in cases like serverless functions where you may want to keep | ||
* the breaker state across multiple executions. | ||
*/ | ||
toJSON(): unknown; | ||
private halfOpen; | ||
@@ -95,0 +128,0 @@ private open; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.CircuitBreakerPolicy = exports.CircuitState = void 0; | ||
const Backoff_1 = require("./backoff/Backoff"); | ||
const abort_1 = require("./common/abort"); | ||
@@ -29,4 +30,16 @@ const Event_1 = require("./common/Event"); | ||
CircuitState[CircuitState["Isolated"] = 3] = "Isolated"; | ||
})(CircuitState = exports.CircuitState || (exports.CircuitState = {})); | ||
})(CircuitState || (exports.CircuitState = CircuitState = {})); | ||
class CircuitBreakerPolicy { | ||
/** | ||
* Gets the current circuit breaker state. | ||
*/ | ||
get state() { | ||
return this.innerState.value; | ||
} | ||
/** | ||
* Gets the last reason the circuit breaker failed. | ||
*/ | ||
get lastFailure() { | ||
return this.innerLastFailure; | ||
} | ||
constructor(options, executor) { | ||
@@ -65,16 +78,28 @@ this.options = options; | ||
this.onFailure = this.executor.onFailure; | ||
this.halfOpenAfterBackoffFactory = | ||
typeof options.halfOpenAfter === 'number' | ||
? new Backoff_1.ConstantBackoff(options.halfOpenAfter) | ||
: options.halfOpenAfter; | ||
if (options.initialState) { | ||
this.innerState = options.initialState; | ||
if (this.innerState.value === CircuitState.Open || | ||
this.innerState.value === CircuitState.HalfOpen) { | ||
this.innerLastFailure = { error: new Errors_1.HydratingCircuitError() }; | ||
let backoff = this.halfOpenAfterBackoffFactory.next({ | ||
attempt: 1, | ||
result: this.innerLastFailure, | ||
signal: abort_1.neverAbortedSignal, | ||
}); | ||
for (let i = 2; i <= this.innerState.attemptNo; i++) { | ||
backoff = backoff.next({ | ||
attempt: i, | ||
result: this.innerLastFailure, | ||
signal: abort_1.neverAbortedSignal, | ||
}); | ||
} | ||
this.innerState.backoff = backoff; | ||
} | ||
} | ||
} | ||
/** | ||
* Gets the current circuit breaker state. | ||
*/ | ||
get state() { | ||
return this.innerState.value; | ||
} | ||
/** | ||
* Gets the last reason the circuit breaker failed. | ||
*/ | ||
get lastFailure() { | ||
return this.innerLastFailure; | ||
} | ||
/** | ||
* Manually holds open the circuit breaker. | ||
@@ -124,3 +149,3 @@ * @returns A handle that keeps the breaker open until `.dispose()` is called. | ||
if (this.options.breaker.failure(state.value)) { | ||
this.open(result); | ||
this.open(result, signal); | ||
} | ||
@@ -136,7 +161,12 @@ } | ||
case CircuitState.Open: | ||
if (Date.now() - state.openedAt < this.options.halfOpenAfter) { | ||
if (Date.now() - state.openedAt < state.backoff.duration) { | ||
throw new Errors_1.BrokenCircuitError(); | ||
} | ||
const test = this.halfOpen(fn, signal); | ||
this.innerState = { value: CircuitState.HalfOpen, test }; | ||
this.innerState = { | ||
value: CircuitState.HalfOpen, | ||
test, | ||
backoff: state.backoff, | ||
attemptNo: state.attemptNo + 1, | ||
}; | ||
this.stateChangeEmitter.emit(CircuitState.HalfOpen); | ||
@@ -150,2 +180,28 @@ return test; | ||
} | ||
/** | ||
* Captures circuit breaker state that can later be used to recreate the | ||
* breaker by passing `state` to the `circuitBreaker` function. This is | ||
* useful in cases like serverless functions where you may want to keep | ||
* the breaker state across multiple executions. | ||
*/ | ||
toJSON() { | ||
const state = this.innerState; | ||
if (state.value === CircuitState.HalfOpen) { | ||
return { | ||
value: CircuitState.Open, | ||
openedAt: 0, | ||
attemptNo: state.attemptNo, | ||
}; | ||
} | ||
else if (state.value === CircuitState.Open) { | ||
return { | ||
value: CircuitState.Open, | ||
openedAt: state.openedAt, | ||
attemptNo: state.attemptNo, | ||
}; | ||
} | ||
else { | ||
return state; | ||
} | ||
} | ||
async halfOpen(fn, signal) { | ||
@@ -162,3 +218,3 @@ this.halfOpenEmitter.emit(); | ||
this.options.breaker.failure(CircuitState.HalfOpen); | ||
this.open(result); | ||
this.open(result, signal); | ||
} | ||
@@ -174,7 +230,12 @@ return (0, Executor_1.returnOrThrow)(result); | ||
} | ||
open(reason) { | ||
open(reason, signal) { | ||
if (this.state === CircuitState.Isolated || this.state === CircuitState.Open) { | ||
return; | ||
} | ||
this.innerState = { value: CircuitState.Open, openedAt: Date.now() }; | ||
const attemptNo = this.innerState.value === CircuitState.HalfOpen ? this.innerState.attemptNo : 1; | ||
const context = { attempt: attemptNo, result: reason, signal }; | ||
const backoff = this.innerState.value === CircuitState.HalfOpen | ||
? this.innerState.backoff.next(context) | ||
: this.halfOpenAfterBackoffFactory.next(context); | ||
this.innerState = { value: CircuitState.Open, openedAt: Date.now(), backoff, attemptNo }; | ||
this.breakEmitter.emit(reason); | ||
@@ -181,0 +242,0 @@ this.stateChangeEmitter.emit(CircuitState.Open); |
@@ -1,2 +0,2 @@ | ||
/// <reference types="node" /> | ||
import { IDisposable } from './Event'; | ||
export declare const neverAbortedSignal: AbortSignal; | ||
@@ -8,2 +8,4 @@ export declare const abortedSignal: AbortSignal; | ||
*/ | ||
export declare const deriveAbortController: (signal?: AbortSignal) => AbortController; | ||
export declare const deriveAbortController: (signal?: AbortSignal) => { | ||
ctrl: AbortController; | ||
} & IDisposable; |
@@ -9,2 +9,3 @@ "use strict"; | ||
exports.abortedSignal = cancelledSrc.signal; | ||
const noop = () => { }; | ||
/** | ||
@@ -16,4 +17,5 @@ * Creates a new AbortController that is aborted when the parent signal aborts. | ||
const ctrl = new AbortController(); | ||
let dispose = noop; | ||
if (!signal) { | ||
return ctrl; | ||
return { ctrl, dispose }; | ||
} | ||
@@ -23,9 +25,10 @@ if (signal.aborted) { | ||
} | ||
if (signal !== exports.neverAbortedSignal) { | ||
const ref = new WeakRef(ctrl); | ||
(0, Event_1.onAbort)(signal)(() => ref.deref()?.abort()); | ||
else { | ||
const abortEvt = (0, Event_1.onAbort)(signal); | ||
abortEvt.event(() => ctrl.abort()); | ||
dispose = abortEvt.dispose; | ||
} | ||
return ctrl; | ||
return { ctrl, dispose }; | ||
}; | ||
exports.deriveAbortController = deriveAbortController; | ||
//# sourceMappingURL=abort.js.map |
@@ -1,2 +0,1 @@ | ||
/// <reference types="node" /> | ||
/** | ||
@@ -8,9 +7,7 @@ * Type that can be disposed. | ||
} | ||
export declare const noopDisposable: { | ||
dispose: () => undefined; | ||
}; | ||
export declare const noopDisposable: IDisposable; | ||
/** | ||
* Function that subscribes the method to receive data. | ||
*/ | ||
export declare type Event<T> = (listener: (data: T) => void) => IDisposable; | ||
export type Event<T> = (listener: (data: T) => void) => IDisposable; | ||
export declare namespace Event { | ||
@@ -28,3 +25,5 @@ /** | ||
/** Creates an Event that fires when the signal is aborted. */ | ||
export declare const onAbort: (signal: AbortSignal) => Event<void>; | ||
export declare const onAbort: (signal: AbortSignal) => { | ||
event: Event<void>; | ||
} & IDisposable; | ||
/** | ||
@@ -31,0 +30,0 @@ * Base event emitter. Calls listeners when data is emitted. |
@@ -40,14 +40,19 @@ "use strict"; | ||
} | ||
const toDispose = []; | ||
return new Promise((resolve, reject) => { | ||
const d1 = (0, exports.onAbort)(signal)(() => { | ||
d2.dispose(); | ||
const abortEvt = (0, exports.onAbort)(signal); | ||
toDispose.push(abortEvt); | ||
toDispose.push(abortEvt.event(() => { | ||
reject(new TaskCancelledError_1.TaskCancelledError()); | ||
}); | ||
const d2 = Event.once(event, data => { | ||
d1.dispose(); | ||
})); | ||
toDispose.push(Event.once(event, data => { | ||
resolve(data); | ||
}); | ||
})); | ||
}).finally(() => { | ||
for (const d of toDispose) { | ||
d.dispose(); | ||
} | ||
}); | ||
}; | ||
})(Event = exports.Event || (exports.Event = {})); | ||
})(Event || (exports.Event = Event = {})); | ||
/** Creates an Event that fires when the signal is aborted. */ | ||
@@ -58,11 +63,12 @@ const onAbort = (signal) => { | ||
evt.emit(); | ||
return evt.addListener; | ||
return { event: evt.addListener, dispose: () => { } }; | ||
} | ||
const dispose = () => signal.removeEventListener('abort', l); | ||
// @types/node is currently missing the event types on AbortSignal | ||
const l = () => { | ||
evt.emit(); | ||
signal.removeEventListener('abort', l); | ||
dispose(); | ||
}; | ||
signal.addEventListener('abort', l); | ||
return evt.addListener; | ||
return { event: evt.addListener, dispose }; | ||
}; | ||
@@ -69,0 +75,0 @@ exports.onAbort = onAbort; |
import { FailureReason, IFailureEvent, ISuccessEvent } from '../Policy'; | ||
export declare type FailureOrSuccess<R> = FailureReason<R> | { | ||
export type FailureOrSuccess<R> = FailureReason<R> | { | ||
success: R; | ||
@@ -4,0 +4,0 @@ }; |
import { BrokenCircuitError } from './BrokenCircuitError'; | ||
import { BulkheadRejectedError } from './BulkheadRejectedError'; | ||
import { HydratingCircuitError } from './HydratingCircuitError'; | ||
import { IsolatedCircuitError } from './IsolatedCircuitError'; | ||
@@ -7,2 +8,3 @@ import { TaskCancelledError } from './TaskCancelledError'; | ||
export * from './BulkheadRejectedError'; | ||
export * from './HydratingCircuitError'; | ||
export * from './IsolatedCircuitError'; | ||
@@ -14,1 +16,2 @@ export * from './TaskCancelledError'; | ||
export declare const isTaskCancelledError: (e: unknown) => e is TaskCancelledError; | ||
export declare const isHydratingCircuitError: (e: unknown) => e is HydratingCircuitError; |
@@ -17,5 +17,6 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.isTaskCancelledError = exports.isIsolatedCircuitError = exports.isBulkheadRejectedError = exports.isBrokenCircuitError = void 0; | ||
exports.isHydratingCircuitError = exports.isTaskCancelledError = exports.isIsolatedCircuitError = exports.isBulkheadRejectedError = exports.isBrokenCircuitError = void 0; | ||
__exportStar(require("./BrokenCircuitError"), exports); | ||
__exportStar(require("./BulkheadRejectedError"), exports); | ||
__exportStar(require("./HydratingCircuitError"), exports); | ||
__exportStar(require("./IsolatedCircuitError"), exports); | ||
@@ -31,2 +32,4 @@ __exportStar(require("./TaskCancelledError"), exports); | ||
exports.isTaskCancelledError = isTaskCancelledError; | ||
const isHydratingCircuitError = (e) => !!e && e instanceof Error && 'isHydratingCircuitError' in e; | ||
exports.isHydratingCircuitError = isHydratingCircuitError; | ||
//# sourceMappingURL=Errors.js.map |
@@ -6,4 +6,3 @@ /** | ||
/** | ||
* Returns the first backoff duration. Can return "undefined" to signal | ||
* that we should not back off. | ||
* Returns the first backoff duration. | ||
*/ | ||
@@ -10,0 +9,0 @@ next(context: T): IBackoff<T>; |
import { IBackoff, IBackoffFactory } from './Backoff'; | ||
export declare type DelegateBackoffFn<T, S = void> = (context: T, state?: S) => { | ||
export type DelegateBackoffFn<T, S = void> = (context: T, state?: S) => { | ||
delay: number; | ||
@@ -4,0 +4,0 @@ state: S; |
@@ -5,3 +5,3 @@ import { IExponentialBackoffOptions } from '../backoff/ExponentialBackoff'; | ||
*/ | ||
export declare type GeneratorFn<S> = (state: S | undefined, options: IExponentialBackoffOptions<S>) => [number, S]; | ||
export type GeneratorFn<S> = (state: S | undefined, options: IExponentialBackoffOptions<S>) => [number, S]; | ||
/** | ||
@@ -8,0 +8,0 @@ * Generator that creates a backoff with no jitter. |
@@ -11,3 +11,3 @@ import { IBackoff, IBackoffFactory } from './Backoff'; | ||
*/ | ||
next(): IBackoff<unknown>; | ||
next(_context: unknown): IBackoff<unknown>; | ||
} |
@@ -11,3 +11,3 @@ export class IterableBackoff { | ||
*/ | ||
next() { | ||
next(_context) { | ||
return instance(this.durations, 0); | ||
@@ -14,0 +14,0 @@ } |
@@ -17,1 +17,2 @@ import { CircuitState } from '../CircuitBreakerPolicy'; | ||
export * from './ConsecutiveBreaker'; | ||
export * from './CountBreaker'; |
export * from './SamplingBreaker'; | ||
export * from './ConsecutiveBreaker'; | ||
export * from './CountBreaker'; | ||
//# sourceMappingURL=Breaker.js.map |
@@ -31,3 +31,3 @@ import { CircuitState } from '../CircuitBreakerPolicy'; | ||
* last `samplingDuration`, so long as there's at least `minimumRps` (to avoid | ||
* closing unnecessarily under low RPS). | ||
* opening unnecessarily under low RPS). | ||
*/ | ||
@@ -34,0 +34,0 @@ constructor({ threshold, duration: samplingDuration, minimumRps }: ISamplingBreakerOptions); |
@@ -6,3 +6,3 @@ import { CircuitState } from '../CircuitBreakerPolicy'; | ||
* last `samplingDuration`, so long as there's at least `minimumRps` (to avoid | ||
* closing unnecessarily under low RPS). | ||
* opening unnecessarily under low RPS). | ||
*/ | ||
@@ -9,0 +9,0 @@ constructor({ threshold, duration: samplingDuration, minimumRps }) { |
@@ -1,2 +0,1 @@ | ||
/// <reference types="node" /> | ||
import { IDefaultPolicyContext, IPolicy } from './Policy'; | ||
@@ -3,0 +2,0 @@ export declare class BulkheadPolicy implements IPolicy { |
@@ -9,2 +9,14 @@ import { neverAbortedSignal } from './common/abort'; | ||
/** | ||
* Returns the number of available execution slots at this point in time. | ||
*/ | ||
get executionSlots() { | ||
return this.capacity - this.active; | ||
} | ||
/** | ||
* Returns the number of queue slots at this point in time. | ||
*/ | ||
get queueSlots() { | ||
return this.queueCapacity - this.queue.length; | ||
} | ||
/** | ||
* Bulkhead limits concurrent requests made. | ||
@@ -33,14 +45,2 @@ */ | ||
/** | ||
* Returns the number of available execution slots at this point in time. | ||
*/ | ||
get executionSlots() { | ||
return this.capacity - this.active; | ||
} | ||
/** | ||
* Returns the number of queue slots at this point in time. | ||
*/ | ||
get queueSlots() { | ||
return this.queueCapacity - this.queue.length; | ||
} | ||
/** | ||
* Executes the given function. | ||
@@ -47,0 +47,0 @@ * @param fn Function to execute |
@@ -1,2 +0,2 @@ | ||
/// <reference types="node" /> | ||
import { IBackoffFactory } from './backoff/Backoff'; | ||
import { IBreaker } from './breaker/Breaker'; | ||
@@ -25,5 +25,30 @@ import { ExecuteWrapper } from './common/Executor'; | ||
} | ||
/** | ||
* Context passed into halfOpenAfter backoff delegate. | ||
*/ | ||
export interface IHalfOpenAfterBackoffContext extends IDefaultPolicyContext { | ||
/** | ||
* The consecutive number of times the circuit has entered the | ||
* {@link CircuitState.Open} state. | ||
*/ | ||
attempt: number; | ||
/** | ||
* The result of the last method call that caused the circuit to enter the | ||
* {@link CircuitState.Open} state. Either a thrown error, or a value that we | ||
* determined should open the circuit. | ||
*/ | ||
result: FailureReason<unknown>; | ||
} | ||
export interface ICircuitBreakerOptions { | ||
breaker: IBreaker; | ||
halfOpenAfter: number; | ||
/** | ||
* When to (potentially) enter the {@link CircuitState.HalfOpen} state from | ||
* the {@link CircuitState.Open} state. Either a duration in milliseconds or a | ||
* backoff factory. | ||
*/ | ||
halfOpenAfter: number | IBackoffFactory<IHalfOpenAfterBackoffContext>; | ||
/** | ||
* Initial state from a previous call to {@link CircuitBreakerPolicy.toJSON}. | ||
*/ | ||
initialState?: unknown; | ||
} | ||
@@ -38,2 +63,3 @@ export declare class CircuitBreakerPolicy implements IPolicy { | ||
private readonly stateChangeEmitter; | ||
private readonly halfOpenAfterBackoffFactory; | ||
private innerLastFailure?; | ||
@@ -93,2 +119,9 @@ private innerState; | ||
execute<T>(fn: (context: IDefaultPolicyContext) => PromiseLike<T> | T, signal?: AbortSignal): Promise<T>; | ||
/** | ||
* Captures circuit breaker state that can later be used to recreate the | ||
* breaker by passing `state` to the `circuitBreaker` function. This is | ||
* useful in cases like serverless functions where you may want to keep | ||
* the breaker state across multiple executions. | ||
*/ | ||
toJSON(): unknown; | ||
private halfOpen; | ||
@@ -95,0 +128,0 @@ private open; |
@@ -0,5 +1,6 @@ | ||
import { ConstantBackoff } from './backoff/Backoff'; | ||
import { neverAbortedSignal } from './common/abort'; | ||
import { EventEmitter } from './common/Event'; | ||
import { returnOrThrow } from './common/Executor'; | ||
import { BrokenCircuitError, TaskCancelledError } from './errors/Errors'; | ||
import { BrokenCircuitError, HydratingCircuitError, TaskCancelledError } from './errors/Errors'; | ||
import { IsolatedCircuitError } from './errors/IsolatedCircuitError'; | ||
@@ -28,2 +29,14 @@ export var CircuitState; | ||
export class CircuitBreakerPolicy { | ||
/** | ||
* Gets the current circuit breaker state. | ||
*/ | ||
get state() { | ||
return this.innerState.value; | ||
} | ||
/** | ||
* Gets the last reason the circuit breaker failed. | ||
*/ | ||
get lastFailure() { | ||
return this.innerLastFailure; | ||
} | ||
constructor(options, executor) { | ||
@@ -62,16 +75,28 @@ this.options = options; | ||
this.onFailure = this.executor.onFailure; | ||
this.halfOpenAfterBackoffFactory = | ||
typeof options.halfOpenAfter === 'number' | ||
? new ConstantBackoff(options.halfOpenAfter) | ||
: options.halfOpenAfter; | ||
if (options.initialState) { | ||
this.innerState = options.initialState; | ||
if (this.innerState.value === CircuitState.Open || | ||
this.innerState.value === CircuitState.HalfOpen) { | ||
this.innerLastFailure = { error: new HydratingCircuitError() }; | ||
let backoff = this.halfOpenAfterBackoffFactory.next({ | ||
attempt: 1, | ||
result: this.innerLastFailure, | ||
signal: neverAbortedSignal, | ||
}); | ||
for (let i = 2; i <= this.innerState.attemptNo; i++) { | ||
backoff = backoff.next({ | ||
attempt: i, | ||
result: this.innerLastFailure, | ||
signal: neverAbortedSignal, | ||
}); | ||
} | ||
this.innerState.backoff = backoff; | ||
} | ||
} | ||
} | ||
/** | ||
* Gets the current circuit breaker state. | ||
*/ | ||
get state() { | ||
return this.innerState.value; | ||
} | ||
/** | ||
* Gets the last reason the circuit breaker failed. | ||
*/ | ||
get lastFailure() { | ||
return this.innerLastFailure; | ||
} | ||
/** | ||
* Manually holds open the circuit breaker. | ||
@@ -121,3 +146,3 @@ * @returns A handle that keeps the breaker open until `.dispose()` is called. | ||
if (this.options.breaker.failure(state.value)) { | ||
this.open(result); | ||
this.open(result, signal); | ||
} | ||
@@ -133,7 +158,12 @@ } | ||
case CircuitState.Open: | ||
if (Date.now() - state.openedAt < this.options.halfOpenAfter) { | ||
if (Date.now() - state.openedAt < state.backoff.duration) { | ||
throw new BrokenCircuitError(); | ||
} | ||
const test = this.halfOpen(fn, signal); | ||
this.innerState = { value: CircuitState.HalfOpen, test }; | ||
this.innerState = { | ||
value: CircuitState.HalfOpen, | ||
test, | ||
backoff: state.backoff, | ||
attemptNo: state.attemptNo + 1, | ||
}; | ||
this.stateChangeEmitter.emit(CircuitState.HalfOpen); | ||
@@ -147,2 +177,28 @@ return test; | ||
} | ||
/** | ||
* Captures circuit breaker state that can later be used to recreate the | ||
* breaker by passing `state` to the `circuitBreaker` function. This is | ||
* useful in cases like serverless functions where you may want to keep | ||
* the breaker state across multiple executions. | ||
*/ | ||
toJSON() { | ||
const state = this.innerState; | ||
if (state.value === CircuitState.HalfOpen) { | ||
return { | ||
value: CircuitState.Open, | ||
openedAt: 0, | ||
attemptNo: state.attemptNo, | ||
}; | ||
} | ||
else if (state.value === CircuitState.Open) { | ||
return { | ||
value: CircuitState.Open, | ||
openedAt: state.openedAt, | ||
attemptNo: state.attemptNo, | ||
}; | ||
} | ||
else { | ||
return state; | ||
} | ||
} | ||
async halfOpen(fn, signal) { | ||
@@ -159,3 +215,3 @@ this.halfOpenEmitter.emit(); | ||
this.options.breaker.failure(CircuitState.HalfOpen); | ||
this.open(result); | ||
this.open(result, signal); | ||
} | ||
@@ -171,7 +227,12 @@ return returnOrThrow(result); | ||
} | ||
open(reason) { | ||
open(reason, signal) { | ||
if (this.state === CircuitState.Isolated || this.state === CircuitState.Open) { | ||
return; | ||
} | ||
this.innerState = { value: CircuitState.Open, openedAt: Date.now() }; | ||
const attemptNo = this.innerState.value === CircuitState.HalfOpen ? this.innerState.attemptNo : 1; | ||
const context = { attempt: attemptNo, result: reason, signal }; | ||
const backoff = this.innerState.value === CircuitState.HalfOpen | ||
? this.innerState.backoff.next(context) | ||
: this.halfOpenAfterBackoffFactory.next(context); | ||
this.innerState = { value: CircuitState.Open, openedAt: Date.now(), backoff, attemptNo }; | ||
this.breakEmitter.emit(reason); | ||
@@ -178,0 +239,0 @@ this.stateChangeEmitter.emit(CircuitState.Open); |
@@ -1,2 +0,2 @@ | ||
/// <reference types="node" /> | ||
import { IDisposable } from './Event'; | ||
export declare const neverAbortedSignal: AbortSignal; | ||
@@ -8,2 +8,4 @@ export declare const abortedSignal: AbortSignal; | ||
*/ | ||
export declare const deriveAbortController: (signal?: AbortSignal) => AbortController; | ||
export declare const deriveAbortController: (signal?: AbortSignal) => { | ||
ctrl: AbortController; | ||
} & IDisposable; |
@@ -6,2 +6,3 @@ import { onAbort } from './Event'; | ||
export const abortedSignal = cancelledSrc.signal; | ||
const noop = () => { }; | ||
/** | ||
@@ -13,4 +14,5 @@ * Creates a new AbortController that is aborted when the parent signal aborts. | ||
const ctrl = new AbortController(); | ||
let dispose = noop; | ||
if (!signal) { | ||
return ctrl; | ||
return { ctrl, dispose }; | ||
} | ||
@@ -20,8 +22,9 @@ if (signal.aborted) { | ||
} | ||
if (signal !== neverAbortedSignal) { | ||
const ref = new WeakRef(ctrl); | ||
onAbort(signal)(() => ref.deref()?.abort()); | ||
else { | ||
const abortEvt = onAbort(signal); | ||
abortEvt.event(() => ctrl.abort()); | ||
dispose = abortEvt.dispose; | ||
} | ||
return ctrl; | ||
return { ctrl, dispose }; | ||
}; | ||
//# sourceMappingURL=abort.js.map |
@@ -1,2 +0,1 @@ | ||
/// <reference types="node" /> | ||
/** | ||
@@ -8,9 +7,7 @@ * Type that can be disposed. | ||
} | ||
export declare const noopDisposable: { | ||
dispose: () => undefined; | ||
}; | ||
export declare const noopDisposable: IDisposable; | ||
/** | ||
* Function that subscribes the method to receive data. | ||
*/ | ||
export declare type Event<T> = (listener: (data: T) => void) => IDisposable; | ||
export type Event<T> = (listener: (data: T) => void) => IDisposable; | ||
export declare namespace Event { | ||
@@ -28,3 +25,5 @@ /** | ||
/** Creates an Event that fires when the signal is aborted. */ | ||
export declare const onAbort: (signal: AbortSignal) => Event<void>; | ||
export declare const onAbort: (signal: AbortSignal) => { | ||
event: Event<void>; | ||
} & IDisposable; | ||
/** | ||
@@ -31,0 +30,0 @@ * Base event emitter. Calls listeners when data is emitted. |
@@ -37,11 +37,16 @@ import { TaskCancelledError } from '../errors/TaskCancelledError'; | ||
} | ||
const toDispose = []; | ||
return new Promise((resolve, reject) => { | ||
const d1 = onAbort(signal)(() => { | ||
d2.dispose(); | ||
const abortEvt = onAbort(signal); | ||
toDispose.push(abortEvt); | ||
toDispose.push(abortEvt.event(() => { | ||
reject(new TaskCancelledError()); | ||
}); | ||
const d2 = Event.once(event, data => { | ||
d1.dispose(); | ||
})); | ||
toDispose.push(Event.once(event, data => { | ||
resolve(data); | ||
}); | ||
})); | ||
}).finally(() => { | ||
for (const d of toDispose) { | ||
d.dispose(); | ||
} | ||
}); | ||
@@ -55,11 +60,12 @@ }; | ||
evt.emit(); | ||
return evt.addListener; | ||
return { event: evt.addListener, dispose: () => { } }; | ||
} | ||
const dispose = () => signal.removeEventListener('abort', l); | ||
// @types/node is currently missing the event types on AbortSignal | ||
const l = () => { | ||
evt.emit(); | ||
signal.removeEventListener('abort', l); | ||
dispose(); | ||
}; | ||
signal.addEventListener('abort', l); | ||
return evt.addListener; | ||
return { event: evt.addListener, dispose }; | ||
}; | ||
@@ -66,0 +72,0 @@ /** |
import { FailureReason, IFailureEvent, ISuccessEvent } from '../Policy'; | ||
export declare type FailureOrSuccess<R> = FailureReason<R> | { | ||
export type FailureOrSuccess<R> = FailureReason<R> | { | ||
success: R; | ||
@@ -4,0 +4,0 @@ }; |
import { BrokenCircuitError } from './BrokenCircuitError'; | ||
import { BulkheadRejectedError } from './BulkheadRejectedError'; | ||
import { HydratingCircuitError } from './HydratingCircuitError'; | ||
import { IsolatedCircuitError } from './IsolatedCircuitError'; | ||
@@ -7,2 +8,3 @@ import { TaskCancelledError } from './TaskCancelledError'; | ||
export * from './BulkheadRejectedError'; | ||
export * from './HydratingCircuitError'; | ||
export * from './IsolatedCircuitError'; | ||
@@ -14,1 +16,2 @@ export * from './TaskCancelledError'; | ||
export declare const isTaskCancelledError: (e: unknown) => e is TaskCancelledError; | ||
export declare const isHydratingCircuitError: (e: unknown) => e is HydratingCircuitError; |
export * from './BrokenCircuitError'; | ||
export * from './BulkheadRejectedError'; | ||
export * from './HydratingCircuitError'; | ||
export * from './IsolatedCircuitError'; | ||
@@ -9,2 +10,3 @@ export * from './TaskCancelledError'; | ||
export const isTaskCancelledError = (e) => !!e && e instanceof Error && 'isBulkheadRejectedError' in e; | ||
export const isHydratingCircuitError = (e) => !!e && e instanceof Error && 'isHydratingCircuitError' in e; | ||
//# sourceMappingURL=Errors.js.map |
@@ -1,2 +0,1 @@ | ||
/// <reference types="node" /> | ||
import { ExecuteWrapper } from './common/Executor'; | ||
@@ -3,0 +2,0 @@ import { IDefaultPolicyContext, IPolicy } from './Policy'; |
@@ -1,2 +0,1 @@ | ||
/// <reference types="node" /> | ||
import { IDefaultPolicyContext, IPolicy } from './Policy'; | ||
@@ -3,0 +2,0 @@ /** |
@@ -1,6 +0,4 @@ | ||
/// <reference types="node" /> | ||
import { IBackoffFactory } from './backoff/Backoff'; | ||
import { IBreaker } from './breaker/Breaker'; | ||
import { BulkheadPolicy } from './BulkheadPolicy'; | ||
import { CircuitBreakerPolicy } from './CircuitBreakerPolicy'; | ||
import { CircuitBreakerPolicy, ICircuitBreakerOptions } from './CircuitBreakerPolicy'; | ||
import { Event } from './common/Event'; | ||
@@ -11,3 +9,3 @@ import { FallbackPolicy } from './FallbackPolicy'; | ||
import { TimeoutPolicy, TimeoutStrategy } from './TimeoutPolicy'; | ||
declare type Constructor<T> = new (...args: any) => T; | ||
type Constructor<T> = new (...args: any) => T; | ||
export interface IBasePolicyOptions { | ||
@@ -21,3 +19,3 @@ errorFilter: (error: Error) => boolean; | ||
*/ | ||
export declare type FailureReason<ReturnType> = { | ||
export type FailureReason<ReturnType> = { | ||
error: Error; | ||
@@ -89,3 +87,3 @@ } | { | ||
} | ||
declare type MergePolicies<A, B> = A extends IPolicy<infer A1, any> ? B extends IPolicy<infer B1, any> ? IMergedPolicy<A1 & B1, A['_altReturn'] | B['_altReturn'], B extends IMergedPolicy<any, any, infer W> ? [A, ...W] : [A, B]> : never : never; | ||
type MergePolicies<A, B> = A extends IPolicy<infer A1, any> ? B extends IPolicy<infer B1, any> ? IMergedPolicy<A1 & B1, A['_altReturn'] | B['_altReturn'], B extends IMergedPolicy<any, any, infer W> ? [A, ...W] : [A, B]> : never : never; | ||
export declare class Policy { | ||
@@ -307,6 +305,3 @@ readonly options: Readonly<IBasePolicyOptions>; | ||
*/ | ||
export declare function circuitBreaker(policy: Policy, opts: { | ||
halfOpenAfter: number; | ||
breaker: IBreaker; | ||
}): CircuitBreakerPolicy; | ||
export declare function circuitBreaker(policy: Policy, opts: ICircuitBreakerOptions): CircuitBreakerPolicy; | ||
/** | ||
@@ -313,0 +308,0 @@ * Falls back to the given value in the event of an error. |
@@ -1,2 +0,1 @@ | ||
/// <reference types="node" /> | ||
import { IBackoffFactory } from './backoff/Backoff'; | ||
@@ -53,2 +52,3 @@ import { ExecuteWrapper } from './common/Executor'; | ||
delay: number; | ||
attempt: number; | ||
}>; | ||
@@ -55,0 +55,0 @@ /** |
@@ -63,3 +63,3 @@ import { ConstantBackoff } from './backoff/ConstantBackoff'; | ||
// when we get an emission in our tests. | ||
this.onRetryEmitter.emit({ ...result, delay: delayDuration }); | ||
this.onRetryEmitter.emit({ ...result, delay: delayDuration, attempt: retries + 1 }); | ||
await delayPromise; | ||
@@ -66,0 +66,0 @@ continue; |
@@ -1,2 +0,1 @@ | ||
/// <reference types="node" /> | ||
import { Event } from './common/Event'; | ||
@@ -3,0 +2,0 @@ import { ExecuteWrapper } from './common/Executor'; |
@@ -54,3 +54,3 @@ import { deriveAbortController } from './common/abort'; | ||
async execute(fn, signal) { | ||
const aborter = deriveAbortController(signal); | ||
const { ctrl: aborter, dispose: disposeAbort } = deriveAbortController(signal); | ||
const timer = setTimeout(() => aborter.abort(), this.duration); | ||
@@ -62,3 +62,3 @@ if (this.unref) { | ||
const onceAborted = onAbort(aborter.signal); | ||
const onCancelledListener = onceAborted(() => this.timeoutEmitter.emit()); | ||
const onCancelledListener = onceAborted.event(() => this.timeoutEmitter.emit()); | ||
try { | ||
@@ -71,3 +71,3 @@ if (this.options.strategy === TimeoutStrategy.Cooperative) { | ||
Promise.resolve(fn(context, aborter.signal)), | ||
Event.toPromise(onceAborted).then(() => { | ||
Event.toPromise(onceAborted.event).then(() => { | ||
throw new TaskCancelledError(`Operation timed out after ${this.duration}ms`); | ||
@@ -80,2 +80,3 @@ }), | ||
onCancelledListener.dispose(); | ||
onceAborted.dispose(); | ||
if (this.options.abortOnReturn !== false) { | ||
@@ -85,2 +86,3 @@ aborter.abort(); | ||
clearTimeout(timer); | ||
disposeAbort(); | ||
} | ||
@@ -87,0 +89,0 @@ } |
@@ -1,2 +0,1 @@ | ||
/// <reference types="node" /> | ||
import { ExecuteWrapper } from './common/Executor'; | ||
@@ -3,0 +2,0 @@ import { IDefaultPolicyContext, IPolicy } from './Policy'; |
@@ -1,2 +0,1 @@ | ||
/// <reference types="node" /> | ||
import { IDefaultPolicyContext, IPolicy } from './Policy'; | ||
@@ -3,0 +2,0 @@ /** |
@@ -1,6 +0,4 @@ | ||
/// <reference types="node" /> | ||
import { IBackoffFactory } from './backoff/Backoff'; | ||
import { IBreaker } from './breaker/Breaker'; | ||
import { BulkheadPolicy } from './BulkheadPolicy'; | ||
import { CircuitBreakerPolicy } from './CircuitBreakerPolicy'; | ||
import { CircuitBreakerPolicy, ICircuitBreakerOptions } from './CircuitBreakerPolicy'; | ||
import { Event } from './common/Event'; | ||
@@ -11,3 +9,3 @@ import { FallbackPolicy } from './FallbackPolicy'; | ||
import { TimeoutPolicy, TimeoutStrategy } from './TimeoutPolicy'; | ||
declare type Constructor<T> = new (...args: any) => T; | ||
type Constructor<T> = new (...args: any) => T; | ||
export interface IBasePolicyOptions { | ||
@@ -21,3 +19,3 @@ errorFilter: (error: Error) => boolean; | ||
*/ | ||
export declare type FailureReason<ReturnType> = { | ||
export type FailureReason<ReturnType> = { | ||
error: Error; | ||
@@ -89,3 +87,3 @@ } | { | ||
} | ||
declare type MergePolicies<A, B> = A extends IPolicy<infer A1, any> ? B extends IPolicy<infer B1, any> ? IMergedPolicy<A1 & B1, A['_altReturn'] | B['_altReturn'], B extends IMergedPolicy<any, any, infer W> ? [A, ...W] : [A, B]> : never : never; | ||
type MergePolicies<A, B> = A extends IPolicy<infer A1, any> ? B extends IPolicy<infer B1, any> ? IMergedPolicy<A1 & B1, A['_altReturn'] | B['_altReturn'], B extends IMergedPolicy<any, any, infer W> ? [A, ...W] : [A, B]> : never : never; | ||
export declare class Policy { | ||
@@ -307,6 +305,3 @@ readonly options: Readonly<IBasePolicyOptions>; | ||
*/ | ||
export declare function circuitBreaker(policy: Policy, opts: { | ||
halfOpenAfter: number; | ||
breaker: IBreaker; | ||
}): CircuitBreakerPolicy; | ||
export declare function circuitBreaker(policy: Policy, opts: ICircuitBreakerOptions): CircuitBreakerPolicy; | ||
/** | ||
@@ -313,0 +308,0 @@ * Falls back to the given value in the event of an error. |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.fallback = exports.circuitBreaker = exports.retry = exports.wrap = exports.timeout = exports.usePolicy = exports.bulkhead = exports.handleWhenResult = exports.handleResultType = exports.handleWhen = exports.handleType = exports.handleAll = exports.noop = exports.Policy = void 0; | ||
exports.handleAll = exports.noop = exports.Policy = void 0; | ||
exports.handleType = handleType; | ||
exports.handleWhen = handleWhen; | ||
exports.handleResultType = handleResultType; | ||
exports.handleWhenResult = handleWhenResult; | ||
exports.bulkhead = bulkhead; | ||
exports.usePolicy = usePolicy; | ||
exports.timeout = timeout; | ||
exports.wrap = wrap; | ||
exports.retry = retry; | ||
exports.circuitBreaker = circuitBreaker; | ||
exports.fallback = fallback; | ||
const Backoff_1 = require("./backoff/Backoff"); | ||
@@ -127,3 +138,2 @@ const BulkheadPolicy_1 = require("./BulkheadPolicy"); | ||
} | ||
exports.handleType = handleType; | ||
/** | ||
@@ -135,3 +145,2 @@ * See {@link Policy.orWhen} for usage. | ||
} | ||
exports.handleWhen = handleWhen; | ||
/** | ||
@@ -143,3 +152,2 @@ * See {@link Policy.orResultType} for usage. | ||
} | ||
exports.handleResultType = handleResultType; | ||
/** | ||
@@ -151,3 +159,2 @@ * See {@link Policy.orWhenResult} for usage. | ||
} | ||
exports.handleWhenResult = handleWhenResult; | ||
/** | ||
@@ -159,3 +166,2 @@ * Creates a bulkhead--a policy that limits the number of concurrent calls. | ||
} | ||
exports.bulkhead = bulkhead; | ||
/** | ||
@@ -199,3 +205,2 @@ * A decorator that can be used to wrap class methods and apply the given | ||
} | ||
exports.usePolicy = usePolicy; | ||
/** | ||
@@ -214,3 +219,2 @@ * Creates a timeout policy. | ||
} | ||
exports.timeout = timeout; | ||
function wrap(...p) { | ||
@@ -230,3 +234,2 @@ return { | ||
} | ||
exports.wrap = wrap; | ||
/** | ||
@@ -254,3 +257,2 @@ * Creates a retry policy. The options should contain the backoff strategy to | ||
} | ||
exports.retry = retry; | ||
/** | ||
@@ -282,3 +284,2 @@ * Returns a circuit breaker for the policy. **Important**: you should share | ||
} | ||
exports.circuitBreaker = circuitBreaker; | ||
/** | ||
@@ -309,3 +310,2 @@ * Falls back to the given value in the event of an error. | ||
} | ||
exports.fallback = fallback; | ||
//# sourceMappingURL=Policy.js.map |
@@ -1,2 +0,1 @@ | ||
/// <reference types="node" /> | ||
import { IBackoffFactory } from './backoff/Backoff'; | ||
@@ -53,2 +52,3 @@ import { ExecuteWrapper } from './common/Executor'; | ||
delay: number; | ||
attempt: number; | ||
}>; | ||
@@ -55,0 +55,0 @@ /** |
@@ -66,3 +66,3 @@ "use strict"; | ||
// when we get an emission in our tests. | ||
this.onRetryEmitter.emit({ ...result, delay: delayDuration }); | ||
this.onRetryEmitter.emit({ ...result, delay: delayDuration, attempt: retries + 1 }); | ||
await delayPromise; | ||
@@ -69,0 +69,0 @@ continue; |
@@ -1,2 +0,1 @@ | ||
/// <reference types="node" /> | ||
import { Event } from './common/Event'; | ||
@@ -3,0 +2,0 @@ import { ExecuteWrapper } from './common/Executor'; |
@@ -19,3 +19,3 @@ "use strict"; | ||
TimeoutStrategy["Aggressive"] = "aggressive"; | ||
})(TimeoutStrategy = exports.TimeoutStrategy || (exports.TimeoutStrategy = {})); | ||
})(TimeoutStrategy || (exports.TimeoutStrategy = TimeoutStrategy = {})); | ||
class TimeoutPolicy { | ||
@@ -58,3 +58,3 @@ constructor(duration, options, executor = new Executor_1.ExecuteWrapper(), unref = false) { | ||
async execute(fn, signal) { | ||
const aborter = (0, abort_1.deriveAbortController)(signal); | ||
const { ctrl: aborter, dispose: disposeAbort } = (0, abort_1.deriveAbortController)(signal); | ||
const timer = setTimeout(() => aborter.abort(), this.duration); | ||
@@ -66,3 +66,3 @@ if (this.unref) { | ||
const onceAborted = (0, Event_1.onAbort)(aborter.signal); | ||
const onCancelledListener = onceAborted(() => this.timeoutEmitter.emit()); | ||
const onCancelledListener = onceAborted.event(() => this.timeoutEmitter.emit()); | ||
try { | ||
@@ -75,3 +75,3 @@ if (this.options.strategy === TimeoutStrategy.Cooperative) { | ||
Promise.resolve(fn(context, aborter.signal)), | ||
Event_1.Event.toPromise(onceAborted).then(() => { | ||
Event_1.Event.toPromise(onceAborted.event).then(() => { | ||
throw new TaskCancelledError_1.TaskCancelledError(`Operation timed out after ${this.duration}ms`); | ||
@@ -84,2 +84,3 @@ }), | ||
onCancelledListener.dispose(); | ||
onceAborted.dispose(); | ||
if (this.options.abortOnReturn !== false) { | ||
@@ -89,2 +90,3 @@ aborter.abort(); | ||
clearTimeout(timer); | ||
disposeAbort(); | ||
} | ||
@@ -91,0 +93,0 @@ } |
{ | ||
"name": "cockatiel", | ||
"version": "3.1.3", | ||
"version": "3.2.0", | ||
"description": "A resilience and transient-fault-handling library that allows developers to express policies such as Backoff, Retry, Circuit Breaker, Timeout, Bulkhead Isolation, and Fallback in a fluent and thread-safe manner. Inspired by .NET Polly.", | ||
@@ -75,3 +75,3 @@ "main": "dist/index.js", | ||
"nyc": "^15.1.0", | ||
"prettier": "^2.6.2", | ||
"prettier": "^3.3.3", | ||
"remark-cli": "^10.0.1", | ||
@@ -84,3 +84,3 @@ "remark-toc": "^8.0.1", | ||
"source-map-support": "^0.5.21", | ||
"typescript": "^4.7.4" | ||
"typescript": "^5.5.3" | ||
}, | ||
@@ -87,0 +87,0 @@ "prettier": { |
@@ -75,5 +75,6 @@ # Cockatiel | ||
- [`retry.onGiveUp(callback)`](#retryongiveupcallback) | ||
- [`circuitBreaker(policy, { halfOpenAfter, breaker })`](#circuitbreakerpolicy--halfopenafter-breaker-) | ||
- [`circuitBreaker(policy, { halfOpenAfter, breaker[, initialState] })`](#circuitbreakerpolicy--halfopenafter-breaker-initialstate-) | ||
- [Breakers](#breakers) | ||
- [`ConsecutiveBreaker`](#consecutivebreaker) | ||
- [`CountBreaker`](#countbreaker) | ||
- [`SamplingBreaker`](#samplingbreaker) | ||
@@ -89,2 +90,3 @@ - [`breaker.execute(fn[, signal])`](#breakerexecutefn-signal) | ||
- [`breaker.isolate()`](#breakerisolate) | ||
- [`breaker.toJSON()`](#breakertojson) | ||
- [`timeout(duration, strategy)`](#timeoutduration-strategy) | ||
@@ -385,3 +387,3 @@ - [`timeout.dangerouslyUnref()`](#timeoutdangerouslyunref) | ||
> Tip: exponential backoffs and [circuit breakers](#circuitbreakerpolicy--halfopenafter-breaker-) are great friends! | ||
> Tip: exponential backoffs and [circuit breakers](#circuitbreakerpolicy--halfopenafter-breaker-initialstate-) are great friends! | ||
@@ -519,6 +521,7 @@ The crowd favorite. By default, it uses a decorrelated jitter algorithm, which is a good default for most applications. Takes in an options object, which can have any of these properties: | ||
- the `delay` we're going to wait before retrying, and; | ||
- the `delay` we're going to wait before retrying, | ||
- the `attempt` number of the upcoming retry, starting at `1`, and; | ||
- either a thrown error like `{ error: someError, delay: number }`, or an errorful result in an object like `{ value: someValue, delay: number }` when using [result filtering](#handleresulttypector-filter--policyorresulttypector-filter). | ||
Useful for telemetry. Returns a dispable instance. | ||
Useful for telemetry. Returns a disposable instance. | ||
@@ -563,3 +566,3 @@ ```js | ||
An [event emitter](#events) that fires when we're no longer retrying a call and are giving up. It's invoked with either a thrown error in an object like `{ error: someError }`, or an errorful result in an object like `{ value: someValue }` when using [result filtering](#handleresulttypector-filter--policyorresulttypector-filter). Useful for telemetry. Returns a dispable instance. | ||
An [event emitter](#events) that fires when we're no longer retrying a call and are giving up. It's invoked with either a thrown error in an object like `{ error: someError }`, or an errorful result in an object like `{ value: someValue }` when using [result filtering](#handleresulttypector-filter--policyorresulttypector-filter). Useful for telemetry. Returns a disposable instance. | ||
@@ -572,3 +575,3 @@ ```js | ||
## `circuitBreaker(policy, { halfOpenAfter, breaker })` | ||
## `circuitBreaker(policy, { halfOpenAfter, breaker[, initialState] })` | ||
@@ -579,4 +582,12 @@ Circuit breakers stop execution for a period of time after a failure threshold has been reached. This is very useful to allow faulting systems to recover without overloading them. See the [Polly docs](https://github.com/App-vNext/Polly/wiki/Circuit-Breaker#how-the-polly-circuitbreaker-works) for more detailed information around circuit breakers. | ||
To create a breaker, you use a [Policy](#Policy) like you normally would, and call `.circuitBreaker()`. The first argument is the number of milliseconds after which we should try to close the circuit after failure ('closing the circuit' means restarting requests). The second argument is the breaker policy. | ||
To create a breaker, you use a [Policy](#Policy) like you normally would, and call `circuitBreaker()`. | ||
- The `halfOpenAfter` option is the number of milliseconds after which we should try to close the circuit after failure ('closing the circuit' means restarting requests). | ||
You may also pass a backoff strategy instead of a constant number of milliseconds if you wish to increase the interval between consecutive failing half-open checks. | ||
- The `breaker` is the [breaker policy](#breakers) which controls when the circuit opens. | ||
- The `initialState` option can be passed if you're hydrating the breaker from state collectiond from previous execution using [breaker.toJSON()](#breakertojson). | ||
Calls to `execute()` while the circuit is open (not taking requests) will throw a `BrokenCircuitError`. | ||
@@ -591,2 +602,3 @@ | ||
SamplingBreaker, | ||
ExponentialBackoff, | ||
} from 'cockatiel'; | ||
@@ -600,5 +612,5 @@ | ||
// Break if more than 5 requests in a row fail: | ||
// Break if more than 5 requests in a row fail, and use a backoff for retry attempts: | ||
const breaker = circuitBreaker(handleAll, { | ||
halfOpenAfter: 10 * 1000, | ||
halfOpenAfter: new ExponentialBackoff(), | ||
breaker: new ConsecutiveBreaker(5), | ||
@@ -635,2 +647,27 @@ }); | ||
#### `CountBreaker` | ||
The `CountBreaker` breaks after a proportion of requests in a count based sliding window fail. It is inspired by the [Count-based sliding window in Resilience4j](https://resilience4j.readme.io/docs/circuitbreaker#count-based-sliding-window). | ||
```js | ||
// Break if more than 20% of requests fail in a sliding window of size 100: | ||
const breaker = circuitBreaker(handleAll, { | ||
halfOpenAfter: 10 * 1000, | ||
breaker: new CountBreaker({ threshold: 0.2, size: 100 }), | ||
}); | ||
``` | ||
You can specify a minimum minimum-number-of-calls value to use, to avoid opening the circuit when there are only few samples in the sliding window. By default this value is set to the sliding window size, but you can override it if necessary: | ||
```js | ||
const breaker = circuitBreaker(handleAll, { | ||
halfOpenAfter: 10 * 1000, | ||
breaker: new CountBreaker({ | ||
threshold: 0.2, | ||
size: 100, | ||
minimumNumberOfCalls: 50, // require 50 requests before we can break | ||
}), | ||
}); | ||
``` | ||
#### `SamplingBreaker` | ||
@@ -648,3 +685,3 @@ | ||
You can specify a minimum requests-per-second value to use to avoid closing the circuit under period of low load. By default we'll choose a value such that you need 5 failures per second for the breaker to kick in, and you can configure this if it doesn't work for you: | ||
You can specify a minimum requests-per-second value to use to avoid opening the circuit under periods of low load. By default we'll choose a value such that you need 5 failures per second for the breaker to kick in, and you can configure this if it doesn't work for you: | ||
@@ -770,2 +807,20 @@ ```js | ||
### `breaker.toJSON()` | ||
Returns the circuit breaker state so that it can be re-created later. This is useful in cases like serverless functions where you may want to keep the breaker state across multiple executions. | ||
```js | ||
const breakerState = breaker.toJSON(); | ||
// ...in a later execution | ||
const breaker = circuitBreaker(policy, { | ||
halfOpenAfter: 1000, | ||
breaker: new ConsecutiveBreaker(3), | ||
initialState: breakerState, | ||
}); | ||
``` | ||
Note that if the breaker is currently half open, the serialized state will record it in such a way that it's open when restored and will use the first call as the half-open test. | ||
## `timeout(duration, strategy)` | ||
@@ -772,0 +827,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
458406
177
5412
1037