npm package: @flagdeck/js
Description: JavaScript SDK for Flagdeck feature flags
Latest version: 0.0.34

# Flagdeck JavaScript SDK

The official JavaScript/TypeScript SDK for [Flagdeck](https://flagdeck.com), a modern feature flag and feature management system.

## Installation

```bash
npm install @flagdeck/js
# or
yarn add @flagdeck/js
```

## Quick Start

```javascript
import { Flagdeck } from '@flagdeck/js';

// Initialize the client
const flagdeck = new Flagdeck({
   apiKey: 'your-api-key'
});

// Check if a feature flag is enabled
async function checkFeature() {
   const isEnabled = await flagdeck.isEnabled('new-feature', {
      userId: 'user-123',
      attributes: {
         country: 'US',
         plan: 'premium'
      }
   });

   if (isEnabled) {
      // Feature is enabled for this user
      enableNewFeature();
   } else {
      // Feature is disabled
      useDefaultFeature();
   }
}
```

## Environment-Specific Imports

The SDK automatically detects your environment (browser or Node.js) and uses the appropriate implementation. If you want to explicitly import a specific version:

### Browser Import

```javascript
import { Flagdeck } from '@flagdeck/js/browser';

const flagdeck = new Flagdeck({
   apiKey: 'your-api-key'
});
```

### Node.js Import

```javascript
import { Flagdeck } from '@flagdeck/js/node';

const flagdeck = new Flagdeck({
   apiKey: 'your-api-key'
});
```

## Working with Different Value Types

Flagdeck supports multiple value types for feature flags:

### Boolean Flags

```typescript
// Check if a feature is enabled
const isEnabled = await flagdeck.isEnabled('new-feature');
if (isEnabled) {
   showNewFeature();
}
```

### String Variants

```typescript
// Get a string variant value with default
const variant = await flagdeck.getValue<string>('experiment-variant', context, 'control');
if (variant === 'A') {
   // Show variant A
} else if (variant === 'B') {
   // Show variant B
}
```

### Numeric Settings

```typescript
// Get a numeric configuration value with default
const limit = await flagdeck.getValue<number>('rate-limit', context, 100);
applyRateLimit(limit);
```

### JSON/Object Configuration

```typescript
// Get complex configuration with TypeScript type
const config = await flagdeck.getValue<{
   url: string;
   timeout: number;
   retries: number;
}>('api-config', context, { url: 'default.com', timeout: 5000, retries: 3 });
```

## Advanced Usage: Full Evaluation Details

When you need additional metadata about an evaluation:

```typescript
// Get the full evaluation result
const result = await flagdeck.evaluateFlag('new-feature', context);
console.log(`Feature is ${result.value ? 'enabled' : 'disabled'}`);
console.log(`Source: ${result.source}`); // 'api', 'cache', etc.
console.log(`Reason: ${result.reason}`);
console.log(`Evaluated at: ${new Date(result.timestamp)}`);
```

## Multiple Flags at Once

```typescript
// Get multiple flag values in a single request
const values = await flagdeck.getValues([
   'feature-a',
   'rate-limit',
   'experiment'
], context);

console.log(values['feature-a']); // boolean
console.log(values['rate-limit']); // number
console.log(values['experiment']); // string
```

## Real-Time Updates

Flagdeck supports real-time flag updates, allowing your application to instantly react to flag changes made in the Flagdeck dashboard without requiring page refreshes or API polling.

### Enabling Real-Time Updates

Real-time updates are enabled by default in browser environments and disabled by default in server environments. You can explicitly control this behavior during initialization:

```javascript
const flagdeck = new Flagdeck({
  apiKey: 'your-api-key',
  realTimeUpdates: true  // Enable real-time updates
});
```

### Listening for Flag Changes

```javascript
// Listen for changes to a specific flag
flagdeck.onFlagChange('welcome-message', (newValue, oldValue) => {
  console.log(`Flag value changed from ${oldValue} to ${newValue}`);
  
  // Update your UI based on the new value
  if (newValue === true) {
    showWelcomeMessage();
  } else {
    hideWelcomeMessage();
  }
});

// Listen for a flag being enabled
flagdeck.on('enabled:feature-name', (value) => {
  console.log('Feature was enabled!');
  showFeature();
});

// Listen for a flag being disabled
flagdeck.on('disabled:feature-name', (value) => {
  console.log('Feature was disabled!');
  hideFeature();
});

// Listen for all flag changes
flagdeck.on('change', (changes) => {
  console.log('Multiple flags changed:', changes);
  // { flagKey1: { newValue, oldValue }, flagKey2: { newValue, oldValue } }
});

// Listen for connection events
flagdeck.on('connected', () => {
  console.log('Connected to real-time updates');
});

flagdeck.on('disconnected', (error) => {
  console.log('Disconnected from real-time updates', error);
});

flagdeck.on('error', (error) => {
  console.error('Real-time updates error', error);
});
```

### Managing Real-Time Updates

You can enable or disable real-time updates at runtime:

```javascript
// Enable real-time updates with options
flagdeck.enableRealTimeUpdates({
  autoUpdate: true,  // Automatically update cached values when flags change
  onFlagUpdate: (flagKey, newValue) => {
    console.log(`Flag ${flagKey} updated to:`, newValue);
  },
  onConnect: () => {
    console.log('Connected to real-time updates');
  },
  onDisconnect: (error) => {
    console.log('Disconnected from real-time updates', error);
  }
});

// Disable real-time updates
flagdeck.disableRealTimeUpdates();

// Check if real-time updates are enabled
const enabled = flagdeck.areRealTimeUpdatesEnabled();
console.log('Real-time updates enabled:', enabled);

// Reset circuit breaker if connections are failing
flagdeck.resetSseCircuitBreaker();
```

### Subscribing to Specific Flags

In server environments, you can optimize performance by subscribing only to specific flags:

```javascript
// In Node.js, subscribe to specific flags with options
flagdeck.subscribeToFlags(['payment-gateway', 'auth-method'], {
  priority: 'high',  // 'high', 'normal', or 'low'
  ttl: 3600000       // Subscription time-to-live in ms (1 hour)
});
```

### Real-Time Updates Implementation Details

- **Browser**: Uses the native EventSource API (Server-Sent Events) with automatic reconnection
- **Node.js**: Uses the 'eventsource' package (must be installed separately)
- **Fault Tolerance**: Includes circuit breaker pattern to prevent excessive reconnection attempts
- **Memory Management**: Automatically cleans up unused flag subscriptions
- **Performance**: Optimized to minimize bandwidth and CPU usage

## Evaluation Context

Most evaluation methods accept a context parameter that defines the user and environment details for targeting rules. Here's how to structure the context:

```typescript
// Basic context with user ID
const context = {
  userId: 'user-123'
};

// Full context with attributes
const detailedContext = {
  userId: 'user-456',
  sessionId: 'session-789',  // Optional session identifier
  attributes: {
    // User attributes for targeting
    email: 'user@example.com',
    plan: 'premium',
    country: 'US',
    isAdmin: true,

    // Custom business attributes
    purchaseCount: 5,
    lastLoginDate: '2023-04-15',

    // Device/environment attributes
    device: 'mobile',
    browser: 'chrome',
    version: '1.2.3'
  }
};
```

## Configuration Options

The following options can be provided when initializing the Flagdeck SDK:

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| apiKey | string | *required* | Your Flagdeck API key |
| enableCache | boolean | true | Cache flag evaluations in memory |
| cacheTimeout | number | 30000 | Cache timeout in milliseconds |
| timeout | number | 5000 | Network request timeout in milliseconds |
| retryConfig | object | { attempts: 3, delay: 1000, maxDelay: 5000 } | Retry configuration for network errors |
| debug | boolean | false | Enable detailed logging |
| enableOfflineMode | boolean | false | Enable offline fallback mode |
| enableAnalytics | boolean | true | Track evaluation metrics |
| realTimeUpdates | boolean | auto (true for browser, false for Node.js) | Enable real-time flag updates |
| onError | function | undefined | Global error handler |
| defaultFlagValue | any | false | Default value when evaluation fails |
| sdkPlatform | string | auto-detected | Override detected platform ('web', 'mobile', 'server') |

## Error Handling

```javascript
// Global error handler
const flagdeck = new Flagdeck({
  apiKey: 'your-api-key',
  onError: (error, flagKey) => {
    console.error(`Error evaluating flag ${flagKey}:`, error);
    // Log to your error tracking system
    errorTracker.captureException(error);
  },
  defaultFlagValue: false // Value to use if evaluation fails
});

// Try/catch for specific evaluations
try {
  const result = await flagdeck.evaluateFlag('new-feature');
  // Use result
} catch (error) {
  // Handle error
  console.error('Failed to evaluate flag:', error);
}
```

## Middleware

Add custom behavior to the flag evaluation process with middleware:

```javascript
flagdeck.use({
  name: 'LoggingMiddleware',
  beforeEvaluation: (flagKey, context) => {
    console.log(`Evaluating flag: ${flagKey}`, context);
  },
  afterEvaluation: (flagKey, result, context) => {
    console.log(`Flag ${flagKey} evaluated to:`, result.value);
    return result;
  },
  onError: (error, flagKey) => {
    console.error(`Error evaluating flag ${flagKey}:`, error);
  }
});
```

## Offline Mode

Enable offline support to maintain functionality when connectivity is lost. Offline mode is essential for applications that need to maintain consistent feature flag evaluation during network outages, mobile usage with intermittent connectivity, or to reduce server load.

```javascript
const flagdeck = new Flagdeck({
  apiKey: 'your-api-key',
  enableOfflineMode: true
});

// Save current flags for offline use
await flagdeck.saveForOffline('feature-a', true);

// Save multiple flags at once
await flagdeck.saveMultipleForOffline({
  'feature-a': true,
  'feature-b': false,
  'limit': 100
});

// Clear offline storage
await flagdeck.clearOfflineStorage();

// When offline, the SDK will automatically use stored flag values
// No additional code needed for fallback behavior
```

### Offline Storage Details

The SDK implements an efficient storage system with the following optimizations:

- **Flag Expiration**: Flags are stored with expiration times (default: 7 days)
- **Automatic Pruning**: Oldest flags are automatically removed when storage limits are reached
- **Size Management**: Storage size is monitored and optimized (100KB limit in browsers, 500KB in Node.js)
- **Periodic Maintenance**: The SDK automatically cleans up expired flags
- **Efficient I/O**: Batch operations minimize disk/localStorage writes

### Environment-Specific Storage

- **Browser**: Uses `localStorage` with memory fallback and handles quota errors
- **Node.js**: Uses filesystem storage in the user's home directory (`~/.flagdeck`) with temp directory fallback

### Important Considerations

1. **Offline Mode Must Be Enabled**: Set `enableOfflineMode: true` in your configuration options
2. **Save Flags Proactively**: Call `saveForOffline()` or `saveMultipleForOffline()` when your app has connectivity
3. **Storage Limits**: Be mindful of storage limits, especially for mobile browsers with tight localStorage quotas
4. **Flag Expiration**: Stored flags expire after 7 days by default; customize expiration by providing a TTL (coming soon)
5. **Flushing Before App Closure**: For browser applications, consider flushing before app closure:

```javascript
// Ensure flags are properly saved before app-boot closes
window.addEventListener('beforeunload', async () => {
  // Save important flags one last time
  await flagdeck.saveMultipleForOffline({
    'critical-feature': await flagdeck.getValue('critical-feature'),
    'user-settings': await flagdeck.getValue('user-settings')
  });
  
  // Also flush analytics
  await flagdeck.flushAnalytics();
});
```

### Best Practices

- **Default Values**: Always provide sensible default values as a final fallback
- **Periodic Refreshes**: Refresh offline flags periodically during active use
- **Critical Flags**: Identify and prioritize saving business-critical flags
- **Storage Monitoring**: For sensitive applications, monitor offline storage usage

```javascript
// Example: Refresh critical flags every hour during active use
setInterval(async () => {
  if (navigator.onLine) {  // Check connectivity (browser only)
    try {
      const criticalFlags = await flagdeck.getValues([
        'payment-gateway', 
        'auth-method',
        'pricing-tiers'
      ]);
      await flagdeck.saveMultipleForOffline(criticalFlags);
      console.log('Refreshed offline flags successfully');
    } catch (error) {
      console.error('Failed to refresh offline flags:', error);
    }
  }
}, 60 * 60 * 1000); // 1 hour
```

## Cache Management

The SDK automatically caches flag evaluations to improve performance. You can manage the cache manually:

```javascript
// Clear the cache
flagdeck.clearCache();
```

## Analytics

The SDK collects anonymous usage statistics by default. You can flush these analytics manually before the app closes:

```javascript
// Flush analytics events
await flagdeck.flushAnalytics();
```

## Cleanup

When you're done with the Flagdeck client, you should clean up resources:

```javascript
// Clean up resources when done
await flagdeck.destroy();
```

## Platform Support

This SDK works across platforms:

* **Browsers:** All modern browsers are supported
* **Node.js:** Server-side environments
* **React Native:** Mobile applications (via the JavaScript runtime)

## TypeScript Support

The SDK includes complete TypeScript definitions:

```typescript
import { Flagdeck, EvaluationContext } from '@flagdeck/js';

const flagdeck = new Flagdeck({
  apiKey: 'your-api-key'
});

const context: Partial<EvaluationContext> = {
  userId: 'user-123',
  attributes: {
    plan: 'premium',
    region: 'eu-west',
    isAdmin: true
  }
};

const result = await flagdeck.evaluateFlag('new-feature', context);
```

## Differences Between Browser and Node.js Versions

While both versions share the same API, there are some internal differences:

1. **Storage Mechanisms**:
   - Browser: Uses `localStorage` for offline storage when available
   - Node.js: Uses file system storage in the user's home directory

2. **Network Implementation**:
   - Browser: Uses the native `fetch` API
   - Node.js: Uses `node-fetch` when the native `fetch` isn't available (Node.js < 18)

3. **Environment Detection**:
   - Browser: Detects browser, screen, and web-specific properties
   - Node.js: Detects Node.js version, platform, and server-specific properties

4. **Real-Time Updates**:
   - Browser: Enabled by default, uses native EventSource API
   - Node.js: Disabled by default, requires the 'eventsource' package

These differences are handled internally, allowing you to use the same API regardless of environment.

## API Reference

### Flagdeck

The main class for interacting with Flagdeck.

#### Methods

- `isEnabled(flagKey: string, context?: EvaluationContext, defaultValue?: boolean): Promise<boolean>` - Checks if a flag is enabled
- `getValue<T>(flagKey: string, context?: EvaluationContext, defaultValue?: T): Promise<T>` - Gets just the flag value
- `getValues<T>(flagKeys: string[], context?: EvaluationContext, defaultValue?: T): Promise<Record<string, T>>` - Gets multiple flag values at once
- `evaluateFlag(flagKey: string, context?: EvaluationContext): Promise<EvaluationResult>` - Evaluates a flag with full details
- `evaluate(flagKey: string, context?: EvaluationContext): Promise<EvaluationResult>` - Legacy alias for evaluateFlag (deprecated)
- `evaluateBulk(flagKeys: string[], context?: EvaluationContext): Promise<BulkEvaluationResponse>` - Evaluates multiple flags
- `use(middleware: EvaluationMiddleware): Flagdeck` - Adds an evaluation middleware
- `clearCache(): void` - Clears the evaluation cache
- `saveForOffline(flagKey: string, value: any): Promise<void>` - Saves a flag for offline use
- `saveMultipleForOffline(flags: Record<string, any>): Promise<void>` - Saves multiple flags for offline use
- `clearOfflineStorage(): Promise<void>` - Clears stored offline flags
- `flushAnalytics(): Promise<void>` - Manually sends any pending analytics events
- `on(event: string, listener: Function): Flagdeck` - Subscribe to events (including real-time updates)
- `off(event: string, listener: Function): Flagdeck` - Unsubscribe from events
- `onFlagChange(flagKey: string, listener: (newValue: any, oldValue: any) => void): Flagdeck` - Subscribe to changes for a specific flag
- `enableRealTimeUpdates(options?: object): void` - Enable real-time flag updates
- `disableRealTimeUpdates(): void` - Disable real-time flag updates
- `areRealTimeUpdatesEnabled(): boolean` - Check if real-time updates are currently enabled
- `resetSseCircuitBreaker(): void` - Reset the circuit breaker if connections are failing
- `subscribeToFlags(flagKeys: string[], options?: object): void` - Subscribe to specific flags (Node.js)
- `destroy(): Promise<void>` - Cleans up resources

## License

MIT

## Support

For issues, feature requests, or questions, please visit our [GitHub repository](https://github.com/flagdeck) or contact [support@flagdeck.com](mailto:support@flagdeck.com).