UNPKG

file-routing-expressjs

Version:

A dependency-free, flexible, system-based file routing for Express.

100 lines 3.37 kB
var __async = (__this, __arguments, generator) => { return new Promise((resolve, reject) => { var fulfilled = (value) => { try { step(generator.next(value)); } catch (e) { reject(e); } }; var rejected = (value) => { try { step(generator.throw(value)); } catch (e) { reject(e); } }; var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected); step((generator = generator.apply(__this, __arguments)).next()); }); }; import { isDefined, isFunction, isNotDefined } from "../helpers/validators"; const STATE = /* @__PURE__ */ new Map(); const ID_SYMBOL = Symbol("id"); const KEY_SYMBOL = Symbol("key"); const circuitBreakerPlugin = { name: "circuit-breaker", validateConfig(config) { if (!config || typeof config.threshold !== "number" || config.threshold <= 0) { throw new Error(`[circuit-breaker] "threshold" must be > 0`); } if (typeof config.cooldown !== "number" || config.cooldown <= 0) { throw new Error(`[circuit-breaker] "cooldown" must be > 0`); } if (isDefined(config.considerError) && !isFunction(config.considerError)) { throw new Error(`[circuit-breaker] "considerError" must be a function`); } }, onRequest(ctx, next) { return __async(this, null, function* () { var _a; const config = ctx.config; if (isNotDefined(config)) return next(); const key = `${ctx.req.method}:${((_a = ctx.req.route) == null ? void 0 : _a.path) || ctx.req.path}`; const now = Date.now(); let state = STATE.get(key); if (isNotDefined(state)) { state = { failures: 0, openUntil: 0, halfOpen: false, inFlight: false }; STATE.set(key, state); } ctx.state[ID_SYMBOL] = { failureCounted: false }; ctx.state[KEY_SYMBOL] = key; if (state.openUntil > now) { ctx.res.status(503).send("Service unavailable (circuit open)"); return; } if (state.failures >= config.threshold && state.openUntil <= now) { if (state.inFlight) { ctx.res.status(503).send("Service unavailable (half-open busy)"); return; } state.halfOpen = true; state.inFlight = true; } try { yield next(); if (ctx.res.statusCode < 500) { state.failures = 0; state.halfOpen = false; } } finally { state.inFlight = false; } }); }, onError(err, ctx) { return __async(this, null, function* () { const config = ctx.config; if (isNotDefined(config)) return; const key = ctx.state[KEY_SYMBOL]; if (isNotDefined(key)) return; const state = STATE.get(key); if (isNotDefined(state)) return; const shouldCount = isDefined(config.considerError) ? config.considerError(err) : true; if (!shouldCount) return; const meta = ctx.state[ID_SYMBOL]; if (!meta || meta.failureCounted) return; meta.failureCounted = true; state.failures++; if (state.failures >= config.threshold) { state.openUntil = Date.now() + config.cooldown; state.halfOpen = false; } }); } }; var circuit_breaker_default = circuitBreakerPlugin; export { circuit_breaker_default as default }; //# sourceMappingURL=circuit-breaker.mjs.map