@daisugi/kintsugi
Version:
Kintsugi is a set of utilities to help build a fault tolerant services.
91 lines • 3.35 kB
JavaScript
;
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