@vercel/flags-core 1.9.0-ea4d7de-20260922121332 → 1.9.0

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.
@@ -1,4 +1,4 @@
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(); } } async function _asyncNullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return await 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; var _class7;var __defProp = Object.defineProperty;
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;var __defProp = Object.defineProperty;
2
2
  var __export = (target, all) => {
3
3
  for (var name in all)
4
4
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -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
  }
@@ -450,7 +450,7 @@ function resolveOutcome(params, outcome) {
450
450
  break;
451
451
  }
452
452
  }
453
- if (exhausted) currentPromille = PROMILLE_SCALE;
453
+ if (exhausted) currentPromille = _nullishCoalesce(outcome.finalPromille, () => ( PROMILLE_SCALE));
454
454
  if (currentPromille <= 0) {
455
455
  return {
456
456
  ...rollFromVariant,
@@ -576,7 +576,7 @@ function bulkEvaluate(flags, shared) {
576
576
  }
577
577
 
578
578
  // package.json
579
- var version = "1.9.0-ea4d7de-20260922121332";
579
+ var version = "1.9.0";
580
580
 
581
581
  // src/lib/report-value.ts
582
582
  function internalReportValue(key, value, data) {
@@ -1040,6 +1040,126 @@ async function readBundledDefinitions(auth) {
1040
1040
  // src/utils/ingest.ts
1041
1041
  var _oidc = require('@vercel/oidc');
1042
1042
 
1043
+ // src/controller/auth.ts
1044
+
1045
+
1046
+ // src/utils/sdk-keys.ts
1047
+ var SDK_KEY_REGEX = /^vf_(?:server|client)_/;
1048
+ function isValidSdkKey(value) {
1049
+ return SDK_KEY_REGEX.test(value);
1050
+ }
1051
+ var PROJECT_ID_REGEX = /^[A-Za-z0-9_]{1,64}$/;
1052
+ function isValidProjectId(value) {
1053
+ return PROJECT_ID_REGEX.test(value);
1054
+ }
1055
+ function parseFlagsConnectionString(text) {
1056
+ if (SDK_KEY_REGEX.test(text)) return { sdkKey: text, projectId: null };
1057
+ if (!text.startsWith("flags:")) return null;
1058
+ try {
1059
+ const params = new URLSearchParams(text.slice(6));
1060
+ return {
1061
+ sdkKey: params.get("sdkKey") || null,
1062
+ projectId: params.get("projectId") || null
1063
+ };
1064
+ } catch (e5) {
1065
+ return null;
1066
+ }
1067
+ }
1068
+
1069
+ // src/controller/auth.ts
1070
+ var SOURCE_PROJECT_HEADER = "X-Vercel-Flags-Project-Id";
1071
+ function authHeaders(token, sourceProjectId) {
1072
+ return {
1073
+ Authorization: `Bearer ${token}`,
1074
+ ...sourceProjectId ? { [SOURCE_PROJECT_HEADER]: sourceProjectId } : null
1075
+ };
1076
+ }
1077
+ function unauthorizedMessage(sourceProjectId) {
1078
+ if (!sourceProjectId) return "unauthorized (401)";
1079
+ return `unauthorized (401): this deployment is not allowed to read the flags of project "${sourceProjectId}". Check the connection string and that the project is connected.`;
1080
+ }
1081
+ async function getOidcToken() {
1082
+ try {
1083
+ return await _oidc.getVercelOidcToken.call(void 0, );
1084
+ } catch (e6) {
1085
+ throw new Error(
1086
+ [
1087
+ "@vercel/flags-core: Failed to get OIDC token.",
1088
+ "Are you running in a Vercel Environment where OIDC tokens are available?",
1089
+ "Did you mean to use an SDK Key instead? Use the environment variable FLAGS or pass it directly to the client."
1090
+ ].join(" ")
1091
+ );
1092
+ }
1093
+ }
1094
+ function getProjectIdFromOidcToken(oidcToken) {
1095
+ const tokenParts = oidcToken.split(".");
1096
+ if (tokenParts.length !== 3 || !tokenParts[1]) {
1097
+ throw new Error("@vercel/flags-core: Invalid OIDC token");
1098
+ }
1099
+ const payload = JSON.parse(
1100
+ Buffer.from(tokenParts[1], "base64url").toString("utf8")
1101
+ );
1102
+ if (typeof payload.project_id !== "string" || !payload.project_id) {
1103
+ throw new Error(
1104
+ "@vercel/flags-core: Missing project_id claim in OIDC token"
1105
+ );
1106
+ }
1107
+ return payload.project_id;
1108
+ }
1109
+ var Authentication = class {
1110
+
1111
+
1112
+ constructor(sdkKeyOrConnectionString) {
1113
+ if (sdkKeyOrConnectionString !== void 0) {
1114
+ if (typeof sdkKeyOrConnectionString !== "string") {
1115
+ throw new Error(
1116
+ `@vercel/flags-core: Invalid sdkKey. Expected string, got ${typeof sdkKeyOrConnectionString}`
1117
+ );
1118
+ }
1119
+ const parsed = parseFlagsConnectionString(sdkKeyOrConnectionString);
1120
+ if (_optionalChain([parsed, 'optionalAccess', _31 => _31.sdkKey]) && parsed.projectId) {
1121
+ throw new Error(
1122
+ "@vercel/flags-core: A connection string must contain either sdkKey or projectId, not both"
1123
+ );
1124
+ }
1125
+ if (_optionalChain([parsed, 'optionalAccess', _32 => _32.sdkKey])) {
1126
+ if (!isValidSdkKey(parsed.sdkKey)) {
1127
+ throw new Error("@vercel/flags-core: Missing sdkKey");
1128
+ }
1129
+ this.sdkKey = parsed.sdkKey;
1130
+ } else if (_optionalChain([parsed, 'optionalAccess', _33 => _33.projectId])) {
1131
+ if (!isValidProjectId(parsed.projectId)) {
1132
+ throw new Error(
1133
+ "@vercel/flags-core: Invalid projectId in connection string"
1134
+ );
1135
+ }
1136
+ this.sourceProjectId = parsed.projectId;
1137
+ } else {
1138
+ throw new Error("@vercel/flags-core: Missing sdkKey");
1139
+ }
1140
+ }
1141
+ }
1142
+ async resolveToken() {
1143
+ if (this.sdkKey) {
1144
+ return this.sdkKey;
1145
+ }
1146
+ return await getOidcToken();
1147
+ }
1148
+ async resolveBundledDefinitionsLookup() {
1149
+ if (this.sdkKey) {
1150
+ return { type: "sdk-key", sdkKey: this.sdkKey };
1151
+ }
1152
+ if (this.sourceProjectId) {
1153
+ return { type: "project-id", projectId: this.sourceProjectId };
1154
+ }
1155
+ const oidcToken = await this.resolveToken();
1156
+ return {
1157
+ type: "project-id",
1158
+ projectId: getProjectIdFromOidcToken(oidcToken)
1159
+ };
1160
+ }
1161
+ };
1162
+
1043
1163
  // src/utils/backoff.ts
1044
1164
  var DEFAULT_BASE_MS = 250;
1045
1165
  var DEFAULT_CAP_MS = 5e3;
@@ -1055,12 +1175,26 @@ function getJitteredWaitMs(baseMs, ratio) {
1055
1175
  return Math.floor(min + Math.random() * span);
1056
1176
  }
1057
1177
 
1178
+ // src/utils/runtime-ingest.ts
1179
+ var FLAGS_CONTEXT_SYMBOL = /* @__PURE__ */ Symbol.for("@vercel/flags-context");
1180
+ function getRuntimeIngestFor(auth) {
1181
+ return auth.sourceProjectId ? void 0 : getRuntimeIngest();
1182
+ }
1183
+ function getRuntimeIngest() {
1184
+ try {
1185
+ const context = globalThis[FLAGS_CONTEXT_SYMBOL];
1186
+ return typeof _optionalChain([context, 'optionalAccess', _34 => _34.ingest]) === "function" ? context.ingest : void 0;
1187
+ } catch (e7) {
1188
+ return void 0;
1189
+ }
1190
+ }
1191
+
1058
1192
  // src/utils/ingest.ts
1059
1193
  var MAX_RETRIES = 3;
1060
1194
  var MAX_EVENTS_PER_REQUEST = 2e3;
1061
1195
  var EVALUATING_OIDC_TOKEN_HEADER = "X-Vercel-Flags-OIDC-Token";
1062
1196
  var FLUSH_REASON_HEADER = "X-Vercel-Flags-Flush-Reason";
1063
- var isDebugMode = _optionalChain([process, 'access', _31 => _31.env, 'access', _32 => _32.DEBUG, 'optionalAccess', _33 => _33.includes, 'call', _34 => _34("@vercel/flags-core")]);
1197
+ var isDebugMode = _optionalChain([process, 'access', _35 => _35.env, 'access', _36 => _36.DEBUG, 'optionalAccess', _37 => _37.includes, 'call', _38 => _38("@vercel/flags-core")]);
1064
1198
  var debugLog = (...args) => {
1065
1199
  if (!isDebugMode) return;
1066
1200
  console.log(...args);
@@ -1069,7 +1203,7 @@ async function getEvaluatingOidcToken(auth) {
1069
1203
  if (!auth.sdkKey) return void 0;
1070
1204
  try {
1071
1205
  return await _oidc.getVercelOidcToken.call(void 0, );
1072
- } catch (e5) {
1206
+ } catch (e8) {
1073
1207
  return void 0;
1074
1208
  }
1075
1209
  }
@@ -1078,7 +1212,7 @@ async function getIngestHeaders(options, flushReason) {
1078
1212
  const evaluatingOidcToken = await getEvaluatingOidcToken(options.auth);
1079
1213
  return {
1080
1214
  "Content-Type": "application/json",
1081
- Authorization: `Bearer ${token}`,
1215
+ ...authHeaders(token, options.auth.sourceProjectId),
1082
1216
  "User-Agent": `VercelFlagsCore/${version}`,
1083
1217
  [FLUSH_REASON_HEADER]: flushReason,
1084
1218
  ..._nullishCoalesce(options.metricEnvironment, () => ( process.env.VERCEL_ENV)) ? {
@@ -1088,8 +1222,28 @@ async function getIngestHeaders(options, flushReason) {
1088
1222
  ...isDebugMode ? { "x-vercel-debug-ingest": "1" } : null
1089
1223
  };
1090
1224
  }
1225
+ function getRuntimeIngestHeaders(options, flushReason) {
1226
+ return {
1227
+ "Content-Type": "application/json",
1228
+ ...options.auth.sdkKey ? { Authorization: `Bearer ${options.auth.sdkKey}` } : null,
1229
+ "User-Agent": `VercelFlagsCore/${version}`,
1230
+ [FLUSH_REASON_HEADER]: flushReason,
1231
+ ..._nullishCoalesce(options.metricEnvironment, () => ( process.env.VERCEL_ENV)) ? {
1232
+ "X-Vercel-Env": _nullishCoalesce(options.metricEnvironment, () => ( process.env.VERCEL_ENV))
1233
+ } : null,
1234
+ ...isDebugMode ? { "x-vercel-debug-ingest": "1" } : null
1235
+ };
1236
+ }
1091
1237
  async function sendIngestEvents(options, events, flushId, flushReason) {
1092
- const eventsToSend = events.map((event) => event.ingestEvent());
1238
+ let eventsToSend = events.map((event) => event.ingestEvent());
1239
+ const runtimeIngest = getRuntimeIngestFor(options.auth);
1240
+ if (runtimeIngest) {
1241
+ const headers = getRuntimeIngestHeaders(options, flushReason);
1242
+ eventsToSend = eventsToSend.filter(
1243
+ (event) => !runtimeIngest({ headers, body: [event] })
1244
+ );
1245
+ if (eventsToSend.length === 0) return;
1246
+ }
1093
1247
  for (let i = 0; i < eventsToSend.length; i += MAX_EVENTS_PER_REQUEST) {
1094
1248
  await sendIngestChunk(
1095
1249
  options,
@@ -1139,7 +1293,7 @@ var SYMBOL_FOR_REQ_CONTEXT = /* @__PURE__ */ Symbol.for("@vercel/request-context
1139
1293
  var fromSymbol = globalThis;
1140
1294
  function getRequestContext() {
1141
1295
  try {
1142
- const ctx = _optionalChain([fromSymbol, 'access', _35 => _35[SYMBOL_FOR_REQ_CONTEXT], 'optionalAccess', _36 => _36.get, 'optionalCall', _37 => _37()]);
1296
+ const ctx = _optionalChain([fromSymbol, 'access', _39 => _39[SYMBOL_FOR_REQ_CONTEXT], 'optionalAccess', _40 => _40.get, 'optionalCall', _41 => _41()]);
1143
1297
  if (ctx && Object.hasOwn(ctx, "headers")) {
1144
1298
  return {
1145
1299
  ctx,
@@ -1147,7 +1301,7 @@ function getRequestContext() {
1147
1301
  };
1148
1302
  }
1149
1303
  return { ctx, headers: void 0 };
1150
- } catch (e6) {
1304
+ } catch (e9) {
1151
1305
  return { ctx: void 0, headers: void 0 };
1152
1306
  }
1153
1307
  }
@@ -1179,7 +1333,7 @@ var Scheduler = (_class = class {
1179
1333
  })();
1180
1334
  try {
1181
1335
  this.scheduleTask(this.pending);
1182
- } catch (e7) {
1336
+ } catch (e10) {
1183
1337
  }
1184
1338
  this.maxTimeout = setTimeout(
1185
1339
  () => this.resolveScheduledFlush("max_timeout"),
@@ -1206,7 +1360,7 @@ var Scheduler = (_class = class {
1206
1360
  }
1207
1361
  resolveScheduledFlush(reason) {
1208
1362
  this.clearTimeouts();
1209
- _optionalChain([this, 'access', _38 => _38.resolveWait, 'optionalCall', _39 => _39(reason)]);
1363
+ _optionalChain([this, 'access', _42 => _42.resolveWait, 'optionalCall', _43 => _43(reason)]);
1210
1364
  }
1211
1365
  reset() {
1212
1366
  this.pending = null;
@@ -1325,11 +1479,14 @@ var UsageTracker = (_class4 = class {
1325
1479
  __init7() {this.flushCount = 0}
1326
1480
 
1327
1481
 
1328
- __init8() {this.trackedRequests = /* @__PURE__ */ new WeakSet()}
1329
- __init9() {this.readEvents = []}
1330
- __init10() {this.evaluationEvents = /* @__PURE__ */ new Map()}
1331
- constructor(options) {;_class4.prototype.__init7.call(this);_class4.prototype.__init8.call(this);_class4.prototype.__init9.call(this);_class4.prototype.__init10.call(this);
1482
+
1483
+ __init8() {this.inflightFlushes = /* @__PURE__ */ new Set()}
1484
+ __init9() {this.trackedRequests = /* @__PURE__ */ new WeakSet()}
1485
+ __init10() {this.readEvents = []}
1486
+ __init11() {this.evaluationEvents = /* @__PURE__ */ new Map()}
1487
+ 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);
1332
1488
  this.options = options;
1489
+ this.waitUntil = options.waitUntil;
1333
1490
  this.scheduler = new Scheduler(
1334
1491
  (reason) => this.flushEvents(reason),
1335
1492
  options.waitUntil
@@ -1342,6 +1499,7 @@ var UsageTracker = (_class4 = class {
1342
1499
  async shutdown() {
1343
1500
  await this.scheduler.shutdown();
1344
1501
  await this.flushEvents("shutdown");
1502
+ await Promise.all([...this.inflightFlushes]);
1345
1503
  }
1346
1504
  /**
1347
1505
  * Tracks a config read event. Deduplicates by request context.
@@ -1353,7 +1511,7 @@ var UsageTracker = (_class4 = class {
1353
1511
  if (this.trackedRequests.has(ctx)) return;
1354
1512
  this.trackedRequests.add(ctx);
1355
1513
  this.readEvents.push(new FlagsConfigReadEvent(headers, options));
1356
- this.scheduler.scheduleFlush();
1514
+ this.requestFlush();
1357
1515
  } catch (error) {
1358
1516
  console.error("@vercel/flags-core: Failed to record event:", error);
1359
1517
  }
@@ -1377,7 +1535,7 @@ var UsageTracker = (_class4 = class {
1377
1535
  new FlagsEvaluationEvent(bucketedOptions)
1378
1536
  );
1379
1537
  }
1380
- this.scheduler.scheduleFlush();
1538
+ this.requestFlush();
1381
1539
  } catch (error) {
1382
1540
  console.error(
1383
1541
  "@vercel/flags-core: Failed to record evaluation event:",
@@ -1385,6 +1543,25 @@ var UsageTracker = (_class4 = class {
1385
1543
  );
1386
1544
  }
1387
1545
  }
1546
+ /**
1547
+ * Flushes immediately when the runtime provides an ingest transport,
1548
+ * otherwise falls back to the time-based scheduler.
1549
+ */
1550
+ requestFlush() {
1551
+ if (getRuntimeIngestFor(this.options.auth)) {
1552
+ const flush = this.flushEvents("immediate").catch((error) => {
1553
+ console.error("@vercel/flags-core: Failed to flush events:", error);
1554
+ });
1555
+ this.inflightFlushes.add(flush);
1556
+ void flush.finally(() => this.inflightFlushes.delete(flush));
1557
+ try {
1558
+ _optionalChain([this, 'access', _44 => _44.waitUntil, 'optionalCall', _45 => _45(flush)]);
1559
+ } catch (e11) {
1560
+ }
1561
+ } else {
1562
+ this.scheduler.scheduleFlush();
1563
+ }
1564
+ }
1388
1565
  /**
1389
1566
  * Send all events to the ingest service
1390
1567
  */
@@ -1457,7 +1634,7 @@ var BundledSource = class {
1457
1634
  */
1458
1635
  async tryLoad() {
1459
1636
  const result = await this.getResult();
1460
- if (_optionalChain([result, 'optionalAccess', _40 => _40.state]) === "ok" && result.definitions) {
1637
+ if (_optionalChain([result, 'optionalAccess', _46 => _46.state]) === "ok" && result.definitions) {
1461
1638
  return result.definitions;
1462
1639
  }
1463
1640
  return void 0;
@@ -1472,192 +1649,91 @@ var BundledSource = class {
1472
1649
 
1473
1650
  // src/controller/fetch-datafile.ts
1474
1651
  var DEFAULT_FETCH_TIMEOUT_MS = 1e4;
1652
+ var DEFAULT_MAX_ATTEMPTS = 3;
1653
+ var DatafileHttpError = class extends Error {
1654
+ constructor(status, statusText) {
1655
+ super(`Failed to fetch data: ${statusText}`);
1656
+ this.status = status;
1657
+ }
1658
+
1659
+ get retryable() {
1660
+ return this.status === 408 || this.status === 429 || this.status >= 500;
1661
+ }
1662
+ };
1475
1663
  async function fetchDatafile(options) {
1476
- const token = await options.auth.resolveToken();
1664
+ _optionalChain([options, 'access', _47 => _47.signal, 'optionalAccess', _48 => _48.throwIfAborted, 'call', _49 => _49()]);
1665
+ const maxAttempts = _nullishCoalesce(options.maxAttempts, () => ( DEFAULT_MAX_ATTEMPTS));
1666
+ if (!Number.isInteger(maxAttempts) || maxAttempts < 1) {
1667
+ throw new Error("maxAttempts must be a positive integer");
1668
+ }
1477
1669
  const controller = new AbortController();
1670
+ const { signal } = controller;
1671
+ let rejectAbort;
1672
+ const aborted = new Promise((_, reject) => {
1673
+ rejectAbort = reject;
1674
+ });
1675
+ const onAbort = () => rejectAbort(signal.reason);
1676
+ signal.addEventListener("abort", onAbort, { once: true });
1677
+ const onExternalAbort = () => controller.abort(_optionalChain([options, 'access', _50 => _50.signal, 'optionalAccess', _51 => _51.reason]));
1678
+ _optionalChain([options, 'access', _52 => _52.signal, 'optionalAccess', _53 => _53.addEventListener, 'call', _54 => _54("abort", onExternalAbort, { once: true })]);
1478
1679
  const timeoutId = setTimeout(
1479
- () => controller.abort(),
1680
+ () => controller.abort(
1681
+ new Error("@vercel/flags-core: Datafile fetch deadline exceeded")
1682
+ ),
1480
1683
  DEFAULT_FETCH_TIMEOUT_MS
1481
1684
  );
1482
- const onExternalAbort = () => controller.abort();
1483
- if (options.signal) {
1484
- if (options.signal.aborted) {
1485
- clearTimeout(timeoutId);
1486
- throw new Error("Fetch aborted");
1487
- }
1488
- options.signal.addEventListener("abort", onExternalAbort, { once: true });
1489
- }
1490
- try {
1685
+ let delay;
1686
+ const fetchAttempt = async () => {
1687
+ const token = await options.auth.resolveToken();
1688
+ signal.throwIfAborted();
1491
1689
  const res = await options.fetch(`${options.host}/v1/datafile`, {
1492
1690
  headers: {
1493
- Authorization: `Bearer ${token}`,
1691
+ ...authHeaders(token, options.auth.sourceProjectId),
1494
1692
  "User-Agent": `VercelFlagsCore/${version}`,
1495
1693
  ...process.env.VERCEL_ENV ? { "X-Vercel-Env": process.env.VERCEL_ENV } : null
1496
1694
  },
1497
- signal: controller.signal
1695
+ signal
1498
1696
  });
1499
- clearTimeout(timeoutId);
1500
- _optionalChain([options, 'access', _41 => _41.signal, 'optionalAccess', _42 => _42.removeEventListener, 'call', _43 => _43("abort", onExternalAbort)]);
1697
+ signal.throwIfAborted();
1501
1698
  if (!res.ok) {
1502
- throw new Error(`Failed to fetch data: ${res.statusText}`);
1503
- }
1504
- return res.json();
1505
- } catch (error) {
1506
- clearTimeout(timeoutId);
1507
- _optionalChain([options, 'access', _44 => _44.signal, 'optionalAccess', _45 => _45.removeEventListener, 'call', _46 => _46("abort", onExternalAbort)]);
1508
- throw error instanceof Error ? error : new Error("Unknown fetch error");
1509
- }
1510
- }
1511
-
1512
- // src/controller/tagged-data.ts
1513
- function tagData(data, origin) {
1514
- const tagged = { ...data, _origin: origin };
1515
- if (origin === "fetched" || origin === "poll" || origin === "stream") {
1516
- tagged.fetchedAt = Date.now();
1517
- } else if (typeof data.fetchedAt !== "number" || !Number.isFinite(data.fetchedAt) || data.fetchedAt < 0) {
1518
- delete tagged.fetchedAt;
1519
- }
1520
- return tagged;
1521
- }
1522
- function originToMetricsSource(origin) {
1523
- switch (origin) {
1524
- case "stream":
1525
- case "poll":
1526
- case "provided":
1527
- return "in-memory";
1528
- case "fetched":
1529
- return "remote";
1530
- case "bundled":
1531
- return "embedded";
1532
- }
1533
- }
1534
-
1535
- // src/controller/typed-emitter.ts
1536
- var TypedEmitter = (_class5 = class {constructor() { _class5.prototype.__init11.call(this); }
1537
- __init11() {this.handlers = /* @__PURE__ */ new Map()}
1538
- on(event, handler) {
1539
- let set = this.handlers.get(event);
1540
- if (!set) {
1541
- set = /* @__PURE__ */ new Set();
1542
- this.handlers.set(event, set);
1543
- }
1544
- set.add(handler);
1545
- }
1546
- off(event, handler) {
1547
- _optionalChain([this, 'access', _47 => _47.handlers, 'access', _48 => _48.get, 'call', _49 => _49(event), 'optionalAccess', _50 => _50.delete, 'call', _51 => _51(handler)]);
1548
- }
1549
- emit(event, ...args) {
1550
- const set = this.handlers.get(event);
1551
- if (set) {
1552
- for (const handler of set) {
1553
- handler(...args);
1554
- }
1699
+ void _optionalChain([res, 'access', _55 => _55.body, 'optionalAccess', _56 => _56.cancel, 'call', _57 => _57(), 'access', _58 => _58.catch, 'call', _59 => _59(() => {
1700
+ })]);
1701
+ throw new DatafileHttpError(
1702
+ res.status,
1703
+ res.status === 401 && options.auth.sourceProjectId ? unauthorizedMessage(options.auth.sourceProjectId) : res.statusText
1704
+ );
1555
1705
  }
1556
- }
1557
- }, _class5);
1558
-
1559
- // src/controller/header-source.ts
1560
- var HeaderSource = (_class6 = class extends TypedEmitter {
1561
-
1562
-
1563
-
1564
- __init12() {this.highestObserved = 0}
1565
-
1566
- constructor(options) {
1567
- super();_class6.prototype.__init12.call(this);;
1568
- this.options = options;
1569
- }
1570
- fetchDatafile() {
1571
- if (this.promise) return this.promise;
1572
- const abortController = new AbortController();
1573
- this.abortController = abortController;
1574
- this.promise = fetchDatafile({
1575
- ...this.options,
1576
- signal: abortController.signal
1577
- }).then((data) => {
1578
- abortController.signal.throwIfAborted();
1579
- this.emit("data", data);
1580
- return data;
1581
- }).finally(() => {
1582
- if (this.abortController === abortController) {
1583
- this.promise = void 0;
1584
- this.abortController = void 0;
1706
+ const data = await res.json();
1707
+ signal.throwIfAborted();
1708
+ return data;
1709
+ };
1710
+ try {
1711
+ for (let attempt = 0; ; attempt++) {
1712
+ if (attempt > 0) {
1713
+ await Promise.race([
1714
+ new Promise((resolve) => {
1715
+ delay = setTimeout(resolve, 100 * 2 ** (attempt - 1));
1716
+ }),
1717
+ aborted
1718
+ ]);
1585
1719
  }
1586
- });
1587
- return this.promise;
1588
- }
1589
- getUpdatedAtHeader(projectId, header) {
1590
- if (!header) {
1591
- return;
1592
- }
1593
- const prefix = `flags_${projectId}=`;
1594
- const value = _optionalChain([header, 'access', _52 => _52.split, 'call', _53 => _53(";"), 'access', _54 => _54.map, 'call', _55 => _55((part) => part.trim()), 'access', _56 => _56.find, 'call', _57 => _57((part) => part.startsWith(prefix)), 'optionalAccess', _58 => _58.slice, 'call', _59 => _59(prefix.length)]);
1595
- const timestamp = Number(value);
1596
- return Number.isFinite(timestamp) && timestamp > 0 ? timestamp : void 0;
1597
- }
1598
- async resolveData(currentData, updatedAtHeader) {
1599
- if (!currentData.configUpdatedAt) {
1600
- return;
1601
- }
1602
- if (!updatedAtHeader) {
1603
- return;
1604
- }
1605
- const currentUpdatedAt = Number(currentData.configUpdatedAt);
1606
- if (updatedAtHeader <= currentUpdatedAt) {
1607
- return [currentData, "HIT"];
1608
- }
1609
- const freshAt = Math.max(
1610
- _nullishCoalesce(currentData.fetchedAt, () => ( -Infinity)),
1611
- _optionalChain([this, 'access', _60 => _60.lastSeen, 'optionalAccess', _61 => _61.version]) === currentUpdatedAt ? this.lastSeen.at : -Infinity
1612
- );
1613
- const { staleWhileRevalidateMs } = this.options;
1614
- if (staleWhileRevalidateMs > 0 && Date.now() - freshAt <= staleWhileRevalidateMs) {
1615
- const pending = this.fetchDatafile();
1616
- const signal = _optionalChain([this, 'access', _62 => _62.abortController, 'optionalAccess', _63 => _63.signal]);
1617
- const background = pending.catch((error) => {
1618
- if (!_optionalChain([signal, 'optionalAccess', _64 => _64.aborted])) {
1619
- console.error("@vercel/flags-core: Header refresh failed:", error);
1620
- }
1621
- });
1720
+ signal.throwIfAborted();
1622
1721
  try {
1623
- this.options.waitUntil(background);
1624
- } catch (e8) {
1722
+ return await Promise.race([fetchAttempt(), aborted]);
1723
+ } catch (error) {
1724
+ signal.throwIfAborted();
1725
+ if (attempt === maxAttempts - 1 || error instanceof DatafileHttpError && !error.retryable) {
1726
+ throw error instanceof Error ? error : new Error("Unknown fetch error");
1727
+ }
1625
1728
  }
1626
- return [currentData, "STALE"];
1627
- }
1628
- const data = await this.fetchDatafile();
1629
- return [tagData(data, "fetched"), "MISS"];
1630
- }
1631
- observe(version2, currentVersion) {
1632
- this.highestObserved = Math.max(this.highestObserved, version2);
1633
- if (version2 !== currentVersion || version2 !== this.highestObserved) return;
1634
- this.lastSeen = { version: version2, at: Date.now() };
1635
- }
1636
- async read(currentData) {
1637
- const { headers } = getRequestContext();
1638
- const header = _nullishCoalesce(_optionalChain([headers, 'optionalAccess', _65 => _65["x-vercel-flags-config-versions"]]), () => ( _optionalChain([headers, 'optionalAccess', _66 => _66["flags-config-versions"]])));
1639
- const data = await _asyncNullishCoalesce(currentData, async () => ( tagData(await this.fetchDatafile(), "fetched")));
1640
- const updatedAtHeader = this.getUpdatedAtHeader(data.projectId, header);
1641
- if (updatedAtHeader) {
1642
- this.observe(updatedAtHeader, Number(data.configUpdatedAt));
1643
1729
  }
1644
- if (!currentData) return [data, "MISS"];
1645
- return this.resolveData(currentData, updatedAtHeader);
1646
- }
1647
- isAvailable() {
1648
- return this.options.vercel && (this.options.stream.enabled || this.options.polling.enabled);
1649
- }
1650
- /**
1651
- * Abort the current header-driven fetch and discard its pending work.
1652
- */
1653
- stop() {
1654
- _optionalChain([this, 'access', _67 => _67.abortController, 'optionalAccess', _68 => _68.abort, 'call', _69 => _69()]);
1655
- this.abortController = void 0;
1656
- this.promise = void 0;
1657
- this.lastSeen = void 0;
1658
- this.highestObserved = 0;
1730
+ } finally {
1731
+ clearTimeout(timeoutId);
1732
+ clearTimeout(delay);
1733
+ signal.removeEventListener("abort", onAbort);
1734
+ _optionalChain([options, 'access', _60 => _60.signal, 'optionalAccess', _61 => _61.removeEventListener, 'call', _62 => _62("abort", onExternalAbort)]);
1659
1735
  }
1660
- }, _class6);
1736
+ }
1661
1737
 
1662
1738
  // src/controller/normalized-options.ts
1663
1739
 
@@ -1665,7 +1741,6 @@ var DEFAULT_STREAM_INIT_TIMEOUT_MS = 3e3;
1665
1741
  var DEFAULT_POLLING_INTERVAL_MS = 3e4;
1666
1742
  var MIN_POLLING_INTERVAL_MS = 3e4;
1667
1743
  var DEFAULT_POLLING_INIT_TIMEOUT_MS = 3e3;
1668
- var DEFAULT_STALE_WHILE_REVALIDATE_MS = 1e4;
1669
1744
  function normalizeOptions(options) {
1670
1745
  const autoDetectedBuildStep = process.env.CI === "1" || process.env.NEXT_PHASE === "phase-production-build";
1671
1746
  const buildStep = _nullishCoalesce(options.buildStep, () => ( autoDetectedBuildStep));
@@ -1698,19 +1773,11 @@ function normalizeOptions(options) {
1698
1773
  initTimeoutMs: options.polling.initTimeoutMs
1699
1774
  };
1700
1775
  }
1701
- const staleWhileRevalidateMs = _nullishCoalesce(options.staleWhileRevalidateMs, () => ( DEFAULT_STALE_WHILE_REVALIDATE_MS));
1702
- if (!Number.isFinite(staleWhileRevalidateMs) || staleWhileRevalidateMs < 0) {
1703
- throw new Error(
1704
- "@vercel/flags-core: staleWhileRevalidateMs must be a finite, non-negative number."
1705
- );
1706
- }
1707
1776
  return {
1708
1777
  auth: options.auth,
1709
1778
  datafile: options.datafile,
1710
1779
  stream,
1711
1780
  polling,
1712
- vercel: _nullishCoalesce(options.vercel, () => ( process.env.VERCEL === "1")),
1713
- staleWhileRevalidateMs,
1714
1781
  buildStep,
1715
1782
  fetch: _nullishCoalesce(options.fetch, () => ( globalThis.fetch)),
1716
1783
  waitUntil: _nullishCoalesce(options.waitUntil, () => ( _functions.waitUntil)),
@@ -1721,6 +1788,35 @@ function normalizeOptions(options) {
1721
1788
  };
1722
1789
  }
1723
1790
 
1791
+ // src/controller/typed-emitter.ts
1792
+ var TypedEmitter = (_class5 = class {constructor() { _class5.prototype.__init12.call(this); }
1793
+ __init12() {this.handlers = /* @__PURE__ */ new Map()}
1794
+ on(event, handler) {
1795
+ let set = this.handlers.get(event);
1796
+ if (!set) {
1797
+ set = /* @__PURE__ */ new Set();
1798
+ this.handlers.set(event, set);
1799
+ }
1800
+ set.add(handler);
1801
+ }
1802
+ off(event, handler) {
1803
+ const set = this.handlers.get(event);
1804
+ if (!set) return;
1805
+ set.delete(handler);
1806
+ if (set.size === 0) {
1807
+ this.handlers.delete(event);
1808
+ }
1809
+ }
1810
+ emit(event, ...args) {
1811
+ const set = this.handlers.get(event);
1812
+ if (set) {
1813
+ for (const handler of set) {
1814
+ handler(...args);
1815
+ }
1816
+ }
1817
+ }
1818
+ }, _class5);
1819
+
1724
1820
  // src/controller/polling-source.ts
1725
1821
  var PollingSource = class extends TypedEmitter {
1726
1822
 
@@ -1735,14 +1831,17 @@ var PollingSource = class extends TypedEmitter {
1735
1831
  * Emits 'data' on success, 'error' on failure.
1736
1832
  */
1737
1833
  async poll() {
1738
- if (_optionalChain([this, 'access', _70 => _70.abortController, 'optionalAccess', _71 => _71.signal, 'access', _72 => _72.aborted])) return;
1834
+ if (_optionalChain([this, 'access', _63 => _63.abortController, 'optionalAccess', _64 => _64.signal, 'access', _65 => _65.aborted])) return;
1835
+ this.abortController ??= new AbortController();
1836
+ const controller = this.abortController;
1739
1837
  try {
1740
1838
  const data = await fetchDatafile({
1741
1839
  ...this.config,
1742
- signal: _optionalChain([this, 'access', _73 => _73.abortController, 'optionalAccess', _74 => _74.signal])
1840
+ signal: controller.signal
1743
1841
  });
1744
1842
  this.emit("data", data);
1745
1843
  } catch (error) {
1844
+ controller.signal.throwIfAborted();
1746
1845
  const err = error instanceof Error ? error : new Error("Unknown poll error");
1747
1846
  this.emit("error", err);
1748
1847
  }
@@ -1754,9 +1853,9 @@ var PollingSource = class extends TypedEmitter {
1754
1853
  */
1755
1854
  startInterval() {
1756
1855
  if (this.intervalId) return;
1757
- this.abortController = new AbortController();
1758
1856
  this.intervalId = setInterval(
1759
- () => void this.poll(),
1857
+ () => void this.poll().catch(() => {
1858
+ }),
1760
1859
  this.config.polling.intervalMs
1761
1860
  );
1762
1861
  }
@@ -1768,7 +1867,7 @@ var PollingSource = class extends TypedEmitter {
1768
1867
  clearInterval(this.intervalId);
1769
1868
  this.intervalId = void 0;
1770
1869
  }
1771
- _optionalChain([this, 'access', _75 => _75.abortController, 'optionalAccess', _76 => _76.abort, 'call', _77 => _77()]);
1870
+ _optionalChain([this, 'access', _66 => _66.abortController, 'optionalAccess', _67 => _67.abort, 'call', _68 => _68()]);
1772
1871
  this.abortController = void 0;
1773
1872
  }
1774
1873
  };
@@ -1797,8 +1896,8 @@ function backoff(retryCount) {
1797
1896
  return delay + Math.random() * 1e3;
1798
1897
  }
1799
1898
  var UnauthorizedError = class extends Error {
1800
- constructor() {
1801
- super("stream: unauthorized (401)");
1899
+ constructor(sourceProjectId) {
1900
+ super(`stream: ${unauthorizedMessage(sourceProjectId)}`);
1802
1901
  this.name = "UnauthorizedError";
1803
1902
  }
1804
1903
  };
@@ -1847,7 +1946,7 @@ async function connectStream(config, callbacks) {
1847
1946
  if (pingTimeoutId !== void 0) clearTimeout(pingTimeoutId);
1848
1947
  if (!initialDataReceived) return;
1849
1948
  pingTimeoutId = setTimeout(() => {
1850
- _optionalChain([responseBody, 'optionalAccess', _78 => _78.cancel, 'call', _79 => _79(), 'access', _80 => _80.catch, 'call', _81 => _81(() => {
1949
+ _optionalChain([responseBody, 'optionalAccess', _69 => _69.cancel, 'call', _70 => _70(), 'access', _71 => _71.catch, 'call', _72 => _72(() => {
1851
1950
  })]);
1852
1951
  connectionAbort.abort();
1853
1952
  }, PING_TIMEOUT_MS);
@@ -1858,7 +1957,7 @@ async function connectStream(config, callbacks) {
1858
1957
  throw new TokenResolutionError(error);
1859
1958
  });
1860
1959
  const headers = {
1861
- Authorization: `Bearer ${token}`,
1960
+ ...authHeaders(token, config.sourceProjectId),
1862
1961
  "User-Agent": `VercelFlagsCore/${version}`,
1863
1962
  "X-Retry-Attempt": String(retryCount)
1864
1963
  };
@@ -1869,7 +1968,7 @@ async function connectStream(config, callbacks) {
1869
1968
  if (vercelEnv) {
1870
1969
  headers["X-Vercel-Env"] = vercelEnv;
1871
1970
  }
1872
- const revision = _optionalChain([config, 'access', _82 => _82.revision, 'optionalCall', _83 => _83()]);
1971
+ const revision = _optionalChain([config, 'access', _73 => _73.revision, 'optionalCall', _74 => _74()]);
1873
1972
  if (revision !== void 0) {
1874
1973
  headers["X-Revision"] = String(revision);
1875
1974
  }
@@ -1880,7 +1979,7 @@ async function connectStream(config, callbacks) {
1880
1979
  if (!response.ok) {
1881
1980
  if (response.status === 401) {
1882
1981
  if (!initialDataReceived) {
1883
- rejectInit(new UnauthorizedError());
1982
+ rejectInit(new UnauthorizedError(config.sourceProjectId));
1884
1983
  }
1885
1984
  abortController.abort();
1886
1985
  break;
@@ -1915,7 +2014,7 @@ async function connectStream(config, callbacks) {
1915
2014
  let message;
1916
2015
  try {
1917
2016
  message = JSON.parse(line);
1918
- } catch (e9) {
2017
+ } catch (e12) {
1919
2018
  console.warn(
1920
2019
  "@vercel/flags-core: Failed to parse stream message, skipping"
1921
2020
  );
@@ -1931,7 +2030,7 @@ async function connectStream(config, callbacks) {
1931
2030
  resetPingTimeout();
1932
2031
  }
1933
2032
  if (message.type === "primed") {
1934
- _optionalChain([onPrimed, 'optionalCall', _84 => _84(message)]);
2033
+ _optionalChain([onPrimed, 'optionalCall', _75 => _75(message)]);
1935
2034
  retryCount = 0;
1936
2035
  if (!initialDataReceived) {
1937
2036
  initialDataReceived = true;
@@ -1954,7 +2053,7 @@ async function connectStream(config, callbacks) {
1954
2053
  clearTimeout(pingTimeoutId);
1955
2054
  abortController.signal.removeEventListener("abort", onMainAbort);
1956
2055
  if (!abortController.signal.aborted) {
1957
- _optionalChain([onDisconnect, 'optionalCall', _85 => _85()]);
2056
+ _optionalChain([onDisconnect, 'optionalCall', _76 => _76()]);
1958
2057
  retryCount++;
1959
2058
  const elapsed = Date.now() - lastAttemptTime;
1960
2059
  const minGap = Math.max(0, BASE_RETRY_DELAY_MS - elapsed);
@@ -1975,7 +2074,7 @@ async function connectStream(config, callbacks) {
1975
2074
  if (!connectionAbort.signal.aborted) {
1976
2075
  lastError = error;
1977
2076
  }
1978
- _optionalChain([onDisconnect, 'optionalCall', _86 => _86()]);
2077
+ _optionalChain([onDisconnect, 'optionalCall', _77 => _77()]);
1979
2078
  retryCount++;
1980
2079
  const elapsed = Date.now() - lastAttemptTime;
1981
2080
  const minGap = Math.max(0, BASE_RETRY_DELAY_MS - elapsed);
@@ -2024,6 +2123,7 @@ var StreamSource = class extends TypedEmitter {
2024
2123
  {
2025
2124
  host: this.options.host,
2026
2125
  resolveToken: () => this.options.auth.resolveToken(),
2126
+ sourceProjectId: this.options.auth.sourceProjectId,
2027
2127
  abortController,
2028
2128
  fetch: this.options.fetch,
2029
2129
  revision: this.revision
@@ -2054,12 +2154,29 @@ var StreamSource = class extends TypedEmitter {
2054
2154
  * Stop the stream connection.
2055
2155
  */
2056
2156
  stop() {
2057
- _optionalChain([this, 'access', _87 => _87.abortController, 'optionalAccess', _88 => _88.abort, 'call', _89 => _89()]);
2157
+ _optionalChain([this, 'access', _78 => _78.abortController, 'optionalAccess', _79 => _79.abort, 'call', _80 => _80()]);
2058
2158
  this.abortController = void 0;
2059
2159
  this.promise = void 0;
2060
2160
  }
2061
2161
  };
2062
2162
 
2163
+ // src/controller/tagged-data.ts
2164
+ function tagData(data, origin) {
2165
+ return Object.assign(data, { _origin: origin });
2166
+ }
2167
+ function originToMetricsSource(origin) {
2168
+ switch (origin) {
2169
+ case "stream":
2170
+ case "poll":
2171
+ case "provided":
2172
+ return "in-memory";
2173
+ case "fetched":
2174
+ return "remote";
2175
+ case "bundled":
2176
+ return "embedded";
2177
+ }
2178
+ }
2179
+
2063
2180
  // src/controller/index.ts
2064
2181
  function parseConfigUpdatedAt(value) {
2065
2182
  if (typeof value === "number") return value;
@@ -2069,7 +2186,7 @@ function parseConfigUpdatedAt(value) {
2069
2186
  }
2070
2187
  return void 0;
2071
2188
  }
2072
- var Controller = (_class7 = class {
2189
+ var Controller = (_class6 = class {
2073
2190
 
2074
2191
  // State machine
2075
2192
  __init13() {this.state = "idle"}
@@ -2077,34 +2194,33 @@ var Controller = (_class7 = class {
2077
2194
 
2078
2195
  // Memoized data spread for read() / getDatafile().
2079
2196
  // Rebuilt only when `this.data` reference changes (e.g. on stream/poll update).
2080
- // Holds the result of stripping internal metadata; metrics are appended per-call.
2197
+ // Holds the result of stripping `_origin`; metrics are appended per-call.
2081
2198
  __init14() {this.dataViewSource = void 0}
2082
2199
  __init15() {this.dataViewBase = void 0}
2083
2200
  // Sources (I/O delegates)
2084
2201
 
2085
2202
 
2086
2203
 
2087
-
2088
2204
  // Usage tracking
2089
2205
 
2090
2206
  __init16() {this.isFirstGetData = true}
2091
2207
  // Build-step deduplication
2092
2208
  __init17() {this.buildDataPromise = null}
2093
2209
  __init18() {this.buildReadTracked = false}
2094
- // Suppresses usage tracking when the SDK key is unauthorized
2210
+ // Suppresses usage tracking while the credential is rejected. Set on a 401
2211
+ // from any source, cleared as soon as a source delivers data again.
2095
2212
  __init19() {this.unauthorized = false}
2096
- constructor(options) {;_class7.prototype.__init13.call(this);_class7.prototype.__init14.call(this);_class7.prototype.__init15.call(this);_class7.prototype.__init16.call(this);_class7.prototype.__init17.call(this);_class7.prototype.__init18.call(this);_class7.prototype.__init19.call(this);_class7.prototype.__init20.call(this);_class7.prototype.__init21.call(this);_class7.prototype.__init22.call(this);_class7.prototype.__init23.call(this);_class7.prototype.__init24.call(this);_class7.prototype.__init25.call(this);_class7.prototype.__init26.call(this);
2213
+ 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);
2097
2214
  this.options = normalizeOptions(options);
2098
2215
  this.streamSource = new StreamSource(
2099
2216
  this.options,
2100
- () => _optionalChain([this, 'access', _90 => _90.data, 'optionalAccess', _91 => _91.revision])
2217
+ () => _optionalChain([this, 'access', _81 => _81.data, 'optionalAccess', _82 => _82.revision])
2101
2218
  );
2102
2219
  this.pollingSource = new PollingSource(this.options);
2103
2220
  this.bundledSource = new BundledSource({
2104
2221
  auth: this.options.auth,
2105
2222
  readBundledDefinitions
2106
2223
  });
2107
- this.headerSource = new HeaderSource(this.options);
2108
2224
  this.wireSourceEvents();
2109
2225
  if (this.options.datafile) {
2110
2226
  this.data = tagData(this.options.datafile, "provided");
@@ -2113,11 +2229,13 @@ var Controller = (_class7 = class {
2113
2229
  }
2114
2230
  // Source event handlers (stored for cleanup)
2115
2231
  __init20() {this.onStreamData = (data) => {
2232
+ this.unauthorized = false;
2116
2233
  if (this.isNewerData(data)) {
2117
2234
  this.data = tagData(data, "stream");
2118
2235
  }
2119
2236
  }}
2120
2237
  __init21() {this.onStreamPrimed = () => {
2238
+ this.unauthorized = false;
2121
2239
  if (this.state === "degraded" || this.state === "initializing:stream") {
2122
2240
  this.transition("streaming");
2123
2241
  }
@@ -2133,18 +2251,15 @@ var Controller = (_class7 = class {
2133
2251
  }
2134
2252
  }}
2135
2253
  __init24() {this.onPollData = (data) => {
2254
+ this.unauthorized = false;
2136
2255
  if (this.isNewerData(data)) {
2137
2256
  this.data = tagData(data, "poll");
2138
2257
  }
2139
2258
  }}
2140
2259
  __init25() {this.onPollError = (error) => {
2260
+ this.noteUnauthorized(error);
2141
2261
  console.error("@vercel/flags-core: Poll failed:", error);
2142
2262
  }}
2143
- __init26() {this.onFetchedData = (data) => {
2144
- if (this.isNewerData(data)) {
2145
- this.data = tagData(data, "fetched");
2146
- }
2147
- }}
2148
2263
  // ---------------------------------------------------------------------------
2149
2264
  // Source event wiring
2150
2265
  // ---------------------------------------------------------------------------
@@ -2155,7 +2270,6 @@ var Controller = (_class7 = class {
2155
2270
  this.streamSource.on("disconnected", this.onStreamDisconnected);
2156
2271
  this.pollingSource.on("data", this.onPollData);
2157
2272
  this.pollingSource.on("error", this.onPollError);
2158
- this.headerSource.on("data", this.onFetchedData);
2159
2273
  }
2160
2274
  unwireSourceEvents() {
2161
2275
  this.streamSource.off("data", this.onStreamData);
@@ -2164,7 +2278,6 @@ var Controller = (_class7 = class {
2164
2278
  this.streamSource.off("disconnected", this.onStreamDisconnected);
2165
2279
  this.pollingSource.off("data", this.onPollData);
2166
2280
  this.pollingSource.off("error", this.onPollError);
2167
- this.headerSource.off("data", this.onFetchedData);
2168
2281
  }
2169
2282
  // ---------------------------------------------------------------------------
2170
2283
  // State machine
@@ -2182,8 +2295,6 @@ var Controller = (_class7 = class {
2182
2295
  return "streaming";
2183
2296
  case "polling":
2184
2297
  return "polling";
2185
- case "vercel":
2186
- return "vercel";
2187
2298
  default:
2188
2299
  return "offline";
2189
2300
  }
@@ -2197,7 +2308,6 @@ var Controller = (_class7 = class {
2197
2308
  * Build step: datafile → bundled → one-time fetch
2198
2309
  * Streaming mode: stream → datafile → bundled
2199
2310
  * Polling mode (no stream): poll → datafile → bundled
2200
- * Vercel mode: datafile → bundled; fetch only on a read
2201
2311
  * Offline mode (neither): datafile → bundled → one-time fetch
2202
2312
  */
2203
2313
  async initialize() {
@@ -2211,20 +2321,13 @@ var Controller = (_class7 = class {
2211
2321
  this.data = tagData(this.options.datafile, "provided");
2212
2322
  }
2213
2323
  if (!this.data) {
2214
- this.transition("initializing:fallback");
2215
- let bundled;
2216
2324
  try {
2217
- bundled = await this.bundledSource.tryLoad();
2218
- } catch (e10) {
2219
- }
2220
- if (this.state === "shutdown") {
2221
- throw new Error("@vercel/flags-core: Client is shut down");
2325
+ const bundled = await this.bundledSource.tryLoad();
2326
+ if (bundled) {
2327
+ this.data = tagData(bundled, "bundled");
2328
+ }
2329
+ } catch (e13) {
2222
2330
  }
2223
- if (bundled) this.data = tagData(bundled, "bundled");
2224
- }
2225
- if (this.headerSource.isAvailable()) {
2226
- this.transition("vercel");
2227
- return;
2228
2331
  }
2229
2332
  if (this.data) {
2230
2333
  if (this.options.stream.enabled) {
@@ -2290,7 +2393,6 @@ var Controller = (_class7 = class {
2290
2393
  this.unwireSourceEvents();
2291
2394
  this.streamSource.stop();
2292
2395
  this.pollingSource.stop();
2293
- this.headerSource.stop();
2294
2396
  this.data = this.options.datafile ? tagData(this.options.datafile, "provided") : void 0;
2295
2397
  this.transition("shutdown");
2296
2398
  await this.usageTracker.shutdown();
@@ -2326,9 +2428,10 @@ var Controller = (_class7 = class {
2326
2428
  this.data = tagData(fetched, "fetched");
2327
2429
  result = this.data;
2328
2430
  cacheStatus = "MISS";
2329
- } catch (e11) {
2330
- throw new Error(
2331
- "@vercel/flags-core: No flag definitions available. Initialize the client or provide a datafile."
2431
+ } catch (error) {
2432
+ this.noteUnauthorized(error);
2433
+ throw this.noDefinitionsError(
2434
+ ". Initialize the client or provide a datafile."
2332
2435
  );
2333
2436
  }
2334
2437
  }
@@ -2371,10 +2474,6 @@ var Controller = (_class7 = class {
2371
2474
  if (this.options.buildStep) {
2372
2475
  return this.resolveDataForBuildStep();
2373
2476
  }
2374
- if (this.state === "vercel") {
2375
- const result = await this.headerSource.read(this.data);
2376
- if (result) return result;
2377
- }
2378
2477
  if (this.data) {
2379
2478
  const cacheStatus = this.isConnected ? "HIT" : "STALE";
2380
2479
  return [this.data, cacheStatus];
@@ -2394,9 +2493,7 @@ var Controller = (_class7 = class {
2394
2493
  await this.streamSource.start();
2395
2494
  return true;
2396
2495
  } catch (error) {
2397
- if (error instanceof UnauthorizedError) {
2398
- this.unauthorized = true;
2399
- }
2496
+ this.noteUnauthorized(error);
2400
2497
  return false;
2401
2498
  }
2402
2499
  }
@@ -2424,9 +2521,7 @@ var Controller = (_class7 = class {
2424
2521
  return true;
2425
2522
  } catch (error) {
2426
2523
  clearTimeout(timeoutId);
2427
- if (error instanceof Error && error.message.includes("401")) {
2428
- this.unauthorized = true;
2429
- }
2524
+ this.noteUnauthorized(error);
2430
2525
  return false;
2431
2526
  }
2432
2527
  }
@@ -2449,7 +2544,7 @@ var Controller = (_class7 = class {
2449
2544
  return true;
2450
2545
  }
2451
2546
  return false;
2452
- } catch (e12) {
2547
+ } catch (e14) {
2453
2548
  return false;
2454
2549
  }
2455
2550
  }
@@ -2474,11 +2569,16 @@ var Controller = (_class7 = class {
2474
2569
  return true;
2475
2570
  }
2476
2571
  return false;
2477
- } catch (e13) {
2572
+ } catch (e15) {
2478
2573
  clearTimeout(timeoutId);
2479
2574
  return false;
2480
2575
  }
2481
2576
  }
2577
+ noteUnauthorized(error) {
2578
+ if (error instanceof UnauthorizedError || error instanceof Error && error.message.includes("401")) {
2579
+ this.unauthorized = true;
2580
+ }
2581
+ }
2482
2582
  // ---------------------------------------------------------------------------
2483
2583
  // Build step helpers
2484
2584
  // ---------------------------------------------------------------------------
@@ -2524,10 +2624,11 @@ var Controller = (_class7 = class {
2524
2624
  fetch: this.options.fetch
2525
2625
  });
2526
2626
  return tagData(fetched, "fetched");
2527
- } catch (e14) {
2627
+ } catch (error) {
2628
+ this.noteUnauthorized(error);
2528
2629
  }
2529
- throw new Error(
2530
- "@vercel/flags-core: No flag definitions available during build. Provide a datafile or bundled definitions."
2630
+ throw this.noDefinitionsError(
2631
+ " during build. Provide a datafile or bundled definitions."
2531
2632
  );
2532
2633
  }
2533
2634
  // ---------------------------------------------------------------------------
@@ -2558,11 +2659,20 @@ var Controller = (_class7 = class {
2558
2659
  this.data = tagData(fetched, "fetched");
2559
2660
  this.transition("degraded");
2560
2661
  return;
2561
- } catch (e15) {
2662
+ } catch (e16) {
2562
2663
  }
2563
2664
  }
2564
- throw new Error(
2565
- "@vercel/flags-core: No flag definitions available. Bundled definitions not found."
2665
+ throw this.noDefinitionsError(". Bundled definitions not found.");
2666
+ }
2667
+ /**
2668
+ * `detail` continues the sentence "No flag definitions available", so it
2669
+ * starts with either "." or " during build.".
2670
+ */
2671
+ noDefinitionsError(detail) {
2672
+ const { sourceProjectId } = this.options.auth;
2673
+ const reason = this.unauthorized && sourceProjectId ? ` Request was ${unauthorizedMessage(sourceProjectId)}` : "";
2674
+ return new Error(
2675
+ `@vercel/flags-core: No flag definitions available${detail}${reason}`
2566
2676
  );
2567
2677
  }
2568
2678
  /**
@@ -2610,11 +2720,11 @@ var Controller = (_class7 = class {
2610
2720
  this.data = tagData(fetched, "fetched");
2611
2721
  this.transition("degraded");
2612
2722
  return [this.data, "MISS"];
2613
- } catch (e16) {
2723
+ } catch (e17) {
2614
2724
  }
2615
2725
  }
2616
- throw new Error(
2617
- "@vercel/flags-core: No flag definitions available. Provide a datafile or bundled definitions."
2726
+ throw this.noDefinitionsError(
2727
+ ". Provide a datafile or bundled definitions."
2618
2728
  );
2619
2729
  }
2620
2730
  // ---------------------------------------------------------------------------
@@ -2662,11 +2772,11 @@ var Controller = (_class7 = class {
2662
2772
  duration: Date.now() - startTime,
2663
2773
  mode: mode === "streaming" ? "stream" : mode === "polling" ? "poll" : mode
2664
2774
  };
2665
- const configUpdatedAt = _optionalChain([this, 'access', _92 => _92.data, 'optionalAccess', _93 => _93.configUpdatedAt]);
2775
+ const configUpdatedAt = _optionalChain([this, 'access', _83 => _83.data, 'optionalAccess', _84 => _84.configUpdatedAt]);
2666
2776
  if (typeof configUpdatedAt === "number") {
2667
2777
  trackOptions.configUpdatedAt = configUpdatedAt;
2668
2778
  }
2669
- const revision = _optionalChain([this, 'access', _94 => _94.data, 'optionalAccess', _95 => _95.revision]);
2779
+ const revision = _optionalChain([this, 'access', _85 => _85.data, 'optionalAccess', _86 => _86.revision]);
2670
2780
  if (typeof revision === "number") {
2671
2781
  trackOptions.revision = revision;
2672
2782
  }
@@ -2685,89 +2795,7 @@ var Controller = (_class7 = class {
2685
2795
  clientName: _nullishCoalesce(options.clientName, () => ( this.options.clientName))
2686
2796
  });
2687
2797
  }
2688
- }, _class7);
2689
-
2690
- // src/controller/auth.ts
2691
-
2692
-
2693
- // src/utils/sdk-keys.ts
2694
- var SDK_KEY_REGEX = /^vf_(?:server|client)_/;
2695
- function parseSdkKeyFromFlagsConnectionString(text) {
2696
- if (SDK_KEY_REGEX.test(text)) return text;
2697
- try {
2698
- if (!text.startsWith("flags:")) return null;
2699
- const params = new URLSearchParams(text.slice(6));
2700
- const sdkKey = params.get("sdkKey");
2701
- if (sdkKey && SDK_KEY_REGEX.test(sdkKey)) return sdkKey;
2702
- } catch (e17) {
2703
- }
2704
- return null;
2705
- }
2706
-
2707
- // src/controller/auth.ts
2708
- async function getOidcToken() {
2709
- try {
2710
- return await _oidc.getVercelOidcToken.call(void 0, );
2711
- } catch (e18) {
2712
- throw new Error(
2713
- [
2714
- "@vercel/flags-core: Failed to get OIDC token.",
2715
- "Are you running in a Vercel Environment where OIDC tokens are available?",
2716
- "Did you mean to use an SDK Key instead? Use the environment variable FLAGS or pass it directly to the client."
2717
- ].join(" ")
2718
- );
2719
- }
2720
- }
2721
- function getProjectIdFromOidcToken(oidcToken) {
2722
- const tokenParts = oidcToken.split(".");
2723
- if (tokenParts.length !== 3 || !tokenParts[1]) {
2724
- throw new Error("@vercel/flags-core: Invalid OIDC token");
2725
- }
2726
- const payload = JSON.parse(
2727
- Buffer.from(tokenParts[1], "base64url").toString("utf8")
2728
- );
2729
- if (typeof payload.project_id !== "string" || !payload.project_id) {
2730
- throw new Error(
2731
- "@vercel/flags-core: Missing project_id claim in OIDC token"
2732
- );
2733
- }
2734
- return payload.project_id;
2735
- }
2736
- var Authentication = class {
2737
-
2738
- constructor(sdkKeyOrConnectionString) {
2739
- if (sdkKeyOrConnectionString !== void 0) {
2740
- if (typeof sdkKeyOrConnectionString !== "string") {
2741
- throw new Error(
2742
- `@vercel/flags-core: Invalid sdkKey. Expected string, got ${typeof sdkKeyOrConnectionString}`
2743
- );
2744
- }
2745
- const parsed = parseSdkKeyFromFlagsConnectionString(
2746
- sdkKeyOrConnectionString
2747
- );
2748
- if (!parsed) {
2749
- throw new Error("@vercel/flags-core: Missing sdkKey");
2750
- }
2751
- this.sdkKey = parsed;
2752
- }
2753
- }
2754
- async resolveToken() {
2755
- if (this.sdkKey) {
2756
- return this.sdkKey;
2757
- }
2758
- return await getOidcToken();
2759
- }
2760
- async resolveBundledDefinitionsLookup() {
2761
- if (this.sdkKey) {
2762
- return { type: "sdk-key", sdkKey: this.sdkKey };
2763
- }
2764
- const oidcToken = await this.resolveToken();
2765
- return {
2766
- type: "project-id",
2767
- projectId: getProjectIdFromOidcToken(oidcToken)
2768
- };
2769
- }
2770
- };
2798
+ }, _class6);
2771
2799
 
2772
2800
  // src/index.make.ts
2773
2801
  function make(createRawClient, defaults) {
@@ -2786,7 +2814,11 @@ function make(createRawClient, defaults) {
2786
2814
  });
2787
2815
  return createRawClient({
2788
2816
  controller,
2789
- origin: { provider: "vercel", sdkKey: auth.sdkKey },
2817
+ origin: {
2818
+ provider: "vercel",
2819
+ sdkKey: auth.sdkKey,
2820
+ ...auth.sourceProjectId ? { projectId: auth.sourceProjectId } : null
2821
+ },
2790
2822
  waitUntil: waitUntil3,
2791
2823
  ...experimental_reportExposures ? { experimental_reportExposures } : {}
2792
2824
  });
@@ -2838,4 +2870,4 @@ var {
2838
2870
 
2839
2871
 
2840
2872
  exports.ResolutionReason = ResolutionReason; exports.evaluate = evaluate; exports.FallbackNotFoundError = FallbackNotFoundError; exports.FallbackEntryNotFoundError = FallbackEntryNotFoundError; exports.Controller = Controller; exports.flagsClient = flagsClient; exports.resetDefaultFlagsClient = resetDefaultFlagsClient; exports.createClient = createClient;
2841
- //# sourceMappingURL=chunk-L6QJOXJN.cjs.map
2873
+ //# sourceMappingURL=chunk-PGKH4XQ7.cjs.map