@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.
160 lines • 5.76 kB
JavaScript
/**
* @module RateLimiter
*/
import {} from "../../../../event-bus/contracts/_module.js";
import {} from "../../../../execution-context/contracts/_module.js";
import {} from "../../../../namespace/contracts/_module.js";
import { BlockedRateLimiterError, RATE_LIMITER_EVENTS, RATE_LIMITER_STATE, } from "../../../../rate-limiter/contracts/_module.js";
import { callErrorPolicyOnThrow, callInvokable, resolveAsyncLazyable, } from "../../../../utilities/_module.js";
/**
* @internal
*/
export class RateLimiter {
/**
* @internal
*/
static _serialize(deserializedValue) {
return {
version: "1",
key: deserializedValue._key.get(),
limit: deserializedValue._limit,
};
}
waitUntil;
_key;
_limit;
errorPolicy;
onlyError;
adapter;
eventDispatcher;
enableAsyncTracking;
serdeTransformerName;
namespace;
executionContext;
constructor(settings) {
const { limit, enableAsyncTracking, eventDispatcher, key, errorPolicy, onlyError, adapter, serdeTransformerName, namespace, waitUntil, executionContext, } = settings;
this.executionContext = executionContext;
this.waitUntil = waitUntil;
this.namespace = namespace;
this.serdeTransformerName = serdeTransformerName;
this._limit = limit;
this.enableAsyncTracking = enableAsyncTracking;
this.eventDispatcher = eventDispatcher;
this._key = key;
this.errorPolicy = errorPolicy;
this.onlyError = onlyError;
this.adapter = adapter;
}
_getNamespace() {
return this.namespace;
}
_getSerdeTransformerName() {
return this.serdeTransformerName;
}
_getAdapter() {
return this.adapter;
}
toRateLimiterState(state) {
if (state === null) {
return {
type: RATE_LIMITER_STATE.EXPIRED,
};
}
if (state.success) {
return {
type: RATE_LIMITER_STATE.ALLOWED,
usedAttempts: state.attempt,
remainingAttempts: this.limit - state.attempt,
limit: this.limit,
resetAfter: state.resetTime,
};
}
return {
type: RATE_LIMITER_STATE.BLOCKED,
limit: this.limit,
totalAttempts: state.attempt,
exceedAttempts: state.attempt - this.limit,
retryAfter: state.resetTime,
};
}
async getState() {
const state = await this.adapter.getState(this.executionContext, this._key.toString());
return this.toRateLimiterState(state);
}
get key() {
return this._key;
}
get limit() {
return this._limit;
}
async trackErrorWrapper(asyncFn) {
const state = this.toRateLimiterState(await this.adapter.getState(this.executionContext, this._key.toString()));
if (state.type === RATE_LIMITER_STATE.BLOCKED) {
callInvokable(this.waitUntil, this.eventDispatcher.dispatch(RATE_LIMITER_EVENTS.BLOCKED, {
rateLimiter: this,
}));
const { type: _type, ...rest } = state;
throw BlockedRateLimiterError.create(rest, this._key);
}
try {
callInvokable(this.waitUntil, this.eventDispatcher.dispatch(RATE_LIMITER_EVENTS.ALLOWED, {
rateLimiter: this,
}));
return await resolveAsyncLazyable(asyncFn);
}
catch (error) {
const isErrorMatching = await callErrorPolicyOnThrow(this.errorPolicy, error);
if (isErrorMatching) {
callInvokable(this.waitUntil, this.eventDispatcher.dispatch(RATE_LIMITER_EVENTS.TRACKED_FAILURE, {
rateLimiter: this,
error,
}));
}
else {
callInvokable(this.waitUntil, this.eventDispatcher.dispatch(RATE_LIMITER_EVENTS.UNTRACKED_FAILURE, {
rateLimiter: this,
error,
}));
}
if (isErrorMatching) {
const fn = async () => {
await this.adapter.updateState(this.executionContext, this._key.toString(), this.limit);
};
if (this.enableAsyncTracking) {
callInvokable(this.waitUntil, fn());
}
else {
await fn();
}
}
throw error;
}
}
async trackWrapper(asyncFn) {
const state = this.toRateLimiterState(await this.adapter.updateState(this.executionContext, this._key.toString(), this.limit));
if (state.type === RATE_LIMITER_STATE.BLOCKED) {
callInvokable(this.waitUntil, this.eventDispatcher.dispatch(RATE_LIMITER_EVENTS.BLOCKED, {
rateLimiter: this,
}));
const { type: _type, ...rest } = state;
throw BlockedRateLimiterError.create(rest, this._key);
}
callInvokable(this.waitUntil, this.eventDispatcher.dispatch(RATE_LIMITER_EVENTS.ALLOWED, {
rateLimiter: this,
}));
return await resolveAsyncLazyable(asyncFn);
}
async runOrFail(asyncFn) {
if (this.onlyError) {
return await this.trackErrorWrapper(asyncFn);
}
return await this.trackWrapper(asyncFn);
}
async reset() {
await this.adapter.reset(this.executionContext, this._key.toString());
callInvokable(this.waitUntil, this.eventDispatcher.dispatch(RATE_LIMITER_EVENTS.RESETED, {
rateLimiter: this,
}));
}
}
//# sourceMappingURL=rate-limiter.js.map