@upstash/ratelimit 2.0.8 → 2.1.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
@@ -78,6 +78,25 @@ For more information on getting started, you can refer to [our documentation](ht
78
78
 
79
79
  See [the documentation](https://upstash.com/docs/redis/sdks/ratelimit-ts/overview) for more information details about this package.
80
80
 
81
+ ## Telemetry
82
+
83
+ The SDK reports its name and version to Upstash as a header on the requests made
84
+ by the redis client you provide, so we know which SDK versions are in use. No
85
+ personal data, keys or identifiers are collected.
86
+
87
+ You can opt out with `enableTelemetry: false`:
88
+
89
+ ```ts
90
+ const ratelimit = new Ratelimit({
91
+ redis: Redis.fromEnv(),
92
+ limiter: Ratelimit.slidingWindow(10, "10 s"),
93
+ enableTelemetry: false,
94
+ });
95
+ ```
96
+
97
+ or by setting the `UPSTASH_DISABLE_TELEMETRY` environment variable. Disabling
98
+ telemetry on the redis client itself also disables it here.
99
+
81
100
  ## Contributing
82
101
 
83
102
  ### Database
package/dist/index.d.mts CHANGED
@@ -495,6 +495,17 @@ type MultiRegionRatelimitConfig = {
495
495
  * @default false
496
496
  */
497
497
  dynamicLimits?: boolean;
498
+ /**
499
+ * Enable telemetry to help us improve the SDK.
500
+ *
501
+ * The sdk name and version are sent to Upstash as a header on the requests
502
+ * made by the redis clients you provide.
503
+ *
504
+ * Can also be disabled with the `UPSTASH_DISABLE_TELEMETRY` env variable.
505
+ *
506
+ * @default true
507
+ */
508
+ enableTelemetry?: boolean;
498
509
  };
499
510
  /**
500
511
  * Ratelimiter using serverless redis from https://upstash.com/
@@ -652,6 +663,17 @@ type RegionRatelimitConfig = {
652
663
  * @default false
653
664
  */
654
665
  dynamicLimits?: boolean;
666
+ /**
667
+ * Enable telemetry to help us improve the SDK.
668
+ *
669
+ * The sdk name and version are sent to Upstash as a header on the requests
670
+ * made by the redis client you provide.
671
+ *
672
+ * Can also be disabled with the `UPSTASH_DISABLE_TELEMETRY` env variable.
673
+ *
674
+ * @default true
675
+ */
676
+ enableTelemetry?: boolean;
655
677
  };
656
678
  /**
657
679
  * Ratelimiter using serverless redis from https://upstash.com/
package/dist/index.d.ts CHANGED
@@ -495,6 +495,17 @@ type MultiRegionRatelimitConfig = {
495
495
  * @default false
496
496
  */
497
497
  dynamicLimits?: boolean;
498
+ /**
499
+ * Enable telemetry to help us improve the SDK.
500
+ *
501
+ * The sdk name and version are sent to Upstash as a header on the requests
502
+ * made by the redis clients you provide.
503
+ *
504
+ * Can also be disabled with the `UPSTASH_DISABLE_TELEMETRY` env variable.
505
+ *
506
+ * @default true
507
+ */
508
+ enableTelemetry?: boolean;
498
509
  };
499
510
  /**
500
511
  * Ratelimiter using serverless redis from https://upstash.com/
@@ -652,6 +663,17 @@ type RegionRatelimitConfig = {
652
663
  * @default false
653
664
  */
654
665
  dynamicLimits?: boolean;
666
+ /**
667
+ * Enable telemetry to help us improve the SDK.
668
+ *
669
+ * The sdk name and version are sent to Upstash as a header on the requests
670
+ * made by the redis client you provide.
671
+ *
672
+ * Can also be disabled with the `UPSTASH_DISABLE_TELEMETRY` env variable.
673
+ *
674
+ * @default true
675
+ */
676
+ enableTelemetry?: boolean;
655
677
  };
