toggleai-sdk
v1.0.2
Published
Universal TypeScript SDK for ToggleAI — feature flags & remote config for Node.js, browsers, NestJS, and edge runtimes
Maintainers
Readme
toggleai-sdk
Universal TypeScript SDK for ToggleAI. Manage feature flags, remote configurations, and logging & error monitoring across Node.js, browsers, NestJS, and edge runtimes (Cloudflare Workers, Vercel Edge, Deno, etc.).
🔗 Quick Links
Features
- 🌍 Universal: Works anywhere JavaScript runs (isomorphic
fetch). - ⚡ Zero Latency: Evaluates flags locally in-memory by default (sub-millisecond).
- 🔄 Real-Time Polling: Automatic background fetching for live flag updates.
- 🎯 Advanced Targeting: Full support for targeting rules, user attributes, and percentage rollouts.
- 🐞 Structured Logging: Built-in batched, buffered logging.
- 🚨 Error Monitoring: Auto-capture global unhandled errors.
- 🛡️ Type-Safe: Written in TypeScript with full generic support for config values.
- 📦 Dual Output: Published as both ESM (
import) and CommonJS (require). - 🏗️ NestJS Ready: First-class
ToggleAIModulewithforRoot/forRootAsync. - 🖥️ Server-Side Evaluation: Call the backend for real-time flag evaluation when needed.
Installation
npm install toggleai-sdk
# or
yarn add toggleai-sdk
# or
pnpm add toggleai-sdkQuick Start
1. Initialize the Client
import { ToggleAIClient } from "toggleai-sdk";
const client = new ToggleAIClient({
clientId: "pk_live_xxxxxxxxxxxxxxxx",
secret: "sk_live_xxxxxxxxxxxxxxxx",
pollingInterval: 30000, // refresh every 30s (default)
});
await client.init();2. Evaluate Feature Flags
Flags are evaluated locally from the cached config payload — zero network latency.
const context = {
userId: "user_123",
attributes: {
plan: "premium",
country: "US",
},
};
// Boolean flag
if (client.getFlag("new-checkout", context)) {
showNewCheckout();
}
// Typed flag value (string, number, JSON)
const buttonColor = client.getFlagValue<string>("buy-button-color", context, "#000000");
const maxItems = client.getFlagValue<number>("max-items", context, 10);3. Read Remote Configs
Remote configs are key-value pairs that don't depend on user context.
const apiTimeout = client.getConfig<number>("api_timeout_ms", 5000);
const theme = client.getConfig<{ primary: string }>("theme_colors", { primary: "#fff" });
// Check all configs
const allConfigs = client.getAllConfigs();4. Logging & Error Monitoring
The SDK provides an integrated edge-native logger that automatically batches and flushes events to the backend.
const logger = client.getLogger();
logger.info("User signed in", { userId: "user_123" });
try {
await riskyOperation();
} catch (err) {
logger.captureError(err as Error, { userId: "user_123" });
}5. Cleanup
// Closes the client, stops polling, and flushes any pending logs
client.close();Server-Side Evaluation
By default, the SDK evaluates flags locally from the cached payload (evaluationMode: "local"). For absolute real-time accuracy, use server-side evaluation which calls the backend directly:
// Local evaluation (instant, from cache)
const localResult = client.evaluateFlag("dark-mode", context);
console.log(localResult.reason); // "TARGETING_MATCH", "ROLLOUT", etc.
// Server-side evaluation (network call to POST /sdk/evaluate/:flagKey)
const remoteResult = await client.evaluateFlagRemote("dark-mode", context);
// Server-side evaluate ALL flags at once (POST /sdk/evaluate)
const allResults = await client.evaluateAllFlagsRemote(context);NestJS Integration
The SDK ships with a dedicated NestJS module at toggleai-sdk/nestjs.
Setup
// app.module.ts
import { Module } from "@nestjs/common";
import { ToggleAIModule } from "toggleai-sdk/nestjs";
@Module({
imports: [
ToggleAIModule.forRoot({
clientId: "pk_live_xxx",
secret: "sk_live_xxx",
}),
],
})
export class AppModule {}Async Configuration (with ConfigService)
import { Module } from "@nestjs/common";
import { ConfigModule, ConfigService } from "@nestjs/config";
import { ToggleAIModule } from "toggleai-sdk/nestjs";
@Module({
imports: [
ConfigModule.forRoot(),
ToggleAIModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
clientId: config.getOrThrow("TOGGLEAI_CLIENT_ID"),
secret: config.getOrThrow("TOGGLEAI_SECRET"),
}),
}),
],
})
export class AppModule {}Injecting the Client
import { Injectable } from "@nestjs/common";
import { InjectToggleAI, ToggleAIClient } from "toggleai-sdk/nestjs";
@Injectable()
export class FeatureService {
constructor(@InjectToggleAI() private readonly client: ToggleAIClient) {}
isDarkModeEnabled(userId: string): boolean {
return this.client.getFlag("dark-mode", { userId });
}
getApiTimeout(): number {
return this.client.getConfig<number>("api_timeout_ms", 5000);
}
async evaluateRemote(flagKey: string, userId: string) {
return this.client.evaluateFlagRemote(flagKey, { userId });
}
}You can also inject using the token directly:
import { Inject, Injectable } from "@nestjs/common";
import { ToggleAI_CLIENT, ToggleAIClient } from "toggleai-sdk";
@Injectable()
export class MyService {
constructor(@Inject(ToggleAI_CLIENT) private client: ToggleAIClient) {}
}Global Default Context
Provide a default context when creating the client. It will be merged with any per-evaluation context (per-eval takes precedence).
const client = new ToggleAIClient({
clientId: process.env.TOGGLEAI_CLIENT_ID!,
secret: process.env.TOGGLEAI_SECRET!,
defaultContext: {
attributes: {
server_region: "us-east-1",
app_version: "2.5.0",
},
},
});Event Listeners
const client = new ToggleAIClient({
clientId: "...",
secret: "...",
onReady: () => console.log("ToggleAI SDK is ready!"),
onConfigUpdate: (payload) => console.log("Config refreshed at", payload.generatedAt),
onError: (error) => console.error("SDK Error:", error.code, error.message),
});Error Handling
The SDK throws ToggleAIError with structured error codes:
import { ToggleAIError } from "toggleai-sdk";
try {
await client.init();
} catch (error) {
if (error instanceof ToggleAIError) {
switch (error.code) {
case "INVALID_KEY":
console.error("Authentication failed. Check your API keys.");
break;
case "RATE_LIMITED":
console.error("Rate limited. Slow down requests.");
break;
case "FORBIDDEN":
console.error("Insufficient API key scope.");
break;
case "NETWORK_ERROR":
console.error("Cannot reach the ToggleAI API.");
break;
}
}
}Logging & Error Monitoring
The SDK includes ToggleAILogger for edge-native logging and error capture. It automatically batches events and flushes them to the backend in the background.
Attached Logger
Get a pre-configured logger that shares the client's API keys:
const logger = client.getLogger();
// Log with structured context
logger.debug("Debugging query", { queryId: "q_1" });
logger.info("Task completed");
logger.warn("Rate limit approaching");
logger.error(new Error("Database connection failed"));
logger.fatal("System out of memory");Global Error Capture
You can configure a standalone logger to automatically catch global unhandled errors (window.onerror or process.uncaughtException):
import { ToggleAILogger } from "toggleai-sdk";
const logger = new ToggleAILogger({
clientId: "pk_live_xxx",
secret: "sk_live_xxx",
captureGlobalErrors: true, // auto-captures unhandled errors
minLevel: "warn", // only send warnings and above
});Batching & Flushing
Logs are queued in memory and batched. By default, they flush every 5 seconds or when 50 events are queued. You should manually flush before shutting down your server:
await logger.flush();A/B Testing & Experiments
The ToggleAI SDK supports powerful A/B testing and experiment tracking, allowing you to measure conversion rates, user exposure, and target metrics natively.
1. Auto-Exposure Tracking
When you evaluate a feature flag that has a running experiment attached to it, the SDK automatically sends an exposure event to the backend in the background (fire-and-forget, with session-level deduplication so we don't spam requests).
// Simply evaluating the flag triggers the exposure event under the hood!
const result = client.evaluateFlag("new-hero-variant", { userId: "user_123" });2. Zero-Code Event Tracking
Track meaningful business actions globally. The backend automatically attributes these events to any running experiments whose metrics match the metricKey.
// Track a single event
await client.track({
metricKey: "purchase_completed",
userIdentifier: "user_123",
value: 49.99, // Optional revenue or metric value
});
// Track multiple events in batch (up to 100)
await client.trackBatch([
{ metricKey: "page_view", userIdentifier: "user_123" },
{ metricKey: "add_to_cart", userIdentifier: "user_123", value: 1 },
]);3. Explicit Experiment Conversion Tracking
If you need precision control over when a user converts for a specific A/B experiment, you can resolve the variationId and send a manual conversion:
// 1. Evaluate the flag
const result = client.evaluateFlag("checkout-layout", { userId: "user_123" });
// 2. Resolve the variation's database UUID
const variationId = client.resolveVariationId("checkout-layout", result.variationKey);
// 3. Track conversion when the action occurs
if (variationId) {
await client.trackConversion({
experimentId: "exp_checkout_redesign",
variationId,
metricKey: "checkout_conversion",
userId: "user_123",
value: 1, // Optional value
});
}4. Manual Exposure Recording
For special setups (like server-side rendering or non-standard routing), you can also manually register exposures:
await client.recordExposure({
experimentId: "exp_checkout_redesign",
variationId: "var_redesign_a",
userIdentifier: "user_123",
});API Reference
Client Methods
| Method | Returns | Description |
|---|---|---|
| init() | Promise<void> | Initialize client, fetch config, start polling |
| close() | void | Stop polling, release resources |
| refresh() | Promise<void> | Manually refresh config payload |
| isReady() | boolean | Check if client is initialized |
| waitForReady() | Promise<void> | Wait for initialization |
| getState() | ClientState | Current lifecycle state |
Feature Flags (Local)
| Method | Returns | Description |
|---|---|---|
| getFlag(key, ctx?, default?) | boolean | Get boolean flag value |
| getFlagValue<T>(key, ctx?, default?) | T | Get typed flag value |
| evaluateFlag(key, ctx?) | FlagEvaluationResult | Get full evaluation result |
| evaluateAllFlags(ctx?) | Record<string, FlagEvaluationResult> | Evaluate all flags |
Feature Flags (Server-Side)
| Method | Returns | Description |
|---|---|---|
| evaluateFlagRemote(key, ctx?) | Promise<FlagEvaluationResult> | Server-side single flag evaluation |
| evaluateAllFlagsRemote(ctx?) | Promise<Record<...>> | Server-side all flags evaluation |
Remote Config
| Method | Returns | Description |
|---|---|---|
| getConfig<T>(key, default?) | T | Get typed config value |
| getAllConfigs() | Record<string, unknown> | Get all config values |
| hasConfig(key) | boolean | Check if config key exists |
Logging
| Method | Returns | Description |
|---|---|---|
| getLogger() | ToggleAILogger | Get the attached logger instance |
| logger.info(msg, ctx?) | void | Log an info message |
| logger.error(err, ctx?) | void | Log an error message or Error object |
| logger.captureError(err, ctx?)| void | Capture an Error object with context |
| logger.setContext(ctx) | void | Set default context for all subsequent logs |
| logger.flush() | Promise<void> | Manually flush queued logs |
A/B Testing & Experiments
| Method | Returns | Description |
|---|---|---|
| track(event) | Promise<TrackEventResult> | Track a generic event for auto-attribution |
| trackBatch(events) | Promise<TrackEventResult> | Track multiple events in a single batch |
| trackConversion(opts) | Promise<TrackConversionResult> | Track explicit conversion for an experiment |
| resolveVariationId(flagKey, varKey) | string \| null | Convert variation key string to DB variation UUID |
| recordExposure(exposure) | Promise<ExposureResult> | Manually record user exposure to a variation |
| recordExposures(exposures) | Promise<ExposureResult> | Manually record multiple user exposures |
| getActiveExperiments() | ExperimentPayloadItem[] | List all running experiments |
| getExperimentForFlag(flagKey) | ExperimentPayloadItem \| null| Get experiment details for a specific flag |
Inspection
| Method | Returns | Description |
|---|---|---|
| getPayload() | ConfigPayload \| null | Raw config payload |
| getFlagDefinition(key) | FlagDefinition \| null | Raw flag definition |
| getFlagKeys() | string[] | All flag keys |
| getConfigKeys() | string[] | All config keys |
| getEnvironment() | { id, slug } \| null | Environment metadata |
Backend Endpoints
This SDK communicates with the following backend endpoints (authenticated via API key pair):
| Endpoint | Method | Description |
|---|---|---|
| /sdk/config | GET | Fetch full config payload (flags + configs) |
| /sdk/evaluate | POST | Server-side evaluate all flags for a user |
| /sdk/evaluate/:flagKey | POST | Evaluate a single flag server-side |
| /sdk/connect | POST | Register SDK connection (analytics) |
| /sdk/logs/ingest | POST | Ingest a single log event |
| /sdk/logs/ingest/batch | POST | Ingest a batch of log events |
Support
For issues, questions, or contributions, please contact the ToggleAI team or open an issue in the repository.
