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 🙏

© 2025 – Pkg Stats / Ryan Hefner

gradual-rollout-sdk

v0.1.16

Published

GradualRollout SDK is a lightweight, framework-agnostic JavaScript/TypeScript client for feature flagging and canary deployments. It enables instant, safe, and incremental feature rollouts by connecting to your GradualRollout backend API, evaluating user-

Downloads

48

Readme

GradualRollout SDK

A lightweight, framework-agnostic JavaScript and TypeScript SDK for feature flagging and canary deployments.
Enable safe, gradual feature rollouts with instant evaluation and real-time updates.


Features

  • Connects seamlessly with your GradualRollout backend API
  • Framework agnostic: use with React, Angular, Vue, or vanilla JS
  • Consistent user bucketing via hashing for percentage rollouts
  • Event-driven API with flagsUpdated, error, and initialized events
  • Automatic polling with configurable interval and manual refresh support
  • Simple to initialize and use with TypeScript support
  • Dynamic user identity support: works with anonymous users (anonId) before login and updates seamlessly post-login (userId)

Installation

npm install gradual-rollout-sdk

Usage

import { GradualRolloutSDK } from 'gradual-rollout-sdk';

const sdk = new GradualRolloutSDK({
  apiKey: 'your_api_key',
  userId: 'user_123',
  pollingIntervalMs: 30000, // optional, default 60000 (60s)
});

sdk.on('initialized', () => {
  console.log('SDK initialized');
});

sdk.on('flagsUpdated', (flags) => {
  console.log('Flags updated:', flags);
});

sdk.on('error', (error) => {
  console.error('SDK error:', error);
});

await sdk.init();

const isNewFeatureEnabled = sdk.isFeatureEnabled('new-feature');

if (isNewFeatureEnabled) {
  // Show new UI or enable feature
}

// Manually refresh flags anytime
await sdk.refreshFlags();

// When done with SDK (e.g., on app unload), clean up
sdk.destroy();

React Integration Example

1. Create a GradualRolloutProvider and useGradualRollout hook

import React, { createContext, useContext, useEffect, useMemo, useState, useCallback } from 'react';
import { GradualRolloutSDK, FeatureFlag } from 'gradual-rollout-sdk';
import { v4 as uuidv4 } from 'uuid';

interface GradualRolloutContextType {
  sdk: GradualRolloutSDK | null;
  flags: FeatureFlag[];
  isFeatureEnabled: (flagKey: string) => boolean;
  refreshFlags: () => Promise<void>;
  initialized: boolean;
  error: Error | null;
}

const GradualRolloutContext = createContext<GradualRolloutContextType | undefined>(undefined);

function useAnonId() {
  const [anonId, setAnonId] = useState<string | null>(null);
  useEffect(() => {
    let storedAnonId = localStorage.getItem('anon_id');
    if (!storedAnonId) {
      storedAnonId = uuidv4();
      localStorage.setItem('anon_id', storedAnonId);
    }
    setAnonId(storedAnonId);
  }, []);
  return anonId;
}

export const GradualRolloutProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
  const anonId = useAnonId();
  const [sdk, setSdk] = useState<GradualRolloutSDK | null>(null);
  const [flags, setFlags] = useState<FeatureFlag[]>([]);
  const [initialized, setInitialized] = useState(false);
  const [error, setError] = useState<Error | null>(null);

  useEffect(() => {
    if (!anonId) return;
    const instance = new GradualRolloutSDK({
      apiKey: 'YOUR_API_KEY', // Replace with your actual API key
      anonId,
      pollingIntervalMs: 60000,
    });
    setSdk(instance);
    const handleFlagsUpdated = (newFlags: FeatureFlag[]) => setFlags(newFlags);
    const handleInitialized = () => setInitialized(true);
    const handleError = (err: Error) => setError(err);
    instance.on('flagsUpdated', handleFlagsUpdated);
    instance.on('initialized', handleInitialized);
    instance.on('error', handleError);
    instance.init();
    return () => {
      instance.off('flagsUpdated', handleFlagsUpdated);
      instance.off('initialized', handleInitialized);
      instance.off('error', handleError);
      instance.destroy();
    };
  }, [anonId]);

  const isFeatureEnabled = useCallback(
    (flagKey: string) => (sdk ? sdk.isFeatureEnabled(flagKey) : false),
    [sdk]
  );

  const refreshFlags = useCallback(async () => {
    if (sdk) await sdk.refreshFlags();
  }, [sdk]);

  const value = useMemo(
    () => ({ sdk, flags, isFeatureEnabled, refreshFlags, initialized, error }),
    [sdk, flags, isFeatureEnabled, refreshFlags, initialized, error]
  );

  return (
    <GradualRolloutContext.Provider value={value}>
      {children}
    </GradualRolloutContext.Provider>
  );
};

