@vercel/flags-core 1.9.0 → 1.10.0-0037f35-20261006132750

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.
Files changed (33) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +184 -0
  3. package/dist/{chunk-TDZBUVBG.js → chunk-D4522FGO.js} +1295 -367
  4. package/dist/chunk-D4522FGO.js.map +1 -0
  5. package/dist/{chunk-PGKH4XQ7.cjs → chunk-ITB5XQD3.cjs} +1335 -407
  6. package/dist/chunk-ITB5XQD3.cjs.map +1 -0
  7. package/dist/{chunk-SLNLPFKU.js → chunk-SYXANNZ4.js} +1295 -367
  8. package/dist/chunk-SYXANNZ4.js.map +1 -0
  9. package/dist/{chunk-W742GGZU.cjs → chunk-W6776MKF.cjs} +1336 -408
  10. package/dist/chunk-W6776MKF.cjs.map +1 -0
  11. package/dist/index.default.cjs +2 -2
  12. package/dist/index.default.d.cts +104 -39
  13. package/dist/index.default.d.ts +104 -39
  14. package/dist/index.default.js +1 -1
  15. package/dist/index.next-js.cjs +2 -2
  16. package/dist/index.next-js.d.cts +104 -39
  17. package/dist/index.next-js.d.ts +104 -39
  18. package/dist/index.next-js.js +1 -1
  19. package/dist/openfeature.default.cjs +2 -2
  20. package/dist/openfeature.default.d.cts +1 -1
  21. package/dist/openfeature.default.d.ts +1 -1
  22. package/dist/openfeature.default.js +1 -1
  23. package/dist/openfeature.next-js.cjs +2 -2
  24. package/dist/openfeature.next-js.d.cts +1 -1
  25. package/dist/openfeature.next-js.d.ts +1 -1
  26. package/dist/openfeature.next-js.js +1 -1
  27. package/dist/{types-1OK52NNi.d.cts → types-DpVlT3fI.d.cts} +7 -1
  28. package/dist/{types-1OK52NNi.d.ts → types-DpVlT3fI.d.ts} +7 -1
  29. package/package.json +1 -1
  30. package/dist/chunk-PGKH4XQ7.cjs.map +0 -1
  31. package/dist/chunk-SLNLPFKU.js.map +0 -1
  32. package/dist/chunk-TDZBUVBG.js.map +0 -1
  33. package/dist/chunk-W742GGZU.cjs.map +0 -1
@@ -1,5 +1,5 @@
1
- import { V as VariantId, R as ResolutionReason, D as DatafileInput, S as StreamOptions, P as PollingOptions, W as WaitUntil, M as MetricEnvironment, C as ControllerInterface, a as Datafile, B as BundledDefinitions, e as experimental_ReportExposures, E as EvaluationParams, b as EvaluationResult, F as FlagsClient } from './types-1OK52NNi.cjs';
2
- export { c as Packed, d as Value, f as experimental_EvaluationOptions, g as experimental_ExperimentAssignment, h as experimental_Exposure } from './types-1OK52NNi.cjs';
1
+ import { V as VariantId, R as ResolutionReason, D as DatafileInput, S as StreamOptions, P as PollingOptions, W as WaitUntil, M as MetricEnvironment, C as ControllerInterface, a as Datafile, B as BundledDefinitions, e as experimental_ReportExposures, E as EvaluationParams, b as EvaluationResult, F as FlagsClient } from './types-DpVlT3fI.cjs';
2
+ export { c as Packed, d as Value, f as experimental_EvaluationOptions, g as experimental_ExperimentAssignment, h as experimental_Exposure } from './types-DpVlT3fI.cjs';
3
3
 
