Core API Reference
Hand-written reference for everything @useoptimus/core exports today
(packages/core/src/index.ts). Generating this from TSDoc via TypeDoc is
planned — see the docs-site README — but not wired up yet.
FlagDefinition<T>
Section titled “FlagDefinition<T>”interface FlagDefinition<T = boolean> { key: string; kind: FlagKind; valueType: "boolean" | "variant" | "value"; defaultValue: T; variants?: FlagVariant<T>[]; failureMode: FailureMode; sticky: boolean; emitsExposure: boolean; dependsOn?: string[]; schedule?: { startAt?: string; endAt?: string }; description?: string; owners?: string[];}The code-defined schema half of a flag. See Flag Taxonomy for what each field does.
FlagKind
Section titled “FlagKind”type FlagKind = | "release" | "killSwitch" | "experiment" | "migration" | "progressiveDeploy" | "entitlement" | "circuitBreaker" | "dynamicConfig" | "custom";FailureMode
Section titled “FailureMode”type FailureMode = "closed" | "open" | "lastKnown";FlagVariant<T>
Section titled “FlagVariant<T>”interface FlagVariant<T> { key: string; value: T; weight: number;}FlagRemoteState
Section titled “FlagRemoteState”interface FlagRemoteState { key: string; enabled?: boolean; rolloutPercentage?: number; bucketingSeed?: string; targetingRules?: TargetingRule[]; variantOverrides?: { key: string; weight: number }[]; valueOverride?: unknown; updatedAt: string;}The live/remote half of a flag. Any field left unset falls back to the
FlagDefinition’s code-defined default — this fallback is the kill-switch
fail-safe path.
EvaluationContext
Section titled “EvaluationContext”interface EvaluationContext { bucketingKey?: string; userId?: string; deviceId?: string; sessionId?: string; anonymousId?: string; attributes?: Record<string, string | number | boolean>; environment?: string;}See Bucketing & Salting for how bucketingKey
is resolved. Identity aliasing (anonymous → identified re-bucketing) is out
of scope for v1 — if the resolved key changes between calls for what is
conceptually “the same user” (e.g. pre- and post-login), bucket assignment
may change.
EvaluatedFlag<T>
Section titled “EvaluatedFlag<T>”interface EvaluatedFlag<T = boolean> { key: string; value: T; variantKey?: string; reason: EvaluationReason; ruleMatched?: string; stale: boolean;}EvaluationReason
Section titled “EvaluationReason”type EvaluationReason = | "default" | "targetingMatch" | "rollout" | "override" | "fallbackError" | "dependencyNotMet";fallbackError is never produced by the pure evaluate() function itself
— it’s returned by FlagsClient.evaluate()/evaluateAll() when a
'closed'-failureMode flag falls back after a provider fetch failure. See
FlagsClient below and
Flag Taxonomy.
TargetingRule
Section titled “TargetingRule”type TargetingRule = | { type: "attributeEquals"; attribute: string; value: string | number | boolean } | { type: "attributeIn"; attribute: string; values: (string | number)[] } | { type: "percentageRollout"; percentage: number; bucketingSeed?: string } | { type: "semverRange"; attribute: string; range: string } | { type: "dateRange"; startAt?: string; endAt?: string } | { type: "and"; rules: TargetingRule[] } | { type: "or"; rules: TargetingRule[] };FlagProvider
Section titled “FlagProvider”interface FlagProvider { name: string; init(): Promise<void>; getRemoteState(keys?: string[]): Promise<FlagRemoteState[]>; subscribe?(onUpdate: (state: FlagRemoteState[]) => void): () => void;}subscribe is optional — implement it for push-based providers (SSE/WS);
polling providers can omit it.
ResolvedFlagConfig<T>
Section titled “ResolvedFlagConfig<T>”interface ResolvedFlagConfig<T> { key: string; defaultValue: T; failureMode: FailureMode; sticky: boolean; emitsExposure: boolean; dependsOn?: string[]; schedule?: { startAt?: string; endAt?: string }; enabled?: boolean; rolloutPercentage?: number; bucketingSeed?: string; targetingRules: TargetingRule[]; variants?: FlagVariant<T>[]; valueOverride?: unknown;}The result of merging a FlagDefinition with its FlagRemoteState — the
return type of mergeDefinitionWithRemoteState.
Functions
Section titled “Functions”evaluate(definition, remoteState, context, dependencies?)
Section titled “evaluate(definition, remoteState, context, dependencies?)”function evaluate<T>( definition: FlagDefinition<T>, remoteState: FlagRemoteState | undefined, context: EvaluationContext, dependencies?: Record<string, EvaluatedFlag>,): EvaluatedFlag<T>Pure and deterministic: identical inputs always produce an identical
EvaluatedFlag, which is what makes SSR snapshot/hydrate parity possible.
dependencies supports only the dependsOn trait’s simple truthy-parent
check — a parent flag resolving to a specific variant is not supported.
resolveBucketingKey(context)
Section titled “resolveBucketingKey(context)”function resolveBucketingKey(context: EvaluationContext): string | undefinedApplies the default identity resolution chain. See Bucketing & Salting.
computeBucket(bucketingKey, flagKey, seed?)
Section titled “computeBucket(bucketingKey, flagKey, seed?)”function computeBucket(bucketingKey: string, flagKey: string, seed?: string): numberReturns a bucket value in [0, 10000).
isInRollout(bucketValue, percentage)
Section titled “isInRollout(bucketValue, percentage)”function isInRollout(bucketValue: number, percentage: number): booleanpercentage is 0–100.
matchRule(rule, context, flagKey)
Section titled “matchRule(rule, context, flagKey)”function matchRule(rule: TargetingRule, context: EvaluationContext, flagKey: string): booleanEvaluates a single TargetingRule against a context.
evaluateRules(rules, context, flagKey)
Section titled “evaluateRules(rules, context, flagKey)”function evaluateRules( rules: TargetingRule[], context: EvaluationContext, flagKey: string,): { matched: boolean; rule?: TargetingRule }First-match-wins over the rule list.
mergeDefinitionWithRemoteState(definition, remoteState?)
Section titled “mergeDefinitionWithRemoteState(definition, remoteState?)”function mergeDefinitionWithRemoteState<T>( definition: FlagDefinition<T>, remoteState?: FlagRemoteState,): ResolvedFlagConfig<T>Field-by-field merge of code schema and remote state. Used internally by
evaluate(); exported for testing merge behavior in isolation.
fnv1a(input)
Section titled “fnv1a(input)”function fnv1a(input: string): numberFNV-1a 32-bit hash. Not cryptographic — used purely for bucketing
distribution. computeBucket runs this through an additional avalanche step
before reducing it; see Bucketing & Salting for
why.
Classes
Section titled “Classes”LocalProvider
Section titled “LocalProvider”class LocalProvider implements FlagProvider { name: "local"; constructor(state: FlagRemoteState[]); init(): Promise<void>; getRemoteState(keys?: string[]): Promise<FlagRemoteState[]>;}Static/in-memory provider, no network calls. Useful for tests and local
development. Does not implement subscribe.
HttpPollingProvider
Section titled “HttpPollingProvider”interface HttpPollingProviderOptions { url: string; intervalMs?: number; // steady-state poll interval, default 30_000 fetchImpl?: FetchLike; headers?: Record<string, string>; now?: () => number; // test seam random?: () => number; // jitter test seam, default Math.random}
class HttpPollingProvider implements FlagProvider { name: "http-polling"; constructor(options: HttpPollingProviderOptions);}Polls url on intervalMs, backing off on failure with Equal Jitter
exponential backoff (50%–100% of the doubling delay, capped at 5 minutes) so
a retry never degenerates to a near-immediate hammering on an unlucky
jitter roll.
SseProvider
Section titled “SseProvider”interface SseProviderOptions { url: string; eventSourceFactory?: () => EventSourceLike;}
class SseProvider implements FlagProvider { name: "sse"; constructor(options: SseProviderOptions);}getRemoteState() returns the last-known pushed state ([] before the
first message) rather than performing a network fetch. No self-implemented
reconnect — both the browser’s native EventSource and the eventsource
npm package already auto-reconnect per the SSE spec.
FlagsClient
Section titled “FlagsClient”Orchestration layer around the pure evaluate() engine: owns a flag
registry, a FlagProvider, cached remote state, dependsOn resolution
across multiple flags, failureMode semantics, and exposure events. Use
this instead of calling evaluate() directly for anything beyond a single
one-off evaluation.
interface FlagsClientOptions { definitions: FlagDefinition<unknown>[]; // immutable for the client's lifetime provider: FlagProvider; context?: EvaluationContext; cache?: FlagStateCache; // defaults to an in-memory Map; see TtlFlagStateCache below onEvaluate?: OnEvaluateHandler; now?: () => number; // clock seam for tests, default Date.now}
class FlagsClient { constructor(options: FlagsClientOptions); init(): Promise<void>; refresh(keys?: string[]): Promise<RefreshResult>; setContext(context: EvaluationContext): void; getContext(): EvaluationContext; evaluate<T = boolean>(key: string, context?: EvaluationContext): EvaluatedFlag<T>; evaluateAll(context?: EvaluationContext): Record<string, EvaluatedFlag>; onEvaluate(handler: OnEvaluateHandler): Unsubscribe; subscribe(listener: ClientUpdateListener): Unsubscribe; setOverrides(overrides: Record<string, FlagOverride>): void; clearOverrides(keys?: string[]): void; dispose(): void;}Supporting types
Section titled “Supporting types”type Unsubscribe = () => void;
type OnEvaluateHandler = (evaluated: EvaluatedFlag, definition: FlagDefinition<unknown>) => void;
type ClientUpdateListener = (changedKeys: string[]) => void;
interface FlagOverride { value: unknown; variantKey?: string;}
interface RefreshResult { succeededKeys: string[]; failedKeys: string[]; errors: Record<string, unknown>;}
interface FlagStateEntry { remoteState: FlagRemoteState | undefined; // last state successfully returned for this key fetchedAt: number | undefined; // epoch ms of last successful fetch; undefined = never fetched lastError: unknown | undefined; // set if the most recent fetch attempt failed}
interface FlagStateCache { get(key: string): FlagStateEntry | undefined; set(key: string, entry: FlagStateEntry): void;}FlagStateCache is the pluggable storage abstraction behind
FlagsClientOptions.cache — TtlFlagStateCache (below) is the built-in
implementation; a plain Map-backed one is used by default.
evaluate/evaluateAll are synchronous cache reads — they never call the
provider, so a broken provider can only degrade individual flags (via
failureMode), never throw out of an evaluation call. The one exception is
evaluate() on a key that was never registered, which throws synchronously
by design (a caller bug, not a runtime condition).
Not thread-safe across concurrent contexts: setContext/getContext
mutate shared instance state. Either instantiate one client per request
(e.g. per SSR request), or always pass context explicitly to
evaluate/evaluateAll and never call setContext on a shared instance.
setOverrides() forces a flag’s resolved value/variantKey regardless of
failureMode, dependsOn, targeting, or remote state — reason is always
"override", and overridden reads never fire onEvaluate handlers even
when emitsExposure is true. See the
DevTools adapter, which builds on this.
TtlFlagStateCache
Section titled “TtlFlagStateCache”interface TtlFlagStateCacheOptions { ttlMs?: number; // default 30_000 staleWhileRevalidateMs?: number; // default 300_000 retriggerCooldownMs?: number; // default = ttlMs now?: () => number; // clock seam for tests onStale?: (key: string) => void;}
class TtlFlagStateCache implements FlagStateCache { constructor(options?: TtlFlagStateCacheOptions); get(key: string): FlagStateEntry | undefined; set(key: string, entry: FlagStateEntry): void; isStale(key: string): boolean; getStaleness(key: string): CacheStaleness; // "fresh" | "stale" | "unknown" setOnStale(handler: ((key: string) => void) | undefined): void;}
function wireAutoRevalidation( cache: TtlFlagStateCache, client: Pick<FlagsClient, "refresh">,): Unsubscribe;get()/set() are pure passthroughs on top of the default in-memory
cache — TTL age never changes what’s returned or evicts data; the only
effect is the onStale side-channel. wireAutoRevalidation wires that
side-channel to call client.refresh([...keys]) automatically, batching
every key that goes stale in the same tick into one call. It only needs a
refresh() method, not a full FlagsClient — pass an instance as
FlagsClientOptions.cache.
Kind-sugar factories
Section titled “Kind-sugar factories”Eight factory functions, each a pre-filled trait bundle over the same
FlagDefinition shape — not a separate evaluation path per “kind”:
interface CommonFlagOptions { schedule?: { startAt?: string; endAt?: string }; description?: string; owners?: string[]; dependsOn?: string[];}
interface DefineBooleanFlagOptions extends CommonFlagOptions { key: string; defaultValue: boolean;}
interface DefineExperimentOptions<T> extends CommonFlagOptions { key: string; defaultValue: T; // fallback used by evaluate() when no bucketing key resolves variants: FlagVariant<T>[];}
interface DefineDynamicConfigOptions<T> extends CommonFlagOptions { key: string; defaultValue: T;}function defineReleaseFlag(options: DefineBooleanFlagOptions): FlagDefinition<boolean>;function defineKillSwitch(options: DefineBooleanFlagOptions): FlagDefinition<boolean>;function defineExperiment<T>(options: DefineExperimentOptions<T>): FlagDefinition<T>;function defineMigrationFlag(options: DefineBooleanFlagOptions): FlagDefinition<boolean>;function defineProgressiveDeploy(options: DefineBooleanFlagOptions): FlagDefinition<boolean>;function defineEntitlementFlag(options: DefineBooleanFlagOptions): FlagDefinition<boolean>;function defineCircuitBreaker(options: DefineBooleanFlagOptions): FlagDefinition<boolean>;function defineDynamicConfig<T>(options: DefineDynamicConfigOptions<T>): FlagDefinition<T>;defaultValue is always required — there are no hidden defaults. See
Flag Taxonomy.
Shadow-mode comparisons
Section titled “Shadow-mode comparisons”function runShadow<T>( oldImpl: () => T, newImpl: () => T, onResult: ShadowResultHandler<T>,): T;
function runShadowAsync<T>( oldImpl: () => Promise<T>, newImpl: () => Promise<T>, onResult: ShadowResultHandler<T>,): Promise<T>;
function deepEqual(a: unknown, b: unknown): boolean;Standalone utilities for comparing an old and new code path during a
migration — decoupled from FlagDefinition/FlagsClient so evaluate()
stays pure. Always returns the old implementation’s result; a throwing
newImpl is caught and reported via onResult, never propagated.