@vercel/flags-core 1.5.0-dde2adb-20260526050633 → 1.5.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.
package/CHANGELOG.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # @vercel/flags-core
2
2
 
3
- ## 1.5.0-dde2adb-20260526050633
3
+ ## 1.5.0
4
4
 
5
5
  ### Minor Changes
6
6
 
7
- - [#385](https://github.com/vercel/flags/pull/385) [`5b2dc37`](https://github.com/vercel/flags/commit/5b2dc37041e24811f735beede41fc8c29f508cb7) Thanks [@dferber90](https://github.com/dferber90)! - Add `bulkEvaluate` method to `FlagsClient` for resolving multiple flags against shared entities in a single call.
7
+ - [#385](https://github.com/vercel/flags/pull/385) [`201f9d5`](https://github.com/vercel/flags/commit/201f9d5988d7fc307511e35638e66769d38cedb3) Thanks [@dferber90](https://github.com/dferber90)! - Add `bulkEvaluate` method to `FlagsClient` for resolving multiple flags against shared entities in a single call.
8
8
 
9
9
  ```ts
10
10
  const results = await client.bulkEvaluate(
@@ -29,6 +29,14 @@
29
29
  processes. When all retry attempts are exhausted the SDK now logs a structured
30
30
  warning so consumers can alert on dropped batches.
31
31
 
32
+ - [#390](https://github.com/vercel/flags/pull/390) [`7b5ea9a`](https://github.com/vercel/flags/commit/7b5ea9a808dfd4155bd2bbf321c3b44ec730cda6) Thanks [@luismeyer](https://github.com/luismeyer)! - Add OIDC authentication support for Vercel Flags clients and generated flag definitions.
33
+
34
+ `@vercel/flags-core` can now create clients without an SDK key and authenticate with a Vercel OIDC token, while still supporting SDK keys and connection strings. Bundled definitions can be looked up by SDK key hash or OIDC project ID.
35
+
36
+ `@vercel/prepare-flags-definitions` now collects both SDK keys and `VERCEL_OIDC_TOKEN`, fetches definitions for each auth entry, deduplicates identical definitions across SDK keys and OIDC project IDs, and writes generated maps keyed by SDK key hash or project ID.
37
+
38
+ `@flags-sdk/vercel` now supports provider data lookup for Vercel flag origins that do not include an SDK key, allowing OIDC-backed clients to resolve project metadata.
39
+
32
40
  ### Patch Changes
33
41
 
34
42
  - [#382](https://github.com/vercel/flags/pull/382) [`4d90e91`](https://github.com/vercel/flags/commit/4d90e912a4d7c9d4ef986d5e8dc609c30b203242) Thanks [@dferber90](https://github.com/dferber90)! - Speed up flag evaluation on the hot path.
@@ -467,7 +467,7 @@ function findWeightedIndex(weights, value, maxValue) {
467
467
  }
468
468
 
469
469
  // package.json
470
- var version = "1.5.0-dde2adb-20260526050633";
470
+ var version = "1.5.0";
471
471
 
472
472
  // src/lib/report-value.ts
473
473
  function internalReportValue(key, value, data) {
@@ -744,7 +744,7 @@ function hashSdkKey(sdkKey) {
744
744
  sdkKeyHashCache.set(sdkKey, promise);
745
745
  return promise;
746
746
  }
747
- async function readBundledDefinitions(sdkKey) {
747
+ async function readBundledDefinitions(auth) {
748
748
  let get;
749
749
  try {
750
750
  const module = await Promise.resolve().then(() => _interopRequireWildcard(require(
@@ -761,10 +761,20 @@ async function readBundledDefinitions(sdkKey) {
761
761
  if (typeof get !== "function") {
762
762
  return { definitions: null, state: "missing-file" };
763
763
  }
764
- const entry = get(sdkKey);
764
+ let lookup;
765
+ try {
766
+ lookup = await auth.resolveBundledDefinitionsLookup();
767
+ } catch (error) {
768
+ return { definitions: null, state: "unexpected-error", error };
769
+ }
770
+ if (lookup.type === "project-id") {
771
+ const entry2 = get(lookup.projectId);
772
+ return entry2 ? { definitions: entry2, state: "ok" } : { definitions: null, state: "missing-entry" };
773
+ }
774
+ const entry = get(lookup.sdkKey);
765
775
  if (entry) return { definitions: entry, state: "ok" };
766
776
  try {
767
- const hashedKey = await hashSdkKey(sdkKey);
777
+ const hashedKey = await hashSdkKey(lookup.sdkKey);
768
778
  const hashedEntry = get(hashedKey);
769
779
  if (hashedEntry) return { definitions: hashedEntry, state: "ok" };
770
780
  } catch (error) {
@@ -935,13 +945,14 @@ var UsageTracker = (_class = class {
935
945
  const flushId = ++this.flushCounter;
936
946
  for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
937
947
  try {
948
+ const token = await this.options.auth.resolveToken();
938
949
  const response = await this.options.fetch(
939
950
  `${this.options.host}/v1/ingest`,
940
951
  {
941
952
  method: "POST",
942
953
  headers: {
943
954
  "Content-Type": "application/json",
944
- Authorization: `Bearer ${this.options.sdkKey}`,
955
+ Authorization: `Bearer ${token}`,
945
956
  "User-Agent": `VercelFlagsCore/${version}`,
946
957
  ...process.env.VERCEL_ENV ? { "X-Vercel-Env": process.env.VERCEL_ENV } : null,
947
958
  ...isDebugMode ? { "x-vercel-debug-ingest": "1" } : null
@@ -993,11 +1004,10 @@ var FallbackEntryNotFoundError = class extends Error {
993
1004
 
994
1005
  // src/controller/bundled-source.ts
995
1006
  var BundledSource = class {
996
-
997
-
998
1007
  constructor(options) {
999
1008
  this.options = options;
1000
1009
  }
1010
+
1001
1011
  /**
1002
1012
  * Load bundled definitions.
1003
1013
  * Throws if bundled definitions are not available.
@@ -1042,7 +1052,7 @@ var BundledSource = class {
1042
1052
  }
1043
1053
  getResult() {
1044
1054
  if (!this.promise) {
1045
- this.promise = this.options.readBundledDefinitions(this.options.sdkKey);
1055
+ this.promise = this.options.readBundledDefinitions(this.options.auth);
1046
1056
  }
1047
1057
  return this.promise;
1048
1058
  }
@@ -1051,6 +1061,7 @@ var BundledSource = class {
1051
1061
  // src/controller/fetch-datafile.ts
1052
1062
  var DEFAULT_FETCH_TIMEOUT_MS = 1e4;
1053
1063
  async function fetchDatafile(options) {
1064
+ const token = await options.auth.resolveToken();
1054
1065
  const controller = new AbortController();
1055
1066
  const timeoutId = setTimeout(
1056
1067
  () => controller.abort(),
@@ -1067,7 +1078,7 @@ async function fetchDatafile(options) {
1067
1078
  try {
1068
1079
  const res = await options.fetch(`${options.host}/v1/datafile`, {
1069
1080
  headers: {
1070
- Authorization: `Bearer ${options.sdkKey}`,
1081
+ Authorization: `Bearer ${token}`,
1071
1082
  "User-Agent": `VercelFlagsCore/${version}`,
1072
1083
  ...process.env.VERCEL_ENV ? { "X-Vercel-Env": process.env.VERCEL_ENV } : null
1073
1084
  },
@@ -1124,7 +1135,7 @@ function normalizeOptions(options) {
1124
1135
  };
1125
1136
  }
1126
1137
  return {
1127
- sdkKey: options.sdkKey,
1138
+ auth: options.auth,
1128
1139
  datafile: options.datafile,
1129
1140
  stream,
1130
1141
  polling,
@@ -1234,13 +1245,14 @@ var UnauthorizedError = class extends Error {
1234
1245
  this.name = "UnauthorizedError";
1235
1246
  }
1236
1247
  };
1248
+ var TokenResolutionError = class extends Error {
1249
+ constructor(error) {
1250
+ super("stream: token resolution failed", { cause: error });
1251
+ this.name = "TokenResolutionError";
1252
+ }
1253
+ };
1237
1254
  async function connectStream(config, callbacks) {
1238
- const {
1239
- host,
1240
- sdkKey,
1241
- abortController,
1242
- fetch: fetchFn = globalThis.fetch
1243
- } = config;
1255
+ const { host, abortController, fetch: fetchFn = globalThis.fetch } = config;
1244
1256
  const { onDatafile, onPrimed, onDisconnect } = callbacks;
1245
1257
  let retryCount = 0;
1246
1258
  let lastAttemptTime = 0;
@@ -1281,8 +1293,11 @@ async function connectStream(config, callbacks) {
1281
1293
  };
1282
1294
  try {
1283
1295
  lastAttemptTime = Date.now();
1296
+ const token = await config.resolveToken().catch((error) => {
1297
+ throw new TokenResolutionError(error);
1298
+ });
1284
1299
  const headers = {
1285
- Authorization: `Bearer ${sdkKey}`,
1300
+ Authorization: `Bearer ${token}`,
1286
1301
  "User-Agent": `VercelFlagsCore/${version}`,
1287
1302
  "X-Retry-Attempt": String(retryCount)
1288
1303
  };
@@ -1388,6 +1403,11 @@ async function connectStream(config, callbacks) {
1388
1403
  if (abortController.signal.aborted) {
1389
1404
  break;
1390
1405
  }
1406
+ if (error instanceof TokenResolutionError && !initialDataReceived) {
1407
+ rejectInit(error.cause);
1408
+ abortController.abort();
1409
+ break;
1410
+ }
1391
1411
  if (!connectionAbort.signal.aborted) {
1392
1412
  console.error("@vercel/flags-core: Stream error", error);
1393
1413
  }
@@ -1439,7 +1459,7 @@ var StreamSource = class extends TypedEmitter {
1439
1459
  const promise = connectStream(
1440
1460
  {
1441
1461
  host: this.options.host,
1442
- sdkKey: this.options.sdkKey,
1462
+ resolveToken: () => this.options.auth.resolveToken(),
1443
1463
  abortController,
1444
1464
  fetch: this.options.fetch,
1445
1465
  revision: this.revision
@@ -1526,11 +1546,6 @@ var Controller = (_class3 = class {
1526
1546
  // Suppresses usage tracking when the SDK key is unauthorized
1527
1547
  __init11() {this.unauthorized = false}
1528
1548
  constructor(options) {;_class3.prototype.__init5.call(this);_class3.prototype.__init6.call(this);_class3.prototype.__init7.call(this);_class3.prototype.__init8.call(this);_class3.prototype.__init9.call(this);_class3.prototype.__init10.call(this);_class3.prototype.__init11.call(this);_class3.prototype.__init12.call(this);_class3.prototype.__init13.call(this);_class3.prototype.__init14.call(this);_class3.prototype.__init15.call(this);_class3.prototype.__init16.call(this);_class3.prototype.__init17.call(this);
1529
- if (!options.sdkKey || typeof options.sdkKey !== "string" || !options.sdkKey.startsWith("vf_")) {
1530
- throw new Error(
1531
- '@vercel/flags-core: SDK key must be a string starting with "vf_"'
1532
- );
1533
- }
1534
1549
  this.options = normalizeOptions(options);
1535
1550
  this.streamSource = new StreamSource(
1536
1551
  this.options,
@@ -1538,7 +1553,7 @@ var Controller = (_class3 = class {
1538
1553
  );
1539
1554
  this.pollingSource = new PollingSource(this.options);
1540
1555
  this.bundledSource = new BundledSource({
1541
- sdkKey: this.options.sdkKey,
1556
+ auth: this.options.auth,
1542
1557
  readBundledDefinitions
1543
1558
  });
1544
1559
  this.wireSourceEvents();
@@ -1738,7 +1753,7 @@ var Controller = (_class3 = class {
1738
1753
  try {
1739
1754
  const fetched = await fetchDatafile({
1740
1755
  host: this.options.host,
1741
- sdkKey: this.options.sdkKey,
1756
+ auth: this.options.auth,
1742
1757
  fetch: this.options.fetch
1743
1758
  });
1744
1759
  this.data = tagData(fetched, "fetched");
@@ -1934,7 +1949,7 @@ var Controller = (_class3 = class {
1934
1949
  try {
1935
1950
  const fetched = await fetchDatafile({
1936
1951
  host: this.options.host,
1937
- sdkKey: this.options.sdkKey,
1952
+ auth: this.options.auth,
1938
1953
  fetch: this.options.fetch
1939
1954
  });
1940
1955
  return tagData(fetched, "fetched");
@@ -1966,7 +1981,7 @@ var Controller = (_class3 = class {
1966
1981
  try {
1967
1982
  const fetched = await fetchDatafile({
1968
1983
  host: this.options.host,
1969
- sdkKey: this.options.sdkKey,
1984
+ auth: this.options.auth,
1970
1985
  fetch: this.options.fetch
1971
1986
  });
1972
1987
  this.data = tagData(fetched, "fetched");
@@ -2018,7 +2033,7 @@ var Controller = (_class3 = class {
2018
2033
  try {
2019
2034
  const fetched = await fetchDatafile({
2020
2035
  host: this.options.host,
2021
- sdkKey: this.options.sdkKey,
2036
+ auth: this.options.auth,
2022
2037
  fetch: this.options.fetch
2023
2038
  });
2024
2039
  this.data = tagData(fetched, "fetched");
@@ -2091,6 +2106,9 @@ var Controller = (_class3 = class {
2091
2106
  }
2092
2107
  }, _class3);
2093
2108
 
2109
+ // src/controller/auth.ts
2110
+ var _oidc = require('@vercel/oidc');
2111
+
2094
2112
  // src/utils/sdk-keys.ts
2095
2113
  var SDK_KEY_REGEX = /^vf_(?:server|client)_/;
2096
2114
  function parseSdkKeyFromFlagsConnectionString(text) {
@@ -2105,28 +2123,80 @@ function parseSdkKeyFromFlagsConnectionString(text) {
2105
2123
  return null;
2106
2124
  }
2107
2125
 
2126
+ // src/controller/auth.ts
2127
+ async function getOidcToken() {
2128
+ try {
2129
+ return await _oidc.getVercelOidcToken.call(void 0, );
2130
+ } catch (e15) {
2131
+ throw new Error(
2132
+ [
2133
+ "@vercel/flags-core: Failed to get OIDC token.",
2134
+ "Are you running in a Vercel Environment where OIDC tokens are available?",
2135
+ "Did you mean to use an SDK Key instead? Use the environment variable FLAGS or pass it directly to the client."
2136
+ ].join(" ")
2137
+ );
2138
+ }
2139
+ }
2140
+ function getProjectIdFromOidcToken(oidcToken) {
2141
+ const tokenParts = oidcToken.split(".");
2142
+ if (tokenParts.length !== 3 || !tokenParts[1]) {
2143
+ throw new Error("@vercel/flags-core: Invalid OIDC token");
2144
+ }
2145
+ const payload = JSON.parse(
2146
+ Buffer.from(tokenParts[1], "base64url").toString("utf8")
2147
+ );
2148
+ if (typeof payload.project_id !== "string" || !payload.project_id) {
2149
+ throw new Error(
2150
+ "@vercel/flags-core: Missing project_id claim in OIDC token"
2151
+ );
2152
+ }
2153
+ return payload.project_id;
2154
+ }
2155
+ var Authentication = class {
2156
+
2157
+ constructor(sdkKeyOrConnectionString) {
2158
+ if (sdkKeyOrConnectionString !== void 0) {
2159
+ if (typeof sdkKeyOrConnectionString !== "string") {
2160
+ throw new Error(
2161
+ `@vercel/flags-core: Invalid sdkKey. Expected string, got ${typeof sdkKeyOrConnectionString}`
2162
+ );
2163
+ }
2164
+ const parsed = parseSdkKeyFromFlagsConnectionString(
2165
+ sdkKeyOrConnectionString
2166
+ );
2167
+ if (!parsed) {
2168
+ throw new Error("@vercel/flags-core: Missing sdkKey");
2169
+ }
2170
+ this.sdkKey = parsed;
2171
+ }
2172
+ }
2173
+ async resolveToken() {
2174
+ if (this.sdkKey) {
2175
+ return this.sdkKey;
2176
+ }
2177
+ return await getOidcToken();
2178
+ }
2179
+ async resolveBundledDefinitionsLookup() {
2180
+ if (this.sdkKey) {
2181
+ return { type: "sdk-key", sdkKey: this.sdkKey };
2182
+ }
2183
+ const oidcToken = await this.resolveToken();
2184
+ return {
2185
+ type: "project-id",
2186
+ projectId: getProjectIdFromOidcToken(oidcToken)
2187
+ };
2188
+ }
2189
+ };
2190
+
2108
2191
  // src/index.make.ts
2109
2192
  function make(createRawClient) {
2110
2193
  let _defaultFlagsClient = null;
2111
2194
  function createClient2(sdkKeyOrConnectionString, options) {
2112
- if (!sdkKeyOrConnectionString)
2113
- throw new Error("@vercel/flags-core: Missing sdkKey");
2114
- if (typeof sdkKeyOrConnectionString !== "string")
2115
- throw new Error(
2116
- `@vercel/flags-core: Invalid sdkKey. Expected string, got ${typeof sdkKeyOrConnectionString}`
2117
- );
2118
- const sdkKey = parseSdkKeyFromFlagsConnectionString(
2119
- sdkKeyOrConnectionString
2120
- );
2121
- if (!sdkKey) {
2122
- throw new Error(
2123
- "@vercel/flags-core: Missing sdkKey in connection string"
2124
- );
2125
- }
2126
- const controller = new Controller({ sdkKey, ...options });
2195
+ const auth = new Authentication(sdkKeyOrConnectionString);
2196
+ const controller = new Controller({ auth, ...options });
2127
2197
  return createRawClient({
2128
2198
  controller,
2129
- origin: { provider: "vercel", sdkKey }
2199
+ origin: { provider: "vercel", sdkKey: auth.sdkKey }
2130
2200
  });
2131
2201
  }
2132
2202
  function resetDefaultFlagsClient2() {
@@ -2135,14 +2205,7 @@ function make(createRawClient) {
2135
2205
  const flagsClient2 = new Proxy({}, {
2136
2206
  get(_, prop) {
2137
2207
  if (!_defaultFlagsClient) {
2138
- if (!process.env.FLAGS) {
2139
- throw new Error("flags: Missing environment variable FLAGS");
2140
- }
2141
- const sdkKey = parseSdkKeyFromFlagsConnectionString(process.env.FLAGS);
2142
- if (!sdkKey) {
2143
- throw new Error("@vercel/flags-core: Missing sdkKey");
2144
- }
2145
- _defaultFlagsClient = createClient2(sdkKey);
2208
+ _defaultFlagsClient = createClient2(process.env.FLAGS);
2146
2209
  }
2147
2210
  return _defaultFlagsClient[prop];
2148
2211
  }
@@ -2159,7 +2222,7 @@ function setCacheLife() {
2159
2222
  try {
2160
2223
  _cache.cacheLife.call(void 0, { revalidate: 0, expire: 0 });
2161
2224
  _cache.cacheLife.call(void 0, { stale: 60 });
2162
- } catch (e15) {
2225
+ } catch (e16) {
2163
2226
  }
2164
2227
  }
2165
2228
  var cachedFns = {
@@ -2208,4 +2271,4 @@ var { flagsClient, resetDefaultFlagsClient, createClient } = make(
2208
2271
 
2209
2272
 
2210
2273
  exports.ResolutionReason = ResolutionReason; exports.evaluate = evaluate; exports.FallbackNotFoundError = FallbackNotFoundError; exports.FallbackEntryNotFoundError = FallbackEntryNotFoundError; exports.Controller = Controller; exports.flagsClient = flagsClient; exports.resetDefaultFlagsClient = resetDefaultFlagsClient; exports.createClient = createClient;
2211
- //# sourceMappingURL=chunk-XINPOF5O.cjs.map
2274
+ //# sourceMappingURL=chunk-CSSU7O2G.cjs.map