@upstash/ratelimit 1.1.2 → 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/README.md +19 -76
- package/dist/index.d.mts +118 -8
- package/dist/index.d.ts +118 -8
- package/dist/index.js +347 -78
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +347 -78
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
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:
|
|
361
|
+
redis: this.primaryRedis,
|
|
277
362
|
prefix: this.prefix
|
|
278
363
|
}) : void 0;
|
|
279
364
|
if (config.ephemeralCache instanceof Map) {
|
|
@@ -319,43 +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
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
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
|
-
console.warn("Failed to record analytics", err);
|
|
352
|
-
});
|
|
353
|
-
res.pending = Promise.all([res.pending, analyticsP]);
|
|
354
|
-
} catch (err) {
|
|
355
|
-
console.warn("Failed to record analytics", err);
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
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;
|
|
359
415
|
} finally {
|
|
360
416
|
if (timeoutId) {
|
|
361
417
|
clearTimeout(timeoutId);
|
|
@@ -414,6 +470,125 @@ var Ratelimit = class {
|
|
|
414
470
|
const pattern = [this.prefix, identifier].join(":");
|
|
415
471
|
return await this.limiter().getRemaining(this.ctx, pattern);
|
|
416
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
|
+
};
|
|
417
592
|
};
|
|
418
593
|
|
|
419
594
|
// src/multi.ts
|
|
@@ -437,7 +612,11 @@ var MultiRegionRatelimit = class extends Ratelimit {
|
|
|
437
612
|
timeout: config.timeout,
|
|
438
613
|
analytics: config.analytics,
|
|
439
614
|
ctx: {
|
|
440
|
-
|
|
615
|
+
regionContexts: config.redis.map((redis) => ({
|
|
616
|
+
redis,
|
|
617
|
+
scriptHashes: {},
|
|
618
|
+
cacheScripts: config.cacheScripts ?? true
|
|
619
|
+
})),
|
|
441
620
|
cache: config.ephemeralCache ? new Cache(config.ephemeralCache) : void 0
|
|
442
621
|
}
|
|
443
622
|
});
|
|
@@ -472,7 +651,8 @@ var MultiRegionRatelimit = class extends Ratelimit {
|
|
|
472
651
|
limit: tokens,
|
|
473
652
|
remaining: 0,
|
|
474
653
|
reset: reset2,
|
|
475
|
-
pending: Promise.resolve()
|
|
654
|
+
pending: Promise.resolve(),
|
|
655
|
+
reason: "cacheBlock"
|
|
476
656
|
};
|
|
477
657
|
}
|
|
478
658
|
}
|
|
@@ -480,10 +660,12 @@ var MultiRegionRatelimit = class extends Ratelimit {
|
|
|
480
660
|
const bucket = Math.floor(Date.now() / windowDuration);
|
|
481
661
|
const key = [identifier, bucket].join(":");
|
|
482
662
|
const incrementBy = rate ? Math.max(1, rate) : 1;
|
|
483
|
-
const dbs = ctx.
|
|
484
|
-
redis,
|
|
485
|
-
request:
|
|
663
|
+
const dbs = ctx.regionContexts.map((regionContext) => ({
|
|
664
|
+
redis: regionContext.redis,
|
|
665
|
+
request: safeEval(
|
|
666
|
+
regionContext,
|
|
486
667
|
fixedWindowLimitScript,
|
|
668
|
+
"limitHash",
|
|
487
669
|
[key],
|
|
488
670
|
[requestId, windowDuration, incrementBy]
|
|
489
671
|
)
|
|
@@ -554,9 +736,15 @@ var MultiRegionRatelimit = class extends Ratelimit {
|
|
|
554
736
|
async getRemaining(ctx, identifier) {
|
|
555
737
|
const bucket = Math.floor(Date.now() / windowDuration);
|
|
556
738
|
const key = [identifier, bucket].join(":");
|
|
557
|
-
const dbs = ctx.
|
|
558
|
-
redis,
|
|
559
|
-
request:
|
|
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
|
+
)
|
|
560
748
|
}));
|
|
561
749
|
const firstResponse = await Promise.any(dbs.map((s) => s.request));
|
|
562
750
|
const usedTokens = firstResponse.reduce((accTokens, usedToken, index) => {
|
|
@@ -573,9 +761,15 @@ var MultiRegionRatelimit = class extends Ratelimit {
|
|
|
573
761
|
if (ctx.cache) {
|
|
574
762
|
ctx.cache.pop(identifier);
|
|
575
763
|
}
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
764
|
+
await Promise.all(ctx.regionContexts.map((regionContext) => {
|
|
765
|
+
safeEval(
|
|
766
|
+
regionContext,
|
|
767
|
+
resetScript,
|
|
768
|
+
"resetHash",
|
|
769
|
+
[pattern],
|
|
770
|
+
[null]
|
|
771
|
+
);
|
|
772
|
+
}));
|
|
579
773
|
}
|
|
580
774
|
});
|
|
581
775
|
}
|
|
@@ -600,6 +794,19 @@ var MultiRegionRatelimit = class extends Ratelimit {
|
|
|
600
794
|
const windowDuration = ms(window);
|
|
601
795
|
return () => ({
|
|
602
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
|
+
}
|
|
603
810
|
const requestId = randomId();
|
|
604
811
|
const now = Date.now();
|
|
605
812
|
const currentWindow = Math.floor(now / windowSize);
|
|
@@ -607,10 +814,12 @@ var MultiRegionRatelimit = class extends Ratelimit {
|
|
|
607
814
|
const previousWindow = currentWindow - 1;
|
|
608
815
|
const previousKey = [identifier, previousWindow].join(":");
|
|
609
816
|
const incrementBy = rate ? Math.max(1, rate) : 1;
|
|
610
|
-
const dbs = ctx.
|
|
611
|
-
redis,
|
|
612
|
-
request:
|
|
817
|
+
const dbs = ctx.regionContexts.map((regionContext) => ({
|
|
818
|
+
redis: regionContext.redis,
|
|
819
|
+
request: safeEval(
|
|
820
|
+
regionContext,
|
|
613
821
|
slidingWindowLimitScript,
|
|
822
|
+
"limitHash",
|
|
614
823
|
[currentKey, previousKey],
|
|
615
824
|
[tokens, now, windowDuration, requestId, incrementBy]
|
|
616
825
|
// lua seems to return `1` for true and `null` for false
|
|
@@ -695,10 +904,12 @@ var MultiRegionRatelimit = class extends Ratelimit {
|
|
|
695
904
|
const currentKey = [identifier, currentWindow].join(":");
|
|
696
905
|
const previousWindow = currentWindow - 1;
|
|
697
906
|
const previousKey = [identifier, previousWindow].join(":");
|
|
698
|
-
const dbs = ctx.
|
|
699
|
-
redis,
|
|
700
|
-
request:
|
|
907
|
+
const dbs = ctx.regionContexts.map((regionContext) => ({
|
|
908
|
+
redis: regionContext.redis,
|
|
909
|
+
request: safeEval(
|
|
910
|
+
regionContext,
|
|
701
911
|
slidingWindowRemainingTokensScript,
|
|
912
|
+
"getRemainingHash",
|
|
702
913
|
[currentKey, previousKey],
|
|
703
914
|
[now, windowSize]
|
|
704
915
|
// lua seems to return `1` for true and `null` for false
|
|
@@ -712,9 +923,15 @@ var MultiRegionRatelimit = class extends Ratelimit {
|
|
|
712
923
|
if (ctx.cache) {
|
|
713
924
|
ctx.cache.pop(identifier);
|
|
714
925
|
}
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
926
|
+
await Promise.all(ctx.regionContexts.map((regionContext) => {
|
|
927
|
+
safeEval(
|
|
928
|
+
regionContext,
|
|
929
|
+
resetScript,
|
|
930
|
+
"resetHash",
|
|
931
|
+
[pattern],
|
|
932
|
+
[null]
|
|
933
|
+
);
|
|
934
|
+
}));
|
|
718
935
|
}
|
|
719
936
|
});
|
|
720
937
|
}
|
|
@@ -887,9 +1104,12 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
887
1104
|
timeout: config.timeout,
|
|
888
1105
|
analytics: config.analytics,
|
|
889
1106
|
ctx: {
|
|
890
|
-
redis: config.redis
|
|
1107
|
+
redis: config.redis,
|
|
1108
|
+
scriptHashes: {},
|
|
1109
|
+
cacheScripts: config.cacheScripts ?? true
|
|
891
1110
|
},
|
|
892
|
-
ephemeralCache: config.ephemeralCache
|
|
1111
|
+
ephemeralCache: config.ephemeralCache,
|
|
1112
|
+
enableProtection: config.enableProtection
|
|
893
1113
|
});
|
|
894
1114
|
}
|
|
895
1115
|
/**
|
|
@@ -924,13 +1144,16 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
924
1144
|
limit: tokens,
|
|
925
1145
|
remaining: 0,
|
|
926
1146
|
reset: reset2,
|
|
927
|
-
pending: Promise.resolve()
|
|
1147
|
+
pending: Promise.resolve(),
|
|
1148
|
+
reason: "cacheBlock"
|
|
928
1149
|
};
|
|
929
1150
|
}
|
|
930
1151
|
}
|
|
931
1152
|
const incrementBy = rate ? Math.max(1, rate) : 1;
|
|
932
|
-
const usedTokensAfterUpdate = await
|
|
1153
|
+
const usedTokensAfterUpdate = await safeEval(
|
|
1154
|
+
ctx,
|
|
933
1155
|
fixedWindowLimitScript2,
|
|
1156
|
+
"limitHash",
|
|
934
1157
|
[key],
|
|
935
1158
|
[windowDuration, incrementBy]
|
|
936
1159
|
);
|
|
@@ -951,8 +1174,10 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
951
1174
|
async getRemaining(ctx, identifier) {
|
|
952
1175
|
const bucket = Math.floor(Date.now() / windowDuration);
|
|
953
1176
|
const key = [identifier, bucket].join(":");
|
|
954
|
-
const usedTokens = await
|
|
1177
|
+
const usedTokens = await safeEval(
|
|
1178
|
+
ctx,
|
|
955
1179
|
fixedWindowRemainingTokensScript2,
|
|
1180
|
+
"getRemainingHash",
|
|
956
1181
|
[key],
|
|
957
1182
|
[null]
|
|
958
1183
|
);
|
|
@@ -963,7 +1188,13 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
963
1188
|
if (ctx.cache) {
|
|
964
1189
|
ctx.cache.pop(identifier);
|
|
965
1190
|
}
|
|
966
|
-
await
|
|
1191
|
+
await safeEval(
|
|
1192
|
+
ctx,
|
|
1193
|
+
resetScript,
|
|
1194
|
+
"resetHash",
|
|
1195
|
+
[pattern],
|
|
1196
|
+
[null]
|
|
1197
|
+
);
|
|
967
1198
|
}
|
|
968
1199
|
});
|
|
969
1200
|
}
|
|
@@ -1000,13 +1231,16 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1000
1231
|
limit: tokens,
|
|
1001
1232
|
remaining: 0,
|
|
1002
1233
|
reset: reset2,
|
|
1003
|
-
pending: Promise.resolve()
|
|
1234
|
+
pending: Promise.resolve(),
|
|
1235
|
+
reason: "cacheBlock"
|
|
1004
1236
|
};
|
|
1005
1237
|
}
|
|
1006
1238
|
}
|
|
1007
1239
|
const incrementBy = rate ? Math.max(1, rate) : 1;
|
|
1008
|
-
const remainingTokens = await
|
|
1240
|
+
const remainingTokens = await safeEval(
|
|
1241
|
+
ctx,
|
|
1009
1242
|
slidingWindowLimitScript2,
|
|
1243
|
+
"limitHash",
|
|
1010
1244
|
[currentKey, previousKey],
|
|
1011
1245
|
[tokens, now, windowSize, incrementBy]
|
|
1012
1246
|
);
|
|
@@ -1029,8 +1263,10 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1029
1263
|
const currentKey = [identifier, currentWindow].join(":");
|
|
1030
1264
|
const previousWindow = currentWindow - 1;
|
|
1031
1265
|
const previousKey = [identifier, previousWindow].join(":");
|
|
1032
|
-
const usedTokens = await
|
|
1266
|
+
const usedTokens = await safeEval(
|
|
1267
|
+
ctx,
|
|
1033
1268
|
slidingWindowRemainingTokensScript2,
|
|
1269
|
+
"getRemainingHash",
|
|
1034
1270
|
[currentKey, previousKey],
|
|
1035
1271
|
[now, windowSize]
|
|
1036
1272
|
);
|
|
@@ -1041,7 +1277,13 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1041
1277
|
if (ctx.cache) {
|
|
1042
1278
|
ctx.cache.pop(identifier);
|
|
1043
1279
|
}
|
|
1044
|
-
await
|
|
1280
|
+
await safeEval(
|
|
1281
|
+
ctx,
|
|
1282
|
+
resetScript,
|
|
1283
|
+
"resetHash",
|
|
1284
|
+
[pattern],
|
|
1285
|
+
[null]
|
|
1286
|
+
);
|
|
1045
1287
|
}
|
|
1046
1288
|
});
|
|
1047
1289
|
}
|
|
@@ -1070,14 +1312,17 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1070
1312
|
limit: maxTokens,
|
|
1071
1313
|
remaining: 0,
|
|
1072
1314
|
reset: reset2,
|
|
1073
|
-
pending: Promise.resolve()
|
|
1315
|
+
pending: Promise.resolve(),
|
|
1316
|
+
reason: "cacheBlock"
|
|
1074
1317
|
};
|
|
1075
1318
|
}
|
|
1076
1319
|
}
|
|
1077
1320
|
const now = Date.now();
|
|
1078
1321
|
const incrementBy = rate ? Math.max(1, rate) : 1;
|
|
1079
|
-
const [remaining, reset] = await
|
|
1322
|
+
const [remaining, reset] = await safeEval(
|
|
1323
|
+
ctx,
|
|
1080
1324
|
tokenBucketLimitScript,
|
|
1325
|
+
"limitHash",
|
|
1081
1326
|
[identifier],
|
|
1082
1327
|
[maxTokens, intervalDuration, refillRate, now, incrementBy]
|
|
1083
1328
|
);
|
|
@@ -1094,8 +1339,10 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1094
1339
|
};
|
|
1095
1340
|
},
|
|
1096
1341
|
async getRemaining(ctx, identifier) {
|
|
1097
|
-
const remainingTokens = await
|
|
1342
|
+
const remainingTokens = await safeEval(
|
|
1343
|
+
ctx,
|
|
1098
1344
|
tokenBucketRemainingTokensScript,
|
|
1345
|
+
"getRemainingHash",
|
|
1099
1346
|
[identifier],
|
|
1100
1347
|
[maxTokens]
|
|
1101
1348
|
);
|
|
@@ -1106,7 +1353,13 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1106
1353
|
if (ctx.cache) {
|
|
1107
1354
|
ctx.cache.pop(identifier);
|
|
1108
1355
|
}
|
|
1109
|
-
await
|
|
1356
|
+
await safeEval(
|
|
1357
|
+
ctx,
|
|
1358
|
+
resetScript,
|
|
1359
|
+
"resetHash",
|
|
1360
|
+
[pattern],
|
|
1361
|
+
[null]
|
|
1362
|
+
);
|
|
1110
1363
|
}
|
|
1111
1364
|
});
|
|
1112
1365
|
}
|
|
@@ -1149,9 +1402,13 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1149
1402
|
if (hit) {
|
|
1150
1403
|
const cachedTokensAfterUpdate = ctx.cache.incr(key);
|
|
1151
1404
|
const success = cachedTokensAfterUpdate < tokens;
|
|
1152
|
-
const pending = success ?
|
|
1153
|
-
ctx
|
|
1154
|
-
|
|
1405
|
+
const pending = success ? safeEval(
|
|
1406
|
+
ctx,
|
|
1407
|
+
cachedFixedWindowLimitScript,
|
|
1408
|
+
"limitHash",
|
|
1409
|
+
[key],
|
|
1410
|
+
[windowDuration, incrementBy]
|
|
1411
|
+
) : Promise.resolve();
|
|
1155
1412
|
return {
|
|
1156
1413
|
success,
|
|
1157
1414
|
limit: tokens,
|
|
@@ -1160,8 +1417,10 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1160
1417
|
pending
|
|
1161
1418
|
};
|
|
1162
1419
|
}
|
|
1163
|
-
const usedTokensAfterUpdate = await
|
|
1420
|
+
const usedTokensAfterUpdate = await safeEval(
|
|
1421
|
+
ctx,
|
|
1164
1422
|
cachedFixedWindowLimitScript,
|
|
1423
|
+
"limitHash",
|
|
1165
1424
|
[key],
|
|
1166
1425
|
[windowDuration, incrementBy]
|
|
1167
1426
|
);
|
|
@@ -1186,20 +1445,30 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1186
1445
|
const cachedUsedTokens = ctx.cache.get(key) ?? 0;
|
|
1187
1446
|
return Math.max(0, tokens - cachedUsedTokens);
|
|
1188
1447
|
}
|
|
1189
|
-
const usedTokens = await
|
|
1448
|
+
const usedTokens = await safeEval(
|
|
1449
|
+
ctx,
|
|
1190
1450
|
cachedFixedWindowRemainingTokenScript,
|
|
1451
|
+
"getRemainingHash",
|
|
1191
1452
|
[key],
|
|
1192
1453
|
[null]
|
|
1193
1454
|
);
|
|
1194
1455
|
return Math.max(0, tokens - usedTokens);
|
|
1195
1456
|
},
|
|
1196
1457
|
async resetTokens(ctx, identifier) {
|
|
1197
|
-
const pattern = [identifier, "*"].join(":");
|
|
1198
1458
|
if (!ctx.cache) {
|
|
1199
1459
|
throw new Error("This algorithm requires a cache");
|
|
1200
1460
|
}
|
|
1201
|
-
|
|
1202
|
-
|
|
1461
|
+
const bucket = Math.floor(Date.now() / windowDuration);
|
|
1462
|
+
const key = [identifier, bucket].join(":");
|
|
1463
|
+
ctx.cache.pop(key);
|
|
1464
|
+
const pattern = [identifier, "*"].join(":");
|
|
1465
|
+
await safeEval(
|
|
1466
|
+
ctx,
|
|
1467
|
+
resetScript,
|
|
1468
|
+
"resetHash",
|
|
1469
|
+
[pattern],
|
|
1470
|
+
[null]
|
|
1471
|
+
);
|
|
1203
1472
|
}
|
|
1204
1473
|
});
|
|
1205
1474
|
}
|