npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@togglely/sdk-core

v1.2.6

Published

Core SDK for Togglely - Framework agnostic feature flag management with offline support

Downloads

145

Readme

Togglely Core SDK

Framework-agnostic core SDK for Togglely feature flag management with offline support, multi-brand/tenant capabilities, and a professionalized refresh model.

Installation

npm install @togglely/sdk-core

Quick Start

import { TogglelyClient } from '@togglely/sdk-core';

const client = new TogglelyClient({
  apiKey: 'your-api-key',
  project: 'my-project',
  environment: 'production',
  baseUrl: 'https://togglely.io',
});

const isEnabled = await client.isEnabled('new-feature');
const message = await client.getString('welcome-message', 'Hello!');
const timeout = await client.getNumber('api-timeout', 5000);
const config = await client.getJSON<AppConfig>('app-config', { theme: 'dark' });

Configuration Reference

const client = new TogglelyClient({
  // --- Required ---
  apiKey: string,         // API key from your Togglely dashboard
  project: string,        // Project key (e.g. 'my-project')
  environment: string,    // Environment key (e.g. 'production', 'staging')
  baseUrl: string,        // Your Togglely instance URL (e.g. 'https://togglely.io')

  // --- Network ---
  timeout: number,        // Request timeout in ms (default: 5000)
  autoFetch: boolean,     // Fetch toggles on construction (default: true)

  // --- Offline ---
  offlineFallback: boolean,               // Enable offline fallback (default: true)
  offlineJsonPath: string | undefined,    // Path to offline JSON file
  offlineToggles: Record<string, ToggleValue>, // Inline offline toggles
  envPrefix: string,                      // Env var prefix (default: 'TOGGLELY_')

  // --- Multi-Tenant ---
  brandKey: string,       // Brand key for multi-brand projects
  tenantId: string,       // Tenant ID (alias for brandKey)

  // --- Targeting ---
  context: ToggleContext, // Initial targeting context (userId, email, country, ...)

  // --- Refresh ---
  refreshStrategy: 'manual' | 'interval' | 'stale-while-revalidate', // default: 'manual'
  refreshIntervalMs: number,       // Interval period in ms (default: 30000)
  minRefreshIntervalMs: number,    // SWR cooldown in ms (default: 5000)
});

Key Types

interface ToggleValue {
  value: any;
  enabled: boolean;
  flagType?: 'BOOLEAN' | 'STRING' | 'NUMBER' | 'JSON';
}

interface ToggleContext {
  userId?: string;
  email?: string;
  country?: string;
  region?: string;
  [key: string]: any;   // arbitrary custom attributes
}

interface TogglelyState {
  isReady: boolean;
  isOffline: boolean;
  lastError: Error | null;
  lastFetch: Date | null;
}

Full API Reference

Toggle Evaluation

isEnabled(key: string, defaultValue?: boolean): Promise<boolean>

Check whether a boolean feature flag is enabled. Returns true only when the toggle exists, enabled === true, and value === true (for boolean flags) or enabled === true (for non-boolean flags).

const showBeta = await client.isEnabled('beta-feature', false);

getString(key: string, defaultValue?: string): Promise<string>

Get the string value of a toggle. Returns defaultValue if the toggle does not exist or is disabled.

const welcomeMsg = await client.getString('welcome-message', 'Hello!');

getNumber(key: string, defaultValue?: number): Promise<number>

Get the numeric value of a toggle. Returns defaultValue if not found or disabled.

const maxItems = await client.getNumber('max-items', 10);

getJSON<T>(key: string, defaultValue?: T): Promise<T>

Get a parsed JSON object from a toggle. If the stored value is a JSON string, it is parsed automatically. Returns defaultValue on parse failure or if disabled.

interface ThemeConfig { primary: string; mode: 'light' | 'dark' }
const theme = await client.getJSON<ThemeConfig>('theme-config', { primary: '#000', mode: 'light' });

getValue(key: string): Promise<ToggleValue | null>

Get the raw ToggleValue for a key. Returns null if the toggle does not exist. This is the low-level method used by all typed accessors above.

const raw = await client.getValue('my-flag');
if (raw?.enabled) {
  console.log('Flag type:', raw.flagType, 'Value:', raw.value);
}

getAllToggles(): Record<string, ToggleValue>

Return all currently cached toggles as a plain object. This is synchronous and does not trigger a network request.

const all = client.getAllToggles();
Object.entries(all).forEach(([key, toggle]) => {
  console.log(`${key}: enabled=${toggle.enabled}, value=${toggle.value}`);
});

Refresh

refresh(): Promise<void>

Manually fetch the latest toggles from the server. Concurrent calls are deduplicated (only one in-flight request at a time). Emits ready (first load), update (if payload changed), or error/offline.

await client.refresh();

Context Management

setContext(context: ToggleContext): void

