@prexjs/rate-limit 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Prexjs (https://prexjs.com)
4
+ Copyright (c) 2026 Sorabh (https://sorabh.org)
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,102 @@
1
- # Temporary Holding Version
1
+ # @prexjs/rate-limit
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ **Configurable rate limiting middleware for Node.js, Express, and Prex to protect endpoints from abuse and brute force.**
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@prexjs/rate-limit)](https://www.npmjs.com/package/@prexjs/rate-limit)
6
+ [![npm downloads](https://img.shields.io/npm/dm/@prexjs/rate-limit)](https://www.npmjs.com/package/@prexjs/rate-limit)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](./LICENSE)
8
+ [![TypeScript](https://img.shields.io/badge/TypeScript-supported-blue)](https://www.typescriptlang.org/)
9
+
10
+ `@prexjs/rate-limit` is a flexible rate-limiting middleware for Node.js HTTP servers, Prex, and Express. It tracks client request frequencies within rolling time windows to protect endpoints against denial of service, brute force attacks, and abusive scraping.
11
+
12
+ ## Features
13
+
14
+ - **Sliding & Fixed Windows**: Configurable time window (`windowMs`) with convenient constants (`MINUTE`, `HOUR`, `DAY`).
15
+ - **Store Abstraction**: Ships with high-performance `MemoryStore`, and supports custom external stores (e.g. Redis, Memcached).
16
+ - **Standard Rate Limit Headers**: Automatically sends `RateLimit-*` and `Retry-After` headers conforming to draft RFC standards.
17
+ - **Custom Key Generation**: Identify clients by IP address with IPv6 subnet normalization, API keys, or custom session tokens.
18
+ - **Granular Handler & Skip Rules**: Conditionally bypass limits or customize rejection payloads and status codes.
19
+
20
+ ## Installation
21
+
22
+ Install using your preferred package manager:
23
+
24
+ ```bash
25
+ # npm
26
+ npm install @prexjs/rate-limit
27
+
28
+ # pnpm
29
+ pnpm add @prexjs/rate-limit
30
+
31
+ # Yarn
32
+ yarn add @prexjs/rate-limit
33
+
34
+ # Bun
35
+ bun add @prexjs/rate-limit
36
+ ```
37
+
38
+ ## Quick start
39
+
40
+ ```ts
41
+ import http from 'node:http';
42
+ import rateLimit, { MINUTE } from '@prexjs/rate-limit';
43
+
44
+ // Allow a maximum of 100 requests per 15 minutes
45
+ const limiter = rateLimit({
46
+ windowMs: 15 * MINUTE,
47
+ limit: 100,
48
+ standardHeaders: 'draft-7', // draft-6, draft-7, or combined
49
+ legacyHeaders: false,
50
+ message: { error: 'Too many requests, please try again later.' },
51
+ });
52
+
53
+ const server = http.createServer((req, res) => {
54
+ limiter(req as any, res as any, (err) => {
55
+ if (err) {
56
+ res.statusCode = 500;
57
+ res.end('Server error');
58
+ return;
59
+ }
60
+
61
+ res.end('Welcome! Request allowed.');
62
+ });
63
+ });
64
+
65
+ server.listen(3000);
66
+ ```
67
+
68
+ ## API
69
+
70
+ ### `rateLimit(options)`
71
+
72
+ Creates the rate-limiting middleware.
73
+
74
+ - `windowMs`: Time frame for which requests are checked/remembered in milliseconds (e.g. `15 * MINUTE`).
75
+ - `limit`: Maximum number of connections during `windowMs` (or a function returning a number).
76
+ - `message`: Response body sent when a client exceeds the limit (string, object, or custom function).
77
+ - `statusCode`: HTTP status returned upon limit breach (default: `429`).
78
+ - `keyGenerator`: Function `(req, res) => string` used to generate unique client identifiers (defaults to IP address).
79
+ - `skip`: Function `(req, res) => boolean` to conditionally bypass rate limiting.
80
+ - `store`: Storage engine instance (defaults to `new MemoryStore()`).
81
+
82
+ ### Exported Constants
83
+
84
+ - `SECOND`: `1000` ms
85
+ - `MINUTE`: `60 * 1000` ms
86
+ - `HOUR`: `60 * 60 * 1000` ms
87
+ - `DAY`: `24 * 60 * 60 * 1000` ms
88
+
89
+ ## Compatibility
90
+
91
+ - Node.js >= 18.0.0
92
+ - Supports both ESM and CommonJS
93
+ - TypeScript 5.0+
94
+
95
+ ## Related Packages
96
+
97
+ - [`@prexjs/ipaddress`](https://github.com/prexjs/prex.js/tree/main/packages/ipaddress): IP address parser and CIDR calculator.
98
+ - [`@prexjs/debug`](https://github.com/prexjs/prex.js/tree/main/packages/debug): Small debugging utility.
99
+
100
+ ## License
101
+
102
+ [MIT](./LICENSE)
package/lib/index.cjs ADDED
@@ -0,0 +1,7 @@
1
+ /*!
2
+ * @prexjs/rate-limit
3
+ * Copyright (c) 2026 Prexjs (https://prexjs.com)
4
+ * Copyright (c) 2026 Sorabh (https://Sorabh.org)
5
+ * MIT Licensed
6
+ */
7
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("node:net"),t=require("@prexjs/ipaddress"),r=require("@prexjs/debug"),i=require("node:buffer"),s=require("node:crypto");function n(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var a=n(r);const o=new t.Address6("::/96");function d(r,i=56){if(e.isIPv6(r)){const e=new t.Address6(r);if(e.isMapped4()||e.is4()&&e.isInSubnet(o))return e.to4().correctForm();if(i){return new t.Address6(`${r}/${i}`).networkForm()}}return r}class c{validations;windowMs;previous=new Map;current=new Map;interval;localKeys=!0;constructor(e){this.validations=e}init(e){this.windowMs=e.windowMs,this.validations?.windowMs(this.windowMs),this.interval&&clearInterval(this.interval),this.interval=setInterval(()=>{this.clearExpired()},this.windowMs),this.interval.unref?.()}async get(e){return this.current.get(e)??this.previous.get(e)}async increment(e){const t=this.getClient(e),r=Date.now();return t.resetTime.getTime()<=r&&this.resetClient(t,r),t.totalHits++,t}async decrement(e){const t=this.getClient(e);t.totalHits>0&&t.totalHits--}async resetKey(e){this.current.delete(e),this.previous.delete(e)}async resetAll(){this.current.clear(),this.previous.clear()}shutdown(){clearInterval(this.interval),this.resetAll()}resetClient(e,t=Date.now()){return e.totalHits=0,e.resetTime.setTime(t+this.windowMs),e}getClient(e){if(this.current.has(e))return this.current.get(e);let t;return this.previous.has(e)?(t=this.previous.get(e),this.previous.delete(e)):(t={totalHits:0,resetTime:new Date},this.resetClient(t)),this.current.set(e,t),t}clearExpired(){this.previous=this.current,this.current=new Map}}const l={warn(...e){console.warn(...e.reverse())},error(...e){console.error(...e.reverse())}},u=["draft-6","draft-7","draft-8"],f=(e,t)=>{let r;if(t){const e=Math.ceil((t.getTime()-Date.now())/1e3);r=Math.max(0,e)}else r=Math.ceil(e/1e3);return r},p=(e,t,r,n,a)=>{if(e.headersSent)return;const o=Math.ceil(r/1e3),d=f(r,t.resetTime),c=(e=>{const t=s.createHash("sha256");t.update(e);const r=t.digest("hex").slice(0,12);return i.Buffer.from(r).toString("base64")})(a),l=`r=${t.remaining}; t=${d}`,u=`q=${t.limit}; w=${o}; pk=:${c}:`;e.append("RateLimit",`"${n}"; ${l}`),e.append("RateLimit-Policy",`"${n}"; ${u}`)};class h extends Error{name;code;help;constructor(e,t){const r=`https://rate-limit.github.io/${e}/`;super(`${t} See ${r} for more information.`),this.name=this.constructor.name,this.code=e,this.help=r}}class m extends h{}const w=new Set,y=new WeakMap,R={enabled:{default:!0},disable(){for(const e of Object.keys(this.enabled))this.enabled[e]=!1},ip(t){if(void 0===t)throw new h("ERR_ERL_UNDEFINED_IP_ADDRESS","An undefined 'request.ip' was detected. This might indicate a misconfiguration or the connection being destroyed prematurely.");if(!e.isIP(t))throw new h("ERR_ERL_INVALID_IP_ADDRESS",`An invalid 'request.ip' (${t}) was detected. Consider passing a custom 'keyGenerator' function to the rate limiter.`)},trustProxy(e){if(!0===e.app.get("trust proxy"))throw new h("ERR_ERL_PERMISSIVE_TRUST_PROXY","The @prexjs/core 'trust proxy' setting is true, which allows anyone to trivially bypass IP-based rate limiting.")},xForwardedForHeader(e){if(e.headers["x-forwarded-for"]&&!1===e.app.get("trust proxy"))throw new h("ERR_ERL_UNEXPECTED_X_FORWARDED_FOR","The 'X-Forwarded-For' header is set but the @prexjs/core 'trust proxy' setting is false (default). This could indicate a misconfiguration which would prevent rate-limit from accurately identifying users.")},forwardedHeader(e){if(e.headers.forwarded&&e.ip===e.socket?.remoteAddress)throw new h("ERR_ERL_FORWARDED_HEADER","The 'Forwarded' header (standardized X-Forwarded-For) is set but currently being ignored. Add a custom keyGenerator to use a value from this header.")},positiveHits(e){if("number"!=typeof e||e<1||e!==Math.round(e))throw new h("ERR_ERL_INVALID_HITS",`The totalHits value returned from the store must be a positive integer, got ${e}`)},unsharedStore(e){if(w.has(e)){const t=e?.localKeys?"":" (with a unique prefix)";throw new h("ERR_ERL_STORE_REUSE",`A Store instance must not be shared across multiple rate limiters. Create a new instance of ${e.constructor.name}${t} for each limiter instead.`)}w.add(e)},singleCount(e,t,r){let i=y.get(e);i||(i=new Map,y.set(e,i));const s=t.localKeys?t:t.constructor.name;let n=i.get(s);n||(n=[],i.set(s,n));const a=`${t.prefix??""}${r}`;if(n.includes(a))throw new h("ERR_ERL_DOUBLE_COUNT",`The hit count for ${r} was incremented more than once for a single request.`);n.push(a)},limit(e){if(0===e)throw new m("WRN_ERL_MAX_ZERO","Setting limit or max to 0 disables rate limiting in rate-limit v6 and older, but will cause all requests to be blocked in v7")},draftPolliHeaders(e){if(e)throw new m("WRN_ERL_DEPRECATED_DRAFT_POLLI_HEADERS","The draft_polli_ratelimit_headers configuration option is deprecated and has been removed in rate-limit v7, please set standardHeaders: 'draft-6' instead.")},onLimitReached(e){if(e)throw new m("WRN_ERL_DEPRECATED_ON_LIMIT_REACHED","The onLimitReached configuration option is deprecated and has been removed in rate-limit v7.")},headersDraftVersion(e){if("string"!=typeof e||!u.includes(e)){const e=u.join(", ");throw new h("ERR_ERL_HEADERS_UNSUPPORTED_DRAFT_VERSION",`standardHeaders: only the following versions of the IETF draft specification are supported: ${e}.`)}},headersResetTime(e){if(!e)throw new h("ERR_ERL_HEADERS_NO_RESET","standardHeaders: 'draft-7' requires a 'resetTime', but the store did not provide one. The 'windowMs' value will be used instead, which may cause clients to wait longer than necessary.")},knownOptions(e){if(!e)return;const t=Object.keys({windowMs:!0,limit:!0,message:!0,statusCode:!0,legacyHeaders:!0,standardHeaders:!0,identifier:!0,retryAfter:!0,requestPropertyName:!0,skipFailedRequests:!0,skipSuccessfulRequests:!0,keyGenerator:!0,ipv6Subnet:!0,handler:!0,skip:!0,requestWasSuccessful:!0,store:!0,validate:!0,headers:!0,max:!0,passOnStoreError:!0,logger:!0}).concat("draft_polli_ratelimit_headers","delayAfter","delayMs","maxDelayMs");for(const r of Object.keys(e))if(!t.includes(r))throw new h("ERR_ERL_UNKNOWN_OPTION",`Unexpected configuration option: ${r}`)},validationsConfig(){const e=Object.keys(this).filter(e=>!["enabled","disable"].includes(e));e.push("default");for(const t of Object.keys(this.enabled))if(!e.includes(t))throw new h("ERR_ERL_UNKNOWN_VALIDATION",`options.validate.${t} is not recognized. Supported validate options are: ${e.join(", ")}.`)},creationStack(e){const{stack:t}=new Error("rate-limit validation check (set options.validate.creationStack=false to disable)");if(t?.includes("Layer.handle [as handle_request]")||t?.includes("Layer.handleRequest")){if(!e.localKeys)throw new h("ERR_ERL_CREATED_IN_REQUEST_HANDLER","rate-limit instance should *usually* be created at app initialization, not when responding to a request.");throw new h("ERR_ERL_CREATED_IN_REQUEST_HANDLER","rate-limit instance should be created at app initialization, not when responding to a request.")}},ipv6Subnet(e){if(!1!==e&&(!Number.isInteger(e)||e<32||e>64))throw new h("ERR_ERL_IPV6_SUBNET",`Unexpected ipv6Subnet value: ${e}. Expected an integer between 32 and 64 (usually 48-64).`)},ipv6SubnetOrKeyGenerator(e){if(void 0!==e.ipv6Subnet&&e.keyGenerator)throw new h("ERR_ERL_IPV6SUBNET_OR_KEYGENERATOR","Incompatible options: the 'ipv6Subnet' option is ignored when a custom 'keyGenerator' function is also set.")},keyGeneratorIpFallback(e){if(!e)return;const t=e.toString();if((t.includes("req.ip")||t.includes("request.ip"))&&!t.includes("ipKeyGenerator"))throw new h("ERR_ERL_KEY_GEN_IPV6","Custom keyGenerator appears to use request IP without calling the ipKeyGenerator helper function for IPv6 addresses. This could allow IPv6 users to bypass limits.")},windowMs(e){const t=2**31-1;if("number"!=typeof e||Number.isNaN(e)||e<1||e>t)throw new h("ERR_ERL_WINDOW_MS",`Invalid windowMs value: ${e}${"number"!=typeof e?` (${typeof e})`:""}, must be a number between 1 and 2147483647 when using the default MemoryStore`)}};const g=(e,t)=>{let r;!function(e){if("object"!=typeof e||"function"!=typeof e.error||"function"!=typeof e.warn)throw new TypeError("Provided logger does not implement the Logger interface")}(t),r="boolean"==typeof e?{default:e}:{default:!0,...e};const i={enabled:r};for(const[e,s]of Object.entries(R))"function"==typeof s&&(i[e]=(...n)=>{if(r[e]??r.default){r[e]=!1;try{s.apply(i,n)}catch(e){e instanceof m?t.warn(e):t.error(e)}}});const s=Symbol.for("nodejs.util.inspect.custom");return s&&(i[s]=()=>i.enabled),i},E=e=>{if("function"!=typeof(t=e).incr||"function"==typeof t.increment)return e;var t;const r=e;return new class{async increment(e){return new Promise((t,i)=>{r.incr(e,(e,r,s)=>{e&&i(e),t({totalHits:r,resetTime:s})})})}async decrement(e){return r.decrement(e)}async resetKey(e){return r.resetKey(e)}async resetAll(){if("function"==typeof r.resetAll)return r.resetAll()}}},v=t=>{const r=(e=>{const t={};for(const r of Object.keys(e)){const i=r;void 0!==e[i]&&(t[i]=e[i])}return t})(t),i=t.logger??l,s=g(r?.validate??!0,i);s.validationsConfig(),s.knownOptions(t),s.draftPolliHeaders(r.draft_polli_ratelimit_headers),s.onLimitReached(r.onLimitReached),void 0!==r.ipv6Subnet&&"function"!=typeof r.ipv6Subnet&&s.ipv6Subnet(r.ipv6Subnet),s.keyGeneratorIpFallback(r.keyGenerator),s.ipv6SubnetOrKeyGenerator(r);let n=r.standardHeaders??!1;!0===n&&(n="draft-6");const a={windowMs:6e4,limit:t.max??5,message:"Too many requests, please try again later.",statusCode:429,legacyHeaders:t.headers??!0,identifier(e,t){let r="";const i=a.requestPropertyName,{limit:s}=e[i],n=a.windowMs/1e3,o=a.windowMs/6e4,d=a.windowMs/36e5,c=a.windowMs/864e5;return r=n<60?`${n}sec`:o<60?`${o}min`:d<24?`${d}hr${d>1?"s":""}`:`${c}day${c>1?"s":""}`,`${s}-in-${r}`},requestPropertyName:"rateLimit",skipFailedRequests:!1,skipSuccessfulRequests:!1,requestWasSuccessful:(e,t)=>t.statusCode<400,skip:(e,t)=>!1,async keyGenerator(t,r){s.ip(t.ip),s.trustProxy(t),s.xForwardedForHeader(t),s.forwardedHeader(t);const i=t.ip;let n=56;return e.isIPv6(i)&&(n="function"==typeof a.ipv6Subnet?await a.ipv6Subnet(t,r):a.ipv6Subnet,"function"==typeof a.ipv6Subnet&&s.ipv6Subnet(n)),d(i,n)},ipv6Subnet:56,async handler(e,t,r,i){t.status(a.statusCode);const s="function"==typeof a.message?await a.message(e,t):a.message;t.writableEnded||t.send(s)},passOnStoreError:!1,...r,standardHeaders:n,store:E(r.store??new c(s)),validations:s,logger:i};if("function"!=typeof a.store.increment||"function"!=typeof a.store.decrement||"function"!=typeof a.store.resetKey||void 0!==a.store.resetAll&&"function"!=typeof a.store.resetAll||void 0!==a.store.init&&"function"!=typeof a.store.init)throw new TypeError("An invalid store was passed. Please ensure that the store is a class that implements the `Store` interface.");return a},_=e=>{const t=v(e??{}),r=(e=>{const{validations:t,...r}=e;return{...r,validate:t.enabled}})(t),i=a.default("rate-limit");i("creating new rate limiter with %o",t.store.constructor.name);for(const[e,r]of Object.entries(t))i("set %s to %o",e,r);if(t.validations.creationStack(t.store),t.validations.unsharedStore(t.store),"function"==typeof t.store.init){i("executing init for store");try{const e=t.store.init(r);e instanceof Promise&&e.catch(e=>t.logger.error(e,"rate-limit: async error during store initialization."))}catch(e){t.logger.error(e,"rate-limit: error during store initialization.")}}const s=(n=async(e,s,n)=>{const a=t.skipFailedRequests&&new Promise(e=>s.once("close",e)),o=(t.skipFailedRequests||t.skipSuccessfulRequests)&&new Promise(e=>s.once("finish",e)),d=t.skipFailedRequests&&new Promise(e=>s.once("error",e));if(i("requested %o",e.originalUrl),i("request from ip %o",e.ip),await t.skip(e,s))return i("skipping request"),void n();const c=e,l=await t.keyGenerator(e,s);i("computed key %o",l),i("incrementing count");let u,h=0;try{const e=await t.store.increment(l);h=e.totalHits,u=e.resetTime}catch(e){if(t.passOnStoreError)return t.logger.error(e,"rate-limit: error from store, allowing request without rate-limiting."),void n();throw e}t.validations.positiveHits(h),t.validations.singleCount(e,t.store,l);const m="function"==typeof t.limit?t.limit(e,s):t.limit,w=await m;t.validations.limit(w);const y={limit:w,used:h,remaining:Math.max(w-h,0),resetTime:u,key:l};if(i.enabled)for(const[e,r]of Object.entries(y))i("set request.%s.%s to be %o",t.requestPropertyName,e,r);if(Object.defineProperty(y,"current",{configurable:!1,enumerable:!1,value:h}),c[t.requestPropertyName]=y,t.legacyHeaders&&!s.headersSent&&(i("set legacy headers"),((e,t)=>{e.headersSent||(e.setHeader("X-RateLimit-Limit",t.limit.toString()),e.setHeader("X-RateLimit-Remaining",t.remaining.toString()),t.resetTime instanceof Date&&(e.setHeader("Date",(new Date).toUTCString()),e.setHeader("X-RateLimit-Reset",Math.ceil(t.resetTime.getTime()/1e3).toString())))})(s,y)),t.standardHeaders&&!s.headersSent)switch(t.standardHeaders){case"draft-6":i("set ietf draft 6 headers"),((e,t,r)=>{if(e.headersSent)return;const i=Math.ceil(r/1e3),s=f(r,t.resetTime);e.setHeader("RateLimit-Policy",`${t.limit};w=${i}`),e.setHeader("RateLimit-Limit",t.limit.toString()),e.setHeader("RateLimit-Remaining",t.remaining.toString()),"number"==typeof s&&e.setHeader("RateLimit-Reset",s.toString())})(s,y,t.windowMs);break;case"draft-7":i("set ietf draft 7 headers"),t.validations.headersResetTime(y.resetTime),((e,t,r)=>{if(e.headersSent)return;const i=Math.ceil(r/1e3),s=f(r,t.resetTime);e.setHeader("RateLimit-Policy",`${t.limit};w=${i}`),e.setHeader("RateLimit",`limit=${t.limit}, remaining=${t.remaining}, reset=${s}`)})(s,y,t.windowMs);break;case"draft-8":{const r="function"==typeof t.identifier?t.identifier(e,s):t.identifier,n=await r;i("set ietf draft 8 headers"),i("set name to %o",n),t.validations.headersResetTime(y.resetTime),p(s,y,t.windowMs,n,l);break}default:t.validations.headersDraftVersion(t.standardHeaders)}if(t.skipFailedRequests||t.skipSuccessfulRequests){let r=!1;const n=async()=>{if(!r){if(u&&Date.now()>=u.getTime())return;i("decrementing count"),await t.store.decrement(l),r=!0}};t.skipFailedRequests&&(o&&o.then(async()=>{const r=await t.requestWasSuccessful(e,s);i("computed requestWasSuccessful as %o",r),r||await n()}),a&&a.then(async()=>{s.writableEnded||await n()}),d&&d.then(async()=>{await n()})),t.skipSuccessfulRequests&&o&&o.then(async()=>{const r=await t.requestWasSuccessful(e,s);i("computed requestWasSuccessful as %o",r),r&&await n()})}if(h>w){if(i("limit exceeded"),t.legacyHeaders||t.standardHeaders){i("set retry-after header");const r="function"==typeof t.retryAfter?t.retryAfter(e,s):t.retryAfter,n=await r;((e,t,r,i)=>{if(e.headersSent)return;const s=i??f(r,t.resetTime);e.setHeader("Retry-After",s.toString())})(s,y,t.windowMs,n)}t.handler(e,s,n,r)}else n()},async(e,t,r)=>{try{await Promise.resolve(n(e,t,r)).catch(r)}catch(e){r(e)}});var n;return s.resetKey=t.store.resetKey.bind(t.store),s.getKey="function"==typeof t.store.get?t.store.get.bind(t.store):()=>{throw new Error("The current store does not support the get/getKey method")},s},b=36e5;exports.DAY=864e5,exports.HOUR=b,exports.MINUTE=6e4,exports.MemoryStore=c,exports.SECOND=1e3,exports.default=_,exports.ipKeyGenerator=d,exports.rateLimit=_;