Vanilla JavaScript
Use the Levered SDK without React -- works in Node.js, Vue, Svelte, server-side handlers, and any JavaScript environment.
The vanilla integration exposes a LeveredClient class that works in any JavaScript or TypeScript environment -- browser, Node.js, edge runtimes, or server-side frameworks. Use it when you are not using React or need full control over variant fetching.
import { LeveredClient } from '@levered_dev/sdk';LeveredClient
Create a client instance with your API URL and callbacks. The client handles retries, timeouts, and in-memory caching internally.
import { LeveredClient } from '@levered_dev/sdk';
const client = new LeveredClient({
apiUrl: 'https://api.levered.dev',
onExposure: (event) => logToWarehouse(event),
onError: (err) => console.error('Levered error:', err),
timeoutMs: 2000,
maxRetries: 2,
cacheTtlMs: 60000,
});ClientConfig
| Option | Type | Default | Description |
|---|---|---|---|
apiUrl | string | -- | Base URL of the Levered API (required). |
onExposure | (event: ExposureEvent) => void | -- | Called when a variant is served to a user. Use this to log exposure events to your warehouse. |
onError | (error: unknown) => void | -- | Called when a variant request fails after all retries. Useful for monitoring. |
timeoutMs | number | 2000 | Request timeout in milliseconds. |
maxRetries | number | 2 | Number of retry attempts on network failure. Uses exponential backoff (200ms base). |
cacheTtlMs | number | 60000 | How long to cache variant responses in memory (milliseconds). Set to 0 to disable caching. |
getVariant
Request the best variant for a specific optimization and user.
const result = await client.getVariant({
anonymousId: 'user-123',
optimizationId: 'abc-123',
context: { country: 'US' },
});
if (result) {
renderPage(result.variant);
} else {
renderPage(fallbackVariant);
}Options
| Option | Type | Required | Description |
|---|---|---|---|
anonymousId | string | Yes | Stable anonymous identifier for the user or session. |
optimizationId | string | Yes | UUID of the optimization to serve variants from. |
context | Record<string, unknown> | No | Context attributes for contextual bandits (CMAB). |
nVariants | number | No | Number of ranked variants to return (default: 1). |
Return value
Returns VariantResult | null.
- On success, returns an object with
variant(the design-feature values) andoptimizationId. - On any error (network failure, timeout, non-200 response, empty result), returns
null.
interface VariantResult {
variant: Record<string, string | number | boolean>;
optimizationId: string;
modelType: 'mab' | 'cmab' | 'holdout';
ready: boolean;
}You must handle the null case. Unlike the React hook, the vanilla client does not manage fallback values for you. Always provide your own default when the result is null.
ExposureEvent
When getVariant succeeds, the client fires your onExposure callback with this event:
interface ExposureEvent {
anonymousId: string;
optimizationId: string;
variant: Record<string, string | number | boolean>;
context: Record<string, unknown>;
timestamp: string; // ISO-8601
modelType: 'mab' | 'cmab' | 'holdout';
ready: boolean;
}Forward this event to your data warehouse. Levered reads exposure data from your warehouse during model training -- if exposures are not logged, the model cannot learn.
Server-side usage
The client works in server-side environments (Express, Fastify, Next.js API routes, etc.) since it uses the standard fetch API. Make sure your runtime supports fetch (Node.js 18+ has it built in, or use a polyfill).
Express example
import express from 'express';
import { LeveredClient } from '@levered_dev/sdk';
const app = express();
const levered = new LeveredClient({
apiUrl: 'https://api.levered.dev',
onExposure: (event) => {
// Write to your exposure table or send to a queue
insertExposure(event);
},
});
const FALLBACK = { headline: 'Welcome', cta_text: 'Get Started' };
app.get('/', async (req, res) => {
const anonymousId = req.cookies.session_id ?? generateId();
const result = await levered.getVariant({
anonymousId,
optimizationId: 'abc-123',
context: { country: req.headers['cf-ipcountry'] ?? 'US' },
});
const variant = result?.variant ?? FALLBACK;
res.render('home', { headline: variant.headline, cta: variant.cta_text });
});
app.listen(3000);Fastify example
import Fastify from 'fastify';
import { LeveredClient } from '@levered_dev/sdk';
const fastify = Fastify();
const levered = new LeveredClient({
apiUrl: 'https://api.levered.dev',
onExposure: (event) => insertExposure(event),
timeoutMs: 1500,
});
const FALLBACK = { headline: 'Welcome', cta_text: 'Get Started' };
fastify.get('/', async (request, reply) => {
const anonymousId = request.cookies.session_id ?? generateId();
const result = await levered.getVariant({
anonymousId,
optimizationId: 'abc-123',
});
return {
headline: result?.variant.headline ?? FALLBACK.headline,
cta_text: result?.variant.cta_text ?? FALLBACK.cta_text,
};
});
fastify.listen({ port: 3000 });Caching behavior
The client caches variant responses in memory, keyed by the combination of optimizationId, anonymousId, and context. A cached response is returned immediately without a network request until it expires.
- Default TTL is 60 seconds (
cacheTtlMs: 60000). - Set
cacheTtlMs: 0to disable caching entirely. - The cache is per-client-instance and lives in memory. If you create the client at module scope on a server, the cache is shared across requests for the same process.
Error handling
The client never throws from getVariant. If the request fails, it retries up to maxRetries times with exponential backoff, then calls your onError callback (if provided) and returns null. Your application code only needs to check for null:
const result = await client.getVariant({ anonymousId, optimizationId });
const variant = result?.variant ?? fallback;