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

@splitlab/js-client

v0.10.3

Published

Lightweight JavaScript client SDK for SplitLab A/B testing and analytics

Readme

@splitlab/js-client

Lightweight JavaScript client SDK for SplitLab A/B testing and analytics. Zero dependencies, under 2KB gzipped.

Installation

<script src="https://splitlab.cc/sdk/splitlab.min.js"></script>

Or build from source:

cd sdk && npm run build
# Outputs ESM + CJS + types to dist/

Quick Start

import { SplitLabClient } from '@splitlab/js-client';

// No distinctId needed — SDK auto-generates a persistent device ID
const client = new SplitLabClient({
  apiKey: 'ot_live_abc123',
  baseUrl: 'https://splitlab.cc',
});
await client.initialize();

// A/B testing (< 1ms — local evaluation by default)
const variant = client.getVariant('checkout-btn');
if (variant === 'variant_a') showGreenButton();

// Feature flags
if (client.isFeatureEnabled('dark-mode')) enableDarkMode();

// Event tracking (auto-enriched with browser context, UTM, sessions)
await client.track('purchase', { value: 49.99, currency: 'USD' });

// When user logs in — links anonymous events to the user
await client.identify('user-42');

// Clean up
await client.destroy();

Configuration

const client = new SplitLabClient({
  // --- Required ---
  apiKey: string;           // Your org API key (ot_live_...)
  baseUrl: string;          // API server URL

  // --- Identity ---
  distinctId?: string;      // User ID (optional — auto-generates device ID if omitted)

  // --- Evaluation ---
  ingestUrl?: string;              // Ingest server URL (defaults to baseUrl)
  enableLocalEvaluation?: boolean; // Evaluate locally via hashing (default: true)
  configRefreshInterval?: number;  // Config polling interval in ms (default: 30000)
  realtimeUpdates?: boolean;       // SSE for instant config push (default: false)
  onConfigUpdate?: () => void;     // Callback when config changes after refresh

  // --- Event batching ---
  autoFlushInterval?: number;      // ms between auto-flushes (default: 30000)
  autoFlushSize?: number;          // events before auto-flush (default: 20)

  // --- Analytics auto-capture ---
  captureContext?: boolean;        // Browser context enrichment (default: true in browser)
  captureUtm?: boolean;            // UTM parameter capture (default: true)
  trackSessions?: boolean;         // Session ID tracking (default: true)
  sessionTimeout?: number;         // Session inactivity timeout in ms (default: 1800000)
  persistDeviceId?: boolean;       // Persistent device ID in localStorage (default: true)
  trackPageviews?: boolean;        // Auto-track SPA navigations (default: false)
  superProperties?: Record<string, any>; // Properties attached to every event
});

Local Evaluation (Default)

The SDK defaults to local evaluation — experiments and flags are computed client-side using the same deterministic hashing as the server. On initialize(), the SDK fetches the org config once and evaluates locally. Subsequent getVariant() and isFeatureEnabled() calls return in < 1ms with zero network calls.

const client = new SplitLabClient({
  apiKey: 'ot_live_abc123',
  baseUrl: 'https://splitlab.cc',
  // enableLocalEvaluation: true  — this is the default
});
await client.initialize();

Config is refreshed every 30 seconds by default. Refreshes use ETag-based conditional requests — if the config hasn't changed, the server returns 304 Not Modified (< 5ms, no body parsed).

To disable local evaluation and use server-side assignment (which respects sticky assignments in the database):

const client = new SplitLabClient({
  // ...
  enableLocalEvaluation: false,
});

Trade-off: local evaluation does not respect sticky assignments stored server-side. For first-time users the results are identical. For users who were previously assigned a variant, the server may return a different (stored) result.

Anonymous Users & Identity

The SDK works out of the box without a user ID. On first visit, a persistent device_id is generated and stored in localStorage. This device ID is used for:

  1. Experiment bucketing — variants are assigned based on device ID, so the assignment is stable across the anonymous → logged-in transition (no re-randomization)
  2. Event attribution — events are tagged with the device ID until identify() is called
// Anonymous — SDK generates a device ID automatically
const client = new SplitLabClient({
  apiKey: 'ot_live_abc123',
  baseUrl: 'https://splitlab.cc',
});
await client.initialize();

// User sees variant B (bucketed on device ID)
const variant = client.getVariant('checkout-btn'); // → 'variant_b'

// Later, user logs in
await client.identify('user-42');

// Still variant B — bucketing is device-level, not user-level
client.getVariant('checkout-btn'); // → 'variant_b' (same)

