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

@moonspot/phlag-client

v0.2.0

Published

JavaScript/TypeScript client library for the Phlag feature flag management system

Readme

Phlag Client

CI npm version License

JavaScript/TypeScript client library for the Phlag feature flag management system

This library provides a simple, type-safe interface for querying feature flags from a Phlag server. It handles authentication, environment management, error handling, and caching so you can focus on feature rollouts.

Features

  • 🎯 Type-safe flag retrieval - Get boolean, number, or string values with full TypeScript support
  • 🌐 Environment-aware - Configure once, query a specific environment
  • 🔄 Multi-environment fallback - Query multiple environments with automatic fallback (v0.2.0+)
  • 🔄 Immutable environment switching - Easy multi-environment queries
  • Simple API - Clean, fluent interface with convenience methods
  • 🛡️ Robust error handling - Specific exceptions for different error conditions
  • 💾 Built-in caching - Optional file-based (Node.js) and in-memory caching
  • Fully tested - Comprehensive test coverage with Vitest

Requirements

  • Node.js 18.0.0 or higher
  • A running Phlag server instance

Installation

npm install @moonspot/phlag-client

Quick Start

import { PhlagClient } from '@moonspot/phlag-client';

// Create a client for a specific environment
const client = new PhlagClient({
  baseUrl: 'http://localhost:8000',
  apiKey: 'your-64-character-api-key',
  environment: 'production',
});

// Check if a feature is enabled
if (await client.isEnabled('feature_checkout')) {
  // Show the new checkout flow
}

// Get typed configuration values
const maxItems = await client.getFlag('max_items'); // returns number or null
const priceMultiplier = await client.getFlag('price_multiplier'); // returns number or null
const welcomeMessage = await client.getFlag('welcome_message'); // returns string or null

Examples

The examples/ directory contains practical usage examples:

  • basic.js/ts - Simple feature flag checks
  • multi-environment.js - Querying flags across environments
  • caching.js - Performance optimization with caching
  • cache-warming.js - Preloading cache at startup
  • error-handling.js - Handling different error types
  • configuration.js - Advanced configuration options

See examples/README.md for details on running examples.

Performance & Caching

For high-traffic applications, enable caching to dramatically reduce API calls:

const client = new PhlagClient({
  baseUrl: 'http://localhost:8000',
  apiKey: 'your-api-key',
  environment: 'production',
  cache: true,           // Enable caching
  cacheTtl: 300,         // Cache for 5 minutes (default)
});

// First call fetches all flags from API (1 request)
const enabled = await client.isEnabled('feature_checkout');

// Subsequent calls use cached data (0 requests)
const max = await client.getFlag('max_items');
const price = await client.getFlag('price_multiplier');

How Caching Works

When caching is enabled:

  1. First request: Client fetches ALL flags for the environment via /all-flags endpoint
  2. Cache storage: Flags stored in memory AND persisted to disk (Node.js)
  3. Subsequent requests: Served from in-memory cache (no API calls)
  4. Cache expiration: After TTL expires, next request refreshes from API
  5. Cross-request persistence: Cache file survives between Node.js process restarts

Cache Management

Warming the cache (preload before first request):

await client.warmCache();  // Immediately fetches and caches all flags

Clearing the cache (force fresh fetch):

await client.clearCache();  // Removes cache file and in-memory data

// Next request will fetch fresh from API
const value = await client.getFlag('feature');

Checking cache status:

if (client.isCacheEnabled()) {
  console.log('Cache file:', client.getCacheFile());
  console.log('TTL:', client.getCacheTtl(), 'seconds');
}

When to Use Caching

✅ Good use cases:

  • High-traffic applications with frequent flag checks
  • Flags that change infrequently (hourly, daily)
  • Reducing API load and network latency
  • Improving response times (sub-millisecond flag checks)

❌ When to avoid caching:

  • You need real-time flag updates (seconds matter)
  • Flags change very frequently
  • Low-traffic applications (caching overhead not worth it)
  • Single flag check per request

Performance Impact

Without caching:

  • API calls: N (one per getFlag() call)
  • Network overhead: ~10-50ms per call
  • Total overhead: N × 10-50ms

With caching:

  • API calls: 1 per TTL period (default 5 minutes)
  • First request: ~10-50ms (fetch all flags)
  • Subsequent requests: <1ms (memory lookup)
  • Cache file I/O: ~1-2ms on first load per process

Example savings (100 flag checks per request, 1000 requests/minute):

  • Without cache: 100,000 API calls/minute
  • With cache (300s TTL): ~20 API calls/minute (99.98% reduction)

API Reference

PhlagClient

constructor(options: PhlagClientOptions)

Creates a new client instance.

