flags
Turn features on for some users and off for others, with the same answer on the server and in the browser.
Every export of kitr/flags, kitr/flags/react, kitr/flags/provider, kitr/flags/server, read from the published type definitions. The guide shows them in use.
kitr/flags
createFlags
function createFlags<D extends FlagDefinitions>(definitions: D, config?: FlagsConfig, app?: KitrApp): Flags<D>;
defineFlags
function defineFlags<const D extends FlagDefinitions>(definitions: D): D;
Declare every flag once, in app/flags.ts, with its default and a sentence on what it gates.
envFlagsProvider
function envFlagsProvider(env?: Record<string, string | undefined>, prefix?: string): FlagsProvider;
FLAG_NEW_DASHBOARD=true answers newDashboard.
openFeatureFlagsProvider
function openFeatureFlagsProvider(client: {
getBooleanValue(key: string, def: boolean, ctx?: any): boolean | Promise<boolean>;
getStringValue(key: string, def: string, ctx?: any): string | Promise<string>;
getNumberValue(key: string, def: number, ctx?: any): number | Promise<number>;
}, definitions: FlagDefinitions): FlagsProvider;
An OpenFeature client (@openfeature/web-sdk or server-sdk) as a kitr provider; the typed
defaults in app/flags.ts pick which get*Value to call.
percentageFlagsProvider
function percentageFlagsProvider(shares: Record<string, number>, salt?: string): FlagsProvider;
Turn a flag on for a share of users, stable per user id. percentageFlagsProvider({ beta: 10 }).
readEmbeddedFlags
function readEmbeddedFlags(): FlagMap | undefined;
Read the snapshot a server page embedded, if any.
staticFlagsProvider
function staticFlagsProvider(values: Partial<FlagMap> | ((context: FlagContext) => Partial<FlagMap>)): FlagsProvider;
A fixed map, or a function of the context: { newDashboard: true } or (ctx) => ....
useFlags
function useFlags<D extends FlagDefinitions = FlagDefinitions>(app: KitrApp): Flags<D>;
defineConfig
const defineConfig: <const C extends FlagsConfig>(config: C) => C;
FLAGS_SCRIPT_ID
const FLAGS_SCRIPT_ID = "__kitr_flags__";
FlagContext
interface FlagContext {
userId?: string;
[key: string]: unknown;
}
Who is asking: the user, their plan, the request. Providers target on it.
FlagDefinition
interface FlagDefinition<T extends FlagValue = FlagValue> {
/** The value when no provider answers. */
default: T;
description?: string;
}
Flags
interface Flags<D extends FlagDefinitions = FlagDefinitions> {
/** The current answer. Synchronous: works from a resolved snapshot (and the defaults before one). */
value<K extends keyof D & string>(key: K): D[K]['default'];
isEnabled(key: keyof D & string): boolean;
/** Every flag, resolved for `context`. Server: once per request. Client: once per session, then cached. */
resolve(context?: FlagContext): Promise<FlagValues<D>>;
/** Load a snapshot the server resolved (what `<FlagsScript>` carries), skipping any resolution. */
hydrate(values: Partial<FlagValues<D>>): void;
snapshot(): FlagValues<D>;
subscribe(listener: () => void): () => void;
readonly definitions: D;
readonly resolved: boolean;
}
FlagsConfig
interface FlagsConfig {
/** The flags from `app/flags.ts`. */
definitions?: FlagDefinitions;
/** First provider with an answer wins. */
providers?: FlagsProvider[];
/** Seconds a resolved snapshot stays fresh in the browser before `refresh()`. Default 300. */
ttl?: number;
}
FlagsProvider
interface FlagsProvider {
name: string;
init?(context: FlagContext): Promise<void> | void;
/** `undefined` means "no opinion": the next provider, then the default, answer. */
resolve(key: string, context: FlagContext): Promise<FlagValue | undefined> | FlagValue | undefined;
}
Where answers come from. Mirrors OpenFeature's provider shape, so an OpenFeature client is a
one-line adapter, but a plain object or envFlagsProvider() also work.
FlagDefinitions
type FlagDefinitions = Record<string, FlagDefinition>;
FlagMap
type FlagMap = Record<string, FlagValue>;
FlagValue
type FlagValue = boolean | string | number;
FlagValues
type FlagValues<D extends FlagDefinitions> = {
[K in keyof D]: D[K]['default'];
};
kitr/flags/react
FlagsScript
function FlagsScript({ values }: {
values: Record<string, unknown>;
}): import("react").DetailedReactHTMLElement<{
type: string;
id: string;
dangerouslySetInnerHTML: {
__html: string;
};
}, HTMLElement>;
Put in the document head of the root route with the values resolveFlags() returned in the loader.
useFlag
function useFlag<D extends FlagDefinitions, K extends keyof D & string>(key: K): D[K]['default'];
One flag's value; re-renders when a resolve or hydrate changes it.
useFlags
function useFlags<D extends FlagDefinitions = FlagDefinitions>(): FlagValues<D>;
Every flag, as a plain object.
kitr/flags/provider
flagsProvider
const flagsProvider: import("@kitr/core").KitrProvider;
Binds flags from config/flags.ts (definitions from app/flags.ts, providers). In the
browser a snapshot embedded by <FlagsScript> loads before the first render; otherwise
resolve() runs at boot. On the server, resolveFlags(app, event) answers per request.
kitr/flags/server
flagsScript
function flagsScript(values: Record<string, unknown>): string;
The server-resolved snapshot as a script tag the client provider reads before hydration.
resolveFlags
function resolveFlags<D extends FlagDefinitions>(app: KitrApp, event: H3Event | {
context: Record<string, any>;
}, context?: FlagContext): Promise<FlagValues<D>>;
Resolve every flag for this request (user from event.context.kitr.locals.user when auth ran),
cached on the event so loaders, controllers and the page share one answer.
Flags
interface Flags<D extends FlagDefinitions = FlagDefinitions> {
/** The current answer. Synchronous: works from a resolved snapshot (and the defaults before one). */
value<K extends keyof D & string>(key: K): D[K]['default'];
isEnabled(key: keyof D & string): boolean;
/** Every flag, resolved for `context`. Server: once per request. Client: once per session, then cached. */
resolve(context?: FlagContext): Promise<FlagValues<D>>;
/** Load a snapshot the server resolved (what `<FlagsScript>` carries), skipping any resolution. */
hydrate(values: Partial<FlagValues<D>>): void;
snapshot(): FlagValues<D>;
subscribe(listener: () => void): () => void;
readonly definitions: D;
readonly resolved: boolean;
}