@upstash/ratelimit 2.0.5 → 2.0.7
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 +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +168 -126
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +168 -126
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -12,7 +12,7 @@ type EphemeralCache = {
|
|
|
12
12
|
blockUntil: (identifier: string, reset: number) => void;
|
|
13
13
|
set: (key: string, value: number) => void;
|
|
14
14
|
get: (key: string) => number | null;
|
|
15
|
-
incr: (key: string) => number;
|
|
15
|
+
incr: (key: string, incrementAmount?: number) => number;
|
|
16
16
|
pop: (key: string) => void;
|
|
17
17
|
empty: () => void;
|
|
18
18
|
size: () => number;
|
|
@@ -497,7 +497,7 @@ declare class MultiRegionRatelimit extends Ratelimit<MultiRegionContext> {
|
|
|
497
497
|
window: Duration): Algorithm<MultiRegionContext>;
|
|
498
498
|
}
|
|
499
499
|
|
|
500
|
-
type Redis = Pick<Redis$1, "get" | "set">;
|
|
500
|
+
type Redis = Pick<Redis$1, "evalsha" | "get" | "set">;
|
|
501
501
|
type RegionRatelimitConfig = {
|
|
502
502
|
/**
|
|
503
503
|
* Instance of `@upstash/redis`
|
package/dist/index.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ type EphemeralCache = {
|
|
|
12
12
|
blockUntil: (identifier: string, reset: number) => void;
|
|
13
13
|
set: (key: string, value: number) => void;
|
|
14
14
|
get: (key: string) => number | null;
|
|
15
|
-
incr: (key: string) => number;
|
|
15
|
+
incr: (key: string, incrementAmount?: number) => number;
|
|
16
16
|
pop: (key: string) => void;
|
|
17
17
|
empty: () => void;
|
|
18
18
|
size: () => number;
|
|
@@ -497,7 +497,7 @@ declare class MultiRegionRatelimit extends Ratelimit<MultiRegionContext> {
|
|
|
497
497
|
window: Duration): Algorithm<MultiRegionContext>;
|
|
498
498
|
}
|
|
499
499
|
|
|
500
|
-
type Redis = Pick<Redis$1, "get" | "set">;
|
|
500
|
+
type Redis = Pick<Redis$1, "evalsha" | "get" | "set">;
|
|
501
501
|
type RegionRatelimitConfig = {
|
|
502
502
|
/**
|
|
503
503
|
* Instance of `@upstash/redis`
|
package/dist/index.js
CHANGED
|
@@ -115,9 +115,9 @@ var Cache = class {
|
|
|
115
115
|
get(key) {
|
|
116
116
|
return this.cache.get(key) || null;
|
|
117
117
|
}
|
|
118
|
-
incr(key) {
|
|
118
|
+
incr(key, incrementAmount = 1) {
|
|
119
119
|
let value = this.cache.get(key) ?? 0;
|
|
120
|
-
value +=
|
|
120
|
+
value += incrementAmount;
|
|
121
121
|
this.cache.set(key, value);
|
|
122
122
|
return value;
|
|
123
123
|
}
|
|
@@ -168,13 +168,7 @@ var safeEval = async (ctx, script, keys, args) => {
|
|
|
168
168
|
return await ctx.redis.evalsha(script.hash, keys, args);
|
|
169
169
|
} catch (error) {
|
|
170
170
|
if (`${error}`.includes("NOSCRIPT")) {
|
|
171
|
-
|
|
172
|
-
if (hash !== script.hash) {
|
|
173
|
-
console.warn(
|
|
174
|
-
"Upstash Ratelimit: Expected hash and the hash received from Redis are different. Ratelimit will work as usual but performance will be reduced."
|
|
175
|
-
);
|
|
176
|
-
}
|
|
177
|
-
return await ctx.redis.evalsha(hash, keys, args);
|
|
171
|
+
return await ctx.redis.eval(script.script, keys, args);
|
|
178
172
|
}
|
|
179
173
|
throw error;
|
|
180
174
|
}
|
|
@@ -211,7 +205,7 @@ var slidingWindowLimitScript = `
|
|
|
211
205
|
local tokens = tonumber(ARGV[1]) -- tokens per window
|
|
212
206
|
local now = ARGV[2] -- current timestamp in milliseconds
|
|
213
207
|
local window = ARGV[3] -- interval in milliseconds
|
|
214
|
-
local incrementBy = ARGV[4]
|
|
208
|
+
local incrementBy = tonumber(ARGV[4]) -- increment rate per request at a given value, default is 1
|
|
215
209
|
|
|
216
210
|
local requestsInCurrentWindow = redis.call("GET", currentKey)
|
|
217
211
|
if requestsInCurrentWindow == false then
|
|
@@ -225,12 +219,14 @@ var slidingWindowLimitScript = `
|
|
|
225
219
|
local percentageInCurrent = ( now % window ) / window
|
|
226
220
|
-- weighted requests to consider from the previous window
|
|
227
221
|
requestsInPreviousWindow = math.floor(( 1 - percentageInCurrent ) * requestsInPreviousWindow)
|
|
228
|
-
|
|
222
|
+
|
|
223
|
+
-- Only check limit if not refunding (negative rate)
|
|
224
|
+
if incrementBy > 0 and requestsInPreviousWindow + requestsInCurrentWindow >= tokens then
|
|
229
225
|
return -1
|
|
230
226
|
end
|
|
231
227
|
|
|
232
228
|
local newValue = redis.call("INCRBY", currentKey, incrementBy)
|
|
233
|
-
if newValue ==
|
|
229
|
+
if newValue == incrementBy then
|
|
234
230
|
-- The first time this key is set, the value will be equal to incrementBy.
|
|
235
231
|
-- So we only need the expire command once
|
|
236
232
|
redis.call("PEXPIRE", currentKey, window * 2 + 1000) -- Enough time to overlap with a new window + 1 second
|
|
@@ -287,7 +283,8 @@ var tokenBucketLimitScript = `
|
|
|
287
283
|
refilledAt = refilledAt + numRefills * interval
|
|
288
284
|
end
|
|
289
285
|
|
|
290
|
-
if tokens
|
|
286
|
+
-- Only reject if tokens are 0 and we're consuming (not refunding)
|
|
287
|
+
if tokens == 0 and incrementBy > 0 then
|
|
291
288
|
return {-1, refilledAt + interval}
|
|
292
289
|
end
|
|
293
290
|
|
|
@@ -295,7 +292,10 @@ var tokenBucketLimitScript = `
|
|
|
295
292
|
local expireAt = math.ceil(((maxTokens - remaining) / refillRate)) * interval
|
|
296
293
|
|
|
297
294
|
redis.call("HSET", key, "refilledAt", refilledAt, "tokens", remaining)
|
|
298
|
-
|
|
295
|
+
|
|
296
|
+
if (expireAt > 0) then
|
|
297
|
+
redis.call("PEXPIRE", key, expireAt)
|
|
298
|
+
end
|
|
299
299
|
return {remaining, refilledAt + interval}
|
|
300
300
|
`;
|
|
301
301
|
var tokenBucketIdentifierNotFound = -1;
|
|
@@ -383,7 +383,9 @@ var slidingWindowLimitScript2 = `
|
|
|
383
383
|
end
|
|
384
384
|
|
|
385
385
|
local percentageInCurrent = ( now % window) / window
|
|
386
|
-
|
|
386
|
+
|
|
387
|
+
-- Only check limit if not refunding (negative rate)
|
|
388
|
+
if incrementBy > 0 and requestsInPreviousWindow * (1 - percentageInCurrent ) + requestsInCurrentWindow + incrementBy > tokens then
|
|
387
389
|
return {currentFields, previousFields, false}
|
|
388
390
|
end
|
|
389
391
|
|
|
@@ -461,7 +463,7 @@ var SCRIPTS = {
|
|
|
461
463
|
slidingWindow: {
|
|
462
464
|
limit: {
|
|
463
465
|
script: slidingWindowLimitScript,
|
|
464
|
-
hash: "
|
|
466
|
+
hash: "9b7842963bd73721f1a3011650c23c0010848ee3"
|
|
465
467
|
},
|
|
466
468
|
getRemaining: {
|
|
467
469
|
script: slidingWindowRemainingTokensScript,
|
|
@@ -471,7 +473,7 @@ var SCRIPTS = {
|
|
|
471
473
|
tokenBucket: {
|
|
472
474
|
limit: {
|
|
473
475
|
script: tokenBucketLimitScript,
|
|
474
|
-
hash: "
|
|
476
|
+
hash: "d1f857ebbdaeca90ccd2cd4eada61d7c8e5db1ca"
|
|
475
477
|
},
|
|
476
478
|
getRemaining: {
|
|
477
479
|
script: tokenBucketRemainingTokensScript,
|
|
@@ -503,7 +505,7 @@ var SCRIPTS = {
|
|
|
503
505
|
slidingWindow: {
|
|
504
506
|
limit: {
|
|
505
507
|
script: slidingWindowLimitScript2,
|
|
506
|
-
hash: "
|
|
508
|
+
hash: "1e7ca8dcd2d600a6d0124a67a57ea225ed62921b"
|
|
507
509
|
},
|
|
508
510
|
getRemaining: {
|
|
509
511
|
script: slidingWindowRemainingTokensScript2,
|
|
@@ -979,7 +981,11 @@ var MultiRegionRatelimit = class extends Ratelimit {
|
|
|
979
981
|
const windowDuration = ms(window);
|
|
980
982
|
return () => ({
|
|
981
983
|
async limit(ctx, identifier, rate) {
|
|
982
|
-
|
|
984
|
+
const requestId = randomId();
|
|
985
|
+
const bucket = Math.floor(Date.now() / windowDuration);
|
|
986
|
+
const key = [identifier, bucket].join(":");
|
|
987
|
+
const incrementBy = rate ?? 1;
|
|
988
|
+
if (ctx.cache && incrementBy > 0) {
|
|
983
989
|
const { blocked, reset: reset2 } = ctx.cache.isBlocked(identifier);
|
|
984
990
|
if (blocked) {
|
|
985
991
|
return {
|
|
@@ -992,10 +998,6 @@ var MultiRegionRatelimit = class extends Ratelimit {
|
|
|
992
998
|
};
|
|
993
999
|
}
|
|
994
1000
|
}
|
|
995
|
-
const requestId = randomId();
|
|
996
|
-
const bucket = Math.floor(Date.now() / windowDuration);
|
|
997
|
-
const key = [identifier, bucket].join(":");
|
|
998
|
-
const incrementBy = rate ? Math.max(1, rate) : 1;
|
|
999
1001
|
const dbs = ctx.regionContexts.map((regionContext) => ({
|
|
1000
1002
|
redis: regionContext.redis,
|
|
1001
1003
|
request: safeEval(
|
|
@@ -1006,24 +1008,29 @@ var MultiRegionRatelimit = class extends Ratelimit {
|
|
|
1006
1008
|
)
|
|
1007
1009
|
}));
|
|
1008
1010
|
const firstResponse = await Promise.any(dbs.map((s) => s.request));
|
|
1009
|
-
const usedTokens = firstResponse.reduce(
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1011
|
+
const usedTokens = firstResponse.reduce(
|
|
1012
|
+
(accTokens, usedToken, index) => {
|
|
1013
|
+
let parsedToken = 0;
|
|
1014
|
+
if (index % 2) {
|
|
1015
|
+
parsedToken = Number.parseInt(usedToken);
|
|
1016
|
+
}
|
|
1017
|
+
return accTokens + parsedToken;
|
|
1018
|
+
},
|
|
1019
|
+
0
|
|
1020
|
+
);
|
|
1016
1021
|
const remaining = tokens - usedTokens;
|
|
1017
1022
|
async function sync() {
|
|
1018
1023
|
const individualIDs = await Promise.all(dbs.map((s) => s.request));
|
|
1019
|
-
const allIDs = [
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1024
|
+
const allIDs = [
|
|
1025
|
+
...new Set(
|
|
1026
|
+
individualIDs.flat().reduce((acc, curr, index) => {
|
|
1027
|
+
if (index % 2 === 0) {
|
|
1028
|
+
acc.push(curr);
|
|
1029
|
+
}
|
|
1030
|
+
return acc;
|
|
1031
|
+
}, [])
|
|
1032
|
+
).values()
|
|
1033
|
+
];
|
|
1027
1034
|
for (const db of dbs) {
|
|
1028
1035
|
const usedDbTokensRequest = await db.request;
|
|
1029
1036
|
const usedDbTokens = usedDbTokensRequest.reduce(
|
|
@@ -1037,12 +1044,15 @@ var MultiRegionRatelimit = class extends Ratelimit {
|
|
|
1037
1044
|
0
|
|
1038
1045
|
);
|
|
1039
1046
|
const dbIdsRequest = await db.request;
|
|
1040
|
-
const dbIds = dbIdsRequest.reduce(
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1047
|
+
const dbIds = dbIdsRequest.reduce(
|
|
1048
|
+
(ids, currentId, index) => {
|
|
1049
|
+
if (index % 2 === 0) {
|
|
1050
|
+
ids.push(currentId);
|
|
1051
|
+
}
|
|
1052
|
+
return ids;
|
|
1053
|
+
},
|
|
1054
|
+
[]
|
|
1055
|
+
);
|
|
1046
1056
|
if (usedDbTokens >= tokens) {
|
|
1047
1057
|
continue;
|
|
1048
1058
|
}
|
|
@@ -1055,10 +1065,14 @@ var MultiRegionRatelimit = class extends Ratelimit {
|
|
|
1055
1065
|
}
|
|
1056
1066
|
}
|
|
1057
1067
|
}
|
|
1058
|
-
const success = remaining
|
|
1068
|
+
const success = remaining >= 0;
|
|
1059
1069
|
const reset = (bucket + 1) * windowDuration;
|
|
1060
|
-
if (ctx.cache
|
|
1061
|
-
|
|
1070
|
+
if (ctx.cache) {
|
|
1071
|
+
if (!success) {
|
|
1072
|
+
ctx.cache.blockUntil(identifier, reset);
|
|
1073
|
+
} else if (incrementBy < 0) {
|
|
1074
|
+
ctx.cache.pop(identifier);
|
|
1075
|
+
}
|
|
1062
1076
|
}
|
|
1063
1077
|
return {
|
|
1064
1078
|
success,
|
|
@@ -1081,13 +1095,16 @@ var MultiRegionRatelimit = class extends Ratelimit {
|
|
|
1081
1095
|
)
|
|
1082
1096
|
}));
|
|
1083
1097
|
const firstResponse = await Promise.any(dbs.map((s) => s.request));
|
|
1084
|
-
const usedTokens = firstResponse.reduce(
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1098
|
+
const usedTokens = firstResponse.reduce(
|
|
1099
|
+
(accTokens, usedToken, index) => {
|
|
1100
|
+
let parsedToken = 0;
|
|
1101
|
+
if (index % 2) {
|
|
1102
|
+
parsedToken = Number.parseInt(usedToken);
|
|
1103
|
+
}
|
|
1104
|
+
return accTokens + parsedToken;
|
|
1105
|
+
},
|
|
1106
|
+
0
|
|
1107
|
+
);
|
|
1091
1108
|
return {
|
|
1092
1109
|
remaining: Math.max(0, tokens - usedTokens),
|
|
1093
1110
|
reset: (bucket + 1) * windowDuration
|
|
@@ -1098,14 +1115,11 @@ var MultiRegionRatelimit = class extends Ratelimit {
|
|
|
1098
1115
|
if (ctx.cache) {
|
|
1099
1116
|
ctx.cache.pop(identifier);
|
|
1100
1117
|
}
|
|
1101
|
-
await Promise.all(
|
|
1102
|
-
|
|
1103
|
-
regionContext,
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
[null]
|
|
1107
|
-
);
|
|
1108
|
-
}));
|
|
1118
|
+
await Promise.all(
|
|
1119
|
+
ctx.regionContexts.map((regionContext) => {
|
|
1120
|
+
safeEval(regionContext, RESET_SCRIPT, [pattern], [null]);
|
|
1121
|
+
})
|
|
1122
|
+
);
|
|
1109
1123
|
}
|
|
1110
1124
|
});
|
|
1111
1125
|
}
|
|
@@ -1130,7 +1144,14 @@ var MultiRegionRatelimit = class extends Ratelimit {
|
|
|
1130
1144
|
const windowDuration = ms(window);
|
|
1131
1145
|
return () => ({
|
|
1132
1146
|
async limit(ctx, identifier, rate) {
|
|
1133
|
-
|
|
1147
|
+
const requestId = randomId();
|
|
1148
|
+
const now = Date.now();
|
|
1149
|
+
const currentWindow = Math.floor(now / windowSize);
|
|
1150
|
+
const currentKey = [identifier, currentWindow].join(":");
|
|
1151
|
+
const previousWindow = currentWindow - 1;
|
|
1152
|
+
const previousKey = [identifier, previousWindow].join(":");
|
|
1153
|
+
const incrementBy = rate ?? 1;
|
|
1154
|
+
if (ctx.cache && incrementBy > 0) {
|
|
1134
1155
|
const { blocked, reset: reset2 } = ctx.cache.isBlocked(identifier);
|
|
1135
1156
|
if (blocked) {
|
|
1136
1157
|
return {
|
|
@@ -1143,13 +1164,6 @@ var MultiRegionRatelimit = class extends Ratelimit {
|
|
|
1143
1164
|
};
|
|
1144
1165
|
}
|
|
1145
1166
|
}
|
|
1146
|
-
const requestId = randomId();
|
|
1147
|
-
const now = Date.now();
|
|
1148
|
-
const currentWindow = Math.floor(now / windowSize);
|
|
1149
|
-
const currentKey = [identifier, currentWindow].join(":");
|
|
1150
|
-
const previousWindow = currentWindow - 1;
|
|
1151
|
-
const previousKey = [identifier, previousWindow].join(":");
|
|
1152
|
-
const incrementBy = rate ? Math.max(1, rate) : 1;
|
|
1153
1167
|
const dbs = ctx.regionContexts.map((regionContext) => ({
|
|
1154
1168
|
redis: regionContext.redis,
|
|
1155
1169
|
request: safeEval(
|
|
@@ -1161,37 +1175,49 @@ var MultiRegionRatelimit = class extends Ratelimit {
|
|
|
1161
1175
|
)
|
|
1162
1176
|
}));
|
|
1163
1177
|
const percentageInCurrent = now % windowDuration / windowDuration;
|
|
1164
|
-
const [current, previous, success] = await Promise.any(
|
|
1178
|
+
const [current, previous, success] = await Promise.any(
|
|
1179
|
+
dbs.map((s) => s.request)
|
|
1180
|
+
);
|
|
1165
1181
|
if (success) {
|
|
1166
1182
|
current.push(requestId, incrementBy.toString());
|
|
1167
1183
|
}
|
|
1168
|
-
const previousUsedTokens = previous.reduce(
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1184
|
+
const previousUsedTokens = previous.reduce(
|
|
1185
|
+
(accTokens, usedToken, index) => {
|
|
1186
|
+
let parsedToken = 0;
|
|
1187
|
+
if (index % 2) {
|
|
1188
|
+
parsedToken = Number.parseInt(usedToken);
|
|
1189
|
+
}
|
|
1190
|
+
return accTokens + parsedToken;
|
|
1191
|
+
},
|
|
1192
|
+
0
|
|
1193
|
+
);
|
|
1194
|
+
const currentUsedTokens = current.reduce(
|
|
1195
|
+
(accTokens, usedToken, index) => {
|
|
1196
|
+
let parsedToken = 0;
|
|
1197
|
+
if (index % 2) {
|
|
1198
|
+
parsedToken = Number.parseInt(usedToken);
|
|
1199
|
+
}
|
|
1200
|
+
return accTokens + parsedToken;
|
|
1201
|
+
},
|
|
1202
|
+
0
|
|
1203
|
+
);
|
|
1204
|
+
const previousPartialUsed = Math.ceil(
|
|
1205
|
+
previousUsedTokens * (1 - percentageInCurrent)
|
|
1206
|
+
);
|
|
1183
1207
|
const usedTokens = previousPartialUsed + currentUsedTokens;
|
|
1184
1208
|
const remaining = tokens - usedTokens;
|
|
1185
1209
|
async function sync() {
|
|
1186
1210
|
const res = await Promise.all(dbs.map((s) => s.request));
|
|
1187
|
-
const allCurrentIds = [
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1211
|
+
const allCurrentIds = [
|
|
1212
|
+
...new Set(
|
|
1213
|
+
res.flatMap(([current2]) => current2).reduce((acc, curr, index) => {
|
|
1214
|
+
if (index % 2 === 0) {
|
|
1215
|
+
acc.push(curr);
|
|
1216
|
+
}
|
|
1217
|
+
return acc;
|
|
1218
|
+
}, [])
|
|
1219
|
+
).values()
|
|
1220
|
+
];
|
|
1195
1221
|
for (const db of dbs) {
|
|
1196
1222
|
const [current2, _previous, _success] = await db.request;
|
|
1197
1223
|
const dbIds = current2.reduce((ids, currentId, index) => {
|
|
@@ -1200,13 +1226,16 @@ var MultiRegionRatelimit = class extends Ratelimit {
|
|
|
1200
1226
|
}
|
|
1201
1227
|
return ids;
|
|
1202
1228
|
}, []);
|
|
1203
|
-
const usedDbTokens = current2.reduce(
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1229
|
+
const usedDbTokens = current2.reduce(
|
|
1230
|
+
(accTokens, usedToken, index) => {
|
|
1231
|
+
let parsedToken = 0;
|
|
1232
|
+
if (index % 2) {
|
|
1233
|
+
parsedToken = Number.parseInt(usedToken);
|
|
1234
|
+
}
|
|
1235
|
+
return accTokens + parsedToken;
|
|
1236
|
+
},
|
|
1237
|
+
0
|
|
1238
|
+
);
|
|
1210
1239
|
if (usedDbTokens >= tokens) {
|
|
1211
1240
|
continue;
|
|
1212
1241
|
}
|
|
@@ -1220,8 +1249,12 @@ var MultiRegionRatelimit = class extends Ratelimit {
|
|
|
1220
1249
|
}
|
|
1221
1250
|
}
|
|
1222
1251
|
const reset = (currentWindow + 1) * windowDuration;
|
|
1223
|
-
if (ctx.cache
|
|
1224
|
-
|
|
1252
|
+
if (ctx.cache) {
|
|
1253
|
+
if (!success) {
|
|
1254
|
+
ctx.cache.blockUntil(identifier, reset);
|
|
1255
|
+
} else if (incrementBy < 0) {
|
|
1256
|
+
ctx.cache.pop(identifier);
|
|
1257
|
+
}
|
|
1225
1258
|
}
|
|
1226
1259
|
return {
|
|
1227
1260
|
success: Boolean(success),
|
|
@@ -1258,14 +1291,11 @@ var MultiRegionRatelimit = class extends Ratelimit {
|
|
|
1258
1291
|
if (ctx.cache) {
|
|
1259
1292
|
ctx.cache.pop(identifier);
|
|
1260
1293
|
}
|
|
1261
|
-
await Promise.all(
|
|
1262
|
-
|
|
1263
|
-
regionContext,
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
[null]
|
|
1267
|
-
);
|
|
1268
|
-
}));
|
|
1294
|
+
await Promise.all(
|
|
1295
|
+
ctx.regionContexts.map((regionContext) => {
|
|
1296
|
+
safeEval(regionContext, RESET_SCRIPT, [pattern], [null]);
|
|
1297
|
+
})
|
|
1298
|
+
);
|
|
1269
1299
|
}
|
|
1270
1300
|
});
|
|
1271
1301
|
}
|
|
@@ -1314,7 +1344,8 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1314
1344
|
async limit(ctx, identifier, rate) {
|
|
1315
1345
|
const bucket = Math.floor(Date.now() / windowDuration);
|
|
1316
1346
|
const key = [identifier, bucket].join(":");
|
|
1317
|
-
|
|
1347
|
+
const incrementBy = rate ?? 1;
|
|
1348
|
+
if (ctx.cache && incrementBy > 0) {
|
|
1318
1349
|
const { blocked, reset: reset2 } = ctx.cache.isBlocked(identifier);
|
|
1319
1350
|
if (blocked) {
|
|
1320
1351
|
return {
|
|
@@ -1327,7 +1358,6 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1327
1358
|
};
|
|
1328
1359
|
}
|
|
1329
1360
|
}
|
|
1330
|
-
const incrementBy = rate ? Math.max(1, rate) : 1;
|
|
1331
1361
|
const usedTokensAfterUpdate = await safeEval(
|
|
1332
1362
|
ctx,
|
|
1333
1363
|
SCRIPTS.singleRegion.fixedWindow.limit,
|
|
@@ -1337,8 +1367,12 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1337
1367
|
const success = usedTokensAfterUpdate <= tokens;
|
|
1338
1368
|
const remainingTokens = Math.max(0, tokens - usedTokensAfterUpdate);
|
|
1339
1369
|
const reset = (bucket + 1) * windowDuration;
|
|
1340
|
-
if (ctx.cache
|
|
1341
|
-
|
|
1370
|
+
if (ctx.cache) {
|
|
1371
|
+
if (!success) {
|
|
1372
|
+
ctx.cache.blockUntil(identifier, reset);
|
|
1373
|
+
} else if (incrementBy < 0) {
|
|
1374
|
+
ctx.cache.pop(identifier);
|
|
1375
|
+
}
|
|
1342
1376
|
}
|
|
1343
1377
|
return {
|
|
1344
1378
|
success,
|
|
@@ -1401,7 +1435,8 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1401
1435
|
const currentKey = [identifier, currentWindow].join(":");
|
|
1402
1436
|
const previousWindow = currentWindow - 1;
|
|
1403
1437
|
const previousKey = [identifier, previousWindow].join(":");
|
|
1404
|
-
|
|
1438
|
+
const incrementBy = rate ?? 1;
|
|
1439
|
+
if (ctx.cache && incrementBy > 0) {
|
|
1405
1440
|
const { blocked, reset: reset2 } = ctx.cache.isBlocked(identifier);
|
|
1406
1441
|
if (blocked) {
|
|
1407
1442
|
return {
|
|
@@ -1414,7 +1449,6 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1414
1449
|
};
|
|
1415
1450
|
}
|
|
1416
1451
|
}
|
|
1417
|
-
const incrementBy = rate ? Math.max(1, rate) : 1;
|
|
1418
1452
|
const remainingTokens = await safeEval(
|
|
1419
1453
|
ctx,
|
|
1420
1454
|
SCRIPTS.singleRegion.slidingWindow.limit,
|
|
@@ -1423,8 +1457,12 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1423
1457
|
);
|
|
1424
1458
|
const success = remainingTokens >= 0;
|
|
1425
1459
|
const reset = (currentWindow + 1) * windowSize;
|
|
1426
|
-
if (ctx.cache
|
|
1427
|
-
|
|
1460
|
+
if (ctx.cache) {
|
|
1461
|
+
if (!success) {
|
|
1462
|
+
ctx.cache.blockUntil(identifier, reset);
|
|
1463
|
+
} else if (incrementBy < 0) {
|
|
1464
|
+
ctx.cache.pop(identifier);
|
|
1465
|
+
}
|
|
1428
1466
|
}
|
|
1429
1467
|
return {
|
|
1430
1468
|
success,
|
|
@@ -1482,7 +1520,9 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1482
1520
|
const intervalDuration = ms(interval);
|
|
1483
1521
|
return () => ({
|
|
1484
1522
|
async limit(ctx, identifier, rate) {
|
|
1485
|
-
|
|
1523
|
+
const now = Date.now();
|
|
1524
|
+
const incrementBy = rate ?? 1;
|
|
1525
|
+
if (ctx.cache && incrementBy > 0) {
|
|
1486
1526
|
const { blocked, reset: reset2 } = ctx.cache.isBlocked(identifier);
|
|
1487
1527
|
if (blocked) {
|
|
1488
1528
|
return {
|
|
@@ -1495,8 +1535,6 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1495
1535
|
};
|
|
1496
1536
|
}
|
|
1497
1537
|
}
|
|
1498
|
-
const now = Date.now();
|
|
1499
|
-
const incrementBy = rate ? Math.max(1, rate) : 1;
|
|
1500
1538
|
const [remaining, reset] = await safeEval(
|
|
1501
1539
|
ctx,
|
|
1502
1540
|
SCRIPTS.singleRegion.tokenBucket.limit,
|
|
@@ -1504,8 +1542,12 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1504
1542
|
[maxTokens, intervalDuration, refillRate, now, incrementBy]
|
|
1505
1543
|
);
|
|
1506
1544
|
const success = remaining >= 0;
|
|
1507
|
-
if (ctx.cache
|
|
1508
|
-
|
|
1545
|
+
if (ctx.cache) {
|
|
1546
|
+
if (!success) {
|
|
1547
|
+
ctx.cache.blockUntil(identifier, reset);
|
|
1548
|
+
} else if (incrementBy < 0) {
|
|
1549
|
+
ctx.cache.pop(identifier);
|
|
1550
|
+
}
|
|
1509
1551
|
}
|
|
1510
1552
|
return {
|
|
1511
1553
|
success,
|
|
@@ -1577,10 +1619,10 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1577
1619
|
const bucket = Math.floor(Date.now() / windowDuration);
|
|
1578
1620
|
const key = [identifier, bucket].join(":");
|
|
1579
1621
|
const reset = (bucket + 1) * windowDuration;
|
|
1580
|
-
const incrementBy = rate
|
|
1622
|
+
const incrementBy = rate ?? 1;
|
|
1581
1623
|
const hit = typeof ctx.cache.get(key) === "number";
|
|
1582
1624
|
if (hit) {
|
|
1583
|
-
const cachedTokensAfterUpdate = ctx.cache.incr(key);
|
|
1625
|
+
const cachedTokensAfterUpdate = ctx.cache.incr(key, incrementBy);
|
|
1584
1626
|
const success = cachedTokensAfterUpdate < tokens;
|
|
1585
1627
|
const pending = success ? safeEval(
|
|
1586
1628
|
ctx,
|