@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.mjs
CHANGED
|
@@ -114,33 +114,30 @@ var DYNAMIC_LIMIT_KEY_SUFFIX = ":dynamic:global";
|
|
|
114
114
|
var DEFAULT_PREFIX = "@upstash/ratelimit";
|
|
115
115
|
|
|
116
116
|
// src/duration.ts
|
|
117
|
+
var MULTIPLIERS = {
|
|
118
|
+
ms: 1,
|
|
119
|
+
s: 1e3,
|
|
120
|
+
m: 1e3 * 60,
|
|
121
|
+
h: 1e3 * 60 * 60,
|
|
122
|
+
d: 1e3 * 60 * 60 * 24,
|
|
123
|
+
w: 1e3 * 60 * 60 * 24 * 7
|
|
124
|
+
};
|
|
117
125
|
function ms(d) {
|
|
118
|
-
const match = d.match(/^(\d+)\s?(ms|s|m|h|d)$/);
|
|
126
|
+
const match = d.match(/^(\d+(?:\.\d+)?)\s?(ms|s|m|h|d|w)$/);
|
|
119
127
|
if (!match) {
|
|
120
128
|
throw new Error(`Unable to parse window size: ${d}`);
|
|
121
129
|
}
|
|
122
|
-
const time = Number.
|
|
130
|
+
const time = Number.parseFloat(match[1]);
|
|
123
131
|
const unit = match[2];
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}
|
|
128
|
-
case "s": {
|
|
129
|
-
return time * 1e3;
|
|
130
|
-
}
|
|
131
|
-
case "m": {
|
|
132
|
-
return time * 1e3 * 60;
|
|
133
|
-
}
|
|
134
|
-
case "h": {
|
|
135
|
-
return time * 1e3 * 60 * 60;
|
|
136
|
-
}
|
|
137
|
-
case "d": {
|
|
138
|
-
return time * 1e3 * 60 * 60 * 24;
|
|
139
|
-
}
|
|
140
|
-
default: {
|
|
141
|
-
throw new Error(`Unable to parse window size: ${d}`);
|
|
142
|
-
}
|
|
132
|
+
const multiplier = MULTIPLIERS[unit];
|
|
133
|
+
if (multiplier === void 0) {
|
|
134
|
+
throw new Error(`Unable to parse window size: ${d}`);
|
|
143
135
|
}
|
|
136
|
+
const result = Math.round(time * multiplier);
|
|
137
|
+
if (result < 1) {
|
|
138
|
+
throw new Error(`Window size must be at least 1 ms, got: ${d}`);
|
|
139
|
+
}
|
|
140
|
+
return result;
|
|
144
141
|
}
|
|
145
142
|
|
|
146
143
|
// src/hash.ts
|
|
@@ -165,7 +162,7 @@ var fixedWindowLimitScript = `#!lua flags=allow-key-locking
|
|
|
165
162
|
|
|
166
163
|
-- Check for dynamic limit
|
|
167
164
|
local effectiveLimit = tokens
|
|
168
|
-
if dynamicLimitKey ~= "" then
|
|
165
|
+
if dynamicLimitKey and dynamicLimitKey ~= "" then
|
|
169
166
|
local dynamicLimit = redis.call("GET", dynamicLimitKey)
|
|
170
167
|
if dynamicLimit then
|
|
171
168
|
effectiveLimit = tonumber(dynamicLimit)
|
|
@@ -188,7 +185,7 @@ var fixedWindowRemainingTokensScript = `#!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)
|
|
@@ -214,7 +211,7 @@ var slidingWindowLimitScript = `#!lua flags=allow-key-locking
|
|
|
214
211
|
|
|
215
212
|
-- Check for dynamic limit
|
|
216
213
|
local effectiveLimit = tokens
|
|
217
|
-
if dynamicLimitKey ~= "" then
|
|
214
|
+
if dynamicLimitKey and dynamicLimitKey ~= "" then
|
|
218
215
|
local dynamicLimit = redis.call("GET", dynamicLimitKey)
|
|
219
216
|
if dynamicLimit then
|
|
220
217
|
effectiveLimit = tonumber(dynamicLimit)
|
|
@@ -257,7 +254,7 @@ var slidingWindowRemainingTokensScript = `#!lua flags=allow-key-locking
|
|
|
257
254
|
|
|
258
255
|
-- Check for dynamic limit
|
|
259
256
|
local effectiveLimit = tokens
|
|
260
|
-
if dynamicLimitKey ~= "" then
|
|
257
|
+
if dynamicLimitKey and dynamicLimitKey ~= "" then
|
|
261
258
|
local dynamicLimit = redis.call("GET", dynamicLimitKey)
|
|
262
259
|
if dynamicLimit then
|
|
263
260
|
effectiveLimit = tonumber(dynamicLimit)
|
|
@@ -292,7 +289,7 @@ var tokenBucketLimitScript = `#!lua flags=allow-key-locking
|
|
|
292
289
|
|
|
293
290
|
-- Check for dynamic limit
|
|
294
291
|
local effectiveLimit = maxTokens
|
|
295
|
-
if dynamicLimitKey ~= "" then
|
|
292
|
+
if dynamicLimitKey and dynamicLimitKey ~= "" then
|
|
296
293
|
local dynamicLimit = redis.call("GET", dynamicLimitKey)
|
|
297
294
|
if dynamicLimit then
|
|
298
295
|
effectiveLimit = tonumber(dynamicLimit)
|
|
@@ -319,8 +316,12 @@ var tokenBucketLimitScript = `#!lua flags=allow-key-locking
|
|
|
319
316
|
refilledAt = refilledAt + numRefills * interval
|
|
320
317
|
end
|
|
321
318
|
|
|
322
|
-
-- Only reject
|
|
323
|
-
|
|
319
|
+
-- Only reject when consuming (not refunding) and there are not enough tokens
|
|
320
|
+
-- for this request. Checking tokens == 0 only covered incrementBy == 1: with
|
|
321
|
+
-- a larger rate (e.g. 2 tokens left, incrementBy 5) the request fell through,
|
|
322
|
+
-- drove tokens negative and still wrote it back, so a rejected request
|
|
323
|
+
-- consumed tokens and a single large rate could lock the identifier out.
|
|
324
|
+
if incrementBy > 0 and tokens < incrementBy then
|
|
324
325
|
return {-1, refilledAt + interval, effectiveLimit}
|
|
325
326
|
end
|
|
326
327
|
|
|
@@ -342,7 +343,7 @@ var tokenBucketRemainingTokensScript = `#!lua flags=allow-key-locking
|
|
|
342
343
|
|
|
343
344
|
-- Check for dynamic limit
|
|
344
345
|
local effectiveLimit = maxTokens
|
|
345
|
-
if dynamicLimitKey ~= "" then
|
|
346
|
+
if dynamicLimitKey and dynamicLimitKey ~= "" then
|
|
346
347
|
local dynamicLimit = redis.call("GET", dynamicLimitKey)
|
|
347
348
|
if dynamicLimit then
|
|
348
349
|
effectiveLimit = tonumber(dynamicLimit)
|
|
@@ -499,31 +500,31 @@ var SCRIPTS = {
|
|
|
499
500
|
fixedWindow: {
|
|
500
501
|
limit: {
|
|
501
502
|
script: fixedWindowLimitScript,
|
|
502
|
-
hash: "
|
|
503
|
+
hash: "b2efc02798c8e9b471a10f9b1d5745db89923ff3"
|
|
503
504
|
},
|
|
504
505
|
getRemaining: {
|
|
505
506
|
script: fixedWindowRemainingTokensScript,
|
|
506
|
-
hash: "
|
|
507
|
+
hash: "823cb2a5a902d69681244f5e28e5ebb8f2a89edd"
|
|
507
508
|
}
|
|
508
509
|
},
|
|
509
510
|
slidingWindow: {
|
|
510
511
|
limit: {
|
|
511
512
|
script: slidingWindowLimitScript,
|
|
512
|
-
hash: "
|
|
513
|
+
hash: "74740bb3d7792848093cdef586b6b6037c455fd6"
|
|
513
514
|
},
|
|
514
515
|
getRemaining: {
|
|
515
516
|
script: slidingWindowRemainingTokensScript,
|
|
516
|
-
hash: "
|
|
517
|
+
hash: "7ca36169e0fc9caedd81b4eef8e99854c157f9b5"
|
|
517
518
|
}
|
|
518
519
|
},
|
|
519
520
|
tokenBucket: {
|
|
520
521
|
limit: {
|
|
521
522
|
script: tokenBucketLimitScript,
|
|
522
|
-
hash: "
|
|
523
|
+
hash: "d60be5bd3d83482ae229309f1f7425424f891204"
|
|
523
524
|
},
|
|
524
525
|
getRemaining: {
|
|
525
526
|
script: tokenBucketRemainingTokensScript,
|
|
526
|
-
hash: "
|
|
527
|
+
hash: "40daf4a441df736f5c99f8b77bbe176c909e5de4"
|
|
527
528
|
}
|
|
528
529
|
},
|
|
529
530
|
cachedFixedWindow: {
|
|
@@ -848,7 +849,7 @@ var Ratelimit = class {
|
|
|
848
849
|
throw new Error("This should not happen");
|
|
849
850
|
}
|
|
850
851
|
const wait = Math.min(res.reset, deadline) - Date.now();
|
|
851
|
-
await new Promise((r) => setTimeout(r, wait));
|
|
852
|
+
await new Promise((r) => setTimeout(r, wait > 0 ? wait : 100));
|
|
852
853
|
if (Date.now() > deadline) {
|
|
853
854
|
break;
|
|
854
855
|
}
|
|
@@ -1037,7 +1038,7 @@ var Ratelimit = class {
|
|
|
1037
1038
|
};
|
|
1038
1039
|
|
|
1039
1040
|
// src/version.ts
|
|
1040
|
-
var VERSION = "2.
|
|
1041
|
+
var VERSION = "2.2.0";
|
|
1041
1042
|
|
|
1042
1043
|
// src/telemetry.ts
|
|
1043
1044
|
var taggedClients = /* @__PURE__ */ new WeakSet();
|
|
@@ -1499,11 +1500,11 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1499
1500
|
};
|
|
1500
1501
|
}
|
|
1501
1502
|
}
|
|
1502
|
-
const
|
|
1503
|
+
const dynamicLimitKeys = ctx.dynamicLimits ? [`${ctx.prefix}${DYNAMIC_LIMIT_KEY_SUFFIX}`] : [];
|
|
1503
1504
|
const [usedTokensAfterUpdate, effectiveLimit] = await safeEval(
|
|
1504
1505
|
ctx,
|
|
1505
1506
|
SCRIPTS.singleRegion.fixedWindow.limit,
|
|
1506
|
-
[key,
|
|
1507
|
+
[key, ...dynamicLimitKeys],
|
|
1507
1508
|
[tokens, windowDuration, incrementBy]
|
|
1508
1509
|
);
|
|
1509
1510
|
const success = usedTokensAfterUpdate <= effectiveLimit;
|
|
@@ -1527,11 +1528,11 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1527
1528
|
async getRemaining(ctx, identifier) {
|
|
1528
1529
|
const bucket = Math.floor(Date.now() / windowDuration);
|
|
1529
1530
|
const key = [identifier, bucket].join(":");
|
|
1530
|
-
const
|
|
1531
|
+
const dynamicLimitKeys = ctx.dynamicLimits ? [`${ctx.prefix}${DYNAMIC_LIMIT_KEY_SUFFIX}`] : [];
|
|
1531
1532
|
const [remaining, effectiveLimit] = await safeEval(
|
|
1532
1533
|
ctx,
|
|
1533
1534
|
SCRIPTS.singleRegion.fixedWindow.getRemaining,
|
|
1534
|
-
[key,
|
|
1535
|
+
[key, ...dynamicLimitKeys],
|
|
1535
1536
|
[tokens]
|
|
1536
1537
|
);
|
|
1537
1538
|
return {
|
|
@@ -1593,11 +1594,11 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1593
1594
|
};
|
|
1594
1595
|
}
|
|
1595
1596
|
}
|
|
1596
|
-
const
|
|
1597
|
+
const dynamicLimitKeys = ctx.dynamicLimits ? [`${ctx.prefix}${DYNAMIC_LIMIT_KEY_SUFFIX}`] : [];
|
|
1597
1598
|
const [remainingTokens, effectiveLimit] = await safeEval(
|
|
1598
1599
|
ctx,
|
|
1599
1600
|
SCRIPTS.singleRegion.slidingWindow.limit,
|
|
1600
|
-
[currentKey, previousKey,
|
|
1601
|
+
[currentKey, previousKey, ...dynamicLimitKeys],
|
|
1601
1602
|
[tokens, now, windowSize, incrementBy]
|
|
1602
1603
|
);
|
|
1603
1604
|
const success = remainingTokens >= 0;
|
|
@@ -1623,11 +1624,11 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1623
1624
|
const currentKey = [identifier, currentWindow].join(":");
|
|
1624
1625
|
const previousWindow = currentWindow - 1;
|
|
1625
1626
|
const previousKey = [identifier, previousWindow].join(":");
|
|
1626
|
-
const
|
|
1627
|
+
const dynamicLimitKeys = ctx.dynamicLimits ? [`${ctx.prefix}${DYNAMIC_LIMIT_KEY_SUFFIX}`] : [];
|
|
1627
1628
|
const [remaining, effectiveLimit] = await safeEval(
|
|
1628
1629
|
ctx,
|
|
1629
1630
|
SCRIPTS.singleRegion.slidingWindow.getRemaining,
|
|
1630
|
-
[currentKey, previousKey,
|
|
1631
|
+
[currentKey, previousKey, ...dynamicLimitKeys],
|
|
1631
1632
|
[tokens, now, windowSize]
|
|
1632
1633
|
);
|
|
1633
1634
|
return {
|
|
@@ -1682,11 +1683,11 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1682
1683
|
};
|
|
1683
1684
|
}
|
|
1684
1685
|
}
|
|
1685
|
-
const
|
|
1686
|
+
const dynamicLimitKeys = ctx.dynamicLimits ? [`${ctx.prefix}${DYNAMIC_LIMIT_KEY_SUFFIX}`] : [];
|
|
1686
1687
|
const [remaining, reset, effectiveLimit] = await safeEval(
|
|
1687
1688
|
ctx,
|
|
1688
1689
|
SCRIPTS.singleRegion.tokenBucket.limit,
|
|
1689
|
-
[identifier,
|
|
1690
|
+
[identifier, ...dynamicLimitKeys],
|
|
1690
1691
|
[maxTokens, intervalDuration, refillRate, now, incrementBy]
|
|
1691
1692
|
);
|
|
1692
1693
|
const success = remaining >= 0;
|
|
@@ -1706,11 +1707,11 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1706
1707
|
};
|
|
1707
1708
|
},
|
|
1708
1709
|
async getRemaining(ctx, identifier) {
|
|
1709
|
-
const
|
|
1710
|
+
const dynamicLimitKeys = ctx.dynamicLimits ? [`${ctx.prefix}${DYNAMIC_LIMIT_KEY_SUFFIX}`] : [];
|
|
1710
1711
|
const [remainingTokens, refilledAt, effectiveLimit] = await safeEval(
|
|
1711
1712
|
ctx,
|
|
1712
1713
|
SCRIPTS.singleRegion.tokenBucket.getRemaining,
|
|
1713
|
-
[identifier,
|
|
1714
|
+
[identifier, ...dynamicLimitKeys],
|
|
1714
1715
|
[maxTokens]
|
|
1715
1716
|
);
|
|
1716
1717
|
const freshRefillAt = Date.now() + intervalDuration;
|
|
@@ -1778,7 +1779,7 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1778
1779
|
const hit = typeof ctx.cache.get(key) === "number";
|
|
1779
1780
|
if (hit) {
|
|
1780
1781
|
const cachedTokensAfterUpdate = ctx.cache.incr(key, incrementBy);
|
|
1781
|
-
const success = cachedTokensAfterUpdate
|
|
1782
|
+
const success = cachedTokensAfterUpdate <= tokens;
|
|
1782
1783
|
const pending = success ? safeEval(
|
|
1783
1784
|
ctx,
|
|
1784
1785
|
SCRIPTS.singleRegion.cachedFixedWindow.limit,
|