656
678
  /**
657
679
  * Ratelimiter using serverless redis from https://upstash.com/
package/dist/index.js CHANGED
@@ -179,7 +179,7 @@ var safeEval = async (ctx, script, keys, args) => {
179
179
  };
180
180
 
181
181
  // src/lua-scripts/single.ts
182
- var fixedWindowLimitScript = `
182
+ var fixedWindowLimitScript = `#!lua flags=allow-key-locking
183
183
  local key = KEYS[1]
184
184
  local dynamicLimitKey = KEYS[2] -- optional: key for dynamic limit in redis
185
185
  local tokens = tonumber(ARGV[1]) -- default limit
@@ -188,7 +188,7 @@ var fixedWindowLimitScript = `
188
188
 
189
189
  -- Check for dynamic limit
190
190
  local effectiveLimit = tokens
191
- if dynamicLimitKey ~= "" then
191
+ if dynamicLimitKey and dynamicLimitKey ~= "" then
192
192
  local dynamicLimit = redis.call("GET", dynamicLimitKey)
193
193
  if dynamicLimit then
194
194
  effectiveLimit = tonumber(dynamicLimit)
@@ -204,14 +204,14 @@ var fixedWindowLimitScript = `
204
204
 
205
205
  return {r, effectiveLimit}
206
206
  `;
207
- var fixedWindowRemainingTokensScript = `
207
+ var fixedWindowRemainingTokensScript = `#!lua flags=allow-key-locking
208
208
  local key = KEYS[1]
209
209
  local dynamicLimitKey = KEYS[2] -- optional: key for dynamic limit in redis
210
210
  local tokens = tonumber(ARGV[1]) -- default limit
211
211
 
212
212
  -- Check for dynamic limit
213
213
  local effectiveLimit = tokens
214
- if dynamicLimitKey ~= "" then
214
+ if dynamicLimitKey and dynamicLimitKey ~= "" then
215
215
  local dynamicLimit = redis.call("GET", dynamicLimitKey)
216
216
  if dynamicLimit then
217
217
  effectiveLimit = tonumber(dynamicLimit)
@@ -226,7 +226,7 @@ var fixedWindowRemainingTokensScript = `
226
226
 
227
227
  return {effectiveLimit - usedTokens, effectiveLimit}
228
228
  `;
229
- var slidingWindowLimitScript = `
229
+ var slidingWindowLimitScript = `#!lua flags=allow-key-locking
230
230
  local currentKey = KEYS[1] -- identifier including prefixes
231
231
  local previousKey = KEYS[2] -- key of the previous bucket
232
232
  local dynamicLimitKey = KEYS[3] -- optional: key for dynamic limit in redis
@@ -237,7 +237,7 @@ var slidingWindowLimitScript = `
237
237
 
238
238
  -- Check for dynamic limit
239
239
  local effectiveLimit = tokens
240
- if dynamicLimitKey ~= "" then
240
+ if dynamicLimitKey and dynamicLimitKey ~= "" then
241
241
  local dynamicLimit = redis.call("GET", dynamicLimitKey)
242
242
  if dynamicLimit then
243
243
  effectiveLimit = tonumber(dynamicLimit)
@@ -270,7 +270,7 @@ var slidingWindowLimitScript = `
270
270
  end
271
271
  return {effectiveLimit - ( newValue + requestsInPreviousWindow ), effectiveLimit}
272
272
  `;
273
- var slidingWindowRemainingTokensScript = `
273
+ var slidingWindowRemainingTokensScript = `#!lua flags=allow-key-locking
274
274
  local currentKey = KEYS[1] -- identifier including prefixes
275
275
  local previousKey = KEYS[2] -- key of the previous bucket
276
276
  local dynamicLimitKey = KEYS[3] -- optional: key for dynamic limit in redis
@@ -280,7 +280,7 @@ var slidingWindowRemainingTokensScript = `
280
280
 
281
281
  -- Check for dynamic limit
282
282
  local effectiveLimit = tokens
283
- if dynamicLimitKey ~= "" then
283
+ if dynamicLimitKey and dynamicLimitKey ~= "" then
284
284
  local dynamicLimit = redis.call("GET", dynamicLimitKey)
285
285
  if dynamicLimit then
286
286
  effectiveLimit = tonumber(dynamicLimit)
@@ -304,7 +304,7 @@ var slidingWindowRemainingTokensScript = `
304
304
  local usedTokens = requestsInPreviousWindow + requestsInCurrentWindow
305
305
  return {effectiveLimit - usedTokens, effectiveLimit}
306
306
  `;
307
- var tokenBucketLimitScript = `
307
+ var tokenBucketLimitScript = `#!lua flags=allow-key-locking
308
308
  local key = KEYS[1] -- identifier including prefixes
309
309
  local dynamicLimitKey = KEYS[2] -- optional: key for dynamic limit in redis
310
310
  local maxTokens = tonumber(ARGV[1]) -- default maximum number of tokens
@@ -315,7 +315,7 @@ var tokenBucketLimitScript = `
315
315
 
316
316
  -- Check for dynamic limit
317
317
  local effectiveLimit = maxTokens
318
- if dynamicLimitKey ~= "" then
318
+ if dynamicLimitKey and dynamicLimitKey ~= "" then
319
319
  local dynamicLimit = redis.call("GET", dynamicLimitKey)
320
320
  if dynamicLimit then
321
321
  effectiveLimit = tonumber(dynamicLimit)
@@ -358,14 +358,14 @@ var tokenBucketLimitScript = `
358
358
  return {remaining, refilledAt + interval, effectiveLimit}
359
359
  `;
360
360
  var tokenBucketIdentifierNotFound = -1;
361
- var tokenBucketRemainingTokensScript = `
361
+ var tokenBucketRemainingTokensScript = `#!lua flags=allow-key-locking
362
362
  local key = KEYS[1]
363
363
  local dynamicLimitKey = KEYS[2] -- optional: key for dynamic limit in redis
364
364
  local maxTokens = tonumber(ARGV[1]) -- default maximum number of tokens
365
365
 
366
366
  -- Check for dynamic limit
367
367
  local effectiveLimit = maxTokens
368
- if dynamicLimitKey ~= "" then
368
+ if dynamicLimitKey and dynamicLimitKey ~= "" then
369
369
  local dynamicLimit = redis.call("GET", dynamicLimitKey)
370
370
  if dynamicLimit then
371
371
  effectiveLimit = tonumber(dynamicLimit)
@@ -380,7 +380,7 @@ var tokenBucketRemainingTokensScript = `
380
380
 
381
381
  return {tonumber(bucket[2]), tonumber(bucket[1]), effectiveLimit}
382
382
  `;
383
- var cachedFixedWindowLimitScript = `
383
+ var cachedFixedWindowLimitScript = `#!lua flags=allow-key-locking
384
384
  local key = KEYS[1]
385
385
  local window = ARGV[1]
386
386
  local incrementBy = ARGV[2] -- increment rate per request at a given value, default is 1
@@ -394,7 +394,7 @@ var cachedFixedWindowLimitScript = `
394
394
 
395
395
  return r
396
396
  `;
397
- var cachedFixedWindowRemainingTokenScript = `
397
+ var cachedFixedWindowRemainingTokenScript = `#!lua flags=allow-key-locking
398
398
  local key = KEYS[1]
399
399
  local tokens = 0
400
400
 
@@ -406,7 +406,7 @@ var cachedFixedWindowRemainingTokenScript = `
406
406
  `;
407
407
 
408
408
  // src/lua-scripts/multi.ts
409
- var fixedWindowLimitScript2 = `
409
+ var fixedWindowLimitScript2 = `#!lua flags=allow-key-locking
410
410
  local key = KEYS[1]
411
411
  local id = ARGV[1]
412
412
  local window = ARGV[2]
@@ -422,7 +422,7 @@ var fixedWindowLimitScript2 = `
422
422
 
423
423
  return fields
424
424
  `;
425
- var fixedWindowRemainingTokensScript2 = `
425
+ var fixedWindowRemainingTokensScript2 = `#!lua flags=allow-key-locking
426
426
  local key = KEYS[1]
427
427
  local tokens = 0
428
428
 
@@ -430,7 +430,7 @@ var fixedWindowRemainingTokensScript2 = `
430
430
 
431
431
  return fields
432
432
  `;
433
- var slidingWindowLimitScript2 = `
433
+ var slidingWindowLimitScript2 = `#!lua flags=allow-key-locking
434
434
  local currentKey = KEYS[1] -- identifier including prefixes
435
435
  local previousKey = KEYS[2] -- key of the previous bucket
436
436
  local tokens = tonumber(ARGV[1]) -- tokens per window
@@ -467,7 +467,7 @@ var slidingWindowLimitScript2 = `
467
467
  end
468
468
  return {currentFields, previousFields, true}
469
469
  `;
470
- var slidingWindowRemainingTokensScript2 = `
470
+ var slidingWindowRemainingTokensScript2 = `#!lua flags=allow-key-locking
471
471
  local currentKey = KEYS[1] -- identifier including prefixes
472
472
  local previousKey = KEYS[2] -- key of the previous bucket
473
473
  local now = ARGV[1] -- current timestamp in milliseconds
@@ -522,41 +522,41 @@ var SCRIPTS = {
522
522
  fixedWindow: {
523
523
  limit: {
524
524
  script: fixedWindowLimitScript,
525
- hash: "472e55443b62f60d0991028456c57815a387066d"
525
+ hash: "b2efc02798c8e9b471a10f9b1d5745db89923ff3"
526
526
  },
527
527
  getRemaining: {
528
528
  script: fixedWindowRemainingTokensScript,
529
- hash: "40515c9dd0a08f8584f5f9b593935f6a87c1c1c3"
529
+ hash: "823cb2a5a902d69681244f5e28e5ebb8f2a89edd"
530
530
  }
531
531
  },
532
532
  slidingWindow: {
533
533
  limit: {
534
534
  script: slidingWindowLimitScript,
535
- hash: "977fb636fb5ceb7e98a96d1b3a1272ba018efdae"
535
+ hash: "74740bb3d7792848093cdef586b6b6037c455fd6"
536
536
  },
537
537
  getRemaining: {
538
538
  script: slidingWindowRemainingTokensScript,
539
- hash: "ee3a3265fad822f83acad23f8a1e2f5c0b156b03"
539
+ hash: "7ca36169e0fc9caedd81b4eef8e99854c157f9b5"
540
540
  }
541
541
  },
542
542
  tokenBucket: {
543
543
  limit: {
544
544
  script: tokenBucketLimitScript,
545
- hash: "b35c5bc0b7fdae7dd0573d4529911cabaf9d1d89"
545
+ hash: "a9873a8026b509e99a49a0e5a2049756c693c384"
546
546
  },
547
547
  getRemaining: {
548
548
  script: tokenBucketRemainingTokensScript,
549
- hash: "deb03663e8af5a968deee895dd081be553d2611b"
549
+ hash: "40daf4a441df736f5c99f8b77bbe176c909e5de4"
550
550
  }
551
551
  },
552
552
  cachedFixedWindow: {
553
553
  limit: {
554
554
  script: cachedFixedWindowLimitScript,
555
- hash: "c26b12703dd137939b9a69a3a9b18e906a2d940f"
555
+ hash: "1861a700bafe96c833a483af6b1c28a8897cfdb0"
556
556
  },
557
557
  getRemaining: {
558
558
  script: cachedFixedWindowRemainingTokenScript,
559
- hash: "8e8f222ccae68b595ee6e3f3bf2199629a62b91a"
559
+ hash: "eb82f0e853d2fc9a236fa9bfbe704fda6ac2fc36"
560
560
  }
561
561
  }
562
562
  },
@@ -564,21 +564,21 @@ var SCRIPTS = {
564
564
  fixedWindow: {
565
565
  limit: {
566
566
  script: fixedWindowLimitScript2,
567
- hash: "a8c14f3835aa87bd70e5e2116081b81664abcf5c"
567
+ hash: "e04b753a75909b7f99aae4c04cf8869a8de02a9e"
568
568
  },
569
569
  getRemaining: {
570
570
  script: fixedWindowRemainingTokensScript2,
571
- hash: "8ab8322d0ed5fe5ac8eb08f0c2e4557f1b4816fd"
571
+ hash: "e066c8ce3eaca142b0894ad1541e0a58c9924819"
572
572
  }
573
573
  },
574
574
  slidingWindow: {
575
575
  limit: {
576
576
  script: slidingWindowLimitScript2,
577
- hash: "1e7ca8dcd2d600a6d0124a67a57ea225ed62921b"
577
+ hash: "aeb4d8f381e8dafc8e69041baae1324d254ded44"
578
578
  },
579
579
  getRemaining: {
580
580
  script: slidingWindowRemainingTokensScript2,
581
- hash: "558c9306b7ec54abb50747fe0b17e5d44bd24868"
581
+ hash: "87828c1088f6a8d1f512a434ea330189982a1c0a"
582
582
  }
583
583
  }
584
584
  }
@@ -1059,6 +1059,27 @@ var Ratelimit = class {
1059
1059
  };
1060
1060
  };
1061
1061
 
1062
+ // src/version.ts
1063
+ var VERSION = "2.1.0";
1064
+
1065
+ // src/telemetry.ts
1066
+ var taggedClients = /* @__PURE__ */ new WeakSet();
1067
+ var getSafeEnv = () => typeof process === "object" && process && typeof process.env === "object" ? process.env : {};
1068
+ var addTelemetry = (redis, enableTelemetry = true) => {
1069
+ if (!enableTelemetry || getSafeEnv().UPSTASH_DISABLE_TELEMETRY)
1070
+ return;
1071
+ if (!redis || typeof redis !== "object")
1072
+ return;
1073
+ if (taggedClients.has(redis))
1074
+ return;
1075
+ taggedClients.add(redis);
1076
+ try {
1077
+ const client = redis;
1078
+ client.addTelemetry?.({ sdk: `@upstash/ratelimit@${VERSION}` });
1079
+ } catch {
1080
+ }
1081
+ };
1082
+
1062
1083
  // src/multi.ts
