Comparing version 0.1.5 to 1.0.0
# Changelog | ||
## 0.1.5 - 2019-03-01 | ||
## 1.0.0 - 2020-06-16 | ||
- **breaking:** Node versions <10 are no longer supported. | ||
- **breaking:** `FallbackPolicy.onFallback` is replaced with `FallbackPolicy.onFailure`. When a failure happens, a fallback will occur. | ||
- **feat**: add `isBrokenCircuitError`, `isBulkheadRejectedError`, `isIsolatedCircuitError`, `isTaskCancelledError` methods to the errors and matching predicate functions. | ||
- **feat**: all policies now include `onFailure` and `onSuccess` callbacks for monitoring purposes (see [#20](https://github.com/connor4312/cockatiel/issues/20)) | ||
- **fix**: add `onHalfOpen` event to the circuit breaker (see [#18](https://github.com/connor4312/cockatiel/issues/18)) | ||
- **fix**: `retry.exponential()` requiring an argument when it should have been optional (see [#18](https://github.com/connor4312/cockatiel/issues/18)) | ||
## 0.1.5 - 2020-03-01 | ||
- **feat**: add `.dangerouslyUnref` methods for timeouts and retries ([#11](https://github.com/connor4312/cockatiel/issues/11), thanks to [@novemberborn](https://github.com/novemberborn)) | ||
## 0.1.4 - 2019-02-24 | ||
## 0.1.4 - 2020-02-24 | ||
@@ -12,3 +21,3 @@ - **fix**: `Timeout.Aggressive` triggering timeouts immediately ([#16](https://github.com/connor4312/cockatiel/issues/16), thanks to [@ekillops](https://github.com/ekillops)) | ||
## 0.1.3 - 2019-01-26 | ||
## 0.1.3 - 2020-01-26 | ||
@@ -15,0 +24,0 @@ - **feat**: add new `Policy.use()` decorator |
@@ -9,5 +9,14 @@ import { IPolicy } from './Policy'; | ||
private active; | ||
private queue; | ||
private onRejectEmitter; | ||
private readonly queue; | ||
private readonly onRejectEmitter; | ||
private readonly executor; | ||
/** | ||
* @inheritdoc | ||
*/ | ||
readonly onSuccess: import("./common/Event").Event<import("./Policy").ISuccessEvent>; | ||
/** | ||
* @inheritdoc | ||
*/ | ||
readonly onFailure: import("./common/Event").Event<import("./Policy").IFailureEvent>; | ||
/** | ||
* Emitter that fires when an item is rejected from the bulkhead. | ||
@@ -14,0 +23,0 @@ */ |
@@ -5,2 +5,3 @@ "use strict"; | ||
const Event_1 = require("./common/Event"); | ||
const Executor_1 = require("./common/Executor"); | ||
const BulkheadRejectedError_1 = require("./errors/BulkheadRejectedError"); | ||
@@ -17,3 +18,14 @@ /** | ||
this.onRejectEmitter = new Event_1.EventEmitter(); | ||
this.executor = new Executor_1.ExecuteWrapper(); | ||
/** | ||
* @inheritdoc | ||
*/ | ||
// tslint:disable-next-line: member-ordering | ||
this.onSuccess = this.executor.onSuccess; | ||
/** | ||
* @inheritdoc | ||
*/ | ||
// tslint:disable-next-line: member-ordering | ||
this.onFailure = this.executor.onFailure; | ||
/** | ||
* Emitter that fires when an item is rejected from the bulkhead. | ||
@@ -20,0 +32,0 @@ */ |
import { IBreaker } from './breaker/Breaker'; | ||
import { IBasePolicyOptions, IPolicy } from './Policy'; | ||
import { ExecuteWrapper } from './common/Executor'; | ||
import { IPolicy } from './Policy'; | ||
export declare enum CircuitState { | ||
@@ -23,3 +24,3 @@ /** | ||
} | ||
export interface ICircuitBreakerOptions extends IBasePolicyOptions { | ||
export interface ICircuitBreakerOptions { | ||
breaker: IBreaker; | ||
@@ -30,4 +31,7 @@ halfOpenAfter: number; | ||
private readonly options; | ||
private readonly executor; | ||
private readonly breakEmitter; | ||
private readonly resetEmitter; | ||
private readonly halfOpenEmitter; | ||
private readonly stateChangeEmitter; | ||
private innerLastFailure?; | ||
@@ -50,2 +54,19 @@ private innerState; | ||
/** | ||
* Event emitted when the circuit breaker is half open (running a test call). | ||
* Either `onBreak` on `onReset` will subsequently fire. | ||
*/ | ||
readonly onHalfOpen: import("./common/Event").Event<void>; | ||
/** | ||
* Fired whenever the circuit breaker state changes. | ||
*/ | ||
readonly onStateChange: import("./common/Event").Event<CircuitState>; | ||
/** | ||
* @inheritdoc | ||
*/ | ||
readonly onSuccess: import("./common/Event").Event<import("./Policy").ISuccessEvent>; | ||
/** | ||
* @inheritdoc | ||
*/ | ||
readonly onFailure: import("./common/Event").Event<import("./Policy").IFailureEvent>; | ||
/** | ||
* Gets the current circuit breaker state. | ||
@@ -62,3 +83,3 @@ */ | ||
} | undefined; | ||
constructor(options: ICircuitBreakerOptions); | ||
constructor(options: ICircuitBreakerOptions, executor: ExecuteWrapper); | ||
/** | ||
@@ -65,0 +86,0 @@ * Manually holds open the circuit breaker. |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const Event_1 = require("./common/Event"); | ||
const execute_1 = require("./common/execute"); | ||
const Executor_1 = require("./common/Executor"); | ||
const Errors_1 = require("./errors/Errors"); | ||
@@ -29,6 +29,9 @@ const IsolatedCircuitError_1 = require("./errors/IsolatedCircuitError"); | ||
class CircuitBreakerPolicy { | ||
constructor(options) { | ||
constructor(options, executor) { | ||
this.options = options; | ||
this.executor = executor; | ||
this.breakEmitter = new Event_1.EventEmitter(); | ||
this.resetEmitter = new Event_1.EventEmitter(); | ||
this.halfOpenEmitter = new Event_1.EventEmitter(); | ||
this.stateChangeEmitter = new Event_1.EventEmitter(); | ||
this.innerState = { value: CircuitState.Closed }; | ||
@@ -45,2 +48,23 @@ /** | ||
this.onReset = this.resetEmitter.addListener; | ||
/** | ||
* Event emitted when the circuit breaker is half open (running a test call). | ||
* Either `onBreak` on `onReset` will subsequently fire. | ||
*/ | ||
// tslint:disable-next-line: member-ordering | ||
this.onHalfOpen = this.halfOpenEmitter.addListener; | ||
/** | ||
* Fired whenever the circuit breaker state changes. | ||
*/ | ||
// tslint:disable-next-line: member-ordering | ||
this.onStateChange = this.stateChangeEmitter.addListener; | ||
/** | ||
* @inheritdoc | ||
*/ | ||
// tslint:disable-next-line: member-ordering | ||
this.onSuccess = this.executor.onSuccess; | ||
/** | ||
* @inheritdoc | ||
*/ | ||
// tslint:disable-next-line: member-ordering | ||
this.onFailure = this.executor.onFailure; | ||
} | ||
@@ -67,2 +91,3 @@ /** | ||
this.breakEmitter.emit({ isolated: true }); | ||
this.stateChangeEmitter.emit(CircuitState.Isolated); | ||
} | ||
@@ -80,2 +105,3 @@ this.innerState.counters++; | ||
this.resetEmitter.emit(); | ||
this.stateChangeEmitter.emit(CircuitState.Closed); | ||
} | ||
@@ -97,3 +123,3 @@ }, | ||
case CircuitState.Closed: | ||
const result = await execute_1.execute(this.options, fn); | ||
const result = await this.executor.invoke(fn); | ||
if ('success' in result) { | ||
@@ -108,3 +134,3 @@ this.options.breaker.success(state.value); | ||
} | ||
return execute_1.returnOrThrow(result); | ||
return Executor_1.returnOrThrow(result); | ||
case CircuitState.HalfOpen: | ||
@@ -119,2 +145,3 @@ await state.test.catch(() => undefined); | ||
this.innerState = { value: CircuitState.HalfOpen, test }; | ||
this.stateChangeEmitter.emit(CircuitState.HalfOpen); | ||
return test; | ||
@@ -128,4 +155,5 @@ case CircuitState.Isolated: | ||
async halfOpen(fn) { | ||
this.halfOpenEmitter.emit(); | ||
try { | ||
const result = await execute_1.execute(this.options, fn); | ||
const result = await this.executor.invoke(fn); | ||
if ('success' in result) { | ||
@@ -140,3 +168,3 @@ this.options.breaker.success(CircuitState.HalfOpen); | ||
} | ||
return execute_1.returnOrThrow(result); | ||
return Executor_1.returnOrThrow(result); | ||
} | ||
@@ -156,2 +184,3 @@ catch (err) { | ||
this.breakEmitter.emit(reason); | ||
this.stateChangeEmitter.emit(CircuitState.Open); | ||
} | ||
@@ -162,2 +191,3 @@ close() { | ||
this.resetEmitter.emit(); | ||
this.stateChangeEmitter.emit(CircuitState.Closed); | ||
} | ||
@@ -164,0 +194,0 @@ } |
@@ -19,2 +19,3 @@ "use strict"; | ||
let onReset; | ||
let onHalfOpen; | ||
beforeEach(() => { | ||
@@ -25,4 +26,6 @@ p = Policy_1.Policy.handleType(MyException).circuitBreaker(1000, new Breaker_1.ConsecutiveBreaker(2)); | ||
onReset = sinon_1.stub(); | ||
onHalfOpen = sinon_1.stub(); | ||
p.onBreak(onBreak); | ||
p.onReset(onReset); | ||
p.onHalfOpen(onHalfOpen); | ||
}); | ||
@@ -59,2 +62,3 @@ afterEach(() => { | ||
chai_1.expect(p.state).to.equal(CircuitBreakerPolicy_1.CircuitState.HalfOpen); | ||
chai_1.expect(onHalfOpen).calledOnce; | ||
chai_1.expect(await result).to.equal(42); | ||
@@ -61,0 +65,0 @@ chai_1.expect(p.state).to.equal(CircuitBreakerPolicy_1.CircuitState.Closed); |
@@ -36,2 +36,6 @@ import { CancellationToken } from '../CancellationToken'; | ||
/** | ||
* Gets the number of event listeners. | ||
*/ | ||
get size(): number; | ||
/** | ||
* Emits event data. | ||
@@ -38,0 +42,0 @@ */ |
@@ -64,2 +64,8 @@ "use strict"; | ||
/** | ||
* Gets the number of event listeners. | ||
*/ | ||
get size() { | ||
return this.listeners.size; | ||
} | ||
/** | ||
* Emits event data. | ||
@@ -66,0 +72,0 @@ */ |
@@ -6,3 +6,4 @@ /** | ||
export declare class BrokenCircuitError extends Error { | ||
readonly isBrokenCircuitError = true; | ||
constructor(message?: string); | ||
} |
@@ -10,2 +10,3 @@ "use strict"; | ||
super(message); | ||
this.isBrokenCircuitError = true; | ||
} | ||
@@ -12,0 +13,0 @@ } |
export declare class BulkheadRejectedError extends Error { | ||
readonly isBulkheadRejectedError = true; | ||
constructor(executionSlots: number, queueSlots: number); | ||
} |
@@ -6,2 +6,3 @@ "use strict"; | ||
super(`Bulkhead capacity exceeded (0/${executionSlots} execution slots, 0/${queueSlots} available)`); | ||
this.isBulkheadRejectedError = true; | ||
} | ||
@@ -8,0 +9,0 @@ } |
@@ -0,1 +1,5 @@ | ||
import { BrokenCircuitError } from './BrokenCircuitError'; | ||
import { BulkheadRejectedError } from './BulkheadRejectedError'; | ||
import { IsolatedCircuitError } from './IsolatedCircuitError'; | ||
import { TaskCancelledError } from './TaskCancelledError'; | ||
export * from './BrokenCircuitError'; | ||
@@ -5,1 +9,5 @@ export * from './BulkheadRejectedError'; | ||
export * from './TaskCancelledError'; | ||
export declare const isBrokenCircuitError: (e: unknown) => e is BrokenCircuitError; | ||
export declare const isBulkheadRejectedError: (e: unknown) => e is BulkheadRejectedError; | ||
export declare const isIsolatedCircuitError: (e: unknown) => e is IsolatedCircuitError; | ||
export declare const isTaskCancelledError: (e: unknown) => e is TaskCancelledError; |
@@ -10,2 +10,6 @@ "use strict"; | ||
__export(require("./TaskCancelledError")); | ||
exports.isBrokenCircuitError = (e) => !!e && e instanceof Error && 'isBrokenCircuitError' in e; | ||
exports.isBulkheadRejectedError = (e) => !!e && e instanceof Error && 'isBulkheadRejectedError' in e; | ||
exports.isIsolatedCircuitError = (e) => !!e && e instanceof Error && 'isBulkheadRejectedError' in e; | ||
exports.isTaskCancelledError = (e) => !!e && e instanceof Error && 'isBulkheadRejectedError' in e; | ||
//# sourceMappingURL=Errors.js.map |
@@ -7,3 +7,4 @@ import { BrokenCircuitError } from './BrokenCircuitError'; | ||
export declare class IsolatedCircuitError extends BrokenCircuitError { | ||
readonly isIsolatedCircuitError = true; | ||
constructor(); | ||
} |
@@ -11,2 +11,3 @@ "use strict"; | ||
super(`Execution prevented because the circuit breaker is open`); | ||
this.isIsolatedCircuitError = true; | ||
} | ||
@@ -13,0 +14,0 @@ } |
@@ -6,3 +6,4 @@ /** | ||
readonly message: string; | ||
readonly isTaskCancelledError = true; | ||
constructor(message?: string); | ||
} |
@@ -10,2 +10,3 @@ "use strict"; | ||
this.message = message; | ||
this.isTaskCancelledError = true; | ||
} | ||
@@ -12,0 +13,0 @@ } |
@@ -9,5 +9,14 @@ import { IPolicy } from './Policy'; | ||
private active; | ||
private queue; | ||
private onRejectEmitter; | ||
private readonly queue; | ||
private readonly onRejectEmitter; | ||
private readonly executor; | ||
/** | ||
* @inheritdoc | ||
*/ | ||
readonly onSuccess: import("./common/Event").Event<import("./Policy").ISuccessEvent>; | ||
/** | ||
* @inheritdoc | ||
*/ | ||
readonly onFailure: import("./common/Event").Event<import("./Policy").IFailureEvent>; | ||
/** | ||
* Emitter that fires when an item is rejected from the bulkhead. | ||
@@ -14,0 +23,0 @@ */ |
import { defer } from './common/defer'; | ||
import { EventEmitter } from './common/Event'; | ||
import { ExecuteWrapper } from './common/Executor'; | ||
import { BulkheadRejectedError } from './errors/BulkheadRejectedError'; | ||
@@ -14,3 +15,14 @@ /** | ||
this.onRejectEmitter = new EventEmitter(); | ||
this.executor = new ExecuteWrapper(); | ||
/** | ||
* @inheritdoc | ||
*/ | ||
// tslint:disable-next-line: member-ordering | ||
this.onSuccess = this.executor.onSuccess; | ||
/** | ||
* @inheritdoc | ||
*/ | ||
// tslint:disable-next-line: member-ordering | ||
this.onFailure = this.executor.onFailure; | ||
/** | ||
* Emitter that fires when an item is rejected from the bulkhead. | ||
@@ -17,0 +29,0 @@ */ |
import { IBreaker } from './breaker/Breaker'; | ||
import { IBasePolicyOptions, IPolicy } from './Policy'; | ||
import { ExecuteWrapper } from './common/Executor'; | ||
import { IPolicy } from './Policy'; | ||
export declare enum CircuitState { | ||
@@ -23,3 +24,3 @@ /** | ||
} | ||
export interface ICircuitBreakerOptions extends IBasePolicyOptions { | ||
export interface ICircuitBreakerOptions { | ||
breaker: IBreaker; | ||
@@ -30,4 +31,7 @@ halfOpenAfter: number; | ||
private readonly options; | ||
private readonly executor; | ||
private readonly breakEmitter; | ||
private readonly resetEmitter; | ||
private readonly halfOpenEmitter; | ||
private readonly stateChangeEmitter; | ||
private innerLastFailure?; | ||
@@ -50,2 +54,19 @@ private innerState; | ||
/** | ||
* Event emitted when the circuit breaker is half open (running a test call). | ||
* Either `onBreak` on `onReset` will subsequently fire. | ||
*/ | ||
readonly onHalfOpen: import("./common/Event").Event<void>; | ||
/** | ||
* Fired whenever the circuit breaker state changes. | ||
*/ | ||
readonly onStateChange: import("./common/Event").Event<CircuitState>; | ||
/** | ||
* @inheritdoc | ||
*/ | ||
readonly onSuccess: import("./common/Event").Event<import("./Policy").ISuccessEvent>; | ||
/** | ||
* @inheritdoc | ||
*/ | ||
readonly onFailure: import("./common/Event").Event<import("./Policy").IFailureEvent>; | ||
/** | ||
* Gets the current circuit breaker state. | ||
@@ -62,3 +83,3 @@ */ | ||
} | undefined; | ||
constructor(options: ICircuitBreakerOptions); | ||
constructor(options: ICircuitBreakerOptions, executor: ExecuteWrapper); | ||
/** | ||
@@ -65,0 +86,0 @@ * Manually holds open the circuit breaker. |
import { EventEmitter } from './common/Event'; | ||
import { execute, returnOrThrow } from './common/execute'; | ||
import { returnOrThrow } from './common/Executor'; | ||
import { BrokenCircuitError } from './errors/Errors'; | ||
@@ -27,6 +27,9 @@ import { IsolatedCircuitError } from './errors/IsolatedCircuitError'; | ||
export class CircuitBreakerPolicy { | ||
constructor(options) { | ||
constructor(options, executor) { | ||
this.options = options; | ||
this.executor = executor; | ||
this.breakEmitter = new EventEmitter(); | ||
this.resetEmitter = new EventEmitter(); | ||
this.halfOpenEmitter = new EventEmitter(); | ||
this.stateChangeEmitter = new EventEmitter(); | ||
this.innerState = { value: CircuitState.Closed }; | ||
@@ -43,2 +46,23 @@ /** | ||
this.onReset = this.resetEmitter.addListener; | ||
/** | ||
* Event emitted when the circuit breaker is half open (running a test call). | ||
* Either `onBreak` on `onReset` will subsequently fire. | ||
*/ | ||
// tslint:disable-next-line: member-ordering | ||
this.onHalfOpen = this.halfOpenEmitter.addListener; | ||
/** | ||
* Fired whenever the circuit breaker state changes. | ||
*/ | ||
// tslint:disable-next-line: member-ordering | ||
this.onStateChange = this.stateChangeEmitter.addListener; | ||
/** | ||
* @inheritdoc | ||
*/ | ||
// tslint:disable-next-line: member-ordering | ||
this.onSuccess = this.executor.onSuccess; | ||
/** | ||
* @inheritdoc | ||
*/ | ||
// tslint:disable-next-line: member-ordering | ||
this.onFailure = this.executor.onFailure; | ||
} | ||
@@ -65,2 +89,3 @@ /** | ||
this.breakEmitter.emit({ isolated: true }); | ||
this.stateChangeEmitter.emit(CircuitState.Isolated); | ||
} | ||
@@ -78,2 +103,3 @@ this.innerState.counters++; | ||
this.resetEmitter.emit(); | ||
this.stateChangeEmitter.emit(CircuitState.Closed); | ||
} | ||
@@ -95,3 +121,3 @@ }, | ||
case CircuitState.Closed: | ||
const result = await execute(this.options, fn); | ||
const result = await this.executor.invoke(fn); | ||
if ('success' in result) { | ||
@@ -116,2 +142,3 @@ this.options.breaker.success(state.value); | ||
this.innerState = { value: CircuitState.HalfOpen, test }; | ||
this.stateChangeEmitter.emit(CircuitState.HalfOpen); | ||
return test; | ||
@@ -125,4 +152,5 @@ case CircuitState.Isolated: | ||
async halfOpen(fn) { | ||
this.halfOpenEmitter.emit(); | ||
try { | ||
const result = await execute(this.options, fn); | ||
const result = await this.executor.invoke(fn); | ||
if ('success' in result) { | ||
@@ -152,2 +180,3 @@ this.options.breaker.success(CircuitState.HalfOpen); | ||
this.breakEmitter.emit(reason); | ||
this.stateChangeEmitter.emit(CircuitState.Open); | ||
} | ||
@@ -158,2 +187,3 @@ close() { | ||
this.resetEmitter.emit(); | ||
this.stateChangeEmitter.emit(CircuitState.Closed); | ||
} | ||
@@ -160,0 +190,0 @@ } |
@@ -17,2 +17,3 @@ import { expect } from 'chai'; | ||
let onReset; | ||
let onHalfOpen; | ||
beforeEach(() => { | ||
@@ -23,4 +24,6 @@ p = Policy.handleType(MyException).circuitBreaker(1000, new ConsecutiveBreaker(2)); | ||
onReset = stub(); | ||
onHalfOpen = stub(); | ||
p.onBreak(onBreak); | ||
p.onReset(onReset); | ||
p.onHalfOpen(onHalfOpen); | ||
}); | ||
@@ -57,2 +60,3 @@ afterEach(() => { | ||
expect(p.state).to.equal(CircuitState.HalfOpen); | ||
expect(onHalfOpen).calledOnce; | ||
expect(await result).to.equal(42); | ||
@@ -59,0 +63,0 @@ expect(p.state).to.equal(CircuitState.Closed); |
@@ -36,2 +36,6 @@ import { CancellationToken } from '../CancellationToken'; | ||
/** | ||
* Gets the number of event listeners. | ||
*/ | ||
get size(): number; | ||
/** | ||
* Emits event data. | ||
@@ -38,0 +42,0 @@ */ |
@@ -62,2 +62,8 @@ import { TaskCancelledError } from '../errors/TaskCancelledError'; | ||
/** | ||
* Gets the number of event listeners. | ||
*/ | ||
get size() { | ||
return this.listeners.size; | ||
} | ||
/** | ||
* Emits event data. | ||
@@ -64,0 +70,0 @@ */ |
@@ -6,3 +6,4 @@ /** | ||
export declare class BrokenCircuitError extends Error { | ||
readonly isBrokenCircuitError = true; | ||
constructor(message?: string); | ||
} |
@@ -8,4 +8,5 @@ /** | ||
super(message); | ||
this.isBrokenCircuitError = true; | ||
} | ||
} | ||
//# sourceMappingURL=BrokenCircuitError.js.map |
export declare class BulkheadRejectedError extends Error { | ||
readonly isBulkheadRejectedError = true; | ||
constructor(executionSlots: number, queueSlots: number); | ||
} |
export class BulkheadRejectedError extends Error { | ||
constructor(executionSlots, queueSlots) { | ||
super(`Bulkhead capacity exceeded (0/${executionSlots} execution slots, 0/${queueSlots} available)`); | ||
this.isBulkheadRejectedError = true; | ||
} | ||
} | ||
//# sourceMappingURL=BulkheadRejectedError.js.map |
@@ -0,1 +1,5 @@ | ||
import { BrokenCircuitError } from './BrokenCircuitError'; | ||
import { BulkheadRejectedError } from './BulkheadRejectedError'; | ||
import { IsolatedCircuitError } from './IsolatedCircuitError'; | ||
import { TaskCancelledError } from './TaskCancelledError'; | ||
export * from './BrokenCircuitError'; | ||
@@ -5,1 +9,5 @@ export * from './BulkheadRejectedError'; | ||
export * from './TaskCancelledError'; | ||
export declare const isBrokenCircuitError: (e: unknown) => e is BrokenCircuitError; | ||
export declare const isBulkheadRejectedError: (e: unknown) => e is BulkheadRejectedError; | ||
export declare const isIsolatedCircuitError: (e: unknown) => e is IsolatedCircuitError; | ||
export declare const isTaskCancelledError: (e: unknown) => e is TaskCancelledError; |
@@ -5,2 +5,6 @@ export * from './BrokenCircuitError'; | ||
export * from './TaskCancelledError'; | ||
export const isBrokenCircuitError = (e) => !!e && e instanceof Error && 'isBrokenCircuitError' in e; | ||
export const isBulkheadRejectedError = (e) => !!e && e instanceof Error && 'isBulkheadRejectedError' in e; | ||
export const isIsolatedCircuitError = (e) => !!e && e instanceof Error && 'isBulkheadRejectedError' in e; | ||
export const isTaskCancelledError = (e) => !!e && e instanceof Error && 'isBulkheadRejectedError' in e; | ||
//# sourceMappingURL=Errors.js.map |
@@ -7,3 +7,4 @@ import { BrokenCircuitError } from './BrokenCircuitError'; | ||
export declare class IsolatedCircuitError extends BrokenCircuitError { | ||
readonly isIsolatedCircuitError = true; | ||
constructor(); | ||
} |
@@ -9,4 +9,5 @@ import { BrokenCircuitError } from './BrokenCircuitError'; | ||
super(`Execution prevented because the circuit breaker is open`); | ||
this.isIsolatedCircuitError = true; | ||
} | ||
} | ||
//# sourceMappingURL=IsolatedCircuitError.js.map |
@@ -6,3 +6,4 @@ /** | ||
readonly message: string; | ||
readonly isTaskCancelledError = true; | ||
constructor(message?: string); | ||
} |
@@ -8,4 +8,5 @@ /** | ||
this.message = message; | ||
this.isTaskCancelledError = true; | ||
} | ||
} | ||
//# sourceMappingURL=TaskCancelledError.js.map |
@@ -1,12 +0,16 @@ | ||
import { FailureReason, IBasePolicyOptions, IPolicy } from './Policy'; | ||
import { ExecuteWrapper } from './common/Executor'; | ||
import { IPolicy } from './Policy'; | ||
export declare class FallbackPolicy<AltReturn> implements IPolicy<void, AltReturn> { | ||
private readonly options; | ||
private readonly executor; | ||
private readonly value; | ||
private readonly fallbackEmitter; | ||
/** | ||
* Event that fires when a fallback happens. | ||
* @inheritdoc | ||
*/ | ||
readonly onFallback: import("./common/Event").Event<FailureReason<unknown>>; | ||
constructor(options: IBasePolicyOptions, value: () => AltReturn); | ||
readonly onSuccess: import(".").Event<import("./Policy").ISuccessEvent>; | ||
/** | ||
* @inheritdoc | ||
*/ | ||
readonly onFailure: import(".").Event<import("./Policy").IFailureEvent>; | ||
constructor(executor: ExecuteWrapper, value: () => AltReturn); | ||
/** | ||
* Executes the given function. | ||
@@ -13,0 +17,0 @@ * @param fn -- Function to execute. |
@@ -1,13 +0,15 @@ | ||
import { EventEmitter } from './common/Event'; | ||
import { execute } from './common/execute'; | ||
export class FallbackPolicy { | ||
constructor(options, value) { | ||
this.options = options; | ||
constructor(executor, value) { | ||
this.executor = executor; | ||
this.value = value; | ||
this.fallbackEmitter = new EventEmitter(); | ||
/** | ||
* Event that fires when a fallback happens. | ||
* @inheritdoc | ||
*/ | ||
// tslint:disable-next-line: member-ordering | ||
this.onFallback = this.fallbackEmitter.addListener; | ||
this.onSuccess = this.executor.onSuccess; | ||
/** | ||
* @inheritdoc | ||
*/ | ||
// tslint:disable-next-line: member-ordering | ||
this.onFailure = this.executor.onFailure; | ||
} | ||
@@ -20,7 +22,6 @@ /** | ||
async execute(fn) { | ||
const result = await execute(this.options, fn); | ||
const result = await this.executor.invoke(fn); | ||
if ('success' in result) { | ||
return result.success; | ||
} | ||
this.fallbackEmitter.emit(result); | ||
return this.value(); | ||
@@ -27,0 +28,0 @@ } |
@@ -12,5 +12,6 @@ import { expect } from 'chai'; | ||
it('returns a fallback and emits an error if necessary', async () => { | ||
var _a; | ||
const policy = await Policy.handleAll().fallback('error'); | ||
const onFallback = stub(); | ||
policy.onFallback(onFallback); | ||
policy.onFailure(onFallback); | ||
const error = new Error('oh no!'); | ||
@@ -21,5 +22,9 @@ const result = await policy.execute(() => { | ||
expect(result).to.equal('error'); | ||
expect(onFallback).calledWith({ error }); | ||
expect(onFallback).calledWith({ | ||
reason: { error }, | ||
handled: true, | ||
duration: (_a = onFallback.args[0]) === null || _a === void 0 ? void 0 : _a[0].duration, | ||
}); | ||
}); | ||
}); | ||
//# sourceMappingURL=FallbackPolicy.test.js.map |
@@ -12,1 +12,2 @@ export { Event, EventEmitter } from './common/Event'; | ||
export * from './TimeoutPolicy'; | ||
export * from './NoopPolicy'; |
@@ -12,2 +12,3 @@ export { Event, EventEmitter } from './common/Event'; | ||
export * from './TimeoutPolicy'; | ||
export * from './NoopPolicy'; | ||
//# sourceMappingURL=index.js.map |
import { IBreaker } from './breaker/Breaker'; | ||
import { BulkheadPolicy } from './BulkheadPolicy'; | ||
import { CircuitBreakerPolicy } from './CircuitBreakerPolicy'; | ||
import { Event } from './common/Event'; | ||
import { FallbackPolicy } from './FallbackPolicy'; | ||
@@ -22,2 +23,28 @@ import { IRetryContext, RetryPolicy } from './RetryPolicy'; | ||
/** | ||
* Event emitted on the `onFailure` calls. | ||
*/ | ||
export interface IFailureEvent { | ||
/** | ||
* Call duration, in milliseconds (with nanosecond precision, as the OS allows). | ||
*/ | ||
duration: number; | ||
/** | ||
* Whether the error was handled by the policy. | ||
*/ | ||
handled: boolean; | ||
/** | ||
* The reason for the error. | ||
*/ | ||
reason: FailureReason<unknown>; | ||
} | ||
/** | ||
* Event emitted on the `onSuccess` calls. | ||
*/ | ||
export interface ISuccessEvent { | ||
/** | ||
* Call duration, in milliseconds (with nanosecond precision, as the OS allows). | ||
*/ | ||
duration: number; | ||
} | ||
/** | ||
* IPolicy is the type of all policies that Cockatiel provides. It describes | ||
@@ -27,2 +54,13 @@ * an execute() function which takes a generic argument. | ||
export interface IPolicy<ContextType, AltReturn = never> { | ||
/** | ||
* Fires on the policy when a request successfully completes and some | ||
* successful value will be returned. In a retry policy, this is fired once | ||
* even if the request took multiple retries to succeed. | ||
*/ | ||
readonly onSuccess: Event<ISuccessEvent>; | ||
/** | ||
* Fires on the policy when a request successfully fails and will throw a | ||
* rejection to the user. | ||
*/ | ||
readonly onFailure: Event<IFailureEvent>; | ||
execute<T>(fn: (context: ContextType) => PromiseLike<T> | T): Promise<T | AltReturn>; | ||
@@ -29,0 +67,0 @@ } |
import { BulkheadPolicy } from './BulkheadPolicy'; | ||
import { CircuitBreakerPolicy } from './CircuitBreakerPolicy'; | ||
import { ExecuteWrapper } from './common/Executor'; | ||
import { FallbackPolicy } from './FallbackPolicy'; | ||
import { NoopPolicy } from './NoopPolicy'; | ||
import { RetryPolicy } from './RetryPolicy'; | ||
@@ -19,2 +21,4 @@ import { TimeoutPolicy } from './TimeoutPolicy'; | ||
return { | ||
onFailure: p[0].onFailure, | ||
onSuccess: p[0].onSuccess, | ||
execute(fn) { | ||
@@ -208,6 +212,3 @@ const run = (context, i) => i === p.length ? fn(context) : p[i].execute(next => run({ ...context, ...next }, i + 1)); | ||
retry() { | ||
return new RetryPolicy({ | ||
errorFilter: this.options.errorFilter, | ||
resultFilter: this.options.resultFilter, | ||
}); | ||
return new RetryPolicy({}, new ExecuteWrapper(this.options.errorFilter, this.options.resultFilter)); | ||
} | ||
@@ -239,6 +240,5 @@ /** | ||
return new CircuitBreakerPolicy({ | ||
...this.options, | ||
breaker, | ||
halfOpenAfter, | ||
}); | ||
}, new ExecuteWrapper(this.options.errorFilter, this.options.resultFilter)); | ||
} | ||
@@ -264,3 +264,3 @@ /** | ||
fallback(valueOrFactory) { | ||
return new FallbackPolicy(this.options, | ||
return new FallbackPolicy(new ExecuteWrapper(this.options.errorFilter, this.options.resultFilter), | ||
// not technically type-safe, since if they actually want to _return_ | ||
@@ -275,3 +275,3 @@ // a function, that gets lost here. We'll just advice in the docs to | ||
*/ | ||
Policy.noop = { execute: async (fn) => fn(undefined) }; | ||
Policy.noop = new NoopPolicy(); | ||
//# sourceMappingURL=Policy.js.map |
import { IBackoff, IExponentialBackoffOptions } from './backoff/Backoff'; | ||
import { DelegateBackoffFn } from './backoff/DelegateBackoff'; | ||
import { FailureReason, IBasePolicyOptions, IPolicy } from './Policy'; | ||
import { ExecuteWrapper } from './common/Executor'; | ||
import { FailureReason, IPolicy } from './Policy'; | ||
/** | ||
@@ -23,3 +24,3 @@ * Context passed into the execute method of the builder. | ||
} | ||
export interface IRetryPolicyConfig extends IBasePolicyOptions { | ||
export interface IRetryPolicyConfig { | ||
backoff?: IBackoff<IRetryBackoffContext<unknown>>; | ||
@@ -34,5 +35,14 @@ /** | ||
private options; | ||
private onRetryEmitter; | ||
private onGiveUpEmitter; | ||
private readonly executor; | ||
private readonly onGiveUpEmitter; | ||
private readonly onRetryEmitter; | ||
/** | ||
* @inheritdoc | ||
*/ | ||
readonly onSuccess: import("./common/Event").Event<import("./Policy").ISuccessEvent>; | ||
/** | ||
* @inheritdoc | ||
*/ | ||
readonly onFailure: import("./common/Event").Event<import("./Policy").IFailureEvent>; | ||
/** | ||
* Emitter that fires when we retry a call, before any backoff. | ||
@@ -51,6 +61,6 @@ * | ||
/** | ||
* Emitter that fires when we retry a call. | ||
* @deprecated use `onFailure` instead | ||
*/ | ||
readonly onGiveUp: import("./common/Event").Event<FailureReason<unknown>>; | ||
constructor(options: Readonly<IRetryPolicyConfig>); | ||
constructor(options: Readonly<IRetryPolicyConfig>, executor: ExecuteWrapper); | ||
/** | ||
@@ -73,3 +83,3 @@ * Sets the number of retry attempts for the function. | ||
*/ | ||
exponential<S>(options: Partial<IExponentialBackoffOptions<S>>): RetryPolicy; | ||
exponential<S>(options?: Partial<IExponentialBackoffOptions<S>>): RetryPolicy; | ||
/** | ||
@@ -76,0 +86,0 @@ * Sets the baackoff to use for retries. |
@@ -7,3 +7,2 @@ import { ExponentialBackoff } from './backoff/Backoff'; | ||
import { EventEmitter } from './common/Event'; | ||
import { execute } from './common/execute'; | ||
const delay = (duration, unref) => new Promise(resolve => { | ||
@@ -16,7 +15,18 @@ const timer = setTimeout(resolve, duration); | ||
export class RetryPolicy { | ||
constructor(options) { | ||
constructor(options, executor) { | ||
this.options = options; | ||
this.executor = executor; | ||
this.onGiveUpEmitter = new EventEmitter(); | ||
this.onRetryEmitter = new EventEmitter(); | ||
this.onGiveUpEmitter = new EventEmitter(); | ||
/** | ||
* @inheritdoc | ||
*/ | ||
// tslint:disable-next-line: member-ordering | ||
this.onSuccess = this.executor.onSuccess; | ||
/** | ||
* @inheritdoc | ||
*/ | ||
// tslint:disable-next-line: member-ordering | ||
this.onFailure = this.executor.onFailure; | ||
/** | ||
* Emitter that fires when we retry a call, before any backoff. | ||
@@ -28,3 +38,3 @@ * | ||
/** | ||
* Emitter that fires when we retry a call. | ||
* @deprecated use `onFailure` instead | ||
*/ | ||
@@ -57,3 +67,3 @@ // tslint:disable-next-line: member-ordering | ||
*/ | ||
exponential(options) { | ||
exponential(options = {}) { | ||
return this.composeBackoff('b', new ExponentialBackoff(options)); | ||
@@ -84,3 +94,3 @@ } | ||
for (let retries = 0;; retries++) { | ||
const result = await execute(this.options, fn, { attempt: retries }); | ||
const result = await this.executor.invoke(fn, { attempt: retries }); | ||
if ('success' in result) { | ||
@@ -113,4 +123,3 @@ return result.success; | ||
derivePolicy(newOptions) { | ||
const p = new RetryPolicy(newOptions); | ||
p.onGiveUp(evt => this.onGiveUpEmitter.emit(evt)); | ||
const p = new RetryPolicy(newOptions, this.executor.derive()); | ||
p.onRetry(evt => this.onRetryEmitter.emit(evt)); | ||
@@ -117,0 +126,0 @@ return p; |
import { CancellationToken } from './CancellationToken'; | ||
import { ExecuteWrapper } from './common/Executor'; | ||
import { IPolicy } from './Policy'; | ||
@@ -20,10 +21,19 @@ export declare enum TimeoutStrategy { | ||
private readonly strategy; | ||
private readonly executor; | ||
private readonly unref; | ||
private readonly timeoutEmitter; | ||
/** | ||
* Event that fires when a function times out. | ||
* @inheritdoc | ||
*/ | ||
readonly onTimeout: import("./common/Event").Event<void>; | ||
constructor(duration: number, strategy: TimeoutStrategy, unref?: boolean); | ||
/** | ||
* @inheritdoc | ||
*/ | ||
readonly onFailure: import("./common/Event").Event<import("./Policy").IFailureEvent>; | ||
/** | ||
* @inheritdoc | ||
*/ | ||
readonly onSuccess: import("./common/Event").Event<import("./Policy").ISuccessEvent>; | ||
constructor(duration: number, strategy: TimeoutStrategy, executor?: ExecuteWrapper, unref?: boolean); | ||
/** | ||
* When timing out, a referenced timer is created. This means the Node.js | ||
@@ -30,0 +40,0 @@ * event loop is kept active while we're waiting for the timeout, as long as |
import { CancellationTokenSource } from './CancellationToken'; | ||
import { EventEmitter } from './common/Event'; | ||
import { ExecuteWrapper, returnOrThrow } from './common/Executor'; | ||
import { TaskCancelledError } from './errors/TaskCancelledError'; | ||
@@ -17,12 +18,23 @@ export var TimeoutStrategy; | ||
export class TimeoutPolicy { | ||
constructor(duration, strategy, unref = false) { | ||
constructor(duration, strategy, executor = new ExecuteWrapper(), unref = false) { | ||
this.duration = duration; | ||
this.strategy = strategy; | ||
this.executor = executor; | ||
this.unref = unref; | ||
this.timeoutEmitter = new EventEmitter(); | ||
/** | ||
* Event that fires when a function times out. | ||
* @inheritdoc | ||
*/ | ||
// tslint:disable-next-line: member-ordering | ||
this.onTimeout = this.timeoutEmitter.addListener; | ||
/** | ||
* @inheritdoc | ||
*/ | ||
// tslint:disable-next-line: member-ordering | ||
this.onFailure = this.executor.onFailure; | ||
/** | ||
* @inheritdoc | ||
*/ | ||
// tslint:disable-next-line: member-ordering | ||
this.onSuccess = this.executor.onSuccess; | ||
} | ||
@@ -37,4 +49,3 @@ /** | ||
dangerouslyUnref() { | ||
const t = new TimeoutPolicy(this.duration, this.strategy, true); | ||
t.onTimeout(() => this.timeoutEmitter.emit()); | ||
const t = new TimeoutPolicy(this.duration, this.strategy, this.executor, true); | ||
return t; | ||
@@ -55,7 +66,8 @@ } | ||
if (this.strategy === TimeoutStrategy.Cooperative) { | ||
return await fn({ cancellation: cts.token }); | ||
return returnOrThrow(await this.executor.invoke(fn, { cancellation: cts.token })); | ||
} | ||
return await Promise.race([ | ||
fn({ cancellation: cts.token }), | ||
this.executor.invoke(fn, { cancellation: cts.token }).then(returnOrThrow), | ||
cts.token.cancellation(cts.token).then(() => { | ||
this.timeoutEmitter.emit(); | ||
throw new TaskCancelledError(`Operation timed out after ${this.duration}ms`); | ||
@@ -62,0 +74,0 @@ }), |
@@ -1,12 +0,16 @@ | ||
import { FailureReason, IBasePolicyOptions, IPolicy } from './Policy'; | ||
import { ExecuteWrapper } from './common/Executor'; | ||
import { IPolicy } from './Policy'; | ||
export declare class FallbackPolicy<AltReturn> implements IPolicy<void, AltReturn> { | ||
private readonly options; | ||
private readonly executor; | ||
private readonly value; | ||
private readonly fallbackEmitter; | ||
/** | ||
* Event that fires when a fallback happens. | ||
* @inheritdoc | ||
*/ | ||
readonly onFallback: import("./common/Event").Event<FailureReason<unknown>>; | ||
constructor(options: IBasePolicyOptions, value: () => AltReturn); | ||
readonly onSuccess: import(".").Event<import("./Policy").ISuccessEvent>; | ||
/** | ||
* @inheritdoc | ||
*/ | ||
readonly onFailure: import(".").Event<import("./Policy").IFailureEvent>; | ||
constructor(executor: ExecuteWrapper, value: () => AltReturn); | ||
/** | ||
* Executes the given function. | ||
@@ -13,0 +17,0 @@ * @param fn -- Function to execute. |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const Event_1 = require("./common/Event"); | ||
const execute_1 = require("./common/execute"); | ||
class FallbackPolicy { | ||
constructor(options, value) { | ||
this.options = options; | ||
constructor(executor, value) { | ||
this.executor = executor; | ||
this.value = value; | ||
this.fallbackEmitter = new Event_1.EventEmitter(); | ||
/** | ||
* Event that fires when a fallback happens. | ||
* @inheritdoc | ||
*/ | ||
// tslint:disable-next-line: member-ordering | ||
this.onFallback = this.fallbackEmitter.addListener; | ||
this.onSuccess = this.executor.onSuccess; | ||
/** | ||
* @inheritdoc | ||
*/ | ||
// tslint:disable-next-line: member-ordering | ||
this.onFailure = this.executor.onFailure; | ||
} | ||
@@ -22,7 +24,6 @@ /** | ||
async execute(fn) { | ||
const result = await execute_1.execute(this.options, fn); | ||
const result = await this.executor.invoke(fn); | ||
if ('success' in result) { | ||
return result.success; | ||
} | ||
this.fallbackEmitter.emit(result); | ||
return this.value(); | ||
@@ -29,0 +30,0 @@ } |
@@ -14,5 +14,6 @@ "use strict"; | ||
it('returns a fallback and emits an error if necessary', async () => { | ||
var _a; | ||
const policy = await Policy_1.Policy.handleAll().fallback('error'); | ||
const onFallback = sinon_1.stub(); | ||
policy.onFallback(onFallback); | ||
policy.onFailure(onFallback); | ||
const error = new Error('oh no!'); | ||
@@ -23,5 +24,9 @@ const result = await policy.execute(() => { | ||
chai_1.expect(result).to.equal('error'); | ||
chai_1.expect(onFallback).calledWith({ error }); | ||
chai_1.expect(onFallback).calledWith({ | ||
reason: { error }, | ||
handled: true, | ||
duration: (_a = onFallback.args[0]) === null || _a === void 0 ? void 0 : _a[0].duration, | ||
}); | ||
}); | ||
}); | ||
//# sourceMappingURL=FallbackPolicy.test.js.map |
@@ -12,1 +12,2 @@ export { Event, EventEmitter } from './common/Event'; | ||
export * from './TimeoutPolicy'; | ||
export * from './NoopPolicy'; |
@@ -19,2 +19,3 @@ "use strict"; | ||
__export(require("./TimeoutPolicy")); | ||
__export(require("./NoopPolicy")); | ||
//# sourceMappingURL=index.js.map |
import { IBreaker } from './breaker/Breaker'; | ||
import { BulkheadPolicy } from './BulkheadPolicy'; | ||
import { CircuitBreakerPolicy } from './CircuitBreakerPolicy'; | ||
import { Event } from './common/Event'; | ||
import { FallbackPolicy } from './FallbackPolicy'; | ||
@@ -22,2 +23,28 @@ import { IRetryContext, RetryPolicy } from './RetryPolicy'; | ||
/** | ||
* Event emitted on the `onFailure` calls. | ||
*/ | ||
export interface IFailureEvent { | ||
/** | ||
* Call duration, in milliseconds (with nanosecond precision, as the OS allows). | ||
*/ | ||
duration: number; | ||
/** | ||
* Whether the error was handled by the policy. | ||
*/ | ||
handled: boolean; | ||
/** | ||
* The reason for the error. | ||
*/ | ||
reason: FailureReason<unknown>; | ||
} | ||
/** | ||
* Event emitted on the `onSuccess` calls. | ||
*/ | ||
export interface ISuccessEvent { | ||
/** | ||
* Call duration, in milliseconds (with nanosecond precision, as the OS allows). | ||
*/ | ||
duration: number; | ||
} | ||
/** | ||
* IPolicy is the type of all policies that Cockatiel provides. It describes | ||
@@ -27,2 +54,13 @@ * an execute() function which takes a generic argument. | ||
export interface IPolicy<ContextType, AltReturn = never> { | ||
/** | ||
* Fires on the policy when a request successfully completes and some | ||
* successful value will be returned. In a retry policy, this is fired once | ||
* even if the request took multiple retries to succeed. | ||
*/ | ||
readonly onSuccess: Event<ISuccessEvent>; | ||
/** | ||
* Fires on the policy when a request successfully fails and will throw a | ||
* rejection to the user. | ||
*/ | ||
readonly onFailure: Event<IFailureEvent>; | ||
execute<T>(fn: (context: ContextType) => PromiseLike<T> | T): Promise<T | AltReturn>; | ||
@@ -29,0 +67,0 @@ } |
@@ -5,3 +5,5 @@ "use strict"; | ||
const CircuitBreakerPolicy_1 = require("./CircuitBreakerPolicy"); | ||
const Executor_1 = require("./common/Executor"); | ||
const FallbackPolicy_1 = require("./FallbackPolicy"); | ||
const NoopPolicy_1 = require("./NoopPolicy"); | ||
const RetryPolicy_1 = require("./RetryPolicy"); | ||
@@ -22,2 +24,4 @@ const TimeoutPolicy_1 = require("./TimeoutPolicy"); | ||
return { | ||
onFailure: p[0].onFailure, | ||
onSuccess: p[0].onSuccess, | ||
execute(fn) { | ||
@@ -211,6 +215,3 @@ const run = (context, i) => i === p.length ? fn(context) : p[i].execute(next => run({ ...context, ...next }, i + 1)); | ||
retry() { | ||
return new RetryPolicy_1.RetryPolicy({ | ||
errorFilter: this.options.errorFilter, | ||
resultFilter: this.options.resultFilter, | ||
}); | ||
return new RetryPolicy_1.RetryPolicy({}, new Executor_1.ExecuteWrapper(this.options.errorFilter, this.options.resultFilter)); | ||
} | ||
@@ -242,6 +243,5 @@ /** | ||
return new CircuitBreakerPolicy_1.CircuitBreakerPolicy({ | ||
...this.options, | ||
breaker, | ||
halfOpenAfter, | ||
}); | ||
}, new Executor_1.ExecuteWrapper(this.options.errorFilter, this.options.resultFilter)); | ||
} | ||
@@ -267,3 +267,3 @@ /** | ||
fallback(valueOrFactory) { | ||
return new FallbackPolicy_1.FallbackPolicy(this.options, | ||
return new FallbackPolicy_1.FallbackPolicy(new Executor_1.ExecuteWrapper(this.options.errorFilter, this.options.resultFilter), | ||
// not technically type-safe, since if they actually want to _return_ | ||
@@ -279,3 +279,3 @@ // a function, that gets lost here. We'll just advice in the docs to | ||
*/ | ||
Policy.noop = { execute: async (fn) => fn(undefined) }; | ||
Policy.noop = new NoopPolicy_1.NoopPolicy(); | ||
//# sourceMappingURL=Policy.js.map |
import { IBackoff, IExponentialBackoffOptions } from './backoff/Backoff'; | ||
import { DelegateBackoffFn } from './backoff/DelegateBackoff'; | ||
import { FailureReason, IBasePolicyOptions, IPolicy } from './Policy'; | ||
import { ExecuteWrapper } from './common/Executor'; | ||
import { FailureReason, IPolicy } from './Policy'; | ||
/** | ||
@@ -23,3 +24,3 @@ * Context passed into the execute method of the builder. | ||
} | ||
export interface IRetryPolicyConfig extends IBasePolicyOptions { | ||
export interface IRetryPolicyConfig { | ||
backoff?: IBackoff<IRetryBackoffContext<unknown>>; | ||
@@ -34,5 +35,14 @@ /** | ||
private options; | ||
private onRetryEmitter; | ||
private onGiveUpEmitter; | ||
private readonly executor; | ||
private readonly onGiveUpEmitter; | ||
private readonly onRetryEmitter; | ||
/** | ||
* @inheritdoc | ||
*/ | ||
readonly onSuccess: import("./common/Event").Event<import("./Policy").ISuccessEvent>; | ||
/** | ||
* @inheritdoc | ||
*/ | ||
readonly onFailure: import("./common/Event").Event<import("./Policy").IFailureEvent>; | ||
/** | ||
* Emitter that fires when we retry a call, before any backoff. | ||
@@ -51,6 +61,6 @@ * | ||
/** | ||
* Emitter that fires when we retry a call. | ||
* @deprecated use `onFailure` instead | ||
*/ | ||
readonly onGiveUp: import("./common/Event").Event<FailureReason<unknown>>; | ||
constructor(options: Readonly<IRetryPolicyConfig>); | ||
constructor(options: Readonly<IRetryPolicyConfig>, executor: ExecuteWrapper); | ||
/** | ||
@@ -73,3 +83,3 @@ * Sets the number of retry attempts for the function. | ||
*/ | ||
exponential<S>(options: Partial<IExponentialBackoffOptions<S>>): RetryPolicy; | ||
exponential<S>(options?: Partial<IExponentialBackoffOptions<S>>): RetryPolicy; | ||
/** | ||
@@ -76,0 +86,0 @@ * Sets the baackoff to use for retries. |
@@ -9,3 +9,2 @@ "use strict"; | ||
const Event_1 = require("./common/Event"); | ||
const execute_1 = require("./common/execute"); | ||
const delay = (duration, unref) => new Promise(resolve => { | ||
@@ -18,7 +17,18 @@ const timer = setTimeout(resolve, duration); | ||
class RetryPolicy { | ||
constructor(options) { | ||
constructor(options, executor) { | ||
this.options = options; | ||
this.executor = executor; | ||
this.onGiveUpEmitter = new Event_1.EventEmitter(); | ||
this.onRetryEmitter = new Event_1.EventEmitter(); | ||
this.onGiveUpEmitter = new Event_1.EventEmitter(); | ||
/** | ||
* @inheritdoc | ||
*/ | ||
// tslint:disable-next-line: member-ordering | ||
this.onSuccess = this.executor.onSuccess; | ||
/** | ||
* @inheritdoc | ||
*/ | ||
// tslint:disable-next-line: member-ordering | ||
this.onFailure = this.executor.onFailure; | ||
/** | ||
* Emitter that fires when we retry a call, before any backoff. | ||
@@ -30,3 +40,3 @@ * | ||
/** | ||
* Emitter that fires when we retry a call. | ||
* @deprecated use `onFailure` instead | ||
*/ | ||
@@ -59,3 +69,3 @@ // tslint:disable-next-line: member-ordering | ||
*/ | ||
exponential(options) { | ||
exponential(options = {}) { | ||
return this.composeBackoff('b', new Backoff_1.ExponentialBackoff(options)); | ||
@@ -86,3 +96,3 @@ } | ||
for (let retries = 0;; retries++) { | ||
const result = await execute_1.execute(this.options, fn, { attempt: retries }); | ||
const result = await this.executor.invoke(fn, { attempt: retries }); | ||
if ('success' in result) { | ||
@@ -115,4 +125,3 @@ return result.success; | ||
derivePolicy(newOptions) { | ||
const p = new RetryPolicy(newOptions); | ||
p.onGiveUp(evt => this.onGiveUpEmitter.emit(evt)); | ||
const p = new RetryPolicy(newOptions, this.executor.derive()); | ||
p.onRetry(evt => this.onRetryEmitter.emit(evt)); | ||
@@ -119,0 +128,0 @@ return p; |
import { CancellationToken } from './CancellationToken'; | ||
import { ExecuteWrapper } from './common/Executor'; | ||
import { IPolicy } from './Policy'; | ||
@@ -20,10 +21,19 @@ export declare enum TimeoutStrategy { | ||
private readonly strategy; | ||
private readonly executor; | ||
private readonly unref; | ||
private readonly timeoutEmitter; | ||
/** | ||
* Event that fires when a function times out. | ||
* @inheritdoc | ||
*/ | ||
readonly onTimeout: import("./common/Event").Event<void>; | ||
constructor(duration: number, strategy: TimeoutStrategy, unref?: boolean); | ||
/** | ||
* @inheritdoc | ||
*/ | ||
readonly onFailure: import("./common/Event").Event<import("./Policy").IFailureEvent>; | ||
/** | ||
* @inheritdoc | ||
*/ | ||
readonly onSuccess: import("./common/Event").Event<import("./Policy").ISuccessEvent>; | ||
constructor(duration: number, strategy: TimeoutStrategy, executor?: ExecuteWrapper, unref?: boolean); | ||
/** | ||
* When timing out, a referenced timer is created. This means the Node.js | ||
@@ -30,0 +40,0 @@ * event loop is kept active while we're waiting for the timeout, as long as |
@@ -5,2 +5,3 @@ "use strict"; | ||
const Event_1 = require("./common/Event"); | ||
const Executor_1 = require("./common/Executor"); | ||
const TaskCancelledError_1 = require("./errors/TaskCancelledError"); | ||
@@ -20,12 +21,23 @@ var TimeoutStrategy; | ||
class TimeoutPolicy { | ||
constructor(duration, strategy, unref = false) { | ||
constructor(duration, strategy, executor = new Executor_1.ExecuteWrapper(), unref = false) { | ||
this.duration = duration; | ||
this.strategy = strategy; | ||
this.executor = executor; | ||
this.unref = unref; | ||
this.timeoutEmitter = new Event_1.EventEmitter(); | ||
/** | ||
* Event that fires when a function times out. | ||
* @inheritdoc | ||
*/ | ||
// tslint:disable-next-line: member-ordering | ||
this.onTimeout = this.timeoutEmitter.addListener; | ||
/** | ||
* @inheritdoc | ||
*/ | ||
// tslint:disable-next-line: member-ordering | ||
this.onFailure = this.executor.onFailure; | ||
/** | ||
* @inheritdoc | ||
*/ | ||
// tslint:disable-next-line: member-ordering | ||
this.onSuccess = this.executor.onSuccess; | ||
} | ||
@@ -40,4 +52,3 @@ /** | ||
dangerouslyUnref() { | ||
const t = new TimeoutPolicy(this.duration, this.strategy, true); | ||
t.onTimeout(() => this.timeoutEmitter.emit()); | ||
const t = new TimeoutPolicy(this.duration, this.strategy, this.executor, true); | ||
return t; | ||
@@ -58,7 +69,8 @@ } | ||
if (this.strategy === TimeoutStrategy.Cooperative) { | ||
return await fn({ cancellation: cts.token }); | ||
return Executor_1.returnOrThrow(await this.executor.invoke(fn, { cancellation: cts.token })); | ||
} | ||
return await Promise.race([ | ||
fn({ cancellation: cts.token }), | ||
this.executor.invoke(fn, { cancellation: cts.token }).then(Executor_1.returnOrThrow), | ||
cts.token.cancellation(cts.token).then(() => { | ||
this.timeoutEmitter.emit(); | ||
throw new TaskCancelledError_1.TaskCancelledError(`Operation timed out after ${this.duration}ms`); | ||
@@ -65,0 +77,0 @@ }), |
{ | ||
"name": "cockatiel", | ||
"version": "0.1.5", | ||
"version": "1.0.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.", | ||
@@ -5,0 +5,0 @@ "main": "dist/index.js", |
181
readme.md
@@ -82,2 +82,4 @@ # Cockatiel | ||
- [`retry.onRetry(callback)`](#retryonretrycallback) | ||
- [`retry.onSuccess(callback)`](#retryonsuccesscallback) | ||
- [`retry.onFailure(callback)`](#retryonfailurecallback) | ||
- [`retry.onGiveUp(callback)`](#retryongiveupcallback) | ||
@@ -91,2 +93,6 @@ - [`Policy.circuitBreaker(openAfter, breaker)`](#policycircuitbreakeropenafter-breaker) | ||
- [`breaker.onReset(callback)`](#breakeronresetcallback) | ||
- [`breaker.onHalfOpen(callback)`](#breakeronhalfopencallback) | ||
- [`breaker.onStateChange(callback)`](#breakeronstatechangecallback) | ||
- [`breaker.onSuccess(callback)`](#breakeronsuccesscallback) | ||
- [`breaker.onFailure(callback)`](#breakeronfailurecallback) | ||
- [`breaker.isolate()`](#breakerisolate) | ||
@@ -97,5 +103,9 @@ - [`Policy.timeout(duration, strategy)`](#policytimeoutduration-strategy) | ||
- [`timeout.onTimeout(callback)`](#timeoutontimeoutcallback) | ||
- [`timeout.onSuccess(callback)`](#timeoutonsuccesscallback) | ||
- [`timeout.onFailure(callback)`](#timeoutonfailurecallback) | ||
- [`Policy.bulkhead(limit[, queue])`](#policybulkheadlimit-queue) | ||
- [`bulkhead.execute(fn)`](#bulkheadexecutefn) | ||
- [`bulkhead.onReject(callback)`](#bulkheadonrejectcallback) | ||
- [`bulkhead.onSuccess(callback)`](#bulkheadonsuccesscallback) | ||
- [`bulkhead.onFailure(callback)`](#bulkheadonfailurecallback) | ||
- [`bulkhead.executionSlots`](#bulkheadexecutionslots) | ||
@@ -105,3 +115,4 @@ - [`bulkhead.queueSlots`](#bulkheadqueueslots) | ||
- [`fallback.execute(fn)`](#fallbackexecutefn) | ||
- [`fallback.onFallback(callback)`](#fallbackonfallbackcallback) | ||
- [`fallback.onSuccess(callback)`](#fallbackonsuccesscallback) | ||
- [`fallback.onFailure(callback)`](#fallbackonfailurecallback) | ||
@@ -480,6 +491,6 @@ ## `Policy` | ||
An event can be subscribed to simply by passing a callback. Take [`onFallback`](#fallbackonfallbackcallback) for instance: | ||
An event can be subscribed to simply by passing a callback. Take [`onFailure`](#fallbackonfailurecallback) for instance: | ||
```js | ||
const listener = policy.onFallback(error => { | ||
const listener = policy.onFailure(error => { | ||
console.log(error); | ||
@@ -645,2 +656,29 @@ }); | ||
### `retry.onSuccess(callback)` | ||
An [event emitter](#events) that fires whenever a function is successfully called. It's invoked with an object containing the duration in milliseconds to nanosecond precision. | ||
```js | ||
const listener = retry.onSuccess({ duration }) => { | ||
console.log(`retry call ran in ${duration}ms`); | ||
}); | ||
// later: | ||
listener.dispose(); | ||
``` | ||
### `retry.onFailure(callback)` | ||
An [event emitter](#events) that fires whenever a function throw an error or returns an errorful result. It's invoked with the duration of the call, the reason for the failure, and an boolean indicating whether the error is handled by the policy. | ||
```js | ||
const listener = retry.onFailure({ duration, handled, reason }) => { | ||
console.log(`retry call ran in ${duration}ms and failed with`, reason); | ||
console.log(handled ? 'error was handled' : 'error was not handled'); | ||
}); | ||
// later: | ||
listener.dispose(); | ||
``` | ||
### `retry.onGiveUp(callback)` | ||
@@ -771,2 +809,57 @@ | ||
### `breaker.onHalfOpen(callback)` | ||
An [event emitter](#events) when the circuit breaker is half open (running a test call). Either `onBreak` on `onReset` will subsequently fire. | ||
```js | ||
const listener = breaker.onHalfOpen(() => console.log('circuit is testing a request')); | ||
// later: | ||
listener.dispose(); | ||
``` | ||
### `breaker.onStateChange(callback)` | ||
An [event emitter](#events) that fires whenever the circuit state changes in general, after the more specific `onReset`, `onHalfOpen`, `onBreak` emitters fires. | ||
```js | ||
import { CircuitState } from 'cockatiel'; | ||
const listener = breaker.onStateChange(state => { | ||
if (state === CircuitState.Closed) { | ||
console.log('circuit breaker is once again closed'); | ||
} | ||
}); | ||
// later: | ||
listener.dispose(); | ||
``` | ||
### `breaker.onSuccess(callback)` | ||
An [event emitter](#events) that fires whenever a function is successfully called. It's invoked with an object containing the duration in milliseconds to nanosecond precision. | ||
```js | ||
const listener = breaker.onSuccess({ duration }) => { | ||
console.log(`circuit breaker call ran in ${duration}ms`); | ||
}); | ||
// later: | ||
listener.dispose(); | ||
``` | ||
### `breaker.onFailure(callback)` | ||
An [event emitter](#events) that fires whenever a function throw an error or returns an errorful result. It's invoked with the duration of the call, the reason for the failure, and an boolean indicating whether the error is handled by the policy. | ||
```js | ||
const listener = breaker.onFailure({ duration, handled, reason }) => { | ||
console.log(`circuit breaker call ran in ${duration}ms and failed with`, reason); | ||
console.log(handled ? 'error was handled' : 'error was not handled'); | ||
}); | ||
// later: | ||
listener.dispose(); | ||
``` | ||
### `breaker.isolate()` | ||
@@ -829,2 +922,31 @@ | ||
### `timeout.onSuccess(callback)` | ||
An [event emitter](#events) that fires whenever a function is successfully called. It's invoked with an object containing the duration in milliseconds to nanosecond precision. | ||
```js | ||
const listener = timeout.onSuccess({ duration }) => { | ||
console.log(`timeout call ran in ${duration}ms`); | ||
}); | ||
// later: | ||
listener.dispose(); | ||
``` | ||
### `timeout.onFailure(callback)` | ||
An [event emitter](#events) that fires whenever a function throw an error or returns an errorful result. It's invoked with the duration of the call, the reason for the failure, and an boolean indicating whether the error is handled by the policy. | ||
This is _only_ called when the function itself fails, and not when a timeout happens. | ||
```js | ||
const listener = timeout.onFailure({ duration, handled, reason }) => { | ||
console.log(`timeout call ran in ${duration}ms and failed with`, reason); | ||
console.log(handled ? 'error was handled' : 'error was not handled'); | ||
}); | ||
// later: | ||
listener.dispose(); | ||
``` | ||
## `Policy.bulkhead(limit[, queue])` | ||
@@ -882,2 +1004,31 @@ | ||
### `bulkhead.onSuccess(callback)` | ||
An [event emitter](#events) that fires whenever a function is successfully called. It's invoked with an object containing the duration in milliseconds to nanosecond precision. | ||
```js | ||
const listener = bulkhead.onSuccess({ duration }) => { | ||
console.log(`bulkhead call ran in ${duration}ms`); | ||
}); | ||
// later: | ||
listener.dispose(); | ||
``` | ||
### `bulkhead.onFailure(callback)` | ||
An [event emitter](#events) that fires whenever a function throw an error or returns an errorful result. It's invoked with the duration of the call, the reason for the failure, and an boolean indicating whether the error is handled by the policy. | ||
This is _only_ called when the function itself fails, and not when a bulkhead rejection occurs. | ||
```js | ||
const listener = bulkhead.onFailure({ duration, handled, reason }) => { | ||
console.log(`bulkhead call ran in ${duration}ms and failed with`, reason); | ||
console.log(handled ? 'error was handled' : 'error was not handled'); | ||
}); | ||
// later: | ||
listener.dispose(); | ||
``` | ||
### `bulkhead.executionSlots` | ||
@@ -913,8 +1064,10 @@ | ||
### `fallback.onFallback(callback)` | ||
### `fallback.onSuccess(callback)` | ||
An [event emitter](#events) that fires when a fallback occurs. 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](#policyhandleresulttypector-filter). Useful for telemetry. Returns a disposable instance. | ||
An [event emitter](#events) that fires whenever a function is successfully called. It's invoked with an object containing the duration in milliseconds to nanosecond precision. | ||
```js | ||
const listener = bulkhead.onReject(() => console.log('bulkhead call was rejected')); | ||
const listener = fallback.onSuccess({ duration }) => { | ||
console.log(`fallback call ran in ${duration}ms`); | ||
}); | ||
@@ -924,1 +1077,17 @@ // later: | ||
``` | ||
### `fallback.onFailure(callback)` | ||
An [event emitter](#events) that fires whenever a function throw an error or returns an errorful result. It's invoked with the duration of the call, the reason for the failure, and an boolean indicating whether the error is handled by the policy. | ||
If the error was handled, the fallback will kick in. | ||
```js | ||
const listener = fallback.onFailure({ duration, handled, reason }) => { | ||
console.log(`fallback call ran in ${duration}ms and failed with`, reason); | ||
console.log(handled ? 'error was handled' : 'error was not handled'); | ||
}); | ||
// later: | ||
listener.dispose(); | ||
``` |
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
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
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
469284
280
7167
0
1083