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

@supashiphq/javascript-sdk

v0.7.12

Published

JavaScript SDK for Supaship

Readme

Supaship JavaScript SDK

A type-safe JavaScript SDK for Supaship that provides a simple way to manage feature flags in your JavaScript applications.

Installation

npm install @supashiphq/javascript-sdk
# or
yarn add @supashiphq/javascript-sdk
# or
pnpm add @supashiphq/javascript-sdk

Quick Start

import { SupaClient, FeaturesWithFallbacks } from '@supashiphq/javascript-sdk'

// Define your features with fallback values
const features = {
  'new-ui': false,
  'premium-features': true,
  'theme-config': {
    primaryColor: '#007bff',
    darkMode: false,
  },
} satisfies FeaturesWithFallbacks

// Create client with features
const client = new SupaClient({
  apiKey: 'your-api-key',
  environment: 'production',
  features,
  context: {
    userID: '123',
    email: '[email protected]',
    plan: 'premium',
  },
})

// Get a single feature flag (fully typed!)
const isNewUIEnabled = await client.getFeature('new-ui')
// Type: boolean

// Get theme configuration (fully typed!)
const themeConfig = await client.getFeature('theme-config')
// Type: { primaryColor: string; darkMode: boolean; }

// Get multiple features at once
const allFeatures = await client.getFeatures(['new-ui', 'premium-features'])
// Type: Record<string, FeatureValue>

Type-Safe Features

The SDK provides type safety through the FeaturesWithFallbacks type. This ensures:

  • ✅ Features must be defined before use
  • ✅ Feature names are validated at compile-time
  • ✅ Feature values are properly typed
  • ✅ No typos in feature names

Defining Features

import { FeaturesWithFallbacks } from '@supashiphq/javascript-sdk'

// ✅ Recommended: satisfies (preserves exact literal types)
const features = {
  // Boolean flags
  'dark-mode': false,
  'beta-access': true,

  // Object configurations
  'ui-config': {
    theme: 'light' as const, // Preserves 'light' literal
    maxItems: 100,
    enableAnimations: true,
  },

  // Arrays
  'allowed-regions': ['us-east', 'eu-west'],

  // Null for disabled/unset features
  'experimental-feature': null,
} satisfies FeaturesWithFallbacks

// ⚠️ Avoid: Type annotation (widens types, loses precision)
const features: FeaturesWithFallbacks = {
  'dark-mode': false,
  'ui-config': {
    theme: 'light', // Widened to string, not 'light' literal
    maxItems: 100,
  },
}

const client = new SupaClient({
  apiKey: 'your-api-key',
  environment: 'production',
  features,
  context: {},
})

// ✅ TypeScript knows 'dark-mode' exists and returns boolean
const darkMode = await client.getFeature('dark-mode')

// ❌ TypeScript error: 'unknown-feature' doesn't exist
const value = await client.getFeature('unknown-feature')

API Reference

SupaClient

Constructor

new SupaClient(config: SupaClientConfig)

Configuration Options:

| Option | Type | Required | Description | | --------------- | ----------------------- | -------- | --------------------------------------------------------------- | | apiKey | string | Yes | Your Supaship API key (Project Settings -> API Keys) | | environment | string | Yes | Environment slug (e.g., production, staging, development) | | features | FeaturesWithFallbacks | Yes | Feature definitions with fallback values | | context | FeatureContext | Yes | Default context for feature evaluation | | networkConfig | NetworkConfig | No | Network settings (endpoints, retry, timeout, custom fetch) | | plugins | SupaPlugin[] | No | Plugins for observability, caching, etc. |

Feature Context:

| Field | Type | Description | | --------------- | ---------------------------------- | ------------------------------------------- | | [key: string] | string number boolean null | Key value pairs for feature flag evaluation |

Network Configuration:

| Field | Type | Required | Default | Description | | ------------------ | ---------------------------------------------------------------------- | -------- | --------------------------------------- | ------------------------------------------------------------- | | featuresAPIUrl | string | No | https://edge.supaship.com/v1/features | Override features API URL | | eventsAPIUrl | string | No | https://edge.supaship.com/v1/events | Override events/analytics API URL | | requestTimeoutMs | number | No | 10000 | Abort requests after N ms (uses AbortController if available) | | fetchFn | (input: RequestInfo \| URL, init?: RequestInit) => Promise<Response> | No | — | Custom fetch (pass in Node < 18 or specialized runtimes) | | retry | RetryConfig | No | see below | Retry behavior for network requests |

Retry (networkConfig.retry):

| Field | Type | Required | Default | Description | | ------------- | --------- | -------- | ------- | -------------------------------------- | | enabled | boolean | No | true | Enable/disable retries | | maxAttempts | number | No | 3 | Maximum retry attempts | | backoff | number | No | 1000 | Base backoff delay in ms (exponential) |

Methods

getFeature()

Retrieves a single feature flag value with full TypeScript type safety.

getFeature<TKey extends keyof TFeatures>(
  featureName: TKey,
  options?: { context?: FeatureContext }
): Promise<TFeatures[TKey]>

Parameters:

  • featureName: The name of the feature flag (must be defined in your features config)
  • options.context: Context override for this request

Examples:

const features = {
  'dark-mode': false,
  'theme-config': { primary: '#007bff' },
} satisfies FeaturesWithFallbacks

const client = new SupaClient({
  apiKey: 'key',
  environment: 'production',
  features,
  context: {},
})

// Get boolean feature
const darkMode = await client.getFeature('dark-mode')
// Type: boolean

// Get object feature
const theme = await client.getFeature('theme-config')
// Type: { primary: string }

// With context override
const darkMode = await client.getFeature('dark-mode', {
  context: { userID: '123', plan: 'premium' },
})
getFeatures()

Retrieves multiple feature flags in a single request.

getFeatures(
  featureNames: (keyof TFeatures)[],
  options?: { context?: FeatureContext }
): Promise<Record<string, FeatureValue>>

Parameters:

  • featureNames: Array of feature flag names (must be defined in your features config)
  • options.context: Context override for this request

Examples:

const features = {
  'new-ui': false,
  'premium-content': false,
  'beta-mode': false,
} satisfies FeaturesWithFallbacks

const client = new SupaClient({
  apiKey: 'key',
  environment: 'prod',
  features,
  context: {},
})

// Get multiple features
const results = await client.getFeatures(['new-ui', 'premium-content'])
// { 'new-ui': true, 'premium-content': false }

// With context override
const results = await client.getFeatures(['new-ui', 'beta-mode'], {
  context: { userID: '123', plan: 'premium' },
})
updateContext()

Updates the default context for the client.

updateContext(context: FeatureContext, mergeWithExisting?: boolean): void

Parameters:

  • context: New context data
  • mergeWithExisting: Whether to merge with existing context (default: true)

Examples:

// Merge with existing context
client.updateContext({ userID: '456' })

// Replace entire context
client.updateContext({ userID: '456', newField: 'value' }, false)
getContext()

Retrieves the current default context.

getContext(): FeatureContext | undefined

Types

FeatureValue

Supported feature flag value types:

type FeatureValue = boolean | null | Record<string, unknown> | unknown[]
  • boolean - Simple on/off flags
  • object - Structured configuration data (e.g., { theme: 'dark', size: 'large' })
  • array - Lists of values (e.g., ['feature-a', 'feature-b'])
  • null - Disabled or unset state

Note: Strings and numbers are not supported as standalone feature values. Use objects or arrays for complex data.

FeatureContext

Context object for feature evaluation:

interface FeatureContext {
  [key: string]: string | number | boolean | null | undefined
}

Common context properties:

  • userID: User identifier
  • email: User email
  • plan: Membership plan (e.g., 'premium', 'free')
  • version: Application version (e.g., 1.0.0)

Note: The above are just common examples. You can use any properties in your context object that make sense for your application's feature targeting needs.

Best Practices

1. Define Features Centrally

// features.ts
import { FeaturesWithFallbacks } from '@supashiphq/javascript-sdk'