Parameters:

  • options.baseUrl - Base URL of your Phlag server (e.g., http://localhost:8000)
  • options.apiKey - 64-character API key from the Phlag admin panel
  • options.environment - Environment name or array of environments for fallback (e.g., 'production' or ['my-branch', 'staging'])
  • options.timeout - Request timeout in milliseconds (default: 10000)
  • options.cache - Enable caching (default: false)
  • options.cacheFile - Custom cache file path (default: auto-generated in temp dir)
  • options.cacheTtl - Cache time-to-live in seconds (default: 300)

async getFlag(name: string): Promise<FlagValue>

Retrieves a flag value.

Returns: The flag value (boolean, number, string, or null)

Throws:

  • AuthenticationError - Invalid API key
  • InvalidFlagError - Flag doesn't exist (cache disabled only)
  • InvalidEnvironmentError - Environment doesn't exist
  • NetworkError - Network communication failed
  • PhlagError - Other errors

async isEnabled(name: string): Promise<boolean>

Convenience method for checking SWITCH flags.

Returns: true if the flag value is boolean true, false otherwise

getEnvironment(): string[]

Gets the current environment(s) as an array.

Returns: Always returns an array, even for single environments

  • Single environment: ['production']
  • Multiple environments: ['my-branch', 'staging', 'development']

Note: In v1.x this returned a string. See migration notes below.

withEnvironment(environment: string | string[]): PhlagClient

Creates a new client for a different environment or environment chain (immutable pattern).

async warmCache(): Promise<void>

Preloads the flag cache immediately. No-op if caching is disabled.

async clearCache(): Promise<void>

Clears in-memory and file cache. No-op if caching is disabled.

isCacheEnabled(): boolean

Checks if caching is enabled.

getCacheFile(): string

Gets the cache file path (even if file doesn't exist yet).

getCacheTtl(): number

Gets the cache TTL in seconds.

Error Handling

The client throws specific exceptions for different error conditions:

import {
  PhlagError,
  AuthenticationError,
  InvalidFlagError,
  NetworkError,
} from '@moonspot/phlag-client';

try {
  const value = await client.getFlag('my_flag');
} catch (error) {
  if (error instanceof AuthenticationError) {
    // Invalid API key (401)
    console.error('Bad API key:', error.message);
  } else if (error instanceof InvalidFlagError) {
    // Flag doesn't exist (404) - only when cache disabled
    console.error('Flag not found:', error.message);
  } else if (error instanceof NetworkError) {
    // Connection failed, timeout, etc.
    console.error('Network error:', error.message);
  } else if (error instanceof PhlagError) {
    // Other errors
    console.error('Phlag error:', error.message);
  }
}

All exceptions extend PhlagError, so you can catch them all with a single block if needed.

Multi-Environment Fallback

New in v0.2.0: Query multiple environments with automatic fallback. Perfect for dev/QA workflows where you want to test branch-specific flags with fallback to shared environments.

How it Works

When you configure multiple environments, the client queries them in order and returns the first non-null value:

const client = new PhlagClient({
  baseUrl: 'http://phlag.example.com',
  apiKey: 'your-api-key',
  environment: ['my-feature-branch', 'staging', 'development'],
});

// Queries environments in order:
// 1. Check 'my-feature-branch' first
// 2. If null, check 'staging'
// 3. If still null, check 'development'
const value = await client.getFlag('new_checkout_flow');

Important: Only null triggers the fallback. Valid values like false, 0, or "" will NOT fall through:

// Example flag states across environments:
// my-feature-branch: null (flag not set)
// staging: false (flag explicitly disabled)
// development: true (flag enabled)

// Result: false (from staging, stops there)
const enabled = await client.isEnabled('feature_beta');

Use Cases

Branch-specific development:

// Test your branch's flags, fall back to staging for unset flags
const client = new PhlagClient({
  baseUrl: 'http://localhost:8000',
  apiKey: 'dev-api-key',
  environment: ['feature-new-checkout', 'staging'],
});

QA testing with overrides:

// QA environment with fallback to production
const client = new PhlagClient({
  baseUrl: 'http://phlag.example.com',
  apiKey: 'api-key',
  environment: ['qa-overrides', 'production'],
  cache: true,
});

Gradual rollout testing:

// Test canary deployment flags with production fallback
const client = new PhlagClient({
  baseUrl: 'http://phlag.example.com',
  apiKey: 'api-key',
  environment: ['canary', 'production'],
});

Performance with Multiple Environments

Without caching:

  • Queries environments in parallel using Promise.all()
  • Returns as soon as first non-null value found
  • Network overhead: max(environment response times)

With caching:

  • Fetches ALL environments' flags in parallel on first request
  • Merges caches with first environment taking precedence
  • Subsequent requests: <1ms (memory lookup)
const client = new PhlagClient({
  baseUrl: 'http://phlag.example.com',
  apiKey: 'api-key',
  environment: ['branch-123', 'staging', 'dev'],
  cache: true,
  cacheTtl: 300,
});

// First request: Fetches all 3 environments in parallel (1 round trip)
const value1 = await client.getFlag('feature_a');

// Subsequent requests: Pure memory lookup
const value2 = await client.getFlag('feature_b');

Checking Current Environment(s)

const client = new PhlagClient({
  baseUrl: 'http://phlag.example.com',
  apiKey: 'api-key',
  environment: ['my-branch', 'staging'],
});

// v0.2.0: Always returns array
console.log(client.getEnvironment());  // ['my-branch', 'staging']

// Single environment still returns array
const prodClient = new PhlagClient({
  baseUrl: 'http://phlag.example.com',
  apiKey: 'api-key',
  environment: 'production',
});

console.log(prodClient.getEnvironment());  // ['production']

Working with Multiple Environments

You can switch environments without creating new client instances:

const prodClient = new PhlagClient({
  baseUrl: 'http://phlag.example.com',
  apiKey: 'your-api-key',
  environment: 'production',
  cache: true,
});

// Create a new client for staging (immutable pattern)
const stagingClient = prodClient.withEnvironment('staging');

// Each has its own cache
console.log(prodClient.getEnvironment());    // ['production']
console.log(stagingClient.getEnvironment()); // ['staging']

// Query both environments
const prodEnabled = await prodClient.isEnabled('feature_beta');
const stagingEnabled = await stagingClient.isEnabled('feature_beta');

// Switch to multi-environment fallback
const devClient = prodClient.withEnvironment(['my-branch', 'staging', 'dev']);
console.log(devClient.getEnvironment());  // ['my-branch', 'staging', 'dev']

Migration from v0.1.x to v0.2.0

Breaking Change: getEnvironment() Return Type

v1.x:

const env = client.getEnvironment();  // Returns: 'production' (string)

v0.2.0:

const env = client.getEnvironment();  // Returns: ['production'] (string[])

Migration:

If you're checking the environment name:

// v1.x
if (client.getEnvironment() === 'production') { ... }

// v0.2.0 - Option 1: Check first element
if (client.getEnvironment()[0] === 'production') { ... }

// v0.2.0 - Option 2: Check if array includes value
if (client.getEnvironment().includes('production')) { ... }

If you're logging the environment:

// v1.x
console.log('Environment:', client.getEnvironment());  // "production"

// v0.2.0
console.log('Environment:', client.getEnvironment());  // ["production"]
console.log('Environment:', client.getEnvironment().join(', '));  // "production"

New Features in v0.2.0

  • ✅ Multi-environment fallback support
  • ✅ Parallel environment queries for better performance
  • withEnvironment() now accepts string arrays
  • ✅ Improved caching for multi-environment scenarios

Development

Building

npm run build

Testing

npm test

Project Status

This library is production-ready and actively maintained:

Version 0.2.0 (Current)

  • ✅ Multi-environment fallback support
  • ✅ Parallel environment queries
  • ✅ Enhanced caching for multiple environments
  • ✅ Breaking change: getEnvironment() returns string[]

Version 1.0.0

  • ✅ TypeScript project setup
  • ✅ HTTP client with fetch API
  • ✅ Error handling with specific exception types
  • ✅ PhlagClient with getFlag() and isEnabled()
  • ✅ In-memory & file-based caching (Node.js)
  • ✅ Cache management methods
  • ✅ Comprehensive test suite
  • ✅ TypeScript type definitions

Troubleshooting

Stale Cache Data

If you're seeing outdated flag values when caching is enabled:

Problem: Cache hasn't expired yet
Solutions:

  1. Use shorter TTL: new PhlagClient({ ..., cache: true, cacheTtl: 60 })
  2. Manually clear: await client.clearCache()
  3. Disable caching if you need real-time updates

Cache File Permissions

If cache files aren't being created:

Problem: No write permission to temp directory or custom cache path
Solution: Ensure Node.js has write access to the cache directory

const cacheFile = client.getCacheFile();
const cacheDir = require('path').dirname(cacheFile);
console.log('Cache directory:', cacheDir);

Note: Cache write failures are logged but don't throw exceptions. The client gracefully degrades to in-memory-only caching.

License

BSD 3-Clause License

Copyright (c) 2025, Brian Moon

Development

This project uses ESLint for linting and Prettier for code formatting. See DEVELOPMENT.md for detailed development setup, available scripts, and workflow guidelines.

Quick commands:

  • npm run build - Compile TypeScript
  • npm test - Run test suite
  • npm run format - Format code with Prettier
  • npm run lint - Check code with ESLint (requires npm install)

Contributing

We welcome contributions! Please see CONTRIBUTING.md for guidelines on:

  • Setting up your development environment
  • Coding standards and style guide
  • Testing requirements
  • Pull request process

Credits

Built by Brian Moon ([email protected])

Support

For bugs and feature requests, please use the GitHub issue tracker.

For questions, contact [email protected].