Merge new attributes into the current targeting context. The context is sent as a query parameter on every refresh.

client.setContext({ userId: 'user-123', country: 'DE' });

getContext(): ToggleContext

Return a copy of the current targeting context.

const ctx = client.getContext();
console.log(ctx.userId);

clearContext(): void

Reset the targeting context to an empty object.

client.clearContext();

Event System

on(event: TogglelyEventType, handler: (state: TogglelyState) => void): () => void

Subscribe to an event. Returns an unsubscribe function.

off(event: TogglelyEventType, handler: (state: TogglelyState) => void): void

Unsubscribe a previously registered handler.

Events:

| Event | Emitted when | |-------|-------------| | ready | Toggles are fetched successfully for the first time | | update | A refresh returns a different toggle payload than the cached one | | error | Any refresh fails (network error, HTTP error, timeout) | | offline | The client transitions to offline mode (fallback toggles active) | | online | The client transitions back from offline to online |

const unsubscribe = client.on('ready', (state) => {
  console.log('Togglely ready! Last fetch:', state.lastFetch);
});

client.on('update', (state) => {
  console.log('Toggles changed');
});

client.on('error', (state) => {
  console.error('Refresh failed:', state.lastError?.message);
});

client.on('offline', () => console.log('Switched to offline fallback'));
client.on('online', () => console.log('Back online'));

// Cleanup
unsubscribe();

State Inspection

isReady(): boolean

Returns true after the first successful fetch.

isOffline(): boolean

Returns true when the client is using offline fallback data.

getState(): TogglelyState

Returns a copy of the full state object (isReady, isOffline, lastError, lastFetch).

Lifecycle

destroy(): void

Clear all cached toggles, remove all event handlers, and stop any interval/timeout timers. Call this when you unmount or shut down.

client.destroy();

forceOfflineMode(): void

Manually switch to offline mode and emit the offline event.

forceOnlineMode(): void

Switch back to online mode, trigger a refresh, and emit the online event.

Error Handling Patterns

The SDK is designed to be resilient. Here is how different failure scenarios behave:

| Scenario | Behavior | |----------|----------| | Network failure / timeout | error event emitted. If offlineFallback is enabled and offline toggles were loaded, switches to offline mode (offline event). Cached values remain available. | | HTTP 401 Unauthorized | Treated as a refresh error. error event with state.lastError.message === 'HTTP 401'. The SDK does not retry automatically. | | HTTP 500 Server Error | Same as network failure -- error event, falls back to offline toggles if available. | | Request timeout | Governed by the timeout config (default 5000ms). Behaves like a network failure. | | Toggle key not found | getValue() returns null; typed accessors (isEnabled, getString, etc.) return the defaultValue. | | JSON parse failure | getJSON() returns the defaultValue if the stored string is not valid JSON. |

Recommended pattern:

const client = new TogglelyClient({
  apiKey: 'tk_xxx',
  project: 'my-project',
  environment: 'production',
  baseUrl: 'https://togglely.io',
  offlineFallback: true,
  offlineToggles: {
    'critical-feature': { value: true, enabled: true },
  },
});

client.on('error', (state) => {
  reportToMonitoring('togglely_error', state.lastError);
});

client.on('offline', () => {
  console.warn('Using offline toggles -- some values may be stale');
});

Offline Fallback Configuration

When the API is unreachable, the SDK uses offline toggles. Four sources are checked in priority order:

1. Inline Toggles (highest priority)

Pass toggle values directly in the constructor config:

const client = new TogglelyClient({
  ...baseConfig,
  offlineToggles: {
    'new-feature': { value: true, enabled: true },
    'api-timeout': { value: 5000, enabled: true, flagType: 'NUMBER' },
    'app-config': { value: { theme: 'dark' }, enabled: true, flagType: 'JSON' },
  },
});

2. JSON File

Load toggles from a static JSON file. In the browser, the file is fetched via fetch(). In Node.js, it is read from the filesystem.

const client = new TogglelyClient({
  ...baseConfig,
  offlineJsonPath: '/toggles.json', // relative to public root in browser
});

Generate the file at build time:

npx @togglely/sdk-core togglely-pull \
  --apiKey=tk_xxx \
  --project=my-project \
  --environment=production \
  --output=./public/toggles.json

3. Environment Variables (Node.js)

Environment variables with the configured prefix are automatically parsed:

TOGGLELY_NEW_FEATURE=true        # -> key "new-feature", value true
TOGGLELY_API_TIMEOUT=5000        # -> key "api-timeout", value 5000
TOGGLELY_APP_CONFIG='{"a":1}'    # -> key "app-config", value {a: 1}

The prefix is configurable (default TOGGLELY_). Key transformation: strip prefix, lowercase, replace _ with -.

const client = new TogglelyClient({
  ...baseConfig,
  envPrefix: 'MYAPP_FLAGS_',
});