export function useGradualRollout() {
  const context = useContext(GradualRolloutContext);
  if (context === undefined) {
    throw new Error('useGradualRollout must be used within a GradualRolloutProvider');
  }
  return context;
}

2. Wrap your app with the provider

import { GradualRolloutProvider } from './GradualRolloutProvider';

function App() {
  return (
    <GradualRolloutProvider>
      {/* your app components */}
    </GradualRolloutProvider>
  );
}

3. Use the hook in your components

import { useGradualRollout } from './GradualRolloutProvider';

function MyComponent() {
  const { flags, isFeatureEnabled, refreshFlags, initialized, error } = useGradualRollout();

  if (!initialized) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <div>
      <div>Flags: {JSON.stringify(flags)}</div>
      <div>Feature A enabled: {isFeatureEnabled('featureA') ? 'Yes' : 'No'}</div>
      <button onClick={refreshFlags}>Refresh Flags</button>
    </div>
  );
}

Multi-Framework Integration Guide

Extend your feature-flagging power beyond React! This guide walks you through integrating the GradualRollout SDK in Angular, Vue 3, Svelte, and Next.js. Each section covers:

  • Introduction
  • Installation & Setup
  • Integration & Initialization
  • Feature-Flag Usage Examples
  • Advanced Configuration
  • Troubleshooting & Common Issues
  • FAQs

Angular Integration

Introduction

The GradualRollout SDK brings safe, gradual feature rollouts to your Angular app.
Benefits:

  • Leverage Angular’s DI and lifecycle hooks for smooth initialization.
  • Centralized flag state via an injectable service.
  • Real-time updates via RxJS streams.

Installation & Setup

npm install gradual-rollout-sdk
# or
yarn add gradual-rollout-sdk

Peer dependencies:

  • Angular ≥11
  • RxJS (bundled with Angular)

Integration & Initialization

  1. Create an Angular service wrapper:
// src/app/services/rollout.service.ts
import { Injectable, OnDestroy } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { GradualRolloutSDK, FeatureFlag } from 'gradual-rollout-sdk';

@Injectable({ providedIn: 'root' })
export class RolloutService implements OnDestroy {
  private sdk = new GradualRolloutSDK({ apiKey: 'YOUR_API_KEY', anonId: this.getAnonId() });
  public flags$ = new BehaviorSubject<FeatureFlag[]>([]);
  public ready$ = new BehaviorSubject<boolean>(false);

  constructor() {
    this.sdk.on('flagsUpdated', flags => this.flags$.next(flags));
    this.sdk.on('initialized', () => this.ready$.next(true));
    this.sdk.on('error', err => console.error('[Rollout]', err));
    this.sdk.init();
  }

  private getAnonId(): string {
    return localStorage.getItem('anon_id') || crypto.randomUUID();
  }

  isFeatureEnabled(key: string): boolean {
    return this.sdk.isFeatureEnabled(key);
  }

  refresh(): Promise<void> {
    return this.sdk.refreshFlags();
  }

  ngOnDestroy() {
    this.sdk.destroy();
  }
}
  1. Inject and use in components:
// src/app/app.component.ts
import { Component } from '@angular/core';
import { RolloutService } from './services/rollout.service';

