@upstash/ratelimit 2.1.0 → 2.2.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
@@ -114,33 +114,30 @@ var DYNAMIC_LIMIT_KEY_SUFFIX = ":dynamic:global";
114
114
  var DEFAULT_PREFIX = "@upstash/ratelimit";
115
115
 
116
116
  // src/duration.ts
117
+ var MULTIPLIERS = {
118
+ ms: 1,
119
+ s: 1e3,
120
+ m: 1e3 * 60,
121
+ h: 1e3 * 60 * 60,
122
+ d: 1e3 * 60 * 60 * 24,
123
+ w: 1e3 * 60 * 60 * 24 * 7
124
+ };
117
125
  function ms(d) {
118
- const match = d.match(/^(\d+)\s?(ms|s|m|h|d)$/);
126
+ const match = d.match(/^(\d+(?:\.\d+)?)\s?(ms|s|m|h|d|w)$/);
119
127
  if (!match) {
120
128
  throw new Error(`Unable to parse window size: ${d}`);
121
129
  }
122
- const time = Number.parseInt(match[1]);
130
+ const time = Number.parseFloat(match[1]);
123
131
  const unit = match[2];
124
- switch (unit) {
125
- case "ms": {
126
- return time;
127
- }
128
- case "s": {
129
- return time * 1e3;
130
- }
131
- case "m": {
132
- return time * 1e3 * 60;
133
- }
134
- case "h": {
135
- return time * 1e3 * 60 * 60;
136
- }
137
- case "d": {
138
- return time * 1e3 * 60 * 60 * 24;
139
- }
140
- default: {
141
- throw new Error(`Unable to parse window size: ${d}`);
142
- }
132
+ const multiplier = MULTIPLIERS[unit];
133
+ if (multiplier === void 0) {
134
+ throw new Error(`Unable to parse window size: ${d}`);
143
135
  }
136
+ const result = Math.round(time * multiplier);
137
+ if (result < 1) {
138
+ throw new Error(`Window size must be at least 1 ms, got: ${d}`);
139
+ }
140
+ return result;
144
141
  }
145
142
 
146
143
  // src/hash.ts
@@ -319,8 +316,12 @@ var tokenBucketLimitScript = `#!lua flags=allow-key-locking
319
316
  refilledAt = refilledAt + numRefills * interval
320
317
  end
321
318
 
322
- -- Only reject if tokens are 0 and we're consuming (not refunding)
323
- if tokens == 0 and incrementBy > 0 then
319
+ -- Only reject when consuming (not refunding) and there are not enough tokens
320
+ -- for this request. Checking tokens == 0 only covered incrementBy == 1: with
321
+ -- a larger rate (e.g. 2 tokens left, incrementBy 5) the request fell through,
322
+ -- drove tokens negative and still wrote it back, so a rejected request
323
+ -- consumed tokens and a single large rate could lock the identifier out.
324
+ if incrementBy > 0 and tokens < incrementBy then
324
325
  return {-1, refilledAt + interval, effectiveLimit}
325
326
  end
326
327
 
@@ -519,7 +520,7 @@ var SCRIPTS = {
519
520
  tokenBucket: {
520
521
  limit: {
521
522
  script: tokenBucketLimitScript,
522
- hash: "a9873a8026b509e99a49a0e5a2049756c693c384"
523
+ hash: "d60be5bd3d83482ae229309f1f7425424f891204"
523
524
  },
524
525
  getRemaining: {
525
526
  script: tokenBucketRemainingTokensScript,
@@ -848,7 +849,7 @@ var Ratelimit = class {
848
849
  throw new Error("This should not happen");
849
850
  }
850
851
  const wait = Math.min(res.reset, deadline) - Date.now();
851
- await new Promise((r) => setTimeout(r, wait));
852
+ await new Promise((r) => setTimeout(r, wait > 0 ? wait : 100));
852
853
  if (Date.now() > deadline) {
853
854
  break;
854
855
  }
@@ -1037,7 +1038,7 @@ var Ratelimit = class {
1037
1038
  };
1038
1039
 
1039
1040
  // src/version.ts
1040
- var VERSION = "2.1.0";
1041
+ var VERSION = "2.2.0";
1041
1042
 
1042
1043
  // src/telemetry.ts
1043
1044
  var taggedClients = /* @__PURE__ */ new WeakSet();
@@ -1778,7 +1779,7 @@ var RegionRatelimit = class extends Ratelimit {
1778
1779
  const hit = typeof ctx.cache.get(key) === "number";
1779
1780
  if (hit) {
1780
1781
  const cachedTokensAfterUpdate = ctx.cache.incr(key, incrementBy);
1781
- const success = cachedTokensAfterUpdate < tokens;
1782
+ const success = cachedTokensAfterUpdate <= tokens;
1782
1783
  const pending = success ? safeEval(
1783
1784
  ctx,
1784
1785
  SCRIPTS.singleRegion.cachedFixedWindow.limit,