@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.d.mts +118 -8
- package/dist/index.d.ts +118 -8
- package/dist/index.js +343 -90
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +343 -90
- 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,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
|
|
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
|
-
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
|
-
|
|
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.
|
|
498
|
-
redis,
|
|
499
|
-
request:
|
|
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.
|
|
572
|
-
redis,
|
|
573
|
-
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
|
+
)
|
|
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
|
-
|
|
591
|
-
|
|
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.
|
|
625
|
-
redis,
|
|
626
|
-
request:
|
|
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.
|
|
713
|
-
redis,
|
|
714
|
-
request:
|
|
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
|
-
|
|
730
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 ?
|
|
1167
|
-
ctx
|
|
1168
|
-
|
|
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
|
|
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
|
|
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
|
|
1465
|
+
await safeEval(
|
|
1466
|
+
ctx,
|
|
1467
|
+
resetScript,
|
|
1468
|
+
"resetHash",
|
|
1469
|
+
[pattern],
|
|
1470
|
+
[null]
|
|
1471
|
+
);
|
|
1219
1472
|
}
|
|
1220
1473
|
});
|
|
1221
1474
|
}
|