@Component({
  selector: 'app-root',
  template: `
    <div *ngIf="!(rollout.ready$ | async)">Loading flags…</div>
    <div *ngIf="rollout.isFeatureEnabled('new-ui')">
      <!-- new UI here -->
    </div>
    <button (click)="rollout.refresh()">Refresh Flags</button>
  `
})
export class AppComponent {
  constructor(public rollout: RolloutService) {}
}

Feature-Flag Usage Examples

  • Toggle UI elements:
    *ngIf="rollout.isFeatureEnabled('beta-banner')"
  • Reactive consumption:
    rollout.flags$.subscribe(flags => /* update local state */)

Advanced Configuration

  • Environment targeting: pass environment: 'staging' to the SDK config.
  • Demo users: supply demoUserIds: ['[email protected]'] for 100% visibility.
  • Custom endpoints: override apiBaseUrl.

Troubleshooting & Common Issues

  • “SDK not initialized”: Ensure you wait on ready$ before checking flags.
  • CORS errors: Confirm your backend allows X-API-KEY header.
  • Duplicate anonId: Clear localStorage.anon_id when switching projects.

FAQs

Q: Can I lazy-load the SDK?
A: Yes—inject your service only in lazy modules to defer initialization.
Q: How to switch user identity after login?
A: Call sdk.setIdentity(userId, anonId) in your service and it auto-refreshes.
Q: Does polling impact performance?
A: Default is 60 s. You can disable by omitting pollingIntervalMs.


Vue 3 Integration (Composition API)

Introduction

Vue 3’s Composition API and reactivity pairs perfectly with GradualRollout:

  • Reactive ref or computed flags.
  • Plugin architecture for global access.

Installation & Setup

npm install gradual-rollout-sdk
# or
yarn add gradual-rollout-sdk

Integration & Initialization

  1. Create a plugin:
// src/plugins/rollout.ts
import { App, reactive } from 'vue';
import { GradualRolloutSDK, FeatureFlag } from 'gradual-rollout-sdk';

export interface RolloutState {
  ready: boolean;
  flags: FeatureFlag[];
}
export const rolloutState = reactive<RolloutState>({ ready: false, flags: [] });

export default {
  install(app: App) {
    const sdk = new GradualRolloutSDK({ apiKey: 'YOUR_API_KEY', anonId: crypto.randomUUID() });
    sdk.on('initialized', () => (rolloutState.ready = true));
    sdk.on('flagsUpdated', f => (rolloutState.flags = f));
    sdk.on('error', console.error);
    sdk.init();
    app.config.globalProperties.$rollout = sdk;
  }
};
  1. Register in main.ts:
import { createApp } from 'vue';
import App from './App.vue';
import rolloutPlugin from './plugins/rollout';

createApp(App).use(rolloutPlugin).mount('#app');
  1. Use in components:
<template>
  <div v-if="!state.ready">Loading…</div>
  <div v-else>
    <div v-if="isEnabled('new-feature')">🎉 New Feature!</div>
    <button @click="refresh">Refresh Flags</button>
  </div>
</template>

<script setup lang="ts">
import { computed } from 'vue';
import { rolloutState as state } from '@/plugins/rollout';
const sdk = getCurrentInstance()!.appContext.config.globalProperties.$rollout;

function isEnabled(key: string) {
  return sdk.isFeatureEnabled(key);
}
async function refresh() {
  await sdk.refreshFlags();
}
</script>

Advanced Configuration

  • Provide demoUserIds or environment in plugin options.
  • Swap apiBaseUrl for self-hosted endpoints.

Troubleshooting & Common Issues

  • Plugin not applied: Confirm .use(rolloutPlugin) runs before .mount().
  • Reactivity not updating: Always update a reactive property (e.g., rolloutState.flags = …).

FAQs

Q: How to access flags outside components?
A: Import rolloutState from your plugin file.
Q: Can I disable polling?
A: Pass pollingIntervalMs: 0 in SDK config.


Svelte Integration

Introduction

Svelte’s stores and automatic reactivity make feature-flag integration a breeze.

  • Leverage writable stores for flag updates.
  • Minimal boilerplate and straightforward reactive statements.

Installation & Setup

npm install gradual-rollout-sdk
# or
yarn add gradual-rollout-sdk

