@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.
- package/CHANGELOG.md +28 -0
- package/README.md +184 -0
- package/dist/{chunk-TDZBUVBG.js → chunk-D4522FGO.js} +1295 -367
- package/dist/chunk-D4522FGO.js.map +1 -0
- package/dist/{chunk-PGKH4XQ7.cjs → chunk-ITB5XQD3.cjs} +1335 -407
- package/dist/chunk-ITB5XQD3.cjs.map +1 -0
- package/dist/{chunk-SLNLPFKU.js → chunk-SYXANNZ4.js} +1295 -367
- package/dist/chunk-SYXANNZ4.js.map +1 -0
- package/dist/{chunk-W742GGZU.cjs → chunk-W6776MKF.cjs} +1336 -408
- package/dist/chunk-W6776MKF.cjs.map +1 -0
- package/dist/index.default.cjs +2 -2
- package/dist/index.default.d.cts +104 -39
- package/dist/index.default.d.ts +104 -39
- package/dist/index.default.js +1 -1
- package/dist/index.next-js.cjs +2 -2
- package/dist/index.next-js.d.cts +104 -39
- package/dist/index.next-js.d.ts +104 -39
- package/dist/index.next-js.js +1 -1
- package/dist/openfeature.default.cjs +2 -2
- package/dist/openfeature.default.d.cts +1 -1
- package/dist/openfeature.default.d.ts +1 -1
- package/dist/openfeature.default.js +1 -1
- package/dist/openfeature.next-js.cjs +2 -2
- package/dist/openfeature.next-js.d.cts +1 -1
- package/dist/openfeature.next-js.d.ts +1 -1
- package/dist/openfeature.next-js.js +1 -1
- package/dist/{types-1OK52NNi.d.cts → types-DpVlT3fI.d.cts} +7 -1
- package/dist/{types-1OK52NNi.d.ts → types-DpVlT3fI.d.ts} +7 -1
- package/package.json +1 -1
- package/dist/chunk-PGKH4XQ7.cjs.map +0 -1
- package/dist/chunk-SLNLPFKU.js.map +0 -1
- package/dist/chunk-TDZBUVBG.js.map +0 -1
- package/dist/chunk-W742GGZU.cjs.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# @vercel/flags-core
|
|
2
2
|
|
|
3
|
+
## 1.10.0-0037f35-20261006132750
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#511](https://github.com/vercel/flags/pull/511) [`ab6d65d`](https://github.com/vercel/flags/commit/ab6d65d5e4fcb0d0a563d982e23ccefd4f314113) Thanks [@luismeyer](https://github.com/luismeyer)! - Add a header-driven `vercel` client mode, enabled by default when `VERCEL=1`. Initialization loads provided/bundled definitions; request versions trigger refreshes and an empty cache fetches on its first read. Explicit offline/build behavior is preserved.
|
|
8
|
+
|
|
9
|
+
Evaluations require a valid positive version header for their own project. Missing, empty, malformed, or unrelated project entries permanently start streaming when enabled, otherwise polling; multiple clients select their sources independently. Concurrent reads share startup and pending HTTP refreshes. Accepted stream updates and valid confirmations cancel superseded refreshes; waiting reads use the confirmed cache, and late responses cannot change cache or authorization state. A cold shared fetch discovers project identity before accepting header evidence; a failed cold fetch rejects without switching sources and is retried by the next read. `getDatafile()` remains a snapshot that never starts streaming or polling: it serves cached definitions through the same header checks, stale-while-revalidate, blocking refresh, and stale-if-error, and loads bundled definitions or performs a one-time fetch when the cache is empty.
|
|
10
|
+
|
|
11
|
+
**Cached definitions now expire by age in streaming and polling mode.** Streaming data is fresh for 60 seconds after the last message; polling data is fresh for its interval plus the 10-second fetch deadline (40 seconds by default). Data then stays stale for `staleWhileRevalidate` seconds (default 10) and expires afterwards. Polling serves stale data while refreshing over HTTP in the background and makes expired reads wait for the shared fetch. While streaming, no HTTP request competes with the connection: stale reads serve the cache, and expired reads drop an overdue connection and wait up to ten seconds for the replacement stream to confirm or replace the cache. Data without a known age is served until its source first confirms it. Accepted updates, source responses, valid confirmations, and stream pings reset age without rewriting `fetchedAt`.
|
|
12
|
+
|
|
13
|
+
Streaming and polling never run at the same time. A stream startup timeout or disconnect keeps the stream reconnecting in the background while reads serve the cache; polling starts only once the stream gives up for good (retries exhausted, 401, or token failure), waiting for its first poll up to `polling.initTimeoutMs`. Ping timeouts reconnect the stream internally, allowing suspended runtimes to resume. Polling gives up on a 401 too. Whenever no live source is active, because the stream is reconnecting or the stream or polling gave up, reads apply plain stale-while-revalidate over HTTP: fresh within `staleWhileRevalidate`, served while refreshing in the background beyond that, and waiting for the refresh once older than `staleWhileRevalidate` plus `staleIfError`; data of unknown age refreshes in the background. Shutting down and reinitializing a client rewires its sources and starts with a clean cache and failure deadline.
|
|
14
|
+
|
|
15
|
+
Use `staleWhileRevalidate` (default 10) and `staleIfError` (default Infinity) in **seconds**, including fractions. Setting either to `0` disables its stale allowance. Datafiles preserve optional `fetchedAt` epoch-millisecond timestamps across serialization and bundled/provided reuse.
|
|
16
|
+
|
|
17
|
+
- [#540](https://github.com/vercel/flags/pull/540) [`1a38dee`](https://github.com/vercel/flags/commit/1a38dee43b41030496bd63290816e06e5d5890ae) Thanks [@vincent-derks](https://github.com/vincent-derks)! - Remove `projectId` from connection strings.
|
|
18
|
+
|
|
19
|
+
- [#510](https://github.com/vercel/flags/pull/510) [`10f19eb`](https://github.com/vercel/flags/commit/10f19ebc3896fe8f0d1ccc56755a59ee81173314) Thanks [@luismeyer](https://github.com/luismeyer)! - Add `staleIfError` in seconds to bound cached runtime reads after the first consecutive stream/poll failure or stream disconnect. The default `Infinity` preserves unlimited fallback; finite nonnegative durations (including fractional seconds) use existing evaluation defaults and errors after expiry, and `getDatafile()` follows the same allowance. Any successful source response resets the allowance, including one the version guard rejects as older or for a different project, as does a matching stream primed revision. Storing provided or bundled fallback data does not confirm freshness or renew the failure clock. Build/offline behavior and source scheduling remain unchanged.
|
|
20
|
+
|
|
21
|
+
### Patch Changes
|
|
22
|
+
|
|
23
|
+
- [#518](https://github.com/vercel/flags/pull/518) [`38f7f77`](https://github.com/vercel/flags/commit/38f7f77b8965a6dc613f2925ae00c137d8d20cd2) Thanks [@luismeyer](https://github.com/luismeyer)! - Add detailed client lifecycle, cache freshness, and network diagnostics using the `DEBUG=@vercel/flags-core` environment variable. `DEBUG` now follows the usual conventions for both the diagnostics and the ingest debug header: comma- or space-separated patterns, `*` wildcards, and `-` exclusions. A shared global logger emits events without passing logger instances through the client. Every client diagnostic includes the configured `clientName`, including cache, source, and background network activity. Logs omit credentials, definitions, and raw errors.
|
|
24
|
+
|
|
25
|
+
State changes include their cause; refresh logs distinguish response receipt, cache application, shared work, stream confirmation, and shutdown cancellation. Startup and source logs expose timeout and freshness thresholds, and recovery is logged only when a failure clears.
|
|
26
|
+
|
|
27
|
+
- [#511](https://github.com/vercel/flags/pull/511) [`e8f0c91`](https://github.com/vercel/flags/commit/e8f0c91ab834ef288c3b8e03faa50b882faca4a6) Thanks [@luismeyer](https://github.com/luismeyer)! - Record `fetchedAt` when a datafile fetch completes and preserve it in generated flag definitions. Loading the bundle retains the original timestamp so the Flags SDK can determine its age.
|
|
28
|
+
|
|
29
|
+
Expose optional `fetchedAt` metadata on datafiles. Record it for accepted live updates and preserve valid timestamps when loading provided or bundled definitions, without mutating the input.
|
|
30
|
+
|
|
3
31
|
## 1.9.0
|
|
4
32
|
|
|
5
33
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -33,6 +33,123 @@ export default app;
|
|
|
33
33
|
|
|
34
34
|
Outside Vercel, pass an SDK key explicitly: `createClient(process.env.FLAGS)`.
|
|
35
35
|
|
|
36
|
+
## Header-driven reads on Vercel
|
|
37
|
+
|
|
38
|
+
When `VERCEL=1`, the client defaults to `vercel: true`. Initialization loads provided
|
|
39
|
+
or bundled definitions without starting a stream or polling. Request version headers
|
|
40
|
+
indicate when cached definitions need refreshing. Header mode requires a valid positive
|
|
41
|
+
version for the client’s own `projectId`. An evaluation whose request carries a missing,
|
|
42
|
+
empty, malformed, or unrelated entry permanently switches that client to streaming when
|
|
43
|
+
enabled, otherwise polling. Clients also switch when cached definitions have no valid
|
|
44
|
+
positive config version to compare. Clients with different projects select their sources
|
|
45
|
+
independently within the same request. Concurrent reads share that startup and later
|
|
46
|
+
headers do not switch the client back. Pending HTTP refreshes remain shared until the
|
|
47
|
+
stream delivers current data or confirms the cached version. That confirmation cancels the
|
|
48
|
+
superseded refresh, and waiting reads use the confirmed cache; late responses cannot
|
|
49
|
+
change cache or authorization state. With an empty cache, the first read uses a shared
|
|
50
|
+
fetch to load definitions and discover the client’s project. The next evaluation assesses
|
|
51
|
+
that project’s header entry and starts the stream/poll fallback if it is unavailable.
|
|
52
|
+
If the cold fetch fails, the read rejects without switching sources, and the next read
|
|
53
|
+
retries it. Fetch failures use cached data only while stale-if-error permits it; source
|
|
54
|
+
assessment errors start fallback.
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
const client = createClient(process.env.FLAGS!, {
|
|
58
|
+
vercel: true,
|
|
59
|
+
staleWhileRevalidate: 10, // Seconds of background-refresh grace.
|
|
60
|
+
staleIfError: 60, // Seconds of cached fallback after a refresh failure.
|
|
61
|
+
});
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`staleWhileRevalidate` defaults to 10 seconds and accepts finite, nonnegative values,
|
|
65
|
+
including fractions. `0` makes header-driven refreshes block. The window starts at the latest
|
|
66
|
+
accepted fetch or valid confirmation, including an equal-version fetch response.
|
|
67
|
+
The cache tracks this age independently of `fetchedAt`. Bundled/provided definitions
|
|
68
|
+
preserve their original `fetchedAt`; unknown or expired cache age requires a blocking
|
|
69
|
+
refresh when a newer request version arrives. Refresh failures use `staleIfError`.
|
|
70
|
+
A newer-header read attempts blocking recovery after that failure allowance expires.
|
|
71
|
+
|
|
72
|
+
`getDatafile()` is a snapshot: it never starts streaming or polling. It serves cached
|
|
73
|
+
definitions through the same header checks, background revalidation, blocking refresh,
|
|
74
|
+
and stale-if-error as evaluations, shares pending HTTP refreshes with them, and loads
|
|
75
|
+
bundled definitions or performs a one-time fetch when the cache is empty. Only
|
|
76
|
+
evaluations switch a client to streaming or polling.
|
|
77
|
+
Use `vercel: false` to select the stream/poll behavior. Disabling both
|
|
78
|
+
stream and polling still selects offline mode, and builds retain their existing loading.
|
|
79
|
+
|
|
80
|
+
## Cached reads after errors
|
|
81
|
+
|
|
82
|
+
`staleIfError` controls how many seconds evaluations and `getDatafile()` may use
|
|
83
|
+
cached flag definitions after a stream/poll/header-refresh failure or stream disconnect:
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
const client = createClient(process.env.FLAGS!, {
|
|
87
|
+
staleIfError: 60,
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
The default is `Infinity`, preserving unlimited cached fallback. Use a finite
|
|
92
|
+
nonnegative number of seconds to bound fallback. Fractional seconds are supported
|
|
93
|
+
(for example, `0.5` allows 500 milliseconds). A positive window includes its exact
|
|
94
|
+
deadline; `0` disables cached fallback immediately after failure.
|
|
95
|
+
Negative values, `NaN`, and negative infinity throw when creating the client.
|
|
96
|
+
|
|
97
|
+
The allowance starts at the first consecutive failure. Repeated errors,
|
|
98
|
+
disconnects, and provided or bundled fallback data do not renew it. Any successful
|
|
99
|
+
source response clears the outage: a newer datafile replaces the cached one, while
|
|
100
|
+
an equal, older, or differently identified response proves the source is reachable
|
|
101
|
+
and leaves the stored definitions in place. A stream `primed` message clears it when
|
|
102
|
+
its finite numeric revision and identity match the cached entry. Pings clear failures
|
|
103
|
+
too: the server sends `primed` or a datafile before pings on each connection. Opening
|
|
104
|
+
a connection alone does not clear a failure. A later failure starts a new allowance.
|
|
105
|
+
Responses are observed in completion order, with existing version acceptance.
|
|
106
|
+
|
|
107
|
+
After expiry, `evaluate()` returns the caller's default with reason `error`, or
|
|
108
|
+
throws the first failure when no default is supplied. `bulkEvaluate()` returns
|
|
109
|
+
an error result for each requested flag, with its default value when provided.
|
|
110
|
+
`getDatafile()` follows the same allowance and throws after expiry. The entry is
|
|
111
|
+
retained for recovery, including its revision for stream reconnection. A clean
|
|
112
|
+
stream close records `stream: disconnected` if no earlier failure exists, and the
|
|
113
|
+
stream reconnects on its own with backoff while reads keep serving the cache. A
|
|
114
|
+
routine reconnect takes about a second, so keep `staleIfError` above the reconnect
|
|
115
|
+
delay; `0` or sub-second values fail reads during every reconnect. Ping timeouts
|
|
116
|
+
reconnect quietly without recording a failure, including after runtime suspension.
|
|
117
|
+
|
|
118
|
+
Streaming and polling never run at the same time. Polling starts only once the stream
|
|
119
|
+
has given up for good: its retry budget is exhausted, it receives a 401, or its token
|
|
120
|
+
cannot be resolved. A stream startup timeout keeps connecting in the background without
|
|
121
|
+
polling. Whenever polling starts, reads wait for the first poll or `polling.initTimeoutMs`;
|
|
122
|
+
on timeout, reads follow `staleIfError` while polling continues at the configured
|
|
123
|
+
interval. A zero initialization timeout waits for the poll, which still has a ten-second
|
|
124
|
+
fetch deadline. Polling gives up on a 401 as well. Whenever no live source is active,
|
|
125
|
+
because the stream is reconnecting or the stream or polling gave up, reads apply plain
|
|
126
|
+
stale-while-revalidate over HTTP: data refreshed within `staleWhileRevalidate` is served
|
|
127
|
+
as is, older data is served while a background refresh runs, and data older than
|
|
128
|
+
`staleWhileRevalidate` plus `staleIfError` waits for the refresh. Data without a known
|
|
129
|
+
age refreshes in the background. `getFallbackDatafile()` remains an independent
|
|
130
|
+
bundled-data export.
|
|
131
|
+
|
|
132
|
+
**Cached definitions now expire by age.** Streaming data is fresh for 60 seconds after
|
|
133
|
+
the last message, allowing one missed 30-second ping. Polling data is fresh for its
|
|
134
|
+
interval plus the 10-second fetch deadline (40 seconds with the default 30-second
|
|
135
|
+
interval). After that, data is stale for `staleWhileRevalidate` seconds (10 by default)
|
|
136
|
+
and then expired. Polling serves stale data while refreshing over HTTP in the background
|
|
137
|
+
and makes expired reads wait for the shared fetch. Streaming starts no HTTP work while
|
|
138
|
+
the connection is active: stale reads serve the cache, and expired reads drop a
|
|
139
|
+
connection that has been silent for over 60 seconds and wait up to ten seconds for the
|
|
140
|
+
replacement stream to confirm or replace the cache.
|
|
141
|
+
`staleWhileRevalidate: 0` makes reads block as soon as the fresh window ends. Data
|
|
142
|
+
without a known age, such as a provided datafile without `fetchedAt`, is served until the
|
|
143
|
+
active source first confirms it; without a live source it is revalidated like stale data.
|
|
144
|
+
Refresh failures still follow `staleIfError`.
|
|
145
|
+
|
|
146
|
+
Accepted updates, source responses, and valid confirmations reset cache age without
|
|
147
|
+
rewriting `fetchedAt`. Stream pings also reset age and clear any failure. Poll errors
|
|
148
|
+
feed the shared failure handler without logging each failed poll. An initialization
|
|
149
|
+
timeout alone does not start the failure allowance or reset cache age; it permits
|
|
150
|
+
cached fallback while the pending update continues. Shutting down and reinitializing
|
|
151
|
+
a client starts with a clean cache and failure deadline.
|
|
152
|
+
|
|
36
153
|
## Evaluation Metrics
|
|
37
154
|
|
|
38
155
|
To associate evaluation metrics with an environment, pass the
|
|
@@ -59,6 +176,73 @@ await OpenFeature.setProviderAndWait(new VercelProvider());
|
|
|
59
176
|
const client = OpenFeature.getClient();
|
|
60
177
|
```
|
|
61
178
|
|
|
179
|
+
## Client debug logging
|
|
180
|
+
|
|
181
|
+
Set `DEBUG=@vercel/flags-core` to enable detailed diagnostics:
|
|
182
|
+
|
|
183
|
+
```sh
|
|
184
|
+
DEBUG=@vercel/flags-core pnpm dev
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
`DEBUG` follows the usual conventions: patterns are separated by commas or spaces,
|
|
188
|
+
`*` is a wildcard (`DEBUG=*` or `DEBUG=@vercel/*` enable the logs), and a leading `-`
|
|
189
|
+
excludes a pattern (`DEBUG=*,-@vercel/flags-core` keeps them off). The same switch
|
|
190
|
+
also marks usage ingestion requests with an `x-vercel-debug-ingest: 1` header.
|
|
191
|
+
|
|
192
|
+
Client diagnostics use `console.debug` with the `@vercel/flags-core` prefix and
|
|
193
|
+
an object containing an `event`, the configured `clientName`, and its diagnostic
|
|
194
|
+
details. Set `clientName` when creating each client to distinguish their logs:
|
|
195
|
+
|
|
196
|
+
```ts
|
|
197
|
+
const client = createClient(undefined, { clientName: 'checkout' });
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
The controller, cache, and network sources all use the same global logging
|
|
201
|
+
function, including the client name on background work and reconnects. For example:
|
|
202
|
+
|
|
203
|
+
```text
|
|
204
|
+
@vercel/flags-core { event: 'client.state', clientName: 'checkout', from: 'idle', to: 'vercel', reason: 'header-mode-enabled', ... }
|
|
205
|
+
@vercel/flags-core { event: 'cache.freshness', clientName: 'checkout', status: 'expired', revision: 42, ageMs: 15000, ... }
|
|
206
|
+
@vercel/flags-core { event: 'cache.refresh.blocking', clientName: 'checkout', reason: 'expired', ... }
|
|
207
|
+
@vercel/flags-core { event: 'datafile.fetch.attempt', clientName: 'checkout', attempt: 1, maxAttempts: 3 }
|
|
208
|
+
@vercel/flags-core { event: 'datafile.fetch.response', clientName: 'checkout', attempt: 1, status: 200 }
|
|
209
|
+
@vercel/flags-core { event: 'cache.update.accepted', clientName: 'checkout', revision: 43, ... }
|
|
210
|
+
@vercel/flags-core { event: 'cache.fetch.applied', clientName: 'checkout', revision: 43, ... }
|
|
211
|
+
@vercel/flags-core { event: 'client.read', clientName: 'checkout', state: 'vercel', cacheStatus: 'MISS', ... }
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Follow `client.state` for the previous state, next state, and transition reason.
|
|
215
|
+
`state` describes the internal lifecycle; `mode` is the public operating mode.
|
|
216
|
+
Read and cache events include the project, revision, and cache age when available.
|
|
217
|
+
|
|
218
|
+
| Event | What it explains |
|
|
219
|
+
| --- | --- |
|
|
220
|
+
| `cache.freshness` | The source's age/version assessment and whether failure policy permits serving the entry. |
|
|
221
|
+
| `cache.source-error` / `header.fallback` | The source cannot assess this project or its cached version; the controller starts fallback without changing the fetch-failure deadline. |
|
|
222
|
+
| `cache.refresh.background` / `.blocking` | Why a read returns stale data immediately or waits for refresh. |
|
|
223
|
+
| `cache.fetch.shared` | A caller joins an existing HTTP refresh instead of starting another. |
|
|
224
|
+
| `datafile.fetch.complete` / `cache.fetch.applied` | The response has been parsed / processed by the cache version guard. Only the latter finishes cache refresh work; an older response may be ignored. |
|
|
225
|
+
| `cache.fetch.cancel` / `cache.refresh.superseded` | A stream update or confirmation supersedes HTTP work / releases a waiting read using the confirmed cache. |
|
|
226
|
+
| `cache.fetch.aborted` | `source-confirmed` distinguishes superseded work from `cache-cleared` during shutdown. |
|
|
227
|
+
| `datafile.fetch.aborted` | `timeout` is the fetch's own ten-second deadline; `external` is a cancellation by its caller. |
|
|
228
|
+
| `stream.exhausted` | The stream gave up for good (retries exhausted, 401, or token failure) and polling takes over when enabled. |
|
|
229
|
+
| `cache.stale-if-error.expired` / `cache.recovered` | The failure allowance prevents serving data / an actual failure has cleared. Ordinary confirmations do not log recovery. |
|
|
230
|
+
| `stream.initialize.timeout` / `poll.initialize.timeout` | Startup reached its deadline; `hasData` and `startupFallback` show whether cached fallback is available. |
|
|
231
|
+
|
|
232
|
+
Events cover initialization and shutdown, selected modes and state transitions,
|
|
233
|
+
evaluation/getDatafile results, cache versions and age, version acceptance/confirmation,
|
|
234
|
+
header timestamps, background/blocking/shared refreshes, stale-if-error expiry,
|
|
235
|
+
HTTP attempts and retry delays, polling, stream pings, timeouts, and reconnect reasons.
|
|
236
|
+
Stream/poll startup events include their stale and expired age thresholds.
|
|
237
|
+
Cache ages and delays are in milliseconds; `Infinity` denotes unknown age or an
|
|
238
|
+
unlimited stale-if-error window. The new client diagnostics omit credentials,
|
|
239
|
+
raw headers, flag definitions, evaluation entities, and raw error messages.
|
|
240
|
+
|
|
241
|
+
Logging is off by default. The logger checks `DEBUG` on each call; removing the
|
|
242
|
+
namespace disables client diagnostics, including for existing clients. Diagnostics
|
|
243
|
+
are verbose, including an event for each read, and event names/fields are internal
|
|
244
|
+
rather than a stable API. Configure your log collector to include `console.debug` output.
|
|
245
|
+
|
|
62
246
|
## Documentation
|
|
63
247
|
|
|
64
248
|
- [Core Library Docs](https://vercel.com/docs/flags/vercel-flags/sdks/core)
|