@vercel/flags-core 1.8.1 → 1.8.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # @vercel/flags-core
2
2
 
3
+ ## 1.8.2
4
+
5
+ ### Patch Changes
6
+
7
+ - [#497](https://github.com/vercel/flags/pull/497) [`4848877`](https://github.com/vercel/flags/commit/4848877ca60c8595745b7759e351e936d7fe5889) Thanks [@feugy](https://github.com/feugy)! - Allow passing a custom `waitUntil` function to `createClient` for background
8
+ usage and exposure reporting. Pending exposure reports are drained by
9
+ `client.shutdown()`. The Next.js conditional export uses `after` from
10
+ `next/server` by default.
11
+
12
+ - [#490](https://github.com/vercel/flags/pull/490) [`186ea50`](https://github.com/vercel/flags/commit/186ea5092b22cd3eadf136824ecb2d7293a047fd) Thanks [@AndyBitz](https://github.com/AndyBitz)! - Use the runtime-provided ingest transport when available
13
+
3
14
  ## 1.8.1
4
15
 
5
16
  ### Patch Changes
package/README.md CHANGED
@@ -12,18 +12,27 @@ npm i @vercel/flags-core
12
12
 
13
13
  ## Usage
14
14
 
15
+ Create a shared client at module scope, but evaluate flags inside a request handler when using Vercel OIDC authentication. `evaluate()` and `bulkEvaluate()` initialize the client automatically on first use; you do not need to call `initialize()` first.
16
+
17
+ For example, in an Express app deployed to Vercel:
18
+
15
19
  ```ts
20
+ import express from 'express';
16
21
  import { createClient } from '@vercel/flags-core';
17
22
 
18
- const client = createClient(process.env.FLAGS!);
19
-
20
- await client.initialize();
23
+ const app = express();
24
+ const client = createClient(); // Uses Vercel OIDC; does not initialize yet.
21
25
 
22
- const result = await client.evaluate<boolean>('show-new-feature', false, {
23
- user: { id: 'user-123' },
26
+ app.get('/api/feature', async (_req, res) => {
27
+ const result = await client.evaluate<boolean>('show-new-feature', false);
28
+ res.json({ enabled: result.value });
24
29
  });
30
+
31
+ export default app;
25
32
  ```
26
33
 
34
+ Outside Vercel, pass an SDK key explicitly: `createClient(process.env.FLAGS)`.
35
+
27
36
  ## Evaluation Metrics
28
37
 
29
38
  To associate evaluation metrics with an environment, pass the
@@ -1,5 +1,6 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2; var _class3; var _class4; var _class5; var _class6;// src/index.next-js.ts
2
2
  var _cache = require('next/cache');
3
+ var _server = require('next/server');
3
4
 
4
5
  // src/evaluate.ts
5
6
  var _jsxxhash = require('js-xxhash');
@@ -558,7 +559,7 @@ function bulkEvaluate(flags, shared) {
558
559
  }
559
560
 
560
561
  // package.json
561
- var version = "1.8.1";
562
+ var version = "1.8.2";
562
563
 
563
564
  // src/lib/report-value.ts
564
565
  function internalReportValue(key, value, data) {
@@ -793,9 +794,11 @@ function createCreateRawClient(fns) {
793
794
  return function createRawClient({
794
795
  controller,
795
796
  origin,
796
- experimental_reportExposures
797
+ experimental_reportExposures,
798
+ waitUntil: waitUntil2 = _functions.waitUntil
797
799
  }) {
798
800
  const id = idCount++;
801
+ const pendingExposureReports = /* @__PURE__ */ new Set();
799
802
  controllerInstanceMap.set(id, {
800
803
  controller,
801
804
  initialized: false,
@@ -813,8 +816,10 @@ function createCreateRawClient(fns) {
813
816
  );
814
817
  }
815
818
  })();
819
+ pendingExposureReports.add(pending);
820
+ void pending.finally(() => pendingExposureReports.delete(pending));
816
821
  try {
817
- _functions.waitUntil.call(void 0, pending);
822
+ waitUntil2(pending);
818
823
  } catch (e) {
819
824
  }
820
825
  }
@@ -849,6 +854,9 @@ function createCreateRawClient(fns) {
849
854
  },
850
855
  shutdown: async () => {
851
856
  await fns.shutdown(id);
857
+ while (pendingExposureReports.size > 0) {
858
+ await Promise.all(pendingExposureReports);
859
+ }
852
860
  controllerInstanceMap.delete(id);
853
861
  },
854
862
  getDatafile: async () => {
@@ -1030,12 +1038,23 @@ function getJitteredWaitMs(baseMs, ratio) {
1030
1038
  return Math.floor(min + Math.random() * span);
1031
1039
  }
1032
1040
 
1041
+ // src/utils/runtime-ingest.ts
1042
+ var FLAGS_CONTEXT_SYMBOL = /* @__PURE__ */ Symbol.for("@vercel/flags-context");
1043
+ function getRuntimeIngest() {
1044
+ try {
1045
+ const context = globalThis[FLAGS_CONTEXT_SYMBOL];
1046
+ return typeof _optionalChain([context, 'optionalAccess', _31 => _31.ingest]) === "function" ? context.ingest : void 0;
1047
+ } catch (e5) {
1048
+ return void 0;
1049
+ }
1050
+ }
1051
+
1033
1052
  // src/utils/ingest.ts
1034
1053
  var MAX_RETRIES = 3;
1035
1054
  var MAX_EVENTS_PER_REQUEST = 2e3;
1036
1055
  var EVALUATING_OIDC_TOKEN_HEADER = "X-Vercel-Flags-OIDC-Token";
1037
1056
  var FLUSH_REASON_HEADER = "X-Vercel-Flags-Flush-Reason";
1038
- var isDebugMode = _optionalChain([process, 'access', _31 => _31.env, 'access', _32 => _32.DEBUG, 'optionalAccess', _33 => _33.includes, 'call', _34 => _34("@vercel/flags-core")]);
1057
+ var isDebugMode = _optionalChain([process, 'access', _32 => _32.env, 'access', _33 => _33.DEBUG, 'optionalAccess', _34 => _34.includes, 'call', _35 => _35("@vercel/flags-core")]);
1039
1058
  var debugLog = (...args) => {
1040
1059
  if (!isDebugMode) return;
1041
1060
  console.log(...args);
@@ -1044,7 +1063,7 @@ async function getEvaluatingOidcToken(auth) {
1044
1063
  if (!auth.sdkKey) return void 0;
1045
1064
  try {
1046
1065
  return await _oidc.getVercelOidcToken.call(void 0, );
1047
- } catch (e5) {
1066
+ } catch (e6) {
1048
1067
  return void 0;
1049
1068
  }
1050
1069
  }
@@ -1063,8 +1082,28 @@ async function getIngestHeaders(options, flushReason) {
1063
1082
  ...isDebugMode ? { "x-vercel-debug-ingest": "1" } : null
1064
1083
  };
1065
1084
  }
1085
+ function getRuntimeIngestHeaders(options, flushReason) {
1086
+ return {
1087
+ "Content-Type": "application/json",
1088
+ ...options.auth.sdkKey ? { Authorization: `Bearer ${options.auth.sdkKey}` } : null,
1089
+ "User-Agent": `VercelFlagsCore/${version}`,
1090
+ [FLUSH_REASON_HEADER]: flushReason,
1091
+ ..._nullishCoalesce(options.metricEnvironment, () => ( process.env.VERCEL_ENV)) ? {
1092
+ "X-Vercel-Env": _nullishCoalesce(options.metricEnvironment, () => ( process.env.VERCEL_ENV))
1093
+ } : null,
1094
+ ...isDebugMode ? { "x-vercel-debug-ingest": "1" } : null
1095
+ };
1096
+ }
1066
1097
  async function sendIngestEvents(options, events, flushId, flushReason) {
1067
- const eventsToSend = events.map((event) => event.ingestEvent());
1098
+ let eventsToSend = events.map((event) => event.ingestEvent());
1099
+ const runtimeIngest = getRuntimeIngest();
1100
+ if (runtimeIngest) {
1101
+ const headers = getRuntimeIngestHeaders(options, flushReason);
1102
+ eventsToSend = eventsToSend.filter(
1103
+ (event) => !runtimeIngest({ headers, body: [event] })
1104
+ );
1105
+ if (eventsToSend.length === 0) return;
1106
+ }
1068
1107
  for (let i = 0; i < eventsToSend.length; i += MAX_EVENTS_PER_REQUEST) {
1069
1108
  await sendIngestChunk(
1070
1109
  options,
@@ -1114,7 +1153,7 @@ var SYMBOL_FOR_REQ_CONTEXT = /* @__PURE__ */ Symbol.for("@vercel/request-context
1114
1153
  var fromSymbol = globalThis;
1115
1154
  function getRequestContext() {
1116
1155
  try {
1117
- const ctx = _optionalChain([fromSymbol, 'access', _35 => _35[SYMBOL_FOR_REQ_CONTEXT], 'optionalAccess', _36 => _36.get, 'optionalCall', _37 => _37()]);
1156
+ const ctx = _optionalChain([fromSymbol, 'access', _36 => _36[SYMBOL_FOR_REQ_CONTEXT], 'optionalAccess', _37 => _37.get, 'optionalCall', _38 => _38()]);
1118
1157
  if (ctx && Object.hasOwn(ctx, "headers")) {
1119
1158
  return {
1120
1159
  ctx,
@@ -1122,7 +1161,7 @@ function getRequestContext() {
1122
1161
  };
1123
1162
  }
1124
1163
  return { ctx, headers: void 0 };
1125
- } catch (e6) {
1164
+ } catch (e7) {
1126
1165
  return { ctx: void 0, headers: void 0 };
1127
1166
  }
1128
1167
  }
@@ -1133,10 +1172,12 @@ var IDLE_FLUSH_WAIT_MS = 5e3;
1133
1172
  var IDLE_FLUSH_JITTER_RATIO = 0.2;
1134
1173
  var MAX_FLUSH_WAIT_MS = 6e4;
1135
1174
  var Scheduler = (_class = class {
1136
- constructor(onFlush) {;_class.prototype.__init.call(this);_class.prototype.__init2.call(this);_class.prototype.__init3.call(this);_class.prototype.__init4.call(this);
1175
+ constructor(onFlush, scheduleTask = _functions.waitUntil) {;_class.prototype.__init.call(this);_class.prototype.__init2.call(this);_class.prototype.__init3.call(this);_class.prototype.__init4.call(this);
1137
1176
  this.onFlush = onFlush;
1177
+ this.scheduleTask = scheduleTask;
1138
1178
  }
1139
1179
 
1180
+
1140
1181
  __init() {this.resolveWait = null}
1141
1182
  __init2() {this.pending = null}
1142
1183
  __init3() {this.idleTimeout = null}
@@ -1151,8 +1192,8 @@ var Scheduler = (_class = class {
1151
1192
  await this.onFlush(reason);
1152
1193
  })();
1153
1194
  try {
1154
- _functions.waitUntil.call(void 0, this.pending);
1155
- } catch (e7) {
1195
+ this.scheduleTask(this.pending);
1196
+ } catch (e8) {
1156
1197
  }
1157
1198
  this.maxTimeout = setTimeout(
1158
1199
  () => this.resolveScheduledFlush("max_timeout"),
@@ -1179,7 +1220,7 @@ var Scheduler = (_class = class {
1179
1220
  }
1180
1221
  resolveScheduledFlush(reason) {
1181
1222
  this.clearTimeouts();
1182
- _optionalChain([this, 'access', _38 => _38.resolveWait, 'optionalCall', _39 => _39(reason)]);
1223
+ _optionalChain([this, 'access', _39 => _39.resolveWait, 'optionalCall', _40 => _40(reason)]);
1183
1224
  }
1184
1225
  reset() {
1185
1226
  this.pending = null;
@@ -1298,12 +1339,18 @@ var UsageTracker = (_class4 = class {
1298
1339
  __init7() {this.flushCount = 0}
1299
1340
 
1300
1341
 
1301
- __init8() {this.trackedRequests = /* @__PURE__ */ new WeakSet()}
1302
- __init9() {this.readEvents = []}
1303
- __init10() {this.evaluationEvents = /* @__PURE__ */ new Map()}
1304
- constructor(options) {;_class4.prototype.__init7.call(this);_class4.prototype.__init8.call(this);_class4.prototype.__init9.call(this);_class4.prototype.__init10.call(this);
1342
+
1343
+ __init8() {this.inflightFlushes = /* @__PURE__ */ new Set()}
1344
+ __init9() {this.trackedRequests = /* @__PURE__ */ new WeakSet()}
1345
+ __init10() {this.readEvents = []}
1346
+ __init11() {this.evaluationEvents = /* @__PURE__ */ new Map()}
1347
+ constructor(options) {;_class4.prototype.__init7.call(this);_class4.prototype.__init8.call(this);_class4.prototype.__init9.call(this);_class4.prototype.__init10.call(this);_class4.prototype.__init11.call(this);
1305
1348
  this.options = options;
1306
- this.scheduler = new Scheduler((reason) => this.flushEvents(reason));
1349
+ this.waitUntil = options.waitUntil;
1350
+ this.scheduler = new Scheduler(
1351
+ (reason) => this.flushEvents(reason),
1352
+ options.waitUntil
1353
+ );
1307
1354
  }
1308
1355
  /**
1309
1356
  * Triggers an immediate flush of any pending events.
@@ -1312,6 +1359,7 @@ var UsageTracker = (_class4 = class {
1312
1359
  async shutdown() {
1313
1360
  await this.scheduler.shutdown();
1314
1361
  await this.flushEvents("shutdown");
1362
+ await Promise.all([...this.inflightFlushes]);
1315
1363
  }
1316
1364
  /**
1317
1365
  * Tracks a config read event. Deduplicates by request context.
@@ -1323,7 +1371,7 @@ var UsageTracker = (_class4 = class {
1323
1371
  if (this.trackedRequests.has(ctx)) return;
1324
1372
  this.trackedRequests.add(ctx);
1325
1373
  this.readEvents.push(new FlagsConfigReadEvent(headers, options));
1326
- this.scheduler.scheduleFlush();
1374
+ this.requestFlush();
1327
1375
  } catch (error) {
1328
1376
  console.error("@vercel/flags-core: Failed to record event:", error);
1329
1377
  }
@@ -1347,7 +1395,7 @@ var UsageTracker = (_class4 = class {
1347
1395
  new FlagsEvaluationEvent(bucketedOptions)
1348
1396
  );
1349
1397
  }
1350
- this.scheduler.scheduleFlush();
1398
+ this.requestFlush();
1351
1399
  } catch (error) {
1352
1400
  console.error(
1353
1401
  "@vercel/flags-core: Failed to record evaluation event:",
@@ -1355,6 +1403,25 @@ var UsageTracker = (_class4 = class {
1355
1403
  );
1356
1404
  }
1357
1405
  }
1406
+ /**
1407
+ * Flushes immediately when the runtime provides an ingest transport,
1408
+ * otherwise falls back to the time-based scheduler.
1409
+ */
1410
+ requestFlush() {
1411
+ if (getRuntimeIngest()) {
1412
+ const flush = this.flushEvents("immediate").catch((error) => {
1413
+ console.error("@vercel/flags-core: Failed to flush events:", error);
1414
+ });
1415
+ this.inflightFlushes.add(flush);
1416
+ void flush.finally(() => this.inflightFlushes.delete(flush));
1417
+ try {
1418
+ _optionalChain([this, 'access', _41 => _41.waitUntil, 'optionalCall', _42 => _42(flush)]);
1419
+ } catch (e9) {
1420
+ }
1421
+ } else {
1422
+ this.scheduler.scheduleFlush();
1423
+ }
1424
+ }
1358
1425
  /**
1359
1426
  * Send all events to the ingest service
1360
1427
  */
@@ -1427,7 +1494,7 @@ var BundledSource = class {
1427
1494
  */
1428
1495
  async tryLoad() {
1429
1496
  const result = await this.getResult();
1430
- if (_optionalChain([result, 'optionalAccess', _40 => _40.state]) === "ok" && result.definitions) {
1497
+ if (_optionalChain([result, 'optionalAccess', _43 => _43.state]) === "ok" && result.definitions) {
1431
1498
  return result.definitions;
1432
1499
  }
1433
1500
  return void 0;
@@ -1467,19 +1534,20 @@ async function fetchDatafile(options) {
1467
1534
  signal: controller.signal
1468
1535
  });
1469
1536
  clearTimeout(timeoutId);
1470
- _optionalChain([options, 'access', _41 => _41.signal, 'optionalAccess', _42 => _42.removeEventListener, 'call', _43 => _43("abort", onExternalAbort)]);
1537
+ _optionalChain([options, 'access', _44 => _44.signal, 'optionalAccess', _45 => _45.removeEventListener, 'call', _46 => _46("abort", onExternalAbort)]);
1471
1538
  if (!res.ok) {
1472
1539
  throw new Error(`Failed to fetch data: ${res.statusText}`);
1473
1540
  }
1474
1541
  return res.json();
1475
1542
  } catch (error) {
1476
1543
  clearTimeout(timeoutId);
1477
- _optionalChain([options, 'access', _44 => _44.signal, 'optionalAccess', _45 => _45.removeEventListener, 'call', _46 => _46("abort", onExternalAbort)]);
1544
+ _optionalChain([options, 'access', _47 => _47.signal, 'optionalAccess', _48 => _48.removeEventListener, 'call', _49 => _49("abort", onExternalAbort)]);
1478
1545
  throw error instanceof Error ? error : new Error("Unknown fetch error");
1479
1546
  }
1480
1547
  }
1481
1548
 
1482
1549
  // src/controller/normalized-options.ts
1550
+
1483
1551
  var DEFAULT_STREAM_INIT_TIMEOUT_MS = 3e3;
1484
1552
  var DEFAULT_POLLING_INTERVAL_MS = 3e4;
1485
1553
  var MIN_POLLING_INTERVAL_MS = 3e4;
@@ -1523,6 +1591,7 @@ function normalizeOptions(options) {
1523
1591
  polling,
1524
1592
  buildStep,
1525
1593
  fetch: _nullishCoalesce(options.fetch, () => ( globalThis.fetch)),
1594
+ waitUntil: _nullishCoalesce(options.waitUntil, () => ( _functions.waitUntil)),
1526
1595
  host: "https://flags.vercel.com",
1527
1596
  metricEnvironment: options.metricEnvironment,
1528
1597
  clientName: options.clientName,
@@ -1531,8 +1600,8 @@ function normalizeOptions(options) {
1531
1600
  }
1532
1601
 
1533
1602
  // src/controller/typed-emitter.ts
1534
- var TypedEmitter = (_class5 = class {constructor() { _class5.prototype.__init11.call(this); }
1535
- __init11() {this.handlers = /* @__PURE__ */ new Map()}
1603
+ var TypedEmitter = (_class5 = class {constructor() { _class5.prototype.__init12.call(this); }
1604
+ __init12() {this.handlers = /* @__PURE__ */ new Map()}
1536
1605
  on(event, handler) {
1537
1606
  let set = this.handlers.get(event);
1538
1607
  if (!set) {
@@ -1542,7 +1611,7 @@ var TypedEmitter = (_class5 = class {constructor() { _class5.prototype.__init11.
1542
1611
  set.add(handler);
1543
1612
  }
1544
1613
  off(event, handler) {
1545
- _optionalChain([this, 'access', _47 => _47.handlers, 'access', _48 => _48.get, 'call', _49 => _49(event), 'optionalAccess', _50 => _50.delete, 'call', _51 => _51(handler)]);
1614
+ _optionalChain([this, 'access', _50 => _50.handlers, 'access', _51 => _51.get, 'call', _52 => _52(event), 'optionalAccess', _53 => _53.delete, 'call', _54 => _54(handler)]);
1546
1615
  }
1547
1616
  emit(event, ...args) {
1548
1617
  const set = this.handlers.get(event);
@@ -1568,11 +1637,11 @@ var PollingSource = class extends TypedEmitter {
1568
1637
  * Emits 'data' on success, 'error' on failure.
1569
1638
  */
1570
1639
  async poll() {
1571
- if (_optionalChain([this, 'access', _52 => _52.abortController, 'optionalAccess', _53 => _53.signal, 'access', _54 => _54.aborted])) return;
1640
+ if (_optionalChain([this, 'access', _55 => _55.abortController, 'optionalAccess', _56 => _56.signal, 'access', _57 => _57.aborted])) return;
1572
1641
  try {
1573
1642
  const data = await fetchDatafile({
1574
1643
  ...this.config,
1575
- signal: _optionalChain([this, 'access', _55 => _55.abortController, 'optionalAccess', _56 => _56.signal])
1644
+ signal: _optionalChain([this, 'access', _58 => _58.abortController, 'optionalAccess', _59 => _59.signal])
1576
1645
  });
1577
1646
  this.emit("data", data);
1578
1647
  } catch (error) {
@@ -1601,7 +1670,7 @@ var PollingSource = class extends TypedEmitter {
1601
1670
  clearInterval(this.intervalId);
1602
1671
  this.intervalId = void 0;
1603
1672
  }
1604
- _optionalChain([this, 'access', _57 => _57.abortController, 'optionalAccess', _58 => _58.abort, 'call', _59 => _59()]);
1673
+ _optionalChain([this, 'access', _60 => _60.abortController, 'optionalAccess', _61 => _61.abort, 'call', _62 => _62()]);
1605
1674
  this.abortController = void 0;
1606
1675
  }
1607
1676
  };
@@ -1680,7 +1749,7 @@ async function connectStream(config, callbacks) {
1680
1749
  if (pingTimeoutId !== void 0) clearTimeout(pingTimeoutId);
1681
1750
  if (!initialDataReceived) return;
1682
1751
  pingTimeoutId = setTimeout(() => {
1683
- _optionalChain([responseBody, 'optionalAccess', _60 => _60.cancel, 'call', _61 => _61(), 'access', _62 => _62.catch, 'call', _63 => _63(() => {
1752
+ _optionalChain([responseBody, 'optionalAccess', _63 => _63.cancel, 'call', _64 => _64(), 'access', _65 => _65.catch, 'call', _66 => _66(() => {
1684
1753
  })]);
1685
1754
  connectionAbort.abort();
1686
1755
  }, PING_TIMEOUT_MS);
@@ -1702,7 +1771,7 @@ async function connectStream(config, callbacks) {
1702
1771
  if (vercelEnv) {
1703
1772
  headers["X-Vercel-Env"] = vercelEnv;
1704
1773
  }
1705
- const revision = _optionalChain([config, 'access', _64 => _64.revision, 'optionalCall', _65 => _65()]);
1774
+ const revision = _optionalChain([config, 'access', _67 => _67.revision, 'optionalCall', _68 => _68()]);
1706
1775
  if (revision !== void 0) {
1707
1776
  headers["X-Revision"] = String(revision);
1708
1777
  }
@@ -1748,7 +1817,7 @@ async function connectStream(config, callbacks) {
1748
1817
  let message;
1749
1818
  try {
1750
1819
  message = JSON.parse(line);
1751
- } catch (e8) {
1820
+ } catch (e10) {
1752
1821
  console.warn(
1753
1822
  "@vercel/flags-core: Failed to parse stream message, skipping"
1754
1823
  );
@@ -1764,7 +1833,7 @@ async function connectStream(config, callbacks) {
1764
1833
  resetPingTimeout();
1765
1834
  }
1766
1835
  if (message.type === "primed") {
1767
- _optionalChain([onPrimed, 'optionalCall', _66 => _66(message)]);
1836
+ _optionalChain([onPrimed, 'optionalCall', _69 => _69(message)]);
1768
1837
  retryCount = 0;
1769
1838
  if (!initialDataReceived) {
1770
1839
  initialDataReceived = true;
@@ -1787,7 +1856,7 @@ async function connectStream(config, callbacks) {
1787
1856
  clearTimeout(pingTimeoutId);
1788
1857
  abortController.signal.removeEventListener("abort", onMainAbort);
1789
1858
  if (!abortController.signal.aborted) {
1790
- _optionalChain([onDisconnect, 'optionalCall', _67 => _67()]);
1859
+ _optionalChain([onDisconnect, 'optionalCall', _70 => _70()]);
1791
1860
  retryCount++;
1792
1861
  const elapsed = Date.now() - lastAttemptTime;
1793
1862
  const minGap = Math.max(0, BASE_RETRY_DELAY_MS - elapsed);
@@ -1808,7 +1877,7 @@ async function connectStream(config, callbacks) {
1808
1877
  if (!connectionAbort.signal.aborted) {
1809
1878
  lastError = error;
1810
1879
  }
1811
- _optionalChain([onDisconnect, 'optionalCall', _68 => _68()]);
1880
+ _optionalChain([onDisconnect, 'optionalCall', _71 => _71()]);
1812
1881
  retryCount++;
1813
1882
  const elapsed = Date.now() - lastAttemptTime;
1814
1883
  const minGap = Math.max(0, BASE_RETRY_DELAY_MS - elapsed);
@@ -1887,7 +1956,7 @@ var StreamSource = class extends TypedEmitter {
1887
1956
  * Stop the stream connection.
1888
1957
  */
1889
1958
  stop() {
1890
- _optionalChain([this, 'access', _69 => _69.abortController, 'optionalAccess', _70 => _70.abort, 'call', _71 => _71()]);
1959
+ _optionalChain([this, 'access', _72 => _72.abortController, 'optionalAccess', _73 => _73.abort, 'call', _74 => _74()]);
1891
1960
  this.abortController = void 0;
1892
1961
  this.promise = void 0;
1893
1962
  }
@@ -1922,31 +1991,31 @@ function parseConfigUpdatedAt(value) {
1922
1991
  var Controller = (_class6 = class {
1923
1992
 
1924
1993
  // State machine
1925
- __init12() {this.state = "idle"}
1994
+ __init13() {this.state = "idle"}
1926
1995
  // Data state — tagged with origin
1927
1996
 
1928
1997
  // Memoized data spread for read() / getDatafile().
1929
1998
  // Rebuilt only when `this.data` reference changes (e.g. on stream/poll update).
1930
1999
  // Holds the result of stripping `_origin`; metrics are appended per-call.
1931
- __init13() {this.dataViewSource = void 0}
1932
- __init14() {this.dataViewBase = void 0}
2000
+ __init14() {this.dataViewSource = void 0}
2001
+ __init15() {this.dataViewBase = void 0}
1933
2002
  // Sources (I/O delegates)
1934
2003
 
1935
2004
 
1936
2005
 
1937
2006
  // Usage tracking
1938
2007
 
1939
- __init15() {this.isFirstGetData = true}
2008
+ __init16() {this.isFirstGetData = true}
1940
2009
  // Build-step deduplication
1941
- __init16() {this.buildDataPromise = null}
1942
- __init17() {this.buildReadTracked = false}
2010
+ __init17() {this.buildDataPromise = null}
2011
+ __init18() {this.buildReadTracked = false}
1943
2012
  // Suppresses usage tracking when the SDK key is unauthorized
1944
- __init18() {this.unauthorized = false}
1945
- constructor(options) {;_class6.prototype.__init12.call(this);_class6.prototype.__init13.call(this);_class6.prototype.__init14.call(this);_class6.prototype.__init15.call(this);_class6.prototype.__init16.call(this);_class6.prototype.__init17.call(this);_class6.prototype.__init18.call(this);_class6.prototype.__init19.call(this);_class6.prototype.__init20.call(this);_class6.prototype.__init21.call(this);_class6.prototype.__init22.call(this);_class6.prototype.__init23.call(this);_class6.prototype.__init24.call(this);
2013
+ __init19() {this.unauthorized = false}
2014
+ constructor(options) {;_class6.prototype.__init13.call(this);_class6.prototype.__init14.call(this);_class6.prototype.__init15.call(this);_class6.prototype.__init16.call(this);_class6.prototype.__init17.call(this);_class6.prototype.__init18.call(this);_class6.prototype.__init19.call(this);_class6.prototype.__init20.call(this);_class6.prototype.__init21.call(this);_class6.prototype.__init22.call(this);_class6.prototype.__init23.call(this);_class6.prototype.__init24.call(this);_class6.prototype.__init25.call(this);
1946
2015
  this.options = normalizeOptions(options);
1947
2016
  this.streamSource = new StreamSource(
1948
2017
  this.options,
1949
- () => _optionalChain([this, 'access', _72 => _72.data, 'optionalAccess', _73 => _73.revision])
2018
+ () => _optionalChain([this, 'access', _75 => _75.data, 'optionalAccess', _76 => _76.revision])
1950
2019
  );
1951
2020
  this.pollingSource = new PollingSource(this.options);
1952
2021
  this.bundledSource = new BundledSource({
@@ -1960,32 +2029,32 @@ var Controller = (_class6 = class {
1960
2029
  this.usageTracker = new UsageTracker(this.options);
1961
2030
  }
1962
2031
  // Source event handlers (stored for cleanup)
1963
- __init19() {this.onStreamData = (data) => {
2032
+ __init20() {this.onStreamData = (data) => {
1964
2033
  if (this.isNewerData(data)) {
1965
2034
  this.data = tagData(data, "stream");
1966
2035
  }
1967
2036
  }}
1968
- __init20() {this.onStreamPrimed = () => {
2037
+ __init21() {this.onStreamPrimed = () => {
1969
2038
  if (this.state === "degraded" || this.state === "initializing:stream") {
1970
2039
  this.transition("streaming");
1971
2040
  }
1972
2041
  }}
1973
- __init21() {this.onStreamConnected = () => {
2042
+ __init22() {this.onStreamConnected = () => {
1974
2043
  if (this.state === "degraded" || this.state === "initializing:stream") {
1975
2044
  this.transition("streaming");
1976
2045
  }
1977
2046
  }}
1978
- __init22() {this.onStreamDisconnected = () => {
2047
+ __init23() {this.onStreamDisconnected = () => {
1979
2048
  if (this.state === "streaming") {
1980
2049
  this.transition("degraded");
1981
2050
  }
1982
2051
  }}
1983
- __init23() {this.onPollData = (data) => {
2052
+ __init24() {this.onPollData = (data) => {
1984
2053
  if (this.isNewerData(data)) {
1985
2054
  this.data = tagData(data, "poll");
1986
2055
  }
1987
2056
  }}
1988
- __init24() {this.onPollError = (error) => {
2057
+ __init25() {this.onPollError = (error) => {
1989
2058
  console.error("@vercel/flags-core: Poll failed:", error);
1990
2059
  }}
1991
2060
  // ---------------------------------------------------------------------------
@@ -2054,7 +2123,7 @@ var Controller = (_class6 = class {
2054
2123
  if (bundled) {
2055
2124
  this.data = tagData(bundled, "bundled");
2056
2125
  }
2057
- } catch (e9) {
2126
+ } catch (e11) {
2058
2127
  }
2059
2128
  }
2060
2129
  if (this.data) {
@@ -2156,7 +2225,7 @@ var Controller = (_class6 = class {
2156
2225
  this.data = tagData(fetched, "fetched");
2157
2226
  result = this.data;
2158
2227
  cacheStatus = "MISS";
2159
- } catch (e10) {
2228
+ } catch (e12) {
2160
2229
  throw new Error(
2161
2230
  "@vercel/flags-core: No flag definitions available. Initialize the client or provide a datafile."
2162
2231
  );
@@ -2275,7 +2344,7 @@ var Controller = (_class6 = class {
2275
2344
  return true;
2276
2345
  }
2277
2346
  return false;
2278
- } catch (e11) {
2347
+ } catch (e13) {
2279
2348
  return false;
2280
2349
  }
2281
2350
  }
@@ -2300,7 +2369,7 @@ var Controller = (_class6 = class {
2300
2369
  return true;
2301
2370
  }
2302
2371
  return false;
2303
- } catch (e12) {
2372
+ } catch (e14) {
2304
2373
  clearTimeout(timeoutId);
2305
2374
  return false;
2306
2375
  }
@@ -2350,7 +2419,7 @@ var Controller = (_class6 = class {
2350
2419
  fetch: this.options.fetch
2351
2420
  });
2352
2421
  return tagData(fetched, "fetched");
2353
- } catch (e13) {
2422
+ } catch (e15) {
2354
2423
  }
2355
2424
  throw new Error(
2356
2425
  "@vercel/flags-core: No flag definitions available during build. Provide a datafile or bundled definitions."
@@ -2384,7 +2453,7 @@ var Controller = (_class6 = class {
2384
2453
  this.data = tagData(fetched, "fetched");
2385
2454
  this.transition("degraded");
2386
2455
  return;
2387
- } catch (e14) {
2456
+ } catch (e16) {
2388
2457
  }
2389
2458
  }
2390
2459
  throw new Error(
@@ -2436,7 +2505,7 @@ var Controller = (_class6 = class {
2436
2505
  this.data = tagData(fetched, "fetched");
2437
2506
  this.transition("degraded");
2438
2507
  return [this.data, "MISS"];
2439
- } catch (e15) {
2508
+ } catch (e17) {
2440
2509
  }
2441
2510
  }
2442
2511
  throw new Error(
@@ -2488,11 +2557,11 @@ var Controller = (_class6 = class {
2488
2557
  duration: Date.now() - startTime,
2489
2558
  mode: mode === "streaming" ? "stream" : mode === "polling" ? "poll" : mode
2490
2559
  };
2491
- const configUpdatedAt = _optionalChain([this, 'access', _74 => _74.data, 'optionalAccess', _75 => _75.configUpdatedAt]);
2560
+ const configUpdatedAt = _optionalChain([this, 'access', _77 => _77.data, 'optionalAccess', _78 => _78.configUpdatedAt]);
2492
2561
  if (typeof configUpdatedAt === "number") {
2493
2562
  trackOptions.configUpdatedAt = configUpdatedAt;
2494
2563
  }
2495
- const revision = _optionalChain([this, 'access', _76 => _76.data, 'optionalAccess', _77 => _77.revision]);
2564
+ const revision = _optionalChain([this, 'access', _79 => _79.data, 'optionalAccess', _80 => _80.revision]);
2496
2565
  if (typeof revision === "number") {
2497
2566
  trackOptions.revision = revision;
2498
2567
  }
@@ -2525,7 +2594,7 @@ function parseSdkKeyFromFlagsConnectionString(text) {
2525
2594
  const params = new URLSearchParams(text.slice(6));
2526
2595
  const sdkKey = params.get("sdkKey");
2527
2596
  if (sdkKey && SDK_KEY_REGEX.test(sdkKey)) return sdkKey;
2528
- } catch (e16) {
2597
+ } catch (e18) {
2529
2598
  }
2530
2599
  return null;
2531
2600
  }
@@ -2534,7 +2603,7 @@ function parseSdkKeyFromFlagsConnectionString(text) {
2534
2603
  async function getOidcToken() {
2535
2604
  try {
2536
2605
  return await _oidc.getVercelOidcToken.call(void 0, );
2537
- } catch (e17) {
2606
+ } catch (e19) {
2538
2607
  throw new Error(
2539
2608
  [
2540
2609
  "@vercel/flags-core: Failed to get OIDC token.",
@@ -2596,7 +2665,7 @@ var Authentication = class {
2596
2665
  };
2597
2666
 
2598
2667
  // src/index.make.ts
2599
- function make(createRawClient) {
2668
+ function make(createRawClient, defaults) {
2600
2669
  let _defaultFlagsClient = null;
2601
2670
  function createClient2(sdkKeyOrConnectionStringOrOptions, options) {
2602
2671
  const optionsOnly = typeof sdkKeyOrConnectionStringOrOptions === "object" && sdkKeyOrConnectionStringOrOptions !== null;
@@ -2604,10 +2673,16 @@ function make(createRawClient) {
2604
2673
  const createClientOptions = optionsOnly ? sdkKeyOrConnectionStringOrOptions : options;
2605
2674
  const { experimental_reportExposures, ...controllerOptions } = _nullishCoalesce(createClientOptions, () => ( {}));
2606
2675
  const auth = new Authentication(sdkKeyOrConnectionString);
2607
- const controller = new Controller({ auth, ...controllerOptions });
2676
+ const waitUntil2 = _nullishCoalesce(controllerOptions.waitUntil, () => ( defaults.waitUntil));
2677
+ const controller = new Controller({
2678
+ auth,
2679
+ ...controllerOptions,
2680
+ waitUntil: waitUntil2
2681
+ });
2608
2682
  return createRawClient({
2609
2683
  controller,
2610
2684
  origin: { provider: "vercel", sdkKey: auth.sdkKey },
2685
+ waitUntil: waitUntil2,
2611
2686
  ...experimental_reportExposures ? { experimental_reportExposures } : {}
2612
2687
  });
2613
2688
  }
@@ -2634,7 +2709,7 @@ function setCacheLife() {
2634
2709
  try {
2635
2710
  _cache.cacheLife.call(void 0, { revalidate: 0, expire: 0 });
2636
2711
  _cache.cacheLife.call(void 0, { stale: 60 });
2637
- } catch (e18) {
2712
+ } catch (e20) {
2638
2713
  }
2639
2714
  }
2640
2715
  var cachedFns = {
@@ -2670,7 +2745,8 @@ var cachedFns = {
2670
2745
  }
2671
2746
  };
2672
2747
  var { flagsClient, resetDefaultFlagsClient, createClient } = make(
2673
- createCreateRawClient(cachedFns)
2748
+ createCreateRawClient(cachedFns),
2749
+ { waitUntil: _server.after }
2674
2750
  );
2675
2751
 
2676
2752
 
@@ -2683,4 +2759,4 @@ var { flagsClient, resetDefaultFlagsClient, createClient } = make(
2683
2759
 
2684
2760
 
2685
2761
  exports.ResolutionReason = ResolutionReason; exports.evaluate = evaluate; exports.FallbackNotFoundError = FallbackNotFoundError; exports.FallbackEntryNotFoundError = FallbackEntryNotFoundError; exports.Controller = Controller; exports.flagsClient = flagsClient; exports.resetDefaultFlagsClient = resetDefaultFlagsClient; exports.createClient = createClient;
2686
- //# sourceMappingURL=chunk-CKL562EU.cjs.map
2762
+ //# sourceMappingURL=chunk-3WMSFQ2F.cjs.map