UNPKG

node-circuit-breaker

Version:

Circuit Breaker: Decorators and tools that can easily apply the Circuit Breaker pattern.

206 lines (205 loc) 9.56 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; 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.CircuitBreakerManager = exports.Circuit = void 0; const path = __importStar(require("path")); const execution_timeout_error_1 = require("./exceptions/execution-timeout-error"); const axios_extended_error_1 = require("./exceptions/axios-extended-error"); const typeorm_extended_error_1 = require("./exceptions/typeorm-extended-error"); const circuit_type_1 = require("./types/circuit.type"); const worker = require('./circuit-breaker-worker'); let seq = 0; class Circuit { constructor(caller, funcName, options) { this.state = circuit_type_1.CircuitBreakerState.Closed; this._serializedOptions = null; this.id = (++seq).toString(); this.caller = caller + ':' + funcName; this.options = options; } get serializedOptions() { if (!this._serializedOptions) this._serializedOptions = { fallbackForSeconds: this.options.fallbackForSeconds, rules: this.options.rules.map((item) => ({ exceptions: item.exceptions.map((errorType) => errorType.name), times: item.times, inSeconds: item.inSeconds, })), }; return this._serializedOptions; } setState(state) { return __awaiter(this, void 0, void 0, function* () { // console.log(this, state); if (state === circuit_type_1.CircuitBreakerState.Open && this.options.onCircuitOpen) { if (!(yield this.options.onCircuitOpen(this))) return false; } else if (state === circuit_type_1.CircuitBreakerState.Closed && this.options.onCircuitClose) { if (!(yield this.options.onCircuitClose(this))) return false; } this.state = state; return true; }); } invoke(context, func, trace, args) { if (this.state === circuit_type_1.CircuitBreakerState.Open) { if (typeof this.options.fallback === 'function') return this.options.fallback.apply(context, args); else if (typeof this.options.fallback === 'string' && this.options.fallback in context && typeof context[this.options.fallback] === 'function') { return context[this.options.fallback].apply(context, args); } else return this.options.fallback; } try { let result; if (this.options.timeoutMilliSeconds) { const begin = Date.now(); result = func.apply(context, args); if (result instanceof Promise) { return result.then((result) => { this.pushTimeoutError(trace, Date.now() - begin, args); return result; }); } else { this.pushTimeoutError(trace, Date.now() - begin, args); return result; } } else result = func.apply(context, args); if (this.state === circuit_type_1.CircuitBreakerState.HalfOpen) { this.setState(circuit_type_1.CircuitBreakerState.Closed).then((success) => { if (success) worker.setState(this.id, circuit_type_1.CircuitBreakerState.Closed); }); } return result; } catch (error) { // console.log('invoke error', error); this.pushError(trace, error, args); throw error; } } pushTimeoutError(trace, timeTaken, args) { if (this.options.timeoutMilliSeconds < timeTaken) { const message = `CircuitBreaker: ${this.caller} execution timeout ${timeTaken} > ${this.options.timeoutMilliSeconds}`; this.pushError(trace, new execution_timeout_error_1.ExecutionTimeoutError(message, timeTaken), args); } } pushError(trace, error, args) { var _a, _b, _c, _d; return __awaiter(this, void 0, void 0, function* () { error = Circuit.exceptionPipes.reduce((result, item) => item(result), error); // console.log(error); // console.log((trace as any).stack); if (yield ((_b = (_a = this.options).ignore) === null || _b === void 0 ? void 0 : _b.call(_a, error, this))) return; // this.records.push({ error, time: new Date(), args, stack: trace?.stack }); yield ((_d = (_c = this.options).onError) === null || _d === void 0 ? void 0 : _d.call(_c, error, this)); worker.pushError(this.id, error.constructor.name); if (this.state === circuit_type_1.CircuitBreakerState.HalfOpen) { this.state = circuit_type_1.CircuitBreakerState.Open; worker.setState(this.id, circuit_type_1.CircuitBreakerState.Open); } }); } } exports.Circuit = Circuit; Circuit.exceptionPipes = [axios_extended_error_1.AxiosExceptionPipe, typeorm_extended_error_1.TypeormExceptionPipe]; function _getCallerFile() { const err = new Error(); Error.prepareStackTrace = (_, stack) => stack; const stack = err.stack; Error.prepareStackTrace = undefined; const paths = stack .filter((callSite) => { const file = callSite.getFileName() || ''; return file && !file.match(/reflect-metadata/i) && !(callSite.getFunctionName() || '').toString().match(/__decorate/); }) .map((callSite) => [path.relative(process.cwd(), callSite.getFileName() || ''), callSite.getLineNumber() || ''].join(':')); const idx = paths.findIndex((path) => path.match(/circuit-breaker.decorator/)); if (idx < 0) throw new Error('CircuitBreaker: Call stack is ambiguous.'); return paths[idx + 1]; } class CircuitBreakerManager { static invoke(context, func, funcName, options, trace, ...args) { var _a; let circuit; if (options.scope === circuit_type_1.CircuitBreakerScope.INSTANCE) { if (!context.__circuit) Reflect.defineProperty(context, '__circuit', { value: [], writable: false, configurable: false, enumerable: false }); circuit = (_a = context.__circuit.find((item) => item.func === func)) === null || _a === void 0 ? void 0 : _a.circuit; if (!circuit) { circuit = new Circuit(_getCallerFile(), funcName, options); context.__circuit.push({ func, circuit }); CircuitBreakerManager.circuits[circuit.id] = circuit; } } else { circuit = func.__circuit; if (!func.__circuit) { circuit = new Circuit(_getCallerFile(), funcName, options); Reflect.defineProperty(func, '__circuit', { value: circuit, writable: false, configurable: false, enumerable: false }); CircuitBreakerManager.circuits[circuit.id] = circuit; } } return circuit.invoke(context, func, trace, args); } static terminate() { return __awaiter(this, void 0, void 0, function* () { return worker.terminate(); }); } } exports.CircuitBreakerManager = CircuitBreakerManager; CircuitBreakerManager.circuits = {}; worker.setMetadataProvider((id) => { var _a; return (_a = CircuitBreakerManager.circuits[id]) === null || _a === void 0 ? void 0 : _a.serializedOptions; }); worker.setStateCallback((id, state) => __awaiter(void 0, void 0, void 0, function* () { const circuit = CircuitBreakerManager.circuits[id]; if (!circuit) return true; return circuit.setState(state); }));