@upstash/ratelimit 1.1.3 → 1.2.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.js CHANGED
@@ -125,6 +125,9 @@ var Cache = class {
125
125
  empty() {
126
126
  this.cache.clear();
127
127
  }
128
+ size() {
129
+ return this.cache.size;
130
+ }
128
131
  };
129
132
 
130
133
  // src/duration.ts
@@ -151,6 +154,37 @@ function ms(d) {
151
154
  }
152
155
  }
153
156
 
157
+ // src/hash.ts
158
+ var setHash = async (ctx, script, kind) => {
159
+ const regionContexts = "redis" in ctx ? [ctx] : ctx.regionContexts;
160
+ const hashSample = regionContexts[0].scriptHashes[kind];
161
+ if (!hashSample) {
162
+ await Promise.all(regionContexts.map(async (context) => {
163
+ context.scriptHashes[kind] = await context.redis.scriptLoad(script);
164
+ }));
165
+ }
166
+ ;
167
+ };
168
+ var safeEval = async (ctx, script, kind, keys, args) => {
169
+ if (!ctx.cacheScripts) {
170
+ return await ctx.redis.eval(script, keys, args);
171
+ }
172
+ ;
173
+ await setHash(ctx, script, kind);
174
+ try {
175
+ return await ctx.redis.evalsha(ctx.scriptHashes[kind], keys, args);
176
+ } catch (error) {
177
+ if (`${error}`.includes("NOSCRIPT")) {
178
+ console.log("Script with the expected hash was not found in redis db. It is probably flushed. Will load another scipt before continuing.");
179
+ ctx.scriptHashes[kind] = void 0;
180
+ await setHash(ctx, script, kind);
181
+ console.log(" New script successfully loaded.");
182
+ return await ctx.redis.evalsha(ctx.scriptHashes[kind], keys, args);
183
+ }
184
+ throw error;
185
+ }
186
+ };
187
+
154
188
  // src/lua-scripts/multi.ts
155
189
  var fixedWindowLimitScript = `
156
190
  local key = KEYS[1]
@@ -260,20 +294,71 @@ var resetScript = `
260
294
  until cursor == "0"
261
295
  `;
262
296
 
297
+ // src/deny-list.ts
298
+ var denyListCache = new Cache(/* @__PURE__ */ new Map());
299
+ var checkDenyListCache = (members) => {
300
+ return members.find(
301
+ (member) => denyListCache.isBlocked(member).blocked
302
+ );
303
+ };
304
+ var blockMember = (member) => {
305
+ if (denyListCache.size() > 1e3)
306
+ denyListCache.empty();
307
+ denyListCache.blockUntil(member, Date.now() + 6e4);
308
+ };
309
+ var checkDenyList = async (redis, prefix, members) => {
310
+ const deniedMembers = await redis.smismember(
311
+ [prefix, "denyList", "all"].join(":"),
312
+ members
313
+ );
314
+ let deniedMember = void 0;
315
+ deniedMembers.map((memberDenied, index) => {
316
+ if (memberDenied) {
317
+ blockMember(members[index]);
318
+ deniedMember = members[index];
319
+ }
320
+ });
321
+ return deniedMember;
322
+ };
323
+ var resolveResponses = ([ratelimitResponse, denyListResponse]) => {
324
+ if (denyListResponse) {
325
+ ratelimitResponse.success = false;
326
+ ratelimitResponse.remaining = 0;
327
+ ratelimitResponse.reason = "denyList";
328
+ ratelimitResponse.deniedValue = denyListResponse;
329
+ }
330
+ return ratelimitResponse;
331
+ };
332
+ var defaultDeniedResponse = (deniedValue) => {
333
+ return {
334
+ success: false,
335
+ limit: 0,
336
+ remaining: 0,
337
+ reset: 0,
338
+ pending: Promise.resolve(),
339
+ reason: "denyList",
340
+ deniedValue
341
+ };
342
+ };
343
+
263
344
  // src/ratelimit.ts
264
345
  var Ratelimit = class {
265
346
  limiter;
266
347
  ctx;
267
348
  prefix;
268
349
  timeout;
350
+ primaryRedis;
269
351
  analytics;
352
+ enableProtection;
270
353
  constructor(config) {
271
354
  this.ctx = config.ctx;
272
355
  this.limiter = config.limiter;
273
356
  this.timeout = config.timeout ?? 5e3;
274
357
  this.prefix = config.prefix ?? "@upstash/ratelimit";
358
+ this.enableProtection = config.enableProtection ?? false;
359
+ this.primaryRedis = "redis" in this.ctx ? this.ctx.redis : this.ctx.regionContexts[0].redis;
275
360
  this.analytics = config.analytics ? new Analytics({
276
- redis: Array.isArray(this.ctx.redis) ? this.ctx.redis[0] : this.ctx.redis,
361
+ redis: this.primaryRedis,
277
362
  prefix: this.prefix
278
363
  }) : void 0;
279
364
  if (config.ephemeralCache instanceof Map) {
@@ -319,57 +404,14 @@ var Ratelimit = class {
319
404
  * ```
