@upstash/ratelimit 2.0.8 → 2.1.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/README.md +19 -0
- package/dist/index.d.mts +22 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +67 -42
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +67 -42
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -156,7 +156,7 @@ var safeEval = async (ctx, script, keys, args) => {
|
|
|
156
156
|
};
|
|
157
157
|
|
|
158
158
|
// src/lua-scripts/single.ts
|
|
159
|
-
var fixedWindowLimitScript =
|
|
159
|
+
var fixedWindowLimitScript = `#!lua flags=allow-key-locking
|
|
160
160
|
local key = KEYS[1]
|
|
161
161
|
local dynamicLimitKey = KEYS[2] -- optional: key for dynamic limit in redis
|
|
162
162
|
local tokens = tonumber(ARGV[1]) -- default limit
|
|
@@ -165,7 +165,7 @@ var fixedWindowLimitScript = `
|
|
|
165
165
|
|
|
166
166
|
-- Check for dynamic limit
|
|
167
167
|
local effectiveLimit = tokens
|
|
168
|
-
if dynamicLimitKey ~= "" then
|
|
168
|
+
if dynamicLimitKey and dynamicLimitKey ~= "" then
|
|
169
169
|
local dynamicLimit = redis.call("GET", dynamicLimitKey)
|
|
170
170
|
if dynamicLimit then
|
|
171
171
|
effectiveLimit = tonumber(dynamicLimit)
|
|
@@ -181,14 +181,14 @@ var fixedWindowLimitScript = `
|
|
|
181
181
|
|
|
182
182
|
return {r, effectiveLimit}
|
|
183
183
|
`;
|
|
184
|
-
var fixedWindowRemainingTokensScript =
|
|
184
|
+
var fixedWindowRemainingTokensScript = `#!lua flags=allow-key-locking
|
|
185
185
|
local key = KEYS[1]
|
|
186
186
|
local dynamicLimitKey = KEYS[2] -- optional: key for dynamic limit in redis
|
|
187
187
|
local tokens = tonumber(ARGV[1]) -- default limit
|
|
188
188
|
|
|
189
189
|
-- Check for dynamic limit
|
|
190
190
|
local effectiveLimit = tokens
|
|
191
|
-
if dynamicLimitKey ~= "" then
|
|
191
|
+
if dynamicLimitKey and dynamicLimitKey ~= "" then
|
|
192
192
|
local dynamicLimit = redis.call("GET", dynamicLimitKey)
|
|
193
193
|
if dynamicLimit then
|
|
194
194
|
effectiveLimit = tonumber(dynamicLimit)
|
|
@@ -203,7 +203,7 @@ var fixedWindowRemainingTokensScript = `
|
|
|
203
203
|
|
|
204
204
|
return {effectiveLimit - usedTokens, effectiveLimit}
|
|
205
205
|
`;
|
|
206
|
-
var slidingWindowLimitScript =
|
|
206
|
+
var slidingWindowLimitScript = `#!lua flags=allow-key-locking
|
|
207
207
|
local currentKey = KEYS[1] -- identifier including prefixes
|
|
208
208
|
local previousKey = KEYS[2] -- key of the previous bucket
|
|
209
209
|
local dynamicLimitKey = KEYS[3] -- optional: key for dynamic limit in redis
|
|
@@ -214,7 +214,7 @@ var slidingWindowLimitScript = `
|
|
|
214
214
|
|
|
215
215
|
-- Check for dynamic limit
|
|
216
216
|
local effectiveLimit = tokens
|
|
217
|
-
if dynamicLimitKey ~= "" then
|
|
217
|
+
if dynamicLimitKey and dynamicLimitKey ~= "" then
|
|
218
218
|
local dynamicLimit = redis.call("GET", dynamicLimitKey)
|
|
219
219
|
if dynamicLimit then
|
|
220
220
|
effectiveLimit = tonumber(dynamicLimit)
|
|
@@ -247,7 +247,7 @@ var slidingWindowLimitScript = `
|
|
|
247
247
|
end
|
|
248
248
|
return {effectiveLimit - ( newValue + requestsInPreviousWindow ), effectiveLimit}
|
|
249
249
|
`;
|
|
250
|
-
var slidingWindowRemainingTokensScript =
|
|
250
|
+
var slidingWindowRemainingTokensScript = `#!lua flags=allow-key-locking
|
|
251
251
|
local currentKey = KEYS[1] -- identifier including prefixes
|
|
252
252
|
local previousKey = KEYS[2] -- key of the previous bucket
|
|
253
253
|
local dynamicLimitKey = KEYS[3] -- optional: key for dynamic limit in redis
|
|
@@ -257,7 +257,7 @@ var slidingWindowRemainingTokensScript = `
|
|
|
257
257
|
|
|
258
258
|
-- Check for dynamic limit
|
|
259
259
|
local effectiveLimit = tokens
|
|
260
|
-
if dynamicLimitKey ~= "" then
|
|
260
|
+
if dynamicLimitKey and dynamicLimitKey ~= "" then
|
|
261
261
|
local dynamicLimit = redis.call("GET", dynamicLimitKey)
|
|
262
262
|
if dynamicLimit then
|
|
263
263
|
effectiveLimit = tonumber(dynamicLimit)
|
|
@@ -281,7 +281,7 @@ var slidingWindowRemainingTokensScript = `
|
|
|
281
281
|
local usedTokens = requestsInPreviousWindow + requestsInCurrentWindow
|
|
282
282
|
return {effectiveLimit - usedTokens, effectiveLimit}
|
|
283
283
|
`;
|
|
284
|
-
var tokenBucketLimitScript =
|
|
284
|
+
var tokenBucketLimitScript = `#!lua flags=allow-key-locking
|
|
285
285
|
local key = KEYS[1] -- identifier including prefixes
|
|
286
286
|
local dynamicLimitKey = KEYS[2] -- optional: key for dynamic limit in redis
|
|
287
287
|
local maxTokens = tonumber(ARGV[1]) -- default maximum number of tokens
|
|
@@ -292,7 +292,7 @@ var tokenBucketLimitScript = `
|
|
|
292
292
|
|
|
293
293
|
-- Check for dynamic limit
|
|
294
294
|
local effectiveLimit = maxTokens
|
|
295
|
-
if dynamicLimitKey ~= "" then
|
|
295
|
+
if dynamicLimitKey and dynamicLimitKey ~= "" then
|
|
296
296
|
local dynamicLimit = redis.call("GET", dynamicLimitKey)
|
|
297
297
|
if dynamicLimit then
|
|
298
298
|
effectiveLimit = tonumber(dynamicLimit)
|
|
@@ -335,14 +335,14 @@ var tokenBucketLimitScript = `
|
|
|
335
335
|
return {remaining, refilledAt + interval, effectiveLimit}
|
|
336
336
|
`;
|
|
337
337
|
var tokenBucketIdentifierNotFound = -1;
|
|
338
|
-
var tokenBucketRemainingTokensScript =
|
|
338
|
+
var tokenBucketRemainingTokensScript = `#!lua flags=allow-key-locking
|
|
339
339
|
local key = KEYS[1]
|
|
340
340
|
local dynamicLimitKey = KEYS[2] -- optional: key for dynamic limit in redis
|
|
341
341
|
local maxTokens = tonumber(ARGV[1]) -- default maximum number of tokens
|
|
342
342
|
|
|
343
343
|
-- Check for dynamic limit
|
|
344
344
|
local effectiveLimit = maxTokens
|
|
345
|
-
if dynamicLimitKey ~= "" then
|
|
345
|
+
if dynamicLimitKey and dynamicLimitKey ~= "" then
|
|
346
346
|
local dynamicLimit = redis.call("GET", dynamicLimitKey)
|
|
347
347
|
if dynamicLimit then
|
|
348
348
|
effectiveLimit = tonumber(dynamicLimit)
|
|
@@ -357,7 +357,7 @@ var tokenBucketRemainingTokensScript = `
|
|
|
357
357
|
|
|
358
358
|
return {tonumber(bucket[2]), tonumber(bucket[1]), effectiveLimit}
|
|
359
359
|
`;
|
|
360
|
-
var cachedFixedWindowLimitScript =
|
|
360
|
+
var cachedFixedWindowLimitScript = `#!lua flags=allow-key-locking
|
|
361
361
|
local key = KEYS[1]
|
|
362
362
|
local window = ARGV[1]
|
|
363
363
|
local incrementBy = ARGV[2] -- increment rate per request at a given value, default is 1
|
|
@@ -371,7 +371,7 @@ var cachedFixedWindowLimitScript = `
|
|
|
371
371
|
|
|
372
372
|
return r
|
|
373
373
|
`;
|
|
374
|
-
var cachedFixedWindowRemainingTokenScript =
|
|
374
|
+
var cachedFixedWindowRemainingTokenScript = `#!lua flags=allow-key-locking
|
|
375
375
|
local key = KEYS[1]
|
|
376
376
|
local tokens = 0
|
|
377
377
|
|
|
@@ -383,7 +383,7 @@ var cachedFixedWindowRemainingTokenScript = `
|
|
|
383
383
|
`;
|
|
384
384
|
|
|
385
385
|
// src/lua-scripts/multi.ts
|
|
386
|
-
var fixedWindowLimitScript2 =
|
|
386
|
+
var fixedWindowLimitScript2 = `#!lua flags=allow-key-locking
|
|
387
387
|
local key = KEYS[1]
|
|
388
388
|
local id = ARGV[1]
|
|
389
389
|
local window = ARGV[2]
|
|
@@ -399,7 +399,7 @@ var fixedWindowLimitScript2 = `
|
|
|
399
399
|
|
|
400
400
|
return fields
|
|
401
401
|
`;
|
|
402
|
-
var fixedWindowRemainingTokensScript2 =
|
|
402
|
+
var fixedWindowRemainingTokensScript2 = `#!lua flags=allow-key-locking
|
|
403
403
|
local key = KEYS[1]
|
|
404
404
|
local tokens = 0
|
|
405
405
|
|
|
@@ -407,7 +407,7 @@ var fixedWindowRemainingTokensScript2 = `
|
|
|
407
407
|
|
|
408
408
|
return fields
|
|
409
409
|
`;
|
|
410
|
-
var slidingWindowLimitScript2 =
|
|
410
|
+
var slidingWindowLimitScript2 = `#!lua flags=allow-key-locking
|
|
411
411
|
local currentKey = KEYS[1] -- identifier including prefixes
|
|
412
412
|
local previousKey = KEYS[2] -- key of the previous bucket
|
|
413
413
|
local tokens = tonumber(ARGV[1]) -- tokens per window
|
|
@@ -444,7 +444,7 @@ var slidingWindowLimitScript2 = `
|
|
|
444
444
|
end
|
|
445
445
|
return {currentFields, previousFields, true}
|
|
446
446
|
`;
|
|
447
|
-
var slidingWindowRemainingTokensScript2 =
|
|
447
|
+
var slidingWindowRemainingTokensScript2 = `#!lua flags=allow-key-locking
|
|
448
448
|
local currentKey = KEYS[1] -- identifier including prefixes
|
|
449
449
|
local previousKey = KEYS[2] -- key of the previous bucket
|
|
450
450
|
local now = ARGV[1] -- current timestamp in milliseconds
|
|
@@ -499,41 +499,41 @@ var SCRIPTS = {
|
|
|
499
499
|
fixedWindow: {
|
|
500
500
|
limit: {
|
|
501
501
|
script: fixedWindowLimitScript,
|
|
502
|
-
hash: "
|
|
502
|
+
hash: "b2efc02798c8e9b471a10f9b1d5745db89923ff3"
|
|
503
503
|
},
|
|
504
504
|
getRemaining: {
|
|
505
505
|
script: fixedWindowRemainingTokensScript,
|
|
506
|
-
hash: "
|
|
506
|
+
hash: "823cb2a5a902d69681244f5e28e5ebb8f2a89edd"
|
|
507
507
|
}
|
|
508
508
|
},
|
|
509
509
|
slidingWindow: {
|
|
510
510
|
limit: {
|
|
511
511
|
script: slidingWindowLimitScript,
|
|
512
|
-
hash: "
|
|
512
|
+
hash: "74740bb3d7792848093cdef586b6b6037c455fd6"
|
|
513
513
|
},
|
|
514
514
|
getRemaining: {
|
|
515
515
|
script: slidingWindowRemainingTokensScript,
|
|
516
|
-
hash: "
|
|
516
|
+
hash: "7ca36169e0fc9caedd81b4eef8e99854c157f9b5"
|
|
517
517
|
}
|
|
518
518
|
},
|
|
519
519
|
tokenBucket: {
|
|
520
520
|
limit: {
|
|
521
521
|
script: tokenBucketLimitScript,
|
|
522
|
-
hash: "
|
|
522
|
+
hash: "a9873a8026b509e99a49a0e5a2049756c693c384"
|
|
523
523
|
},
|
|
524
524
|
getRemaining: {
|
|
525
525
|
script: tokenBucketRemainingTokensScript,
|
|
526
|
-
hash: "
|
|
526
|
+
hash: "40daf4a441df736f5c99f8b77bbe176c909e5de4"
|
|
527
527
|
}
|
|
528
528
|
},
|
|
529
529
|
cachedFixedWindow: {
|
|
530
530
|
limit: {
|
|
531
531
|
script: cachedFixedWindowLimitScript,
|
|
532
|
-
hash: "
|
|
532
|
+
hash: "1861a700bafe96c833a483af6b1c28a8897cfdb0"
|
|
533
533
|
},
|
|
534
534
|
getRemaining: {
|
|
535
535
|
script: cachedFixedWindowRemainingTokenScript,
|
|
536
|
-
hash: "
|
|
536
|
+
hash: "eb82f0e853d2fc9a236fa9bfbe704fda6ac2fc36"
|
|
537
537
|
}
|
|
538
538
|
}
|
|
539
539
|
},
|
|
@@ -541,21 +541,21 @@ var SCRIPTS = {
|
|
|
541
541
|
fixedWindow: {
|
|
542
542
|
limit: {
|
|
543
543
|
script: fixedWindowLimitScript2,
|
|
544
|
-
hash: "
|
|
544
|
+
hash: "e04b753a75909b7f99aae4c04cf8869a8de02a9e"
|
|
545
545
|
},
|
|
546
546
|
getRemaining: {
|
|
547
547
|
script: fixedWindowRemainingTokensScript2,
|
|
548
|
-
hash: "
|
|
548
|
+
hash: "e066c8ce3eaca142b0894ad1541e0a58c9924819"
|
|
549
549
|
}
|
|
550
550
|
},
|
|
551
551
|
slidingWindow: {
|
|
552
552
|
limit: {
|
|
553
553
|
script: slidingWindowLimitScript2,
|
|
554
|
-
hash: "
|
|
554
|
+
hash: "aeb4d8f381e8dafc8e69041baae1324d254ded44"
|
|
555
555
|
},
|
|
556
556
|
getRemaining: {
|
|
557
557
|
script: slidingWindowRemainingTokensScript2,
|
|
558
|
-
hash: "
|
|
558
|
+
hash: "87828c1088f6a8d1f512a434ea330189982a1c0a"
|
|
559
559
|
}
|
|
560
560
|
}
|
|
561
561
|
}
|
|
@@ -1036,6 +1036,27 @@ var Ratelimit = class {
|
|
|
1036
1036
|
};
|
|
1037
1037
|
};
|
|
1038
1038
|
|
|
1039
|
+
// src/version.ts
|
|
1040
|
+
var VERSION = "2.1.0";
|
|
1041
|
+
|
|
1042
|
+
// src/telemetry.ts
|
|
1043
|
+
var taggedClients = /* @__PURE__ */ new WeakSet();
|
|
1044
|
+
var getSafeEnv = () => typeof process === "object" && process && typeof process.env === "object" ? process.env : {};
|
|
1045
|
+
var addTelemetry = (redis, enableTelemetry = true) => {
|
|
1046
|
+
if (!enableTelemetry || getSafeEnv().UPSTASH_DISABLE_TELEMETRY)
|
|
1047
|
+
return;
|
|
1048
|
+
if (!redis || typeof redis !== "object")
|
|
1049
|
+
return;
|
|
1050
|
+
if (taggedClients.has(redis))
|
|
1051
|
+
return;
|
|
1052
|
+
taggedClients.add(redis);
|
|
1053
|
+
try {
|
|
1054
|
+
const client = redis;
|
|
1055
|
+
client.addTelemetry?.({ sdk: `@upstash/ratelimit@${VERSION}` });
|
|
1056
|
+
} catch {
|
|
1057
|
+
}
|
|
1058
|
+
};
|
|
1059
|
+
|
|
1039
1060
|
// src/multi.ts
|
|
1040
1061
|
function randomId() {
|
|
1041
1062
|
let result = "";
|
|
@@ -1065,6 +1086,9 @@ var MultiRegionRatelimit = class extends Ratelimit {
|
|
|
1065
1086
|
cache: config.ephemeralCache ? new Cache(config.ephemeralCache) : void 0
|
|
1066
1087
|
}
|
|
1067
1088
|
});
|
|
1089
|
+
for (const redis of config.redis) {
|
|
1090
|
+
addTelemetry(redis, config.enableTelemetry);
|
|
1091
|
+
}
|
|
1068
1092
|
if (config.dynamicLimits) {
|
|
1069
1093
|
console.warn(
|
|
1070
1094
|
"Warning: Dynamic limits are not yet supported for multi-region rate limiters. The dynamicLimits option will be ignored."
|
|
@@ -1435,6 +1459,7 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1435
1459
|
denyListThreshold: config.denyListThreshold,
|
|
1436
1460
|
dynamicLimits: config.dynamicLimits
|
|
1437
1461
|
});
|
|
1462
|
+
addTelemetry(config.redis, config.enableTelemetry);
|
|
1438
1463
|
}
|
|
1439
1464
|
/**
|
|
1440
1465
|
* Each request inside a fixed time increases a counter.
|
|
@@ -1474,11 +1499,11 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1474
1499
|
};
|
|
1475
1500
|
}
|
|
1476
1501
|
}
|
|
1477
|
-
const
|
|
1502
|
+
const dynamicLimitKeys = ctx.dynamicLimits ? [`${ctx.prefix}${DYNAMIC_LIMIT_KEY_SUFFIX}`] : [];
|
|
1478
1503
|
const [usedTokensAfterUpdate, effectiveLimit] = await safeEval(
|
|
1479
1504
|
ctx,
|
|
1480
1505
|
SCRIPTS.singleRegion.fixedWindow.limit,
|
|
1481
|
-
[key,
|
|
1506
|
+
[key, ...dynamicLimitKeys],
|
|
1482
1507
|
[tokens, windowDuration, incrementBy]
|
|
1483
1508
|
);
|
|
1484
1509
|
const success = usedTokensAfterUpdate <= effectiveLimit;
|
|
@@ -1502,11 +1527,11 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1502
1527
|
async getRemaining(ctx, identifier) {
|
|
1503
1528
|
const bucket = Math.floor(Date.now() / windowDuration);
|
|
1504
1529
|
const key = [identifier, bucket].join(":");
|
|
1505
|
-
const
|
|
1530
|
+
const dynamicLimitKeys = ctx.dynamicLimits ? [`${ctx.prefix}${DYNAMIC_LIMIT_KEY_SUFFIX}`] : [];
|
|
1506
1531
|
const [remaining, effectiveLimit] = await safeEval(
|
|
1507
1532
|
ctx,
|
|
1508
1533
|
SCRIPTS.singleRegion.fixedWindow.getRemaining,
|
|
1509
|
-
[key,
|
|
1534
|
+
[key, ...dynamicLimitKeys],
|
|
1510
1535
|
[tokens]
|
|
1511
1536
|
);
|
|
1512
1537
|
return {
|
|
@@ -1568,11 +1593,11 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1568
1593
|
};
|
|
1569
1594
|
}
|
|
1570
1595
|
}
|
|
1571
|
-
const
|
|
1596
|
+
const dynamicLimitKeys = ctx.dynamicLimits ? [`${ctx.prefix}${DYNAMIC_LIMIT_KEY_SUFFIX}`] : [];
|
|
1572
1597
|
const [remainingTokens, effectiveLimit] = await safeEval(
|
|
1573
1598
|
ctx,
|
|
1574
1599
|
SCRIPTS.singleRegion.slidingWindow.limit,
|
|
1575
|
-
[currentKey, previousKey,
|
|
1600
|
+
[currentKey, previousKey, ...dynamicLimitKeys],
|
|
1576
1601
|
[tokens, now, windowSize, incrementBy]
|
|
1577
1602
|
);
|
|
1578
1603
|
const success = remainingTokens >= 0;
|
|
@@ -1598,11 +1623,11 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1598
1623
|
const currentKey = [identifier, currentWindow].join(":");
|
|
1599
1624
|
const previousWindow = currentWindow - 1;
|
|
1600
1625
|
const previousKey = [identifier, previousWindow].join(":");
|
|
1601
|
-
const
|
|
1626
|
+
const dynamicLimitKeys = ctx.dynamicLimits ? [`${ctx.prefix}${DYNAMIC_LIMIT_KEY_SUFFIX}`] : [];
|
|
1602
1627
|
const [remaining, effectiveLimit] = await safeEval(
|
|
1603
1628
|
ctx,
|
|
1604
1629
|
SCRIPTS.singleRegion.slidingWindow.getRemaining,
|
|
1605
|
-
[currentKey, previousKey,
|
|
1630
|
+
[currentKey, previousKey, ...dynamicLimitKeys],
|
|
1606
1631
|
[tokens, now, windowSize]
|
|
1607
1632
|
);
|
|
1608
1633
|
return {
|
|
@@ -1657,11 +1682,11 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1657
1682
|
};
|
|
1658
1683
|
}
|
|
1659
1684
|
}
|
|
1660
|
-
const
|
|
1685
|
+
const dynamicLimitKeys = ctx.dynamicLimits ? [`${ctx.prefix}${DYNAMIC_LIMIT_KEY_SUFFIX}`] : [];
|
|
1661
1686
|
const [remaining, reset, effectiveLimit] = await safeEval(
|
|
1662
1687
|
ctx,
|
|
1663
1688
|
SCRIPTS.singleRegion.tokenBucket.limit,
|
|
1664
|
-
[identifier,
|
|
1689
|
+
[identifier, ...dynamicLimitKeys],
|
|
1665
1690
|
[maxTokens, intervalDuration, refillRate, now, incrementBy]
|
|
1666
1691
|
);
|
|
1667
1692
|
const success = remaining >= 0;
|
|
@@ -1681,11 +1706,11 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1681
1706
|
};
|
|
1682
1707
|
},
|
|
1683
1708
|
async getRemaining(ctx, identifier) {
|
|
1684
|
-
const
|
|
1709
|
+
const dynamicLimitKeys = ctx.dynamicLimits ? [`${ctx.prefix}${DYNAMIC_LIMIT_KEY_SUFFIX}`] : [];
|
|
1685
1710
|
const [remainingTokens, refilledAt, effectiveLimit] = await safeEval(
|
|
1686
1711
|
ctx,
|
|
1687
1712
|
SCRIPTS.singleRegion.tokenBucket.getRemaining,
|
|
1688
|
-
[identifier,
|
|
1713
|
+
[identifier, ...dynamicLimitKeys],
|
|
1689
1714
|
[maxTokens]
|
|
1690
1715
|
);
|
|
1691
1716
|
const freshRefillAt = Date.now() + intervalDuration;
|