@upstash/ratelimit 2.0.2 → 2.0.4

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.mjs CHANGED
@@ -26,10 +26,10 @@ var Analytics = class {
26
26
  * @returns
27
27
  */
28
28
  extractGeo(req) {
29
- if (typeof req.geo !== "undefined") {
29
+ if (req.geo !== void 0) {
30
30
  return req.geo;
31
31
  }
32
- if (typeof req.cf !== "undefined") {
32
+ if (req.cf !== void 0) {
33
33
  return req.cf;
34
34
  }
35
35
  return {};
@@ -118,54 +118,203 @@ function ms(d) {
118
118
  const time = Number.parseInt(match[1]);
119
119
  const unit = match[2];
120
120
  switch (unit) {
121
- case "ms":
121
+ case "ms": {
122
122
  return time;
123
- case "s":
123
+ }
124
+ case "s": {
124
125
  return time * 1e3;
125
- case "m":
126
+ }
127
+ case "m": {
126
128
  return time * 1e3 * 60;
127
- case "h":
129
+ }
130
+ case "h": {
128
131
  return time * 1e3 * 60 * 60;
129
- case "d":
132
+ }
133
+ case "d": {
130
134
  return time * 1e3 * 60 * 60 * 24;
131
- default:
135
+ }
136
+ default: {
132
137
  throw new Error(`Unable to parse window size: ${d}`);
138
+ }
133
139
  }
134
140
  }
135
141
 
136
142
  // src/hash.ts
137
- var setHash = async (ctx, script, kind) => {
138
- const regionContexts = "redis" in ctx ? [ctx] : ctx.regionContexts;
139
- const hashSample = regionContexts[0].scriptHashes[kind];
140
- if (!hashSample) {
141
- await Promise.all(regionContexts.map(async (context) => {
142
- context.scriptHashes[kind] = await context.redis.scriptLoad(script);
143
- }));
144
- }
145
- ;
146
- };
147
- var safeEval = async (ctx, script, kind, keys, args) => {
148
- if (!ctx.cacheScripts) {
149
- return await ctx.redis.eval(script, keys, args);
150
- }
151
- ;
152
- await setHash(ctx, script, kind);
143
+ var safeEval = async (ctx, script, keys, args) => {
153
144
  try {
154
- return await ctx.redis.evalsha(ctx.scriptHashes[kind], keys, args);
145
+ return await ctx.redis.evalsha(script.hash, keys, args);
155
146
  } catch (error) {
156
147
  if (`${error}`.includes("NOSCRIPT")) {
157
- console.log("Script with the expected hash was not found in redis db. It is probably flushed. Will load another scipt before continuing.");
158
- ctx.scriptHashes[kind] = void 0;
159
- await setHash(ctx, script, kind);
160
- console.log(" New script successfully loaded.");
161
- return await ctx.redis.evalsha(ctx.scriptHashes[kind], keys, args);
148
+ const hash = await ctx.redis.scriptLoad(script.script);
149
+ if (hash !== script.hash) {
150
+ console.warn(
151
+ "Upstash Ratelimit: Expected hash and the hash received from Redis are different. Ratelimit will work as usual but performance will be reduced."
152
+ );
153
+ }
154
+ return await ctx.redis.evalsha(hash, keys, args);
162
155
  }
163
156
  throw error;
164
157
  }
165
158
  };
166
159
 
167
- // src/lua-scripts/multi.ts
160
+ // src/lua-scripts/single.ts
168
161
  var fixedWindowLimitScript = `
162
+ local key = KEYS[1]
163
+ local window = ARGV[1]
164
+ local incrementBy = ARGV[2] -- increment rate per request at a given value, default is 1
165
+
166
+ local r = redis.call("INCRBY", key, incrementBy)
167
+ if r == tonumber(incrementBy) then
168
+ -- The first time this key is set, the value will be equal to incrementBy.
169
+ -- So we only need the expire command once
170
+ redis.call("PEXPIRE", key, window)
171
+ end
172
+
173
+ return r
174
+ `;
175
+ var fixedWindowRemainingTokensScript = `
176
+ local key = KEYS[1]
177
+ local tokens = 0
178
+
179
+ local value = redis.call('GET', key)
180
+ if value then
181
+ tokens = value
182
+ end
183
+ return tokens
184
+ `;
185
+ var slidingWindowLimitScript = `
186
+ local currentKey = KEYS[1] -- identifier including prefixes
187
+ local previousKey = KEYS[2] -- key of the previous bucket
188
+ local tokens = tonumber(ARGV[1]) -- tokens per window
189
+ local now = ARGV[2] -- current timestamp in milliseconds
190
+ local window = ARGV[3] -- interval in milliseconds
191
+ local incrementBy = ARGV[4] -- increment rate per request at a given value, default is 1
192
+
193
+ local requestsInCurrentWindow = redis.call("GET", currentKey)
194
+ if requestsInCurrentWindow == false then
195
+ requestsInCurrentWindow = 0
196
+ end
197
+
198
+ local requestsInPreviousWindow = redis.call("GET", previousKey)
199
+ if requestsInPreviousWindow == false then
200
+ requestsInPreviousWindow = 0
201
+ end
202
+ local percentageInCurrent = ( now % window ) / window
203
+ -- weighted requests to consider from the previous window
204
+ requestsInPreviousWindow = math.floor(( 1 - percentageInCurrent ) * requestsInPreviousWindow)
205
+ if requestsInPreviousWindow + requestsInCurrentWindow >= tokens then
206
+ return -1
207
+ end
208
+
209
+ local newValue = redis.call("INCRBY", currentKey, incrementBy)
210
+ if newValue == tonumber(incrementBy) then
211
+ -- The first time this key is set, the value will be equal to incrementBy.
212
+ -- So we only need the expire command once
213
+ redis.call("PEXPIRE", currentKey, window * 2 + 1000) -- Enough time to overlap with a new window + 1 second
214
+ end
215
+ return tokens - ( newValue + requestsInPreviousWindow )
216
+ `;
217
+ var slidingWindowRemainingTokensScript = `
218
+ local currentKey = KEYS[1] -- identifier including prefixes
219
+ local previousKey = KEYS[2] -- key of the previous bucket
220
+ local now = ARGV[1] -- current timestamp in milliseconds
221
+ local window = ARGV[2] -- interval in milliseconds
222
+
223
+ local requestsInCurrentWindow = redis.call("GET", currentKey)
224
+ if requestsInCurrentWindow == false then
225
+ requestsInCurrentWindow = 0
226
+ end
227
+
228
+ local requestsInPreviousWindow = redis.call("GET", previousKey)
229
+ if requestsInPreviousWindow == false then
230
+ requestsInPreviousWindow = 0
231
+ end
232
+
233
+ local percentageInCurrent = ( now % window ) / window
234
+ -- weighted requests to consider from the previous window
235
+ requestsInPreviousWindow = math.floor(( 1 - percentageInCurrent ) * requestsInPreviousWindow)
236
+
237
+ return requestsInPreviousWindow + requestsInCurrentWindow
238
+ `;
239
+ var tokenBucketLimitScript = `
240
+ local key = KEYS[1] -- identifier including prefixes
241
+ local maxTokens = tonumber(ARGV[1]) -- maximum number of tokens
242
+ local interval = tonumber(ARGV[2]) -- size of the window in milliseconds
243
+ local refillRate = tonumber(ARGV[3]) -- how many tokens are refilled after each interval
244
+ local now = tonumber(ARGV[4]) -- current timestamp in milliseconds
245
+ local incrementBy = tonumber(ARGV[5]) -- how many tokens to consume, default is 1
246
+
247
+ local bucket = redis.call("HMGET", key, "refilledAt", "tokens")
248
+
249
+ local refilledAt
250
+ local tokens
251
+
252
+ if bucket[1] == false then
253
+ refilledAt = now
254
+ tokens = maxTokens
255
+ else
256
+ refilledAt = tonumber(bucket[1])
257
+ tokens = tonumber(bucket[2])
258
+ end
259
+
260
+ if now >= refilledAt + interval then
261
+ local numRefills = math.floor((now - refilledAt) / interval)
262
+ tokens = math.min(maxTokens, tokens + numRefills * refillRate)
263
+
264
+ refilledAt = refilledAt + numRefills * interval
265
+ end
266
+
267
+ if tokens == 0 then
268
+ return {-1, refilledAt + interval}
269
+ end
270
+
271
+ local remaining = tokens - incrementBy
272
+ local expireAt = math.ceil(((maxTokens - remaining) / refillRate)) * interval
273
+
274
+ redis.call("HSET", key, "refilledAt", refilledAt, "tokens", remaining)
275
+ redis.call("PEXPIRE", key, expireAt)
276
+ return {remaining, refilledAt + interval}
277
+ `;
278
+ var tokenBucketIdentifierNotFound = -1;
279
+ var tokenBucketRemainingTokensScript = `
280
+ local key = KEYS[1]
281
+ local maxTokens = tonumber(ARGV[1])
282
+
283
+ local bucket = redis.call("HMGET", key, "refilledAt", "tokens")
284
+
285
+ if bucket[1] == false then
286
+ return {maxTokens, ${tokenBucketIdentifierNotFound}}
287
+ end
288
+
289
+ return {tonumber(bucket[2]), tonumber(bucket[1])}
290
+ `;
291
+ var cachedFixedWindowLimitScript = `
292
+ local key = KEYS[1]
293
+ local window = ARGV[1]
294
+ local incrementBy = ARGV[2] -- increment rate per request at a given value, default is 1
295
+
296
+ local r = redis.call("INCRBY", key, incrementBy)
297
+ if r == incrementBy then
298
+ -- The first time this key is set, the value will be equal to incrementBy.
299
+ -- So we only need the expire command once
300
+ redis.call("PEXPIRE", key, window)
301
+ end
302
+
303
+ return r
304
+ `;
305
+ var cachedFixedWindowRemainingTokenScript = `
306
+ local key = KEYS[1]
307
+ local tokens = 0
308
+
309
+ local value = redis.call('GET', key)
310
+ if value then
311
+ tokens = value
312
+ end
313
+ return tokens
314
+ `;
315
+
316
+ // src/lua-scripts/multi.ts
317
+ var fixedWindowLimitScript2 = `
169
318
  local key = KEYS[1]
170
319
  local id = ARGV[1]
171
320
  local window = ARGV[2]
@@ -181,7 +330,7 @@ var fixedWindowLimitScript = `
181
330
 
182
331
  return fields
183
332
  `;
184
- var fixedWindowRemainingTokensScript = `
333
+ var fixedWindowRemainingTokensScript2 = `
185
334
  local key = KEYS[1]
186
335
  local tokens = 0
187
336
 
@@ -189,7 +338,7 @@ var fixedWindowRemainingTokensScript = `
189
338
 
190
339
  return fields
191
340
  `;
192
- var slidingWindowLimitScript = `
341
+ var slidingWindowLimitScript2 = `
193
342
  local currentKey = KEYS[1] -- identifier including prefixes
194
343
  local previousKey = KEYS[2] -- key of the previous bucket
195
344
  local tokens = tonumber(ARGV[1]) -- tokens per window
@@ -224,7 +373,7 @@ var slidingWindowLimitScript = `
224
373
  end
225
374
  return {currentFields, previousFields, true}
226
375
  `;
227
- var slidingWindowRemainingTokensScript = `
376
+ var slidingWindowRemainingTokensScript2 = `
228
377
  local currentKey = KEYS[1] -- identifier including prefixes
229
378
  local previousKey = KEYS[2] -- key of the previous bucket
230
379
  local now = ARGV[1] -- current timestamp in milliseconds
@@ -273,6 +422,78 @@ var resetScript = `
273
422
  until cursor == "0"
274
423
  `;
275
424
 
425
+ // src/lua-scripts/hash.ts
426
+ var SCRIPTS = {
427
+ singleRegion: {
428
+ fixedWindow: {
429
+ limit: {
430
+ script: fixedWindowLimitScript,
431
+ hash: "b13943e359636db027ad280f1def143f02158c13"
432
+ },
433
+ getRemaining: {
434
+ script: fixedWindowRemainingTokensScript,
435
+ hash: "8c4c341934502aee132643ffbe58ead3450e5208"
436
+ }
437
+ },
438
+ slidingWindow: {
439
+ limit: {
440
+ script: slidingWindowLimitScript,
441
+ hash: "e1391e429b699c780eb0480350cd5b7280fd9213"
442
+ },
443
+ getRemaining: {
444
+ script: slidingWindowRemainingTokensScript,
445
+ hash: "65a73ac5a05bf9712903bc304b77268980c1c417"
446
+ }
447
+ },
448
+ tokenBucket: {
449
+ limit: {
450
+ script: tokenBucketLimitScript,
451
+ hash: "5bece90aeef8189a8cfd28995b479529e270b3c6"
452
+ },
453
+ getRemaining: {
454
+ script: tokenBucketRemainingTokensScript,
455
+ hash: "a15be2bb1db2a15f7c82db06146f9d08983900d0"
456
+ }
457
+ },
458
+ cachedFixedWindow: {
459
+ limit: {
460
+ script: cachedFixedWindowLimitScript,
461
+ hash: "c26b12703dd137939b9a69a3a9b18e906a2d940f"
462
+ },
463
+ getRemaining: {
464
+ script: cachedFixedWindowRemainingTokenScript,
465
+ hash: "8e8f222ccae68b595ee6e3f3bf2199629a62b91a"
466
+ }
467
+ }
468
+ },
469
+ multiRegion: {
470
+ fixedWindow: {
471
+ limit: {
472
+ script: fixedWindowLimitScript2,
473
+ hash: "a8c14f3835aa87bd70e5e2116081b81664abcf5c"
474
+ },
475
+ getRemaining: {
476
+ script: fixedWindowRemainingTokensScript2,
477
+ hash: "8ab8322d0ed5fe5ac8eb08f0c2e4557f1b4816fd"
478
+ }
479
+ },
480
+ slidingWindow: {
481
+ limit: {
482
+ script: slidingWindowLimitScript2,
483
+ hash: "cb4fdc2575056df7c6d422764df0de3a08d6753b"
484
+ },
485
+ getRemaining: {
486
+ script: slidingWindowRemainingTokensScript2,
487
+ hash: "558c9306b7ec54abb50747fe0b17e5d44bd24868"
488
+ }
489
+ }
490
+ }
491
+ };
492
+ var RESET_SCRIPT = {
493
+ script: resetScript,
494
+ hash: "54bd274ddc59fb3be0f42deee2f64322a10e2b50"
495
+ };
496
+
276
497
  // src/types.ts
277
498
  var DenyListExtension = "denyList";
278
499
  var IpDenyListKey = "ipDenyList";
@@ -356,7 +577,7 @@ var updateIpDenyList = async (redis, prefix, threshold, ttl) => {
356
577
  const transaction = redis.multi();
357
578
  transaction.sdiffstore(allDenyLists, allDenyLists, ipDenyList);
358
579
  transaction.del(ipDenyList);
359
- transaction.sadd(ipDenyList, ...allIps);
580
+ transaction.sadd(ipDenyList, allIps.at(0), ...allIps.slice(1));
360
581
  transaction.sdiffstore(ipDenyList, ipDenyList, allDenyLists);
361
582
  transaction.sunionstore(allDenyLists, allDenyLists, ipDenyList);
362
583
  transaction.set(statusKey, "valid", { px: ttl ?? getIpListTTL() });
@@ -458,7 +679,7 @@ var Ratelimit = class {
458
679
  }) : void 0;
459
680
  if (config.ephemeralCache instanceof Map) {
460
681
  this.ctx.cache = new Cache(config.ephemeralCache);
461
- } else if (typeof config.ephemeralCache === "undefined") {
682
+ } else if (config.ephemeralCache === void 0) {
462
683
  this.ctx.cache = new Cache(/* @__PURE__ */ new Map());
463
684
  }
464
685
  }
@@ -589,15 +810,10 @@ var Ratelimit = class {
589
810
  const key = this.getKey(identifier);
590
811
  const definedMembers = this.getDefinedMembers(identifier, req);
591
812
  const deniedValue = checkDenyListCache(definedMembers);
592
- let result;
593
- if (deniedValue) {
594
- result = [defaultDeniedResponse(deniedValue), { deniedValue, invalidIpDenyList: false }];
595
- } else {
596
- result = await Promise.all([
597
- this.limiter().limit(this.ctx, key, req?.rate),
598
- this.enableProtection ? checkDenyList(this.primaryRedis, this.prefix, definedMembers) : { deniedValue: void 0, invalidIpDenyList: false }
599
- ]);
600
- }
813
+ const result = deniedValue ? [defaultDeniedResponse(deniedValue), { deniedValue, invalidIpDenyList: false }] : await Promise.all([
814
+ this.limiter().limit(this.ctx, key, req?.rate),
815
+ this.enableProtection ? checkDenyList(this.primaryRedis, this.prefix, definedMembers) : { deniedValue: void 0, invalidIpDenyList: false }
816
+ ]);
601
817
  return resolveLimitPayload(this.primaryRedis, this.prefix, result, this.denyListThreshold);
602
818
  };
603
819
  /**
@@ -647,9 +863,9 @@ var Ratelimit = class {
647
863
  time: Date.now(),
648
864
  success: ratelimitResponse.reason === "denyList" ? "denied" : ratelimitResponse.success,
649
865
  ...geo
650
- }).catch((err) => {
866
+ }).catch((error) => {
651
867
  let errorMessage = "Failed to record analytics";
652
- if (`${err}`.includes("WRONGTYPE")) {
868
+ if (`${error}`.includes("WRONGTYPE")) {
653
869
  errorMessage = `
654
870
  Failed to record analytics. See the information below:
655
871
 
@@ -662,11 +878,11 @@ var Ratelimit = class {
662
878
 
663
879
  `;
664
880
  }
665
- console.warn(errorMessage, err);
881
+ console.warn(errorMessage, error);
666
882
  });
667
883
  ratelimitResponse.pending = Promise.all([ratelimitResponse.pending, analyticsP]);
668
- } catch (err) {
669
- console.warn("Failed to record analytics", err);
884
+ } catch (error) {
885
+ console.warn("Failed to record analytics", error);
670
886
  }
671
887
  ;
672
888
  }
@@ -712,9 +928,7 @@ var MultiRegionRatelimit = class extends Ratelimit {
712
928
  analytics: config.analytics,
713
929
  ctx: {
714
930
  regionContexts: config.redis.map((redis) => ({
715
- redis,
716
- scriptHashes: {},
717
- cacheScripts: config.cacheScripts ?? true
931
+ redis
718
932
  })),
719
933
  cache: config.ephemeralCache ? new Cache(config.ephemeralCache) : void 0
720
934
  }
@@ -763,8 +977,7 @@ var MultiRegionRatelimit = class extends Ratelimit {
763
977
  redis: regionContext.redis,
764
978
  request: safeEval(
765
979
  regionContext,
766
- fixedWindowLimitScript,
767
- "limitHash",
980
+ SCRIPTS.multiRegion.fixedWindow.limit,
768
981
  [key],
769
982
  [requestId, windowDuration, incrementBy]
770
983
  )
@@ -780,18 +993,17 @@ var MultiRegionRatelimit = class extends Ratelimit {
780
993
  const remaining = tokens - usedTokens;
781
994
  async function sync() {
782
995
  const individualIDs = await Promise.all(dbs.map((s) => s.request));
783
- const allIDs = Array.from(
784
- new Set(
785
- individualIDs.flatMap((_) => _).reduce((acc, curr, index) => {
786
- if (index % 2 === 0) {
787
- acc.push(curr);
788
- }
789
- return acc;
790
- }, [])
791
- ).values()
792
- );
996
+ const allIDs = [...new Set(
997
+ individualIDs.flat().reduce((acc, curr, index) => {
998
+ if (index % 2 === 0) {
999
+ acc.push(curr);
1000
+ }
1001
+ return acc;
1002
+ }, [])
1003
+ ).values()];
793
1004
  for (const db of dbs) {
794
- const usedDbTokens = (await db.request).reduce(
1005
+ const usedDbTokensRequest = await db.request;
1006
+ const usedDbTokens = usedDbTokensRequest.reduce(
795
1007
  (accTokens, usedToken, index) => {
796
1008
  let parsedToken = 0;
797
1009
  if (index % 2) {
@@ -801,7 +1013,8 @@ var MultiRegionRatelimit = class extends Ratelimit {
801
1013
  },
802
1014
  0
803
1015
  );
804
- const dbIds = (await db.request).reduce((ids, currentId, index) => {
1016
+ const dbIdsRequest = await db.request;
1017
+ const dbIds = dbIdsRequest.reduce((ids, currentId, index) => {
805
1018
  if (index % 2 === 0) {
806
1019
  ids.push(currentId);
807
1020
  }
@@ -839,8 +1052,7 @@ var MultiRegionRatelimit = class extends Ratelimit {
839
1052
  redis: regionContext.redis,
840
1053
  request: safeEval(
841
1054
  regionContext,
842
- fixedWindowRemainingTokensScript,
843
- "getRemainingHash",
1055
+ SCRIPTS.multiRegion.fixedWindow.getRemaining,
844
1056
  [key],
845
1057
  [null]
846
1058
  )
@@ -866,8 +1078,7 @@ var MultiRegionRatelimit = class extends Ratelimit {
866
1078
  await Promise.all(ctx.regionContexts.map((regionContext) => {
867
1079
  safeEval(
868
1080
  regionContext,
869
- resetScript,
870
- "resetHash",
1081
+ RESET_SCRIPT,
871
1082
  [pattern],
872
1083
  [null]
873
1084
  );
@@ -920,8 +1131,7 @@ var MultiRegionRatelimit = class extends Ratelimit {
920
1131
  redis: regionContext.redis,
921
1132
  request: safeEval(
922
1133
  regionContext,
923
- slidingWindowLimitScript,
924
- "limitHash",
1134
+ SCRIPTS.multiRegion.slidingWindow.limit,
925
1135
  [currentKey, previousKey],
926
1136
  [tokens, now, windowDuration, requestId, incrementBy]
927
1137
  // lua seems to return `1` for true and `null` for false
@@ -951,16 +1161,14 @@ var MultiRegionRatelimit = class extends Ratelimit {
951
1161
  const remaining = tokens - usedTokens;
952
1162
  async function sync() {
953
1163
  const res = await Promise.all(dbs.map((s) => s.request));
954
- const allCurrentIds = Array.from(
955
- new Set(
956
- res.flatMap(([current2]) => current2).reduce((acc, curr, index) => {
957
- if (index % 2 === 0) {
958
- acc.push(curr);
959
- }
960
- return acc;
961
- }, [])
962
- ).values()
963
- );
1164
+ const allCurrentIds = [...new Set(
1165
+ res.flatMap(([current2]) => current2).reduce((acc, curr, index) => {
1166
+ if (index % 2 === 0) {
1167
+ acc.push(curr);
1168
+ }
1169
+ return acc;
1170
+ }, [])
1171
+ ).values()];
964
1172
  for (const db of dbs) {
965
1173
  const [current2, _previous, _success] = await db.request;
966
1174
  const dbIds = current2.reduce((ids, currentId, index) => {
@@ -1010,8 +1218,7 @@ var MultiRegionRatelimit = class extends Ratelimit {
1010
1218
  redis: regionContext.redis,
1011
1219
  request: safeEval(
1012
1220
  regionContext,
1013
- slidingWindowRemainingTokensScript,
1014
- "getRemainingHash",
1221
+ SCRIPTS.multiRegion.slidingWindow.getRemaining,
1015
1222
  [currentKey, previousKey],
1016
1223
  [now, windowSize]
1017
1224
  // lua seems to return `1` for true and `null` for false
@@ -1031,8 +1238,7 @@ var MultiRegionRatelimit = class extends Ratelimit {
1031
1238
  await Promise.all(ctx.regionContexts.map((regionContext) => {
1032
1239
  safeEval(
1033
1240
  regionContext,
1034
- resetScript,
1035
- "resetHash",
1241
+ RESET_SCRIPT,
1036
1242
  [pattern],
1037
1243
  [null]
1038
1244
  );
@@ -1042,162 +1248,6 @@ var MultiRegionRatelimit = class extends Ratelimit {
1042
1248
  }
1043
1249
  };
1044
1250
 
1045
- // src/lua-scripts/single.ts
1046
- var fixedWindowLimitScript2 = `
1047
- local key = KEYS[1]
1048
- local window = ARGV[1]
1049
- local incrementBy = ARGV[2] -- increment rate per request at a given value, default is 1
1050
-
1051
- local r = redis.call("INCRBY", key, incrementBy)
1052
- if r == tonumber(incrementBy) then
1053
- -- The first time this key is set, the value will be equal to incrementBy.
1054
- -- So we only need the expire command once
1055
- redis.call("PEXPIRE", key, window)
1056
- end
1057
-
1058
- return r
1059
- `;
1060
- var fixedWindowRemainingTokensScript2 = `
1061
- local key = KEYS[1]
1062
- local tokens = 0
1063
-
1064
- local value = redis.call('GET', key)
1065
- if value then
1066
- tokens = value
1067
- end
1068
- return tokens
1069
- `;
1070
- var slidingWindowLimitScript2 = `
1071
- local currentKey = KEYS[1] -- identifier including prefixes
1072
- local previousKey = KEYS[2] -- key of the previous bucket
1073
- local tokens = tonumber(ARGV[1]) -- tokens per window
1074
- local now = ARGV[2] -- current timestamp in milliseconds
1075
- local window = ARGV[3] -- interval in milliseconds
1076
- local incrementBy = ARGV[4] -- increment rate per request at a given value, default is 1
1077
-
1078
- local requestsInCurrentWindow = redis.call("GET", currentKey)
1079
- if requestsInCurrentWindow == false then
1080
- requestsInCurrentWindow = 0
1081
- end
1082
-
1083
- local requestsInPreviousWindow = redis.call("GET", previousKey)
1084
- if requestsInPreviousWindow == false then
1085
- requestsInPreviousWindow = 0
1086
- end
1087
- local percentageInCurrent = ( now % window ) / window
1088
- -- weighted requests to consider from the previous window
1089
- requestsInPreviousWindow = math.floor(( 1 - percentageInCurrent ) * requestsInPreviousWindow)
1090
- if requestsInPreviousWindow + requestsInCurrentWindow >= tokens then
1091
- return -1
1092
- end
1093
-
1094
- local newValue = redis.call("INCRBY", currentKey, incrementBy)
1095
- if newValue == tonumber(incrementBy) then
1096
- -- The first time this key is set, the value will be equal to incrementBy.
1097
- -- So we only need the expire command once
1098
- redis.call("PEXPIRE", currentKey, window * 2 + 1000) -- Enough time to overlap with a new window + 1 second
1099
- end
1100
- return tokens - ( newValue + requestsInPreviousWindow )
1101
- `;
1102
- var slidingWindowRemainingTokensScript2 = `
1103
- local currentKey = KEYS[1] -- identifier including prefixes
1104
- local previousKey = KEYS[2] -- key of the previous bucket
1105
- local now = ARGV[1] -- current timestamp in milliseconds
1106
- local window = ARGV[2] -- interval in milliseconds
1107
-
1108
- local requestsInCurrentWindow = redis.call("GET", currentKey)
1109
- if requestsInCurrentWindow == false then
1110
- requestsInCurrentWindow = 0
1111
- end
1112
-
1113
- local requestsInPreviousWindow = redis.call("GET", previousKey)
1114
- if requestsInPreviousWindow == false then
1115
- requestsInPreviousWindow = 0
1116
- end
1117
-
1118
- local percentageInCurrent = ( now % window ) / window
1119
- -- weighted requests to consider from the previous window
1120
- requestsInPreviousWindow = math.floor(( 1 - percentageInCurrent ) * requestsInPreviousWindow)
1121
-
1122
- return requestsInPreviousWindow + requestsInCurrentWindow
1123
- `;
1124
- var tokenBucketLimitScript = `
1125
- local key = KEYS[1] -- identifier including prefixes
1126
- local maxTokens = tonumber(ARGV[1]) -- maximum number of tokens
1127
- local interval = tonumber(ARGV[2]) -- size of the window in milliseconds
1128
- local refillRate = tonumber(ARGV[3]) -- how many tokens are refilled after each interval
1129
- local now = tonumber(ARGV[4]) -- current timestamp in milliseconds
1130
- local incrementBy = tonumber(ARGV[5]) -- how many tokens to consume, default is 1
1131
-
1132
- local bucket = redis.call("HMGET", key, "refilledAt", "tokens")
1133
-
1134
- local refilledAt
1135
- local tokens
1136
-
1137
- if bucket[1] == false then
1138
- refilledAt = now
1139
- tokens = maxTokens
1140
- else
1141
- refilledAt = tonumber(bucket[1])
1142
- tokens = tonumber(bucket[2])
1143
- end
1144
-
1145
- if now >= refilledAt + interval then
1146
- local numRefills = math.floor((now - refilledAt) / interval)
1147
- tokens = math.min(maxTokens, tokens + numRefills * refillRate)
1148
-
1149
- refilledAt = refilledAt + numRefills * interval
1150
- end
1151
-
1152
- if tokens == 0 then
1153
- return {-1, refilledAt + interval}
1154
- end
1155
-
1156
- local remaining = tokens - incrementBy
1157
- local expireAt = math.ceil(((maxTokens - remaining) / refillRate)) * interval
1158
-
1159
- redis.call("HSET", key, "refilledAt", refilledAt, "tokens", remaining)
1160
- redis.call("PEXPIRE", key, expireAt)
1161
- return {remaining, refilledAt + interval}
1162
- `;
1163
- var tokenBucketIdentifierNotFound = -1;
1164
- var tokenBucketRemainingTokensScript = `
1165
- local key = KEYS[1]
1166
- local maxTokens = tonumber(ARGV[1])
1167
-
1168
- local bucket = redis.call("HMGET", key, "refilledAt", "tokens")
1169
-
1170
- if bucket[1] == false then
1171
- return {maxTokens, ${tokenBucketIdentifierNotFound}}
1172
- end
1173
-
1174
- return {tonumber(bucket[2]), tonumber(bucket[1])}
1175
- `;
1176
- var cachedFixedWindowLimitScript = `
1177
- local key = KEYS[1]
1178
- local window = ARGV[1]
1179
- local incrementBy = ARGV[2] -- increment rate per request at a given value, default is 1
1180
-
1181
- local r = redis.call("INCRBY", key, incrementBy)
1182
- if r == incrementBy then
1183
- -- The first time this key is set, the value will be equal to incrementBy.
1184
- -- So we only need the expire command once
1185
- redis.call("PEXPIRE", key, window)
1186
- end
1187
-
1188
- return r
1189
- `;
1190
- var cachedFixedWindowRemainingTokenScript = `
1191
- local key = KEYS[1]
1192
- local tokens = 0
1193
-
1194
- local value = redis.call('GET', key)
1195
- if value then
1196
- tokens = value
1197
- end
1198
- return tokens
1199
- `;
1200
-
1201
1251
  // src/single.ts
1202
1252
  var RegionRatelimit = class extends Ratelimit {
1203
1253
  /**
@@ -1210,9 +1260,7 @@ var RegionRatelimit = class extends Ratelimit {
1210
1260
  timeout: config.timeout,
1211
1261
  analytics: config.analytics,
1212
1262
  ctx: {
1213
- redis: config.redis,
1214
- scriptHashes: {},
1215
- cacheScripts: config.cacheScripts ?? true
1263
+ redis: config.redis
1216
1264
  },
1217
1265
  ephemeralCache: config.ephemeralCache,
1218
1266
  enableProtection: config.enableProtection,
@@ -1259,8 +1307,7 @@ var RegionRatelimit = class extends Ratelimit {
1259
1307
  const incrementBy = rate ? Math.max(1, rate) : 1;
1260
1308
  const usedTokensAfterUpdate = await safeEval(
1261
1309
  ctx,
1262
- fixedWindowLimitScript2,
1263
- "limitHash",
1310
+ SCRIPTS.singleRegion.fixedWindow.limit,
1264
1311
  [key],
1265
1312
  [windowDuration, incrementBy]
1266
1313
  );
@@ -1283,8 +1330,7 @@ var RegionRatelimit = class extends Ratelimit {
1283
1330
  const key = [identifier, bucket].join(":");
1284
1331
  const usedTokens = await safeEval(
1285
1332
  ctx,
1286
- fixedWindowRemainingTokensScript2,
1287
- "getRemainingHash",
1333
+ SCRIPTS.singleRegion.fixedWindow.getRemaining,
1288
1334
  [key],
1289
1335
  [null]
1290
1336
  );
@@ -1300,8 +1346,7 @@ var RegionRatelimit = class extends Ratelimit {
1300
1346
  }
1301
1347
  await safeEval(
1302
1348
  ctx,
1303
- resetScript,
1304
- "resetHash",
1349
+ RESET_SCRIPT,
1305
1350
  [pattern],
1306
1351
  [null]
1307
1352
  );
@@ -1349,8 +1394,7 @@ var RegionRatelimit = class extends Ratelimit {
1349
1394
  const incrementBy = rate ? Math.max(1, rate) : 1;
1350
1395
  const remainingTokens = await safeEval(
1351
1396
  ctx,
1352
- slidingWindowLimitScript2,
1353
- "limitHash",
1397
+ SCRIPTS.singleRegion.slidingWindow.limit,
1354
1398
  [currentKey, previousKey],
1355
1399
  [tokens, now, windowSize, incrementBy]
1356
1400
  );
@@ -1375,8 +1419,7 @@ var RegionRatelimit = class extends Ratelimit {
1375
1419
  const previousKey = [identifier, previousWindow].join(":");
1376
1420
  const usedTokens = await safeEval(
1377
1421
  ctx,
1378
- slidingWindowRemainingTokensScript2,
1379
- "getRemainingHash",
1422
+ SCRIPTS.singleRegion.slidingWindow.getRemaining,
1380
1423
  [currentKey, previousKey],
1381
1424
  [now, windowSize]
1382
1425
  );
@@ -1392,8 +1435,7 @@ var RegionRatelimit = class extends Ratelimit {
1392
1435
  }
1393
1436
  await safeEval(
1394
1437
  ctx,
1395
- resetScript,
1396
- "resetHash",
1438
+ RESET_SCRIPT,
1397
1439
  [pattern],
1398
1440
  [null]
1399
1441
  );
@@ -1434,8 +1476,7 @@ var RegionRatelimit = class extends Ratelimit {
1434
1476
  const incrementBy = rate ? Math.max(1, rate) : 1;
1435
1477
  const [remaining, reset] = await safeEval(
1436
1478
  ctx,
1437
- tokenBucketLimitScript,
1438
- "limitHash",
1479
+ SCRIPTS.singleRegion.tokenBucket.limit,
1439
1480
  [identifier],
1440
1481
  [maxTokens, intervalDuration, refillRate, now, incrementBy]
1441
1482
  );
@@ -1454,8 +1495,7 @@ var RegionRatelimit = class extends Ratelimit {
1454
1495
  async getRemaining(ctx, identifier) {
1455
1496
  const [remainingTokens, refilledAt] = await safeEval(
1456
1497
  ctx,
1457
- tokenBucketRemainingTokensScript,
1458
- "getRemainingHash",
1498
+ SCRIPTS.singleRegion.tokenBucket.getRemaining,
1459
1499
  [identifier],
1460
1500
  [maxTokens]
1461
1501
  );
@@ -1473,8 +1513,7 @@ var RegionRatelimit = class extends Ratelimit {
1473
1513
  }
1474
1514
  await safeEval(
1475
1515
  ctx,
1476
- resetScript,
1477
- "resetHash",
1516
+ RESET_SCRIPT,
1478
1517
  [pattern],
1479
1518
  [null]
1480
1519
  );
@@ -1522,8 +1561,7 @@ var RegionRatelimit = class extends Ratelimit {
1522
1561
  const success = cachedTokensAfterUpdate < tokens;
1523
1562
  const pending = success ? safeEval(
1524
1563
  ctx,
1525
- cachedFixedWindowLimitScript,
1526
- "limitHash",
1564
+ SCRIPTS.singleRegion.cachedFixedWindow.limit,
1527
1565
  [key],
1528
1566
  [windowDuration, incrementBy]
1529
1567
  ) : Promise.resolve();
@@ -1537,8 +1575,7 @@ var RegionRatelimit = class extends Ratelimit {
1537
1575
  }
1538
1576
  const usedTokensAfterUpdate = await safeEval(
1539
1577
  ctx,
1540
- cachedFixedWindowLimitScript,
1541
- "limitHash",
1578
+ SCRIPTS.singleRegion.cachedFixedWindow.limit,
1542
1579
  [key],
1543
1580
  [windowDuration, incrementBy]
1544
1581
  );
@@ -1568,8 +1605,7 @@ var RegionRatelimit = class extends Ratelimit {
1568
1605
  }
1569
1606
  const usedTokens = await safeEval(
1570
1607
  ctx,
1571
- cachedFixedWindowRemainingTokenScript,
1572
- "getRemainingHash",
1608
+ SCRIPTS.singleRegion.cachedFixedWindow.getRemaining,
1573
1609
  [key],
1574
1610
  [null]
1575
1611
  );
@@ -1588,8 +1624,7 @@ var RegionRatelimit = class extends Ratelimit {
1588
1624
  const pattern = [identifier, "*"].join(":");
1589
1625
  await safeEval(
1590
1626
  ctx,
1591
- resetScript,
1592
- "resetHash",
1627
+ RESET_SCRIPT,
1593
1628
  [pattern],
1594
1629
  [null]
1595
1630
  );