Lifts the per-request evaluation pipeline into src/shared/ and points both
frameworks at it, so behavior is defined once. The Next-specific
isInternalNextError check becomes an injected `isFrameworkError` hook on
applyResult (Next passes it; SvelteKit defaults to none).
- shared/evaluation.ts: evaluationCache (+ getUsedFlags accessor), getEntities
+ identify-args dedupe, and applyResult (cache -> override -> produce ->
defaultValue/error -> report).
- shared/flag-meta.ts: resolveAdapter, getDecide (with adapter validation),
getIdentify, getOrigin.
SvelteKit's flag() now runs through this pipeline. This is a behavior change
that brings it to parity with Next — SvelteKit now:
- falls back to defaultValue when decide throws or returns undefined
- honors config.reportValue / adapter.config.reportValue
- resolves adapter.origin (value or (key) => origin)
- dedupes evaluation via the shared headers-keyed cache (replacing the
per-store usedFlags/identifiers maps); createHandle's transformPageChunk
reads used flags via getUsedFlags()
- getProviderData emits defaultValue + declaredInCode
Adds `defaultValue?` to the SvelteKit Flag type (additive) and 9 parity tests.
Next's public .d.ts is unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Both flags/next and flags/sveltekit implemented the same flag pipeline
twice and had drifted. This lifts the parts that are framework-agnostic
(or trivially so) into src/shared/, with each framework keeping thin
wrappers for its differences. Zero public API change — the generated
.d.ts for flags, flags/next and flags/sveltekit are byte-identical.
- shared/seal.ts: sealHeaders/sealCookies/transformToHeaders
(was duplicated in next/evaluate.ts and sveltekit/index.ts)
- shared/overrides.ts: readOverrides + memoized decrypt; SvelteKit now
gets the override memoization Next already had. Removes next/overrides.ts.
- shared/precompute.ts: combine/serialize/deserialize/generatePermutations
+ getPrecomputed core. Frameworks keep wrappers for secret defaulting
and the getPrecomputed(flag) vs getPrecomputed(key) signatures.
- shared/discovery.ts: shared authorize -> 401-or-data -> version-header
control flow behind createFlagsDiscoveryEndpoint.
Named "shared" rather than "core" to avoid confusion with the separate
@vercel/flags-core package.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Migrate docs app to Geistdocs canary
* Fix docs proxy matcher
* Use Geistdocs UI exports
* Update Geistdocs to 1.3.0
* upgrade geistdocs and fix svelte logo
* add copy prompt to certain docs pages
* migrate to 1.5.0
* refactor(docs): use geistdocs v1.5 Badge, CommandPrompt, ThemeAwareImage
Remove the local copies now exported by @vercel/geistdocs@1.5 and import
them from the package instead:
- Badge -> @vercel/geistdocs/components/badge (provider-list)
- CommandPrompt -> @vercel/geistdocs/components/command-prompt (install-command)
- ThemeAwareImage -> @vercel/geistdocs/components/theme-aware-image
(docs MDX components); migrate the SvelteKit MDX call site to the new
src={{ light, dark }} API.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(docs): restore install switcher width animation
The hero "For humans / For agents" switcher called router.refresh() on
every toggle (added in #366). That remounts/reconciles the switcher
subtree mid-toggle, resetting the CommandPrompt's measured width state so
the command-line width spring never plays — the line snapped instead of
growing.
Drop the refresh: this flag's only consumer is the switcher itself, so the
optimistic override already shows the correct command and the cookie
persists the choice across reloads (the server reads it and renders the
matching prebuilt `[code]`). The refresh changed nothing visible and only
broke the animation. startTransition() does not help, since router.refresh
already runs as a transition.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(docs): render home code blocks with geistdocs CodeBlock
Replace the hand-rolled shiki token renderer in HighlightedCode with
fumadocs highlight() + the geistdocs CodeBlock, highlighting with the
shared geistShikiTheme so the home page "Effortless setup" blocks match
the documentation (same syntax colors, header, language icon, copy
button, radius). transformerIcon() fills the filename icon slot; the
caption is preserved below.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(docs): equal-height home code blocks
Grow each HighlightedCode block to fill its grid cell (h-full root +
flex-1 wrapper forcing the Card to h-full) so both columns share the
taller block's height and their captions align. The Card and its <pre>
share bg-background-100, so the shorter block fills seamlessly.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(docs): drop dead shadcn tokens (chart-*, sidebar-primary)
Only chart-1..5 and sidebar-primary are unused by both the app and the
geistdocs/fumadocs package CSS. The other shadcn tokens (secondary,
card-foreground, the remaining sidebar-* set, etc.) must stay: the package
stylesheet consumes them via var(--token) for its fd-* utility mappings
and the #nd-sidebar rule, and does not self-define them — the consuming
app has to provide them.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(docs): migrate consumers off shadcn tokens to Geist scale
Switch every shadcn-token consumer (select, switch, iframe-browser, the
home marketing pages, and the illustrations SVGs) to the Geist --ds-*
scale using the geistdocs PR #75 mapping (foreground->gray-1000,
muted->gray-100, muted-foreground->gray-800, border/input->gray-alpha-400,
ring->gray-600, destructive->red-800, background/card/popover->
background-100, accent->gray-100, etc.).
With no consumers left, drop the entire local shadcn layer from
geistdocs.css (the @theme --color-* mappings and the :root/.dark oklch
palette) and instead override fumadocs --color-fd-* and #nd-sidebar to
--ds-* (so the package's docs UI keeps working without the raw palette),
matching the geistdocs template. The geist utilities themselves come from
@vercel/geistdocs/styles.css.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Update geistdocs.css
* migrate to 1.6.0
* chore(docs): bump @vercel/geistdocs to 1.6.1
Picks up the Next.js logo gradient-id fix so the framework logo renders
correctly in the mobile docs menu.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Update pnpm-lock.yaml
* fix(docs): drop language icons from landing-page code blocks
The home "Effortless setup" snippets are Next.js examples, but the
language-based transformerIcon rendered a React glyph for the .tsx block.
Remove the icons (these blocks never had them) and hide the now-empty
header icon slot so the filename stays flush.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(docs): drop redundant streamdown @source
The streamdown @source pointed at a transitive dep path that doesn't
resolve under pnpm (matched nothing). Streamdown is now sourced upstream
from @vercel/geistdocs's own styles.css, so this line is unneeded.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* update
* bump @vercel/geistdocs
* Update pnpm-lock.yaml
* update
---------
Co-authored-by: christopherkindl <53372002+christopherkindl@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Allow passing an adapter factory directly to flag()
flag() now accepts the adapter factory by reference (`adapter: vercelAdapter`)
as a shorthand for calling it (`adapter: vercelAdapter()`). The factory is
resolved once per declaration; passing an instance keeps working. Applies to
both the Next.js and SvelteKit entrypoints.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* [@flags-sdk/vercel] reuse adapter instance
* reword changeset
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* use __no_flags__ to handle precompute with empty flags
* handle __no_flags__ in serialize
* add __no_flags__ handling to sveltekit
* avoid returning undefined when defaultValue is set
This could only happen on precomputed flags in case of bad usage.
* add error messages
* test console logs
* update changeset
* prepare
* origin
* prepare script
* step
* redo peer deps
* wip
* continue
* every 5 secs
* connect flag network
* add shirt-shop-vercel
* upgrade
* push
* update endpoint
* add vercel getProviderData
* fix importts
* Add retries to flag network datasource (#237)
* Fix React Server Components CVE vulnerabilities (#235)
Updated dependencies to fix Next.js and React CVE vulnerabilities.
The fix-react2shell-next tool automatically updated the following packages to their secure versions:
- next
- react-server-dom-webpack
- react-server-dom-parcel
- react-server-dom-turbopack
All package.json files have been scanned and vulnerable versions have been patched to the correct fixed versions based on the official React advisory.
Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
* Add retries to flag network datasource when stream closes
Implements automatic retry logic with exponential backoff when the flag network
stream unexpectedly closes. The stream will retry with delays increasing from
1s to a maximum of 30s, allowing the datasource to recover from temporary
network issues while falling back to bundled definitions if initial connection
fails.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
* Revert package.json and pnpm-lock changes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com>
Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
* use ndjson (#238)
* try
* try
* use debugLog
* replace testkey
* never retry on 4xx
* Track FLAG_CONFIG_READ events
* update @biomejs/biome
* send user-agent, rm terminate handling, fix tests
* extract usage-tracker and add tests
* rm EdgeConfigDataSource
* export EvaluationParams and EvaluationResult
* drop edge-config dep
* drop edge config from @flags-sdk/vercel
* adapt @flags-sdk/vercel
* fix integration tests
* wip
* assert ingest requests
* race initial connection, retry in background, abort on shutdown
* rm store
* make readBundledDefinitions async
* read from @vercel/flags-definitions/definitions.json
* add ensureFallback fn
* getDefaultFlagsClient() → flagsClient
* rm process.pid
* rm webpackIgnore: true
* require fallbacks at build time on vercel only
* test in layout
* vercel-flags prepare --verbose
* rm prebuild script
* ensure fallback from instrumentation.ts
* rm last process.pid
* print warning
* Track duration and cache status for config reads
* add tests
* await stream in initialize method
* consumeStream → runStreamLoop
* replace subscribe with doInitialize
* add webpackIgnore
* make @vercel/flags-core a peer dep
* fresh start
* debug
* step
* next-connection
* avoid opening stream during builds
* undo next specific exports
* handle abort
* fake
* poc
* clean up imports
* next-js exports
* upgrade next
* move "use cache" up
* move "use cache" handling to client
* split index.default.ts and index.next-js.ts
* separate
* jsdoc
* use scoped map instead of passing ref
* move jsdoc
* rename
* keep dataSource private, clean clientMap
* reset retryCount on connection; abort on 401
* break after 10 retries
* add backoff with jitter + limit
* add usage tracking and read fallback
* refactor
* extract stream-connection, update tests
* add tests
* reduce test amount
* add perf metadata
* add BaseEvaluationResult
* getData → read
* rm shirt-shop-vercel
* drop next-connection
* update pnpm-lock
* getMetadata → getInfo
* add getDatafile
* DataSourceData → Datafile
* merge metrics into datafile
* simplify metrics
* use embed for getDatafile
* add comment
* comment
* bring shirt-shop-vercel back
* try setCacheLife
* support cache components in OpenFeature import
* use new format
* rm cli
* fix origin
* use bundler
* simplify
* ensure warning is highlighted in build logs
* warn only once
* ensureFallback → getFallbackDatafile
* improve perf
* avoid creating new objects
* fix type issues
* stop exposing createRawClient
* fix: mark stream reconnection loop as intentional fire-and-forget
Add void operator before async IIFE to explicitly indicate the floating
promise is intentional, preventing linter warnings and making the code
intent clearer to future maintainers.
* fix: handle malformed JSON in stream messages gracefully
Wrap JSON.parse in try/catch to prevent crashes from malformed server
responses. Logs a warning and skips the invalid message instead of
crashing the stream connection handler.
* fix: add bounds checking for variant index access
Add getVariant() helper that throws a descriptive error if the variant
index is out of bounds. This prevents silent undefined returns when
variant indices are invalid, making issues easier to debug in production.
* fix: prevent concurrent initialization race condition
Add initializingPromise to coordinate concurrent initialize() calls.
The fast boolean check is preserved for the hot path, while concurrent
calls during initialization now await the same promise instead of
triggering multiple initializations.
* fix: add timeout and retry logic to fetchDatafile
- Add 10-second timeout using AbortController to prevent indefinite hangs
- Add exponential backoff retry (up to 3 attempts) for transient failures
- Skip retries for 4xx client errors (except 429 rate limiting)
- Improves resilience against network issues in production
* fix: prevent race condition in ensureStream
Use local variable for abortController and store streamPromise
immediately after creation. This prevents concurrent calls from
creating multiple streams or overwriting the abort controller
before the promise is stored.
* fix: prevent race condition in read() by capturing data reference
Capture this.data reference at the start of read() to ensure consistent
state throughout the method. The onMessage callback from the stream can
update this.data during async operations, which could cause inconsistent
behavior if the reference changes mid-operation.
* comments
* add streaming connectionState
* update usage
* fix init
* try to avoid hanging promise
* update attw, add types
* get rid of unnecessary async/await
* avoid printing the warning
* distinct warnings
* don't export cachedFns
* add CLAUDE.md
* jsdoc
* clear timeout
* export Datafile
* avoid exposing data sources
* simplify
* types
* avoid dangling timeout
* more options
* tests
* allow options in createClient
* DatafileInput
* better options
* Update event tracking to use streams
* Revert "Update event tracking to use streams"
This reverts commit cff740a18c.
* fix datafile input type
* type
* handle shutdown and re-init
* defensive
* retry stream for up to 15 minutes
* Update event reporting to use ndjson for more events
* Revert "Update event reporting to use ndjson for more events"
This reverts commit f70a7bfc59.
* Add debug logs to the usage tracker
* [adapter-vercel] fix getProviderData
* [adapter-vercel] only return vercel flags from getProviderData
* fix
* rm getInfo
* rm tgz
* rm example
* rm next/connection from gitignore
* rm unused deps
* use DatafileInput type
* fix tests
* ensure pollCount > 0
* fix type issues
* fix retry logic
* upgrade @sveltejs/kit
* resolve ci issues
---------
Co-authored-by: Luis Meyer <luis.meyer@vercel.com>
Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com>
Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
Co-authored-by: Andy Bitz <artzbitz@gmail.com>
Updated dependencies to fix Next.js and React CVE vulnerabilities.
The fix-react2shell-next tool automatically updated the following packages to their secure versions:
- next
- react-server-dom-webpack
- react-server-dom-parcel
- react-server-dom-turbopack
All package.json files have been scanned and vulnerable versions have been patched to the correct fixed versions based on the official React advisory.
Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
* use `$env/dynamic/private` from SvelteKit for convenience: people don't have to pass FLAGS_SECRET manually anymore then
* allow flag to be called outside of the lifecycle of the handle hook, with a request object being the key for deduplication etc
* add support for identifiers
* fix types
* new function for managing precomputed flags
* align with next.js API instead
* update example app
* lockfile
* test
* fix example
* restructure examples app
* make it two flags to show power of precompute + code
* add manual approach
* lets see if preview deployment is picked up
* change crypto usage to be usable in middleware
* make sveltekit flags pkg usable within edge middleware
* use ISR
* changeset
* enhance error message
* use static env instead of dynamic env
* bump kit
* use vercel adapter
* make sure Vite tooling is used for sveltekit entry point
---------
Co-authored-by: Dominik Ferber <dominik.ferber@gmail.com>
* Create LaunchDarkly adapter
* Update example to include a LaunchDarkly flag
* Add missing env to turbo
* Rename to defaultLaunchDarklyAdapter
* Add winter sale example for providers
* Update winter-sale
* Document usage in readme
* Allow to set the default value through the adapter
* Fix readme
* Add default value for adapter
* Make argument optional
* Add @ts-expect-error
* Add a LaunchDarkly adapter (#10)
* add ld example
* reword
* remove winter-sale example
* remove defaultValue
* use @flags-sdk/launchdarkly
* add install steps
* add changeset
* move @vercel/edge-config to peer deps
* add more secrets
* spell out name
* remove unused env var
* rename properties
* Capitalize
* Change import
* Fix more imports
* expose ldClient
* move peerDeps to real deps
* clarify Edge Config requirement in README
* remove adapter-launchdarkly snippet
* upgrade next@canary
* polish @flags-sdk/launchdarkly README
---------
Co-authored-by: Dominik Ferber <dominik.ferber@gmail.com>