@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 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;
@@ -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
- 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;
@@ -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
- 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
@@ -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
- return this.analytics.getMostAllowedBlocked(this.table, timestampCount, getTop);
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 Math.max(0, tokens - usedTokens);
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 Math.max(0, tokens - usedTokens);
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 Math.max(0, tokens - usedTokens);
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 Math.max(0, tokens - usedTokens);
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
- return remainingTokens;
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 Math.max(0, tokens - cachedUsedTokens);
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 Math.max(0, tokens - usedTokens);
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) {