npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@vercel/flags-core

v1.10.1

Published

A server-side client for Vercel Flags

Readme

@vercel/flags-core

The core evaluation engine for Vercel Flags, the feature flag platform built into Vercel. This package provides direct access to the flag evaluation client, data fetching, and an OpenFeature provider.

For Next.js and SvelteKit applications, use the Flags SDK with @flags-sdk/vercel provider instead. Use @vercel/flags-core when you need lower-level control, are working with an unsupported framework, or want to use the OpenFeature standard.

Installation

npm i @vercel/flags-core

Usage

Create a shared client at module scope, but evaluate flags inside a request handler when using Vercel OIDC authentication. evaluate() and bulkEvaluate() initialize the client automatically on first use; you do not need to call initialize() first.

For example, in an Express app deployed to Vercel:

import express from 'express';
import { createClient } from '@vercel/flags-core';

const app = express();
const client = createClient(); // Uses Vercel OIDC; does not initialize yet.

app.get('/api/feature', async (_req, res) => {
  const result = await client.evaluate<boolean>('show-new-feature', false);
  res.json({ enabled: result.value });
});

export default app;

Outside Vercel, pass an SDK key explicitly: createClient(process.env.FLAGS).

Header-driven reads on Vercel

When VERCEL=1, the client defaults to vercel: true. Initialization loads provided or bundled definitions. At module scope it starts no network activity. Called inside a request, initialize() also prepares the cache for that request the way its first evaluation would: a matching version header confirms the cache, a newer version or an empty cache fetches, and a missing entry starts streaming or polling. It resolves once that work settles and rejects only when no definitions are available. Calling it again in later requests repeats the preparation without setting the client up twice. Request version headers indicate when cached definitions need refreshing. Header mode requires a valid positive version for the client’s own projectId. An evaluation whose request carries a missing, empty, malformed, or unrelated entry permanently switches that client to streaming when enabled, otherwise polling. Clients also switch when cached definitions have no valid positive config version to compare. Clients with different projects select their sources independently within the same request. Concurrent reads share that startup and later headers do not switch the client back. Pending HTTP refreshes remain shared until the stream delivers current data or confirms the cached version. That confirmation cancels the superseded refresh, and waiting reads use the confirmed cache; late responses cannot change cache or authorization state. With an empty cache, the first read uses a shared fetch to load definitions and discover the client’s project. The next evaluation assesses that project’s header entry and starts the stream/poll fallback if it is unavailable. If the cold fetch fails, the read rejects without switching sources, and the next read retries it. Fetch failures use cached data only while stale-if-error permits it; source assessment errors start fallback.

const client = createClient(process.env.FLAGS!, {
  vercel: true,
  staleWhileRevalidate: 10, // Seconds of background-refresh grace.
  staleIfError: 60, // Seconds of cached fallback after a refresh failure.
});

staleWhileRevalidate defaults to 10 seconds and accepts finite, nonnegative values, including fractions. 0 makes header-driven refreshes block. The window starts at the latest accepted fetch or valid confirmation, including an equal-version fetch response. The cache tracks this age independently of fetchedAt. Bundled/provided definitions preserve their original fetchedAt; unknown or expired cache age requires a blocking refresh when a newer request version arrives. Refresh failures use staleIfError. A newer-header read attempts blocking recovery after that failure allowance expires.

getDatafile() is a snapshot: it never starts streaming or polling. It serves cached definitions through the same header checks, background revalidation, blocking refresh, and stale-if-error as evaluations, shares pending HTTP refreshes with them, and loads bundled definitions or performs a one-time fetch when the cache is empty. Only evaluations switch a client to streaming or polling. Use vercel: false to select the stream/poll behavior. Disabling both stream and polling still selects offline mode, and builds retain their existing loading.

Cached reads after errors

staleIfError controls how many seconds evaluations and getDatafile() may use cached flag definitions after a stream/poll/header-refresh failure or stream disconnect:

const client = createClient(process.env.FLAGS!, {
  staleIfError: 60,
});

