nestjs-resilience
Version:
A module for improving the reliability and fault-tolerance of your NestJS applications
107 lines (106 loc) • 5.45 kB
JavaScript
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CircuitBreakerStrategy = void 0;
const base_strategy_1 = require("./base.strategy");
const enum_1 = require("../enum");
const rxjs_1 = require("rxjs");
const exceptions_1 = require("../exceptions");
const timeout_strategy_1 = require("./timeout.strategy");
const cache_strategy_1 = require("./cache.strategy");
const resilience_states_manager_1 = require("../resilience.states-manager");
class CircuitBreakerStrategy extends base_strategy_1.Strategy {
constructor(options) {
super(Object.assign(Object.assign({}, CircuitBreakerStrategy.DEFAULT_OPTIONS), options));
this.cacheStrategy = new cache_strategy_1.CacheStrategy(this.options.cachedTimeoutInMilliseconds);
this.timeoutStrategy = new timeout_strategy_1.TimeoutStrategy(this.options.timeoutInMilliseconds);
}
process(observable, command, ...args) {
const statesManager = resilience_states_manager_1.ResilienceStatesManager.getInstance();
const key = [this.name, command].join('/');
const state$ = (0, rxjs_1.from)(statesManager.wrap(key, () => __awaiter(this, void 0, void 0, function* () {
return ({
status: enum_1.CircuitBreakerStatus.Closed,
openedAt: 0,
failuresCount: 0,
succeedsCount: 0,
lastRequestTimeMs: 0
});
})));
return state$.pipe((0, rxjs_1.switchMap)(state => {
const isOpen = () => state.status === enum_1.CircuitBreakerStatus.Open;
const isHalfOpen = () => state.status === enum_1.CircuitBreakerStatus.HalfOpen;
const failuresPercentage = () => (state.failuresCount / this.options.requestVolumeThreshold) * 100;
if (this.options.rollingWindowInMilliseconds) {
const rollingWindowHasExpired = () => Date.now() >=
state.lastRequestTimeMs + this.options.rollingWindowInMilliseconds;
if (rollingWindowHasExpired()) {
state.failuresCount = 0;
state.succeedsCount = 0;
state.openedAt = 0;
}
}
state.lastRequestTimeMs = Date.now();
if (isOpen()) {
if (state.openedAt + this.options.sleepWindowInMilliseconds > Date.now()) {
return this.getFallbackOrThrowError(new exceptions_1.CircuitOpenedException());
}
state.status = enum_1.CircuitBreakerStatus.HalfOpen;
}
if (this.options.cachedTimeoutInMilliseconds) {
observable = this.cacheStrategy.process(observable, command, ...args);
}
if (this.options.timeoutInMilliseconds) {
observable = this.timeoutStrategy.process(observable);
}
let wasError = false;
return observable.pipe((0, rxjs_1.tap)(() => (state.succeedsCount = state.succeedsCount + 1)), (0, rxjs_1.finalize)(() => __awaiter(this, void 0, void 0, function* () {
if (state.succeedsCount + state.failuresCount >=
this.options.requestVolumeThreshold) {
state.succeedsCount = 0;
state.failuresCount = 0;
}
if (isHalfOpen() && !wasError) {
state.status = enum_1.CircuitBreakerStatus.Closed;
}
yield statesManager.set(key, state);
})), (0, rxjs_1.catchError)(error => {
state.failuresCount = state.failuresCount + 1;
if (state.succeedsCount + state.failuresCount >=
this.options.requestVolumeThreshold) {
if (failuresPercentage() >= this.options.errorThresholdPercentage) {
state.status = enum_1.CircuitBreakerStatus.Open;
state.openedAt = Date.now();
}
}
if (isHalfOpen()) {
state.status = enum_1.CircuitBreakerStatus.Open;
state.openedAt = Date.now();
}
wasError = true;
return this.getFallbackOrThrowError(error);
}));
}));
}
getFallbackOrThrowError(error) {
if (this.options.fallback) {
return (0, rxjs_1.of)(this.options.fallback(error));
}
return (0, rxjs_1.throwError)(() => error);
}
}
exports.CircuitBreakerStrategy = CircuitBreakerStrategy;
CircuitBreakerStrategy.DEFAULT_OPTIONS = {
requestVolumeThreshold: 20,
sleepWindowInMilliseconds: 5000,
errorThresholdPercentage: 50,
rollingWindowInMilliseconds: 20000
};
;