@vercel/flags-core 1.8.2-d31aae4-20260916165712 → 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.
@@ -73,7 +73,7 @@ function getSplitBoundaries(outcome) {
73
73
  function getCompiledRegex(rhs) {
74
74
  const cached = compiledRegexCache.get(rhs);
75
75
  if (cached) return cached;
76
- const compiled = new RegExp(rhs.pattern, rhs.flags.replace(/[gy]/, ""));
76
+ const compiled = new RegExp(rhs.pattern, rhs.flags.replace(/[gy]/g, ""));
77
77
  compiledRegexCache.set(rhs, compiled);
78
78
  return compiled;
79
79
  }
@@ -576,7 +576,7 @@ function bulkEvaluate(flags, shared) {
576
576
  }
577
577
 
578
578
  // package.json
579
- var version = "1.8.2-d31aae4-20260916165712";
579
+ var version = "1.8.3";
580
580
 
581
581
  // src/lib/report-value.ts
582
582
  function internalReportValue(key, value, data) {
@@ -1526,40 +1526,86 @@ var BundledSource = class {
1526
1526
 
1527
1527
  // src/controller/fetch-datafile.ts
1528
1528
  var DEFAULT_FETCH_TIMEOUT_MS = 1e4;
1529
+ var DEFAULT_MAX_ATTEMPTS = 3;
1530
+ var DatafileHttpError = class extends Error {
1531
+ constructor(status, statusText) {
1532
+ super(`Failed to fetch data: ${statusText}`);
1533
+ this.status = status;
1534
+ }
1535
+
1536
+ get retryable() {
1537
+ return this.status === 408 || this.status === 429 || this.status >= 500;
1538
+ }
1539
+ };
1529
1540
  async function fetchDatafile(options) {
1530
- const token = await options.auth.resolveToken();
1541
+ _optionalChain([options, 'access', _44 => _44.signal, 'optionalAccess', _45 => _45.throwIfAborted, 'call', _46 => _46()]);
1542
+ const maxAttempts = _nullishCoalesce(options.maxAttempts, () => ( DEFAULT_MAX_ATTEMPTS));
1543
+ if (!Number.isInteger(maxAttempts) || maxAttempts < 1) {
1544
+ throw new Error("maxAttempts must be a positive integer");
1545
+ }
1531
1546
  const controller = new AbortController();
1547
+ const { signal } = controller;
1548
+ let rejectAbort;
1549
+ const aborted = new Promise((_, reject) => {
1550
+ rejectAbort = reject;
1551
+ });
1552
+ const onAbort = () => rejectAbort(signal.reason);
1553
+ signal.addEventListener("abort", onAbort, { once: true });
1554
+ const onExternalAbort = () => controller.abort(_optionalChain([options, 'access', _47 => _47.signal, 'optionalAccess', _48 => _48.reason]));
1555
+ _optionalChain([options, 'access', _49 => _49.signal, 'optionalAccess', _50 => _50.addEventListener, 'call', _51 => _51("abort", onExternalAbort, { once: true })]);
1532
1556
  const timeoutId = setTimeout(
1533
- () => controller.abort(),
1557
+ () => controller.abort(
1558
+ new Error("@vercel/flags-core: Datafile fetch deadline exceeded")
1559
+ ),
1534
1560
  DEFAULT_FETCH_TIMEOUT_MS
1535
1561
  );
1536
- const onExternalAbort = () => controller.abort();
1537
- if (options.signal) {
1538
- if (options.signal.aborted) {
1539
- clearTimeout(timeoutId);
1540
- throw new Error("Fetch aborted");
1541
- }
1542
- options.signal.addEventListener("abort", onExternalAbort, { once: true });
1543
- }
1544
- try {
1562
+ let delay;
1563
+ const fetchAttempt = async () => {
1564
+ const token = await options.auth.resolveToken();
1565
+ signal.throwIfAborted();
1545
1566
  const res = await options.fetch(`${options.host}/v1/datafile`, {
1546
1567
  headers: {
1547
1568
  Authorization: `Bearer ${token}`,
1548
1569
  "User-Agent": `VercelFlagsCore/${version}`,
1549
1570
  ...process.env.VERCEL_ENV ? { "X-Vercel-Env": process.env.VERCEL_ENV } : null
1550
1571
  },
1551
- signal: controller.signal
1572
+ signal
1552
1573
  });
1553
- clearTimeout(timeoutId);
1554
- _optionalChain([options, 'access', _44 => _44.signal, 'optionalAccess', _45 => _45.removeEventListener, 'call', _46 => _46("abort", onExternalAbort)]);
1574
+ signal.throwIfAborted();
1555
1575
  if (!res.ok) {
1556
- throw new Error(`Failed to fetch data: ${res.statusText}`);
1576
+ void _optionalChain([res, 'access', _52 => _52.body, 'optionalAccess', _53 => _53.cancel, 'call', _54 => _54(), 'access', _55 => _55.catch, 'call', _56 => _56(() => {
1577
+ })]);
1578
+ throw new DatafileHttpError(res.status, res.statusText);
1557
1579
  }
1558
- return res.json();
1559
- } catch (error) {
1580
+ const data = await res.json();
1581
+ signal.throwIfAborted();
1582
+ return data;
1583
+ };
1584
+ try {
1585
+ for (let attempt = 0; ; attempt++) {
1586
+ if (attempt > 0) {
1587
+ await Promise.race([
1588
+ new Promise((resolve) => {
1589
+ delay = setTimeout(resolve, 100 * 2 ** (attempt - 1));
1590
+ }),
1591
+ aborted
1592
+ ]);
1593
+ }
1594
+ signal.throwIfAborted();
1595
+ try {
1596
+ return await Promise.race([fetchAttempt(), aborted]);
1597
+ } catch (error) {
1598
+ signal.throwIfAborted();
1599
+ if (attempt === maxAttempts - 1 || error instanceof DatafileHttpError && !error.retryable) {
1600
+ throw error instanceof Error ? error : new Error("Unknown fetch error");
1601
+ }
1602
+ }
1603
+ }
1604
+ } finally {
1560
1605
  clearTimeout(timeoutId);
1561
- _optionalChain([options, 'access', _47 => _47.signal, 'optionalAccess', _48 => _48.removeEventListener, 'call', _49 => _49("abort", onExternalAbort)]);
1562
- throw error instanceof Error ? error : new Error("Unknown fetch error");
1606
+ clearTimeout(delay);
1607
+ signal.removeEventListener("abort", onAbort);
1608
+ _optionalChain([options, 'access', _57 => _57.signal, 'optionalAccess', _58 => _58.removeEventListener, 'call', _59 => _59("abort", onExternalAbort)]);
1563
1609
  }
1564
1610
  }
1565
1611
 
@@ -1628,7 +1674,7 @@ var TypedEmitter = (_class5 = class {constructor() { _class5.prototype.__init12.
1628
1674
  set.add(handler);
1629
1675
  }
1630
1676
  off(event, handler) {
1631
- _optionalChain([this, 'access', _50 => _50.handlers, 'access', _51 => _51.get, 'call', _52 => _52(event), 'optionalAccess', _53 => _53.delete, 'call', _54 => _54(handler)]);
1677
+ _optionalChain([this, 'access', _60 => _60.handlers, 'access', _61 => _61.get, 'call', _62 => _62(event), 'optionalAccess', _63 => _63.delete, 'call', _64 => _64(handler)]);
1632
1678
  }
1633
1679
  emit(event, ...args) {
1634
1680
  const set = this.handlers.get(event);
@@ -1654,14 +1700,17 @@ var PollingSource = class extends TypedEmitter {
1654
1700
  * Emits 'data' on success, 'error' on failure.
1655
1701
  */
1656
1702
  async poll() {
1657
- if (_optionalChain([this, 'access', _55 => _55.abortController, 'optionalAccess', _56 => _56.signal, 'access', _57 => _57.aborted])) return;
1703
+ if (_optionalChain([this, 'access', _65 => _65.abortController, 'optionalAccess', _66 => _66.signal, 'access', _67 => _67.aborted])) return;
1704
+ this.abortController ??= new AbortController();
1705
+ const controller = this.abortController;
1658
1706
  try {
1659
1707
  const data = await fetchDatafile({
1660
1708
  ...this.config,
1661
- signal: _optionalChain([this, 'access', _58 => _58.abortController, 'optionalAccess', _59 => _59.signal])
1709
+ signal: controller.signal
1662
1710
  });
1663
1711
  this.emit("data", data);
1664
1712
  } catch (error) {
1713
+ controller.signal.throwIfAborted();
1665
1714
  const err = error instanceof Error ? error : new Error("Unknown poll error");
1666
1715
  this.emit("error", err);
1667
1716
  }
@@ -1673,9 +1722,9 @@ var PollingSource = class extends TypedEmitter {
1673
1722
  */
1674
1723
  startInterval() {
1675
1724
  if (this.intervalId) return;
1676
- this.abortController = new AbortController();
1677
1725
  this.intervalId = setInterval(
1678
- () => void this.poll(),
1726
+ () => void this.poll().catch(() => {
1727
+ }),
1679
1728
  this.config.polling.intervalMs
1680
1729
  );
1681
1730
  }
@@ -1687,7 +1736,7 @@ var PollingSource = class extends TypedEmitter {
1687
1736
  clearInterval(this.intervalId);
1688
1737
  this.intervalId = void 0;
1689
1738
  }
1690
- _optionalChain([this, 'access', _60 => _60.abortController, 'optionalAccess', _61 => _61.abort, 'call', _62 => _62()]);
1739
+ _optionalChain([this, 'access', _68 => _68.abortController, 'optionalAccess', _69 => _69.abort, 'call', _70 => _70()]);
1691
1740
  this.abortController = void 0;
1692
1741
  }
1693
1742
  };
@@ -1766,7 +1815,7 @@ async function connectStream(config, callbacks) {
1766
1815
  if (pingTimeoutId !== void 0) clearTimeout(pingTimeoutId);
1767
1816
  if (!initialDataReceived) return;
1768
1817
  pingTimeoutId = setTimeout(() => {
1769
- _optionalChain([responseBody, 'optionalAccess', _63 => _63.cancel, 'call', _64 => _64(), 'access', _65 => _65.catch, 'call', _66 => _66(() => {
1818
+ _optionalChain([responseBody, 'optionalAccess', _71 => _71.cancel, 'call', _72 => _72(), 'access', _73 => _73.catch, 'call', _74 => _74(() => {
1770
1819
  })]);
1771
1820
  connectionAbort.abort();
1772
1821
  }, PING_TIMEOUT_MS);
@@ -1788,7 +1837,7 @@ async function connectStream(config, callbacks) {
1788
1837
  if (vercelEnv) {
1789
1838
  headers["X-Vercel-Env"] = vercelEnv;
1790
1839
  }
1791
- const revision = _optionalChain([config, 'access', _67 => _67.revision, 'optionalCall', _68 => _68()]);
1840
+ const revision = _optionalChain([config, 'access', _75 => _75.revision, 'optionalCall', _76 => _76()]);
1792
1841
  if (revision !== void 0) {
1793
1842
  headers["X-Revision"] = String(revision);
1794
1843
  }
@@ -1850,7 +1899,7 @@ async function connectStream(config, callbacks) {
1850
1899
  resetPingTimeout();
1851
1900
  }
1852
1901
  if (message.type === "primed") {
1853
- _optionalChain([onPrimed, 'optionalCall', _69 => _69(message)]);
1902
+ _optionalChain([onPrimed, 'optionalCall', _77 => _77(message)]);
1854
1903
  retryCount = 0;
1855
1904
  if (!initialDataReceived) {
1856
1905
  initialDataReceived = true;
@@ -1873,7 +1922,7 @@ async function connectStream(config, callbacks) {
1873
1922
  clearTimeout(pingTimeoutId);
1874
1923
  abortController.signal.removeEventListener("abort", onMainAbort);
1875
1924
  if (!abortController.signal.aborted) {
1876
- _optionalChain([onDisconnect, 'optionalCall', _70 => _70()]);
1925
+ _optionalChain([onDisconnect, 'optionalCall', _78 => _78()]);
1877
1926
  retryCount++;
1878
1927
  const elapsed = Date.now() - lastAttemptTime;
1879
1928
  const minGap = Math.max(0, BASE_RETRY_DELAY_MS - elapsed);
@@ -1894,7 +1943,7 @@ async function connectStream(config, callbacks) {
1894
1943
  if (!connectionAbort.signal.aborted) {
1895
1944
  lastError = error;
1896
1945
  }
1897
- _optionalChain([onDisconnect, 'optionalCall', _71 => _71()]);
1946
+ _optionalChain([onDisconnect, 'optionalCall', _79 => _79()]);
1898
1947
  retryCount++;
1899
1948
  const elapsed = Date.now() - lastAttemptTime;
1900
1949
  const minGap = Math.max(0, BASE_RETRY_DELAY_MS - elapsed);
@@ -1973,7 +2022,7 @@ var StreamSource = class extends TypedEmitter {
1973
2022
  * Stop the stream connection.
1974
2023
  */
1975
2024
  stop() {
1976
- _optionalChain([this, 'access', _72 => _72.abortController, 'optionalAccess', _73 => _73.abort, 'call', _74 => _74()]);
2025
+ _optionalChain([this, 'access', _80 => _80.abortController, 'optionalAccess', _81 => _81.abort, 'call', _82 => _82()]);
1977
2026
  this.abortController = void 0;
1978
2027
  this.promise = void 0;
1979
2028
  }
@@ -2032,7 +2081,7 @@ var Controller = (_class6 = class {
2032
2081
  this.options = normalizeOptions(options);
2033
2082
  this.streamSource = new StreamSource(
2034
2083
  this.options,
2035
- () => _optionalChain([this, 'access', _75 => _75.data, 'optionalAccess', _76 => _76.revision])
2084
+ () => _optionalChain([this, 'access', _83 => _83.data, 'optionalAccess', _84 => _84.revision])
2036
2085
  );
2037
2086
  this.pollingSource = new PollingSource(this.options);
2038
2087
  this.bundledSource = new BundledSource({
@@ -2574,11 +2623,11 @@ var Controller = (_class6 = class {
2574
2623
  duration: Date.now() - startTime,
2575
2624
  mode: mode === "streaming" ? "stream" : mode === "polling" ? "poll" : mode
2576
2625
  };
2577
- const configUpdatedAt = _optionalChain([this, 'access', _77 => _77.data, 'optionalAccess', _78 => _78.configUpdatedAt]);
2626
+ const configUpdatedAt = _optionalChain([this, 'access', _85 => _85.data, 'optionalAccess', _86 => _86.configUpdatedAt]);
2578
2627
  if (typeof configUpdatedAt === "number") {
2579
2628
  trackOptions.configUpdatedAt = configUpdatedAt;
2580
2629
  }
2581
- const revision = _optionalChain([this, 'access', _79 => _79.data, 'optionalAccess', _80 => _80.revision]);
2630
+ const revision = _optionalChain([this, 'access', _87 => _87.data, 'optionalAccess', _88 => _88.revision]);
2582
2631
  if (typeof revision === "number") {
2583
2632
  trackOptions.revision = revision;
2584
2633
  }
@@ -2750,4 +2799,4 @@ var {
2750
2799
 
2751
2800
 
2752
2801
  exports.ResolutionReason = ResolutionReason; exports.evaluate = evaluate; exports.FallbackNotFoundError = FallbackNotFoundError; exports.FallbackEntryNotFoundError = FallbackEntryNotFoundError; exports.Controller = Controller; exports.flagsClient = flagsClient; exports.resetDefaultFlagsClient = resetDefaultFlagsClient; exports.createClient = createClient;
2753
- //# sourceMappingURL=chunk-2WO37TKH.cjs.map
2802
+ //# sourceMappingURL=chunk-UX43BKE3.cjs.map