@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.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:
|
|
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
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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
|
-
|
|
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.
|
|
470
|
-
redis,
|
|
471
|
-
request:
|
|
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.
|
|
544
|
-
redis,
|
|
545
|
-
request:
|
|
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
|
-
|
|
563
|
-
|
|
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.
|
|
597
|
-
redis,
|
|
598
|
-
request:
|
|
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.
|
|
685
|
-
redis,
|
|
686
|
-
request:
|
|
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
|
-
|
|
702
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 ?
|
|
1139
|
-
ctx
|
|
1140
|
-
|
|
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
|
|
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
|
|
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
|
|
1437
|
+
await safeEval(
|
|
1438
|
+
ctx,
|
|
1439
|
+
resetScript,
|
|
1440
|
+
"resetHash",
|
|
1441
|
+
[pattern],
|
|
1442
|
+
[null]
|
|
1443
|
+
);
|
|
1191
1444
|
}
|
|
1192
1445
|
});
|
|
1193
1446
|
}
|