@vercel/flags-core 1.10.3 → 1.11.0-31e6462-20261026142820

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,19 +1,15 @@
1
1
  # @vercel/flags-core
2
2
 
3
- ## 1.10.3
3
+ ## 1.11.0-31e6462-20261026142820
4
4
 
5
- ### Patch Changes
6
-
7
- - [#578](https://github.com/vercel/flags/pull/578) [`63d13be`](https://github.com/vercel/flags/commit/63d13be0193102a4f0cbcbf48e7f67efad96a462) Thanks [@luismeyer](https://github.com/luismeyer)! - Fail fast on authentication errors. When the stream gets a 401 or no OIDC token is available, the client no longer falls back to polling, which would fail the same way, and goes straight to revalidating over HTTP on reads. When polling is the primary source, its first auth failure stops it, and a missing OIDC token is no longer retried. A failed OIDC token refresh is still retried. A client without credentials no longer keeps scripts and test files running.
5
+ ### Minor Changes
8
6
 
9
- ## 1.10.2
7
+ - [#539](https://github.com/vercel/flags/pull/539) [`fc7da82`](https://github.com/vercel/flags/commit/fc7da82d4cffb9d4662335e984e09fec22ba4a6b) Thanks [@vincent-derks](https://github.com/vincent-derks)! - Add a `projectId` option to read the flags of a connected project.
10
8
 
11
9
  ### Patch Changes
12
10
 
13
11
  - [#567](https://github.com/vercel/flags/pull/567) [`5f738f0`](https://github.com/vercel/flags/commit/5f738f08f182838d0efb13fd3efb89751d48cb0a) Thanks [@vincent-derks](https://github.com/vincent-derks)! - Improve guidance for authorization errors.
14
12
 
15
- - [#573](https://github.com/vercel/flags/pull/573) [`e2a1a39`](https://github.com/vercel/flags/commit/e2a1a394e3de4b271c8305bae6cbcf053814ab70) Thanks [@luismeyer](https://github.com/luismeyer)! - Read embedded flag definitions when the client is created, using the SDK key or `VERCEL_PROJECT_ID` in OIDC mode.
16
-
17
13
  ## 1.10.1
18
14
 
19
15
  ### Patch Changes
package/README.md CHANGED
@@ -33,6 +33,18 @@ export default app;
33
33
 
34
34
  Outside Vercel, pass an SDK key explicitly: `createClient(process.env.FLAGS)`.
35
35
 
36
+ ### Read a connected project's flags
37
+
38
+ Grant your project access from the source project's **Flags → Connections** page, then create a client with the source project's ID:
39
+
40
+ ```ts
41
+ const sharedClient = createClient({ projectId: 'prj_source' });
42
+ ```
43
+
44
+ The client uses your project's OIDC token. Both projects must belong to the same team. Connections apply across environments; the token selects the source environment, with custom environments mapped to preview. Run `vercel env pull` in your project for local development. The `projectId` option cannot be combined with an SDK key.
45
+
46
+ See [Project Connections](https://vercel.com/docs/flags/vercel-flags/project-connections) for permissions and setup.
47
+
36
48
  ## Header-driven reads on Vercel
37
49
 
38
50
  When `VERCEL=1`, the client defaults to `vercel: true`. Initialization loads provided
@@ -559,7 +559,7 @@ function bulkEvaluate(flags, shared) {
559
559
  }
560
560
 
561
561
  // package.json
562
- var version = "1.10.3";
562
+ var version = "1.11.0-31e6462-20261026142820";
563
563
 
564
564
  // src/lib/report-value.ts
565
565
  function internalReportValue(key, value, data) {
@@ -1063,6 +1063,10 @@ var _oidc = require('@vercel/oidc');
1063
1063
 
1064
1064
  // src/utils/sdk-keys.ts
1065
1065
  var SDK_KEY_REGEX = /^vf_(?:server|client)_/;
1066
+ var PROJECT_ID_REGEX = /^[A-Za-z0-9_]{1,64}$/;
1067
+ function isValidProjectId(value) {
1068
+ return PROJECT_ID_REGEX.test(value);
1069
+ }
1066
1070
  function parseSdkKeyFromFlagsConnectionString(text) {
1067
1071
  if (SDK_KEY_REGEX.test(text)) return text;
1068
1072
  try {
@@ -1089,26 +1093,17 @@ function unauthorizedMessage(sourceProjectId) {
1089
1093
  }
1090
1094
  return `unauthorized (401): this deployment is not allowed to read the flags of project "${sourceProjectId}". Check that the project is connected. See https://vercel.com/docs/flags/vercel-flags/project-connections`;
1091
1095
  }
1092
- var oidcTokenMessage = [
1093
- "@vercel/flags-core: Failed to get OIDC token.",
1094
- "Are you running in a Vercel Environment where OIDC tokens are available?",
1095
- "Did you mean to use an SDK Key instead? Use the environment variable FLAGS or pass it directly to the client."
1096
- ].join(" ");
1097
- var missingCredentials = /`vc link|vercel login|^The 'x-vercel-oidc-token' header is missing from the request\.$/;
1098
- var OidcTokenError = class extends Error {
1099
- constructor(cause) {
1100
- super(oidcTokenMessage, { cause });
1101
- this.name = "OidcTokenError";
1102
- }
1103
- };
1104
1096
  async function getOidcToken() {
1105
1097
  try {
1106
1098
  return await _oidc.getVercelOidcToken.call(void 0, );
1107
- } catch (error) {
1108
- if (error instanceof Error && missingCredentials.test(error.message)) {
1109
- throw new OidcTokenError(error);
1110
- }
1111
- throw new Error(oidcTokenMessage, { cause: error });
1099
+ } catch (e7) {
1100
+ throw new Error(
1101
+ [
1102
+ "@vercel/flags-core: Failed to get OIDC token.",
1103
+ "Are you running in a Vercel Environment where OIDC tokens are available?",
1104
+ "Did you mean to use an SDK Key instead? Use the environment variable FLAGS or pass it directly to the client."
1105
+ ].join(" ")
1106
+ );
1112
1107
  }
1113
1108
  }
1114
1109
  function getProjectIdFromOidcToken(oidcToken) {
@@ -1129,7 +1124,21 @@ function getProjectIdFromOidcToken(oidcToken) {
1129
1124
  var Authentication = class {
1130
1125
 
1131
1126
 
1132
- constructor(sdkKeyOrConnectionString) {
1127
+ constructor(sdkKeyOrConnectionString, options = {}) {
1128
+ if (options.projectId !== void 0) {
1129
+ if (typeof options.projectId !== "string" || !isValidProjectId(options.projectId)) {
1130
+ throw new Error(
1131
+ `@vercel/flags-core: Invalid projectId "${options.projectId}". Expected a project id like "prj_\u2026" (letters, digits and underscores, up to 64 characters)`
1132
+ );
1133
+ }
1134
+ if (sdkKeyOrConnectionString !== void 0) {
1135
+ throw new Error(
1136
+ "@vercel/flags-core: projectId cannot be combined with an SDK key. Connected projects are read with the OIDC token of this deployment."
1137
+ );
1138
+ }
1139
+ this.sourceProjectId = options.projectId;
1140
+ return;
1141
+ }
1133
1142
  if (sdkKeyOrConnectionString !== void 0) {
1134
1143
  if (typeof sdkKeyOrConnectionString !== "string") {
1135
1144
  throw new Error(
@@ -1151,24 +1160,13 @@ var Authentication = class {
1151
1160
  }
1152
1161
  return await getOidcToken();
1153
1162
  }
1154
- getStaticBundledDefinitionsLookup() {
1163
+ async resolveBundledDefinitionsLookup() {
1155
1164
  if (this.sdkKey) {
1156
1165
  return { type: "sdk-key", sdkKey: this.sdkKey };
1157
1166
  }
1158
1167
  if (this.sourceProjectId) {
1159
1168
  return { type: "project-id", projectId: this.sourceProjectId };
1160
1169
  }
1161
- const projectId = process.env.VERCEL_PROJECT_ID;
1162
- if (projectId) {
1163
- return { type: "project-id", projectId };
1164
- }
1165
- return void 0;
1166
- }
1167
- async resolveBundledDefinitionsLookup() {
1168
- const lookup = this.getStaticBundledDefinitionsLookup();
1169
- if (lookup) {
1170
- return lookup;
1171
- }
1172
1170
  const oidcToken = await this.resolveToken();
1173
1171
  return {
1174
1172
  type: "project-id",
@@ -1238,7 +1236,7 @@ function getRuntimeIngest() {
1238
1236
  try {
1239
1237
  const context = globalThis[FLAGS_CONTEXT_SYMBOL];
1240
1238
  return typeof _optionalChain([context, 'optionalAccess', _34 => _34.ingest]) === "function" ? context.ingest : void 0;
1241
- } catch (e7) {
1239
+ } catch (e8) {
1242
1240
  return void 0;
1243
1241
  }
1244
1242
  }
@@ -1256,7 +1254,7 @@ async function getEvaluatingOidcToken(auth) {
1256
1254
  if (!auth.sdkKey) return void 0;
1257
1255
  try {
1258
1256
  return await _oidc.getVercelOidcToken.call(void 0, );
1259
- } catch (e8) {
1257
+ } catch (e9) {
1260
1258
  return void 0;
1261
1259
  }
1262
1260
  }
@@ -1368,7 +1366,7 @@ var Scheduler = (_class = class {
1368
1366
  })();
1369
1367
  try {
1370
1368
  this.scheduleTask(this.pending);
1371
- } catch (e9) {
1369
+ } catch (e10) {
1372
1370
  }
1373
1371
  this.maxTimeout = setTimeout(
1374
1372
  () => this.resolveScheduledFlush("max_timeout"),
@@ -1595,7 +1593,7 @@ var UsageTracker = (_class4 = class {
1595
1593
  void flush.finally(() => this.inflightFlushes.delete(flush));
1596
1594
  try {
1597
1595
  _optionalChain([this, 'access', _37 => _37.waitUntil, 'optionalCall', _38 => _38(flush)]);
1598
- } catch (e10) {
1596
+ } catch (e11) {
1599
1597
  }
1600
1598
  } else {
1601
1599
  this.scheduler.scheduleFlush();
@@ -1678,13 +1676,6 @@ var BundledSource = class {
1678
1676
  }
1679
1677
  return void 0;
1680
1678
  }
1681
- /**
1682
- * Starts reading bundled definitions without waiting for the result.
1683
- */
1684
- preload() {
1685
- this.getResult().catch(() => {
1686
- });
1687
- }
1688
1679
  getResult() {
1689
1680
  if (!this.promise) {
1690
1681
  this.promise = this.options.readBundledDefinitions(this.options.auth);
@@ -1700,7 +1691,7 @@ function debug(clientName, event, details) {
1700
1691
  }
1701
1692
  try {
1702
1693
  console.debug("@vercel/flags-core", { event, clientName, ..._optionalChain([details, 'optionalCall', _40 => _40()]) });
1703
- } catch (e11) {
1694
+ } catch (e12) {
1704
1695
  }
1705
1696
  }
1706
1697
 
@@ -2039,7 +2030,7 @@ var DatafileCache = (_class5 = class {
2039
2030
  });
2040
2031
  try {
2041
2032
  this.waitUntil(background);
2042
- } catch (e12) {
2033
+ } catch (e13) {
2043
2034
  }
2044
2035
  }
2045
2036
  /** Resets storage, age, and the failure deadline so a restarted client begins clean. */
@@ -2152,8 +2143,7 @@ async function fetchDatafile(options) {
2152
2143
  return await Promise.race([fetchAttempt(attempt + 1), aborted]);
2153
2144
  } catch (error) {
2154
2145
  signal.throwIfAborted();
2155
- if (attempt === maxAttempts - 1 || error instanceof DatafileHttpError && !error.retryable || // Retrying will not produce a token that is not there.
2156
- error instanceof OidcTokenError) {
2146
+ if (attempt === maxAttempts - 1 || error instanceof DatafileHttpError && !error.retryable) {
2157
2147
  throw error instanceof Error ? error : new Error("Unknown fetch error");
2158
2148
  }
2159
2149
  debug(options.clientName, "datafile.fetch.retry", () => ({
@@ -2571,7 +2561,7 @@ async function connectStream(config, callbacks) {
2571
2561
  rejectInit(error);
2572
2562
  }
2573
2563
  _optionalChain([onDisconnect, 'optionalCall', _97 => _97()]);
2574
- abortController.abort(error);
2564
+ abortController.abort();
2575
2565
  break;
2576
2566
  }
2577
2567
  if (!response.ok) {
@@ -2607,7 +2597,7 @@ async function connectStream(config, callbacks) {
2607
2597
  let message;
2608
2598
  try {
2609
2599
  message = JSON.parse(line);
2610
- } catch (e13) {
2600
+ } catch (e14) {
2611
2601
  console.warn(
2612
2602
  "@vercel/flags-core: Failed to parse stream message, skipping"
2613
2603
  );
@@ -2673,7 +2663,7 @@ async function connectStream(config, callbacks) {
2673
2663
  }
2674
2664
  if (error instanceof TokenResolutionError && !initialDataReceived) {
2675
2665
  rejectInit(error.cause);
2676
- abortController.abort(error.cause);
2666
+ abortController.abort();
2677
2667
  break;
2678
2668
  }
2679
2669
  if (connectionAbort.signal.reason !== PING_TIMEOUT) {
@@ -2747,7 +2737,7 @@ var StreamSource = (_class9 = class extends TypedEmitter {
2747
2737
  if (this.abortController === abortController) {
2748
2738
  this.promise = void 0;
2749
2739
  this.abortController = void 0;
2750
- this.emit("exhausted", abortController.signal.reason);
2740
+ this.emit("exhausted");
2751
2741
  }
2752
2742
  },
2753
2743
  { once: true }
@@ -2808,7 +2798,7 @@ var UNKNOWN_FRESHNESS = {
2808
2798
  assess: () => ({ status: "unknown" })
2809
2799
  };
2810
2800
  function isUnauthorizedError(error) {
2811
- if (error instanceof UnauthorizedError || error instanceof OidcTokenError) {
2801
+ if (error instanceof UnauthorizedError) {
2812
2802
  return true;
2813
2803
  }
2814
2804
  if (!(error instanceof Error)) {
@@ -2843,9 +2833,8 @@ var Controller = (_class10 = class {
2843
2833
  // Build-step deduplication
2844
2834
  __init23() {this.buildDataPromise = null}
2845
2835
  __init24() {this.buildReadTracked = false}
2846
- // Suppresses usage tracking while the credential is rejected or no OIDC
2847
- // token is available. Set on an auth failure from any source, cleared as
2848
- // soon as a source delivers data again.
2836
+ // Suppresses usage tracking while the credential is rejected. Set on a 401
2837
+ // from any source, cleared as soon as a source delivers data again.
2849
2838
  __init25() {this.unauthorized = false}
2850
2839
  constructor(options) {;_class10.prototype.__init18.call(this);_class10.prototype.__init19.call(this);_class10.prototype.__init20.call(this);_class10.prototype.__init21.call(this);_class10.prototype.__init22.call(this);_class10.prototype.__init23.call(this);_class10.prototype.__init24.call(this);_class10.prototype.__init25.call(this);_class10.prototype.__init26.call(this);_class10.prototype.__init27.call(this);_class10.prototype.__init28.call(this);_class10.prototype.__init29.call(this);_class10.prototype.__init30.call(this);_class10.prototype.__init31.call(this);_class10.prototype.__init32.call(this);_class10.prototype.__init33.call(this);_class10.prototype.__init34.call(this);
2851
2840
  this.options = normalizeOptions(options);
@@ -2882,9 +2871,6 @@ var Controller = (_class10 = class {
2882
2871
  auth: this.options.auth,
2883
2872
  readBundledDefinitions
2884
2873
  });
2885
- if (!this.options.datafile && this.options.auth.getStaticBundledDefinitionsLookup()) {
2886
- this.bundledSource.preload();
2887
- }
2888
2874
  this.wireSourceEvents();
2889
2875
  if (this.options.datafile) {
2890
2876
  this.cache.seed(tagData({ ...this.options.datafile }, "provided"));
@@ -2941,16 +2927,10 @@ var Controller = (_class10 = class {
2941
2927
  this.transition("degraded", "stream-disconnected");
2942
2928
  }
2943
2929
  }}
2944
- __init31() {this.onStreamExhausted = (reason) => {
2930
+ __init31() {this.onStreamExhausted = () => {
2945
2931
  if (this.state !== "streaming" && this.state !== "degraded") {
2946
2932
  return;
2947
2933
  }
2948
- if (isUnauthorizedError(reason)) {
2949
- if (this.state === "streaming") {
2950
- this.transition("degraded", "stream-auth-failed");
2951
- }
2952
- return;
2953
- }
2954
2934
  void this.activateFallbackSource().catch(() => {
2955
2935
  });
2956
2936
  }}
@@ -2962,7 +2942,7 @@ var Controller = (_class10 = class {
2962
2942
  this.onSourceError(error);
2963
2943
  if (this.state === "polling" && isUnauthorizedError(error)) {
2964
2944
  this.pollingSource.stop();
2965
- this.transition("degraded", "polling-auth-failed");
2945
+ this.transition("degraded", "polling-unauthorized");
2966
2946
  }
2967
2947
  }}
2968
2948
  // ---------------------------------------------------------------------------
@@ -3091,7 +3071,7 @@ var Controller = (_class10 = class {
3091
3071
  try {
3092
3072
  const bundled = await this.bundledSource.tryLoad();
3093
3073
  if (bundled) this.cache.seed(tagData({ ...bundled }, "bundled"));
3094
- } catch (e14) {
3074
+ } catch (e15) {
3095
3075
  }
3096
3076
  }
3097
3077
  if (this.headerSource.isEnabled()) {
@@ -3276,7 +3256,7 @@ var Controller = (_class10 = class {
3276
3256
  }
3277
3257
  try {
3278
3258
  await this.cache.refresh();
3279
- } catch (e15) {
3259
+ } catch (e16) {
3280
3260
  throw this.noDefinitionsError(
3281
3261
  ". Initialize the client or provide a datafile."
3282
3262
  );
@@ -3372,10 +3352,6 @@ var Controller = (_class10 = class {
3372
3352
  this.transition("degraded", "stream-still-connecting");
3373
3353
  return;
3374
3354
  }
3375
- if (outcome === "auth-failed") {
3376
- this.transition("degraded", "stream-auth-failed");
3377
- return;
3378
- }
3379
3355
  }
3380
3356
  if (this.options.polling.enabled) {
3381
3357
  if (this.state !== "polling") {
@@ -3408,7 +3384,7 @@ var Controller = (_class10 = class {
3408
3384
  return "connected";
3409
3385
  } catch (error) {
3410
3386
  this.noteUnauthorized(error);
3411
- return isUnauthorizedError(error) ? "auth-failed" : "failed";
3387
+ return "failed";
3412
3388
  }
3413
3389
  }
3414
3390
  let timeoutId;
@@ -3442,7 +3418,7 @@ var Controller = (_class10 = class {
3442
3418
  } catch (error) {
3443
3419
  clearTimeout(timeoutId);
3444
3420
  this.noteUnauthorized(error);
3445
- return isUnauthorizedError(error) ? "auth-failed" : "failed";
3421
+ return "failed";
3446
3422
  }
3447
3423
  }
3448
3424
  // ---------------------------------------------------------------------------
@@ -3593,7 +3569,7 @@ var Controller = (_class10 = class {
3593
3569
  this.acceptLastResortFetch(await this.fetchOnce());
3594
3570
  this.transition("degraded", "fetched-fallback");
3595
3571
  return;
3596
- } catch (e16) {
3572
+ } catch (e17) {
3597
3573
  }
3598
3574
  }
3599
3575
  throw this.noDefinitionsError(". Bundled definitions not found.");
@@ -3638,7 +3614,7 @@ var Controller = (_class10 = class {
3638
3614
  let fetched;
3639
3615
  try {
3640
3616
  fetched = await this.fetchOnce();
3641
- } catch (e17) {
3617
+ } catch (e18) {
3642
3618
  }
3643
3619
  if (fetched) {
3644
3620
  const remote = this.acceptLastResortFetch(fetched);
@@ -3704,8 +3680,8 @@ function make(createRawClient, defaults) {
3704
3680
  const optionsOnly = typeof sdkKeyOrConnectionStringOrOptions === "object" && sdkKeyOrConnectionStringOrOptions !== null;
3705
3681
  const sdkKeyOrConnectionString = optionsOnly ? void 0 : sdkKeyOrConnectionStringOrOptions;
3706
3682
  const createClientOptions = optionsOnly ? sdkKeyOrConnectionStringOrOptions : options;
3707
- const { experimental_reportExposures, ...controllerOptions } = _nullishCoalesce(createClientOptions, () => ( {}));
3708
- const auth = new Authentication(sdkKeyOrConnectionString);
3683
+ const { experimental_reportExposures, projectId, ...controllerOptions } = _nullishCoalesce(createClientOptions, () => ( {}));
3684
+ const auth = new Authentication(sdkKeyOrConnectionString, { projectId });
3709
3685
  const waitUntil2 = _nullishCoalesce(controllerOptions.waitUntil, () => ( defaults.waitUntil));
3710
3686
  const controller = new Controller({
3711
3687
  auth,
@@ -3746,7 +3722,7 @@ function setCacheLife() {
3746
3722
  try {
3747
3723
  _cache.cacheLife.call(void 0, { revalidate: 0, expire: 0 });
3748
3724
  _cache.cacheLife.call(void 0, { stale: 60 });
3749
- } catch (e18) {
3725
+ } catch (e19) {
3750
3726
  }
3751
3727
  }
3752
3728
  var cachedFns = {
@@ -3796,4 +3772,4 @@ var { flagsClient, resetDefaultFlagsClient, createClient } = make(
3796
3772
 
3797
3773
 
3798
3774
  exports.ResolutionReason = ResolutionReason; exports.evaluate = evaluate; exports.FallbackNotFoundError = FallbackNotFoundError; exports.FallbackEntryNotFoundError = FallbackEntryNotFoundError; exports.Controller = Controller; exports.flagsClient = flagsClient; exports.resetDefaultFlagsClient = resetDefaultFlagsClient; exports.createClient = createClient;
3799
- //# sourceMappingURL=chunk-EGGUENXP.cjs.map
3775
+ //# sourceMappingURL=chunk-J73HQYK2.cjs.map