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.

106 lines 3.88 kB
/** * @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