@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.js CHANGED
@@ -49,10 +49,10 @@ var Analytics = class {
49
49
  * @returns
50
50
  */
51
51
  extractGeo(req) {
52
- if (typeof req.geo !== "undefined") {
52
+ if (req.geo !== void 0) {
53
53
  return req.geo;
54
54
  }
55
- if (typeof req.cf !== "undefined") {
55
+ if (req.cf !== void 0) {
56
56
  return req.cf;
57
57
  }
58
58
  return {};
@@ -141,54 +141,203 @@ function ms(d) {
141
141
  const time = Number.parseInt(match[1]);
142
142
  const unit = match[2];
143
143
  switch (unit) {
144
- case "ms":
144
+ case "ms": {
145
145
  return time;
146
- case "s":
146
+ }
147
+ case "s": {
147
148
  return time * 1e3;
148
- case "m":
149
+ }
150
+ case "m": {
149
151
  return time * 1e3 * 60;
150
- case "h":
152
+ }
153
+ case "h": {
151
154
  return time * 1e3 * 60 * 60;
152
- case "d":
155
+ }
156
+ case "d": {
153
157
  return time * 1e3 * 60 * 60 * 24;
154
- default:
158
+ }
159
+ default: {
155
160
  throw new Error(`Unable to parse window size: ${d}`);
161
+ }
156
162
  }
157
163
  }
158
164
 
159
165
  // src/hash.ts
160
- var setHash = async (ctx, script, kind) => {
161
- const regionContexts = "redis" in ctx ? [ctx] : ctx.regionContexts;
162
- const hashSample = regionContexts[0].scriptHashes[kind];
163
- if (!hashSample) {
164
- await Promise.all(regionContexts.map(async (context) => {
165
- context.scriptHashes[kind] = await context.redis.scriptLoad(script);
166
- }));
167
- }
168
- ;
169
- };
170
- var safeEval = async (ctx, script, kind, keys, args) => {
171
- if (!ctx.cacheScripts) {
172
- return await ctx.redis.eval(script, keys, args);
173
- }
174
- ;
175
- await setHash(ctx, script, kind);
166
+ var safeEval = async (ctx, script, keys, args) => {
176
167
  try {
177
- return await ctx.redis.evalsha(ctx.scriptHashes[kind], keys, args);
168
+ return await ctx.redis.evalsha(script.hash, keys, args);
178
169
  } catch (error) {
179
170
  if (`${error}`.includes("NOSCRIPT")) {
180
- console.log("Script with the expected hash was not found in redis db. It is probably flushed. Will load another scipt before continuing.");
181
- ctx.scriptHashes[kind] = void 0;
182
- await setHash(ctx, script, kind);
183
- console.log(" New script successfully loaded.");
184
- return await ctx.redis.evalsha(ctx.scriptHashes[kind], keys, args);
171
+ const hash = await ctx.redis.scriptLoad(script.script);
172
+ if (hash !== script.hash) {
173
+ console.warn(
174
+ "Upstash Ratelimit: Expected hash and the hash received from Redis are different. Ratelimit will work as usual but performance will be reduced."
175
+ );
176
+ }
177
+ return await ctx.redis.evalsha(hash, keys, args);
185
178
  }
186
179
  throw error;
187
180
  }
188
181
  };
189
182
 
190
- // src/lua-scripts/multi.ts
183
+ // src/lua-scripts/single.ts
191
184
  var fixedWindowLimitScript = `
185
+ local key = KEYS[1]
186
+ local window = ARGV[1]
187
+ local incrementBy = ARGV[2] -- increment rate per request at a given value, default is 1
188
+
189
+ local r = redis.call("INCRBY", key, incrementBy)
190
+ if r == tonumber(incrementBy) then
191
+ -- The first time this key is set, the value will be equal to incrementBy.
192
+ -- So we only need the expire command once
193
+ redis.call("PEXPIRE", key, window)
194
+ end
195
+
196
+ return r
197
+ `;
198
+ var fixedWindowRemainingTokensScript = `
199
+ local key = KEYS[1]
200
+ local tokens = 0
201
+
202
+ local value = redis.call('GET', key)
203
+ if value then
204
+ tokens = value
205
+ end
206
+ return tokens
207
+ `;
208
+ var slidingWindowLimitScript = `
209
+ local currentKey = KEYS[1] -- identifier including prefixes
210
+ local previousKey = KEYS[2] -- key of the previous bucket
211
+ local tokens = tonumber(ARGV[1]) -- tokens per window
212
+ local now = ARGV[2] -- current timestamp in milliseconds
213
+ local window = ARGV[3] -- interval in milliseconds
214
+ local incrementBy = ARGV[4] -- increment rate per request at a given value, default is 1
215
+
216
+ local requestsInCurrentWindow = redis.call("GET", currentKey)
217
+ if requestsInCurrentWindow == false then
218
+ requestsInCurrentWindow = 0
219
+ end
220
+
221
+ local requestsInPreviousWindow = redis.call("GET", previousKey)
222
+ if requestsInPreviousWindow == false then
223
+ requestsInPreviousWindow = 0
224
+ end
225
+ local percentageInCurrent = ( now % window ) / window
226
+ -- weighted requests to consider from the previous window
227
+ requestsInPreviousWindow = math.floor(( 1 - percentageInCurrent ) * requestsInPreviousWindow)
228
+ if requestsInPreviousWindow + requestsInCurrentWindow >= tokens then
229
+ return -1
230
+ end
231
+
232
+ local newValue = redis.call("INCRBY", currentKey, incrementBy)
233
+ if newValue == tonumber(incrementBy) then
234
+ -- The first time this key is set, the value will be equal to incrementBy.
235
+ -- So we only need the expire command once
236
+ redis.call("PEXPIRE", currentKey, window * 2 + 1000) -- Enough time to overlap with a new window + 1 second
237
+ end
238
+ return tokens - ( newValue + requestsInPreviousWindow )
239
+ `;
240
+ var slidingWindowRemainingTokensScript = `
241
+ local currentKey = KEYS[1] -- identifier including prefixes
242
+ local previousKey = KEYS[2] -- key of the previous bucket
243
+ local now = ARGV[1] -- current timestamp in milliseconds
244
+ local window = ARGV[2] -- interval in milliseconds
245
+
246
+ local requestsInCurrentWindow = redis.call("GET", currentKey)
247
+ if requestsInCurrentWindow == false then
248
+ requestsInCurrentWindow = 0
249
+ end
250
+
251
+ local requestsInPreviousWindow = redis.call("GET", previousKey)
252
+ if requestsInPreviousWindow == false then
253
+ requestsInPreviousWindow = 0
254
+ end
255
+
256
+ local percentageInCurrent = ( now % window ) / window
257
+ -- weighted requests to consider from the previous window
258
+ requestsInPreviousWindow = math.floor(( 1 - percentageInCurrent ) * requestsInPreviousWindow)
259
+
260
+ return requestsInPreviousWindow + requestsInCurrentWindow
261
+ `;
262
+ var tokenBucketLimitScript = `
263
+ local key = KEYS[1] -- identifier including prefixes
264
+ local maxTokens = tonumber(ARGV[1]) -- maximum number of tokens
265
+ local interval = tonumber(ARGV[2]) -- size of the window in milliseconds
266
+ local refillRate = tonumber(ARGV[3]) -- how many tokens are refilled after each interval
267
+ local now = tonumber(ARGV[4]) -- current timestamp in milliseconds
268
+ local incrementBy = tonumber(ARGV[5]) -- how many tokens to consume, default is 1
269
+
270
+ local bucket = redis.call("HMGET", key, "refilledAt", "tokens")
271
+
272
+ local refilledAt
273
+ local tokens
274
+
275
+ if bucket[1] == false then
276
+ refilledAt = now
277
+ tokens = maxTokens
278
+ else
279
+ refilledAt = tonumber(bucket[1])
280
+ tokens = tonumber(bucket[2])
281
+ end
282
+
283
+ if now >= refilledAt + interval then
284
+ local numRefills = math.floor((now - refilledAt) / interval)
285
+ tokens = math.min(maxTokens, tokens + numRefills * refillRate)
286
+
287
+ refilledAt = refilledAt + numRefills * interval
288
+ end
289
+
290
+ if tokens == 0 then
291
+ return {-1, refilledAt + interval}
292
+ end
293
+
294
+ local remaining = tokens - incrementBy
295
+ local expireAt = math.ceil(((maxTokens - remaining) / refillRate)) * interval
296
+
297
+ redis.call("HSET", key, "refilledAt", refilledAt, "tokens", remaining)
298
+ redis.call("PEXPIRE", key, expireAt)
299
+ return {remaining, refilledAt + interval}
300
+ `;
301
+ var tokenBucketIdentifierNotFound = -1;
302
+ var tokenBucketRemainingTokensScript = `
303
+ local key = KEYS[1]
304
+ local maxTokens = tonumber(ARGV[1])
305
+
306
+ local bucket = redis.call("HMGET", key, "refilledAt", "tokens")
307
+
308
+ if bucket[1] == false then
309
+ return {maxTokens, ${tokenBucketIdentifierNotFound}}
310
+ end
311
+
312
+ return {tonumber(bucket[2]), tonumber(bucket[1])}
313
+ `;
314
+ var cachedFixedWindowLimitScript = `
315
+ local key = KEYS[1]
316
+ local window = ARGV[1]
317
+ local incrementBy = ARGV[2] -- increment rate per request at a given value, default is 1
318
+
319
+ local r = redis.call("INCRBY", key, incrementBy)
320
+ if r == incrementBy then
321
+ -- The first time this key is set, the value will be equal to incrementBy.
322
+ -- So we only need the expire command once
323
+ redis.call("PEXPIRE", key, window)
324
+ end
325
+
326
+ return r
327
+ `;
328
+ var cachedFixedWindowRemainingTokenScript = `
329
+ local key = KEYS[1]
330
+ local tokens = 0
331
+
332
+ local value = redis.call('GET', key)
333
+ if value then
334
+ tokens = value
335
+ end
336
+ return tokens
337
+ `;
338
+
339
+ // src/lua-scripts/multi.ts
340
+ var fixedWindowLimitScript2 = `
192
341
  local key = KEYS[1]
193
342
  local id = ARGV[1]
194
343
  local window = ARGV[2]
@@ -204,7 +353,7 @@ var fixedWindowLimitScript = `
204
353
 
205
354
  return fields
206
355
  `;
207
- var fixedWindowRemainingTokensScript = `
356
+ var fixedWindowRemainingTokensScript2 = `
208
357
  local key = KEYS[1]
209
358
  local tokens = 0
210
359
 
@@ -212,7 +361,7 @@ var fixedWindowRemainingTokensScript = `
212
361
 
213
362
  return fields
214
363
  `;
215
- var slidingWindowLimitScript = `
364
+ var slidingWindowLimitScript2 = `
216
365
  local currentKey = KEYS[1] -- identifier including prefixes
217
366
  local previousKey = KEYS[2] -- key of the previous bucket
218
367
  local tokens = tonumber(ARGV[1]) -- tokens per window
@@ -247,7 +396,7 @@ var slidingWindowLimitScript = `
247
396
  end
248
397
  return {currentFields, previousFields, true}
249
398
  `;
250
- var slidingWindowRemainingTokensScript = `
399
+ var slidingWindowRemainingTokensScript2 = `
251
400
  local currentKey = KEYS[1] -- identifier including prefixes
252
401
  local previousKey = KEYS[2] -- key of the previous bucket
253
402
  local now = ARGV[1] -- current timestamp in milliseconds
@@ -296,6 +445,78 @@ var resetScript = `
296
445
  until cursor == "0"
297
446
  `;
298
447
 
448
+ // src/lua-scripts/hash.ts
449
+ var SCRIPTS = {
450
+ singleRegion: {
451
+ fixedWindow: {
452
+ limit: {
453
+ script: fixedWindowLimitScript,
454
+ hash: "b13943e359636db027ad280f1def143f02158c13"
455
+ },
456
+ getRemaining: {
457
+ script: fixedWindowRemainingTokensScript,
458
+ hash: "8c4c341934502aee132643ffbe58ead3450e5208"
459
+ }
460
+ },
461
+ slidingWindow: {
462
+ limit: {
463
+ script: slidingWindowLimitScript,
464
+ hash: "e1391e429b699c780eb0480350cd5b7280fd9213"
465
+ },
466
+ getRemaining: {
467
+ script: slidingWindowRemainingTokensScript,
468
+ hash: "65a73ac5a05bf9712903bc304b77268980c1c417"
469
+ }
470
+ },
471
+ tokenBucket: {
472
+ limit: {
473
+ script: tokenBucketLimitScript,
474
+ hash: "5bece90aeef8189a8cfd28995b479529e270b3c6"
475
+ },
476
+ getRemaining: {
477
+ script: tokenBucketRemainingTokensScript,
478
+ hash: "a15be2bb1db2a15f7c82db06146f9d08983900d0"
479
+ }
480
+ },
481
+ cachedFixedWindow: {
482
+ limit: {
483
+ script: cachedFixedWindowLimitScript,
484
+ hash: "c26b12703dd137939b9a69a3a9b18e906a2d940f"
485
+ },
486
+ getRemaining: {
487
+ script: cachedFixedWindowRemainingTokenScript,
488
+ hash: "8e8f222ccae68b595ee6e3f3bf2199629a62b91a"
489
+ }
490
+ }
491
+ },
492
+ multiRegion: {
493
+ fixedWindow: {
494
+ limit: {
495
+ script: fixedWindowLimitScript2,
496
+ hash: "a8c14f3835aa87bd70e5e2116081b81664abcf5c"
497
+ },
498
+ getRemaining: {
499
+ script: fixedWindowRemainingTokensScript2,
500
+ hash: "8ab8322d0ed5fe5ac8eb08f0c2e4557f1b4816fd"
501
+ }
502
+ },
503
+ slidingWindow: {
504
+ limit: {
505
+ script: slidingWindowLimitScript2,
506
+ hash: "cb4fdc2575056df7c6d422764df0de3a08d6753b"
507
+ },
508
+ getRemaining: {
509
+ script: slidingWindowRemainingTokensScript2,
510
+ hash: "558c9306b7ec54abb50747fe0b17e5d44bd24868"
511
+ }
512
+ }
513
+ }
514
+ };
515
+ var RESET_SCRIPT = {
516
+ script: resetScript,
517
+ hash: "54bd274ddc59fb3be0f42deee2f64322a10e2b50"
518
+ };
519
+
299
520
  // src/types.ts
300
521
  var DenyListExtension = "denyList";
301
522
  var IpDenyListKey = "ipDenyList";
@@ -379,7 +600,7 @@ var updateIpDenyList = async (redis, prefix, threshold, ttl) => {
379
600
  const transaction = redis.multi();
380
601
  transaction.sdiffstore(allDenyLists, allDenyLists, ipDenyList);
381
602
  transaction.del(ipDenyList);
382
- transaction.sadd(ipDenyList, ...allIps);
603
+ transaction.sadd(ipDenyList, allIps.at(0), ...allIps.slice(1));
383
604
  transaction.sdiffstore(ipDenyList, ipDenyList, allDenyLists);
384
605
  transaction.sunionstore(allDenyLists, allDenyLists, ipDenyList);
385
606
  transaction.set(statusKey, "valid", { px: ttl ?? getIpListTTL() });
@@ -481,7 +702,7 @@ var Ratelimit = class {
481
702
  }) : void 0;
482
703
  if (config.ephemeralCache instanceof Map) {
483
704
  this.ctx.cache = new Cache(config.ephemeralCache);
484
- } else if (typeof config.ephemeralCache === "undefined") {
705
+ } else if (config.ephemeralCache === void 0) {
485
706
  this.ctx.cache = new Cache(/* @__PURE__ */ new Map());
486
707
  }
487
708
  }
@@ -612,15 +833,10 @@ var Ratelimit = class {
612
833
  const key = this.getKey(identifier);
613
834
  const definedMembers = this.getDefinedMembers(identifier, req);
614
835
  const deniedValue = checkDenyListCache(definedMembers);
615
- let result;
616
- if (deniedValue) {
617
- result = [defaultDeniedResponse(deniedValue), { deniedValue, invalidIpDenyList: false }];
618
- } else {
619
- result = await Promise.all([
620
- this.limiter().limit(this.ctx, key, req?.rate),
621
- this.enableProtection ? checkDenyList(this.primaryRedis, this.prefix, definedMembers) : { deniedValue: void 0, invalidIpDenyList: false }
622
- ]);
623
- }
836
+ const result = deniedValue ? [defaultDeniedResponse(deniedValue), { deniedValue, invalidIpDenyList: false }] : await Promise.all([
837
+ this.limiter().limit(this.ctx, key, req?.rate),
838
+ this.enableProtection ? checkDenyList(this.primaryRedis, this.prefix, definedMembers) : { deniedValue: void 0, invalidIpDenyList: false }
839
+ ]);
624
840
  return resolveLimitPayload(this.primaryRedis, this.prefix, result, this.denyListThreshold);
625
841
  };
626
842
  /**
@@ -670,9 +886,9 @@ var Ratelimit = class {
670
886
  time: Date.now(),
671
887
  success: ratelimitResponse.reason === "denyList" ? "denied" : ratelimitResponse.success,
672
888
  ...geo
673
- }).catch((err) => {
889
+ }).catch((error) => {
674
890
  let errorMessage = "Failed to record analytics";
675
- if (`${err}`.includes("WRONGTYPE")) {
891
+ if (`${error}`.includes("WRONGTYPE")) {
676
892
  errorMessage = `
677
893
  Failed to record analytics. See the information below:
678
894
 
@@ -685,11 +901,11 @@ var Ratelimit = class {
685
901
 
686
902
  `;
687
903
  }
688
- console.warn(errorMessage, err);
904
+ console.warn(errorMessage, error);
689
905
  });
690
906
  ratelimitResponse.pending = Promise.all([ratelimitResponse.pending, analyticsP]);
691
- } catch (err) {
692
- console.warn("Failed to record analytics", err);
907
+ } catch (error) {
908
+ console.warn("Failed to record analytics", error);
693
909
  }
694
910
  ;
695
911
  }
@@ -735,9 +951,7 @@ var MultiRegionRatelimit = class extends Ratelimit {
735
951
  analytics: config.analytics,
736
952
  ctx: {
737
953
  regionContexts: config.redis.map((redis) => ({
738
- redis,
739
- scriptHashes: {},
740
- cacheScripts: config.cacheScripts ?? true
954
+ redis
741
955
  })),
742
956
  cache: config.ephemeralCache ? new Cache(config.ephemeralCache) : void 0
743
957
  }
@@ -786,8 +1000,7 @@ var MultiRegionRatelimit = class extends Ratelimit {
786
1000
  redis: regionContext.redis,
787
1001
  request: safeEval(
788
1002
  regionContext,
789
- fixedWindowLimitScript,
790
- "limitHash",
1003
+ SCRIPTS.multiRegion.fixedWindow.limit,
791
1004
  [key],
792
1005
  [requestId, windowDuration, incrementBy]
793
1006
  )
@@ -803,18 +1016,17 @@ var MultiRegionRatelimit = class extends Ratelimit {
803
1016
  const remaining = tokens - usedTokens;
804
1017
  async function sync() {
805
1018
  const individualIDs = await Promise.all(dbs.map((s) => s.request));
806
- const allIDs = Array.from(
807
- new Set(
808
- individualIDs.flatMap((_) => _).reduce((acc, curr, index) => {
809
- if (index % 2 === 0) {
810
- acc.push(curr);
811
- }
812
- return acc;
813
- }, [])
814
- ).values()
815
- );
1019
+ const allIDs = [...new Set(
1020
+ individualIDs.flat().reduce((acc, curr, index) => {
1021
+ if (index % 2 === 0) {
1022
+ acc.push(curr);
1023
+ }
1024
+ return acc;
1025
+ }, [])
1026
+ ).values()];
816
1027
  for (const db of dbs) {
817
- const usedDbTokens = (await db.request).reduce(
1028
+ const usedDbTokensRequest = await db.request;
1029
+ const usedDbTokens = usedDbTokensRequest.reduce(
818
1030
  (accTokens, usedToken, index) => {
819
1031
  let parsedToken = 0;
820
1032
  if (index % 2) {
@@ -824,7 +1036,8 @@ var MultiRegionRatelimit = class extends Ratelimit {
824
1036
  },
825
1037
  0
826
1038
  );
827
- const dbIds = (await db.request).reduce((ids, currentId, index) => {
1039
+ const dbIdsRequest = await db.request;
1040
+ const dbIds = dbIdsRequest.reduce((ids, currentId, index) => {
828
1041
  if (index % 2 === 0) {
829
1042
  ids.push(currentId);
830
1043
  }
@@ -862,8 +1075,7 @@ var MultiRegionRatelimit = class extends Ratelimit {
862
1075
  redis: regionContext.redis,
863
1076
  request: safeEval(
864
1077
  regionContext,
865
- fixedWindowRemainingTokensScript,
866
- "getRemainingHash",
1078
+ SCRIPTS.multiRegion.fixedWindow.getRemaining,
867
1079
  [key],
868
1080
  [null]
869
1081
  )
@@ -889,8 +1101,7 @@ var MultiRegionRatelimit = class extends Ratelimit {
889
1101
  await Promise.all(ctx.regionContexts.map((regionContext) => {
890
1102
  safeEval(
891
1103
  regionContext,
892
- resetScript,
893
- "resetHash",
1104
+ RESET_SCRIPT,
894
1105
  [pattern],
895
1106
  [null]
896
1107
  );
@@ -943,8 +1154,7 @@ var MultiRegionRatelimit = class extends Ratelimit {
943
1154
  redis: regionContext.redis,
944
1155
  request: safeEval(
945
1156
  regionContext,
946
- slidingWindowLimitScript,
947
- "limitHash",
1157
+ SCRIPTS.multiRegion.slidingWindow.limit,
948
1158
  [currentKey, previousKey],
949
1159
  [tokens, now, windowDuration, requestId, incrementBy]
950
1160
  // lua seems to return `1` for true and `null` for false
@@ -974,16 +1184,14 @@ var MultiRegionRatelimit = class extends Ratelimit {
974
1184
  const remaining = tokens - usedTokens;
975
1185
  async function sync() {
976
1186
  const res = await Promise.all(dbs.map((s) => s.request));
977
- const allCurrentIds = Array.from(
978
- new Set(
979
- res.flatMap(([current2]) => current2).reduce((acc, curr, index) => {
980
- if (index % 2 === 0) {
981
- acc.push(curr);
982
- }
983
- return acc;
984
- }, [])
985
- ).values()
986
- );
1187
+ const allCurrentIds = [...new Set(
1188
+ res.flatMap(([current2]) => current2).reduce((acc, curr, index) => {
1189
+ if (index % 2 === 0) {
1190
+ acc.push(curr);
1191
+ }
1192
+ return acc;
1193
+ }, [])
1194
+ ).values()];
987
1195
  for (const db of dbs) {
988
1196
  const [current2, _previous, _success] = await db.request;
989
1197
  const dbIds = current2.reduce((ids, currentId, index) => {
@@ -1033,8 +1241,7 @@ var MultiRegionRatelimit = class extends Ratelimit {
1033
1241
  redis: regionContext.redis,
1034
1242
  request: safeEval(
1035
1243
  regionContext,
1036
- slidingWindowRemainingTokensScript,
1037
- "getRemainingHash",
1244
+ SCRIPTS.multiRegion.slidingWindow.getRemaining,
1038
1245
  [currentKey, previousKey],
1039
1246
  [now, windowSize]
1040
1247
  // lua seems to return `1` for true and `null` for false
@@ -1054,8 +1261,7 @@ var MultiRegionRatelimit = class extends Ratelimit {
1054
1261
  await Promise.all(ctx.regionContexts.map((regionContext) => {
1055
1262
  safeEval(
1056
1263
  regionContext,
1057
- resetScript,
1058
- "resetHash",
1264
+ RESET_SCRIPT,
1059
1265
  [pattern],
1060
1266
  [null]
1061
1267
  );
@@ -1065,162 +1271,6 @@ var MultiRegionRatelimit = class extends Ratelimit {
1065
1271
  }
1066
1272
  };
1067
1273
 
1068
- // src/lua-scripts/single.ts
1069
- var fixedWindowLimitScript2 = `
1070
- local key = KEYS[1]
1071
- local window = ARGV[1]
1072
- local incrementBy = ARGV[2] -- increment rate per request at a given value, default is 1
1073
-
1074
- local r = redis.call("INCRBY", key, incrementBy)
1075
- if r == tonumber(incrementBy) then
1076
- -- The first time this key is set, the value will be equal to incrementBy.
1077
- -- So we only need the expire command once
1078
- redis.call("PEXPIRE", key, window)
1079
- end
1080
-
1081
- return r
1082
- `;
1083
- var fixedWindowRemainingTokensScript2 = `
1084
- local key = KEYS[1]
1085
- local tokens = 0
1086
-
1087
- local value = redis.call('GET', key)
1088
- if value then
1089
- tokens = value
1090
- end
1091
- return tokens
1092
- `;
1093
- var slidingWindowLimitScript2 = `
1094
- local currentKey = KEYS[1] -- identifier including prefixes
1095
- local previousKey = KEYS[2] -- key of the previous bucket
1096
- local tokens = tonumber(ARGV[1]) -- tokens per window
1097
- local now = ARGV[2] -- current timestamp in milliseconds
1098
- local window = ARGV[3] -- interval in milliseconds
1099
- local incrementBy = ARGV[4] -- increment rate per request at a given value, default is 1
1100
-
1101
- local requestsInCurrentWindow = redis.call("GET", currentKey)
1102
- if requestsInCurrentWindow == false then
1103
- requestsInCurrentWindow = 0
1104
- end
1105
-
1106
- local requestsInPreviousWindow = redis.call("GET", previousKey)
1107
- if requestsInPreviousWindow == false then
1108
- requestsInPreviousWindow = 0
1109
- end
1110
- local percentageInCurrent = ( now % window ) / window
1111
- -- weighted requests to consider from the previous window
1112
- requestsInPreviousWindow = math.floor(( 1 - percentageInCurrent ) * requestsInPreviousWindow)
1113
- if requestsInPreviousWindow + requestsInCurrentWindow >= tokens then
1114
- return -1
1115
- end
1116
-
1117
- local newValue = redis.call("INCRBY", currentKey, incrementBy)
1118
- if newValue == tonumber(incrementBy) then
1119
- -- The first time this key is set, the value will be equal to incrementBy.
1120
- -- So we only need the expire command once
1121
- redis.call("PEXPIRE", currentKey, window * 2 + 1000) -- Enough time to overlap with a new window + 1 second
1122
- end
1123
- return tokens - ( newValue + requestsInPreviousWindow )
1124
- `;
1125
- var slidingWindowRemainingTokensScript2 = `
1126
- local currentKey = KEYS[1] -- identifier including prefixes
1127
- local previousKey = KEYS[2] -- key of the previous bucket
1128
- local now = ARGV[1] -- current timestamp in milliseconds
1129
- local window = ARGV[2] -- interval in milliseconds
1130
-
1131
- local requestsInCurrentWindow = redis.call("GET", currentKey)
1132
- if requestsInCurrentWindow == false then
1133
- requestsInCurrentWindow = 0
1134
- end
1135
-
1136
- local requestsInPreviousWindow = redis.call("GET", previousKey)
1137
- if requestsInPreviousWindow == false then
1138
- requestsInPreviousWindow = 0
1139
- end
1140
-
1141
- local percentageInCurrent = ( now % window ) / window
1142
- -- weighted requests to consider from the previous window
1143
- requestsInPreviousWindow = math.floor(( 1 - percentageInCurrent ) * requestsInPreviousWindow)
1144
-
1145
- return requestsInPreviousWindow + requestsInCurrentWindow
1146
- `;
1147
- var tokenBucketLimitScript = `
1148
- local key = KEYS[1] -- identifier including prefixes
1149
- local maxTokens = tonumber(ARGV[1]) -- maximum number of tokens
1150
- local interval = tonumber(ARGV[2]) -- size of the window in milliseconds
1151
- local refillRate = tonumber(ARGV[3]) -- how many tokens are refilled after each interval
1152
- local now = tonumber(ARGV[4]) -- current timestamp in milliseconds
1153
- local incrementBy = tonumber(ARGV[5]) -- how many tokens to consume, default is 1
1154
-
1155
- local bucket = redis.call("HMGET", key, "refilledAt", "tokens")
1156
-
1157
- local refilledAt
1158
- local tokens
1159
-
1160
- if bucket[1] == false then
1161
- refilledAt = now
1162
- tokens = maxTokens
1163
- else
1164
- refilledAt = tonumber(bucket[1])
1165
- tokens = tonumber(bucket[2])
1166
- end
1167
-
1168
- if now >= refilledAt + interval then
1169
- local numRefills = math.floor((now - refilledAt) / interval)
1170
- tokens = math.min(maxTokens, tokens + numRefills * refillRate)
1171
-
1172
- refilledAt = refilledAt + numRefills * interval
1173
- end
1174
-
1175
- if tokens == 0 then
1176
- return {-1, refilledAt + interval}
1177
- end
1178
-
1179
- local remaining = tokens - incrementBy
1180
- local expireAt = math.ceil(((maxTokens - remaining) / refillRate)) * interval
1181
-
1182
- redis.call("HSET", key, "refilledAt", refilledAt, "tokens", remaining)
1183
- redis.call("PEXPIRE", key, expireAt)
1184
- return {remaining, refilledAt + interval}
1185
- `;
1186
- var tokenBucketIdentifierNotFound = -1;
1187
- var tokenBucketRemainingTokensScript = `
1188
- local key = KEYS[1]
1189
- local maxTokens = tonumber(ARGV[1])
1190
-
1191
- local bucket = redis.call("HMGET", key, "refilledAt", "tokens")
1192
-
1193
- if bucket[1] == false then
1194
- return {maxTokens, ${tokenBucketIdentifierNotFound}}
1195
- end
1196
-
1197
- return {tonumber(bucket[2]), tonumber(bucket[1])}
1198
- `;
1199
- var cachedFixedWindowLimitScript = `
1200
- local key = KEYS[1]
1201
- local window = ARGV[1]
1202
- local incrementBy = ARGV[2] -- increment rate per request at a given value, default is 1
1203
-
1204
- local r = redis.call("INCRBY", key, incrementBy)
1205
- if r == incrementBy then
1206
- -- The first time this key is set, the value will be equal to incrementBy.
1207
- -- So we only need the expire command once
1208
- redis.call("PEXPIRE", key, window)
1209
- end
1210
-
1211
- return r
1212
- `;
1213
- var cachedFixedWindowRemainingTokenScript = `
1214
- local key = KEYS[1]
1215
- local tokens = 0
1216
-
1217
- local value = redis.call('GET', key)
1218
- if value then
1219
- tokens = value
1220
- end
1221
- return tokens
1222
- `;
1223
-
1224
1274
  // src/single.ts
1225
1275
  var RegionRatelimit = class extends Ratelimit {
1226
1276
  /**
@@ -1233,9 +1283,7 @@ var RegionRatelimit = class extends Ratelimit {
1233
1283
  timeout: config.timeout,
1234
1284
  analytics: config.analytics,
1235
1285
  ctx: {
1236
- redis: config.redis,
1237
- scriptHashes: {},
1238
- cacheScripts: config.cacheScripts ?? true
1286
+ redis: config.redis
1239
1287
  },
1240
1288
  ephemeralCache: config.ephemeralCache,
1241
1289
  enableProtection: config.enableProtection,
@@ -1282,8 +1330,7 @@ var RegionRatelimit = class extends Ratelimit {
1282
1330
  const incrementBy = rate ? Math.max(1, rate) : 1;
1283
1331
  const usedTokensAfterUpdate = await safeEval(
1284
1332
  ctx,
1285
- fixedWindowLimitScript2,
1286
- "limitHash",
1333
+ SCRIPTS.singleRegion.fixedWindow.limit,
1287
1334
  [key],
1288
1335
  [windowDuration, incrementBy]
1289
1336
  );
@@ -1306,8 +1353,7 @@ var RegionRatelimit = class extends Ratelimit {
1306
1353
  const key = [identifier, bucket].join(":");
1307
1354
  const usedTokens = await safeEval(
1308
1355
  ctx,
1309
- fixedWindowRemainingTokensScript2,
1310
- "getRemainingHash",
1356
+ SCRIPTS.singleRegion.fixedWindow.getRemaining,
1311
1357
  [key],
1312
1358
  [null]
1313
1359
  );
@@ -1323,8 +1369,7 @@ var RegionRatelimit = class extends Ratelimit {
1323
1369
  }
1324
1370
  await safeEval(
1325
1371
  ctx,
1326
- resetScript,
1327
- "resetHash",
1372
+ RESET_SCRIPT,
1328
1373
  [pattern],
1329
1374
  [null]
1330
1375
  );
@@ -1372,8 +1417,7 @@ var RegionRatelimit = class extends Ratelimit {
1372
1417
  const incrementBy = rate ? Math.max(1, rate) : 1;
1373
1418
  const remainingTokens = await safeEval(
1374
1419
  ctx,
1375
- slidingWindowLimitScript2,
1376
- "limitHash",
1420
+ SCRIPTS.singleRegion.slidingWindow.limit,
1377
1421
  [currentKey, previousKey],
1378
1422
  [tokens, now, windowSize, incrementBy]
1379
1423
  );
@@ -1398,8 +1442,7 @@ var RegionRatelimit = class extends Ratelimit {
1398
1442
  const previousKey = [identifier, previousWindow].join(":");
1399
1443
  const usedTokens = await safeEval(
1400
1444
  ctx,
1401
- slidingWindowRemainingTokensScript2,
1402
- "getRemainingHash",
1445
+ SCRIPTS.singleRegion.slidingWindow.getRemaining,
1403
1446
  [currentKey, previousKey],
1404
1447
  [now, windowSize]
1405
1448
  );
@@ -1415,8 +1458,7 @@ var RegionRatelimit = class extends Ratelimit {
1415
1458
  }
1416
1459
  await safeEval(
1417
1460
  ctx,
1418
- resetScript,
1419
- "resetHash",
1461
+ RESET_SCRIPT,
1420
1462
  [pattern],
1421
1463
  [null]
1422
1464
  );
@@ -1457,8 +1499,7 @@ var RegionRatelimit = class extends Ratelimit {
1457
1499
  const incrementBy = rate ? Math.max(1, rate) : 1;
1458
1500
  const [remaining, reset] = await safeEval(
1459
1501
  ctx,
1460
- tokenBucketLimitScript,
1461
- "limitHash",
1502
+ SCRIPTS.singleRegion.tokenBucket.limit,
1462
1503
  [identifier],
1463
1504
  [maxTokens, intervalDuration, refillRate, now, incrementBy]
1464
1505
  );
@@ -1477,8 +1518,7 @@ var RegionRatelimit = class extends Ratelimit {
1477
1518
  async getRemaining(ctx, identifier) {
1478
1519
  const [remainingTokens, refilledAt] = await safeEval(
1479
1520
  ctx,
1480
- tokenBucketRemainingTokensScript,
1481
- "getRemainingHash",
1521
+ SCRIPTS.singleRegion.tokenBucket.getRemaining,
1482
1522
  [identifier],
1483
1523
  [maxTokens]
1484
1524
  );
@@ -1496,8 +1536,7 @@ var RegionRatelimit = class extends Ratelimit {
1496
1536
  }
1497
1537
  await safeEval(
1498
1538
  ctx,
1499
- resetScript,
1500
- "resetHash",
1539
+ RESET_SCRIPT,
1501
1540
  [pattern],
1502
1541
  [null]
1503
1542
  );
@@ -1545,8 +1584,7 @@ var RegionRatelimit = class extends Ratelimit {
1545
1584
  const success = cachedTokensAfterUpdate < tokens;
1546
1585
  const pending = success ? safeEval(
1547
1586
  ctx,
1548
- cachedFixedWindowLimitScript,
1549
- "limitHash",
1587
+ SCRIPTS.singleRegion.cachedFixedWindow.limit,
1550
1588
  [key],
1551
1589
  [windowDuration, incrementBy]
1552
1590
  ) : Promise.resolve();
@@ -1560,8 +1598,7 @@ var RegionRatelimit = class extends Ratelimit {
1560
1598
  }
1561
1599
  const usedTokensAfterUpdate = await safeEval(
1562
1600
  ctx,
1563
- cachedFixedWindowLimitScript,
1564
- "limitHash",
1601
+ SCRIPTS.singleRegion.cachedFixedWindow.limit,
1565
1602
  [key],
1566
1603
  [windowDuration, incrementBy]
1567
1604
  );
@@ -1591,8 +1628,7 @@ var RegionRatelimit = class extends Ratelimit {
1591
1628
  }
1592
1629
  const usedTokens = await safeEval(
1593
1630
  ctx,
1594
- cachedFixedWindowRemainingTokenScript,
1595
- "getRemainingHash",
1631
+ SCRIPTS.singleRegion.cachedFixedWindow.getRemaining,
1596
1632
  [key],
1597
1633
  [null]
1598
1634
  );
@@ -1611,8 +1647,7 @@ var RegionRatelimit = class extends Ratelimit {
1611
1647
  const pattern = [identifier, "*"].join(":");
1612
1648
  await safeEval(
1613
1649
  ctx,
1614
- resetScript,
1615
- "resetHash",
1650
+ RESET_SCRIPT,
1616
1651
  [pattern],
1617
1652
  [null]
1618
1653
  );