4
4
  interface TrackEvaluationOptions {
5
5
  flagKey: string;
@@ -51,6 +51,37 @@ type ControllerOptions = {
51
51
  * @default true
52
52
  */
53
53
  polling?: boolean | PollingOptions;
54
+ /**
55
+ * Use request version headers instead of streaming or polling at runtime.
56
+ * Initialization starts no network activity; reads fetch only when needed.
57
+ * An evaluation without a version header permanently falls back to stream/poll.
58
+ * Disabling both stream and polling still selects offline mode.
59
+ * @default process.env.VERCEL === '1'
60
+ */
61
+ vercel?: boolean;
62
+ /**
63
+ * How long reads may serve stale cached data while refreshing in the background.
64
+ * Header-driven reads measure from the last fetch or matching version header.
65
+ * Streaming data is fresh for 60 seconds after the last message and polling data
66
+ * for its interval plus the 10-second fetch deadline; this window follows, after
67
+ * which reads wait for the refresh. Accepts finite, non-negative seconds,
68
+ * including fractional seconds. Set to 0 to block as soon as data is no longer fresh.
69
+ * @default 10
70
+ */
71
+ staleWhileRevalidate?: number;
72
+ /**
73
+ * How long runtime reads may use cached data after the first consecutive
74
+ * stream/poll/header failure or stream disconnect. Accepts nonnegative seconds or Infinity.
75
+ * Fractional seconds are supported.
76
+ * Zero disables fallback immediately; positive windows include the deadline.
77
+ * Any successful source response, or a matching stream primed revision, resets the
78
+ * allowance. A routine stream reconnect takes about a second, so keep this above
79
+ * the reconnect delay. Applies to evaluations and getDatafile(). Without a live
80
+ * source, data older than staleWhileRevalidate plus this window waits for a refresh.
81
+ * Build/offline behavior is unchanged.
82
+ * @default Infinity
83
+ */
84
+ staleIfError?: number;
54
85
  /**
55
86
  * Override build step detection
56
87
  * - `true`: Treat as build step (use datafile/bundled only, no network)
@@ -98,14 +129,22 @@ type ControllerOptions = {
98
129
  * - No streaming or polling
99
130
  *
100
131
  * **Runtime — streaming mode** (stream enabled):
101
- * - Uses streaming exclusively; polling is never started, even if configured
102
- * - Init fallback (no data yet): constructor datafile → bundled → throw
103
- * - Read fallback (post-init): in-memory value → constructor datafile → bundled → throw
132
+ * - Streams exclusively; a startup timeout keeps connecting in the background
133
+ * - Retains provided/bundled data during startup; fetches if the cache remains empty
134
+ * - Stale reads serve the cache; expired reads wait for the stream to confirm, never HTTP
135
+ * - Polling starts only once the stream gives up (retries exhausted, 401, or token failure)
136
+ * - Without a live source (`degraded`), reads apply stale-while-revalidate over HTTP
104
137
  *
105
138
  * **Runtime — polling mode** (polling enabled, stream disabled):
106
- * - Uses polling exclusively
139
+ * - Uses polling exclusively; a 401 stops polling and reads revalidate over HTTP
107
140
  * - Same fallback chains as streaming mode
108
141
  *
142
+ * **Runtime — Vercel mode** (vercel enabled, with stream or polling enabled):
143
+ * - Loads provided/bundled data before selecting the mode; no startup network
144
+ * - HeaderSource checks request versions and refreshes when needed
145
+ * - An evaluation without a valid project version header permanently starts stream/poll
146
+ * - Cache applies version acceptance and stale-if-error to all served data
147
+ *
109
148
  * **Runtime — offline mode** (neither stream nor polling):
110
149
  * - Init fallback: constructor datafile → bundled → one-time fetch → throw
111
150
  * - Read fallback: in-memory value → constructor datafile → bundled → one-time fetch → throw
@@ -113,12 +152,15 @@ type ControllerOptions = {
113
152
  declare class Controller implements ControllerInterface {
114
153
  private options;
115
154
  private state;
116
- private data;
155
+ private readonly cache;
117
156
  private dataViewSource;
118
157
  private dataViewBase;
119
158
  private streamSource;
120
159
  private pollingSource;
121
160
  private bundledSource;
161
+ private headerSource;
162
+ private sourceStartup;
163
+ private readonly degradedReadPolicy;
122
164
  private usageTracker;
123
165
  private isFirstGetData;
124
166
  private buildDataPromise;
@@ -127,13 +169,17 @@ declare class Controller implements ControllerInterface {
127
169
  constructor(options: ControllerOptions);
128
170
  private onStreamData;
129
171
  private onStreamPrimed;
172
+ private onStreamPing;
130
173
  private onStreamConnected;
131
174
  private onStreamDisconnected;
132
- private onPollData;
133
- private onPollError;
175
+ private onStreamExhausted;
176
+ private onSourceError;
177
+ private onPollingError;
134
178
  private wireSourceEvents;
135
179
  private unwireSourceEvents;
180
+ private debugState;
136
181
  private transition;
182
+ private get isShutdown();
137
183
  private get isConnected();
138
184
  private get mode();
139
185
  /**
@@ -142,6 +188,7 @@ declare class Controller implements ControllerInterface {
142
188
  * Build step: datafile → bundled → one-time fetch
143
189
  * Streaming mode: stream → datafile → bundled
144
190
  * Polling mode (no stream): poll → datafile → bundled
191
+ * Vercel mode: datafile → bundled; fetch only on a read
145
192
  * Offline mode (neither): datafile → bundled → one-time fetch
146
193
  */
147
194
  initialize(): Promise<void>;
@@ -154,36 +201,62 @@ declare class Controller implements ControllerInterface {
154
201
  */
155
202
  shutdown(): Promise<void>;
156
203
  /**
157
- * Returns the datafile with metrics.
158
- * Uses in-memory data if available, otherwise falls back to bundled,
159
- * then to a one-time fetch if called without prior initialization.
204
+ * Returns the datafile with metrics as a snapshot: it never starts streaming
205
+ * or polling. Cached data is served through the active source policy
206
+ * (header checks and HTTP revalidation); an empty cache loads bundled
207
+ * definitions, then performs a one-time fetch.
160
208
  */
161
209
  getDatafile(): Promise<Datafile>;
162
210
  /**
163
211
  * Returns the bundled fallback datafile.
164
212
  */
165
213
  getFallbackDatafile(): Promise<BundledDefinitions>;
214
+ private toDatafile;
166
215
  /**
167
216
  * Resolves the current data, using the appropriate strategy for the
168
217
  * current mode. Returns tagged data and cache status.
169
218
  *
170
219
  * Build step: cached → bundled → one-time fetch
171
- * Runtime with cache: return cached data
172
- * Runtime without cache: stream/poll → datafile → bundled → fetch → throw
220
+ * Runtime: source policy chooses cached data or refresh; fall back if empty.
173
221
  */
174
222
  private resolveData;
223
+ /** Initializes the active runtime source, then resolves through its policy. */
224
+ private resolveRuntimeData;
175
225
  /**
176
- * Attempts to initialize via stream with timeout.
177
- * Returns true if stream connected successfully within timeout.
226
+ * Resolves data for getDatafile() without starting a runtime source.
227
+ * Build step: cached → bundled → one-time fetch
228
+ * Runtime: cached data through the source policy; otherwise bundled → one-time fetch → throw
229
+ */
230
+ private resolveSnapshot;
231
+ private get cacheReadPolicy();
232
+ /**
233
+ * Without a live source nothing confirms the cache, so reads apply plain
234
+ * stale-while-revalidate over HTTP from the two public windows: data
235
+ * refreshed within `staleWhileRevalidateMs` is served as is, older data is
236
+ * served while refreshing in the background, and data older than that
237
+ * window plus `staleIfErrorMs` is expired and waits for the refresh. An
238
+ * unknown age is not known to be old, so it refreshes in the background.
239
+ * Offline clients never refresh.
240
+ */
241
+ private createDegradedReadPolicy;
242
+ /**
243
+ * Advances through the runtime source chain. Every caller uses the same path:
244
+ * request headers → stream → polling → direct cache refresh.
245
+ */
246
+ private activateFallbackSource;
247
+ private startFallbackSource;
248
+ /**
249
+ * Attempts to initialize via stream with timeout. A timeout leaves the
250
+ * stream connecting in the background; a rejection means it gave up.
178
251
  */
179
252
  private tryInitializeStream;
180
253
  /**
181
- * Attempts to initialize via polling with timeout.
182
- * Returns true if first poll succeeded within timeout.
183
- *
184
- * Only used when streaming is disabled and polling is the primary source.
254
+ * Waits for the first poll whenever polling becomes the active runtime source.
255
+ * On timeout, initialization falls back while the pending poll and interval
256
+ * continue in the background. Poll errors propagate if no data is cached or
257
+ * the client is shutting down.
185
258
  */
186
- private tryInitializePolling;
259
+ private initializePolling;
187
260
  private noteUnauthorized;
188
261
  /**
189
262
  * Initializes data for build step environments.
@@ -192,13 +265,19 @@ declare class Controller implements ControllerInterface {
192
265
  /**
193
266
  * Retrieves data during build steps.
194
267
  * Concurrent callers share a single load promise. The first caller to
195
- * populate `this.data` gets cacheStatus MISS; subsequent callers get HIT.
268
+ * populate the cache gets cacheStatus MISS; subsequent callers get HIT.
196
269
  */
197
270
  private resolveDataForBuildStep;
198
271
  /**
199
272
  * Loads data for a build step: bundled → one-time fetch.
200
273
  */
201
274
  private loadBuildData;
275
+ private fetchOnce;
276
+ /**
277
+ * Stores a last-resort fetch as a source response, which clears any failure,
278
+ * and serves it through the cache boundary.
279
+ */
280
+ private acceptLastResortFetch;
202
281
  /**
203
282
  * Shared fallback chain used by both initialize() and resolveData().
204
283
  */
@@ -209,24 +288,10 @@ declare class Controller implements ControllerInterface {
209
288
  */
210
289
  private noDefinitionsError;
211
290
  /**
212
- * Retrieves data using the fallback chain (called when no cached data exists).
213
- * Streaming mode: stream → datafile → bundled.
214
- * Polling mode: poll → datafile → bundled.
215
- * Offline mode: datafile → bundled → one-time fetch.
216
- */
217
- private resolveDataWithFallbacks;
218
- /**
219
- * Checks if the incoming data is newer than the current in-memory data.
220
- * Returns true if the update should proceed, false if it should be skipped.
221
- *
222
- * Always accepts the update if:
223
- * - There is no current data
224
- * - The current data has no configUpdatedAt
225
- * - The incoming data has no configUpdatedAt
226
- *
227
- * Skips the update only when both have configUpdatedAt and incoming is not newer.
291
+ * Retrieves data when the cache is empty in offline mode:
292
+ * datafile → bundled → one-time fetch.
228
293
  */
229
- private isNewerData;
294
+ private resolveStaticFallbackData;
230
295
  /**
231
296
  * Tracks a read operation for usage analytics.
232
297
  * During build steps, only the first read is tracked.
@@ -1,5 +1,5 @@
1
- import { V as VariantId, R as ResolutionReason, D as DatafileInput, S as StreamOptions, P as PollingOptions, W as WaitUntil, M as MetricEnvironment, C as ControllerInterface, a as Datafile, B as BundledDefinitions, e as experimental_ReportExposures, E as EvaluationParams, b as EvaluationResult, F as FlagsClient } from './types-1OK52NNi.js';
2
- export { c as Packed, d as Value, f as experimental_EvaluationOptions, g as experimental_ExperimentAssignment, h as experimental_Exposure } from './types-1OK52NNi.js';
1
+ import { V as VariantId, R as ResolutionReason, D as DatafileInput, S as StreamOptions, P as PollingOptions, W as WaitUntil, M as MetricEnvironment, C as ControllerInterface, a as Datafile, B as BundledDefinitions, e as experimental_ReportExposures, E as EvaluationParams, b as EvaluationResult, F as FlagsClient } from './types-DpVlT3fI.js';
2
+ export { c as Packed, d as Value, f as experimental_EvaluationOptions, g as experimental_ExperimentAssignment, h as experimental_Exposure } from './types-DpVlT3fI.js';
3
3
 
4
4
  interface TrackEvaluationOptions {
5
5
  flagKey: string;
@@ -51,6 +51,37 @@ type ControllerOptions = {
51
51
  * @default true
52
52
  */
53
53
  polling?: boolean | PollingOptions;
54
+ /**
55
+ * Use request version headers instead of streaming or polling at runtime.
56
+ * Initialization starts no network activity; reads fetch only when needed.
57
+ * An evaluation without a version header permanently falls back to stream/poll.
58
+ * Disabling both stream and polling still selects offline mode.
59
+ * @default process.env.VERCEL === '1'
60
+ */
61
+ vercel?: boolean;
62
+ /**
63
+ * How long reads may serve stale cached data while refreshing in the background.
64
+ * Header-driven reads measure from the last fetch or matching version header.
65
+ * Streaming data is fresh for 60 seconds after the last message and polling data
66
+ * for its interval plus the 10-second fetch deadline; this window follows, after
67
+ * which reads wait for the refresh. Accepts finite, non-negative seconds,
68
+ * including fractional seconds. Set to 0 to block as soon as data is no longer fresh.
69
+ * @default 10
70
+ */
71
+ staleWhileRevalidate?: number;
72
+ /**
73
+ * How long runtime reads may use cached data after the first consecutive
74
+ * stream/poll/header failure or stream disconnect. Accepts nonnegative seconds or Infinity.
75
+ * Fractional seconds are supported.
76
+ * Zero disables fallback immediately; positive windows include the deadline.
77
+ * Any successful source response, or a matching stream primed revision, resets the
78
+ * allowance. A routine stream reconnect takes about a second, so keep this above
79
+ * the reconnect delay. Applies to evaluations and getDatafile(). Without a live
80
+ * source, data older than staleWhileRevalidate plus this window waits for a refresh.
81
+ * Build/offline behavior is unchanged.
82
+ * @default Infinity
83
+ */
84
+ staleIfError?: number;
54
85
  /**
55
86
  * Override build step detection
56
87
  * - `true`: Treat as build step (use datafile/bundled only, no network)
@@ -98,14 +129,22 @@ type ControllerOptions = {
98
129
  * - No streaming or polling
99
130
  *
100
131
  * **Runtime — streaming mode** (stream enabled):
101
- * - Uses streaming exclusively; polling is never started, even if configured
102
- * - Init fallback (no data yet): constructor datafile → bundled → throw
103
- * - Read fallback (post-init): in-memory value → constructor datafile → bundled → throw
132
+ * - Streams exclusively; a startup timeout keeps connecting in the background
133
+ * - Retains provided/bundled data during startup; fetches if the cache remains empty
134
+ * - Stale reads serve the cache; expired reads wait for the stream to confirm, never HTTP
135
+ * - Polling starts only once the stream gives up (retries exhausted, 401, or token failure)
136
+ * - Without a live source (`degraded`), reads apply stale-while-revalidate over HTTP
104
137
  *
105
138
  * **Runtime — polling mode** (polling enabled, stream disabled):
106
- * - Uses polling exclusively
139
+ * - Uses polling exclusively; a 401 stops polling and reads revalidate over HTTP
107
140
  * - Same fallback chains as streaming mode
108
141
  *
142
+ * **Runtime — Vercel mode** (vercel enabled, with stream or polling enabled):
143
+ * - Loads provided/bundled data before selecting the mode; no startup network
144
+ * - HeaderSource checks request versions and refreshes when needed
145
+ * - An evaluation without a valid project version header permanently starts stream/poll
146
+ * - Cache applies version acceptance and stale-if-error to all served data
147
+ *
109
148
  * **Runtime — offline mode** (neither stream nor polling):
110
149
  * - Init fallback: constructor datafile → bundled → one-time fetch → throw
111
150
  * - Read fallback: in-memory value → constructor datafile → bundled → one-time fetch → throw
@@ -113,12 +152,15 @@ type ControllerOptions = {
113
152
  declare class Controller implements ControllerInterface {
114
153
  private options;
115
154
  private state;
116
- private data;
155
+ private readonly cache;
117
156
  private dataViewSource;
118
157
  private dataViewBase;
119
158
  private streamSource;
120
159
  private pollingSource;
121
160
  private bundledSource;
161
+ private headerSource;
162
+ private sourceStartup;
163
+ private readonly degradedReadPolicy;
122
164
  private usageTracker;
123
165
  private isFirstGetData;
124
166
  private buildDataPromise;
@@ -127,13 +169,17 @@ declare class Controller implements ControllerInterface {
127
169
  constructor(options: ControllerOptions);
128
170
  private onStreamData;
129
171
  private onStreamPrimed;
172
+ private onStreamPing;
130
173
  private onStreamConnected;
131
174
  private onStreamDisconnected;
132
- private onPollData;
133
- private onPollError;
175
+ private onStreamExhausted;
176
+ private onSourceError;
177
+ private onPollingError;
134
178
  private wireSourceEvents;
135
179
  private unwireSourceEvents;
180
+ private debugState;
136
181
  private transition;
182
+ private get isShutdown();
137
183
  private get isConnected();
138
184
  private get mode();
139
185
  /**
@@ -142,6 +188,7 @@ declare class Controller implements ControllerInterface {
142
188
  * Build step: datafile → bundled → one-time fetch
143
189
  * Streaming mode: stream → datafile → bundled
144
190
  * Polling mode (no stream): poll → datafile → bundled
191
+ * Vercel mode: datafile → bundled; fetch only on a read
145
192
  * Offline mode (neither): datafile → bundled → one-time fetch
146
193
  */
147
194
  initialize(): Promise<void>;
@@ -154,36 +201,62 @@ declare class Controller implements ControllerInterface {
154
201
  */
155
202
  shutdown(): Promise<void>;
156
203
  /**
157
- * Returns the datafile with metrics.
158
- * Uses in-memory data if available, otherwise falls back to bundled,
159
- * then to a one-time fetch if called without prior initialization.
204
+ * Returns the datafile with metrics as a snapshot: it never starts streaming
205
+ * or polling. Cached data is served through the active source policy
206
+ * (header checks and HTTP revalidation); an empty cache loads bundled
207
+ * definitions, then performs a one-time fetch.
160
208
  */
161
209
  getDatafile(): Promise<Datafile>;
162
210
  /**
163
211
  * Returns the bundled fallback datafile.
164
212
  */
165
213
  getFallbackDatafile(): Promise<BundledDefinitions>;
214
+ private toDatafile;
166
215
  /**
167
216
  * Resolves the current data, using the appropriate strategy for the
168
217
  * current mode. Returns tagged data and cache status.
169
218
  *
170
219
  * Build step: cached → bundled → one-time fetch
171
- * Runtime with cache: return cached data
172
- * Runtime without cache: stream/poll → datafile → bundled → fetch → throw
220
+ * Runtime: source policy chooses cached data or refresh; fall back if empty.
173
221
  */
174
222
  private resolveData;
223
+ /** Initializes the active runtime source, then resolves through its policy. */
224
+ private resolveRuntimeData;
175
225
  /**
176
- * Attempts to initialize via stream with timeout.
177
- * Returns true if stream connected successfully within timeout.
226
+ * Resolves data for getDatafile() without starting a runtime source.
227
+ * Build step: cached → bundled → one-time fetch
228
+ * Runtime: cached data through the source policy; otherwise bundled → one-time fetch → throw
229
+ */
230
+ private resolveSnapshot;
231
+ private get cacheReadPolicy();
232
+ /**
233
+ * Without a live source nothing confirms the cache, so reads apply plain
234
+ * stale-while-revalidate over HTTP from the two public windows: data
235
+ * refreshed within `staleWhileRevalidateMs` is served as is, older data is
236
+ * served while refreshing in the background, and data older than that
237
+ * window plus `staleIfErrorMs` is expired and waits for the refresh. An
238
+ * unknown age is not known to be old, so it refreshes in the background.
239
+ * Offline clients never refresh.
240
+ */
241
+ private createDegradedReadPolicy;
242
+ /**
243
+ * Advances through the runtime source chain. Every caller uses the same path:
244
+ * request headers → stream → polling → direct cache refresh.
245
+ */
246
+ private activateFallbackSource;
247
+ private startFallbackSource;
248
+ /**
249
+ * Attempts to initialize via stream with timeout. A timeout leaves the
250
+ * stream connecting in the background; a rejection means it gave up.
178
251
  */
179
252
  private tryInitializeStream;
180
253
  /**
181
- * Attempts to initialize via polling with timeout.
182
- * Returns true if first poll succeeded within timeout.
183
- *
184
- * Only used when streaming is disabled and polling is the primary source.
254
+ * Waits for the first poll whenever polling becomes the active runtime source.
255
+ * On timeout, initialization falls back while the pending poll and interval
256
+ * continue in the background. Poll errors propagate if no data is cached or
257
+ * the client is shutting down.
185
258
  */
186
- private tryInitializePolling;
259
+ private initializePolling;
187
260
  private noteUnauthorized;
188
261
  /**
189
262
  * Initializes data for build step environments.
@@ -192,13 +265,19 @@ declare class Controller implements ControllerInterface {
192
265
  /**
193
266
  * Retrieves data during build steps.
194
267
  * Concurrent callers share a single load promise. The first caller to
195
- * populate `this.data` gets cacheStatus MISS; subsequent callers get HIT.
268
+ * populate the cache gets cacheStatus MISS; subsequent callers get HIT.
196
269
  */
197
270
  private resolveDataForBuildStep;
198
271
  /**
199
272
  * Loads data for a build step: bundled → one-time fetch.
200
273
  */
201
274
  private loadBuildData;
275
+ private fetchOnce;
276
+ /**
277
+ * Stores a last-resort fetch as a source response, which clears any failure,
278
+ * and serves it through the cache boundary.
279
+ */
280
+ private acceptLastResortFetch;
202
281
  /**
203
282
  * Shared fallback chain used by both initialize() and resolveData().
204
283
  */
@@ -209,24 +288,10 @@ declare class Controller implements ControllerInterface {
209
288
  */
210
289
  private noDefinitionsError;
211
290
  /**
212
- * Retrieves data using the fallback chain (called when no cached data exists).
213
- * Streaming mode: stream → datafile → bundled.
214
- * Polling mode: poll → datafile → bundled.
215
- * Offline mode: datafile → bundled → one-time fetch.
216
- */
217
- private resolveDataWithFallbacks;
218
- /**
219
- * Checks if the incoming data is newer than the current in-memory data.
220
- * Returns true if the update should proceed, false if it should be skipped.
221
- *
222
- * Always accepts the update if:
223
- * - There is no current data
224
- * - The current data has no configUpdatedAt
225
- * - The incoming data has no configUpdatedAt
226
- *
227
- * Skips the update only when both have configUpdatedAt and incoming is not newer.
291
+ * Retrieves data when the cache is empty in offline mode:
292
+ * datafile → bundled → one-time fetch.
228
293
  */
229
- private isNewerData;
294
+ private resolveStaticFallbackData;
230
295
  /**
231
296
  * Tracks a read operation for usage analytics.
232
297
  * During build steps, only the first read is tracked.
@@ -7,7 +7,7 @@ import {
7
7
  evaluate,
8
8
  flagsClient,
9
9
  resetDefaultFlagsClient
10
- } from "./chunk-TDZBUVBG.js";
10
+ } from "./chunk-D4522FGO.js";
11
11
  export {
12
12
  Controller,
13
13
  FallbackEntryNotFoundError,
@@ -1,7 +1,7 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true}); var _class;
2
2
 
3
3
 
4
- var _chunkPGKH4XQ7cjs = require('./chunk-PGKH4XQ7.cjs');
4
+ var _chunkITB5XQD3cjs = require('./chunk-ITB5XQD3.cjs');
5
5
 
6
6
  // src/openfeature.make.ts
7
7
 
@@ -150,7 +150,7 @@ function make(createClient2, defaultFlagsClient) {
150
150
  }
151
151
 
152
152
  // src/openfeature.default.ts
153
- var VercelProvider = make(_chunkPGKH4XQ7cjs.createClient, _chunkPGKH4XQ7cjs.flagsClient);
153
+ var VercelProvider = make(_chunkITB5XQD3cjs.createClient, _chunkITB5XQD3cjs.flagsClient);
154
154
 
155
155
 
156
156
  exports.VercelProvider = VercelProvider;
@@ -1,4 +1,4 @@
1
- import { F as FlagsClient } from './types-1OK52NNi.cjs';
1
+ import { F as FlagsClient } from './types-DpVlT3fI.cjs';
2
2
  import * as _openfeature_server_sdk from '@openfeature/server-sdk';
3
3
 
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { F as FlagsClient } from './types-1OK52NNi.js';
1
+ import { F as FlagsClient } from './types-DpVlT3fI.js';
2
2
  import * as _openfeature_server_sdk from '@openfeature/server-sdk';
3
3
 
4
4
  /**
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createClient,
3
3
  flagsClient
4
- } from "./chunk-SLNLPFKU.js";
4
+ } from "./chunk-SYXANNZ4.js";
5
5
 
6
6
  // src/openfeature.make.ts
7
7
  import {
@@ -1,7 +1,7 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true}); var _class;
2
2
 
3
3
 
4
- var _chunkW742GGZUcjs = require('./chunk-W742GGZU.cjs');
4
+ var _chunkW6776MKFcjs = require('./chunk-W6776MKF.cjs');
5
5
 
6
6
  // src/openfeature.make.ts
7
7
 
@@ -150,7 +150,7 @@ function make(createClient2, defaultFlagsClient) {
150
150
  }
151
151
 
152
152
  // src/openfeature.next-js.ts
153
- var VercelProvider = make(_chunkW742GGZUcjs.createClient, _chunkW742GGZUcjs.flagsClient);
153
+ var VercelProvider = make(_chunkW6776MKFcjs.createClient, _chunkW6776MKFcjs.flagsClient);
154
154
 
155
155
 
156
156
  exports.VercelProvider = VercelProvider;
@@ -1,4 +1,4 @@
1
- import { F as FlagsClient } from './types-1OK52NNi.cjs';
1
+ import { F as FlagsClient } from './types-DpVlT3fI.cjs';
2
2
  import * as _openfeature_server_sdk from '@openfeature/server-sdk';
3
3
 
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { F as FlagsClient } from './types-1OK52NNi.js';
1
+ import { F as FlagsClient } from './types-DpVlT3fI.js';
2
2
  import * as _openfeature_server_sdk from '@openfeature/server-sdk';
3
3
 
4
4
  /**
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createClient,
3
3
  flagsClient
4
- } from "./chunk-TDZBUVBG.js";
4
+ } from "./chunk-D4522FGO.js";
5
5
 
6
6
  // src/openfeature.make.ts
7
7
  import {
@@ -31,6 +31,12 @@ type DatafileInput = Packed.Data & {
31
31
  * Some older responses might return a string instead of a number. Both will be timestamps.
32
32
  */
33
33
  configUpdatedAt?: number | string;
34
+ /**
35
+ * When this datafile was successfully fetched, as Unix epoch milliseconds.
36
+ * Preserved when bundled, serialized, or supplied to another client.
37
+ * Omit when the original fetch time is unknown; loading data does not reset it.
38
+ */
39
+ fetchedAt?: number;
34
40
  /** Version number of the data */
35
41
  revision?: number;
36
42
  };
@@ -61,7 +67,7 @@ type Metrics = {
61
67
  /** Whether the stream is currently connected */
62
68
  connectionState: 'connected' | 'disconnected';
63
69
  /** The current operating mode of the client */
64
- mode: 'streaming' | 'polling' | 'build' | 'offline';
70
+ mode: 'streaming' | 'polling' | 'build' | 'vercel' | 'offline';
65
71
  /** Time in ms for the pure flag evaluation logic (only present on EvaluationResult) */
66
72
  evaluationMs?: number;
67
73
  };
@@ -31,6 +31,12 @@ type DatafileInput = Packed.Data & {
31
31
  * Some older responses might return a string instead of a number. Both will be timestamps.
32
32
  */
33
33
  configUpdatedAt?: number | string;
34
+ /**
35
+ * When this datafile was successfully fetched, as Unix epoch milliseconds.
36
+ * Preserved when bundled, serialized, or supplied to another client.
37
+ * Omit when the original fetch time is unknown; loading data does not reset it.
38
+ */
39
+ fetchedAt?: number;
34
40
  /** Version number of the data */
35
41
  revision?: number;
36
42
  };
@@ -61,7 +67,7 @@ type Metrics = {
61
67
  /** Whether the stream is currently connected */
62
68
  connectionState: 'connected' | 'disconnected';
63
69
  /** The current operating mode of the client */
64
- mode: 'streaming' | 'polling' | 'build' | 'offline';
70
+ mode: 'streaming' | 'polling' | 'build' | 'vercel' | 'offline';
65
71
  /** Time in ms for the pure flag evaluation logic (only present on EvaluationResult) */
66
72
  evaluationMs?: number;
67
73
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vercel/flags-core",
3
- "version": "1.9.0",
3
+ "version": "1.10.0-0037f35-20261006132750",
4
4
  "description": "A server-side client for Vercel Flags",
5
5
  "keywords": [
6
6
  "vercel",