320
405
  */
321
406
  limit = async (identifier, req) => {
322
- const key = [this.prefix, identifier].join(":");
323
407
  let timeoutId = null;
324
408
  try {
325
- const arr = [this.limiter().limit(this.ctx, key, req?.rate)];
326
- if (this.timeout > 0) {
327
- arr.push(
328
- new Promise((resolve) => {
329
- timeoutId = setTimeout(() => {
330
- resolve({
331
- success: true,
332
- limit: 0,
333
- remaining: 0,
334
- reset: 0,
335
- pending: Promise.resolve()
336
- });
337
- }, this.timeout);
338
- })
339
- );
340
- }
341
- const res = await Promise.race(arr);
342
- if (this.analytics) {
343
- try {
344
- const geo = req ? this.analytics.extractGeo(req) : void 0;
345
- const analyticsP = this.analytics.record({
346
- identifier,
347
- time: Date.now(),
348
- success: res.success,
349
- ...geo
350
- }).catch((err) => {
351
- let errorMessage = "Failed to record analytics";
352
- if (`${err}`.includes("WRONGTYPE")) {
353
- errorMessage = `
354
- Failed to record analytics. See the information below:
355
-
356
- This can occur when you uprade to Ratelimit version 1.1.2
357
- or later from an earlier version.
358
-
359
- This occurs simply because the way we store analytics data
360
- has changed. To avoid getting this error, disable analytics
361
- for *an hour*, then simply enable it back.
362
-
363
- `;
364
- }
365
- console.warn(errorMessage, err);
366
- });
367
- res.pending = Promise.all([res.pending, analyticsP]);
368
- } catch (err) {
369
- console.warn("Failed to record analytics", err);
370
- }
371
- }
372
- return res;
409
+ const response = this.getRatelimitResponse(identifier, req);
410
+ const { responseArray, newTimeoutId } = this.applyTimeout(response);
411
+ timeoutId = newTimeoutId;
412
+ const timedResponse = await Promise.race(responseArray);
413
+ const finalResponse = this.submitAnalytics(timedResponse, identifier, req);
414
+ return finalResponse;
373
415
  } finally {
374
416
  if (timeoutId) {
375
417
  clearTimeout(timeoutId);
@@ -428,6 +470,125 @@ for *an hour*, then simply enable it back.
428
470
  const pattern = [this.prefix, identifier].join(":");
429
471
  return await this.limiter().getRemaining(this.ctx, pattern);
430
472
  };
473
+ /**
474
+ * Checks if the identifier or the values in req are in the deny list cache.
475
+ * If so, returns the default denied response.
476
+ *
477
+ * Otherwise, calls redis to check the rate limit and deny list. Returns after
478
+ * resolving the result. Resolving is overriding the rate limit result if
479
+ * the some value is in deny list.
480
+ *
481
+ * @param identifier identifier to block
482
+ * @param req options with ip, user agent, country, rate and geo info
483
+ * @returns rate limit response
484
+ */
485
+ getRatelimitResponse = async (identifier, req) => {
486
+ const key = this.getKey(identifier);
487
+ const definedMembers = this.getDefinedMembers(identifier, req);
488
+ const deniedMember = checkDenyListCache(definedMembers);
489
+ let result;
490
+ if (deniedMember) {
491
+ result = [defaultDeniedResponse(deniedMember), deniedMember];
492
+ } else {
493
+ result = await Promise.all([
494
+ this.limiter().limit(this.ctx, key, req?.rate),
495
+ checkDenyList(
496
+ this.primaryRedis,
497
+ this.prefix,
498
+ definedMembers
499
+ )
500
+ ]);
501
+ }
502
+ return resolveResponses(result);
503
+ };
504
+ /**
505
+ * Creates an array with the original response promise and a timeout promise
506
+ * if this.timeout > 0.
507
+ *
508
+ * @param response Ratelimit response promise
509
+ * @returns array with the response and timeout promise. also includes the timeout id
510
+ */
511
+ applyTimeout = (response) => {
512
+ let newTimeoutId = null;
513
+ const responseArray = [response];
514
+ if (this.timeout > 0) {
515
+ const timeoutResponse = new Promise((resolve) => {
516
+ newTimeoutId = setTimeout(() => {
517
+ resolve({
518
+ success: true,
519
+ limit: 0,
520
+ remaining: 0,
521
+ reset: 0,
522
+ pending: Promise.resolve(),
523
+ reason: "timeout"
524
+ });
525
+ }, this.timeout);
526
+ });
527
+ responseArray.push(timeoutResponse);
528
+ }
529
+ return {
530
+ responseArray,
531
+ newTimeoutId
532
+ };
533
+ };
534
+ /**
535
+ * submits analytics if this.analytics is set
536
+ *
537
+ * @param ratelimitResponse final rate limit response
538
+ * @param identifier identifier to submit
539
+ * @param req limit options
540
+ * @returns rate limit response after updating the .pending field
541
+ */
542
+ submitAnalytics = (ratelimitResponse, identifier, req) => {
543
+ if (this.analytics) {
544
+ try {
545
+ const geo = req ? this.analytics.extractGeo(req) : void 0;
546
+ const analyticsP = this.analytics.record({
547
+ identifier: ratelimitResponse.reason === "denyList" ? ratelimitResponse.deniedValue : identifier,
548
+ time: Date.now(),
549
+ success: ratelimitResponse.reason === "denyList" ? "denied" : ratelimitResponse.success,
550
+ ...geo
551
+ }).catch((err) => {
552
+ let errorMessage = "Failed to record analytics";
553
+ if (`${err}`.includes("WRONGTYPE")) {
554
+ errorMessage = `
555
+ Failed to record analytics. See the information below:
556
+
557
+ This can occur when you uprade to Ratelimit version 1.1.2
558
+ or later from an earlier version.
559
+
560
+ This occurs simply because the way we store analytics data
561
+ has changed. To avoid getting this error, disable analytics
562
+ for *an hour*, then simply enable it back.
563
+
564
+ `;
565
+ }
566
+ console.warn(errorMessage, err);
567
+ });
568
+ ratelimitResponse.pending = Promise.all([ratelimitResponse.pending, analyticsP]);
569
+ } catch (err) {
570
+ console.warn("Failed to record analytics", err);
571
+ }
572
+ ;
573
+ }
574
+ ;
575
+ return ratelimitResponse;
576
+ };
577
+ getKey = (identifier) => {
578
+ return [this.prefix, identifier].join(":");
579
+ };
580
+ /**
581
+ * returns a list of defined values from
582
+ * [identifier, req.ip, req.userAgent, req.country]
583
+ *
584
+ * @param identifier identifier
585
+ * @param req limit options
586
+ * @returns list of defined values
587
+ */
588
+ getDefinedMembers = (identifier, req) => {
589
+ const members = [identifier, req?.ip, req?.userAgent, req?.country];
590
+ return members.filter((item) => Boolean(item));
591
+ };
431
592
  };
432
593
 
433
594
  // src/multi.ts
@@ -451,7 +612,11 @@ var MultiRegionRatelimit = class extends Ratelimit {
451
612
  timeout: config.timeout,
452
613
  analytics: config.analytics,
453
614
  ctx: {
454
- redis: config.redis,
615
+ regionContexts: config.redis.map((redis) => ({
616
+ redis,
617
+ scriptHashes: {},
618
+ cacheScripts: config.cacheScripts ?? true
619
+ })),
455
620
  cache: config.ephemeralCache ? new Cache(config.ephemeralCache) : void 0
456
621
  }
457
622
  });
