@vercel/flags-core 1.8.2 → 1.8.3

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.
@@ -56,7 +56,7 @@ function getSplitBoundaries(outcome) {
56
56
  function getCompiledRegex(rhs) {
57
57
  const cached = compiledRegexCache.get(rhs);
58
58
  if (cached) return cached;
59
- const compiled = new RegExp(rhs.pattern, rhs.flags.replace(/[gy]/, ""));
59
+ const compiled = new RegExp(rhs.pattern, rhs.flags.replace(/[gy]/g, ""));
60
60
  compiledRegexCache.set(rhs, compiled);
61
61
  return compiled;
62
62
  }
@@ -559,7 +559,7 @@ function bulkEvaluate(flags, shared) {
559
559
  }
560
560
 
561
561
  // package.json
562
- var version = "1.8.2";
562
+ var version = "1.8.3";
563
563
 
564
564
  // src/lib/report-value.ts
565
565
  function internalReportValue(key, value, data) {
@@ -1509,40 +1509,86 @@ var BundledSource = class {
1509
1509
 
1510
1510
  // src/controller/fetch-datafile.ts
1511
1511
  var DEFAULT_FETCH_TIMEOUT_MS = 1e4;
1512
+ var DEFAULT_MAX_ATTEMPTS = 3;
1513
+ var DatafileHttpError = class extends Error {
1514
+ constructor(status, statusText) {
1515
+ super(`Failed to fetch data: ${statusText}`);
1516
+ this.status = status;
1517
+ }
1518
+
1519
+ get retryable() {
1520
+ return this.status === 408 || this.status === 429 || this.status >= 500;
1521
+ }
1522
+ };
1512
1523
  async function fetchDatafile(options) {
1513
- const token = await options.auth.resolveToken();
1524
+ _optionalChain([options, 'access', _44 => _44.signal, 'optionalAccess', _45 => _45.throwIfAborted, 'call', _46 => _46()]);
1525
+ const maxAttempts = _nullishCoalesce(options.maxAttempts, () => ( DEFAULT_MAX_ATTEMPTS));
1526
+ if (!Number.isInteger(maxAttempts) || maxAttempts < 1) {
1527
+ throw new Error("maxAttempts must be a positive integer");
1528
+ }
1514
1529
  const controller = new AbortController();
1530
+ const { signal } = controller;
1531
+ let rejectAbort;
1532
+ const aborted = new Promise((_, reject) => {
1533
+ rejectAbort = reject;
1534
+ });
1535
+ const onAbort = () => rejectAbort(signal.reason);
1536
+ signal.addEventListener("abort", onAbort, { once: true });
1537
+ const onExternalAbort = () => controller.abort(_optionalChain([options, 'access', _47 => _47.signal, 'optionalAccess', _48 => _48.reason]));
1538
+ _optionalChain([options, 'access', _49 => _49.signal, 'optionalAccess', _50 => _50.addEventListener, 'call', _51 => _51("abort", onExternalAbort, { once: true })]);
1515
1539
  const timeoutId = setTimeout(
1516
- () => controller.abort(),
1540
+ () => controller.abort(
1541
+ new Error("@vercel/flags-core: Datafile fetch deadline exceeded")
1542
+ ),
1517
1543
  DEFAULT_FETCH_TIMEOUT_MS
1518
1544
  );
1519
- const onExternalAbort = () => controller.abort();
1520
- if (options.signal) {
1521
- if (options.signal.aborted) {
1522
- clearTimeout(timeoutId);
1523
- throw new Error("Fetch aborted");
1524
- }
1525
- options.signal.addEventListener("abort", onExternalAbort, { once: true });
1526
- }
1527
- try {
1545
+ let delay;
1546
+ const fetchAttempt = async () => {
1547
+ const token = await options.auth.resolveToken();
1548
+ signal.throwIfAborted();
1528
1549
  const res = await options.fetch(`${options.host}/v1/datafile`, {
1529
1550
  headers: {
1530
1551
  Authorization: `Bearer ${token}`,
1531
1552
  "User-Agent": `VercelFlagsCore/${version}`,
1532
1553
  ...process.env.VERCEL_ENV ? { "X-Vercel-Env": process.env.VERCEL_ENV } : null
1533
1554
  },
1534
- signal: controller.signal
1555
+ signal
1535
1556
  });
1536
- clearTimeout(timeoutId);
1537
- _optionalChain([options, 'access', _44 => _44.signal, 'optionalAccess', _45 => _45.removeEventListener, 'call', _46 => _46("abort", onExternalAbort)]);
1557
+ signal.throwIfAborted();
1538
1558
  if (!res.ok) {
1539
- throw new Error(`Failed to fetch data: ${res.statusText}`);
1559
+ void _optionalChain([res, 'access', _52 => _52.body, 'optionalAccess', _53 => _53.cancel, 'call', _54 => _54(), 'access', _55 => _55.catch, 'call', _56 => _56(() => {
1560
+ })]);
1561
+ throw new DatafileHttpError(res.status, res.statusText);
1540
1562
  }
1541
- return res.json();
1542
- } catch (error) {
1563
+ const data = await res.json();
1564
+ signal.throwIfAborted();
1565
+ return data;
1566
+ };
1567
+ try {
1568
+ for (let attempt = 0; ; attempt++) {
1569
+ if (attempt > 0) {
1570
+ await Promise.race([
1571
+ new Promise((resolve) => {
1572
+ delay = setTimeout(resolve, 100 * 2 ** (attempt - 1));
1573
+ }),
1574
+ aborted
1575
+ ]);
1576
+ }
1577
+ signal.throwIfAborted();
1578
+ try {
1579
+ return await Promise.race([fetchAttempt(), aborted]);
1580
+ } catch (error) {
1581
+ signal.throwIfAborted();
1582
+ if (attempt === maxAttempts - 1 || error instanceof DatafileHttpError && !error.retryable) {
1583
+ throw error instanceof Error ? error : new Error("Unknown fetch error");
1584
+ }
1585
+ }
1586
+ }
1587
+ } finally {
1543
1588
  clearTimeout(timeoutId);
1544
- _optionalChain([options, 'access', _47 => _47.signal, 'optionalAccess', _48 => _48.removeEventListener, 'call', _49 => _49("abort", onExternalAbort)]);
1545
- throw error instanceof Error ? error : new Error("Unknown fetch error");
1589
+ clearTimeout(delay);
1590
+ signal.removeEventListener("abort", onAbort);
1591
+ _optionalChain([options, 'access', _57 => _57.signal, 'optionalAccess', _58 => _58.removeEventListener, 'call', _59 => _59("abort", onExternalAbort)]);
1546
1592
  }
1547
1593
  }
1548
1594
 
@@ -1611,7 +1657,7 @@ var TypedEmitter = (_class5 = class {constructor() { _class5.prototype.__init12.
1611
1657
  set.add(handler);
1612
1658
  }
1613
1659
  off(event, handler) {
1614
- _optionalChain([this, 'access', _50 => _50.handlers, 'access', _51 => _51.get, 'call', _52 => _52(event), 'optionalAccess', _53 => _53.delete, 'call', _54 => _54(handler)]);
1660
+ _optionalChain([this, 'access', _60 => _60.handlers, 'access', _61 => _61.get, 'call', _62 => _62(event), 'optionalAccess', _63 => _63.delete, 'call', _64 => _64(handler)]);
1615
1661
  }
1616
1662
  emit(event, ...args) {
1617
1663
  const set = this.handlers.get(event);
@@ -1637,14 +1683,17 @@ var PollingSource = class extends TypedEmitter {
1637
1683
  * Emits 'data' on success, 'error' on failure.
1638
1684
  */
1639
1685
  async poll() {
1640
- if (_optionalChain([this, 'access', _55 => _55.abortController, 'optionalAccess', _56 => _56.signal, 'access', _57 => _57.aborted])) return;
1686
+ if (_optionalChain([this, 'access', _65 => _65.abortController, 'optionalAccess', _66 => _66.signal, 'access', _67 => _67.aborted])) return;
1687
+ this.abortController ??= new AbortController();
1688
+ const controller = this.abortController;
1641
1689
  try {
1642
1690
  const data = await fetchDatafile({
1643
1691
  ...this.config,
1644
- signal: _optionalChain([this, 'access', _58 => _58.abortController, 'optionalAccess', _59 => _59.signal])
1692
+ signal: controller.signal
1645
1693
  });
1646
1694
  this.emit("data", data);
1647
1695
  } catch (error) {
1696
+ controller.signal.throwIfAborted();
1648
1697
  const err = error instanceof Error ? error : new Error("Unknown poll error");
1649
1698
  this.emit("error", err);
1650
1699
  }
@@ -1656,9 +1705,9 @@ var PollingSource = class extends TypedEmitter {
1656
1705
  */
1657
1706
  startInterval() {
1658
1707
  if (this.intervalId) return;
1659
- this.abortController = new AbortController();
1660
1708
  this.intervalId = setInterval(
1661
- () => void this.poll(),
1709
+ () => void this.poll().catch(() => {
1710
+ }),
1662
1711
  this.config.polling.intervalMs
1663
1712
  );
1664
1713
  }
@@ -1670,7 +1719,7 @@ var PollingSource = class extends TypedEmitter {
1670
1719
  clearInterval(this.intervalId);
1671
1720
  this.intervalId = void 0;
1672
1721
  }
1673
- _optionalChain([this, 'access', _60 => _60.abortController, 'optionalAccess', _61 => _61.abort, 'call', _62 => _62()]);
1722
+ _optionalChain([this, 'access', _68 => _68.abortController, 'optionalAccess', _69 => _69.abort, 'call', _70 => _70()]);
1674
1723
  this.abortController = void 0;
1675
1724
  }
1676
1725
  };
@@ -1749,7 +1798,7 @@ async function connectStream(config, callbacks) {
1749
1798
  if (pingTimeoutId !== void 0) clearTimeout(pingTimeoutId);
1750
1799
  if (!initialDataReceived) return;
1751
1800
  pingTimeoutId = setTimeout(() => {
1752
- _optionalChain([responseBody, 'optionalAccess', _63 => _63.cancel, 'call', _64 => _64(), 'access', _65 => _65.catch, 'call', _66 => _66(() => {
1801
+ _optionalChain([responseBody, 'optionalAccess', _71 => _71.cancel, 'call', _72 => _72(), 'access', _73 => _73.catch, 'call', _74 => _74(() => {
1753
1802
  })]);
1754
1803
  connectionAbort.abort();
1755
1804
  }, PING_TIMEOUT_MS);
@@ -1771,7 +1820,7 @@ async function connectStream(config, callbacks) {
1771
1820
  if (vercelEnv) {
1772
1821
  headers["X-Vercel-Env"] = vercelEnv;
1773
1822
  }
1774
- const revision = _optionalChain([config, 'access', _67 => _67.revision, 'optionalCall', _68 => _68()]);
1823
+ const revision = _optionalChain([config, 'access', _75 => _75.revision, 'optionalCall', _76 => _76()]);
1775
1824
  if (revision !== void 0) {
1776
1825
  headers["X-Revision"] = String(revision);
1777
1826
  }
@@ -1833,7 +1882,7 @@ async function connectStream(config, callbacks) {
1833
1882
  resetPingTimeout();
1834
1883
  }
1835
1884
  if (message.type === "primed") {
1836
- _optionalChain([onPrimed, 'optionalCall', _69 => _69(message)]);
1885
+ _optionalChain([onPrimed, 'optionalCall', _77 => _77(message)]);
1837
1886
  retryCount = 0;
1838
1887
  if (!initialDataReceived) {
1839
1888
  initialDataReceived = true;
@@ -1856,7 +1905,7 @@ async function connectStream(config, callbacks) {
1856
1905
  clearTimeout(pingTimeoutId);
1857
1906
  abortController.signal.removeEventListener("abort", onMainAbort);
1858
1907
  if (!abortController.signal.aborted) {
1859
- _optionalChain([onDisconnect, 'optionalCall', _70 => _70()]);
1908
+ _optionalChain([onDisconnect, 'optionalCall', _78 => _78()]);
1860
1909
  retryCount++;
1861
1910
  const elapsed = Date.now() - lastAttemptTime;
1862
1911
  const minGap = Math.max(0, BASE_RETRY_DELAY_MS - elapsed);
@@ -1877,7 +1926,7 @@ async function connectStream(config, callbacks) {
1877
1926
  if (!connectionAbort.signal.aborted) {
1878
1927
  lastError = error;
1879
1928
  }
1880
- _optionalChain([onDisconnect, 'optionalCall', _71 => _71()]);
1929
+ _optionalChain([onDisconnect, 'optionalCall', _79 => _79()]);
1881
1930
  retryCount++;
1882
1931
  const elapsed = Date.now() - lastAttemptTime;
1883
1932
  const minGap = Math.max(0, BASE_RETRY_DELAY_MS - elapsed);
@@ -1956,7 +2005,7 @@ var StreamSource = class extends TypedEmitter {
1956
2005
  * Stop the stream connection.
1957
2006
  */
1958
2007
  stop() {
1959
- _optionalChain([this, 'access', _72 => _72.abortController, 'optionalAccess', _73 => _73.abort, 'call', _74 => _74()]);
2008
+ _optionalChain([this, 'access', _80 => _80.abortController, 'optionalAccess', _81 => _81.abort, 'call', _82 => _82()]);
1960
2009
  this.abortController = void 0;
1961
2010
  this.promise = void 0;
1962
2011
  }
@@ -2015,7 +2064,7 @@ var Controller = (_class6 = class {
2015
2064
  this.options = normalizeOptions(options);
2016
2065
  this.streamSource = new StreamSource(
2017
2066
  this.options,
2018
- () => _optionalChain([this, 'access', _75 => _75.data, 'optionalAccess', _76 => _76.revision])
2067
+ () => _optionalChain([this, 'access', _83 => _83.data, 'optionalAccess', _84 => _84.revision])
2019
2068
  );
2020
2069
  this.pollingSource = new PollingSource(this.options);
2021
2070
  this.bundledSource = new BundledSource({
@@ -2557,11 +2606,11 @@ var Controller = (_class6 = class {
2557
2606
  duration: Date.now() - startTime,
2558
2607
  mode: mode === "streaming" ? "stream" : mode === "polling" ? "poll" : mode
2559
2608
  };
2560
- const configUpdatedAt = _optionalChain([this, 'access', _77 => _77.data, 'optionalAccess', _78 => _78.configUpdatedAt]);
2609
+ const configUpdatedAt = _optionalChain([this, 'access', _85 => _85.data, 'optionalAccess', _86 => _86.configUpdatedAt]);
2561
2610
  if (typeof configUpdatedAt === "number") {
2562
2611
  trackOptions.configUpdatedAt = configUpdatedAt;
2563
2612
  }
2564
- const revision = _optionalChain([this, 'access', _79 => _79.data, 'optionalAccess', _80 => _80.revision]);
2613
+ const revision = _optionalChain([this, 'access', _87 => _87.data, 'optionalAccess', _88 => _88.revision]);
2565
2614
  if (typeof revision === "number") {
2566
2615
  trackOptions.revision = revision;
2567
2616
  }
@@ -2759,4 +2808,4 @@ var { flagsClient, resetDefaultFlagsClient, createClient } = make(
2759
2808
 
2760
2809
 
2761
2810
  exports.ResolutionReason = ResolutionReason; exports.evaluate = evaluate; exports.FallbackNotFoundError = FallbackNotFoundError; exports.FallbackEntryNotFoundError = FallbackEntryNotFoundError; exports.Controller = Controller; exports.flagsClient = flagsClient; exports.resetDefaultFlagsClient = resetDefaultFlagsClient; exports.createClient = createClient;
2762
- //# sourceMappingURL=chunk-3WMSFQ2F.cjs.map
2811
+ //# sourceMappingURL=chunk-UYSSHTCJ.cjs.map