@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.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,43 +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
|
-
console.warn("Failed to record analytics", err);
|
|
324
|
-
});
|
|
325
|
-
res.pending = Promise.all([res.pending, analyticsP]);
|
|
326
|
-
} catch (err) {
|
|
327
|
-
console.warn("Failed to record analytics", err);
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
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;
|
|
331
387
|
} finally {
|
|
332
388
|
if (timeoutId) {
|
|
333
389
|
clearTimeout(timeoutId);
|
|
@@ -386,6 +442,125 @@ var Ratelimit = class {
|
|
|
386
442
|
const pattern = [this.prefix, identifier].join(":");
|
|
387
443
|
return await this.limiter().getRemaining(this.ctx, pattern);
|
|
388
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
|
+
};
|
|
389
564
|
};
|
|
390
565
|
|
|
391
566
|
// src/multi.ts
|
|
@@ -409,7 +584,11 @@ var MultiRegionRatelimit = class extends Ratelimit {
|
|
|
409
584
|
timeout: config.timeout,
|
|
410
585
|
analytics: config.analytics,
|
|
411
586
|
ctx: {
|
|
412
|
-
|
|
587
|
+
regionContexts: config.redis.map((redis) => ({
|
|
588
|
+
redis,
|
|
589
|
+
scriptHashes: {},
|
|
590
|
+
cacheScripts: config.cacheScripts ?? true
|
|
591
|
+
})),
|
|
413
592
|
cache: config.ephemeralCache ? new Cache(config.ephemeralCache) : void 0
|
|
414
593
|
}
|
|
415
594
|
});
|
|
@@ -444,7 +623,8 @@ var MultiRegionRatelimit = class extends Ratelimit {
|
|
|
444
623
|
limit: tokens,
|
|
445
624
|
remaining: 0,
|
|
446
625
|
reset: reset2,
|
|
447
|
-
pending: Promise.resolve()
|
|
626
|
+
pending: Promise.resolve(),
|
|
627
|
+
reason: "cacheBlock"
|
|
448
628
|
};
|
|
449
629
|
}
|
|
450
630
|
}
|
|
@@ -452,10 +632,12 @@ var MultiRegionRatelimit = class extends Ratelimit {
|
|
|
452
632
|
const bucket = Math.floor(Date.now() / windowDuration);
|
|
453
633
|
const key = [identifier, bucket].join(":");
|
|
454
634
|
const incrementBy = rate ? Math.max(1, rate) : 1;
|
|
455
|
-
const dbs = ctx.
|
|
456
|
-
redis,
|
|
457
|
-
request:
|
|
635
|
+
const dbs = ctx.regionContexts.map((regionContext) => ({
|
|
636
|
+
redis: regionContext.redis,
|
|
637
|
+
request: safeEval(
|
|
638
|
+
regionContext,
|
|
458
639
|
fixedWindowLimitScript,
|
|
640
|
+
"limitHash",
|
|
459
641
|
[key],
|
|
460
642
|
[requestId, windowDuration, incrementBy]
|
|
461
643
|
)
|
|
@@ -526,9 +708,15 @@ var MultiRegionRatelimit = class extends Ratelimit {
|
|
|
526
708
|
async getRemaining(ctx, identifier) {
|
|
527
709
|
const bucket = Math.floor(Date.now() / windowDuration);
|
|
528
710
|
const key = [identifier, bucket].join(":");
|
|
529
|
-
const dbs = ctx.
|
|
530
|
-
redis,
|
|
531
|
-
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
|
+
)
|
|
532
720
|
}));
|
|
533
721
|
const firstResponse = await Promise.any(dbs.map((s) => s.request));
|
|
534
722
|
const usedTokens = firstResponse.reduce((accTokens, usedToken, index) => {
|
|
@@ -545,9 +733,15 @@ var MultiRegionRatelimit = class extends Ratelimit {
|
|
|
545
733
|
if (ctx.cache) {
|
|
546
734
|
ctx.cache.pop(identifier);
|
|
547
735
|
}
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
736
|
+
await Promise.all(ctx.regionContexts.map((regionContext) => {
|
|
737
|
+
safeEval(
|
|
738
|
+
regionContext,
|
|
739
|
+
resetScript,
|
|
740
|
+
"resetHash",
|
|
741
|
+
[pattern],
|
|
742
|
+
[null]
|
|
743
|
+
);
|
|
744
|
+
}));
|
|
551
745
|
}
|
|
552
746
|
});
|
|
553
747
|
}
|
|
@@ -572,6 +766,19 @@ var MultiRegionRatelimit = class extends Ratelimit {
|
|
|
572
766
|
const windowDuration = ms(window);
|
|
573
767
|
return () => ({
|
|
574
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
|
+
}
|
|
575
782
|
const requestId = randomId();
|
|
576
783
|
const now = Date.now();
|
|
577
784
|
const currentWindow = Math.floor(now / windowSize);
|
|
@@ -579,10 +786,12 @@ var MultiRegionRatelimit = class extends Ratelimit {
|
|
|
579
786
|
const previousWindow = currentWindow - 1;
|
|
580
787
|
const previousKey = [identifier, previousWindow].join(":");
|
|
581
788
|
const incrementBy = rate ? Math.max(1, rate) : 1;
|
|
582
|
-
const dbs = ctx.
|
|
583
|
-
redis,
|
|
584
|
-
request:
|
|
789
|
+
const dbs = ctx.regionContexts.map((regionContext) => ({
|
|
790
|
+
redis: regionContext.redis,
|
|
791
|
+
request: safeEval(
|
|
792
|
+
regionContext,
|
|
585
793
|
slidingWindowLimitScript,
|
|
794
|
+
"limitHash",
|
|
586
795
|
[currentKey, previousKey],
|
|
587
796
|
[tokens, now, windowDuration, requestId, incrementBy]
|
|
588
797
|
// lua seems to return `1` for true and `null` for false
|
|
@@ -667,10 +876,12 @@ var MultiRegionRatelimit = class extends Ratelimit {
|
|
|
667
876
|
const currentKey = [identifier, currentWindow].join(":");
|
|
668
877
|
const previousWindow = currentWindow - 1;
|
|
669
878
|
const previousKey = [identifier, previousWindow].join(":");
|
|
670
|
-
const dbs = ctx.
|
|
671
|
-
redis,
|
|
672
|
-
request:
|
|
879
|
+
const dbs = ctx.regionContexts.map((regionContext) => ({
|
|
880
|
+
redis: regionContext.redis,
|
|
881
|
+
request: safeEval(
|
|
882
|
+
regionContext,
|
|
673
883
|
slidingWindowRemainingTokensScript,
|
|
884
|
+
"getRemainingHash",
|
|
674
885
|
[currentKey, previousKey],
|
|
675
886
|
[now, windowSize]
|
|
676
887
|
// lua seems to return `1` for true and `null` for false
|
|
@@ -684,9 +895,15 @@ var MultiRegionRatelimit = class extends Ratelimit {
|
|
|
684
895
|
if (ctx.cache) {
|
|
685
896
|
ctx.cache.pop(identifier);
|
|
686
897
|
}
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
898
|
+
await Promise.all(ctx.regionContexts.map((regionContext) => {
|
|
899
|
+
safeEval(
|
|
900
|
+
regionContext,
|
|
901
|
+
resetScript,
|
|
902
|
+
"resetHash",
|
|
903
|
+
[pattern],
|
|
904
|
+
[null]
|
|
905
|
+
);
|
|
906
|
+
}));
|
|
690
907
|
}
|
|
691
908
|
});
|
|
692
909
|
}
|
|
@@ -859,9 +1076,12 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
859
1076
|
timeout: config.timeout,
|
|
860
1077
|
analytics: config.analytics,
|
|
861
1078
|
ctx: {
|
|
862
|
-
redis: config.redis
|
|
1079
|
+
redis: config.redis,
|
|
1080
|
+
scriptHashes: {},
|
|
1081
|
+
cacheScripts: config.cacheScripts ?? true
|
|
863
1082
|
},
|
|
864
|
-
ephemeralCache: config.ephemeralCache
|
|
1083
|
+
ephemeralCache: config.ephemeralCache,
|
|
1084
|
+
enableProtection: config.enableProtection
|
|
865
1085
|
});
|
|
866
1086
|
}
|
|
867
1087
|
/**
|
|
@@ -896,13 +1116,16 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
896
1116
|
limit: tokens,
|
|
897
1117
|
remaining: 0,
|
|
898
1118
|
reset: reset2,
|
|
899
|
-
pending: Promise.resolve()
|
|
1119
|
+
pending: Promise.resolve(),
|
|
1120
|
+
reason: "cacheBlock"
|
|
900
1121
|
};
|
|
901
1122
|
}
|
|
902
1123
|
}
|
|
903
1124
|
const incrementBy = rate ? Math.max(1, rate) : 1;
|
|
904
|
-
const usedTokensAfterUpdate = await
|
|
1125
|
+
const usedTokensAfterUpdate = await safeEval(
|
|
1126
|
+
ctx,
|
|
905
1127
|
fixedWindowLimitScript2,
|
|
1128
|
+
"limitHash",
|
|
906
1129
|
[key],
|
|
907
1130
|
[windowDuration, incrementBy]
|
|
908
1131
|
);
|
|
@@ -923,8 +1146,10 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
923
1146
|
async getRemaining(ctx, identifier) {
|
|
924
1147
|
const bucket = Math.floor(Date.now() / windowDuration);
|
|
925
1148
|
const key = [identifier, bucket].join(":");
|
|
926
|
-
const usedTokens = await
|
|
1149
|
+
const usedTokens = await safeEval(
|
|
1150
|
+
ctx,
|
|
927
1151
|
fixedWindowRemainingTokensScript2,
|
|
1152
|
+
"getRemainingHash",
|
|
928
1153
|
[key],
|
|
929
1154
|
[null]
|
|
930
1155
|
);
|
|
@@ -935,7 +1160,13 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
935
1160
|
if (ctx.cache) {
|
|
936
1161
|
ctx.cache.pop(identifier);
|
|
937
1162
|
}
|
|
938
|
-
await
|
|
1163
|
+
await safeEval(
|
|
1164
|
+
ctx,
|
|
1165
|
+
resetScript,
|
|
1166
|
+
"resetHash",
|
|
1167
|
+
[pattern],
|
|
1168
|
+
[null]
|
|
1169
|
+
);
|
|
939
1170
|
}
|
|
940
1171
|
});
|
|
941
1172
|
}
|
|
@@ -972,13 +1203,16 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
972
1203
|
limit: tokens,
|
|
973
1204
|
remaining: 0,
|
|
974
1205
|
reset: reset2,
|
|
975
|
-
pending: Promise.resolve()
|
|
1206
|
+
pending: Promise.resolve(),
|
|
1207
|
+
reason: "cacheBlock"
|
|
976
1208
|
};
|
|
977
1209
|
}
|
|
978
1210
|
}
|
|
979
1211
|
const incrementBy = rate ? Math.max(1, rate) : 1;
|
|
980
|
-
const remainingTokens = await
|
|
1212
|
+
const remainingTokens = await safeEval(
|
|
1213
|
+
ctx,
|
|
981
1214
|
slidingWindowLimitScript2,
|
|
1215
|
+
"limitHash",
|
|
982
1216
|
[currentKey, previousKey],
|
|
983
1217
|
[tokens, now, windowSize, incrementBy]
|
|
984
1218
|
);
|
|
@@ -1001,8 +1235,10 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1001
1235
|
const currentKey = [identifier, currentWindow].join(":");
|
|
1002
1236
|
const previousWindow = currentWindow - 1;
|
|
1003
1237
|
const previousKey = [identifier, previousWindow].join(":");
|
|
1004
|
-
const usedTokens = await
|
|
1238
|
+
const usedTokens = await safeEval(
|
|
1239
|
+
ctx,
|
|
1005
1240
|
slidingWindowRemainingTokensScript2,
|
|
1241
|
+
"getRemainingHash",
|
|
1006
1242
|
[currentKey, previousKey],
|
|
1007
1243
|
[now, windowSize]
|
|
1008
1244
|
);
|
|
@@ -1013,7 +1249,13 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1013
1249
|
if (ctx.cache) {
|
|
1014
1250
|
ctx.cache.pop(identifier);
|
|
1015
1251
|
}
|
|
1016
|
-
await
|
|
1252
|
+
await safeEval(
|
|
1253
|
+
ctx,
|
|
1254
|
+
resetScript,
|
|
1255
|
+
"resetHash",
|
|
1256
|
+
[pattern],
|
|
1257
|
+
[null]
|
|
1258
|
+
);
|
|
1017
1259
|
}
|
|
1018
1260
|
});
|
|
1019
1261
|
}
|
|
@@ -1042,14 +1284,17 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1042
1284
|
limit: maxTokens,
|
|
1043
1285
|
remaining: 0,
|
|
1044
1286
|
reset: reset2,
|
|
1045
|
-
pending: Promise.resolve()
|
|
1287
|
+
pending: Promise.resolve(),
|
|
1288
|
+
reason: "cacheBlock"
|
|
1046
1289
|
};
|
|
1047
1290
|
}
|
|
1048
1291
|
}
|
|
1049
1292
|
const now = Date.now();
|
|
1050
1293
|
const incrementBy = rate ? Math.max(1, rate) : 1;
|
|
1051
|
-
const [remaining, reset] = await
|
|
1294
|
+
const [remaining, reset] = await safeEval(
|
|
1295
|
+
ctx,
|
|
1052
1296
|
tokenBucketLimitScript,
|
|
1297
|
+
"limitHash",
|
|
1053
1298
|
[identifier],
|
|
1054
1299
|
[maxTokens, intervalDuration, refillRate, now, incrementBy]
|
|
1055
1300
|
);
|
|
@@ -1066,8 +1311,10 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1066
1311
|
};
|
|
1067
1312
|
},
|
|
1068
1313
|
async getRemaining(ctx, identifier) {
|
|
1069
|
-
const remainingTokens = await
|
|
1314
|
+
const remainingTokens = await safeEval(
|
|
1315
|
+
ctx,
|
|
1070
1316
|
tokenBucketRemainingTokensScript,
|
|
1317
|
+
"getRemainingHash",
|
|
1071
1318
|
[identifier],
|
|
1072
1319
|
[maxTokens]
|
|
1073
1320
|
);
|
|
@@ -1078,7 +1325,13 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1078
1325
|
if (ctx.cache) {
|
|
1079
1326
|
ctx.cache.pop(identifier);
|
|
1080
1327
|
}
|
|
1081
|
-
await
|
|
1328
|
+
await safeEval(
|
|
1329
|
+
ctx,
|
|
1330
|
+
resetScript,
|
|
1331
|
+
"resetHash",
|
|
1332
|
+
[pattern],
|
|
1333
|
+
[null]
|
|
1334
|
+
);
|
|
1082
1335
|
}
|
|
1083
1336
|
});
|
|
1084
1337
|
}
|
|
@@ -1121,9 +1374,13 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1121
1374
|
if (hit) {
|
|
1122
1375
|
const cachedTokensAfterUpdate = ctx.cache.incr(key);
|
|
1123
1376
|
const success = cachedTokensAfterUpdate < tokens;
|
|
1124
|
-
const pending = success ?
|
|
1125
|
-
ctx
|
|
1126
|
-
|
|
1377
|
+
const pending = success ? safeEval(
|
|
1378
|
+
ctx,
|
|
1379
|
+
cachedFixedWindowLimitScript,
|
|
1380
|
+
"limitHash",
|
|
1381
|
+
[key],
|
|
1382
|
+
[windowDuration, incrementBy]
|
|
1383
|
+
) : Promise.resolve();
|
|
1127
1384
|
return {
|
|
1128
1385
|
success,
|
|
1129
1386
|
limit: tokens,
|
|
@@ -1132,8 +1389,10 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1132
1389
|
pending
|
|
1133
1390
|
};
|
|
1134
1391
|
}
|
|
1135
|
-
const usedTokensAfterUpdate = await
|
|
1392
|
+
const usedTokensAfterUpdate = await safeEval(
|
|
1393
|
+
ctx,
|
|
1136
1394
|
cachedFixedWindowLimitScript,
|
|
1395
|
+
"limitHash",
|
|
1137
1396
|
[key],
|
|
1138
1397
|
[windowDuration, incrementBy]
|
|
1139
1398
|
);
|
|
@@ -1158,20 +1417,30 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1158
1417
|
const cachedUsedTokens = ctx.cache.get(key) ?? 0;
|
|
1159
1418
|
return Math.max(0, tokens - cachedUsedTokens);
|
|
1160
1419
|
}
|
|
1161
|
-
const usedTokens = await
|
|
1420
|
+
const usedTokens = await safeEval(
|
|
1421
|
+
ctx,
|
|
1162
1422
|
cachedFixedWindowRemainingTokenScript,
|
|
1423
|
+
"getRemainingHash",
|
|
1163
1424
|
[key],
|
|
1164
1425
|
[null]
|
|
1165
1426
|
);
|
|
1166
1427
|
return Math.max(0, tokens - usedTokens);
|
|
1167
1428
|
},
|
|
1168
1429
|
async resetTokens(ctx, identifier) {
|
|
1169
|
-
const pattern = [identifier, "*"].join(":");
|
|
1170
1430
|
if (!ctx.cache) {
|
|
1171
1431
|
throw new Error("This algorithm requires a cache");
|
|
1172
1432
|
}
|
|
1173
|
-
|
|
1174
|
-
|
|
1433
|
+
const bucket = Math.floor(Date.now() / windowDuration);
|
|
1434
|
+
const key = [identifier, bucket].join(":");
|
|
1435
|
+
ctx.cache.pop(key);
|
|
1436
|
+
const pattern = [identifier, "*"].join(":");
|
|
1437
|
+
await safeEval(
|
|
1438
|
+
ctx,
|
|
1439
|
+
resetScript,
|
|
1440
|
+
"resetHash",
|
|
1441
|
+
[pattern],
|
|
1442
|
+
[null]
|
|
1443
|
+
);
|
|
1175
1444
|
}
|
|
1176
1445
|
});
|
|
1177
1446
|
}
|