@upstash/ratelimit 1.1.3 → 1.2.0-canary

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
@@ -97,6 +97,9 @@ var Cache = class {
97
97
  empty() {
98
98
  this.cache.clear();
99
99
  }
100
+ size() {
101
+ return this.cache.size;
102
+ }
100
103
  };
101
104
 
102
105
  // src/duration.ts
@@ -123,6 +126,37 @@ function ms(d) {
123
126
  }
124
127
  }
125
128
 
129
+ // src/hash.ts
130
+ var setHash = async (ctx, script, kind) => {
131
+ const regionContexts = "redis" in ctx ? [ctx] : ctx.regionContexts;
132
+ const hashSample = regionContexts[0].scriptHashes[kind];
133
+ if (!hashSample) {
134
+ await Promise.all(regionContexts.map(async (context) => {
135
+ context.scriptHashes[kind] = await context.redis.scriptLoad(script);
136
+ }));
137
+ }
138
+ ;
139
+ };
140
+ var safeEval = async (ctx, script, kind, keys, args) => {
141
+ if (!ctx.cacheScripts) {
142
+ return await ctx.redis.eval(script, keys, args);
143
+ }
144
+ ;
145
+ await setHash(ctx, script, kind);
146
+ try {
147
+ return await ctx.redis.evalsha(ctx.scriptHashes[kind], keys, args);
148
+ } catch (error) {
149
+ if (`${error}`.includes("NOSCRIPT")) {
150
+ console.log("Script with the expected hash was not found in redis db. It is probably flushed. Will load another scipt before continuing.");
151
+ ctx.scriptHashes[kind] = void 0;
152
+ await setHash(ctx, script, kind);
153
+ console.log(" New script successfully loaded.");
154
+ return await ctx.redis.evalsha(ctx.scriptHashes[kind], keys, args);
155
+ }
156
+ throw error;
157
+ }
158
+ };
159
+
126
160
  // src/lua-scripts/multi.ts
127
161
  var fixedWindowLimitScript = `
128
162
  local key = KEYS[1]
@@ -232,20 +266,71 @@ var resetScript = `
232
266
  until cursor == "0"
233
267
  `;
234
268
 
269
+ // src/deny-list.ts
270
+ var denyListCache = new Cache(/* @__PURE__ */ new Map());
271
+ var checkDenyListCache = (members) => {
272
+ return members.find(
273
+ (member) => denyListCache.isBlocked(member).blocked
274
+ );
275
+ };
276
+ var blockMember = (member) => {
277
+ if (denyListCache.size() > 1e3)
278
+ denyListCache.empty();
279
+ denyListCache.blockUntil(member, Date.now() + 6e4);
280
+ };
281
+ var checkDenyList = async (redis, prefix, members) => {
282
+ const deniedMembers = await redis.smismember(
283
+ [prefix, "denyList", "all"].join(":"),
284
+ members
285
+ );
286
+ let deniedMember = void 0;
287
+ deniedMembers.map((memberDenied, index) => {
288
+ if (memberDenied) {
289
+ blockMember(members[index]);
290
+ deniedMember = members[index];
291
+ }
292
+ });
293
+ return deniedMember;
294
+ };
295
+ var resolveResponses = ([ratelimitResponse, denyListResponse]) => {
296
+ if (denyListResponse) {
297
+ ratelimitResponse.success = false;
298
+ ratelimitResponse.remaining = 0;
299
+ ratelimitResponse.reason = "denyList";
300
+ ratelimitResponse.deniedValue = denyListResponse;
301
+ }
302
+ return ratelimitResponse;
303
+ };
304
+ var defaultDeniedResponse = (deniedValue) => {
305
+ return {
306
+ success: false,
307
+ limit: 0,
308
+ remaining: 0,
309
+ reset: 0,
310
+ pending: Promise.resolve(),
311
+ reason: "denyList",
312
+ deniedValue
313
+ };
314
+ };
315
+
235
316
  // src/ratelimit.ts
236
317
  var Ratelimit = class {
237
318
  limiter;
238
319
  ctx;
239
320
  prefix;
240
321
  timeout;
322
+ primaryRedis;
241
323
  analytics;
324
+ enableProtection;
242
325
  constructor(config) {
243
326
  this.ctx = config.ctx;
244
327
  this.limiter = config.limiter;
245
328
  this.timeout = config.timeout ?? 5e3;
246
329
  this.prefix = config.prefix ?? "@upstash/ratelimit";
330
+ this.enableProtection = config.enableProtection ?? false;
331
+ this.primaryRedis = "redis" in this.ctx ? this.ctx.redis : this.ctx.regionContexts[0].redis;
247
332
  this.analytics = config.analytics ? new Analytics({
248
- redis: Array.isArray(this.ctx.redis) ? this.ctx.redis[0] : this.ctx.redis,
333
+ redis: this.primaryRedis,
249
334
  prefix: this.prefix
250
335
  }) : void 0;
251
336
  if (config.ephemeralCache instanceof Map) {
@@ -291,57 +376,14 @@ var Ratelimit = class {
291
376
  * ```
