@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.
@@ -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-d31aae4-20260916165712";
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
+ status;
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
+ options.signal?.throwIfAborted();
1525
+ const maxAttempts = 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(options.signal?.reason);
1538
+ options.signal?.addEventListener("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
- options.signal?.removeEventListener("abort", onExternalAbort);
1557
+ signal.throwIfAborted();
1538
1558
  if (!res.ok) {
1539
- throw new Error(`Failed to fetch data: ${res.statusText}`);
1559
+ void res.body?.cancel().catch(() => {
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);
1589
+ clearTimeout(delay);
1590
+ signal.removeEventListener("abort", onAbort);
1544
1591
  options.signal?.removeEventListener("abort", onExternalAbort);
1545
- throw error instanceof Error ? error : new Error("Unknown fetch error");
1546
1592
  }
1547
1593
  }
1548
1594
 
@@ -1638,13 +1684,16 @@ var PollingSource = class extends TypedEmitter {
1638
1684
  */
1639
1685
  async poll() {
1640
1686
  if (this.abortController?.signal.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: this.abortController?.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
  }
@@ -2759,4 +2808,4 @@ export {
2759
2808
  resetDefaultFlagsClient,
2760
2809
  createClient
2761
2810
  };
2762
- //# sourceMappingURL=chunk-DAWMMLXD.js.map
2811
+ //# sourceMappingURL=chunk-ZUA6TEG3.js.map