UNPKG

@daisugi/kintsugi

Version:

Kintsugi is a set of utilities to help build a fault tolerant services.

91 lines 3.35 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.isFailureResponse = isFailureResponse; exports.withCircuitBreaker = withCircuitBreaker; const node_timers_1 = require("node:timers"); const anzen_1 = require("@daisugi/anzen"); const ayamari_1 = require("@daisugi/ayamari"); const { errFn, errCode } = new ayamari_1.Ayamari(); const defaultWindowDurationMs = 30000; const defaultTotalBuckets = 10; const defaultFailureThresholdRate = 50; const defaultVolumeThreshold = 10; const defaultReturnToServiceAfterMs = 5000; const State = { Close: 1, Open: 2, HalfOpen: 3, }; const Measure = { Failure: 1, Calls: 2, }; const circuitSuspendedErr = anzen_1.Result.failure(errFn.CircuitSuspended('Circuit suspended by circuit breaker.')); function isFailureResponse(response) { if (response.isSuccess) { return false; } if (response.isFailure && response.getError().code === errCode.NotFound) { return false; } return true; } function withCircuitBreaker(fn, opts = {}) { const windowDurationMs = opts.windowDurationMs || defaultWindowDurationMs; const totalBuckets = opts.totalBuckets || defaultTotalBuckets; const failureThresholdRate = opts.failureThresholdRate || defaultFailureThresholdRate; const volumeThreshold = opts.volumeThreshold || defaultVolumeThreshold; const _isFailureResponse = opts.isFailureResponse || isFailureResponse; const returnToServiceAfterMs = opts.returnToServiceAfterMs || defaultReturnToServiceAfterMs; const buckets = [[0, 0]]; let currentState = State.Close; let nextAttemptMs = Date.now(); (0, node_timers_1.setInterval)(() => { buckets.push([0, 0]); if (buckets.length > totalBuckets) { buckets.shift(); } }, windowDurationMs / totalBuckets); return async function (...args) { if (currentState === State.Open) { if (nextAttemptMs > Date.now()) { return circuitSuspendedErr; } currentState = State.HalfOpen; } const response = await fn.apply(this, args); const lastBucket = buckets[buckets.length - 1]; const isFailure = _isFailureResponse(response); lastBucket[Measure.Calls] += 1; if (isFailure) { lastBucket[Measure.Failure] += 1; } let bucketsFailures = 0; let bucketsCalls = 0; for (const bucket of buckets) { bucketsFailures += bucket[Measure.Failure]; bucketsCalls += bucket[Measure.Calls]; } if (currentState === State.HalfOpen) { const lastCallFailed = isFailure && bucketsCalls > volumeThreshold; if (lastCallFailed) { currentState = State.Open; return circuitSuspendedErr; } currentState = State.Close; return response; } const failuresRate = (bucketsFailures / bucketsCalls) * 100; if (failuresRate > failureThresholdRate && bucketsCalls > volumeThreshold) { currentState = State.Open; nextAttemptMs = Date.now() + returnToServiceAfterMs; return circuitSuspendedErr; } return response; }; } //# sourceMappingURL=with_circuit_breaker.js.map