The default is Infinity, preserving unlimited cached fallback. Use a finite nonnegative number of seconds to bound fallback. Fractional seconds are supported (for example, 0.5 allows 500 milliseconds). A positive window includes its exact deadline; 0 disables cached fallback immediately after failure. Negative values, NaN, and negative infinity throw when creating the client.

The allowance starts at the first consecutive failure. Repeated errors, disconnects, and provided or bundled fallback data do not renew it. Any successful source response clears the outage: a newer datafile replaces the cached one, while an equal, older, or differently identified response proves the source is reachable and leaves the stored definitions in place. A stream primed message clears it when its finite numeric revision and identity match the cached entry. Pings clear failures too: the server sends primed or a datafile before pings on each connection. Opening a connection alone does not clear a failure. A later failure starts a new allowance. Responses are observed in completion order, with existing version acceptance.

After expiry, evaluate() returns the caller's default with reason error, or throws the first failure when no default is supplied. bulkEvaluate() returns an error result for each requested flag, with its default value when provided. getDatafile() follows the same allowance and throws after expiry. The entry is retained for recovery, including its revision for stream reconnection. A clean stream close records stream: disconnected if no earlier failure exists, and the stream reconnects on its own with backoff while reads keep serving the cache. A routine reconnect takes about a second, so keep staleIfError above the reconnect delay; 0 or sub-second values fail reads during every reconnect. Ping timeouts reconnect quietly without recording a failure, including after runtime suspension.

Streaming and polling never run at the same time. Polling starts only once the stream has given up for good: its retry budget is exhausted, it receives a 401, or its token cannot be resolved. A stream startup timeout keeps connecting in the background without polling. Whenever polling starts, reads wait for the first poll or polling.initTimeoutMs; on timeout, reads follow staleIfError while polling continues at the configured interval. A zero initialization timeout waits for the poll, which still has a ten-second fetch deadline. Polling gives up on a 401 as well. 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: data refreshed within staleWhileRevalidate is served as is, older data is served while a background refresh runs, and data older than staleWhileRevalidate plus staleIfError waits for the refresh. Data without a known age refreshes in the background. getFallbackDatafile() remains an independent bundled-data export.

Cached definitions now age. Streaming data is fresh for 60 seconds after the last message, allowing one missed 30-second ping. Polling data is fresh for its interval plus the 10-second fetch deadline (40 seconds with the default 30-second interval). After that, polling data is stale for staleWhileRevalidate seconds (10 by default) and then expired: stale reads refresh over HTTP in the background, and expired reads wait for the shared fetch. Streaming reads never wait and start no HTTP work while the stream is live: older data is served immediately and reported as stale. The stream recovers on its own. Its ping watchdog reconnects a connection that has been silent for 90 seconds, including right after a runtime resumes from a suspension, and the reconnect's first message confirms or replaces the cache. A stream that keeps failing gives up and hands over to polling or HTTP revalidation. staleWhileRevalidate: 0 makes polling reads block as soon as the fresh window ends. Data without a known age, such as a provided datafile without fetchedAt, is served until the active source first confirms it; without a live source it is revalidated like stale data. Refresh failures still follow staleIfError.

Accepted updates, source responses, and valid confirmations reset cache age without rewriting fetchedAt. Stream pings also reset age and clear any failure. Poll errors feed the shared failure handler without logging each failed poll. An initialization timeout alone does not start the failure allowance or reset cache age; it permits cached fallback while the pending update continues. Shutting down and reinitializing a client starts with a clean cache and failure deadline.

Evaluation Metrics

To associate evaluation metrics with an environment, pass the metricEnvironment option:

const client = createClient(process.env.FLAGS!, {
  metricEnvironment: 'preview',
});

This option is sent only to the metrics ingestion endpoint. It does not select the environment used for flag evaluation.

OpenFeature

An OpenFeature-compatible provider is available at @vercel/flags-core/openfeature:

import { OpenFeature } from '@openfeature/server-sdk';
import { VercelProvider } from '@vercel/flags-core/openfeature';

