express-rate-limit 6.11.2 → 7.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/dist/index.cjs CHANGED
@@ -3,7 +3,6 @@ var __defProp = Object.defineProperty;
3
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
5
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
7
6
  var __export = (target, all) => {
8
7
  for (var name in all)
9
8
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -17,10 +16,6 @@ var __copyProps = (to, from, except, desc) => {
17
16
  return to;
18
17
  };
19
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
- var __publicField = (obj, key, value) => {
21
- __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
22
- return value;
23
- };
24
19
 
25
20
  // source/index.ts
26
21
  var source_exports = {};
@@ -97,9 +92,6 @@ var ValidationError = class extends Error {
97
92
  constructor(code, message) {
98
93
  const url = `https://express-rate-limit.github.io/${code}/`;
99
94
  super(`${message} See ${url} for more information.`);
100
- __publicField(this, "name");
101
- __publicField(this, "code");
102
- __publicField(this, "help");
103
95
  this.name = this.constructor.name;
104
96
  this.code = code;
105
97
  this.help = url;
@@ -107,18 +99,17 @@ var ValidationError = class extends Error {
107
99
  };
108
100
  var ChangeWarning = class extends ValidationError {
109
101
  };
110
- var _Validations = class _Validations {
111
- constructor(enabled) {
112
- // eslint-disable-next-line @typescript-eslint/parameter-properties
113
- __publicField(this, "enabled");
114
- this.enabled = enabled;
115
- }
116
- enable() {
117
- this.enabled = true;
118
- }
102
+ var singleCountKeys = /* @__PURE__ */ new WeakMap();
103
+ var validations = {
104
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
105
+ enabled: {
106
+ default: true
107
+ },
108
+ // Should be EnabledValidations type, but that's a circular reference
119
109
  disable() {
120
- this.enabled = false;
121
- }
110
+ for (const k of Object.keys(this.enabled))
111
+ this.enabled[k] = false;
112
+ },
122
113
  /**
123
114
  * Checks whether the IP address is valid, and that it does not have a port
124
115
  * number in it.
@@ -130,21 +121,19 @@ var _Validations = class _Validations {
130
121
  * @returns {void}
131
122
  */
132
123
  ip(ip) {
133
- this.wrap(() => {
134
- if (ip === void 0) {
135
- throw new ValidationError(
136
- "ERR_ERL_UNDEFINED_IP_ADDRESS",
137
- `An undefined 'request.ip' was detected. This might indicate a misconfiguration or the connection being destroyed prematurely.`
138
- );
139
- }
140
- if (!(0, import_node_net.isIP)(ip)) {
141
- throw new ValidationError(
142
- "ERR_ERL_INVALID_IP_ADDRESS",
143
- `An invalid 'request.ip' (${ip}) was detected. Consider passing a custom 'keyGenerator' function to the rate limiter.`
144
- );
145
- }
146
- });
147
- }
124
+ if (ip === void 0) {
125
+ throw new ValidationError(
126
+ "ERR_ERL_UNDEFINED_IP_ADDRESS",
127
+ `An undefined 'request.ip' was detected. This might indicate a misconfiguration or the connection being destroyed prematurely.`
128
+ );
129
+ }
130
+ if (!(0, import_node_net.isIP)(ip)) {
131
+ throw new ValidationError(
132
+ "ERR_ERL_INVALID_IP_ADDRESS",
133
+ `An invalid 'request.ip' (${ip}) was detected. Consider passing a custom 'keyGenerator' function to the rate limiter.`
134
+ );
135
+ }
136
+ },
148
137
  /**
149
138
  * Makes sure the trust proxy setting is not set to `true`.
150
139
  *
@@ -155,15 +144,13 @@ var _Validations = class _Validations {
155
144
  * @returns {void}
156
145
  */
157
146
  trustProxy(request) {
158
- this.wrap(() => {
159
- if (request.app.get("trust proxy") === true) {
160
- throw new ValidationError(
161
- "ERR_ERL_PERMISSIVE_TRUST_PROXY",
162
- `The Express 'trust proxy' setting is true, which allows anyone to trivially bypass IP-based rate limiting.`
163
- );
164
- }
165
- });
166
- }
147
+ if (request.app.get("trust proxy") === true) {
148
+ throw new ValidationError(
149
+ "ERR_ERL_PERMISSIVE_TRUST_PROXY",
150
+ `The Express 'trust proxy' setting is true, which allows anyone to trivially bypass IP-based rate limiting.`
151
+ );
152
+ }
153
+ },
167
154
  /**
168
155
  * Makes sure the trust proxy setting is set in case the `X-Forwarded-For`
169
156
  * header is present.
@@ -175,31 +162,26 @@ var _Validations = class _Validations {
175
162
  * @returns {void}
176
163
  */
177
164
  xForwardedForHeader(request) {
178
- this.wrap(() => {
179
- if (request.headers["x-forwarded-for"] && request.app.get("trust proxy") === false) {
180
- throw new ValidationError(
181
- "ERR_ERL_UNEXPECTED_X_FORWARDED_FOR",
182
- `The 'X-Forwarded-For' header is set but the Express 'trust proxy' setting is false (default). This could indicate a misconfiguration which would prevent express-rate-limit from accurately identifying users.`
183
- );
184
- }
185
- });
186
- }
165
+ if (request.headers["x-forwarded-for"] && request.app.get("trust proxy") === false) {
166
+ throw new ValidationError(
167
+ "ERR_ERL_UNEXPECTED_X_FORWARDED_FOR",
168
+ `The 'X-Forwarded-For' header is set but the Express 'trust proxy' setting is false (default). This could indicate a misconfiguration which would prevent express-rate-limit from accurately identifying users.`
169
+ );
170
+ }
171
+ },
187
172
  /**
188
173
  * Ensures totalHits value from store is a positive integer.
189
174
  *
190
175
  * @param hits {any} - The `totalHits` returned by the store.
191
176
  */
192
177
  positiveHits(hits) {
193
- this.wrap(() => {
194
- if (typeof hits !== "number" || hits < 1 || hits !== Math.round(hits)) {
195
- throw new ValidationError(
196
- "ERR_ERL_INVALID_HITS",
197
- `The totalHits value returned from the store must be a positive integer, got ${hits}`
198
- // eslint-disable-line @typescript-eslint/restrict-template-expressions
199
- );
200
- }
201
- });
202
- }
178
+ if (typeof hits !== "number" || hits < 1 || hits !== Math.round(hits)) {
179
+ throw new ValidationError(
180
+ "ERR_ERL_INVALID_HITS",
181
+ `The totalHits value returned from the store must be a positive integer, got ${hits}`
182
+ );
183
+ }
184
+ },
203
185
  /**
204
186
  * Ensures a given key is incremented only once per request.
205
187
  *
@@ -210,83 +192,74 @@ var _Validations = class _Validations {
210
192
  * @returns {void}
211
193
  */