1063
1084
  function randomId() {
1064
1085
  let result = "";
@@ -1088,6 +1109,9 @@ var MultiRegionRatelimit = class extends Ratelimit {
1088
1109
  cache: config.ephemeralCache ? new Cache(config.ephemeralCache) : void 0
1089
1110
  }
1090
1111
  });
1112
+ for (const redis of config.redis) {
1113
+ addTelemetry(redis, config.enableTelemetry);
1114
+ }
1091
1115
  if (config.dynamicLimits) {
1092
1116
  console.warn(
1093
1117
  "Warning: Dynamic limits are not yet supported for multi-region rate limiters. The dynamicLimits option will be ignored."
@@ -1458,6 +1482,7 @@ var RegionRatelimit = class extends Ratelimit {
1458
1482
  denyListThreshold: config.denyListThreshold,
1459
1483
  dynamicLimits: config.dynamicLimits
1460
1484
  });
1485
+ addTelemetry(config.redis, config.enableTelemetry);
1461
1486
  }
1462
1487
  /**
1463
1488
  * Each request inside a fixed time increases a counter.
@@ -1497,11 +1522,11 @@ var RegionRatelimit = class extends Ratelimit {
1497
1522
  };
1498
1523
  }
1499
1524
  }
1500
- const dynamicLimitKey = ctx.dynamicLimits ? `${ctx.prefix}${DYNAMIC_LIMIT_KEY_SUFFIX}` : "";
1525
+ const dynamicLimitKeys = ctx.dynamicLimits ? [`${ctx.prefix}${DYNAMIC_LIMIT_KEY_SUFFIX}`] : [];
1501
1526
  const [usedTokensAfterUpdate, effectiveLimit] = await safeEval(
1502
1527
  ctx,
1503
1528
  SCRIPTS.singleRegion.fixedWindow.limit,
1504
- [key, dynamicLimitKey],
1529
+ [key, ...dynamicLimitKeys],
1505
1530
  [tokens, windowDuration, incrementBy]
1506
1531
  );
1507
1532
  const success = usedTokensAfterUpdate <= effectiveLimit;
@@ -1525,11 +1550,11 @@ var RegionRatelimit = class extends Ratelimit {
1525
1550
  async getRemaining(ctx, identifier) {
1526
1551
  const bucket = Math.floor(Date.now() / windowDuration);
1527
1552
  const key = [identifier, bucket].join(":");
1528
- const dynamicLimitKey = ctx.dynamicLimits ? `${ctx.prefix}${DYNAMIC_LIMIT_KEY_SUFFIX}` : "";
1553
+ const dynamicLimitKeys = ctx.dynamicLimits ? [`${ctx.prefix}${DYNAMIC_LIMIT_KEY_SUFFIX}`] : [];
1529
1554
  const [remaining, effectiveLimit] = await safeEval(
1530
1555
  ctx,
1531
1556
  SCRIPTS.singleRegion.fixedWindow.getRemaining,
1532
- [key, dynamicLimitKey],
1557
+ [key, ...dynamicLimitKeys],
1533
1558
  [tokens]
1534
1559
  );
1535
1560
  return {
@@ -1591,11 +1616,11 @@ var RegionRatelimit = class extends Ratelimit {
1591
1616
  };
1592
1617
  }
1593
1618
  }
1594
- const dynamicLimitKey = ctx.dynamicLimits ? `${ctx.prefix}${DYNAMIC_LIMIT_KEY_SUFFIX}` : "";
1619
+ const dynamicLimitKeys = ctx.dynamicLimits ? [`${ctx.prefix}${DYNAMIC_LIMIT_KEY_SUFFIX}`] : [];
1595
1620
  const [remainingTokens, effectiveLimit] = await safeEval(
1596
1621
  ctx,
1597
1622
  SCRIPTS.singleRegion.slidingWindow.limit,
1598
- [currentKey, previousKey, dynamicLimitKey],
1623
+ [currentKey, previousKey, ...dynamicLimitKeys],
1599
1624
  [tokens, now, windowSize, incrementBy]
1600
1625
  );
1601
1626
  const success = remainingTokens >= 0;
@@ -1621,11 +1646,11 @@ var RegionRatelimit = class extends Ratelimit {
1621
1646
  const currentKey = [identifier, currentWindow].join(":");
1622
1647
  const previousWindow = currentWindow - 1;
1623
1648
  const previousKey = [identifier, previousWindow].join(":");
1624
- const dynamicLimitKey = ctx.dynamicLimits ? `${ctx.prefix}${DYNAMIC_LIMIT_KEY_SUFFIX}` : "";
1649
+ const dynamicLimitKeys = ctx.dynamicLimits ? [`${ctx.prefix}${DYNAMIC_LIMIT_KEY_SUFFIX}`] : [];
1625
1650
  const [remaining, effectiveLimit] = await safeEval(
1626
1651
  ctx,
1627
1652
  SCRIPTS.singleRegion.slidingWindow.getRemaining,
1628
- [currentKey, previousKey, dynamicLimitKey],
1653
+ [currentKey, previousKey, ...dynamicLimitKeys],
1629
1654
  [tokens, now, windowSize]
1630
1655
  );
1631
1656
  return {
@@ -1680,11 +1705,11 @@ var RegionRatelimit = class extends Ratelimit {
1680
1705
  };
1681
1706
  }
1682
1707
  }
1683
- const dynamicLimitKey = ctx.dynamicLimits ? `${ctx.prefix}${DYNAMIC_LIMIT_KEY_SUFFIX}` : "";
1708
+ const dynamicLimitKeys = ctx.dynamicLimits ? [`${ctx.prefix}${DYNAMIC_LIMIT_KEY_SUFFIX}`] : [];
1684
1709
  const [remaining, reset, effectiveLimit] = await safeEval(
1685
1710
  ctx,
1686
1711
  SCRIPTS.singleRegion.tokenBucket.limit,
1687
- [identifier, dynamicLimitKey],
1712
+ [identifier, ...dynamicLimitKeys],
1688
1713
  [maxTokens, intervalDuration, refillRate, now, incrementBy]
1689
1714
  );
1690
1715
  const success = remaining >= 0;
@@ -1704,11 +1729,11 @@ var RegionRatelimit = class extends Ratelimit {
1704
1729
  };
1705
1730
  },
1706
1731
  async getRemaining(ctx, identifier) {
1707
- const dynamicLimitKey = ctx.dynamicLimits ? `${ctx.prefix}${DYNAMIC_LIMIT_KEY_SUFFIX}` : "";
1732
+ const dynamicLimitKeys = ctx.dynamicLimits ? [`${ctx.prefix}${DYNAMIC_LIMIT_KEY_SUFFIX}`] : [];
1708
1733
  const [remainingTokens, refilledAt, effectiveLimit] = await safeEval(
1709
1734
  ctx,
1710
1735
  SCRIPTS.singleRegion.tokenBucket.getRemaining,
1711
- [identifier, dynamicLimitKey],
1736
+ [identifier, ...dynamicLimitKeys],
1712
1737
  [maxTokens]
1713
1738
  );
1714
1739
  const freshRefillAt = Date.now() + intervalDuration;