redis-sliding-rate-limiter
Version:
Flexible and performant rate limiter based on sliding window algorithm with arbitrary precision
139 lines (138 loc) • 5.56 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.RateLimiter = void 0;
const lua_1 = require("./lua");
const strategies_1 = require("./strategies");
class RateLimiter {
constructor(options) {
var _a, _b, _c;
this._tag = '[RateLimiter]';
this.get = (key) => __awaiter(this, void 0, void 0, function* () {
return yield this._strategy.execScript(key);
});
if (!options.client) {
throw new Error(`Missing required property 'client'`);
}
if (!options.window || !options.window.hasOwnProperty('unit')) {
throw new Error(`Missing required property 'window.unit'`);
}
if (!options.window || !options.window.hasOwnProperty('size')) {
throw new Error(`Missing required property 'window.size'`);
}
if (!options.limit || options.limit <= 0) {
throw new Error(`Invalid or missing required property 'limit'`);
}
if (options.limitOverhead && options.limitOverhead < 0) {
throw new Error(`Property 'limitOverheadFraction' must be greater or equal than zero`);
}
if (options.window.hasOwnProperty('subdivisionUnit')
&& options.window.subdivisionUnit > options.window.unit) {
throw new Error(`window.subdivisionUnit must be lower or equal to window.unit`);
}
this._client = options.client;
this._windowUnit = options.window.unit;
this._windowSize = options.window.size;
this._windowSubdivisionUnit = (_a = options.window.subdivisionUnit) !== null && _a !== void 0 ? _a : options.window.unit;
this._limit = options.limit;
this._limitOverheadFraction = (_b = options.limitOverhead) !== null && _b !== void 0 ? _b : 0;
this._limitOverhead = Math.floor(this._limit * this._limitOverheadFraction);
this._window = (0, lua_1.convertWindowUnitToSubdivision)(this._windowUnit, this._windowSubdivisionUnit) * this._windowSize;
this._windowExpireMs = lua_1.WindowUnitToMilliseconds[this._windowUnit] * this._windowSize;
this._name = (_c = options.name) !== null && _c !== void 0 ? _c : `${this.windowUnit}_${this.windowSize}_${this.windowSubdivisionUnit}`;
// TODO: Switch strategy based on call function.
// TODO: This is very likely to be broken in the future, a better way should be found ;-)
if (typeof this._client.call === 'function') {
this._strategy = new strategies_1.IORedisStrategy(this);
}
else {
this._strategy = new strategies_1.RedisStrategy(this);
}
}
_updateWindow() {
this._window = (0, lua_1.convertWindowUnitToSubdivision)(this._windowUnit, this._windowSubdivisionUnit) * this._windowSize;
}
_updateWindowExpiration() {
this._windowExpireMs = lua_1.WindowUnitToMilliseconds[this._windowUnit] * this._windowSize;
}
get client() {
return this._client;
}
set client(v) {
this._client = v;
}
get windowUnit() {
return this._windowUnit;
}
set windowUnit(v) {
this._windowUnit = v;
this._updateWindow();
this._updateWindowExpiration();
}
get windowSize() {
return this._windowSize;
}
set windowSize(v) {
this._windowSize = v;
this._updateWindow();
this._updateWindowExpiration();
}
get windowSubdivisionUnit() {
return this._windowSubdivisionUnit;
}
set windowSubdivisionUnit(v) {
if (v > this.windowUnit) {
throw new Error(`Window subdivision must be lower or equal than window unit`);
}
this._windowSubdivisionUnit = v;
this._updateWindow();
}
get limit() {
return this._limit;
}
set limit(v) {
this._limit = v;
this._limitOverhead = Math.floor(this._limit * this._limitOverheadFraction);
}
get limitOverheadFraction() {
return this._limitOverheadFraction;
}
set limitOverheadFraction(v) {
this._limitOverheadFraction = v;
this._limitOverhead = Math.floor(this._limit * this._limitOverheadFraction);
}
get limitOverhead() {
return this._limitOverhead;
}
get window() {
return this._window;
}
get windowExpireMs() {
return this._windowExpireMs;
}
get name() {
return this._name;
}
set name(v) {
this._name = v;
}
toString() {
return JSON.stringify({
windowUnit: this.windowUnit,
windowSize: this.windowSize,
windowSubdivisionUnit: this.windowSubdivisionUnit,
window: this.window,
windowExpireMs: this.windowExpireMs,
limit: this.limit,
}, null, 4);
}
}
exports.RateLimiter = RateLimiter;