@upstash/ratelimit 2.1.0-rc → 2.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 +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +51 -50
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +51 -50
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -137,33 +137,30 @@ var DYNAMIC_LIMIT_KEY_SUFFIX = ":dynamic:global";
|
|
|
137
137
|
var DEFAULT_PREFIX = "@upstash/ratelimit";
|
|
138
138
|
|
|
139
139
|
// src/duration.ts
|
|
140
|
+
var MULTIPLIERS = {
|
|
141
|
+
ms: 1,
|
|
142
|
+
s: 1e3,
|
|
143
|
+
m: 1e3 * 60,
|
|
144
|
+
h: 1e3 * 60 * 60,
|
|
145
|
+
d: 1e3 * 60 * 60 * 24,
|
|
146
|
+
w: 1e3 * 60 * 60 * 24 * 7
|
|
147
|
+
};
|
|
140
148
|
function ms(d) {
|
|
141
|
-
const match = d.match(/^(\d+)\s?(ms|s|m|h|d)$/);
|
|
149
|
+
const match = d.match(/^(\d+(?:\.\d+)?)\s?(ms|s|m|h|d|w)$/);
|
|
142
150
|
if (!match) {
|
|
143
151
|
throw new Error(`Unable to parse window size: ${d}`);
|
|
144
152
|
}
|
|
145
|
-
const time = Number.
|
|
153
|
+
const time = Number.parseFloat(match[1]);
|
|
146
154
|
const unit = match[2];
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}
|
|
151
|
-
case "s": {
|
|
152
|
-
return time * 1e3;
|
|
153
|
-
}
|
|
154
|
-
case "m": {
|
|
155
|
-
return time * 1e3 * 60;
|
|
156
|
-
}
|
|
157
|
-
case "h": {
|
|
158
|
-
return time * 1e3 * 60 * 60;
|
|
159
|
-
}
|
|
160
|
-
case "d": {
|
|
161
|
-
return time * 1e3 * 60 * 60 * 24;
|
|
162
|
-
}
|
|
163
|
-
default: {
|
|
164
|
-
throw new Error(`Unable to parse window size: ${d}`);
|
|
165
|
-
}
|
|
155
|
+
const multiplier = MULTIPLIERS[unit];
|
|
156
|
+
if (multiplier === void 0) {
|
|
157
|
+
throw new Error(`Unable to parse window size: ${d}`);
|
|
166
158
|
}
|
|
159
|
+
const result = Math.round(time * multiplier);
|
|
160
|
+
if (result < 1) {
|
|
161
|
+
throw new Error(`Window size must be at least 1 ms, got: ${d}`);
|
|
162
|
+
}
|
|
163
|
+
return result;
|
|
167
164
|
}
|
|
168
165
|
|
|
169
166
|
// src/hash.ts
|
|
@@ -188,7 +185,7 @@ var fixedWindowLimitScript = `#!lua flags=allow-key-locking
|
|
|
188
185
|
|
|
189
186
|
-- Check for dynamic limit
|
|
190
187
|
local effectiveLimit = tokens
|
|
191
|
-
if dynamicLimitKey ~= "" then
|
|
188
|
+
if dynamicLimitKey and dynamicLimitKey ~= "" then
|
|
192
189
|
local dynamicLimit = redis.call("GET", dynamicLimitKey)
|
|
193
190
|
if dynamicLimit then
|
|
194
191
|
effectiveLimit = tonumber(dynamicLimit)
|
|
@@ -211,7 +208,7 @@ var fixedWindowRemainingTokensScript = `#!lua flags=allow-key-locking
|
|
|
211
208
|
|
|
212
209
|
-- Check for dynamic limit
|
|
213
210
|
local effectiveLimit = tokens
|
|
214
|
-
if dynamicLimitKey ~= "" then
|
|
211
|
+
if dynamicLimitKey and dynamicLimitKey ~= "" then
|
|
215
212
|
local dynamicLimit = redis.call("GET", dynamicLimitKey)
|
|
216
213
|
if dynamicLimit then
|
|
217
214
|
effectiveLimit = tonumber(dynamicLimit)
|
|
@@ -237,7 +234,7 @@ var slidingWindowLimitScript = `#!lua flags=allow-key-locking
|
|
|
237
234
|
|
|
238
235
|
-- Check for dynamic limit
|
|
239
236
|
local effectiveLimit = tokens
|
|
240
|
-
if dynamicLimitKey ~= "" then
|
|
237
|
+
if dynamicLimitKey and dynamicLimitKey ~= "" then
|
|
241
238
|
local dynamicLimit = redis.call("GET", dynamicLimitKey)
|
|
242
239
|
if dynamicLimit then
|
|
243
240
|
effectiveLimit = tonumber(dynamicLimit)
|
|
@@ -280,7 +277,7 @@ var slidingWindowRemainingTokensScript = `#!lua flags=allow-key-locking
|
|
|
280
277
|
|
|
281
278
|
-- Check for dynamic limit
|
|
282
279
|
local effectiveLimit = tokens
|
|
283
|
-
if dynamicLimitKey ~= "" then
|
|
280
|
+
if dynamicLimitKey and dynamicLimitKey ~= "" then
|
|
284
281
|
local dynamicLimit = redis.call("GET", dynamicLimitKey)
|
|
285
282
|
if dynamicLimit then
|
|
286
283
|
effectiveLimit = tonumber(dynamicLimit)
|
|
@@ -315,7 +312,7 @@ var tokenBucketLimitScript = `#!lua flags=allow-key-locking
|
|
|
315
312
|
|
|
316
313
|
-- Check for dynamic limit
|
|
317
314
|
local effectiveLimit = maxTokens
|
|
318
|
-
if dynamicLimitKey ~= "" then
|
|
315
|
+
if dynamicLimitKey and dynamicLimitKey ~= "" then
|
|
319
316
|
local dynamicLimit = redis.call("GET", dynamicLimitKey)
|
|
320
317
|
if dynamicLimit then
|
|
321
318
|
effectiveLimit = tonumber(dynamicLimit)
|
|
@@ -342,8 +339,12 @@ var tokenBucketLimitScript = `#!lua flags=allow-key-locking
|
|
|
342
339
|
refilledAt = refilledAt + numRefills * interval
|
|
343
340
|
end
|
|
344
341
|
|
|
345
|
-
-- Only reject
|
|
346
|
-
|
|
342
|
+
-- Only reject when consuming (not refunding) and there are not enough tokens
|
|
343
|
+
-- for this request. Checking tokens == 0 only covered incrementBy == 1: with
|
|
344
|
+
-- a larger rate (e.g. 2 tokens left, incrementBy 5) the request fell through,
|
|
345
|
+
-- drove tokens negative and still wrote it back, so a rejected request
|
|
346
|
+
-- consumed tokens and a single large rate could lock the identifier out.
|
|
347
|
+
if incrementBy > 0 and tokens < incrementBy then
|
|
347
348
|
return {-1, refilledAt + interval, effectiveLimit}
|
|
348
349
|
end
|
|
349
350
|
|
|
@@ -365,7 +366,7 @@ var tokenBucketRemainingTokensScript = `#!lua flags=allow-key-locking
|
|
|
365
366
|
|
|
366
367
|
-- Check for dynamic limit
|
|
367
368
|
local effectiveLimit = maxTokens
|
|
368
|
-
if dynamicLimitKey ~= "" then
|
|
369
|
+
if dynamicLimitKey and dynamicLimitKey ~= "" then
|
|
369
370
|
local dynamicLimit = redis.call("GET", dynamicLimitKey)
|
|
370
371
|
if dynamicLimit then
|
|
371
372
|
effectiveLimit = tonumber(dynamicLimit)
|
|
@@ -522,31 +523,31 @@ var SCRIPTS = {
|
|
|
522
523
|
fixedWindow: {
|
|
523
524
|
limit: {
|
|
524
525
|
script: fixedWindowLimitScript,
|
|
525
|
-
hash: "
|
|
526
|
+
hash: "b2efc02798c8e9b471a10f9b1d5745db89923ff3"
|
|
526
527
|
},
|
|
527
528
|
getRemaining: {
|
|
528
529
|
script: fixedWindowRemainingTokensScript,
|
|
529
|
-
hash: "
|
|
530
|
+
hash: "823cb2a5a902d69681244f5e28e5ebb8f2a89edd"
|
|
530
531
|
}
|
|
531
532
|
},
|
|
532
533
|
slidingWindow: {
|
|
533
534
|
limit: {
|
|
534
535
|
script: slidingWindowLimitScript,
|
|
535
|
-
hash: "
|
|
536
|
+
hash: "74740bb3d7792848093cdef586b6b6037c455fd6"
|
|
536
537
|
},
|
|
537
538
|
getRemaining: {
|
|
538
539
|
script: slidingWindowRemainingTokensScript,
|
|
539
|
-
hash: "
|
|
540
|
+
hash: "7ca36169e0fc9caedd81b4eef8e99854c157f9b5"
|
|
540
541
|
}
|
|
541
542
|
},
|
|
542
543
|
tokenBucket: {
|
|
543
544
|
limit: {
|
|
544
545
|
script: tokenBucketLimitScript,
|
|
545
|
-
hash: "
|
|
546
|
+
hash: "d60be5bd3d83482ae229309f1f7425424f891204"
|
|
546
547
|
},
|
|
547
548
|
getRemaining: {
|
|
548
549
|
script: tokenBucketRemainingTokensScript,
|
|
549
|
-
hash: "
|
|
550
|
+
hash: "40daf4a441df736f5c99f8b77bbe176c909e5de4"
|
|
550
551
|
}
|
|
551
552
|
},
|
|
552
553
|
cachedFixedWindow: {
|
|
@@ -871,7 +872,7 @@ var Ratelimit = class {
|
|
|
871
872
|
throw new Error("This should not happen");
|
|
872
873
|
}
|
|
873
874
|
const wait = Math.min(res.reset, deadline) - Date.now();
|
|
874
|
-
await new Promise((r) => setTimeout(r, wait));
|
|
875
|
+
await new Promise((r) => setTimeout(r, wait > 0 ? wait : 100));
|
|
875
876
|
if (Date.now() > deadline) {
|
|
876
877
|
break;
|
|
877
878
|
}
|
|
@@ -1060,7 +1061,7 @@ var Ratelimit = class {
|
|
|
1060
1061
|
};
|
|
1061
1062
|
|
|
1062
1063
|
// src/version.ts
|
|
1063
|
-
var VERSION = "2.
|
|
1064
|
+
var VERSION = "2.2.0";
|
|
1064
1065
|
|
|
1065
1066
|
// src/telemetry.ts
|
|
1066
1067
|
var taggedClients = /* @__PURE__ */ new WeakSet();
|
|
@@ -1522,11 +1523,11 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1522
1523
|
};
|
|
1523
1524
|
}
|
|
1524
1525
|
}
|
|
1525
|
-
const
|
|
1526
|
+
const dynamicLimitKeys = ctx.dynamicLimits ? [`${ctx.prefix}${DYNAMIC_LIMIT_KEY_SUFFIX}`] : [];
|
|
1526
1527
|
const [usedTokensAfterUpdate, effectiveLimit] = await safeEval(
|
|
1527
1528
|
ctx,
|
|
1528
1529
|
SCRIPTS.singleRegion.fixedWindow.limit,
|
|
1529
|
-
[key,
|
|
1530
|
+
[key, ...dynamicLimitKeys],
|
|
1530
1531
|
[tokens, windowDuration, incrementBy]
|
|
1531
1532
|
);
|
|
1532
1533
|
const success = usedTokensAfterUpdate <= effectiveLimit;
|
|
@@ -1550,11 +1551,11 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1550
1551
|
async getRemaining(ctx, identifier) {
|
|
1551
1552
|
const bucket = Math.floor(Date.now() / windowDuration);
|
|
1552
1553
|
const key = [identifier, bucket].join(":");
|
|
1553
|
-
const
|
|
1554
|
+
const dynamicLimitKeys = ctx.dynamicLimits ? [`${ctx.prefix}${DYNAMIC_LIMIT_KEY_SUFFIX}`] : [];
|
|
1554
1555
|
const [remaining, effectiveLimit] = await safeEval(
|
|
1555
1556
|
ctx,
|
|
1556
1557
|
SCRIPTS.singleRegion.fixedWindow.getRemaining,
|
|
1557
|
-
[key,
|
|
1558
|
+
[key, ...dynamicLimitKeys],
|
|
1558
1559
|
[tokens]
|
|
1559
1560
|
);
|
|
1560
1561
|
return {
|
|
@@ -1616,11 +1617,11 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1616
1617
|
};
|
|
1617
1618
|
}
|
|
1618
1619
|
}
|
|
1619
|
-
const
|
|
1620
|
+
const dynamicLimitKeys = ctx.dynamicLimits ? [`${ctx.prefix}${DYNAMIC_LIMIT_KEY_SUFFIX}`] : [];
|
|
1620
1621
|
const [remainingTokens, effectiveLimit] = await safeEval(
|
|
1621
1622
|
ctx,
|
|
1622
1623
|
SCRIPTS.singleRegion.slidingWindow.limit,
|
|
1623
|
-
[currentKey, previousKey,
|
|
1624
|
+
[currentKey, previousKey, ...dynamicLimitKeys],
|
|
1624
1625
|
[tokens, now, windowSize, incrementBy]
|
|
1625
1626
|
);
|
|
1626
1627
|
const success = remainingTokens >= 0;
|
|
@@ -1646,11 +1647,11 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1646
1647
|
const currentKey = [identifier, currentWindow].join(":");
|
|
1647
1648
|
const previousWindow = currentWindow - 1;
|
|
1648
1649
|
const previousKey = [identifier, previousWindow].join(":");
|
|
1649
|
-
const
|
|
1650
|
+
const dynamicLimitKeys = ctx.dynamicLimits ? [`${ctx.prefix}${DYNAMIC_LIMIT_KEY_SUFFIX}`] : [];
|
|
1650
1651
|
const [remaining, effectiveLimit] = await safeEval(
|
|
1651
1652
|
ctx,
|
|
1652
1653
|
SCRIPTS.singleRegion.slidingWindow.getRemaining,
|
|
1653
|
-
[currentKey, previousKey,
|
|
1654
|
+
[currentKey, previousKey, ...dynamicLimitKeys],
|
|
1654
1655
|
[tokens, now, windowSize]
|
|
1655
1656
|
);
|
|
1656
1657
|
return {
|
|
@@ -1705,11 +1706,11 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1705
1706
|
};
|
|
1706
1707
|
}
|
|
1707
1708
|
}
|
|
1708
|
-
const
|
|
1709
|
+
const dynamicLimitKeys = ctx.dynamicLimits ? [`${ctx.prefix}${DYNAMIC_LIMIT_KEY_SUFFIX}`] : [];
|
|
1709
1710
|
const [remaining, reset, effectiveLimit] = await safeEval(
|
|
1710
1711
|
ctx,
|
|
1711
1712
|
SCRIPTS.singleRegion.tokenBucket.limit,
|
|
1712
|
-
[identifier,
|
|
1713
|
+
[identifier, ...dynamicLimitKeys],
|
|
1713
1714
|
[maxTokens, intervalDuration, refillRate, now, incrementBy]
|
|
1714
1715
|
);
|
|
1715
1716
|
const success = remaining >= 0;
|
|
@@ -1729,11 +1730,11 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1729
1730
|
};
|
|
1730
1731
|
},
|
|
1731
1732
|
async getRemaining(ctx, identifier) {
|
|
1732
|
-
const
|
|
1733
|
+
const dynamicLimitKeys = ctx.dynamicLimits ? [`${ctx.prefix}${DYNAMIC_LIMIT_KEY_SUFFIX}`] : [];
|
|
1733
1734
|
const [remainingTokens, refilledAt, effectiveLimit] = await safeEval(
|
|
1734
1735
|
ctx,
|
|
1735
1736
|
SCRIPTS.singleRegion.tokenBucket.getRemaining,
|
|
1736
|
-
[identifier,
|
|
1737
|
+
[identifier, ...dynamicLimitKeys],
|
|
1737
1738
|
[maxTokens]
|
|
1738
1739
|
);
|
|
1739
1740
|
const freshRefillAt = Date.now() + intervalDuration;
|
|
@@ -1801,7 +1802,7 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1801
1802
|
const hit = typeof ctx.cache.get(key) === "number";
|
|
1802
1803
|
if (hit) {
|
|
1803
1804
|
const cachedTokensAfterUpdate = ctx.cache.incr(key, incrementBy);
|
|
1804
|
-
const success = cachedTokensAfterUpdate
|
|
1805
|
+
const success = cachedTokensAfterUpdate <= tokens;
|
|
1805
1806
|
const pending = success ? safeEval(
|
|
1806
1807
|
ctx,
|
|
1807
1808
|
SCRIPTS.singleRegion.cachedFixedWindow.limit,
|