212
194
  singleCount(request, store, key) {
213
- this.wrap(() => {
214
- var _a;
215
- let storeKeys = _Validations.singleCountKeys.get(request);
216
- if (!storeKeys) {
217
- storeKeys = /* @__PURE__ */ new Map();
218
- _Validations.singleCountKeys.set(request, storeKeys);
219
- }
220
- const storeKey = store.localKeys ? store : store.constructor.name;
221
- let keys = storeKeys.get(storeKey);
222
- if (!keys) {
223
- keys = [];
224
- storeKeys.set(storeKey, keys);
225
- }
226
- const prefixedKey = `${(_a = store.prefix) != null ? _a : ""}${key}`;
227
- if (keys.includes(prefixedKey)) {
228
- throw new ValidationError(
229
- "ERR_ERL_DOUBLE_COUNT",
230
- `The hit count for ${key} was incremented more than once for a single request.`
231
- );
232
- }
233
- keys.push(prefixedKey);
234
- });
235
- }
195
+ let storeKeys = singleCountKeys.get(request);
196
+ if (!storeKeys) {
197
+ storeKeys = /* @__PURE__ */ new Map();
198
+ singleCountKeys.set(request, storeKeys);
199
+ }
200
+ const storeKey = store.localKeys ? store : store.constructor.name;
201
+ let keys = storeKeys.get(storeKey);
202
+ if (!keys) {
203
+ keys = [];
204
+ storeKeys.set(storeKey, keys);
205
+ }
206
+ const prefixedKey = `${store.prefix ?? ""}${key}`;
207
+ if (keys.includes(prefixedKey)) {
208
+ throw new ValidationError(
209
+ "ERR_ERL_DOUBLE_COUNT",
210
+ `The hit count for ${key} was incremented more than once for a single request.`
211
+ );
212
+ }
213
+ keys.push(prefixedKey);
214
+ },
236
215
  /**
237
- * Warns the user that the behaviour for `max: 0` is changing in the next
216
+ * Warns the user that the behaviour for `max: 0` / `limit: 0` is changing in the next
238
217
  * major release.
239
218
  *
240
- * @param max {number} - The maximum number of hits per client.
219
+ * @param limit {number} - The maximum number of hits per client.
241
220
  *
242
221
  * @returns {void}
243
222
  */
