UNPKG

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
"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;