// source/memory-store.ts var calculateNextResetTime = (windowMs) => { const resetTime = new Date(); resetTime.setMilliseconds(resetTime.getMilliseconds() + windowMs); return resetTime; }; var MemoryStore = class { init(options) { this.windowMs = options.windowMs; this.resetTime = calculateNextResetTime(this.windowMs); this.hits = {}; const interval = setInterval(async () => { await this.resetAll(); }, this.windowMs); if (interval.unref) { interval.unref(); } } async increment(key) { const totalHits = (this.hits[key] ?? 0) + 1; this.hits[key] = totalHits; return { totalHits, resetTime: this.resetTime }; } async decrement(key) { const current = this.hits[key]; if (current) { this.hits[key] = current - 1; } } async resetKey(key) { delete this.hits[key]; } async resetAll() { this.hits = {}; this.resetTime = calculateNextResetTime(this.windowMs); } }; // source/lib.ts var isLegacyStore = (store) => typeof store.incr === "function" && typeof store.increment !== "function"; var promisifyStore = (passedStore) => { if (!isLegacyStore(passedStore)) { return passedStore; } const legacyStore = passedStore; class PromisifiedStore { async increment(key) { return new Promise((resolve, reject) => { legacyStore.incr(key, (error, totalHits, resetTime) => { if (error) reject(error); resolve({ totalHits, resetTime }); }); }); } async decrement(key) { return Promise.resolve(legacyStore.decrement(key)); } async resetKey(key) { return Promise.resolve(legacyStore.resetKey(key)); } async resetAll() { if (typeof legacyStore.resetAll === "function") return Promise.resolve(legacyStore.resetAll()); } } return new PromisifiedStore(); }; var parseOptions = (passedOptions) => { const options = { windowMs: 60 * 1e3, store: new MemoryStore(), max: 5, message: "Too many requests, please try again later.", statusCode: 429, legacyHeaders: passedOptions.headers ?? true, standardHeaders: passedOptions.draft_polli_ratelimit_headers ?? false, requestPropertyName: "rateLimit", skipFailedRequests: false, skipSuccessfulRequests: false, requestWasSuccessful: (_request, response) => response.statusCode < 400, skip: (_request, _response) => false, keyGenerator: (request, _response) => { if (!request.ip) { console.error("WARN | `express-rate-limit` | `request.ip` is undefined. You can avoid this by providing a custom `keyGenerator` function, but it may be indicative of a larger issue."); } return request.ip; }, handler: (_request, response, _next, _optionsUsed) => { response.status(options.statusCode).send(options.message); }, onLimitReached: (_request, _response, _optionsUsed) => { }, ...passedOptions }; if (typeof options.store.incr !== "function" && typeof options.store.increment !== "function" || typeof options.store.decrement !== "function" || typeof options.store.resetKey !== "function" || typeof options.store.resetAll !== "undefined" && typeof options.store.resetAll !== "function" || typeof options.store.init !== "undefined" && typeof options.store.init !== "function") { throw new TypeError("An invalid store was passed. Please ensure that the store is a class that implements the `Store` interface."); } options.store = promisifyStore(options.store); return options; }; var handleAsyncErrors = (fn) => async (request, response, next) => { try { await Promise.resolve(fn(request, response, next)).catch(next); } catch (error) { next(error); } }; var rateLimit = (passedOptions) => { const options = parseOptions(passedOptions ?? {}); if (typeof options.store.init === "function") options.store.init(options); const middleware = handleAsyncErrors(async (request, response, next) => { const skip = await options.skip(request, response); if (skip) { next(); return; } const augmentedRequest = request; const key = await options.keyGenerator(request, response); const { totalHits, resetTime } = await options.store.increment(key); const retrieveQuota = typeof options.max === "function" ? options.max(request, response) : options.max; const maxHits = await retrieveQuota; augmentedRequest[options.requestPropertyName] = { limit: maxHits, current: totalHits, remaining: Math.max(maxHits - totalHits, 0), resetTime }; if (options.legacyHeaders && !response.headersSent) { response.setHeader("X-RateLimit-Limit", maxHits); response.setHeader("X-RateLimit-Remaining", augmentedRequest[options.requestPropertyName].remaining); if (resetTime instanceof Date) { response.setHeader("Date", new Date().toUTCString()); response.setHeader("X-RateLimit-Reset", Math.ceil(resetTime.getTime() / 1e3)); } } if (options.standardHeaders && !response.headersSent) { response.setHeader("RateLimit-Limit", maxHits); response.setHeader("RateLimit-Remaining", augmentedRequest[options.requestPropertyName].remaining); if (resetTime) { const deltaSeconds = Math.ceil((resetTime.getTime() - Date.now()) / 1e3); response.setHeader("RateLimit-Reset", Math.max(0, deltaSeconds)); } } if (options.skipFailedRequests || options.skipSuccessfulRequests) { let decremented = false; const decrementKey = async () => { if (!decremented) { await options.store.decrement(key); decremented = true; } }; if (options.skipFailedRequests) { response.on("finish", async () => { if (!options.requestWasSuccessful(request, response)) await decrementKey(); }); response.on("close", async () => { if (!response.writableEnded) await decrementKey(); }); response.on("error", async () => { await decrementKey(); }); } if (options.skipSuccessfulRequests) { response.on("finish", async () => { if (options.requestWasSuccessful(request, response)) await decrementKey(); }); } } if (maxHits && totalHits === maxHits + 1) { options.onLimitReached(request, response, options); } if (maxHits && totalHits > maxHits) { if ((options.legacyHeaders || options.standardHeaders) && !response.headersSent) { response.setHeader("Retry-After", Math.ceil(options.windowMs / 1e3)); } options.handler(request, response, next, options); return; } next(); }); middleware.resetKey = options.store.resetKey.bind(options.store); return middleware; }; var lib_default = rateLimit; export { lib_default as default, lib_default as rateLimit };