244
- max(max) {
245
- this.wrap(() => {
246
- if (max === 0) {
247
- throw new ChangeWarning(
248
- "WRN_ERL_MAX_ZERO",
249
- `Setting max to 0 disables rate limiting in express-rate-limit v6 and older, but will cause all requests to be blocked in v7`
250
- );
251
- }
252
- });
253
- }
223
+ limit(limit) {
224
+ if (limit === 0) {
225
+ throw new ChangeWarning(
226
+ "WRN_ERL_MAX_ZERO",
227
+ `Setting limit or max to 0 disables rate limiting in express-rate-limit v6 and older, but will cause all requests to be blocked in v7`
228
+ );
229
+ }
230
+ },
254
231
  /**
255
232
  * Warns the user that the `draft_polli_ratelimit_headers` option is deprecated
256
233
  * and will be removed in the next major release.
257
234
  *
258
- * @param draft_polli_ratelimit_headers {boolean|undefined} - The now-deprecated setting that was used to enable standard headers.
235
+ * @param draft_polli_ratelimit_headers {any | undefined} - The now-deprecated setting that was used to enable standard headers.
259
236
  *
260
237
  * @returns {void}
261
238
  */
262
239
  draftPolliHeaders(draft_polli_ratelimit_headers) {
263
- this.wrap(() => {
264
- if (draft_polli_ratelimit_headers) {
265
- throw new ChangeWarning(
266
- "WRN_ERL_DEPRECATED_DRAFT_POLLI_HEADERS",
267
- `The draft_polli_ratelimit_headers configuration option is deprecated and will be removed in express-rate-limit v7, please set standardHeaders: 'draft-6' instead.`
268
- );
269
- }
270
- });
271
- }
240
+ if (draft_polli_ratelimit_headers) {
241
+ throw new ChangeWarning(
242
+ "WRN_ERL_DEPRECATED_DRAFT_POLLI_HEADERS",
243
+ `The draft_polli_ratelimit_headers configuration option is deprecated and has been removed in express-rate-limit v7, please set standardHeaders: 'draft-6' instead.`
244
+ );
245
+ }
246
+ },
272
247
  /**
273
248
  * Warns the user that the `onLimitReached` option is deprecated and will be removed in the next
274
249
  * major release.
275
250
  *
276
- * @param onLimitReached {function|undefined} - The maximum number of hits per client.
251
+ * @param onLimitReached {any | undefined} - The maximum number of hits per client.
277
252
  *
278
253
  * @returns {void}
279
254
  */
280
255
  onLimitReached(onLimitReached) {
281
- this.wrap(() => {
282
- if (onLimitReached) {
283
- throw new ChangeWarning(
284
- "WRN_ERL_DEPRECATED_ON_LIMIT_REACHED",
285
- `The onLimitReached configuration option is deprecated and will be removed in express-rate-limit v7.`
286
- );
287
- }
288
- });
289
- }
256
+ if (onLimitReached) {
257
+ throw new ChangeWarning(
258
+ "WRN_ERL_DEPRECATED_ON_LIMIT_REACHED",
259
+ `The onLimitReached configuration option is deprecated and has been removed in express-rate-limit v7.`
260
+ );
261
+ }
262
+ },
290
263
  /**
291
264
  * Warns the user when the selected headers option requires a reset time but
292
265
  * the store does not provide one.
@@ -296,71 +269,93 @@ var _Validations = class _Validations {
296
269
  * @returns {void}
297
270
  */
