@upstash/ratelimit 1.2.1-canary → 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/dist/index.mjs CHANGED
@@ -560,6 +560,14 @@ var Ratelimit = class {
560
560
  const pattern = [this.prefix, identifier].join(":");
561
561
  await this.limiter().resetTokens(this.ctx, pattern);
562
562
  };
563
+ /**
564
+ * Returns the remaining token count together with a reset timestamps
565
+ *
566
+ * @param identifier identifir to check
567
+ * @returns object with `remaining` and reset fields. `remaining` denotes
568
+ * the remaining tokens and reset denotes the timestamp when the
569
+ * tokens reset.
570
+ */
563
571
  getRemaining = async (identifier) => {
564
572
  const pattern = [this.prefix, identifier].join(":");
565
573
  return await this.limiter().getRemaining(this.ctx, pattern);
@@ -844,7 +852,10 @@ var MultiRegionRatelimit = class extends Ratelimit {
844
852
  }
845
853
  return accTokens + parsedToken;
846
854
  }, 0);
847
- return Math.max(0, tokens - usedTokens);
855
+ return {
856
+ remaining: Math.max(0, tokens - usedTokens),
857
+ reset: (bucket + 1) * windowDuration
858
+ };
848
859
  },
849
860
  async resetTokens(ctx, identifier) {
850
861
  const pattern = [identifier, "*"].join(":");
@@ -1006,7 +1017,10 @@ var MultiRegionRatelimit = class extends Ratelimit {
1006
1017
  )
1007
1018
  }));
1008
1019
  const usedTokens = await Promise.any(dbs.map((s) => s.request));
1009
- return Math.max(0, tokens - usedTokens);
1020
+ return {
1021
+ remaining: Math.max(0, tokens - usedTokens),
1022
+ reset: (currentWindow + 1) * windowSize
1023
+ };
1010
1024
  },
1011
1025
  async resetTokens(ctx, identifier) {
1012
1026
  const pattern = [identifier, "*"].join(":");
@@ -1145,17 +1159,18 @@ var tokenBucketLimitScript = `
1145
1159
  redis.call("PEXPIRE", key, expireAt)
1146
1160
  return {remaining, refilledAt + interval}
1147
1161
  `;
1162
+ var tokenBucketIdentifierNotFound = -1;
1148
1163
  var tokenBucketRemainingTokensScript = `
1149
1164
  local key = KEYS[1]
1150
1165
  local maxTokens = tonumber(ARGV[1])
1151
1166
 
1152
- local bucket = redis.call("HMGET", key, "tokens")
1167
+ local bucket = redis.call("HMGET", key, "refilledAt", "tokens")
1153
1168
 
1154
1169
  if bucket[1] == false then
1155
- return maxTokens
1170
+ return {maxTokens, ${tokenBucketIdentifierNotFound}}
1156
1171
  end
1157
1172
 
1158
- return tonumber(bucket[1])
1173
+ return {tonumber(bucket[2]), tonumber(bucket[1])}
1159
1174
  `;
1160
1175
  var cachedFixedWindowLimitScript = `
1161
1176
  local key = KEYS[1]
@@ -1272,7 +1287,10 @@ var RegionRatelimit = class extends Ratelimit {
1272
1287
  [key],
1273
1288
  [null]
1274
1289
  );
1275
- return Math.max(0, tokens - usedTokens);
1290
+ return {
1291
+ remaining: Math.max(0, tokens - usedTokens),
1292
+ reset: (bucket + 1) * windowDuration
1293
+ };
1276
1294
  },
1277
1295
  async resetTokens(ctx, identifier) {
1278
1296
  const pattern = [identifier, "*"].join(":");
@@ -1361,7 +1379,10 @@ var RegionRatelimit = class extends Ratelimit {
1361
1379
  [currentKey, previousKey],
1362
1380
  [now, windowSize]
1363
1381
  );
1364
- return Math.max(0, tokens - usedTokens);
1382
+ return {
1383
+ remaining: Math.max(0, tokens - usedTokens),
1384
+ reset: (currentWindow + 1) * windowSize
1385
+ };
1365
1386
  },
1366
1387
  async resetTokens(ctx, identifier) {
1367
1388
  const pattern = [identifier, "*"].join(":");
@@ -1430,14 +1451,19 @@ var RegionRatelimit = class extends Ratelimit {
1430
1451
  };
1431
1452
  },
1432
1453
  async getRemaining(ctx, identifier) {
1433
- const remainingTokens = await safeEval(
1454
+ const [remainingTokens, refilledAt] = await safeEval(
1434
1455
  ctx,
1435
1456
  tokenBucketRemainingTokensScript,
1436
1457
  "getRemainingHash",
1437
1458
  [identifier],
1438
1459
  [maxTokens]
1439
1460
  );
1440
- return remainingTokens;
1461
+ const freshRefillAt = Date.now() + intervalDuration;
1462
+ const identifierRefillsAt = refilledAt + intervalDuration;
1463
+ return {
1464
+ remaining: remainingTokens,
1465
+ reset: refilledAt === tokenBucketIdentifierNotFound ? freshRefillAt : identifierRefillsAt
1466
+ };
1441
1467
  },
1442
1468
  async resetTokens(ctx, identifier) {
1443
1469
  const pattern = identifier;
@@ -1534,7 +1560,10 @@ var RegionRatelimit = class extends Ratelimit {
1534
1560
  const hit = typeof ctx.cache.get(key) === "number";
1535
1561
  if (hit) {
1536
1562
  const cachedUsedTokens = ctx.cache.get(key) ?? 0;
1537
- return Math.max(0, tokens - cachedUsedTokens);
1563
+ return {
1564
+ remaining: Math.max(0, tokens - cachedUsedTokens),
1565
+ reset: (bucket + 1) * windowDuration
1566
+ };
1538
1567
  }
1539
1568
  const usedTokens = await safeEval(
1540
1569
  ctx,
@@ -1543,7 +1572,10 @@ var RegionRatelimit = class extends Ratelimit {
1543
1572
  [key],
1544
1573
  [null]
1545
1574
  );
1546
- return Math.max(0, tokens - usedTokens);
1575
+ return {
1576
+ remaining: Math.max(0, tokens - usedTokens),
1577
+ reset: (bucket + 1) * windowDuration
1578
+ };
1547
1579
  },
1548
1580
  async resetTokens(ctx, identifier) {
1549
1581
  if (!ctx.cache) {