@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.
201 lines • 6.99 kB
JavaScript
/**
* @module CircuitBreaker
*/
import { OpenCircuitBreakerError, IsolatedCircuitBreakerError, CIRCUIT_BREAKER_TRIGGER, CIRCUIT_BREAKER_STATE, CIRCUIT_BREAKER_EVENTS, } from "../../../../circuit-breaker/contracts/_module.js";
import {} from "../../../../event-bus/contracts/_module.js";
import {} from "../../../../namespace/_module.js";
import {} from "../../../../task/contracts/_module.js";
import { Task } from "../../../../task/implementations/_module.js";
import { TimeSpan } from "../../../../time-span/implementations/_module.js";
import { callErrorPolicyOnThrow, resolveAsyncLazyable, } from "../../../../utilities/_module.js";
/**
* @internal
*/
export class CircuitBreaker {
/**
* @internal
*/
static _internal_serialize(deserializedValue) {
return {
version: "1",
key: deserializedValue._key.get(),
};
}
_key;
errorPolicy;
trigger;
slowCallTime;
adapter;
eventDispatcher;
serdeTransformerName;
namespace;
enableAsyncTracking;
constructor(settings) {
const { enableAsyncTracking, eventDispatcher, key, errorPolicy, trigger, adapter, slowCallTime, serdeTransformerName, namespace, } = settings;
this.enableAsyncTracking = enableAsyncTracking;
this.eventDispatcher = eventDispatcher;
this._key = key;
this.errorPolicy = errorPolicy;
this.trigger = trigger;
this.adapter = adapter;
this.slowCallTime = slowCallTime;
this.serdeTransformerName = serdeTransformerName;
this.namespace = namespace;
}
_internal_getNamespace() {
return this.namespace;
}
_internal_getSerdeTransformerName() {
return this.serdeTransformerName;
}
_internal_getAdapter() {
return this.adapter;
}
get key() {
return this._key.get();
}
getState() {
return new Task(async () => {
return this.adapter.getState(this._key.toString());
});
}
async trackFailure() {
if (this.enableAsyncTracking) {
new Task(async () => {
await this.adapter.trackFailure(this._key.toString());
}).detach();
return;
}
await this.adapter.trackFailure(this._key.toString());
}
async trackSuccess() {
if (this.enableAsyncTracking) {
new Task(async () => {
await this.adapter.trackSuccess(this._key.toString());
}).detach();
return;
}
await this.adapter.trackSuccess(this._key.toString());
}
async trackErrorWrapper(fn) {
try {
return await fn();
}
catch (error) {
const isErrorMatching = await callErrorPolicyOnThrow(this.errorPolicy, error);
const shouldRecordError = this.trigger === CIRCUIT_BREAKER_TRIGGER.BOTH ||
this.trigger === CIRCUIT_BREAKER_TRIGGER.ONLY_ERROR;
if (shouldRecordError && isErrorMatching) {
if (this.enableAsyncTracking) {
new Task(async () => {
await this.trackFailure();
}).detach();
}
else {
await this.trackFailure();
}
this.eventDispatcher
.dispatch(CIRCUIT_BREAKER_EVENTS.TRACKED_FAILURE, {
circuitBreaker: this,
error,
})
.detach();
}
else if (shouldRecordError) {
this.eventDispatcher
.dispatch(CIRCUIT_BREAKER_EVENTS.UNTRACKED_FAILURE, {
circuitBreaker: this,
error,
})
.detach();
}
throw error;
}
}
async trackSlowCallWrapper(fn) {
const start = performance.now();
const value = await fn();
const end = performance.now();
const executionTime = TimeSpan.fromMilliseconds(end - start);
const shouldRecordSlowCall = this.trigger === CIRCUIT_BREAKER_TRIGGER.BOTH ||
this.trigger === CIRCUIT_BREAKER_TRIGGER.ONLY_SLOW_CALL;
const isCallSlow = executionTime.gte(this.slowCallTime);
if (shouldRecordSlowCall && isCallSlow) {
await this.trackFailure();
this.eventDispatcher
.dispatch(CIRCUIT_BREAKER_EVENTS.TRACKED_SLOW_CALL, {
circuitBreaker: this,
})
.detach();
}
if (shouldRecordSlowCall && !isCallSlow) {
await this.trackSuccess();
this.eventDispatcher
.dispatch(CIRCUIT_BREAKER_EVENTS.TRACKED_SUCCESS, {
circuitBreaker: this,
})
.detach();
}
if (!shouldRecordSlowCall) {
await this.trackSuccess();
this.eventDispatcher
.dispatch(CIRCUIT_BREAKER_EVENTS.TRACKED_SUCCESS, {
circuitBreaker: this,
})
.detach();
}
return value;
}
async guard() {
const transition = await this.adapter.updateState(this._key.toString());
const hasStateChaned = transition.to !== transition.from;
if (hasStateChaned) {
this.eventDispatcher
.dispatch(CIRCUIT_BREAKER_EVENTS.STATE_TRANSITIONED, {
circuitBreaker: this,
from: transition.from,
to: transition.to,
})
.detach();
}
const isInOpenState = transition.to === CIRCUIT_BREAKER_STATE.OPEN;
if (isInOpenState) {
throw OpenCircuitBreakerError.create(this._key);
}
const isIsolatedState = transition.to === CIRCUIT_BREAKER_EVENTS.ISOLATED;
if (isIsolatedState) {
throw IsolatedCircuitBreakerError.create(this._key);
}
}
runOrFail(asyncFn) {
return new Task(async () => {
await this.guard();
return await this.trackErrorWrapper(async () => {
return await this.trackSlowCallWrapper(async () => {
return await resolveAsyncLazyable(asyncFn);
});
});
});
}
reset() {
return new Task(async () => {
await this.adapter.reset(this._key.toString());
this.eventDispatcher
.dispatch(CIRCUIT_BREAKER_EVENTS.RESETED, {
circuitBreaker: this,
})
.detach();
});
}
isolate() {
return new Task(async () => {
await this.adapter.isolate(this.key);
this.eventDispatcher
.dispatch(CIRCUIT_BREAKER_EVENTS.ISOLATED, {
circuitBreaker: this,
})
.detach();
});
}
}
//# sourceMappingURL=circuit-breaker.js.map