@upstash/ratelimit 1.2.1 → 2.0.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 CHANGED
@@ -3,7 +3,8 @@
3
3
  [![npm (scoped)](https://img.shields.io/npm/v/@upstash/ratelimit)](https://www.npmjs.com/package/@upstash/ratelimit)
4
4
  [![Tests](https://github.com/upstash/ratelimit/actions/workflows/tests.yaml/badge.svg)](https://github.com/upstash/ratelimit/actions/workflows/tests.yaml)
5
5
 
6
- > [!NOTE] > **This project is in GA Stage.**
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<number>;
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;
@@ -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
- getRemaining: (identifier: string) => Promise<number>;
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<number>;
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;
@@ -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
- getRemaining: (identifier: string) => Promise<number>;
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
@@ -583,6 +583,14 @@ var Ratelimit = class {
583
583
  const pattern = [this.prefix, identifier].join(":");
584
584
  await this.limiter().resetTokens(this.ctx, pattern);
585
585
  };
586
+ /**
587
+ * Returns the remaining token count together with a reset timestamps
588
+ *
589
+ * @param identifier identifir to check
590
+ * @returns object with `remaining` and reset fields. `remaining` denotes
591
+ * the remaining tokens and reset denotes the timestamp when the
592
+ * tokens reset.
593
+ */
586
594
  getRemaining = async (identifier) => {
587
595
  const pattern = [this.prefix, identifier].join(":");
588
596
  return await this.limiter().getRemaining(this.ctx, pattern);
@@ -867,7 +875,10 @@ var MultiRegionRatelimit = class extends Ratelimit {
867
875
  }
868
876
  return accTokens + parsedToken;
869
877
  }, 0);
870
- return Math.max(0, tokens - usedTokens);
878
+ return {
879
+ remaining: Math.max(0, tokens - usedTokens),
880
+ reset: (bucket + 1) * windowDuration
881
+ };
871
882
  },
872
883
  async resetTokens(ctx, identifier) {
873
884
  const pattern = [identifier, "*"].join(":");
@@ -1029,7 +1040,10 @@ var MultiRegionRatelimit = class extends Ratelimit {
1029
1040
  )
1030
1041
  }));
1031
1042
  const usedTokens = await Promise.any(dbs.map((s) => s.request));
1032
- return Math.max(0, tokens - usedTokens);
1043
+ return {
1044
+ remaining: Math.max(0, tokens - usedTokens),
1045
+ reset: (currentWindow + 1) * windowSize
1046
+ };
1033
1047
  },
1034
1048
  async resetTokens(ctx, identifier) {
1035
1049
  const pattern = [identifier, "*"].join(":");
@@ -1168,17 +1182,18 @@ var tokenBucketLimitScript = `
1168
1182
  redis.call("PEXPIRE", key, expireAt)
1169
1183
  return {remaining, refilledAt + interval}
1170
1184
  `;
1185
+ var tokenBucketIdentifierNotFound = -1;
1171
1186
  var tokenBucketRemainingTokensScript = `
1172
1187
  local key = KEYS[1]
1173
1188
  local maxTokens = tonumber(ARGV[1])
1174
1189
 
1175
- local bucket = redis.call("HMGET", key, "tokens")
1190
+ local bucket = redis.call("HMGET", key, "refilledAt", "tokens")
1176
1191
 
1177
1192
  if bucket[1] == false then
1178
- return maxTokens
1193
+ return {maxTokens, ${tokenBucketIdentifierNotFound}}
1179
1194
  end
1180
1195
 
1181
- return tonumber(bucket[1])
1196
+ return {tonumber(bucket[2]), tonumber(bucket[1])}
1182
1197
  `;
1183
1198
  var cachedFixedWindowLimitScript = `
1184
1199
  local key = KEYS[1]
@@ -1295,7 +1310,10 @@ var RegionRatelimit = class extends Ratelimit {
1295
1310
  [key],
1296
1311
  [null]
1297
1312
  );
1298
- return Math.max(0, tokens - usedTokens);
1313
+ return {
1314
+ remaining: Math.max(0, tokens - usedTokens),
1315
+ reset: (bucket + 1) * windowDuration
1316
+ };
1299
1317
  },
1300
1318
  async resetTokens(ctx, identifier) {
1301
1319
  const pattern = [identifier, "*"].join(":");
@@ -1384,7 +1402,10 @@ var RegionRatelimit = class extends Ratelimit {
1384
1402
  [currentKey, previousKey],
1385
1403
  [now, windowSize]
1386
1404
  );
1387
- return Math.max(0, tokens - usedTokens);
1405
+ return {
1406
+ remaining: Math.max(0, tokens - usedTokens),
1407
+ reset: (currentWindow + 1) * windowSize
1408
+ };
1388
1409
  },
1389
1410
  async resetTokens(ctx, identifier) {
1390
1411
  const pattern = [identifier, "*"].join(":");
@@ -1453,14 +1474,19 @@ var RegionRatelimit = class extends Ratelimit {
1453
1474
  };
1454
1475
  },
1455
1476
  async getRemaining(ctx, identifier) {
1456
- const remainingTokens = await safeEval(
1477
+ const [remainingTokens, refilledAt] = await safeEval(
1457
1478
  ctx,
1458
1479
  tokenBucketRemainingTokensScript,
1459
1480
  "getRemainingHash",
1460
1481
  [identifier],
1461
1482
  [maxTokens]
1462
1483
  );
1463
- return remainingTokens;
1484
+ const freshRefillAt = Date.now() + intervalDuration;
1485
+ const identifierRefillsAt = refilledAt + intervalDuration;
1486
+ return {
1487
+ remaining: remainingTokens,
1488
+ reset: refilledAt === tokenBucketIdentifierNotFound ? freshRefillAt : identifierRefillsAt
1489
+ };
1464
1490
  },
1465
1491
  async resetTokens(ctx, identifier) {
1466
1492
  const pattern = identifier;
@@ -1557,7 +1583,10 @@ var RegionRatelimit = class extends Ratelimit {
1557
1583
  const hit = typeof ctx.cache.get(key) === "number";
1558
1584
  if (hit) {
1559
1585
  const cachedUsedTokens = ctx.cache.get(key) ?? 0;
1560
- return Math.max(0, tokens - cachedUsedTokens);
1586
+ return {
1587
+ remaining: Math.max(0, tokens - cachedUsedTokens),
1588
+ reset: (bucket + 1) * windowDuration
1589
+ };
1561
1590
  }
1562
1591
  const usedTokens = await safeEval(
1563
1592
  ctx,
@@ -1566,7 +1595,10 @@ var RegionRatelimit = class extends Ratelimit {
1566
1595
  [key],
1567
1596
  [null]
1568
1597
  );
1569
- return Math.max(0, tokens - usedTokens);
1598
+ return {
1599
+ remaining: Math.max(0, tokens - usedTokens),
1600
+ reset: (bucket + 1) * windowDuration
1601
+ };
1570
1602
  },
1571
1603
  async resetTokens(ctx, identifier) {
1572
1604
  if (!ctx.cache) {