@adaptic/utils 0.0.1002 → 0.0.1004

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/test.js CHANGED
@@ -1597,6 +1597,19 @@ class RateLimitError extends AdapticUtilsError {
1597
1597
  * const result = await makeAlpacaApiCall();
1598
1598
  * ```
1599
1599
  */
1600
+ /** Number of milliseconds in one second, used for token-refill timing math. */
1601
+ const MS_PER_SECOND = 1000;
1602
+ /**
1603
+ * Minimum delay (ms) for a scheduled queue wake-up. Guards against a `0`/`NaN`
1604
+ * delay when the token deficit rounds down, ensuring the timer always makes
1605
+ * forward progress rather than busy-looping on the event loop.
1606
+ */
1607
+ const MIN_WAKE_DELAY_MS = 1;
1608
+ /**
1609
+ * Number of whole tokens required to release a single queued request. The token
1610
+ * bucket consumes exactly one token per admitted request.
1611
+ */
1612
+ const TOKENS_PER_REQUEST = 1;
1600
1613
  /**
1601
1614
  * Token bucket rate limiter implementation
1602
1615
  *
@@ -1612,6 +1625,13 @@ class TokenBucketRateLimiter {
1612
1625
  queue = [];
1613
1626
  timeoutMs;
1614
1627
  processingQueue = false;
1628
+ /**
1629
+ * Single pending timer that wakes the limiter to refill tokens and drain the
1630
+ * queue. Without this, a queued request would only be released by a
1631
+ * subsequent {@link acquire} call and would otherwise stall until its own
1632
+ * timeout fired. `null` means no wake-up is currently scheduled.
1633
+ */
1634
+ wakeTimer = null;
1615
1635
  /**
1616
1636
  * Creates a new rate limiter instance
1617
1637
  *
@@ -1685,8 +1705,48 @@ class TokenBucketRateLimiter {
1685
1705
  reject(error);
1686
1706
  }, this.timeoutMs);
1687
1707
  this.queue.push({ resolve, reject, timeoutHandle });
1708
+ // Ensure the queue is actively drained even if no further acquire() calls
1709
+ // arrive: schedule a wake-up to refill tokens and release this request.
1710
+ this.scheduleQueueWake();
1688
1711
  });
1689
1712
  }
1713
+ /**
1714
+ * Schedules a single wake-up timer that refills tokens and drains the queue.
1715
+ *
1716
+ * The delay is the time required to accrue the tokens still needed to release
1717
+ * the next queued request at the configured refill rate. Only one timer is
1718
+ * ever outstanding (guarded by {@link wakeTimer}); the timer is `unref`'d so
1719
+ * it never keeps the Node.js process alive on its own. When it fires it
1720
+ * refills, drains what it can, and re-arms itself if work remains.
1721
+ */
1722
+ scheduleQueueWake() {
1723
+ // A wake-up is already pending, or there is nothing to wake for.
1724
+ if (this.wakeTimer !== null || this.queue.length === 0) {
1725
+ return;
1726
+ }
1727
+ const tokensNeeded = Math.max(0, TOKENS_PER_REQUEST - this.tokens);
1728
+ const deficitMs = Math.max(MIN_WAKE_DELAY_MS, Math.ceil((tokensNeeded / this.config.refillRate) * MS_PER_SECOND));
1729
+ const timer = setTimeout(() => {
1730
+ this.wakeTimer = null;
1731
+ // refill() drains the queue via processQueue(); if requests remain
1732
+ // afterwards, processQueue() re-arms the wake-up.
1733
+ this.refill();
1734
+ }, deficitMs);
1735
+ // Do not let a pending rate-limiter wake-up keep the process alive.
1736
+ if (typeof timer.unref === "function") {
1737
+ timer.unref();
1738
+ }
1739
+ this.wakeTimer = timer;
1740
+ }
1741
+ /**
1742
+ * Clears any pending wake-up timer.
1743
+ */
1744
+ clearWakeTimer() {
1745
+ if (this.wakeTimer !== null) {
1746
+ clearTimeout(this.wakeTimer);
1747
+ this.wakeTimer = null;
1748
+ }
1749
+ }
1690
1750
  /**
1691
1751
  * Refills tokens based on elapsed time and processes queued requests
1692
1752
  *
@@ -1731,6 +1791,15 @@ class TokenBucketRateLimiter {
1731
1791
  finally {
1732
1792
  this.processingQueue = false;
1733
1793
  }
1794
+ // Keep the wake-up state consistent with the queue: if requests are still
1795
+ // waiting (tokens ran out mid-drain), ensure a wake-up is armed; otherwise
1796
+ // release any pending timer so it cannot fire needlessly.
1797
+ if (this.queue.length > 0) {
1798
+ this.scheduleQueueWake();
1799
+ }
1800
+ else {
1801
+ this.clearWakeTimer();
1802
+ }
1734
1803
  }
1735
1804
  /**
1736
1805
  * Gets the current number of available tokens
@@ -1762,6 +1831,7 @@ class TokenBucketRateLimiter {
1762
1831
  clearTimeout(request.timeoutHandle);
1763
1832
  request.reject(new RateLimitError(`Rate limiter reset for ${this.config.label}`, this.config.label, undefined));
1764
1833
  }
1834
+ this.clearWakeTimer();
1765
1835
  this.queue = [];
1766
1836
  this.tokens = this.config.maxTokens;
1767
1837
  this.lastRefill = Date.now();