@vexilapp/sdk
v0.1.2
Published
Vexil JavaScript/TypeScript SDK
Readme
@vexilapp/sdk
Official JavaScript/TypeScript SDK for Vexil. Works in browsers, Node.js, and edge runtimes.
Installation
npm install @vexilapp/sdk
# or
bun add @vexilapp/sdkQuick start
import { VexilClient } from "@vexilapp/sdk";
const client = new VexilClient({
apiKey: "your-api-key",
environment: "production",
context: { userId: "user_123" },
});
await client.initialize();
if (client.isEnabled("new-checkout-flow")) {
// show new checkout
}
const variant = client.getVariant("ui-variant", "control");Configuration
new VexilClient({
apiKey: string // required — your Vexil API key
environment: string // required — e.g. "production", "staging", "development"
baseUrl?: string // default: "https://api.vexil.online"
pollingInterval?: number // ms between flag refreshes. default: 30_000. set 0 to disable.
context?: FlagContext // initial user context for targeting
localCache?: boolean // persist to localStorage for offline fallback. default: true
onChange?: ( // called when a flag value changes after a poll
key: string,
next: FlagEvaluation,
prev: FlagEvaluation | undefined
) => void
})API
initialize(): Promise<void>
Fetches all flag evaluations for the configured environment and user context, then starts polling. Call this once before using any flag methods.
await client.initialize();isEnabled(key: string, defaultValue?: boolean): boolean
Returns true if the flag is enabled for the current user. Falls back to defaultValue if the flag is not found.
client.isEnabled("dark-mode"); // false
client.isEnabled("dark-mode", true); // true (default if not found)getVariant<T>(key: string, defaultValue: T): T
Returns the flag's evaluated value. Use for string, number, or JSON flags.
const theme = client.getVariant("ui-theme", "default"); // string
const limit = client.getVariant("rate-limit", 100); // number
const config = client.getVariant("checkout-config", {}); // objectidentify(context: FlagContext): Promise<void>
Updates the user context and re-fetches all flag evaluations. Call this after a user logs in or their attributes change.
await client.identify({
userId: "user_456",
plan: "pro",
country: "US",
});getAllFlags(): Record<string, FlagEvaluation>
Returns a snapshot of all currently evaluated flags.
const flags = client.getAllFlags();
// { "new-checkout-flow": { enabled: true, value: true }, ... }destroy(): void
Stops polling and cleans up. Call this when the client is no longer needed (e.g. on component unmount or process exit).
client.destroy();A/B Experiments
When a running experiment is attached to a flag in the current environment, the SDK automatically:
- Assigns the user to a variant — deterministically, based on
userIdand flag key, so the same user always sees the same variant across sessions and devices. - Tracks an impression — fires a background request to record that the user was exposed to the experiment variant. No extra code needed.
You only need to call track() when the goal event occurs.
getExperiment(flagKey: string): ExperimentAssignment | undefined
Returns the experiment assignment for a flag, if one is active for the current user. Use this to read the assigned variant without re-evaluating the flag.
const assignment = client.getExperiment("checkout-redesign");
// { experimentId: 'exp_abc123', variantValue: 'variant-a', variantLabel: 'Variant A' }
if (assignment) {
console.log("User is in variant:", assignment.variantLabel);
}track(eventName: string, properties?: Record<string, unknown>, flagKey?: string): void
Records a goal conversion event. Call this when the user performs the action your experiment is measuring.
- If
flagKeyis provided, only records a conversion for that flag's active experiment. - If
flagKeyis omitted, fans out to all flags that currently have an active experiment assignment.
// Track 'signup' goal for all active experiments
client.track("signup");
// Track with metadata
client.track("purchase_completed", { revenue: 49.99, currency: "USD" });
// Track for a specific flag's experiment only
client.track("purchase_completed", { revenue: 49.99 }, "checkout-redesign");Full A/B example
const client = new VexilClient({
apiKey: "ff_live_...",
environment: "production",
context: { userId: "user_123" },
});
await client.initialize();
// 1. Check if the flag (and its experiment) is enabled for this user.
// Impression is automatically tracked.
if (client.isEnabled("checkout-redesign")) {
const assignment = client.getExperiment("checkout-redesign");
if (assignment?.variantValue === "one-page") {
renderOnePageCheckout();
} else {
renderMultiStepCheckout();
}
}
// 2. When the user converts, track the goal event.
onPurchaseComplete((order) => {
client.track("purchase_completed", { revenue: order.total });
});Flag dependencies
Flag dependencies are enforced server-side — when you call isEnabled for a dependent flag, the API already factors in whether its parent flags are enabled. No client-side changes are needed; the SDK always reflects the effective state.
// 'advanced-analytics' requires 'analytics-base' to be enabled.
// If 'analytics-base' is off, this returns false automatically.
client.isEnabled("advanced-analytics");Offline resilience
The SDK caches flag evaluations in localStorage (browser) after every successful fetch. If the API is unreachable, isEnabled and getVariant return the last cached values instead of the defaults.
To disable caching:
new VexilClient({ ..., localCache: false })Reacting to flag changes
Use onChange to react when a flag's value changes after a background poll:
const client = new VexilClient({
apiKey: "...",
environment: "production",
onChange: (key, next, prev) => {
console.log(`[${key}] ${prev?.enabled} → ${next.enabled}`);
},
});Types
interface FlagContext {
userId?: string;
[key: string]: string | number | boolean | undefined;
}
interface ExperimentAssignment {
experimentId: string; // ID of the running experiment
variantValue: unknown; // the value assigned to this user (matches a variant in the experiment)
variantLabel: string; // human-readable label, e.g. "Control", "Variant A"
}
interface FlagEvaluation {
enabled: boolean;
value: FlagValue; // boolean | string | number | Record<string, unknown>
rolloutPercentage?: number;
experiment?: ExperimentAssignment; // present when a running experiment is attached
}Building from source
npm run build # outputs CJS + ESM + types to dist/
npm run dev # watch mode