{"version":3,"sources":["../../src/plugins/circuit-breaker.ts"],"sourcesContent":["import { isDefined, isFunction, isNotDefined } from \"../helpers/validators\";\nimport { CircutBreakerConfig, CircuteBreakerEntryState, Plugin } from \"../types\";\n\nconst STATE = new Map<string, CircuteBreakerEntryState>();\nconst ID_SYMBOL = Symbol(\"id\");\nconst KEY_SYMBOL = Symbol(\"key\");\n\nconst circuitBreakerPlugin: Plugin<CircutBreakerConfig> = {\n    name: \"circuit-breaker\",\n\n    validateConfig(config) {\n        if (!config || typeof config.threshold !== \"number\" || config.threshold <= 0) {\n            throw new Error(`[circuit-breaker] \"threshold\" must be > 0`);\n        }\n\n        if (typeof config.cooldown !== \"number\" || config.cooldown <= 0) {\n            throw new Error(`[circuit-breaker] \"cooldown\" must be > 0`);\n        }\n\n        if(isDefined(config.considerError) && !isFunction(config.considerError)) {\n            throw new Error(`[circuit-breaker] \"considerError\" must be a function`);\n        }\n    },\n\n    async onRequest(ctx, next) {\n        const config = ctx.config;\n        if (isNotDefined(config)) return next();\n\n        const key = `${ctx.req.method}:${ctx.req.route?.path || ctx.req.path}`;\n        const now = Date.now();\n\n        let state = STATE.get(key);\n        if (isNotDefined(state)) {\n            state = { failures: 0, openUntil: 0, halfOpen: false, inFlight: false };\n            STATE.set(key, state);\n        }\n\n        ctx.state[ID_SYMBOL] = { failureCounted: false };\n        ctx.state[KEY_SYMBOL] = key;\n\n        if (state.openUntil > now) {\n            ctx.res.status(503).send(\"Service unavailable (circuit open)\");\n            return;\n        }\n\n        if (state.failures >= config.threshold && state.openUntil <= now) {\n            if (state.inFlight) {\n                ctx.res.status(503).send(\"Service unavailable (half-open busy)\");\n                return;\n            }\n\n            state.halfOpen = true;\n            state.inFlight = true;\n        }\n\n        try {\n            await next();\n\n            if (ctx.res.statusCode < 500) {\n                state.failures = 0;\n                state.halfOpen = false;\n            }\n        } finally {\n            state.inFlight = false;\n        }\n    },\n\n    async onError(err, ctx) {\n        const config = ctx.config;\n        if (isNotDefined(config)) return;\n\n        const key = ctx.state[KEY_SYMBOL];\n\n        if(isNotDefined(key)) return;\n\n        const state = STATE.get(key);\n\n        if (isNotDefined(state)) return;\n\n        const shouldCount = isDefined(config.considerError)\n            ? config.considerError(err)\n            : true;\n\n        if (!shouldCount) return;\n\n        const meta = ctx.state[ID_SYMBOL]; \n        if (!meta || meta.failureCounted) return;\n\n        meta.failureCounted = true;\n        state.failures++;\n\n        if (state.failures >= config.threshold) {\n            state.openUntil = Date.now() + config.cooldown;\n            state.halfOpen = false;\n        }\n    }\n};\n\nexport default circuitBreakerPlugin;"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA,SAAS,WAAW,YAAY,oBAAoB;AAGpD,MAAM,QAAQ,oBAAI,IAAsC;AACxD,MAAM,YAAY,OAAO,IAAI;AAC7B,MAAM,aAAa,OAAO,KAAK;AAE/B,MAAM,uBAAoD;AAAA,EACtD,MAAM;AAAA,EAEN,eAAe,QAAQ;AACnB,QAAI,CAAC,UAAU,OAAO,OAAO,cAAc,YAAY,OAAO,aAAa,GAAG;AAC1E,YAAM,IAAI,MAAM,2CAA2C;AAAA,IAC/D;AAEA,QAAI,OAAO,OAAO,aAAa,YAAY,OAAO,YAAY,GAAG;AAC7D,YAAM,IAAI,MAAM,0CAA0C;AAAA,IAC9D;AAEA,QAAG,UAAU,OAAO,aAAa,KAAK,CAAC,WAAW,OAAO,aAAa,GAAG;AACrE,YAAM,IAAI,MAAM,sDAAsD;AAAA,IAC1E;AAAA,EACJ;AAAA,EAEM,UAAU,KAAK,MAAM;AAAA;AAxB/B;AAyBQ,YAAM,SAAS,IAAI;AACnB,UAAI,aAAa,MAAM,EAAG,QAAO,KAAK;AAEtC,YAAM,MAAM,GAAG,IAAI,IAAI,MAAM,MAAI,SAAI,IAAI,UAAR,mBAAe,SAAQ,IAAI,IAAI,IAAI;AACpE,YAAM,MAAM,KAAK,IAAI;AAErB,UAAI,QAAQ,MAAM,IAAI,GAAG;AACzB,UAAI,aAAa,KAAK,GAAG;AACrB,gBAAQ,EAAE,UAAU,GAAG,WAAW,GAAG,UAAU,OAAO,UAAU,MAAM;AACtE,cAAM,IAAI,KAAK,KAAK;AAAA,MACxB;AAEA,UAAI,MAAM,SAAS,IAAI,EAAE,gBAAgB,MAAM;AAC/C,UAAI,MAAM,UAAU,IAAI;AAExB,UAAI,MAAM,YAAY,KAAK;AACvB,YAAI,IAAI,OAAO,GAAG,EAAE,KAAK,oCAAoC;AAC7D;AAAA,MACJ;AAEA,UAAI,MAAM,YAAY,OAAO,aAAa,MAAM,aAAa,KAAK;AAC9D,YAAI,MAAM,UAAU;AAChB,cAAI,IAAI,OAAO,GAAG,EAAE,KAAK,sCAAsC;AAC/D;AAAA,QACJ;AAEA,cAAM,WAAW;AACjB,cAAM,WAAW;AAAA,MACrB;AAEA,UAAI;AACA,cAAM,KAAK;AAEX,YAAI,IAAI,IAAI,aAAa,KAAK;AAC1B,gBAAM,WAAW;AACjB,gBAAM,WAAW;AAAA,QACrB;AAAA,MACJ,UAAE;AACE,cAAM,WAAW;AAAA,MACrB;AAAA,IACJ;AAAA;AAAA,EAEM,QAAQ,KAAK,KAAK;AAAA;AACpB,YAAM,SAAS,IAAI;AACnB,UAAI,aAAa,MAAM,EAAG;AAE1B,YAAM,MAAM,IAAI,MAAM,UAAU;AAEhC,UAAG,aAAa,GAAG,EAAG;AAEtB,YAAM,QAAQ,MAAM,IAAI,GAAG;AAE3B,UAAI,aAAa,KAAK,EAAG;AAEzB,YAAM,cAAc,UAAU,OAAO,aAAa,IAC5C,OAAO,cAAc,GAAG,IACxB;AAEN,UAAI,CAAC,YAAa;AAElB,YAAM,OAAO,IAAI,MAAM,SAAS;AAChC,UAAI,CAAC,QAAQ,KAAK,eAAgB;AAElC,WAAK,iBAAiB;AACtB,YAAM;AAEN,UAAI,MAAM,YAAY,OAAO,WAAW;AACpC,cAAM,YAAY,KAAK,IAAI,IAAI,OAAO;AACtC,cAAM,WAAW;AAAA,MACrB;AAAA,IACJ;AAAA;AACJ;AAEA,IAAO,0BAAQ;","names":[]}