UNPKG

@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
/** * @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