292
377
  */
293
378
  limit = async (identifier, req) => {
294
- const key = [this.prefix, identifier].join(":");
295
379
  let timeoutId = null;
296
380
  try {
297
- const arr = [this.limiter().limit(this.ctx, key, req?.rate)];
298
- if (this.timeout > 0) {
299
- arr.push(
300
- new Promise((resolve) => {
301
- timeoutId = setTimeout(() => {
302
- resolve({
303
- success: true,
304
- limit: 0,
305
- remaining: 0,
306
- reset: 0,
307
- pending: Promise.resolve()
308
- });
309
- }, this.timeout);
310
- })
311
- );
312
- }
313
- const res = await Promise.race(arr);
314
- if (this.analytics) {
315
- try {
316
- const geo = req ? this.analytics.extractGeo(req) : void 0;
317
- const analyticsP = this.analytics.record({
318
- identifier,
319
- time: Date.now(),
320
- success: res.success,
321
- ...geo
322
- }).catch((err) => {
323
- let errorMessage = "Failed to record analytics";
324
- if (`${err}`.includes("WRONGTYPE")) {
325
- errorMessage = `
326
- Failed to record analytics. See the information below:
327
-
328
- This can occur when you uprade to Ratelimit version 1.1.2
329
- or later from an earlier version.
330
-
331
- This occurs simply because the way we store analytics data
332
- has changed. To avoid getting this error, disable analytics
333
- for *an hour*, then simply enable it back.
334
-
335
- `;
336
- }
337
- console.warn(errorMessage, err);
338
- });
339
- res.pending = Promise.all([res.pending, analyticsP]);
340
- } catch (err) {
341
- console.warn("Failed to record analytics", err);
342
- }
343
- }
344
- return res;
381
+ const response = this.getRatelimitResponse(identifier, req);
382
+ const { responseArray, newTimeoutId } = this.applyTimeout(response);
383
+ timeoutId = newTimeoutId;
384
+ const timedResponse = await Promise.race(responseArray);
385
+ const finalResponse = this.submitAnalytics(timedResponse, identifier, req);
386
+ return finalResponse;
345
387
  } finally {
346
388
  if (timeoutId) {
347
389
  clearTimeout(timeoutId);
@@ -400,6 +442,125 @@ for *an hour*, then simply enable it back.
400
442
  const pattern = [this.prefix, identifier].join(":");
401
443
  return await this.limiter().getRemaining(this.ctx, pattern);
402
444
  };
445
+ /**
446
+ * Checks if the identifier or the values in req are in the deny list cache.
447
+ * If so, returns the default denied response.
448
+ *
449
+ * Otherwise, calls redis to check the rate limit and deny list. Returns after
450
+ * resolving the result. Resolving is overriding the rate limit result if
451
+ * the some value is in deny list.
452
+ *
453
+ * @param identifier identifier to block
454
+ * @param req options with ip, user agent, country, rate and geo info
455
+ * @returns rate limit response
456
+ */
457
+ getRatelimitResponse = async (identifier, req) => {
458
+ const key = this.getKey(identifier);
459
+ const definedMembers = this.getDefinedMembers(identifier, req);
460
+ const deniedMember = checkDenyListCache(definedMembers);
461
+ let result;
462
+ if (deniedMember) {
463
+ result = [defaultDeniedResponse(deniedMember), deniedMember];
464
+ } else {
465
+ result = await Promise.all([
466
+ this.limiter().limit(this.ctx, key, req?.rate),
467
+ checkDenyList(
468
+ this.primaryRedis,
469
+ this.prefix,
470
+ definedMembers
471
+ )
472
+ ]);
473
+ }
474
+ return resolveResponses(result);
475
+ };
476
+ /**
477
+ * Creates an array with the original response promise and a timeout promise
478
+ * if this.timeout > 0.
479
+ *
480
+ * @param response Ratelimit response promise
481
+ * @returns array with the response and timeout promise. also includes the timeout id
482
+ */
483
+ applyTimeout = (response) => {
484
+ let newTimeoutId = null;
485
+ const responseArray = [response];
486
+ if (this.timeout > 0) {
487
+ const timeoutResponse = new Promise((resolve) => {
488
+ newTimeoutId = setTimeout(() => {
489
+ resolve({
490
+ success: true,
491
+ limit: 0,
492
+ remaining: 0,
493
+ reset: 0,
494
+ pending: Promise.resolve(),
495
+ reason: "timeout"
496
+ });
497
+ }, this.timeout);
498
+ });
499
+ responseArray.push(timeoutResponse);
500
+ }
501
+ return {
502
+ responseArray,
503
+ newTimeoutId
504
+ };
505
+ };
506
+ /**
507
+ * submits analytics if this.analytics is set
508
+ *
509
+ * @param ratelimitResponse final rate limit response
510
+ * @param identifier identifier to submit
511
+ * @param req limit options
512
+ * @returns rate limit response after updating the .pending field
513
+ */
514
+ submitAnalytics = (ratelimitResponse, identifier, req) => {
515
+ if (this.analytics) {
516
+ try {
517
+ const geo = req ? this.analytics.extractGeo(req) : void 0;
518
+ const analyticsP = this.analytics.record({
519
+ identifier: ratelimitResponse.reason === "denyList" ? ratelimitResponse.deniedValue : identifier,
520
+ time: Date.now(),
521
+ success: ratelimitResponse.reason === "denyList" ? "denied" : ratelimitResponse.success,
522
+ ...geo
523
+ }).catch((err) => {
524
+ let errorMessage = "Failed to record analytics";
525
+ if (`${err}`.includes("WRONGTYPE")) {
526
+ errorMessage = `
527
+ Failed to record analytics. See the information below:
528
+
529
+ This can occur when you uprade to Ratelimit version 1.1.2
530
+ or later from an earlier version.
531
+
532
+ This occurs simply because the way we store analytics data
533
+ has changed. To avoid getting this error, disable analytics
534
+ for *an hour*, then simply enable it back.
535
+
536
+ `;
537
+ }
538
+ console.warn(errorMessage, err);
539
+ });
540
+ ratelimitResponse.pending = Promise.all([ratelimitResponse.pending, analyticsP]);
541
+ } catch (err) {
542
+ console.warn("Failed to record analytics", err);
543
+ }
544
+ ;
545
+ }
546
+ ;
547
+ return ratelimitResponse;
548
+ };
549
+ getKey = (identifier) => {
550
+ return [this.prefix, identifier].join(":");
551
+ };
552
+ /**
553
+ * returns a list of defined values from
554
+ * [identifier, req.ip, req.userAgent, req.country]
555
+ *
556
+ * @param identifier identifier
557
+ * @param req limit options
558
+ * @returns list of defined values
559
+ */
560
+ getDefinedMembers = (identifier, req) => {
561
+ const members = [identifier, req?.ip, req?.userAgent, req?.country];
562
+ return members.filter((item) => Boolean(item));
563
+ };
403
564
  };
404
565
 
405
566
  // src/multi.ts
@@ -423,7 +584,11 @@ var MultiRegionRatelimit = class extends Ratelimit {
423
584
  timeout: config.timeout,
424
585
  analytics: config.analytics,
425
586
  ctx: {
426
- redis: config.redis,
587
+ regionContexts: config.redis.map((redis) => ({
588
+ redis,
589
+ scriptHashes: {},
590
+ cacheScripts: config.cacheScripts ?? true
591
+ })),
427
592
  cache: config.ephemeralCache ? new Cache(config.ephemeralCache) : void 0
428
593
  }
429
594
  });