export const features = {
  'new-dashboard': false,
  'premium-features': false,
  'ui-settings': {
    theme: 'light' as const,
    sidebarCollapsed: false,
  },
  'enabled-regions': ['us', 'eu'],
} satisfies FeaturesWithFallbacks

// client.ts
import { SupaClient } from '@supashiphq/javascript-sdk'
import { features } from './features'

export const client = new SupaClient({
  apiKey: process.env.SUPASHIP_API_KEY!,
  environment: process.env.ENVIRONMENT!,
  features,
  context: {},
})

2. Use satisfies for Type Safety

Always use satisfies instead of type annotations to preserve literal types:

// ✅ Good - preserves literal types
const features = {
  'dark-mode': false,
  config: {
    maxItems: 50,
    theme: 'light' as const,
  },
} satisfies FeaturesWithFallbacks

// ❌ Bad - loses literal types
const features: FeaturesWithFallbacks = {
  'dark-mode': false,
  config: {
    maxItems: 50,
    theme: 'light', // Widened to string
  },
}

const client = new SupaClient({
  apiKey: 'key',
  environment: 'prod',
  features,
  context: {},
})

// With satisfies: TypeScript knows the exact literal type
const config = await client.getFeature('config')
// Type: { maxItems: number; theme: 'light'; }

const maxItems: number = config.maxItems // ✅ Type-safe
const theme = config.theme // ✅ Type-safe, inferred as 'light' literal

3. Use Context for Targeting

const client = new SupaClient({
  apiKey: 'your-api-key',
  environment: 'production',
  features,
  context: {
    userID: user.id,
    email: user.email,
    plan: user.subscriptionPlan,
    version: process.env.APP_VERSION!,
  },
})

// Update context when user state changes
function onUserLogin(user) {
  client.updateContext({
    userID: user.id,
    email: user.email,
    plan: user.plan,
  })
}

4. Batch Feature Requests

// ✅ Good - single API call
const results = await client.getFeatures(['feature-1', 'feature-2', 'feature-3'])

// ❌ Less efficient - multiple API calls
const feature1 = await client.getFeature('feature-1')
const feature2 = await client.getFeature('feature-2')
const feature3 = await client.getFeature('feature-3')

Plugin System

The SDK supports plugins for observability, caching, logging, and more.

Built-in Plugins

Toolbar Plugin

Visual toolbar for local development and testing.

✨ Auto-enabled in browser environments! The toolbar is automatically enabled in browsers with enabled: 'auto' (shows only on localhost). No manual configuration needed!

import { SupaClient, FeaturesWithFallbacks } from '@supashiphq/javascript-sdk'

const features = {
  'new-ui': false,
  premium: false,
} satisfies FeaturesWithFallbacks

// ✅ Automatic (recommended) - Toolbar auto-enabled in browser with 'auto' mode
const client = new SupaClient({
  apiKey: 'your-api-key',
  environment: 'development',
  features,
  context: {},
})

// 🎨 Custom configuration
const client = new SupaClient({
  apiKey: 'your-api-key',
  environment: 'development',
  features,
  context: {},
  toolbar: {
    enabled: true, // Always show
    position: {
      placement: 'bottom-right',
      offset: { x: '1rem', y: '1rem' },
    },
  },
})

// ❌ Opt-out (disable toolbar)
const client = new SupaClient({
  apiKey: 'your-api-key',
  environment: 'production',
  features,
  context: {},
  toolbar: false,
})

Features:

  • Auto-enabled in browser - No configuration needed!
  • 🎯 Visual interface showing all configured feature flags
  • 🔄 Override feature flag values locally
  • 💾 Persistent storage in localStorage
  • 🎨 Customizable position
  • 🏠 Smart detection (shows only on localhost by default)
  • 🚫 Easy opt-out with toolbar: false

Examples

React Integration

For React applications, use our dedicated React SDK which provides hooks and components optimized for React:

📦 @supashiphq/react-sdk

Node.js Server