@@ -486,7 +651,8 @@ var MultiRegionRatelimit = class extends Ratelimit {
486
651
  limit: tokens,
487
652
  remaining: 0,
488
653
  reset: reset2,
489
- pending: Promise.resolve()
654
+ pending: Promise.resolve(),
655
+ reason: "cacheBlock"
490
656
  };
491
657
  }
492
658
  }
@@ -494,10 +660,12 @@ var MultiRegionRatelimit = class extends Ratelimit {
494
660
  const bucket = Math.floor(Date.now() / windowDuration);
495
661
  const key = [identifier, bucket].join(":");
496
662
  const incrementBy = rate ? Math.max(1, rate) : 1;
497
- const dbs = ctx.redis.map((redis) => ({
498
- redis,
499
- request: redis.eval(
663
+ const dbs = ctx.regionContexts.map((regionContext) => ({
664
+ redis: regionContext.redis,
665
+ request: safeEval(
666
+ regionContext,
500
667
  fixedWindowLimitScript,
668
+ "limitHash",
501
669
  [key],
502
670
  [requestId, windowDuration, incrementBy]
503
671
  )
@@ -568,9 +736,15 @@ var MultiRegionRatelimit = class extends Ratelimit {
568
736
  async getRemaining(ctx, identifier) {
569
737
  const bucket = Math.floor(Date.now() / windowDuration);
570
738
  const key = [identifier, bucket].join(":");
571
- const dbs = ctx.redis.map((redis) => ({
572
- redis,
573
- request: redis.eval(fixedWindowRemainingTokensScript, [key], [null])
739
+ const dbs = ctx.regionContexts.map((regionContext) => ({
740
+ redis: regionContext.redis,
741
+ request: safeEval(
742
+ regionContext,
743
+ fixedWindowRemainingTokensScript,
744
+ "getRemainingHash",
745
+ [key],
746
+ [null]
747
+ )
574
748
  }));
575
749
  const firstResponse = await Promise.any(dbs.map((s) => s.request));
576
750
  const usedTokens = firstResponse.reduce((accTokens, usedToken, index) => {
@@ -587,9 +761,15 @@ var MultiRegionRatelimit = class extends Ratelimit {
587
761
  if (ctx.cache) {
588
762
  ctx.cache.pop(identifier);
589
763
  }
590
- for (const db of ctx.redis) {
591
- await db.eval(resetScript, [pattern], [null]);
592
- }
764
+ await Promise.all(ctx.regionContexts.map((regionContext) => {
765
+ safeEval(
766
+ regionContext,
767
+ resetScript,
768
+ "resetHash",
769
+ [pattern],
770
+ [null]
771
+ );
772
+ }));
593
773
  }
594
774
  });
595
775
  }
@@ -614,6 +794,19 @@ var MultiRegionRatelimit = class extends Ratelimit {
614
794
  const windowDuration = ms(window);
615
795
  return () => ({
616
796
  async limit(ctx, identifier, rate) {
797
+ if (ctx.cache) {
798
+ const { blocked, reset: reset2 } = ctx.cache.isBlocked(identifier);
799
+ if (blocked) {
800
+ return {
801
+ success: false,
802
+ limit: tokens,
803
+ remaining: 0,
804
+ reset: reset2,
805
+ pending: Promise.resolve(),
806
+ reason: "cacheBlock"
807
+ };
808
+ }
809
+ }
617
810
  const requestId = randomId();
618
811
  const now = Date.now();
619
812
  const currentWindow = Math.floor(now / windowSize);
@@ -621,10 +814,12 @@ var MultiRegionRatelimit = class extends Ratelimit {
621
814
  const previousWindow = currentWindow - 1;
622
815
  const previousKey = [identifier, previousWindow].join(":");
623
816
  const incrementBy = rate ? Math.max(1, rate) : 1;
624
- const dbs = ctx.redis.map((redis) => ({
625
- redis,
626
- request: redis.eval(
817
+ const dbs = ctx.regionContexts.map((regionContext) => ({
818
+ redis: regionContext.redis,
819
+ request: safeEval(
820
+ regionContext,
627
821
  slidingWindowLimitScript,
822
+ "limitHash",
628
823
  [currentKey, previousKey],
629
824
  [tokens, now, windowDuration, requestId, incrementBy]
630
825
  // lua seems to return `1` for true and `null` for false
@@ -709,10 +904,12 @@ var MultiRegionRatelimit = class extends Ratelimit {
709
904
  const currentKey = [identifier, currentWindow].join(":");
710
905
  const previousWindow = currentWindow - 1;
711
906
  const previousKey = [identifier, previousWindow].join(":");
712
- const dbs = ctx.redis.map((redis) => ({
713
- redis,
714
- request: redis.eval(
907
+ const dbs = ctx.regionContexts.map((regionContext) => ({
908
+ redis: regionContext.redis,
909
+ request: safeEval(
910
+ regionContext,
715
911
  slidingWindowRemainingTokensScript,
912
+ "getRemainingHash",
716
913
  [currentKey, previousKey],
717
914
  [now, windowSize]
718
915
  // lua seems to return `1` for true and `null` for false
@@ -726,9 +923,15 @@ var MultiRegionRatelimit = class extends Ratelimit {
726
923
  if (ctx.cache) {
727
924
  ctx.cache.pop(identifier);
728
925
  }
729
- for (const db of ctx.redis) {
730
- await db.eval(resetScript, [pattern], [null]);
731
- }
926
+ await Promise.all(ctx.regionContexts.map((regionContext) => {
927
+ safeEval(
928
+ regionContext,
929
+ resetScript,
930
+ "resetHash",
931
+ [pattern],
932
+ [null]
933
+ );
934
+ }));
732
935
  }
733
936
  });
734
937
  }
@@ -901,9 +1104,12 @@ var RegionRatelimit = class extends Ratelimit {
901
1104
  timeout: config.timeout,
902
1105
  analytics: config.analytics,
903
1106
  ctx: {
904
- redis: config.redis
1107
+ redis: config.redis,
1108
+ scriptHashes: {},
1109
+ cacheScripts: config.cacheScripts ?? true
905
1110
  },
906
- ephemeralCache: config.ephemeralCache
1111
+ ephemeralCache: config.ephemeralCache,
1112
+ enableProtection: config.enableProtection
907
1113
  });
908
1114
  }
909
1115
  /**
@@ -938,13 +1144,16 @@ var RegionRatelimit = class extends Ratelimit {
938
1144
  limit: tokens,
939
1145
  remaining: 0,
940
1146
  reset: reset2,
941
- pending: Promise.resolve()
1147
+ pending: Promise.resolve(),
1148
+ reason: "cacheBlock"
942
1149
  };
943
1150
  }
944
1151
  }
945
1152
  const incrementBy = rate ? Math.max(1, rate) : 1;
946
- const usedTokensAfterUpdate = await ctx.redis.eval(
1153
+ const usedTokensAfterUpdate = await safeEval(
1154
+ ctx,
947
1155
  fixedWindowLimitScript2,
1156
+ "limitHash",
948
1157
  [key],
949
1158
  [windowDuration, incrementBy]
950
1159
  );
@@ -965,8 +1174,10 @@ var RegionRatelimit = class extends Ratelimit {
965
1174
  async getRemaining(ctx, identifier) {
966
1175
  const bucket = Math.floor(Date.now() / windowDuration);
967
1176
  const key = [identifier, bucket].join(":");
968
- const usedTokens = await ctx.redis.eval(
1177
+ const usedTokens = await safeEval(
1178
+ ctx,
969
1179
  fixedWindowRemainingTokensScript2,
1180
+ "getRemainingHash",
970
1181
  [key],
971
1182
  [null]
972
1183
  );
@@ -977,7 +1188,13 @@ var RegionRatelimit = class extends Ratelimit {
977
1188
  if (ctx.cache) {
978
1189
  ctx.cache.pop(identifier);
979
1190
  }
980
- await ctx.redis.eval(resetScript, [pattern], [null]);
1191
+ await safeEval(
1192
+ ctx,
1193
+ resetScript,
1194
+ "resetHash",
1195
+ [pattern],
1196
+ [null]
1197
+ );
981
1198
  }
982
1199
  });
983
1200
  }
@@ -1014,13 +1231,16 @@ var RegionRatelimit = class extends Ratelimit {
1014
1231
  limit: tokens,
1015
1232
  remaining: 0,
1016
1233
  reset: reset2,
1017
- pending: Promise.resolve()
1234
+ pending: Promise.resolve(),
1235
+ reason: "cacheBlock"
1018
1236
  };
1019
1237
  }
1020
1238
  }
1021
1239
  const incrementBy = rate ? Math.max(1, rate) : 1;
1022
- const remainingTokens = await ctx.redis.eval(
1240
+ const remainingTokens = await safeEval(
1241
+ ctx,
1023
1242
  slidingWindowLimitScript2,
1243
+ "limitHash",
1024
1244
  [currentKey, previousKey],
1025
1245
  [tokens, now, windowSize, incrementBy]
1026
1246
  );
@@ -1043,8 +1263,10 @@ var RegionRatelimit = class extends Ratelimit {
1043
1263
  const currentKey = [identifier, currentWindow].join(":");
1044
1264
  const previousWindow = currentWindow - 1;
1045
1265
  const previousKey = [identifier, previousWindow].join(":");
1046
- const usedTokens = await ctx.redis.eval(
1266
+ const usedTokens = await safeEval(
1267
+ ctx,
1047
1268
  slidingWindowRemainingTokensScript2,
1269
+ "getRemainingHash",
1048
1270
  [currentKey, previousKey],
1049
1271
  [now, windowSize]
1050
1272
  );
@@ -1055,7 +1277,13 @@ var RegionRatelimit = class extends Ratelimit {
1055
1277
  if (ctx.cache) {
1056
1278
  ctx.cache.pop(identifier);
1057
1279
  }
1058
- await ctx.redis.eval(resetScript, [pattern], [null]);
1280
+ await safeEval(
1281
+ ctx,
1282
+ resetScript,
1283
+ "resetHash",
1284
+ [pattern],
1285
+ [null]
1286
+ );
1059
1287
  }
1060
1288
  });
1061
1289
  }
@@ -1084,14 +1312,17 @@ var RegionRatelimit = class extends Ratelimit {
1084
1312
  limit: maxTokens,
1085
1313
  remaining: 0,
1086
1314
  reset: reset2,
1087
- pending: Promise.resolve()
1315
+ pending: Promise.resolve(),
1316
+ reason: "cacheBlock"
1088
1317
  };
1089
1318
  }
1090
1319
  }
1091
1320
  const now = Date.now();
1092
1321
  const incrementBy = rate ? Math.max(1, rate) : 1;
1093
- const [remaining, reset] = await ctx.redis.eval(
1322
+ const [remaining, reset] = await safeEval(
1323
+ ctx,
1094
1324
  tokenBucketLimitScript,
1325
+ "limitHash",
1095
1326
  [identifier],
1096
1327
  [maxTokens, intervalDuration, refillRate, now, incrementBy]
1097
1328
  );
@@ -1108,8 +1339,10 @@ var RegionRatelimit = class extends Ratelimit {
1108
1339
  };
1109
1340
  },
1110
1341
  async getRemaining(ctx, identifier) {
1111
- const remainingTokens = await ctx.redis.eval(
1342
+ const remainingTokens = await safeEval(
1343
+ ctx,
1112
1344
  tokenBucketRemainingTokensScript,
1345
+ "getRemainingHash",
1113
1346
  [identifier],
1114
1347
  [maxTokens]
1115
1348
  );
@@ -1120,7 +1353,13 @@ var RegionRatelimit = class extends Ratelimit {
1120
1353
  if (ctx.cache) {
1121
1354
  ctx.cache.pop(identifier);
1122
1355
  }
1123
- await ctx.redis.eval(resetScript, [pattern], [null]);
1356
+ await safeEval(
1357
+ ctx,
1358
+ resetScript,
1359
+ "resetHash",
1360
+ [pattern],
1361
+ [null]
1362
+ );
1124
1363
  }
1125
1364
  });
1126
1365
  }
@@ -1163,9 +1402,13 @@ var RegionRatelimit = class extends Ratelimit {
1163
1402
  if (hit) {
1164
1403
  const cachedTokensAfterUpdate = ctx.cache.incr(key);
1165
1404
  const success = cachedTokensAfterUpdate < tokens;
1166
- const pending = success ? ctx.redis.eval(cachedFixedWindowLimitScript, [key], [windowDuration, incrementBy]).then((t) => {
1167
- ctx.cache.set(key, t);
1168
- }) : Promise.resolve();
1405
+ const pending = success ? safeEval(
1406
+ ctx,
1407
+ cachedFixedWindowLimitScript,
1408
+ "limitHash",
1409
+ [key],
1410
+ [windowDuration, incrementBy]
1411
+ ) : Promise.resolve();
1169
1412
  return {
1170
1413
  success,
1171
1414
  limit: tokens,
@@ -1174,8 +1417,10 @@ var RegionRatelimit = class extends Ratelimit {
1174
1417
  pending
1175
1418
  };
1176
1419
  }
1177
- const usedTokensAfterUpdate = await ctx.redis.eval(
1420
+ const usedTokensAfterUpdate = await safeEval(
1421
+ ctx,
1178
1422
  cachedFixedWindowLimitScript,
1423
+ "limitHash",
1179
1424
  [key],
1180
1425
  [windowDuration, incrementBy]
1181
1426
  );
@@ -1200,8 +1445,10 @@ var RegionRatelimit = class extends Ratelimit {
1200
1445
  const cachedUsedTokens = ctx.cache.get(key) ?? 0;
1201
1446
  return Math.max(0, tokens - cachedUsedTokens);
1202
1447
  }
1203
- const usedTokens = await ctx.redis.eval(
1448
+ const usedTokens = await safeEval(
1449
+ ctx,
1204
1450
  cachedFixedWindowRemainingTokenScript,
1451
+ "getRemainingHash",
1205
1452
  [key],
1206
1453
  [null]
1207
1454
  );
@@ -1215,7 +1462,13 @@ var RegionRatelimit = class extends Ratelimit {
1215
1462
  const key = [identifier, bucket].join(":");
1216
1463
  ctx.cache.pop(key);
1217
1464
  const pattern = [identifier, "*"].join(":");
1218
- await ctx.redis.eval(resetScript, [pattern], [null]);
1465
+ await safeEval(
1466
+ ctx,
1467
+ resetScript,
1468
+ "resetHash",
1469
+ [pattern],
1470
+ [null]
1471
+ );
1219
1472
  }
1220
1473
  });
1221
1474
  }