await OpenFeature.setProviderAndWait(new VercelProvider());
const client = OpenFeature.getClient();

Client debug logging

Set DEBUG=@vercel/flags-core to enable detailed diagnostics:

DEBUG=@vercel/flags-core pnpm dev

DEBUG follows the usual conventions: patterns are separated by commas or spaces, * is a wildcard (DEBUG=* or DEBUG=@vercel/* enable the logs), and a leading - excludes a pattern (DEBUG=*,-@vercel/flags-core keeps them off). The same switch also marks usage ingestion requests with an x-vercel-debug-ingest: 1 header.

Client diagnostics use console.debug with the @vercel/flags-core prefix and an object containing an event, the configured clientName, and its diagnostic details. Set clientName when creating each client to distinguish their logs:

const client = createClient(undefined, { clientName: 'checkout' });

The controller, cache, and network sources all use the same global logging function, including the client name on background work and reconnects. For example:

@vercel/flags-core { event: 'client.state', clientName: 'checkout', from: 'idle', to: 'vercel', reason: 'header-mode-enabled', ... }
@vercel/flags-core { event: 'cache.freshness', clientName: 'checkout', status: 'expired', revision: 42, ageMs: 15000, ... }
@vercel/flags-core { event: 'cache.refresh.blocking', clientName: 'checkout', reason: 'expired', ... }
@vercel/flags-core { event: 'datafile.fetch.attempt', clientName: 'checkout', attempt: 1, maxAttempts: 3 }
@vercel/flags-core { event: 'datafile.fetch.response', clientName: 'checkout', attempt: 1, status: 200 }
@vercel/flags-core { event: 'cache.update.accepted', clientName: 'checkout', revision: 43, ... }
@vercel/flags-core { event: 'cache.fetch.applied', clientName: 'checkout', revision: 43, ... }
@vercel/flags-core { event: 'client.read', clientName: 'checkout', state: 'vercel', cacheStatus: 'MISS', ... }

Follow client.state for the previous state, next state, and transition reason. state describes the internal lifecycle; mode is the public operating mode. Read and cache events include the project, revision, and cache age when available.

| Event | What it explains | | --- | --- | | cache.freshness | The source's age/version assessment and whether failure policy permits serving the entry. | | 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. | | cache.refresh.background / .blocking | Why a read returns stale data immediately or waits for refresh. | | cache.fetch.shared | A caller joins an existing HTTP refresh instead of starting another. | | 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. | | cache.fetch.cancel / cache.refresh.superseded | A stream update or confirmation supersedes HTTP work / releases a waiting read using the confirmed cache. | | cache.fetch.aborted | source-confirmed distinguishes superseded work from cache-cleared during shutdown. | | datafile.fetch.aborted | timeout is the fetch's own ten-second deadline; external is a cancellation by its caller. | | stream.exhausted | The stream gave up for good (retries exhausted, 401, or token failure) and polling takes over when enabled. | | cache.stale-if-error.expired / cache.recovered | The failure allowance prevents serving data / an actual failure has cleared. Ordinary confirmations do not log recovery. | | stream.initialize.timeout / poll.initialize.timeout | Startup reached its deadline; hasData and startupFallback show whether cached fallback is available. |

Events cover initialization and shutdown, selected modes and state transitions, evaluation/getDatafile results, cache versions and age, version acceptance/confirmation, header timestamps, background/blocking/shared refreshes, stale-if-error expiry, HTTP attempts and retry delays, polling, stream pings, timeouts, and reconnect reasons. Stream/poll startup events include their stale and expired age thresholds. Cache ages and delays are in milliseconds; Infinity denotes unknown age or an unlimited stale-if-error window. The new client diagnostics omit credentials, raw headers, flag definitions, evaluation entities, and raw error messages.

Logging is off by default. The logger checks DEBUG on each call; removing the namespace disables client diagnostics, including for existing clients. Diagnostics are verbose, including an event for each read, and event names/fields are internal rather than a stable API. Configure your log collector to include console.debug output.

Documentation