@@ -458,7 +623,8 @@ var MultiRegionRatelimit = class extends Ratelimit {
458
623
  limit: tokens,
459
624
  remaining: 0,
460
625
  reset: reset2,
461
- pending: Promise.resolve()
626
+ pending: Promise.resolve(),
627
+ reason: "cacheBlock"
462
628
  };
463
629
  }
464
630
  }
@@ -466,10 +632,12 @@ var MultiRegionRatelimit = class extends Ratelimit {
466
632
  const bucket = Math.floor(Date.now() / windowDuration);
467
633
  const key = [identifier, bucket].join(":");
468
634
  const incrementBy = rate ? Math.max(1, rate) : 1;
469
- const dbs = ctx.redis.map((redis) => ({
470
- redis,
471
- request: redis.eval(
635
+ const dbs = ctx.regionContexts.map((regionContext) => ({
636
+ redis: regionContext.redis,
637
+ request: safeEval(
638
+ regionContext,
472
639
  fixedWindowLimitScript,
640
+ "limitHash",
473
641
  [key],
474
642
  [requestId, windowDuration, incrementBy]
475
643
  )
@@ -540,9 +708,15 @@ var MultiRegionRatelimit = class extends Ratelimit {
540
708
  async getRemaining(ctx, identifier) {
541
709
  const bucket = Math.floor(Date.now() / windowDuration);
542
710
  const key = [identifier, bucket].join(":");
543
- const dbs = ctx.redis.map((redis) => ({
544
- redis,
545
- request: redis.eval(fixedWindowRemainingTokensScript, [key], [null])
711
+ const dbs = ctx.regionContexts.map((regionContext) => ({
712
+ redis: regionContext.redis,
713
+ request: safeEval(
714
+ regionContext,
715
+ fixedWindowRemainingTokensScript,
716
+ "getRemainingHash",
717
+ [key],
718
+ [null]
719
+ )
546
720
  }));
547
721
  const firstResponse = await Promise.any(dbs.map((s) => s.request));
548
722
  const usedTokens = firstResponse.reduce((accTokens, usedToken, index) => {
@@ -559,9 +733,15 @@ var MultiRegionRatelimit = class extends Ratelimit {
559
733
  if (ctx.cache) {
560
734
  ctx.cache.pop(identifier);
561
735
  }
562
- for (const db of ctx.redis) {
563
- await db.eval(resetScript, [pattern], [null]);
564
- }
736
+ await Promise.all(ctx.regionContexts.map((regionContext) => {
737
+ safeEval(
738
+ regionContext,
739
+ resetScript,
740
+ "resetHash",
741
+ [pattern],
742
+ [null]
743
+ );
744
+ }));
565
745
  }
566
746
  });
567
747
  }
@@ -586,6 +766,19 @@ var MultiRegionRatelimit = class extends Ratelimit {
586
766
  const windowDuration = ms(window);
587
767
  return () => ({
588
768
  async limit(ctx, identifier, rate) {
769
+ if (ctx.cache) {
770
+ const { blocked, reset: reset2 } = ctx.cache.isBlocked(identifier);
771
+ if (blocked) {
772
+ return {
773
+ success: false,
774
+ limit: tokens,
775
+ remaining: 0,
776
+ reset: reset2,
777
+ pending: Promise.resolve(),
778
+ reason: "cacheBlock"
779
+ };
780
+ }
781
+ }
589
782
  const requestId = randomId();
590
783
  const now = Date.now();
591
784
  const currentWindow = Math.floor(now / windowSize);
@@ -593,10 +786,12 @@ var MultiRegionRatelimit = class extends Ratelimit {
593
786
  const previousWindow = currentWindow - 1;
594
787
  const previousKey = [identifier, previousWindow].join(":");
595
788
  const incrementBy = rate ? Math.max(1, rate) : 1;
596
- const dbs = ctx.redis.map((redis) => ({
597
- redis,
598
- request: redis.eval(
789
+ const dbs = ctx.regionContexts.map((regionContext) => ({
790
+ redis: regionContext.redis,
791
+ request: safeEval(
792
+ regionContext,
599
793
  slidingWindowLimitScript,
794
+ "limitHash",
600
795
  [currentKey, previousKey],
601
796
  [tokens, now, windowDuration, requestId, incrementBy]
602
797
  // lua seems to return `1` for true and `null` for false
@@ -681,10 +876,12 @@ var MultiRegionRatelimit = class extends Ratelimit {
681
876
  const currentKey = [identifier, currentWindow].join(":");
682
877
  const previousWindow = currentWindow - 1;
683
878
  const previousKey = [identifier, previousWindow].join(":");
684
- const dbs = ctx.redis.map((redis) => ({
685
- redis,
686
- request: redis.eval(
879
+ const dbs = ctx.regionContexts.map((regionContext) => ({
880
+ redis: regionContext.redis,
881
+ request: safeEval(
882
+ regionContext,
687
883
  slidingWindowRemainingTokensScript,
884
+ "getRemainingHash",
688
885
  [currentKey, previousKey],
689
886
  [now, windowSize]
690
887
  // lua seems to return `1` for true and `null` for false
@@ -698,9 +895,15 @@ var MultiRegionRatelimit = class extends Ratelimit {
698
895
  if (ctx.cache) {
699
896
  ctx.cache.pop(identifier);
700
897
  }
701
- for (const db of ctx.redis) {
702
- await db.eval(resetScript, [pattern], [null]);
703
- }
898
+ await Promise.all(ctx.regionContexts.map((regionContext) => {
899
+ safeEval(
900
+ regionContext,
901
+ resetScript,
902
+ "resetHash",
903
+ [pattern],
904
+ [null]
905
+ );
906
+ }));
704
907
  }
705
908
  });
706
909
  }
@@ -873,9 +1076,12 @@ var RegionRatelimit = class extends Ratelimit {
873
1076
  timeout: config.timeout,
874
1077
  analytics: config.analytics,
875
1078
  ctx: {
876
- redis: config.redis
1079
+ redis: config.redis,
1080
+ scriptHashes: {},
1081
+ cacheScripts: config.cacheScripts ?? true
877
1082
  },
878
- ephemeralCache: config.ephemeralCache
1083
+ ephemeralCache: config.ephemeralCache,
1084
+ enableProtection: config.enableProtection
879
1085
  });
880
1086
  }
881
1087
  /**
@@ -910,13 +1116,16 @@ var RegionRatelimit = class extends Ratelimit {
910
1116
  limit: tokens,
911
1117
  remaining: 0,
912
1118
  reset: reset2,
913
- pending: Promise.resolve()
1119
+ pending: Promise.resolve(),
1120
+ reason: "cacheBlock"
914
1121
  };
915
1122
  }
916
1123
  }
917
1124
  const incrementBy = rate ? Math.max(1, rate) : 1;
918
- const usedTokensAfterUpdate = await ctx.redis.eval(
1125
+ const usedTokensAfterUpdate = await safeEval(
1126
+ ctx,
919
1127
  fixedWindowLimitScript2,
1128
+ "limitHash",
920
1129
  [key],
921
1130
  [windowDuration, incrementBy]
922
1131
  );
@@ -937,8 +1146,10 @@ var RegionRatelimit = class extends Ratelimit {
937
1146
  async getRemaining(ctx, identifier) {
938
1147
  const bucket = Math.floor(Date.now() / windowDuration);
939
1148
  const key = [identifier, bucket].join(":");
940
- const usedTokens = await ctx.redis.eval(
1149
+ const usedTokens = await safeEval(
1150
+ ctx,
941
1151
  fixedWindowRemainingTokensScript2,
1152
+ "getRemainingHash",
942
1153
  [key],
943
1154
  [null]
944
1155
  );
@@ -949,7 +1160,13 @@ var RegionRatelimit = class extends Ratelimit {
949
1160
  if (ctx.cache) {
950
1161
  ctx.cache.pop(identifier);
951
1162
  }
952
- await ctx.redis.eval(resetScript, [pattern], [null]);
1163
+ await safeEval(
1164
+ ctx,
1165
+ resetScript,
1166
+ "resetHash",
1167
+ [pattern],
1168
+ [null]
1169
+ );
953
1170
  }
954
1171
  });
955
1172
  }
@@ -986,13 +1203,16 @@ var RegionRatelimit = class extends Ratelimit {
986
1203
  limit: tokens,
987
1204
  remaining: 0,
988
1205
  reset: reset2,
989
- pending: Promise.resolve()
1206
+ pending: Promise.resolve(),
1207
+ reason: "cacheBlock"
990
1208
  };
991
1209
  }
992
1210
  }
993
1211
  const incrementBy = rate ? Math.max(1, rate) : 1;
994
- const remainingTokens = await ctx.redis.eval(
1212
+ const remainingTokens = await safeEval(
1213
+ ctx,
995
1214
  slidingWindowLimitScript2,
1215
+ "limitHash",
996
1216
  [currentKey, previousKey],
997
1217
  [tokens, now, windowSize, incrementBy]
998
1218
  );
@@ -1015,8 +1235,10 @@ var RegionRatelimit = class extends Ratelimit {
1015
1235
  const currentKey = [identifier, currentWindow].join(":");
1016
1236
  const previousWindow = currentWindow - 1;
1017
1237
  const previousKey = [identifier, previousWindow].join(":");
1018
- const usedTokens = await ctx.redis.eval(
1238
+ const usedTokens = await safeEval(
1239
+ ctx,
1019
1240
  slidingWindowRemainingTokensScript2,
1241
+ "getRemainingHash",
1020
1242
  [currentKey, previousKey],
1021
1243
  [now, windowSize]
1022
1244
  );
@@ -1027,7 +1249,13 @@ var RegionRatelimit = class extends Ratelimit {
1027
1249
  if (ctx.cache) {
1028
1250
  ctx.cache.pop(identifier);
1029
1251
  }
1030
- await ctx.redis.eval(resetScript, [pattern], [null]);
1252
+ await safeEval(
1253
+ ctx,
1254
+ resetScript,
1255
+ "resetHash",
1256
+ [pattern],
1257
+ [null]
1258
+ );
1031
1259
  }
1032
1260
  });
1033
1261
  }
@@ -1056,14 +1284,17 @@ var RegionRatelimit = class extends Ratelimit {
1056
1284
  limit: maxTokens,
1057
1285
  remaining: 0,
1058
1286
  reset: reset2,
1059
- pending: Promise.resolve()
1287
+ pending: Promise.resolve(),
1288
+ reason: "cacheBlock"
1060
1289
  };
1061
1290
  }
1062
1291
  }
1063
1292
  const now = Date.now();
1064
1293
  const incrementBy = rate ? Math.max(1, rate) : 1;
1065
- const [remaining, reset] = await ctx.redis.eval(
1294
+ const [remaining, reset] = await safeEval(
1295
+ ctx,
1066
1296
  tokenBucketLimitScript,
1297
+ "limitHash",
1067
1298
  [identifier],
1068
1299
  [maxTokens, intervalDuration, refillRate, now, incrementBy]
1069
1300
  );
@@ -1080,8 +1311,10 @@ var RegionRatelimit = class extends Ratelimit {
1080
1311
  };
1081
1312
  },
1082
1313
  async getRemaining(ctx, identifier) {
1083
- const remainingTokens = await ctx.redis.eval(
1314
+ const remainingTokens = await safeEval(
1315
+ ctx,
1084
1316
  tokenBucketRemainingTokensScript,
1317
+ "getRemainingHash",
1085
1318
  [identifier],
1086
1319
  [maxTokens]
1087
1320
  );
@@ -1092,7 +1325,13 @@ var RegionRatelimit = class extends Ratelimit {
1092
1325
  if (ctx.cache) {
1093
1326
  ctx.cache.pop(identifier);
1094
1327
  }
1095
- await ctx.redis.eval(resetScript, [pattern], [null]);
1328
+ await safeEval(
1329
+ ctx,
1330
+ resetScript,
1331
+ "resetHash",
1332
+ [pattern],
1333
+ [null]
1334
+ );
1096
1335
  }
1097
1336
  });
1098
1337
  }
@@ -1135,9 +1374,13 @@ var RegionRatelimit = class extends Ratelimit {
1135
1374
  if (hit) {
1136
1375
  const cachedTokensAfterUpdate = ctx.cache.incr(key);
1137
1376
  const success = cachedTokensAfterUpdate < tokens;
1138
- const pending = success ? ctx.redis.eval(cachedFixedWindowLimitScript, [key], [windowDuration, incrementBy]).then((t) => {
1139
- ctx.cache.set(key, t);
1140
- }) : Promise.resolve();
1377
+ const pending = success ? safeEval(
1378
+ ctx,
1379
+ cachedFixedWindowLimitScript,
1380
+ "limitHash",
1381
+ [key],
1382
+ [windowDuration, incrementBy]
1383
+ ) : Promise.resolve();
1141
1384
  return {
1142
1385
  success,
1143
1386
  limit: tokens,
@@ -1146,8 +1389,10 @@ var RegionRatelimit = class extends Ratelimit {
1146
1389
  pending
1147
1390
  };
1148
1391
  }
1149
- const usedTokensAfterUpdate = await ctx.redis.eval(
1392
+ const usedTokensAfterUpdate = await safeEval(
1393
+ ctx,
1150
1394
  cachedFixedWindowLimitScript,
1395
+ "limitHash",
1151
1396
  [key],
1152
1397
  [windowDuration, incrementBy]
1153
1398
  );
@@ -1172,8 +1417,10 @@ var RegionRatelimit = class extends Ratelimit {
1172
1417
  const cachedUsedTokens = ctx.cache.get(key) ?? 0;
1173
1418
  return Math.max(0, tokens - cachedUsedTokens);
1174
1419
  }
1175
- const usedTokens = await ctx.redis.eval(
1420
+ const usedTokens = await safeEval(
1421
+ ctx,
1176
1422
  cachedFixedWindowRemainingTokenScript,
1423
+ "getRemainingHash",
1177
1424
  [key],
1178
1425
  [null]
1179
1426
  );
@@ -1187,7 +1434,13 @@ var RegionRatelimit = class extends Ratelimit {
1187
1434
  const key = [identifier, bucket].join(":");
1188
1435
  ctx.cache.pop(key);
1189
1436
  const pattern = [identifier, "*"].join(":");
1190
- await ctx.redis.eval(resetScript, [pattern], [null]);
1437
+ await safeEval(
1438
+ ctx,
1439
+ resetScript,
1440
+ "resetHash",
1441
+ [pattern],
1442
+ [null]
1443
+ );
1191
1444
  }
1192
1445
  });
1193
1446
  }