@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.
106 lines • 3.88 kB
JavaScript
/**
* @module RateLimiter
*/
import {} from "../../../../rate-limiter/contracts/_module.js";
import { TO_MILLISECONDS, } from "../../../../time-span/contracts/_module.js";
import { TimeSpan } from "../../../../time-span/implementations/_module.js";
/**
* @internal
*/
export function resolveSlidingWindowLimiterSettings(settings) {
const { window = TimeSpan.fromSeconds(1), margin = TimeSpan.fromTimeSpan(window).divide(4), } = settings;
return {
window,
margin,
};
}
/**
* @internal
*/
export function serializeSlidingWindowLimiterSettings(settings) {
const { window, margin } = resolveSlidingWindowLimiterSettings(settings);
return {
window: window[TO_MILLISECONDS](),
margin: margin[TO_MILLISECONDS](),
};
}
/**
* Combined approach of `slidingLogs` and `fixedWindow` with lower storage
* costs than `slidingLogs` and improved boundary behavior by calculating a
* weighted score between two windows.
*
* **Pro:**
*
* Good performance allows this to scale to very high loads.
*
* **Con:**
*
* Nothing major.
*
* IMPORT_PATH: `"@daiso-tech/core/rate-limiter/policies"`
* @group Policies
*/
export class SlidingWindowLimiter {
window;
margin;
constructor(settings = {}) {
const { window, margin } = resolveSlidingWindowLimiterSettings(settings);
this.window = TimeSpan.fromTimeSpan(window);
this.margin = TimeSpan.fromTimeSpan(margin);
}
currentWindow(currentDate) {
return (Math.floor(currentDate.getTime() / this.window.toMilliseconds()) *
this.window.toMilliseconds());
}
previousWindow(currentDate) {
return this.currentWindow(currentDate) - this.window.toMilliseconds();
}
cleanup(metrics, currentDate) {
const previousWindow = this.previousWindow(currentDate);
return Object.fromEntries(Object.entries(metrics).filter(([timeStampAsStr]) => {
const timeStamp = Number(timeStampAsStr);
return timeStamp >= previousWindow;
}));
}
currentAttempt(currentMetrics, currentDate) {
return currentMetrics[this.currentWindow(currentDate)] ?? 0;
}
previousAttempt(currentMetrics, currentDate) {
let previousAttempt = currentMetrics[this.previousWindow(currentDate)] ?? 0;
const percentageInCurrentWindow = (currentDate.getTime() % this.window.toMilliseconds()) /
this.window.toMilliseconds();
previousAttempt = Math.floor((1 - percentageInCurrentWindow) * previousAttempt);
return previousAttempt;
}
initialMetrics(currentDate) {
return {
[this.currentWindow(currentDate)]: 0,
};
}
shouldBlock(currentMetrics, limit, currentDate) {
const currentAttempts = this.currentAttempt(currentMetrics, currentDate);
const previousAttempts = this.previousAttempt(currentMetrics, currentDate);
return currentAttempts + previousAttempts >= limit;
}
getExpiration(_currentMetrics, currentDate) {
return this.window
.multiply(2)
.addTimeSpan(this.margin)
.toEndDate(new Date(this.currentWindow(currentDate)));
}
getAttempts(currentMetrics, currentDate) {
const currentAttempt = this.currentAttempt(currentMetrics, currentDate);
const previousAttempt = this.previousAttempt(currentMetrics, currentDate);
return currentAttempt + previousAttempt;
}
updateMetrics(currentMetrics, currentDate) {
currentMetrics = this.cleanup(currentMetrics, currentDate);
const currentAttempt = this.currentAttempt(currentMetrics, currentDate);
const currentKey = this.currentWindow(currentDate);
return {
...currentMetrics,
[currentKey]: currentAttempt + 1,
};
}
}
//# sourceMappingURL=sliding-window-limiter.js.map