import express from 'express'
import { SupaClient, FeaturesWithFallbacks } from '@supashiphq/javascript-sdk'

const features = {
  'new-api': false,
  'cache-enabled': true,
  'rate-limit': { maxRequests: 100, windowMs: 60000 },
} satisfies FeaturesWithFallbacks

const featureClient = new SupaClient({
  apiKey: process.env.SUPASHIP_API_KEY!,
  environment: 'production',
  features,
  context: {},
})

const app = express()

app.get('/api/config', async (req, res) => {
  const rateLimit = await featureClient.getFeature('rate-limit')

  res.json({
    rateLimit: rateLimit,
    newApiEnabled: await featureClient.getFeature('new-api'),
  })
})

app.get('/api/user-features/:userId', async (req, res) => {
  const results = await featureClient.getFeatures(['new-api', 'cache-enabled'], {
    context: {
      userID: req.params.userId,
      plan: req.user.plan,
      region: req.headers['cloudfront-viewer-country'],
    },
  })

  res.json(results)
})

Vue Integration

// plugins/feature-flags.ts
import { SupaClient, FeaturesWithFallbacks } from '@supashiphq/javascript-sdk'

const features = {
  'new-nav': false,
  'dark-mode': false,
  'premium-content': false,
} satisfies FeaturesWithFallbacks

const client = new SupaClient({
  apiKey: import.meta.env.VITE_SUPASHIP_API_KEY,
  environment: 'production',
  features,
  context: {},
})

export default {
  install(app: App) {
    app.config.globalProperties.$featureFlags = client
    app.provide('featureFlags', client)
  }
}

// components/MyComponent.vue
<script setup lang="ts">
import { inject, ref, onMounted } from 'vue'
import type { SupaClient } from '@supashiphq/javascript-sdk'

const featureFlags = inject<SupaClient>('featureFlags')!
const showNewNav = ref(false)
const darkMode = ref(false)

onMounted(async () => {
  showNewNav.value = await featureFlags.getFeature('new-nav')
  darkMode.value = await featureFlags.getFeature('dark-mode')
})
</script>

<template>
  <div :class="{ dark: darkMode }">
    <NewNav v-if="showNewNav" />
    <OldNav v-else />
  </div>
</template>

Angular Integration

// feature-flag.service.ts
import { Injectable } from '@angular/core'
import { SupaClient, FeaturesWithFallbacks } from '@supashiphq/javascript-sdk'
import { environment } from '../environments/environment'

const features = {
  'new-dashboard': false,
  analytics: true,
  'theme-config': { mode: 'light' as const },
} satisfies FeaturesWithFallbacks

@Injectable({
  providedIn: 'root',
})
export class FeatureFlagService {
  private client: SupaClient

  constructor() {
    this.client = new SupaClient({
      apiKey: environment.SUPASHIP_API_KEY,
      environment: 'production',
      features,
      context: {
        userID: this.getCurrentUserId(),
        version: environment.version,
      },
    })
  }

  async getFeature(featureName: string): Promise<any> {
    return this.client.getFeature(featureName)
  }

  async getFeatures(featureNames: string[]): Promise<Record<string, any>> {
    return this.client.getFeatures(featureNames)
  }

  updateContext(context: Record<string, any>) {
    this.client.updateContext(context)
  }

  private getCurrentUserId(): string {
    return localStorage.getItem('userID') || 'anonymous'
  }
}

// app.component.ts
import { Component, OnInit } from '@angular/core'
import { FeatureFlagService } from './feature-flag.service'

@Component({
  selector: 'app-root',
  template: `
    <div>
      <new-dashboard *ngIf="showNewDashboard"></new-dashboard>
      <old-dashboard *ngIf="!showNewDashboard"></old-dashboard>
    </div>
  `,
})
export class AppComponent implements OnInit {
  showNewDashboard = false

  constructor(private featureFlags: FeatureFlagService) {}

  async ngOnInit() {
    this.showNewDashboard = await this.featureFlags.getFeature('new-dashboard')
  }
}

License

This project is licensed under the MIT License - see the LICENSE file for details.