@upstash/ratelimit 2.0.6 → 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 +167 -119
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +167 -119
- 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
|
}
|
|
@@ -205,7 +205,7 @@ var slidingWindowLimitScript = `
|
|
|
205
205
|
local tokens = tonumber(ARGV[1]) -- tokens per window
|
|
206
206
|
local now = ARGV[2] -- current timestamp in milliseconds
|
|
207
207
|
local window = ARGV[3] -- interval in milliseconds
|
|
208
|
-
local incrementBy = ARGV[4]
|
|
208
|
+
local incrementBy = tonumber(ARGV[4]) -- increment rate per request at a given value, default is 1
|
|
209
209
|
|
|
210
210
|
local requestsInCurrentWindow = redis.call("GET", currentKey)
|
|
211
211
|
if requestsInCurrentWindow == false then
|
|
@@ -219,12 +219,14 @@ var slidingWindowLimitScript = `
|
|
|
219
219
|
local percentageInCurrent = ( now % window ) / window
|
|
220
220
|
-- weighted requests to consider from the previous window
|
|
221
221
|
requestsInPreviousWindow = math.floor(( 1 - percentageInCurrent ) * requestsInPreviousWindow)
|
|
222
|
-
|
|
222
|
+
|
|
223
|
+
-- Only check limit if not refunding (negative rate)
|
|
224
|
+
if incrementBy > 0 and requestsInPreviousWindow + requestsInCurrentWindow >= tokens then
|
|
223
225
|
return -1
|
|
224
226
|
end
|
|
225
227
|
|
|
226
228
|
local newValue = redis.call("INCRBY", currentKey, incrementBy)
|
|
227
|
-
if newValue ==
|
|
229
|
+
if newValue == incrementBy then
|
|
228
230
|
-- The first time this key is set, the value will be equal to incrementBy.
|
|
229
231
|
-- So we only need the expire command once
|
|
230
232
|
redis.call("PEXPIRE", currentKey, window * 2 + 1000) -- Enough time to overlap with a new window + 1 second
|
|
@@ -281,7 +283,8 @@ var tokenBucketLimitScript = `
|
|
|
281
283
|
refilledAt = refilledAt + numRefills * interval
|
|
282
284
|
end
|
|
283
285
|
|
|
284
|
-
if tokens
|
|
286
|
+
-- Only reject if tokens are 0 and we're consuming (not refunding)
|
|
287
|
+
if tokens == 0 and incrementBy > 0 then
|
|
285
288
|
return {-1, refilledAt + interval}
|
|
286
289
|
end
|
|
287
290
|
|
|
@@ -289,7 +292,10 @@ var tokenBucketLimitScript = `
|
|
|
289
292
|
local expireAt = math.ceil(((maxTokens - remaining) / refillRate)) * interval
|
|
290
293
|
|
|
291
294
|
redis.call("HSET", key, "refilledAt", refilledAt, "tokens", remaining)
|
|
292
|
-
|
|
295
|
+
|
|
296
|
+
if (expireAt > 0) then
|
|
297
|
+
redis.call("PEXPIRE", key, expireAt)
|
|
298
|
+
end
|
|
293
299
|
return {remaining, refilledAt + interval}
|
|
294
300
|
`;
|
|
295
301
|
var tokenBucketIdentifierNotFound = -1;
|
|
@@ -377,7 +383,9 @@ var slidingWindowLimitScript2 = `
|
|
|
377
383
|
end
|
|
378
384
|
|
|
379
385
|
local percentageInCurrent = ( now % window) / window
|
|
380
|
-
|
|
386
|
+
|
|
387
|
+
-- Only check limit if not refunding (negative rate)
|
|
388
|
+
if incrementBy > 0 and requestsInPreviousWindow * (1 - percentageInCurrent ) + requestsInCurrentWindow + incrementBy > tokens then
|
|
381
389
|
return {currentFields, previousFields, false}
|
|
382
390
|
end
|
|
383
391
|
|
|
@@ -455,7 +463,7 @@ var SCRIPTS = {
|
|
|
455
463
|
slidingWindow: {
|
|
456
464
|
limit: {
|
|
457
465
|
script: slidingWindowLimitScript,
|
|
458
|
-
hash: "
|
|
466
|
+
hash: "9b7842963bd73721f1a3011650c23c0010848ee3"
|
|
459
467
|
},
|
|
460
468
|
getRemaining: {
|
|
461
469
|
script: slidingWindowRemainingTokensScript,
|
|
@@ -465,7 +473,7 @@ var SCRIPTS = {
|
|
|
465
473
|
tokenBucket: {
|
|
466
474
|
limit: {
|
|
467
475
|
script: tokenBucketLimitScript,
|
|
468
|
-
hash: "
|
|
476
|
+
hash: "d1f857ebbdaeca90ccd2cd4eada61d7c8e5db1ca"
|
|
469
477
|
},
|
|
470
478
|
getRemaining: {
|
|
471
479
|
script: tokenBucketRemainingTokensScript,
|
|
@@ -497,7 +505,7 @@ var SCRIPTS = {
|
|
|
497
505
|
slidingWindow: {
|
|
498
506
|
limit: {
|
|
499
507
|
script: slidingWindowLimitScript2,
|
|
500
|
-
hash: "
|
|
508
|
+
hash: "1e7ca8dcd2d600a6d0124a67a57ea225ed62921b"
|
|
501
509
|
},
|
|
502
510
|
getRemaining: {
|
|
503
511
|
script: slidingWindowRemainingTokensScript2,
|
|
@@ -973,7 +981,11 @@ var MultiRegionRatelimit = class extends Ratelimit {
|
|
|
973
981
|
const windowDuration = ms(window);
|
|
974
982
|
return () => ({
|
|
975
983
|
async limit(ctx, identifier, rate) {
|
|
976
|
-
|
|
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) {
|
|
977
989
|
const { blocked, reset: reset2 } = ctx.cache.isBlocked(identifier);
|
|
978
990
|
if (blocked) {
|
|
979
991
|
return {
|
|
@@ -986,10 +998,6 @@ var MultiRegionRatelimit = class extends Ratelimit {
|
|
|
986
998
|
};
|
|
987
999
|
}
|
|
988
1000
|
}
|
|
989
|
-
const requestId = randomId();
|
|
990
|
-
const bucket = Math.floor(Date.now() / windowDuration);
|
|
991
|
-
const key = [identifier, bucket].join(":");
|
|
992
|
-
const incrementBy = rate ? Math.max(1, rate) : 1;
|
|
993
1001
|
const dbs = ctx.regionContexts.map((regionContext) => ({
|
|
994
1002
|
redis: regionContext.redis,
|
|
995
1003
|
request: safeEval(
|
|
@@ -1000,24 +1008,29 @@ var MultiRegionRatelimit = class extends Ratelimit {
|
|
|
1000
1008
|
)
|
|
1001
1009
|
}));
|
|
1002
1010
|
const firstResponse = await Promise.any(dbs.map((s) => s.request));
|
|
1003
|
-
const usedTokens = firstResponse.reduce(
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
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
|
+
);
|
|
1010
1021
|
const remaining = tokens - usedTokens;
|
|
1011
1022
|
async function sync() {
|
|
1012
1023
|
const individualIDs = await Promise.all(dbs.map((s) => s.request));
|
|
1013
|
-
const allIDs = [
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
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
|
+
];
|
|
1021
1034
|
for (const db of dbs) {
|
|
1022
1035
|
const usedDbTokensRequest = await db.request;
|
|
1023
1036
|
const usedDbTokens = usedDbTokensRequest.reduce(
|
|
@@ -1031,12 +1044,15 @@ var MultiRegionRatelimit = class extends Ratelimit {
|
|
|
1031
1044
|
0
|
|
1032
1045
|
);
|
|
1033
1046
|
const dbIdsRequest = await db.request;
|
|
1034
|
-
const dbIds = dbIdsRequest.reduce(
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
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
|
+
);
|
|
1040
1056
|
if (usedDbTokens >= tokens) {
|
|
1041
1057
|
continue;
|
|
1042
1058
|
}
|
|
@@ -1049,10 +1065,14 @@ var MultiRegionRatelimit = class extends Ratelimit {
|
|
|
1049
1065
|
}
|
|
1050
1066
|
}
|
|
1051
1067
|
}
|
|
1052
|
-
const success = remaining
|
|
1068
|
+
const success = remaining >= 0;
|
|
1053
1069
|
const reset = (bucket + 1) * windowDuration;
|
|
1054
|
-
if (ctx.cache
|
|
1055
|
-
|
|
1070
|
+
if (ctx.cache) {
|
|
1071
|
+
if (!success) {
|
|
1072
|
+
ctx.cache.blockUntil(identifier, reset);
|
|
1073
|
+
} else if (incrementBy < 0) {
|
|
1074
|
+
ctx.cache.pop(identifier);
|
|
1075
|
+
}
|
|
1056
1076
|
}
|
|
1057
1077
|
return {
|
|
1058
1078
|
success,
|
|
@@ -1075,13 +1095,16 @@ var MultiRegionRatelimit = class extends Ratelimit {
|
|
|
1075
1095
|
)
|
|
1076
1096
|
}));
|
|
1077
1097
|
const firstResponse = await Promise.any(dbs.map((s) => s.request));
|
|
1078
|
-
const usedTokens = firstResponse.reduce(
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
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
|
+
);
|
|
1085
1108
|
return {
|
|
1086
1109
|
remaining: Math.max(0, tokens - usedTokens),
|
|
1087
1110
|
reset: (bucket + 1) * windowDuration
|
|
@@ -1092,14 +1115,11 @@ var MultiRegionRatelimit = class extends Ratelimit {
|
|
|
1092
1115
|
if (ctx.cache) {
|
|
1093
1116
|
ctx.cache.pop(identifier);
|
|
1094
1117
|
}
|
|
1095
|
-
await Promise.all(
|
|
1096
|
-
|
|
1097
|
-
regionContext,
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
[null]
|
|
1101
|
-
);
|
|
1102
|
-
}));
|
|
1118
|
+
await Promise.all(
|
|
1119
|
+
ctx.regionContexts.map((regionContext) => {
|
|
1120
|
+
safeEval(regionContext, RESET_SCRIPT, [pattern], [null]);
|
|
1121
|
+
})
|
|
1122
|
+
);
|
|
1103
1123
|
}
|
|
1104
1124
|
});
|
|
1105
1125
|
}
|
|
@@ -1124,7 +1144,14 @@ var MultiRegionRatelimit = class extends Ratelimit {
|
|
|
1124
1144
|
const windowDuration = ms(window);
|
|
1125
1145
|
return () => ({
|
|
1126
1146
|
async limit(ctx, identifier, rate) {
|
|
1127
|
-
|
|
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) {
|
|
1128
1155
|
const { blocked, reset: reset2 } = ctx.cache.isBlocked(identifier);
|
|
1129
1156
|
if (blocked) {
|
|
1130
1157
|
return {
|
|
@@ -1137,13 +1164,6 @@ var MultiRegionRatelimit = class extends Ratelimit {
|
|
|
1137
1164
|
};
|
|
1138
1165
|
}
|
|
1139
1166
|
}
|
|
1140
|
-
const requestId = randomId();
|
|
1141
|
-
const now = Date.now();
|
|
1142
|
-
const currentWindow = Math.floor(now / windowSize);
|
|
1143
|
-
const currentKey = [identifier, currentWindow].join(":");
|
|
1144
|
-
const previousWindow = currentWindow - 1;
|
|
1145
|
-
const previousKey = [identifier, previousWindow].join(":");
|
|
1146
|
-
const incrementBy = rate ? Math.max(1, rate) : 1;
|
|
1147
1167
|
const dbs = ctx.regionContexts.map((regionContext) => ({
|
|
1148
1168
|
redis: regionContext.redis,
|
|
1149
1169
|
request: safeEval(
|
|
@@ -1155,37 +1175,49 @@ var MultiRegionRatelimit = class extends Ratelimit {
|
|
|
1155
1175
|
)
|
|
1156
1176
|
}));
|
|
1157
1177
|
const percentageInCurrent = now % windowDuration / windowDuration;
|
|
1158
|
-
const [current, previous, success] = await Promise.any(
|
|
1178
|
+
const [current, previous, success] = await Promise.any(
|
|
1179
|
+
dbs.map((s) => s.request)
|
|
1180
|
+
);
|
|
1159
1181
|
if (success) {
|
|
1160
1182
|
current.push(requestId, incrementBy.toString());
|
|
1161
1183
|
}
|
|
1162
|
-
const previousUsedTokens = previous.reduce(
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
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
|
+
);
|
|
1177
1207
|
const usedTokens = previousPartialUsed + currentUsedTokens;
|
|
1178
1208
|
const remaining = tokens - usedTokens;
|
|
1179
1209
|
async function sync() {
|
|
1180
1210
|
const res = await Promise.all(dbs.map((s) => s.request));
|
|
1181
|
-
const allCurrentIds = [
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
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
|
+
];
|
|
1189
1221
|
for (const db of dbs) {
|
|
1190
1222
|
const [current2, _previous, _success] = await db.request;
|
|
1191
1223
|
const dbIds = current2.reduce((ids, currentId, index) => {
|
|
@@ -1194,13 +1226,16 @@ var MultiRegionRatelimit = class extends Ratelimit {
|
|
|
1194
1226
|
}
|
|
1195
1227
|
return ids;
|
|
1196
1228
|
}, []);
|
|
1197
|
-
const usedDbTokens = current2.reduce(
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
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
|
+
);
|
|
1204
1239
|
if (usedDbTokens >= tokens) {
|
|
1205
1240
|
continue;
|
|
1206
1241
|
}
|
|
@@ -1214,8 +1249,12 @@ var MultiRegionRatelimit = class extends Ratelimit {
|
|
|
1214
1249
|
}
|
|
1215
1250
|
}
|
|
1216
1251
|
const reset = (currentWindow + 1) * windowDuration;
|
|
1217
|
-
if (ctx.cache
|
|
1218
|
-
|
|
1252
|
+
if (ctx.cache) {
|
|
1253
|
+
if (!success) {
|
|
1254
|
+
ctx.cache.blockUntil(identifier, reset);
|
|
1255
|
+
} else if (incrementBy < 0) {
|
|
1256
|
+
ctx.cache.pop(identifier);
|
|
1257
|
+
}
|
|
1219
1258
|
}
|
|
1220
1259
|
return {
|
|
1221
1260
|
success: Boolean(success),
|
|
@@ -1252,14 +1291,11 @@ var MultiRegionRatelimit = class extends Ratelimit {
|
|
|
1252
1291
|
if (ctx.cache) {
|
|
1253
1292
|
ctx.cache.pop(identifier);
|
|
1254
1293
|
}
|
|
1255
|
-
await Promise.all(
|
|
1256
|
-
|
|
1257
|
-
regionContext,
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
[null]
|
|
1261
|
-
);
|
|
1262
|
-
}));
|
|
1294
|
+
await Promise.all(
|
|
1295
|
+
ctx.regionContexts.map((regionContext) => {
|
|
1296
|
+
safeEval(regionContext, RESET_SCRIPT, [pattern], [null]);
|
|
1297
|
+
})
|
|
1298
|
+
);
|
|
1263
1299
|
}
|
|
1264
1300
|
});
|
|
1265
1301
|
}
|
|
@@ -1308,7 +1344,8 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1308
1344
|
async limit(ctx, identifier, rate) {
|
|
1309
1345
|
const bucket = Math.floor(Date.now() / windowDuration);
|
|
1310
1346
|
const key = [identifier, bucket].join(":");
|
|
1311
|
-
|
|
1347
|
+
const incrementBy = rate ?? 1;
|
|
1348
|
+
if (ctx.cache && incrementBy > 0) {
|
|
1312
1349
|
const { blocked, reset: reset2 } = ctx.cache.isBlocked(identifier);
|
|
1313
1350
|
if (blocked) {
|
|
1314
1351
|
return {
|
|
@@ -1321,7 +1358,6 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1321
1358
|
};
|
|
1322
1359
|
}
|
|
1323
1360
|
}
|
|
1324
|
-
const incrementBy = rate ? Math.max(1, rate) : 1;
|
|
1325
1361
|
const usedTokensAfterUpdate = await safeEval(
|
|
1326
1362
|
ctx,
|
|
1327
1363
|
SCRIPTS.singleRegion.fixedWindow.limit,
|
|
@@ -1331,8 +1367,12 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1331
1367
|
const success = usedTokensAfterUpdate <= tokens;
|
|
1332
1368
|
const remainingTokens = Math.max(0, tokens - usedTokensAfterUpdate);
|
|
1333
1369
|
const reset = (bucket + 1) * windowDuration;
|
|
1334
|
-
if (ctx.cache
|
|
1335
|
-
|
|
1370
|
+
if (ctx.cache) {
|
|
1371
|
+
if (!success) {
|
|
1372
|
+
ctx.cache.blockUntil(identifier, reset);
|
|
1373
|
+
} else if (incrementBy < 0) {
|
|
1374
|
+
ctx.cache.pop(identifier);
|
|
1375
|
+
}
|
|
1336
1376
|
}
|
|
1337
1377
|
return {
|
|
1338
1378
|
success,
|
|
@@ -1395,7 +1435,8 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1395
1435
|
const currentKey = [identifier, currentWindow].join(":");
|
|
1396
1436
|
const previousWindow = currentWindow - 1;
|
|
1397
1437
|
const previousKey = [identifier, previousWindow].join(":");
|
|
1398
|
-
|
|
1438
|
+
const incrementBy = rate ?? 1;
|
|
1439
|
+
if (ctx.cache && incrementBy > 0) {
|
|
1399
1440
|
const { blocked, reset: reset2 } = ctx.cache.isBlocked(identifier);
|
|
1400
1441
|
if (blocked) {
|
|
1401
1442
|
return {
|
|
@@ -1408,7 +1449,6 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1408
1449
|
};
|
|
1409
1450
|
}
|
|
1410
1451
|
}
|
|
1411
|
-
const incrementBy = rate ? Math.max(1, rate) : 1;
|
|
1412
1452
|
const remainingTokens = await safeEval(
|
|
1413
1453
|
ctx,
|
|
1414
1454
|
SCRIPTS.singleRegion.slidingWindow.limit,
|
|
@@ -1417,8 +1457,12 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1417
1457
|
);
|
|
1418
1458
|
const success = remainingTokens >= 0;
|
|
1419
1459
|
const reset = (currentWindow + 1) * windowSize;
|
|
1420
|
-
if (ctx.cache
|
|
1421
|
-
|
|
1460
|
+
if (ctx.cache) {
|
|
1461
|
+
if (!success) {
|
|
1462
|
+
ctx.cache.blockUntil(identifier, reset);
|
|
1463
|
+
} else if (incrementBy < 0) {
|
|
1464
|
+
ctx.cache.pop(identifier);
|
|
1465
|
+
}
|
|
1422
1466
|
}
|
|
1423
1467
|
return {
|
|
1424
1468
|
success,
|
|
@@ -1476,7 +1520,9 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1476
1520
|
const intervalDuration = ms(interval);
|
|
1477
1521
|
return () => ({
|
|
1478
1522
|
async limit(ctx, identifier, rate) {
|
|
1479
|
-
|
|
1523
|
+
const now = Date.now();
|
|
1524
|
+
const incrementBy = rate ?? 1;
|
|
1525
|
+
if (ctx.cache && incrementBy > 0) {
|
|
1480
1526
|
const { blocked, reset: reset2 } = ctx.cache.isBlocked(identifier);
|
|
1481
1527
|
if (blocked) {
|
|
1482
1528
|
return {
|
|
@@ -1489,8 +1535,6 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1489
1535
|
};
|
|
1490
1536
|
}
|
|
1491
1537
|
}
|
|
1492
|
-
const now = Date.now();
|
|
1493
|
-
const incrementBy = rate ? Math.max(1, rate) : 1;
|
|
1494
1538
|
const [remaining, reset] = await safeEval(
|
|
1495
1539
|
ctx,
|
|
1496
1540
|
SCRIPTS.singleRegion.tokenBucket.limit,
|
|
@@ -1498,8 +1542,12 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1498
1542
|
[maxTokens, intervalDuration, refillRate, now, incrementBy]
|
|
1499
1543
|
);
|
|
1500
1544
|
const success = remaining >= 0;
|
|
1501
|
-
if (ctx.cache
|
|
1502
|
-
|
|
1545
|
+
if (ctx.cache) {
|
|
1546
|
+
if (!success) {
|
|
1547
|
+
ctx.cache.blockUntil(identifier, reset);
|
|
1548
|
+
} else if (incrementBy < 0) {
|
|
1549
|
+
ctx.cache.pop(identifier);
|
|
1550
|
+
}
|
|
1503
1551
|
}
|
|
1504
1552
|
return {
|
|
1505
1553
|
success,
|
|
@@ -1571,10 +1619,10 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1571
1619
|
const bucket = Math.floor(Date.now() / windowDuration);
|
|
1572
1620
|
const key = [identifier, bucket].join(":");
|
|
1573
1621
|
const reset = (bucket + 1) * windowDuration;
|
|
1574
|
-
const incrementBy = rate
|
|
1622
|
+
const incrementBy = rate ?? 1;
|
|
1575
1623
|
const hit = typeof ctx.cache.get(key) === "number";
|
|
1576
1624
|
if (hit) {
|
|
1577
|
-
const cachedTokensAfterUpdate = ctx.cache.incr(key);
|
|
1625
|
+
const cachedTokensAfterUpdate = ctx.cache.incr(key, incrementBy);
|
|
1578
1626
|
const success = cachedTokensAfterUpdate < tokens;
|
|
1579
1627
|
const pending = success ? safeEval(
|
|
1580
1628
|
ctx,
|