@daiso-tech/core
Version:
The library offers flexible, framework-agnostic solutions for modern web applications, built on adaptable components that integrate seamlessly with popular frameworks like Next Js.
154 lines • 5.67 kB
JavaScript
/**
* @module CircuitBreaker
*/
import { HALF_OPEN_TRANSITIONS, CLOSED_TRANSITIONS, } from "../../../../circuit-breaker/contracts/_module.js";
/**
* @internal
*/
function validateFailureThreshold(failureThreshold) {
if (Number.isInteger(failureThreshold)) {
throw new TypeError(`"CountBreakerSettings.failureThreshold" should be a float, got integer instead`);
}
if (failureThreshold <= 0 || failureThreshold >= 1) {
throw new RangeError(`"CountBreakerSettings.failureThreshold" should be between 0 and 1, got ${String(failureThreshold)}`);
}
}
/**
* @internal
*/
function validateSuccessThreshold(successThreshold) {
if (Number.isInteger(successThreshold)) {
throw new TypeError(`"CountBreakerSettings.successThreshold" should be a float, got integer instead`);
}
if (successThreshold <= 0 || successThreshold >= 1) {
throw new RangeError(`"CountBreakerSettings.successThreshold" should be between 0 and 1, got ${String(successThreshold)}`);
}
}
/**
* @internal
*/
function validateSize(size) {
if (!Number.isSafeInteger(size)) {
throw new TypeError(`"CountBreakerSettings.size" should be an integer, got float instead`);
}
if (size < 1) {
throw new RangeError(`"CountBreakerSettings.size" should be a positive, got ${String(size)}`);
}
}
/**
* @internal
*/
function validateMinimumNumberOfCalls(minimumNumberOfCalls, size) {
if (!Number.isSafeInteger(minimumNumberOfCalls)) {
throw new TypeError(`"CountBreakerSettings.minimumNumberOfCalls" should be an integer, got float instead`);
}
if (minimumNumberOfCalls < 1 || minimumNumberOfCalls > size) {
throw new RangeError(`"CountBreakerSettings.minimumNumberOfCalls" should be between 1, and "CountBreakerSettings.size" (which is ${String(size)}), got ${String(minimumNumberOfCalls)}`);
}
}
/**
* @internal
*/
export function resolveCountBreakerSettings(settings) {
const { failureThreshold = 0.2, successThreshold = 1 - failureThreshold, size = 20, minimumNumberOfCalls = size, } = settings;
validateFailureThreshold(failureThreshold);
validateSuccessThreshold(successThreshold);
validateSize(size);
validateMinimumNumberOfCalls(minimumNumberOfCalls, size);
return {
failureThreshold,
successThreshold,
size,
minimumNumberOfCalls,
};
}
/**
* The `CountBreaker` breaks after a proportion of requests in a count based sliding window fail.
*
* IMPORT_PATH: `"@daiso-tech/core/circuit-breaker/policies"`
* @group Policies
*/
export class CountBreaker {
failureThreshold;
successThreshold;
minimumNumberOfCalls;
size;
constructor(settings = {}) {
const { failureThreshold, successThreshold, size, minimumNumberOfCalls, } = resolveCountBreakerSettings(settings);
this.failureThreshold = failureThreshold;
this.successThreshold = successThreshold;
this.minimumNumberOfCalls = minimumNumberOfCalls;
this.size = size;
}
initialMetrics() {
return {
samples: [],
};
}
static failureCount(currentMetrics) {
let count = 0;
for (const isSuccess of currentMetrics.samples) {
if (!isSuccess) {
count++;
}
}
return count;
}
static successCount(currentMetrics) {
let count = 0;
for (const isSuccess of currentMetrics.samples) {
if (isSuccess) {
count++;
}
}
return count;
}
isMinimumNotMet(currentMetrics) {
return currentMetrics.samples.length <= this.minimumNumberOfCalls;
}
whenClosed(currentMetrics, _currentDate) {
if (this.isMinimumNotMet(currentMetrics)) {
return CLOSED_TRANSITIONS.NONE;
}
const allowedFailureCount = Math.ceil(this.failureThreshold * currentMetrics.samples.length);
const hasFailed = CountBreaker.failureCount(currentMetrics) > allowedFailureCount;
if (hasFailed) {
return CLOSED_TRANSITIONS.TO_OPEN;
}
return CLOSED_TRANSITIONS.NONE;
}
whenHalfOpened(currentMetrics, _currentDate) {
if (this.isMinimumNotMet(currentMetrics)) {
return CLOSED_TRANSITIONS.NONE;
}
const allowedSuccessCount = Math.ceil(this.successThreshold * currentMetrics.samples.length);
const hasSucceeded = CountBreaker.successCount(currentMetrics) >= allowedSuccessCount;
if (hasSucceeded) {
return HALF_OPEN_TRANSITIONS.TO_CLOSED;
}
return HALF_OPEN_TRANSITIONS.TO_OPEN;
}
track(success, currentState) {
let newSamples = [...currentState.metrics.samples, success];
if (currentState.metrics.samples.length >= this.size) {
newSamples = newSamples.slice(1);
}
return {
samples: newSamples,
};
}
trackFailure(currentState, _settings) {
return this.track(false, currentState);
}
trackSuccess(currentState, _settings) {
return this.track(true, currentState);
}
isEqual(metricsA, metricsB) {
const failureCountA = metricsA.samples.filter((isSuccess) => !isSuccess).length;
const failureCountB = metricsB.samples.filter((isSuccess) => !isSuccess).length;
const successCountA = metricsA.samples.filter((isSuccess) => isSuccess).length;
const successCountB = metricsB.samples.filter((isSuccess) => isSuccess).length;
return (failureCountA === failureCountB && successCountA === successCountB);
}
}
//# sourceMappingURL=count-breaker.js.map