TypeScript / Node.js
Universal TypeScript SDK. Works anywhere JavaScript runs — Node.js, browsers, NestJS, edge runtimes (Cloudflare Workers, Vercel Edge, Deno), React Native, and Electron.
npm install toggleai-sdkThe TypeScript SDK operates in close synergy with ToggleAI backend services to guarantee high performance, security, and developer convenience. Evaluated values are resolved using two standard paradigms under the hood:
⚡ Local In-Memory Evaluation
When client.init() is called, the SDK makes a single, secure HTTP request to GET /sdk/config, retrieving the active configuration rules. Subsequent evaluations like client.getFlag() execute in-memory with sub-millisecond latency—eliminating all network hops from critical user paths.
🌐 Real-Time Remote Evaluation
In latency-tolerant or highly sensitive contexts requiring immediate synchronization, client.evaluateFlagRemote() makes a direct edge call to POST /sdk/evaluate, completely bypassing local caches to fetch the absolute latest resolution logic.
import { ToggleAIClient } from "toggleai-sdk";
const client = new ToggleAIClient({
clientId: process.env.TOGGLEAI_CLIENT_ID!,
secret: process.env.TOGGLEAI_SECRET!,
pollingInterval: 30000,
defaultContext: {
attributes: {
server_region: "us-east-1",
app_version: "2.5.0",
},
},
onReady: () => console.log("ToggleAI ready!"),
onConfigUpdate: (payload) => console.log("Refreshed at", payload.generatedAt),
onError: (error) => console.error("SDK Error:", error.code, error.message),
});
await client.init();
// Feature flags — evaluated instantly in-memory from local cache
const ctx = { userId: "user_123", attributes: { plan: "premium" } };
if (client.getFlag("new-checkout", ctx)) {
showNewCheckout();
}
// Server-side evaluation — makes direct API call bypassing local cache
const remoteResult = await client.evaluateFlagRemote("new-checkout", ctx);
// Remote config — resolved instantly locally
const timeout = client.getConfig<number>("api_timeout_ms", 5000);
// Logging — non-blocking, batched, and asynchronously flushed
const logger = client.getLogger();
logger.info("Request processed", { userId: "user_123" });
// Cleanup — flushes final logs and stops the background polling loop
client.close();The SDK ships with a first-class NestJS module at toggleai-sdk/nestjs.
Static Configuration
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 (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) {}
isDarkMode(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 });
}
}The SDK works with React Native out of the box since it uses isomorphic fetch. No native modules required.
// lib/toggleai.ts — Singleton client
import { ToggleAIClient } from "toggleai-sdk";
export const toggleai = new ToggleAIClient({
clientId: "pk_live_xxx",
secret: "sk_live_xxx",
pollingInterval: 60000, // poll less frequently on mobile
});// App.tsx — Initialize on app start
import { useEffect, useState } from "react";
import { toggleai } from "./lib/toggleai";
export default function App() {
const [ready, setReady] = useState(false);
useEffect(() => {
toggleai.init().then(() => setReady(true));
return () => toggleai.close();
}, []);
if (!ready) return <LoadingScreen />;
return <MainNavigator />;
}
// In any component:
function CheckoutScreen() {
const showNew = toggleai.getFlag("new-checkout", {
userId: currentUser.id,
attributes: { platform: "ios" },
});
return showNew ? <NewCheckout /> : <LegacyCheckout />;
}For Electron apps, initialize the SDK in the main process and expose flag values to renderer processes via IPC or preload scripts.
// Main process
import { ToggleAIClient } from "toggleai-sdk";
import { ipcMain } from "electron";
const client = new ToggleAIClient({
clientId: process.env.TOGGLEAI_CLIENT_ID!,
secret: process.env.TOGGLEAI_SECRET!,
});
app.whenReady().then(async () => {
await client.init();
// Expose flag evaluation to renderer via IPC
ipcMain.handle("toggleai:getFlag", (_event, key: string, ctx?: any) => {
return client.getFlag(key, ctx);
});
ipcMain.handle("toggleai:getConfig", (_event, key: string, def: any) => {
return client.getConfig(key, def);
});
});
app.on("before-quit", () => client.close());// Preload script
import { contextBridge, ipcRenderer } from "electron";
contextBridge.exposeInMainWorld("toggleai", {
getFlag: (key: string, ctx?: any) =>
ipcRenderer.invoke("toggleai:getFlag", key, ctx),
getConfig: (key: string, defaultValue: any) =>
ipcRenderer.invoke("toggleai:getConfig", key, defaultValue),
});import { ToggleAIError } from "toggleai-sdk";
try {
await client.init();
} catch (error) {
if (error instanceof ToggleAIError) {
switch (error.code) {
case "INVALID_KEY": console.error("Bad credentials"); break;
case "RATE_LIMITED": console.error("Rate limited"); break;
case "FORBIDDEN": console.error("Insufficient scope"); break;
case "NETWORK_ERROR": console.error("Cannot reach API"); break;
}
}
}| Option | Type | Description |
|---|---|---|
onReady | () => void | Client initialized and config fetched |
onConfigUpdate | (payload) => void | Config payload refreshed by polling |
onError | (error) => void | SDK error (network, rate limit, etc.) |
Client Lifecycle
| Method | Returns | Description |
|---|---|---|
init() | Promise<void> | Fetch config, start polling |
close() | void | Stop polling, flush logs |
refresh() | Promise<void> | Manually refresh config |
isReady() | boolean | Client initialized? |
waitForReady() | Promise<void> | Wait for initialization |
getState() | ClientState | Current lifecycle state |
Feature Flags
| Method | Returns |
|---|---|
getFlag(key, ctx?, default?) | boolean |
getFlagValue<T>(key, ctx?, default?) | T |
evaluateFlag(key, ctx?) | FlagEvaluationResult |
evaluateAllFlags(ctx?) | Record<string, FlagEvaluationResult> |
evaluateFlagRemote(key, ctx?) | Promise<FlagEvaluationResult> |
evaluateAllFlagsRemote(ctx?) | Promise<Record<...>> |
Remote Config
| Method | Returns |
|---|---|
getConfig<T>(key, default?) | T |
getAllConfigs() | Record<string, unknown> |
hasConfig(key) | boolean |
getConfigKeys() | string[] |
Logging
| Method | Returns |
|---|---|
getLogger() | ToggleAILogger |
logger.info(msg, ctx?) | void |
logger.error(err, ctx?) | void |
logger.captureError(err, ctx?) | void |
logger.setContext(ctx) | void |
logger.flush() | Promise<void> |