@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.d.mts CHANGED
@@ -172,7 +172,7 @@ declare class Analytics {
172
172
  }>;
173
173
  }
174
174
 
175
- type Unit = "ms" | "s" | "m" | "h" | "d";
175
+ type Unit = "ms" | "s" | "m" | "h" | "d" | "w";
176
176
  type Duration = `${number} ${Unit}` | `${number}${Unit}`;
177
177
 
178
178
  type RatelimitConfig<TContext> = {
package/dist/index.d.ts CHANGED
@@ -172,7 +172,7 @@ declare class Analytics {
172
172
  }>;
173
173
  }
174
174
 
175
- type Unit = "ms" | "s" | "m" | "h" | "d";
175
+ type Unit = "ms" | "s" | "m" | "h" | "d" | "w";
176
176
  type Duration = `${number} ${Unit}` | `${number}${Unit}`;
177
177
 
178
178
  type RatelimitConfig<TContext> = {
package/dist/index.js CHANGED
@@ -137,33 +137,30 @@ var DYNAMIC_LIMIT_KEY_SUFFIX = ":dynamic:global";
137
137
  var DEFAULT_PREFIX = "@upstash/ratelimit";
138
138
 
139
139
  // src/duration.ts
140
+ var MULTIPLIERS = {
141
+ ms: 1,
142
+ s: 1e3,
143
+ m: 1e3 * 60,
144
+ h: 1e3 * 60 * 60,
145
+ d: 1e3 * 60 * 60 * 24,
146
+ w: 1e3 * 60 * 60 * 24 * 7
147
+ };
140
148
  function ms(d) {
141
- const match = d.match(/^(\d+)\s?(ms|s|m|h|d)$/);
149
+ const match = d.match(/^(\d+(?:\.\d+)?)\s?(ms|s|m|h|d|w)$/);
142
150
  if (!match) {
143
151
  throw new Error(`Unable to parse window size: ${d}`);
144
152
  }
145
- const time = Number.parseInt(match[1]);
153
+ const time = Number.parseFloat(match[1]);
146
154
  const unit = match[2];
147
- switch (unit) {
148
- case "ms": {
149
- return time;
150
- }
151
- case "s": {
152
- return time * 1e3;
153
- }
154
- case "m": {
155
- return time * 1e3 * 60;
156
- }
157
- case "h": {
158
- return time * 1e3 * 60 * 60;
159
- }
160
- case "d": {
161
- return time * 1e3 * 60 * 60 * 24;
162
- }
163
- default: {
164
- throw new Error(`Unable to parse window size: ${d}`);
165
- }
155
+ const multiplier = MULTIPLIERS[unit];
156
+ if (multiplier === void 0) {
157
+ throw new Error(`Unable to parse window size: ${d}`);
166
158
  }
159
+ const result = Math.round(time * multiplier);
160
+ if (result < 1) {
161
+ throw new Error(`Window size must be at least 1 ms, got: ${d}`);
162
+ }
163
+ return result;
167
164
  }
168
165
 
169
166
  // src/hash.ts
@@ -342,8 +339,12 @@ var tokenBucketLimitScript = `#!lua flags=allow-key-locking
342
339
  refilledAt = refilledAt + numRefills * interval
343
340
  end
344
341
 
345
- -- Only reject if tokens are 0 and we're consuming (not refunding)
346
- if tokens == 0 and incrementBy > 0 then
342
+ -- Only reject when consuming (not refunding) and there are not enough tokens
343
+ -- for this request. Checking tokens == 0 only covered incrementBy == 1: with
344
+ -- a larger rate (e.g. 2 tokens left, incrementBy 5) the request fell through,
345
+ -- drove tokens negative and still wrote it back, so a rejected request
346
+ -- consumed tokens and a single large rate could lock the identifier out.
347
+ if incrementBy > 0 and tokens < incrementBy then
347
348
  return {-1, refilledAt + interval, effectiveLimit}
348
349
  end
349
350
 
@@ -542,7 +543,7 @@ var SCRIPTS = {
542
543
  tokenBucket: {
543
544
  limit: {
544
545
  script: tokenBucketLimitScript,
545
- hash: "a9873a8026b509e99a49a0e5a2049756c693c384"
546
+ hash: "d60be5bd3d83482ae229309f1f7425424f891204"
546
547
  },
547
548
  getRemaining: {
548
549
  script: tokenBucketRemainingTokensScript,
@@ -871,7 +872,7 @@ var Ratelimit = class {
871
872
  throw new Error("This should not happen");
872
873
  }
873
874
  const wait = Math.min(res.reset, deadline) - Date.now();
874
- await new Promise((r) => setTimeout(r, wait));
875
+ await new Promise((r) => setTimeout(r, wait > 0 ? wait : 100));
875
876
  if (Date.now() > deadline) {
876
877
  break;
877
878
  }
@@ -1060,7 +1061,7 @@ var Ratelimit = class {
1060
1061
  };
1061
1062
 
1062
1063
  // src/version.ts
1063
- var VERSION = "2.1.0";
1064
+ var VERSION = "2.2.0";
1064
1065
 
1065
1066
  // src/telemetry.ts
1066
1067
  var taggedClients = /* @__PURE__ */ new WeakSet();
@@ -1801,7 +1802,7 @@ var RegionRatelimit = class extends Ratelimit {
1801
1802
  const hit = typeof ctx.cache.get(key) === "number";
1802
1803
  if (hit) {
1803
1804
  const cachedTokensAfterUpdate = ctx.cache.incr(key, incrementBy);
1804
- const success = cachedTokensAfterUpdate < tokens;
1805
+ const success = cachedTokensAfterUpdate <= tokens;
1805
1806
  const pending = success ? safeEval(
1806
1807
  ctx,
1807
1808
  SCRIPTS.singleRegion.cachedFixedWindow.limit,