@upstash/ratelimit 1.2.1 → 2.0.1-canary
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 +2 -1
- package/dist/index.d.mts +17 -3
- package/dist/index.d.ts +17 -3
- package/dist/index.js +46 -13
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +46 -13
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/@upstash/ratelimit)
|
|
4
4
|
[](https://github.com/upstash/ratelimit/actions/workflows/tests.yaml)
|
|
5
5
|
|
|
6
|
-
> [!NOTE]
|
|
6
|
+
> [!NOTE]
|
|
7
|
+
> **This project is in GA Stage.**
|
|
7
8
|
> The Upstash Professional Support fully covers this project. It receives regular updates, and bug fixes. The Upstash team is committed to maintaining and improving its functionality.
|
|
8
9
|
|
|
9
10
|
It is the only connectionless (HTTP based) rate limiting library and designed
|
package/dist/index.d.mts
CHANGED
|
@@ -90,7 +90,10 @@ type Algorithm<TContext> = () => {
|
|
|
90
90
|
limit: (ctx: TContext, identifier: string, rate?: number, opts?: {
|
|
91
91
|
cache?: EphemeralCache;
|
|
92
92
|
}) => Promise<RatelimitResponse>;
|
|
93
|
-
getRemaining: (ctx: TContext, identifier: string) => Promise<
|
|
93
|
+
getRemaining: (ctx: TContext, identifier: string) => Promise<{
|
|
94
|
+
remaining: number;
|
|
95
|
+
reset: number;
|
|
96
|
+
}>;
|
|
94
97
|
resetTokens: (ctx: TContext, identifier: string) => Promise<void>;
|
|
95
98
|
};
|
|
96
99
|
type IsDenied = 0 | 1;
|
|
@@ -163,7 +166,7 @@ declare class Analytics {
|
|
|
163
166
|
blocked: number;
|
|
164
167
|
}>>;
|
|
165
168
|
getUsageOverTime<TFilter extends keyof Omit<Event, "time">>(timestampCount: number, groupby: TFilter): Promise<Aggregate[]>;
|
|
166
|
-
getMostAllowedBlocked(timestampCount: number, getTop?: number): Promise<{
|
|
169
|
+
getMostAllowedBlocked(timestampCount: number, getTop?: number, checkAtMost?: number): Promise<{
|
|
167
170
|
allowed: {
|
|
168
171
|
identifier: string;
|
|
169
172
|
count: number;
|
|
@@ -328,7 +331,18 @@ declare abstract class Ratelimit<TContext extends Context> {
|
|
|
328
331
|
*/
|
|
329
332
|
blockUntilReady: (identifier: string, timeout: number) => Promise<RatelimitResponse>;
|
|
330
333
|
resetUsedTokens: (identifier: string) => Promise<void>;
|
|
331
|
-
|
|
334
|
+
/**
|
|
335
|
+
* Returns the remaining token count together with a reset timestamps
|
|
336
|
+
*
|
|
337
|
+
* @param identifier identifir to check
|
|
338
|
+
* @returns object with `remaining` and reset fields. `remaining` denotes
|
|
339
|
+
* the remaining tokens and reset denotes the timestamp when the
|
|
340
|
+
* tokens reset.
|
|
341
|
+
*/
|
|
342
|
+
getRemaining: (identifier: string) => Promise<{
|
|
343
|
+
remaining: number;
|
|
344
|
+
reset: number;
|
|
345
|
+
}>;
|
|
332
346
|
/**
|
|
333
347
|
* Checks if the identifier or the values in req are in the deny list cache.
|
|
334
348
|
* If so, returns the default denied response.
|
package/dist/index.d.ts
CHANGED
|
@@ -90,7 +90,10 @@ type Algorithm<TContext> = () => {
|
|
|
90
90
|
limit: (ctx: TContext, identifier: string, rate?: number, opts?: {
|
|
91
91
|
cache?: EphemeralCache;
|
|
92
92
|
}) => Promise<RatelimitResponse>;
|
|
93
|
-
getRemaining: (ctx: TContext, identifier: string) => Promise<
|
|
93
|
+
getRemaining: (ctx: TContext, identifier: string) => Promise<{
|
|
94
|
+
remaining: number;
|
|
95
|
+
reset: number;
|
|
96
|
+
}>;
|
|
94
97
|
resetTokens: (ctx: TContext, identifier: string) => Promise<void>;
|
|
95
98
|
};
|
|
96
99
|
type IsDenied = 0 | 1;
|
|
@@ -163,7 +166,7 @@ declare class Analytics {
|
|
|
163
166
|
blocked: number;
|
|
164
167
|
}>>;
|
|
165
168
|
getUsageOverTime<TFilter extends keyof Omit<Event, "time">>(timestampCount: number, groupby: TFilter): Promise<Aggregate[]>;
|
|
166
|
-
getMostAllowedBlocked(timestampCount: number, getTop?: number): Promise<{
|
|
169
|
+
getMostAllowedBlocked(timestampCount: number, getTop?: number, checkAtMost?: number): Promise<{
|
|
167
170
|
allowed: {
|
|
168
171
|
identifier: string;
|
|
169
172
|
count: number;
|
|
@@ -328,7 +331,18 @@ declare abstract class Ratelimit<TContext extends Context> {
|
|
|
328
331
|
*/
|
|
329
332
|
blockUntilReady: (identifier: string, timeout: number) => Promise<RatelimitResponse>;
|
|
330
333
|
resetUsedTokens: (identifier: string) => Promise<void>;
|
|
331
|
-
|
|
334
|
+
/**
|
|
335
|
+
* Returns the remaining token count together with a reset timestamps
|
|
336
|
+
*
|
|
337
|
+
* @param identifier identifir to check
|
|
338
|
+
* @returns object with `remaining` and reset fields. `remaining` denotes
|
|
339
|
+
* the remaining tokens and reset denotes the timestamp when the
|
|
340
|
+
* tokens reset.
|
|
341
|
+
*/
|
|
342
|
+
getRemaining: (identifier: string) => Promise<{
|
|
343
|
+
remaining: number;
|
|
344
|
+
reset: number;
|
|
345
|
+
}>;
|
|
332
346
|
/**
|
|
333
347
|
* Checks if the identifier or the values in req are in the deny list cache.
|
|
334
348
|
* If so, returns the default denied response.
|
package/dist/index.js
CHANGED
|
@@ -79,9 +79,10 @@ var Analytics = class {
|
|
|
79
79
|
const result = await this.analytics.aggregateBucketsWithPipeline(this.table, groupby, timestampCount);
|
|
80
80
|
return result;
|
|
81
81
|
}
|
|
82
|
-
async getMostAllowedBlocked(timestampCount, getTop) {
|
|
82
|
+
async getMostAllowedBlocked(timestampCount, getTop, checkAtMost) {
|
|
83
83
|
getTop = getTop ?? 5;
|
|
84
|
-
|
|
84
|
+
const timestamp = void 0;
|
|
85
|
+
return this.analytics.getMostAllowedBlocked(this.table, timestampCount, getTop, timestamp, checkAtMost);
|
|
85
86
|
}
|
|
86
87
|
};
|
|
87
88
|
|
|
@@ -583,6 +584,14 @@ var Ratelimit = class {
|
|
|
583
584
|
const pattern = [this.prefix, identifier].join(":");
|
|
584
585
|
await this.limiter().resetTokens(this.ctx, pattern);
|
|
585
586
|
};
|
|
587
|
+
/**
|
|
588
|
+
* Returns the remaining token count together with a reset timestamps
|
|
589
|
+
*
|
|
590
|
+
* @param identifier identifir to check
|
|
591
|
+
* @returns object with `remaining` and reset fields. `remaining` denotes
|
|
592
|
+
* the remaining tokens and reset denotes the timestamp when the
|
|
593
|
+
* tokens reset.
|
|
594
|
+
*/
|
|
586
595
|
getRemaining = async (identifier) => {
|
|
587
596
|
const pattern = [this.prefix, identifier].join(":");
|
|
588
597
|
return await this.limiter().getRemaining(this.ctx, pattern);
|
|
@@ -867,7 +876,10 @@ var MultiRegionRatelimit = class extends Ratelimit {
|
|
|
867
876
|
}
|
|
868
877
|
return accTokens + parsedToken;
|
|
869
878
|
}, 0);
|
|
870
|
-
return
|
|
879
|
+
return {
|
|
880
|
+
remaining: Math.max(0, tokens - usedTokens),
|
|
881
|
+
reset: (bucket + 1) * windowDuration
|
|
882
|
+
};
|
|
871
883
|
},
|
|
872
884
|
async resetTokens(ctx, identifier) {
|
|
873
885
|
const pattern = [identifier, "*"].join(":");
|
|
@@ -1029,7 +1041,10 @@ var MultiRegionRatelimit = class extends Ratelimit {
|
|
|
1029
1041
|
)
|
|
1030
1042
|
}));
|
|
1031
1043
|
const usedTokens = await Promise.any(dbs.map((s) => s.request));
|
|
1032
|
-
return
|
|
1044
|
+
return {
|
|
1045
|
+
remaining: Math.max(0, tokens - usedTokens),
|
|
1046
|
+
reset: (currentWindow + 1) * windowSize
|
|
1047
|
+
};
|
|
1033
1048
|
},
|
|
1034
1049
|
async resetTokens(ctx, identifier) {
|
|
1035
1050
|
const pattern = [identifier, "*"].join(":");
|
|
@@ -1168,17 +1183,18 @@ var tokenBucketLimitScript = `
|
|
|
1168
1183
|
redis.call("PEXPIRE", key, expireAt)
|
|
1169
1184
|
return {remaining, refilledAt + interval}
|
|
1170
1185
|
`;
|
|
1186
|
+
var tokenBucketIdentifierNotFound = -1;
|
|
1171
1187
|
var tokenBucketRemainingTokensScript = `
|
|
1172
1188
|
local key = KEYS[1]
|
|
1173
1189
|
local maxTokens = tonumber(ARGV[1])
|
|
1174
1190
|
|
|
1175
|
-
local bucket = redis.call("HMGET", key, "tokens")
|
|
1191
|
+
local bucket = redis.call("HMGET", key, "refilledAt", "tokens")
|
|
1176
1192
|
|
|
1177
1193
|
if bucket[1] == false then
|
|
1178
|
-
return maxTokens
|
|
1194
|
+
return {maxTokens, ${tokenBucketIdentifierNotFound}}
|
|
1179
1195
|
end
|
|
1180
1196
|
|
|
1181
|
-
return tonumber(bucket[1])
|
|
1197
|
+
return {tonumber(bucket[2]), tonumber(bucket[1])}
|
|
1182
1198
|
`;
|
|
1183
1199
|
var cachedFixedWindowLimitScript = `
|
|
1184
1200
|
local key = KEYS[1]
|
|
@@ -1295,7 +1311,10 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1295
1311
|
[key],
|
|
1296
1312
|
[null]
|
|
1297
1313
|
);
|
|
1298
|
-
return
|
|
1314
|
+
return {
|
|
1315
|
+
remaining: Math.max(0, tokens - usedTokens),
|
|
1316
|
+
reset: (bucket + 1) * windowDuration
|
|
1317
|
+
};
|
|
1299
1318
|
},
|
|
1300
1319
|
async resetTokens(ctx, identifier) {
|
|
1301
1320
|
const pattern = [identifier, "*"].join(":");
|
|
@@ -1384,7 +1403,10 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1384
1403
|
[currentKey, previousKey],
|
|
1385
1404
|
[now, windowSize]
|
|
1386
1405
|
);
|
|
1387
|
-
return
|
|
1406
|
+
return {
|
|
1407
|
+
remaining: Math.max(0, tokens - usedTokens),
|
|
1408
|
+
reset: (currentWindow + 1) * windowSize
|
|
1409
|
+
};
|
|
1388
1410
|
},
|
|
1389
1411
|
async resetTokens(ctx, identifier) {
|
|
1390
1412
|
const pattern = [identifier, "*"].join(":");
|
|
@@ -1453,14 +1475,19 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1453
1475
|
};
|
|
1454
1476
|
},
|
|
1455
1477
|
async getRemaining(ctx, identifier) {
|
|
1456
|
-
const remainingTokens = await safeEval(
|
|
1478
|
+
const [remainingTokens, refilledAt] = await safeEval(
|
|
1457
1479
|
ctx,
|
|
1458
1480
|
tokenBucketRemainingTokensScript,
|
|
1459
1481
|
"getRemainingHash",
|
|
1460
1482
|
[identifier],
|
|
1461
1483
|
[maxTokens]
|
|
1462
1484
|
);
|
|
1463
|
-
|
|
1485
|
+
const freshRefillAt = Date.now() + intervalDuration;
|
|
1486
|
+
const identifierRefillsAt = refilledAt + intervalDuration;
|
|
1487
|
+
return {
|
|
1488
|
+
remaining: remainingTokens,
|
|
1489
|
+
reset: refilledAt === tokenBucketIdentifierNotFound ? freshRefillAt : identifierRefillsAt
|
|
1490
|
+
};
|
|
1464
1491
|
},
|
|
1465
1492
|
async resetTokens(ctx, identifier) {
|
|
1466
1493
|
const pattern = identifier;
|
|
@@ -1557,7 +1584,10 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1557
1584
|
const hit = typeof ctx.cache.get(key) === "number";
|
|
1558
1585
|
if (hit) {
|
|
1559
1586
|
const cachedUsedTokens = ctx.cache.get(key) ?? 0;
|
|
1560
|
-
return
|
|
1587
|
+
return {
|
|
1588
|
+
remaining: Math.max(0, tokens - cachedUsedTokens),
|
|
1589
|
+
reset: (bucket + 1) * windowDuration
|
|
1590
|
+
};
|
|
1561
1591
|
}
|
|
1562
1592
|
const usedTokens = await safeEval(
|
|
1563
1593
|
ctx,
|
|
@@ -1566,7 +1596,10 @@ var RegionRatelimit = class extends Ratelimit {
|
|
|
1566
1596
|
[key],
|
|
1567
1597
|
[null]
|
|
1568
1598
|
);
|
|
1569
|
-
return
|
|
1599
|
+
return {
|
|
1600
|
+
remaining: Math.max(0, tokens - usedTokens),
|
|
1601
|
+
reset: (bucket + 1) * windowDuration
|
|
1602
|
+
};
|
|
1570
1603
|
},
|
|
1571
1604
|
async resetTokens(ctx, identifier) {
|
|
1572
1605
|
if (!ctx.cache) {
|