API
@ogygia/flags
Feature flags and A/B tests, with flag(), decide() and the exposure stream.
On this page
- decide
- flag
- flush_exposures
- prime_flags
- BoolFlag
- BoolFlag.variants
- BoolFlag.pick
- BoolFlag.pick
- ComponentPick
- DecideOptions
- DecideOptions.source
- DecideOptions.overrides
- DecideOptions.exposure
- DecideOptions.batch
- ExposureEvent
- ExposureEvent.name
- ExposureEvent.variant
- ExposureEvent.sub
- ExposureEvent.at
- FlagOptions
- FlagOptions.layer
- FlagOptions.value
- FlagOptions.fallback
- FlagQuery
- FlagQuery.name
- FlagQuery.variants
- FlagQuery.kind
- VariantFlag
- VariantFlag.variants
- VariantFlag.pick
- VariantFlag.pick
- CtxLike
- FlagShape
- FlagSource
- Resolved
This page is generated from the package’s own .d.ts files on every build, so the signatures, descriptions and examples below always match the source.
import { … } from '@ogygia/flags';Exports decide · flag · flush_exposures · prime_flags · BoolFlag · ComponentPick · DecideOptions · ExposureEvent · FlagOptions · FlagQuery · VariantFlag · CtxLike · FlagShape · FlagSource · Resolved
decide
function
Configure flags for this server, once (hooks.server.ts / any server module). Everything is
optional — with no decide() at all, flags are native, sticky, and zero-JS.
decide({ source: openfeature(client), overrides: (c) => !!c.visitor?.qa, exposure: send });
function decide(opts: DecideOptions): void;flag
function
function flag<V extends string, Value = unknown>(name: string, shape: Record<V, number>, opts?: FlagOptions<Value>): VariantFlag<V, Value>;
function flag<Value = unknown>(name: string, shape?: number | ((c: CtxLike) => boolean | undefined), opts?: FlagOptions<Value>): BoolFlag<Value>;flush_exposures
function
Drain queued exposures to the sink. Called at each request's end (serverless-safe tail) and on the size/age caps. A sink failure is contained — a lost batch is never a request's problem.
function flush_exposures(): void;prime_flags
function
Resolve the source ONCE per request over every registered flag, caching decisions on the
Request so reads stay sync. Awaited by the flags plugin's request stage (flags/server.ts), above
the page / hole split. Idempotent; a no-op without a source. Never throws — a source failure
degrades every flag to its native rule, it does not take the request down.
function prime_flags(c: CtxLike): Promise<void>;BoolFlag
interface
A boolean flag — calling it answers "does this request get the feature?".
interface BoolFlag<Value = unknown> extends FlagBase<Value> {/*…*/}BoolFlag.variants
readonly variants: readonly ("off" | "on")[];BoolFlag.pick
Page slot (no c — the router supplies it): page(flag.pick({ … })).
Pick this visitor's entry from a per-variant map ('off' | 'on'), typed + total.
pick(map: Record<"off" | "on", unknown>): ComponentPick;BoolFlag.pick
Page slot (no c — the router supplies it): page(flag.pick({ … })).
Pick this visitor's entry from a per-variant map ('off' | 'on'), typed + total.
pick<T>(c: CtxLike, map: Record<"off" | "on", T>): T;ComponentPick
interface
A per-request component chooser for page() — page(hero.pick({ control: A, bold: B })).
Branded (not a bare function) because Svelte 5 components ARE functions.
interface ComponentPick {/*…*/}DecideOptions
interface
interface DecideOptions {/*…*/}DecideOptions.source
Where decisions come from (a vendor). Omit for pure-native flags (still sticky + zero-JS).
source?: FlagSource;DecideOptions.overrides
Honor ?og-exp=name:variant beyond dev — gate on something a visitor can't forge (a session
role, a signed QA cookie). Default: dev only.
overrides?: (c: CtxLike) => boolean;DecideOptions.exposure
Exposure sink — called with a BATCH of events (batching is built in: flushed at max events, after ms, or at each request's end). A throwing sink never breaks a request.
exposure?: (events: ExposureEvent[]) => void | Promise<void>;DecideOptions.batch
Exposure batch size / age caps. Defaults: max 25, ms 2000.
batch?: {
max?: number;
ms?: number;
};ExposureEvent
interface
interface ExposureEvent {/*…*/}ExposureEvent.name
name: string;ExposureEvent.variant
variant: string;ExposureEvent.sub
the sticky identity the decision keyed on (undefined = anonymous)
sub?: string;ExposureEvent.at
at: number;FlagOptions
interface
Rare knobs (third arg to flag).
interface FlagOptions<Value = unknown> {/*…*/}FlagOptions.layer
Mutual-exclusion group. Members sharing a layer string split visitors into equal LANES:
one sticky hash of (group, visitor) over the sorted member names picks each visitor's
OWNER, and every other member answers control before its own rule runs — two experiments
on one surface never share a visitor, so their measurements stay attributable. Real
exposure = a member's split ÷ member count. Override/carried/source outrank the gate.
layer?: string;FlagOptions.value
Standard Schema for a vendor-authored payload read via .value(c). A vendor value that
fails the schema is IGNORED (never enters) — the same trust-boundary law as body schemas.
value?: StandardSchemaV1<Value>;FlagOptions.fallback
Required alongside value: the payload when the source is silent or a value is invalid, so .value(c) never returns undefined.
fallback?: Value;FlagQuery
interface
What the source is asked to decide — the app's flags for this request.
interface FlagQuery {/*…*/}FlagQuery.name
name: string;FlagQuery.variants
variants: readonly string[];FlagQuery.kind
'boolean' for a kill switch / rollout / targeting flag, 'variant' for a weighted set.
kind: "boolean" | "variant";VariantFlag
interface
A weighted-variant flag — calling it answers which variant this request gets.
interface VariantFlag<V extends string, Value = unknown> extends FlagBase<Value> {/*…*/}VariantFlag.variants
readonly variants: readonly V[];VariantFlag.pick
Page slot (no c — the router supplies it): page(flag.pick({ … })).
Pick this visitor's entry from a per-variant map, typed + total.
pick(map: Record<V, unknown>): ComponentPick;VariantFlag.pick
Page slot (no c — the router supplies it): page(flag.pick({ … })).
Pick this visitor's entry from a per-variant map, typed + total.
pick<T>(c: CtxLike, map: Record<V, T>): T;CtxLike
type
type CtxLike = {
request: Request;
url: URL;
cookies?: {
get(name: string): string | undefined;
};
visitor?: (Record<string, unknown> & {
sub?: string;
}) | undefined;
};FlagShape
type
The shape a flag is declared with (second arg):
- omitted → a kill switch (off until decided on),
- a number → sticky rollout percent (0–100) of the feature,
- a function → per-request targeting (
true/false, orundefinedto fall through), - a record → weighted variants (ratios; the first key is control).
type FlagShape<V extends string> = undefined | number | ((c: CtxLike) => boolean | undefined) | Record<V, number>;FlagSource
type
A decision source — OpenFeature, OFREP, or any function. Given the app's flags + the request
context, return each flag's decision (missing keys fall through to the native rule). Resolved
ONCE per request (awaited by handle() / the router), so every read stays sync.
type FlagSource = (queries: FlagQuery[], c: CtxLike) => Record<string, Resolved> | Promise<Record<string, Resolved>>;Resolved
type
One decided flag from the SOURCE: a variant name (validated against the flag's declared
variants), optionally with a vendor-authored value (validated against the flag's value schema). A bare boolean/string is accepted too — sugar for { variant }.
type Resolved = {
variant: string;
value?: unknown;
} | string | boolean;