4. Window Object (Browser)

Set window.__TOGGLELY_TOGGLES before the SDK initializes:

<script>
  window.__TOGGLELY_TOGGLES = {
    'new-feature': { value: true, enabled: true },
    'api-timeout': { value: 5000, enabled: true },
  };
</script>
<script src="your-app.js"></script>

Injecting Offline Toggles at Build Time

Two utility functions help with build-time injection:

import { createOfflineTogglesScript, togglesToEnvVars } from '@togglely/sdk-core';

// Generate a <script> tag for HTML injection
const scriptTag = createOfflineTogglesScript({ 'feature-a': true });
// -> '<script>window.__TOGGLELY_TOGGLES = {"feature-a":true};</script>'

// Generate env var mappings
const envVars = togglesToEnvVars({ 'feature-a': true }, 'TOGGLELY_');
// -> { TOGGLELY_FEATURE_A: 'true' }

Refresh Strategies

manual (default)

Cached reads never trigger network requests. Call refresh() explicitly when you want fresh data.

const client = new TogglelyClient({
  ...baseConfig,
  refreshStrategy: 'manual',
});

// Refresh on user action
document.getElementById('refresh-btn').addEventListener('click', () => {
  client.refresh();
});

interval

A background timer calls refresh() at a fixed interval.

const client = new TogglelyClient({
  ...baseConfig,
  refreshStrategy: 'interval',
  refreshIntervalMs: 30000, // every 30 seconds
});

stale-while-revalidate

Cached reads return immediately but may schedule a background refresh. A cooldown (minRefreshIntervalMs) prevents excessive requests.

const client = new TogglelyClient({
  ...baseConfig,
  refreshStrategy: 'stale-while-revalidate',
  minRefreshIntervalMs: 5000, // at most one refresh every 5 seconds
});

| Strategy | Network on read? | Automatic background refresh? | Best for | |----------|-----------------|------------------------------|----------| | manual | No | No | Frontend apps, controlled refresh | | interval | No | Yes (fixed timer) | Dashboards, internal tools | | stale-while-revalidate | No (returns cache) | Yes (throttled) | Interactive apps with tolerance for staleness |

Multi-Tenant / Brand Context

For projects serving multiple brands or tenants, use tenantId or brandKey. Both are sent as query parameters and added to the targeting context automatically.

const client = new TogglelyClient({
  apiKey: 'tk_xxx',
  project: 'my-project',
  environment: 'production',
  baseUrl: 'https://togglely.io',
  tenantId: 'brand-a',   // or brandKey: 'brand-a'
});

// Switch tenant at runtime
client.setContext({ tenantId: 'brand-b' });
await client.refresh();

Performance Best Practices

  • Batch mode: The SDK automatically batches concurrent getValue() calls within a 10ms window into a single refresh() request. You do not need to manage this yourself.
  • Cache TTL: All typed accessors (isEnabled, getString, etc.) return from the in-memory cache when a value exists. No network call is made unless the cache is empty or stale-while-revalidate triggers a background refresh.
  • Deduplicated refreshes: Concurrent calls to refresh() share a single in-flight request.
  • Refresh intervals: For interval strategy, 30 seconds is a reasonable default. For stale-while-revalidate, set minRefreshIntervalMs to at least 5 seconds to avoid excessive traffic.
  • Destroy on unmount: Always call client.destroy() when the client is no longer needed to clean up timers and handlers.

Build-Time JSON Generation (CLI)

# Basic usage
npx @togglely/sdk-core togglely-pull \
  --apiKey=tk_xxx \
  --project=my-project \
  --environment=production \
  --output=./public/toggles.json

# With tenant
togglely-pull \
  --apiKey=tk_xxx \
  --project=my-project \
  --environment=production \
  --tenantId=brand-a \
  --output=./toggles.json

# Output formats
togglely-pull --format=json  # JSON file (default)
togglely-pull --format=env   # .env file
togglely-pull --format=js    # JavaScript module

# Using environment variables instead of flags
export TOGGLELY_APIKEY=tk_xxx
export TOGGLELY_PROJECT=my-project
export TOGGLELY_ENVIRONMENT=production
togglely-pull --output=./toggles.json

# Using a config file
togglely-pull --config=./togglely.config.js

Config File (togglely.config.js)

module.exports = {
  apiKey: process.env.TOGGLELY_APIKEY,
  project: 'my-project',
  environment: 'production',
  baseUrl: 'https://togglely.io',
  tenantId: process.env.BRAND_KEY,
  output: './public/toggles.json',
  format: 'json',
};

Build Script Integration

{
  "scripts": {
    "build": "togglely-pull && vite build",
    "build:staging": "togglely-pull --environment=staging && vite build",
    "build:prod": "togglely-pull --environment=production && vite build"
  }
}

License

MIT