// Events now attributed to 'user-42' instead of the device ID
await client.track('purchase', { value: 49.99 });

When identify() is called, the SDK:

  • Fires a $identify event linking device_iduser_id (with previous_distinct_id)
  • Switches event attribution to the new user ID
  • Does not re-evaluate experiments (bucketing stays on device ID)

You can also provide distinctId upfront if the user is already authenticated:

const client = new SplitLabClient({
  apiKey: 'ot_live_abc123',
  baseUrl: 'https://splitlab.cc',
  distinctId: currentUser.id, // Already logged in
});

Realtime Config Updates

For instant config propagation (< 1 second after a dashboard edit), enable SSE:

const client = new SplitLabClient({
  // ...
  realtimeUpdates: true,
  onConfigUpdate: () => {
    console.log('Config updated — flags and experiments may have changed');
  },
});

The SDK opens an EventSource connection to the API's SSE endpoint. When an experiment or flag is created, updated, or deleted, the server pushes a config_updated event and the SDK refreshes automatically. EventSource handles reconnection natively.

Analytics Auto-Capture

Every track() call is automatically enriched with contextual properties (in browser environments):

Browser context (captureContext: true):

  • pathname, hostname, referrer, search_params, page_title, hash
  • browser, browser_version, os, os_version, device_type (parsed from User-Agent)
  • screen_width, screen_height, viewport_width, viewport_height
  • language, timezone

UTM parameters (captureUtm: true):

  • utm_source, utm_medium, utm_campaign, utm_term, utm_content
  • Captured on first page load and persisted in sessionStorage across SPA navigations

Session tracking (trackSessions: true):

  • session_id — random ID that resets after 30 minutes of inactivity (configurable via sessionTimeout)

Device ID (persistDeviceId: true):

  • device_id — persistent random ID stored in localStorage, survives across sessions

Auto-pageview tracking (trackPageviews: true):

  • Wraps history.pushState, history.replaceState, and the popstate event
  • Fires a $pageview event on each SPA navigation with previous_url

Super properties (superProperties):

  • Key-value pairs attached to every event, overriding auto-captured values
  • Modify at runtime with setSuperProperties() and unsetSuperProperty()

Property merge order (last wins): browser context < UTM < session < device < super properties < user properties.

Resilient Initialization

If the config fetch fails during initialize() and no cached config exists, the SDK falls back to safe defaults instead of throwing:

  • All experiments return null (control)
  • All flags return false (off)
  • A warning is logged

If refresh() fails, the existing config is kept.

Direct Ingest URL

In development or when the ingest service runs on a separate host, use ingestUrl:

const client = new SplitLabClient({
  apiKey: 'ot_live_abc123',
  baseUrl: 'http://localhost:3001',    // API server
  ingestUrl: 'http://localhost:3002',  // Ingest server
});

In production behind a reverse proxy (Caddy, nginx), both services share one origin and ingestUrl is not needed.

API

client.initialize(): Promise<void>

Fetches config (or evaluation data), starts the auto-flush timer and config polling timer. If realtimeUpdates is enabled, opens the SSE connection.

client.getVariant(experimentKey): string | null

Returns the assigned variant key, or null if excluded. < 1ms with local evaluation.

client.isFeatureEnabled(flagKey): boolean

Returns whether the feature flag is enabled for this user. < 1ms with local evaluation.

client.track(eventName, properties?): Promise<void>

Queues an event (auto-enriched with context). Auto-flushes when queue reaches autoFlushSize.

client.trackPageview(properties?): Promise<void>

Shorthand for track('$pageview', properties).

client.flush(): Promise<void>

Sends all queued events to the ingest service.

client.identify(distinctId, properties?): Promise<void>

Links the anonymous device to a user ID. Fires a $identify event with previous_distinct_id. Experiment assignments are unchanged (bucketed on device ID). Future events are attributed to the new user ID.

client.getDistinctId(): string

Returns the current distinct ID (user ID if identified, device ID otherwise).

client.getDeviceId(): string

Returns the stable device ID used for experiment bucketing.

client.refresh(): Promise<void>

Re-fetches config from the server. Uses ETag — returns immediately on 304.

client.setAttributes(attributes): void

Merges attributes for targeting rule evaluation.

client.setSuperProperties(props): void

Sets properties attached to every subsequent event.

client.unsetSuperProperty(key): void

Removes a super property by key.

client.destroy(): Promise<void>

Flushes remaining events, stops all timers, closes SSE connection.

React

For React bindings (provider + hooks), see @splitlab/react.