298
271
  headersResetTime(resetTime) {
299
- this.wrap(() => {
300
- if (!resetTime) {
272
+ if (!resetTime) {
273
+ throw new ValidationError(
274
+ "ERR_ERL_HEADERS_NO_RESET",
275
+ `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.`
276
+ );
277
+ }
278
+ },
279
+ /**
280
+ * Checks the options.validate setting to ensure that only recognized validations are enabled or disabled.
281
+ *
282
+ * If any unrecognized values are found, an error is logged that includes the list of supported vaidations.
283
+ */
284
+ validationsConfig() {
285
+ const supportedValidations = Object.keys(this).filter(
286
+ (k) => !["enabled", "disable"].includes(k)
287
+ );
288
+ supportedValidations.push("default");
289
+ for (const key of Object.keys(this.enabled)) {
290
+ if (!supportedValidations.includes(key)) {
301
291
  throw new ValidationError(
302
- "ERR_ERL_HEADERS_NO_RESET",
303
- `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.`
292
+ "ERR_ERL_UNKNOWN_VALIDATION",
293
+ `options.validate.${key} is not recognized. Supported validate options are: ${supportedValidations.join(
294
+ ", "
295
+ )}.`
304
296
  );
305
297
  }
306
- });
307
- }
308
- wrap(validation) {
309
- if (!this.enabled) {
310
- return;
311
- }
312
- try {
313
- validation.call(this);
314
- } catch (error) {
315
- if (error instanceof ChangeWarning)
316
- console.warn(error);
317
- else
318
- console.error(error);
319
298
  }
320
299
  }
321
300
  };
322
- /**
323
- * Maps the key used in a store for a certain request, and ensures that the
324
- * same key isn't used more than once per request.
325
- *
326
- * The store can be any one of the following:
327
- * - An instance, for stores like the MemoryStore where two instances do not
328
- * share state.
329
- * - A string (class name), for stores where multiple instances
330
- * typically share state, such as the Redis store.
331
- */
332
- __publicField(_Validations, "singleCountKeys", /* @__PURE__ */ new WeakMap());
333
- var Validations = _Validations;
301
+ var getValidations = (_enabled) => {
302
+ let enabled;
303
+ if (typeof _enabled === "boolean") {
304
+ enabled = {
305
+ default: _enabled
306
+ };
307
+ } else {
308
+ enabled = {
309
+ default: true,
310
+ ..._enabled
311
+ };
312
+ }
313
+ const wrappedValidations = {
314
+ enabled
315
+ };
316
+ for (const [name, validation] of Object.entries(validations)) {
317
+ if (typeof validation === "function")
318
+ wrappedValidations[name] = (...args) => {
319
+ if (!(enabled[name] ?? enabled.default)) {
320
+ return;
321
+ }
322
+ try {
323
+ ;
324
+ validation.apply(
325
+ wrappedValidations,
326
+ args
327
+ );
328
+ } catch (error) {
329
+ if (error instanceof ChangeWarning)
330
+ console.warn(error);
331
+ else
332
+ console.error(error);
333
+ }
334
+ };
335
+ }
336
+ return wrappedValidations;
337
+ };
334
338
 
335
339
  // source/memory-store.ts
336
- var calculateNextResetTime = (windowMs) => {
337
- const resetTime = /* @__PURE__ */ new Date();
338
- resetTime.setMilliseconds(resetTime.getMilliseconds() + windowMs);
339
- return resetTime;
340
- };
341
340
  var MemoryStore = class {
342
341
  constructor() {
343
342
  /**
344
- * The duration of time before which all hit counts are reset (in milliseconds).
345
- */
346
- __publicField(this, "windowMs");
347
- /**
348
- * The map that stores the number of hits for each client in memory.
349
- */
350
- __publicField(this, "hits");
351
- /**
352
- * The time at which all hit counts will be reset.
353
- */
354
- __publicField(this, "resetTime");
355
- /**
356
- * Reference to the active timer.
343
+ * These two maps store usage (requests) and reset time by key (for example, IP
344
+ * addresses or API keys).
345
+ *
346
+ * They are split into two to avoid having to iterate through the entire set to
347
+ * determine which ones need reset. Instead, `Client`s are moved from `previous`
348
+ * to `current` as they hit the endpoint. Once `windowMs` has elapsed, all clients
349
+ * left in `previous`, i.e., those that have not made any recent requests, are
350
+ * known to be expired and can be deleted in bulk.
357
351
  */
358
- __publicField(this, "interval");
352
+ this.previous = /* @__PURE__ */ new Map();
353
+ this.current = /* @__PURE__ */ new Map();
359
354
  /**
360
355
  * Confirmation that the keys incremented in once instance of MemoryStore
361
356
  * cannot affect other instances.
362
357
  */
363
- __publicField(this, "localKeys", true);
358
+ this.localKeys = true;
364
359
  }
365
360
  /**
366
361
  * Method that initializes the store.
@@ -369,10 +364,10 @@ var MemoryStore = class {
369
364
  */
370
365
  init(options) {
371
366
  this.windowMs = options.windowMs;
372
- this.resetTime = calculateNextResetTime(this.windowMs);
373
- this.hits = {};
374
- this.interval = setInterval(async () => {
375
- await this.resetAll();
367
+ if (this.interval)
368
+ clearInterval(this.interval);
369
+ this.interval = setInterval(() => {
370
+ this.clearExpired();
376
371
  }, this.windowMs);
377
372
  if (this.interval.unref)
378
373
  this.interval.unref();
@@ -387,12 +382,7 @@ var MemoryStore = class {
387
382
  * @public
388
383
  */
389
384
  async get(key) {
390
- if (this.hits[key] !== void 0)
391
- return {
392
- totalHits: this.hits[key],
393
- resetTime: this.resetTime
394
- };
395
- return void 0;
385
+ return this.current.get(key) ?? this.previous.get(key);
396
386
  }
397
387
  /**
398
388
  * Method to increment a client's hit counter.
@@ -404,13 +394,13 @@ var MemoryStore = class {
404
394
  * @public
405
395
  */
406
396
  async increment(key) {
407
- var _a;
408
- const totalHits = ((_a = this.hits[key]) != null ? _a : 0) + 1;
409
- this.hits[key] = totalHits;
410
- return {
411
- totalHits,
412
- resetTime: this.resetTime
413
- };
397
+ const client = this.getClient(key);
398
+ const now = Date.now();
399
+ if (client.resetTime.getTime() <= now) {
400
+ this.resetClient(client, now);
401
+ }
402
+ client.totalHits++;
403
+ return client;
414
404
  }
415
405
  /**
416
406
  * Method to decrement a client's hit counter.
@@ -420,9 +410,9 @@ var MemoryStore = class {
420
410
  * @public
421
411
  */
422
412
  async decrement(key) {
423
- const current = this.hits[key];
424
- if (current)
425
- this.hits[key] = current - 1;
413
+ const client = this.getClient(key);
414
+ if (client.totalHits > 0)
415
+ client.totalHits--;
426
416
  }
427
417
  /**
428
418
  * Method to reset a client's hit counter.
@@ -432,7 +422,8 @@ var MemoryStore = class {
432
422
  * @public
433
423
  */
434
424
  async resetKey(key) {
435
- delete this.hits[key];
425
+ this.current.delete(key);
426
+ this.previous.delete(key);
436
427
  }
437
428
  /**
438
429
  * Method to reset everyone's hit counter.
@@ -440,8 +431,8 @@ var MemoryStore = class {
440
431
  * @public
441
432
  */
442
433
  async resetAll() {
443
- this.hits = {};
444
- this.resetTime = calculateNextResetTime(this.windowMs);
434
+ this.current.clear();
435
+ this.previous.clear();
445
436
  }
446
437
  /**
447
438
  * Method to stop the timer (if currently running) and prevent any memory
@@ -451,6 +442,55 @@ var MemoryStore = class {
451
442
  */
452
443
  shutdown() {
453
444
  clearInterval(this.interval);
445
+ void this.resetAll();
446
+ }
447
+ /**
448
+ * Recycles a client by setting its hit count to zero, and reset time to
449
+ * `windowMs` milliseconds from now.
450
+ *
451
+ * NOT to be confused with `#resetKey()`, which removes a client from both the
452
+ * `current` and `previous` maps.
453
+ *
454
+ * @param client {Client} - The client to recycle.
455
+ * @param now {number} - The current time, to which the `windowMs` is added to get the `resetTime` for the client.
456
+ *
457
+ * @return {Client} - The modified client that was passed in, to allow for chaining.
458
+ */
459
+ resetClient(client, now = Date.now()) {
460
+ client.totalHits = 0;
461
+ client.resetTime.setTime(now + this.windowMs);
462
+ return client;
463
+ }
464
+ /**
465
+ * Retrieves or creates a client, given a key. Also ensures that the client being
466
+ * returned is in the `current` map.
467
+ *
468
+ * @param key {string} - The key under which the client is (or is to be) stored.
469
+ *
470
+ * @returns {Client} - The requested client.
471
+ */
472
+ getClient(key) {
473
+ if (this.current.has(key))
474
+ return this.current.get(key);
475
+ let client;
476
+ if (this.previous.has(key)) {
477
+ client = this.previous.get(key);
478
+ this.previous.delete(key);
479
+ } else {
480
+ client = { totalHits: 0, resetTime: /* @__PURE__ */ new Date() };
481
+ this.resetClient(client);
482
+ }
483
+ this.current.set(key, client);
484
+ return client;
485
+ }
486
+ /**
487
+ * Move current clients to previous, create a new map for current.
488
+ *
489
+ * This function is called every `windowMs`.
490
+ */
491
+ clearExpired() {
492
+ this.previous = this.current;
493
+ this.current = /* @__PURE__ */ new Map();
454
494
  }
455
495
  };
456
496
 
@@ -497,10 +537,10 @@ var promisifyStore = (passedStore) => {
497
537
  return new PromisifiedStore();
498
538
  };
499
539
  var getOptionsFromConfig = (config) => {
500
- const { validations, ...directlyPassableEntries } = config;
540
+ const { validations: validations2, ...directlyPassableEntries } = config;
501
541
  return {
502
542
  ...directlyPassableEntries,
503
- validate: validations.enabled
543
+ validate: validations2.enabled
504
544
  };
505
545
  };
506
546
  var omitUndefinedOptions = (passedOptions) => {
@@ -514,32 +554,33 @@ var omitUndefinedOptions = (passedOptions) => {
514
554
  return omittedOptions;
515
555
  };
516
556
  var parseOptions = (passedOptions) => {
517
- var _a, _b, _c, _d;
518
557
  const notUndefinedOptions = omitUndefinedOptions(passedOptions);
519
- const validations = new Validations((_a = notUndefinedOptions == null ? void 0 : notUndefinedOptions.validate) != null ? _a : true);
520
- validations.draftPolliHeaders(
558
+ const validations2 = getValidations(notUndefinedOptions?.validate ?? true);
559
+ validations2.validationsConfig();
560
+ validations2.draftPolliHeaders(
561
+ // @ts-expect-error see the note above.
521
562
  notUndefinedOptions.draft_polli_ratelimit_headers
522
563
  );
523
- validations.onLimitReached(notUndefinedOptions.onLimitReached);
524
- let standardHeaders = (_b = notUndefinedOptions.standardHeaders) != null ? _b : false;
525
- if (standardHeaders === true || standardHeaders === void 0 && notUndefinedOptions.draft_polli_ratelimit_headers) {
564
+ validations2.onLimitReached(notUndefinedOptions.onLimitReached);
565
+ let standardHeaders = notUndefinedOptions.standardHeaders ?? false;
566
+ if (standardHeaders === true)
526
567
  standardHeaders = "draft-6";
527
- }
528
568
  const config = {
529
569
  windowMs: 60 * 1e3,
530
- max: 5,
570
+ limit: passedOptions.max ?? 5,
571
+ // `max` is deprecated, but support it anyways.
531
572
  message: "Too many requests, please try again later.",
532
573
  statusCode: 429,
533
- legacyHeaders: (_c = passedOptions.headers) != null ? _c : true,
574
+ legacyHeaders: passedOptions.headers ?? true,
534
575
  requestPropertyName: "rateLimit",
535
576
  skipFailedRequests: false,
536
577
  skipSuccessfulRequests: false,
537
578
  requestWasSuccessful: (_request, response) => response.statusCode < 400,
538
579
  skip: (_request, _response) => false,
539
580
  keyGenerator(request, _response) {
540
- validations.ip(request.ip);
541
- validations.trustProxy(request);
542
- validations.xForwardedForHeader(request);
581
+ validations2.ip(request.ip);
582
+ validations2.trustProxy(request);
583
+ validations2.xForwardedForHeader(request);
543
584
  return request.ip;
544
585
  },
545
586
  async handler(request, response, _next, _optionsUsed) {
@@ -552,17 +593,15 @@ var parseOptions = (passedOptions) => {
552
593
  response.send(message);
553
594
  }
554
595
  },
555
- onLimitReached(_request, _response, _optionsUsed) {
556
- },
557
596
  // Allow the default options to be overriden by the options passed to the middleware.
558
597
  ...notUndefinedOptions,
559
598
  // `standardHeaders` is resolved into a draft version above, use that.
560
599
  standardHeaders,
561
600
  // Note that this field is declared after the user's options are spread in,
562
601
  // so that this field doesn't get overriden with an un-promisified store!
563
- store: promisifyStore((_d = notUndefinedOptions.store) != null ? _d : new MemoryStore()),
602
+ store: promisifyStore(notUndefinedOptions.store ?? new MemoryStore()),
564
603
  // Print an error to the console if a few known misconfigurations are detected.
565
- validations
604
+ validations: validations2
566
605
  };
567
606
  if (typeof config.store.increment !== "function" || typeof config.store.decrement !== "function" || typeof config.store.resetKey !== "function" || config.store.resetAll !== void 0 && typeof config.store.resetAll !== "function" || config.store.init !== void 0 && typeof config.store.init !== "function") {
568
607
  throw new TypeError(
@@ -579,8 +618,7 @@ var handleAsyncErrors = (fn) => async (request, response, next) => {
579
618
  }
580
619
  };
581
620
  var rateLimit = (passedOptions) => {
582
- var _a;
583
- const config = parseOptions(passedOptions != null ? passedOptions : {});
621
+ const config = parseOptions(passedOptions ?? {});
584
622
  const options = getOptionsFromConfig(config);
585
623
  if (typeof config.store.init === "function")
586
624
  config.store.init(options);
@@ -596,15 +634,20 @@ var rateLimit = (passedOptions) => {
596
634
  const { totalHits, resetTime } = await config.store.increment(key);
597
635
  config.validations.positiveHits(totalHits);
598
636
  config.validations.singleCount(request, config.store, key);
599
- const retrieveQuota = typeof config.max === "function" ? config.max(request, response) : config.max;
600
- const maxHits = await retrieveQuota;
601
- config.validations.max(maxHits);
637
+ const retrieveLimit = typeof config.limit === "function" ? config.limit(request, response) : config.limit;
638
+ const limit = await retrieveLimit;
639
+ config.validations.limit(limit);
602
640
  const info = {
603
- limit: maxHits,
604
- current: totalHits,
605
- remaining: Math.max(maxHits - totalHits, 0),
641
+ limit,
642
+ used: totalHits,
643
+ remaining: Math.max(limit - totalHits, 0),
606
644
  resetTime
607
645
  };
646
+ Object.defineProperty(info, "current", {
647
+ configurable: false,
648
+ enumerable: false,
649
+ value: totalHits
650
+ });
608
651
  augmentedRequest[config.requestPropertyName] = info;
609
652
  if (config.legacyHeaders && !response.headersSent) {
610
653
  setLegacyHeaders(response, info);
@@ -645,11 +688,8 @@ var rateLimit = (passedOptions) => {
645
688
  });
646
689
  }
647
690
  }
648
- if (maxHits && totalHits === maxHits + 1) {
649
- config.onLimitReached(request, response, options);
650
- }
651
691
  config.validations.disable();
652
- if (maxHits && totalHits > maxHits) {
692
+ if (totalHits > limit) {
653
693
  if (config.legacyHeaders || config.standardHeaders) {
654
694
  setRetryAfterHeader(response, info, config.windowMs);
655
695
  }
@@ -660,7 +700,7 @@ var rateLimit = (passedOptions) => {
660
700
  }
661
701
  );
662
702
  middleware.resetKey = config.store.resetKey.bind(config.store);
663
- middleware.getKey = (_a = config.store.get) == null ? void 0 : _a.bind(
703
+ middleware.getKey = config.store.get?.bind(
664
704
  config.store
665
705
  );
666
706
  return middleware;