Integration & Initialization

  1. Create a store:
// src/lib/rollout.ts
import { writable } from 'svelte/store';
import { GradualRolloutSDK, FeatureFlag } from 'gradual-rollout-sdk';

export const flags = writable<FeatureFlag[]>([]);
export const ready = writable(false);
export const error = writable<Error | null>(null);

const sdk = new GradualRolloutSDK({ apiKey: 'YOUR_API_KEY', anonId: crypto.randomUUID() });
sdk.on('initialized', () => ready.set(true));
sdk.on('flagsUpdated', f => flags.set(f));
sdk.on('error', e => error.set(e));
sdk.init();

export function isFeatureEnabled(key: string) {
  return sdk.isFeatureEnabled(key);
}

export function refreshFlags() {
  return sdk.refreshFlags();
}

export function setIdentity(userId?: string, anonId?: string) {
  sdk.setIdentity(userId, anonId);
}
  1. Use in components:
<script lang="ts">
  import { flags, ready, isFeatureEnabled, refreshFlags } from '$lib/rollout';

  let isNewFeatureEnabled = false;
  $: isNewFeatureEnabled = isFeatureEnabled('new-feature');

  async function handleRefresh() {
    await refreshFlags();
  }
</script>

{#if $ready}
  <div>
    {#if isNewFeatureEnabled}
      <p>🎉 New feature is enabled!</p>
    {/if}
    <button on:click={handleRefresh}>Refresh Flags</button>
  </div>
{:else}
  <p>Loading feature flags...</p>
{/if}

Advanced Configuration

  • Pass environment or demoUserIds in SDK options.
  • Override apiBaseUrl for custom backends.

Troubleshooting & Common Issues

  • Flags not updating: Ensure you’re using the $ prefix to subscribe to stores.
  • CORS issues: Check your browser’s console for CORS-related errors.

FAQs

Q: How to test different user roles?
A: Use setIdentity('user_id', 'anon_id') with known IDs.
Q: Can I use with SvelteKit?
A: Yes, just ensure the store is created in a module context (not in a component).


API

Constructor: new GradualRolloutSDK(config)

Creates a new SDK instance.

| Option | Type | Required | Description | |------------------|----------|----------|--------------------------------------------| | apiKey | string | Yes | API key for authenticating calls | | userId | string | Cond. | Unique identifier for logged-in user | | anonId | string | Cond. | Unique identifier for anonymous visitor | | pollingIntervalMs| number | No | Interval in ms for auto polling | | apiBaseUrl | string | No | Backend API base URL |


Methods

  • init(): Promise<void>
    Initializes the SDK, fetches flags, and starts polling if enabled.

  • isFeatureEnabled(flagKey: string): boolean
    Returns true if the feature flag is enabled for the current user.

  • refreshFlags(): Promise<void>
    Manually fetches the latest flags from the backend.

  • setIdentity(userId?: string, anonId?: string): void
    Dynamically updates the user identity and refreshes flags accordingly.

  • on(event: string, handler: Function)
    Subscribes to events: flagsUpdated, error, initialized.

  • off(event: string, handler: Function)
    Unsubscribes a handler from an event.

  • destroy(): void
    Cleans up timers and event listeners (stop polling).


Events

| Event | Payload | Description | |--------------|----------------|----------------------------------------------| | flagsUpdated | FeatureFlag[] | Emitted whenever flags are fetched or updated| | error | Error | Emitted on API or internal errors | | initialized | void | Emitted once when SDK finishes initialization|


Dynamic User Identity Support

This SDK introduces support for dynamic user identity updates, enabling:

  • Seamless A/B testing before and after login without losing rollout consistency
  • Anonymous user bucketing using anonId
  • Smooth transition to logged-in userId after authentication

Why this is a big improvement

Traditional rollout systems often required:

  • Backend config updates with manual user assignments
  • New client versions to control phased rollouts

Our SDK simplifies this by:

  • Using stateless, hash-based bucketing
  • Allowing runtime identity updates without app reloads

Contributing

Contributions are welcome! Please open issues or pull requests on GitHub.


License

MIT © GradualRollout