Skip to main content

JavaScript / TypeScript SDK

Official SDK for accessing Replane configs from JavaScript and TypeScript applications.

Installation

npm install replane-sdk

Quick Start

import { createReplaneClient } from 'replane-sdk';

const client = createReplaneClient({
apiKey: process.env.REPLANE_API_KEY!,
baseUrl: 'https://replane.yourdomain.com',
});

// Watch a config (receives realtime updates via SSE)
const flags = await client.watchConfig<Record<string, boolean>>('feature-flags');

// Get the current value
if (flags.getValue()['new-feature']) {
console.log('Feature enabled!');
}

// With context for override evaluation
const enabled = flags.getValue({
userId: 'user-123',
plan: 'premium',
});

API Reference

createReplaneClient(options)

Creates a new Replane client instance.

Options

OptionTypeRequiredDefaultDescription
apiKeystringYes-API key for authentication. Each key is tied to a specific project.
baseUrlstringYes-Base URL of your Replane instance (no trailing slash)
contextobjectNo{}Default context for all config evaluations (can be overridden per-request)
fetchFnfunctionNoglobalThis.fetchCustom fetch function (for testing or unsupported environments)
timeoutMsnumberNo2000Request timeout in milliseconds
retriesnumberNo2Number of retry attempts on transient failures
retryDelayMsnumberNo200Base delay between retries in milliseconds
loggerobjectNoconsoleCustom logger with debug, info, warn, error methods

Returns

Client object with methods: { watchConfig, close }

Example

const client = createReplaneClient({
apiKey: 'rpk_abc123...',
baseUrl: 'https://config.company.com',
context: {
environment: 'production',
region: 'us-east',
},
timeoutMs: 5000,
retries: 3,
});

client.watchConfig(name, options?)

Creates a watcher that receives realtime updates for a config value via Server-Sent Events (SSE). The watcher evaluates override rules client-side based on context you provide.

Parameters

ParameterTypeRequiredDescription
namestringYesConfig name to watch
optionsobjectNoOptions for this watcher
options.contextobjectNoContext merged with client-level context for override evaluation

Returns

Promise<ConfigWatcher<T>> - Watcher object with methods:

  • getValue(context?) - Returns current value with override evaluation based on context
  • close() - Stops watching and closes the SSE connection

Errors

Throws if the initial fetch fails. Subsequent SSE update errors are logged but don't throw.

Examples

Basic usage:

const flags = await client.watchConfig<Record<string, boolean>>('feature-flags');

// Get current value
console.log(flags.getValue()); // { "new-onboarding": true, ... }

With context for override evaluation:

const watcher = await client.watchConfig<boolean>('premium-features');

// Evaluate for different users
const freeUser = watcher.getValue({ plan: 'free' }); // false
const premiumUser = watcher.getValue({ plan: 'premium' }); // true

With type safety:

interface FeatureFlags {
'new-onboarding': boolean;
'dark-mode': boolean;
}

const flags = await client.watchConfig<FeatureFlags>('feature-flags');
if (flags.getValue()['new-onboarding']) {
// TypeScript knows this is a boolean
}

Client-level context:

const client = createReplaneClient({
apiKey: process.env.REPLANE_API_KEY!,
baseUrl: 'https://config.company.com',
context: {
environment: 'production',
region: 'us-east',
},
});

const watcher = await client.watchConfig('feature');
// Uses client-level context
watcher.getValue();
// Merges with client-level context
watcher.getValue({ userId: '123' });

In Express middleware:

// Initialize once
const rateConfig = await client.watchConfig<Record<string, number>>('rate-limits');

app.use((req, res, next) => {
const limits = rateConfig.getValue();
const rpm = limits['api-requests-per-minute'];

// Apply rate limiting
if (rateLimiter.isExceeded(req.ip, rpm)) {
return res.status(429).json({ error: 'Too many requests' });
}

next();
});

// Cleanup on shutdown
process.on('SIGTERM', () => {
rateConfig.close();
});

Typed watcher:

interface RateLimits {
'api-requests-per-minute': number;
'max-concurrent-connections': number;
}

const limits = await client.watchConfig<RateLimits>('rate-limits');
const rpm = limits.getValue()['api-requests-per-minute']; // TypeScript knows this is a number

A/B testing with context:

const experiment = await client.watchConfig<string>('homepage-variant');

app.get('/', (req, res) => {
// Evaluate based on user ID for consistent experience
const variant = experiment.getValue({
userId: req.user.id,
country: req.geo.country,
});

res.render(variant === 'b' ? 'homepage-v2' : 'homepage-v1');
});

client.close()

Gracefully shuts down the client and closes all active watchers. Subsequent calls to watchConfig will throw.

Example

// During application shutdown
client.close();

createInMemoryReplaneClient(initialData)

Creates a client backed by an in-memory store. Useful for testing or local development.

Parameters

ParameterTypeRequiredDescription
initialDataRecord<string, any>YesMap of config name to value

Returns

Client with same API as createReplaneClient

Notes

  • watchConfig resolves to watchers with values from initialData
  • Throws ReplaneError if config name is missing
  • Watchers work but don't receive SSE updates (values remain static from initialData)

Example

import { createInMemoryReplaneClient } from 'replane-sdk';

const client = createInMemoryReplaneClient({
'feature-flags': { 'new-feature': true },
'rate-limits': { 'api-requests-per-minute': 100 },
});

const flags = await client.watchConfig('feature-flags');
console.log(flags.getValue()); // { "new-feature": true }

Error Handling

ReplaneError

The SDK throws ReplaneError for HTTP failures:

try {
const watcher = await client.watchConfig('non-existent');
} catch (error) {
if (error instanceof ReplaneError) {
console.error('Replane error:', error.message);
console.error('Error code:', error.code);
} else {
console.error('Other error:', error);
}
}

Retry Behavior

Transient failures (5xx responses or network errors) are automatically retried:

const client = createReplaneClient({
apiKey: 'rpk_...',
baseUrl: 'https://config.company.com',
retries: 3, // Retry up to 3 times
retryDelayMs: 200, // Wait 200ms between retries (with jitter)
});

Non-transient errors (4xx) are not retried.

Environment Support

  • Node.js 18+: Built-in fetch support
  • Browsers: Modern browsers with fetch and EventSource
  • Edge runtimes: Cloudflare Workers, Vercel Edge, Deno
  • Older environments: Provide a polyfill via fetchFn

TypeScript

The SDK is written in TypeScript and exports full type definitions.

import { createReplaneClient, ReplaneError } from 'replane-sdk';
import type { ReplaneClient, ConfigWatcher } from 'replane-sdk';

const client: ReplaneClient = createReplaneClient({
apiKey: 'rpk_...',
baseUrl: 'https://config.company.com',
});

const watcher: ConfigWatcher<Record<string, boolean>> = await client.watchConfig('flags');

Best Practices

Store API Keys Securely

// ✅ Good: Use environment variables
const client = createReplaneClient({
apiKey: process.env.REPLANE_API_KEY!,
baseUrl: process.env.REPLANE_URL!,
});

// ❌ Bad: Hardcode credentials
const client = createReplaneClient({
apiKey: 'rpk_abc123...',
baseUrl: 'https://...',
});

Use Watchers for All Configs

All config access uses watchers with realtime updates:

// Initialize watchers once at startup
const flags = await client.watchConfig('feature-flags');

app.get('/api/data', (req, res) => {
// Always up-to-date via SSE, no network request needed
const currentFlags = flags.getValue();
// ...
});

Provide Fallbacks

let config: ConfigWatcher<MyConfig>;
try {
config = await client.watchConfig('my-config');
} catch (error) {
// Use in-memory client with safe defaults
const fallbackClient = createInMemoryReplaneClient({
'my-config': {
'feature-enabled': false,
'rate-limit': 100,
},
});
config = await fallbackClient.watchConfig('my-config');
}

Clean Up Resources

// Close individual watchers
const watcher = await client.watchConfig('config');
// ... use watcher
watcher.close();

// Or close entire client
client.close(); // Closes all watchers

Examples

Feature Flags

const flags = await client.watchConfig<Record<string, boolean>>('feature-flags');

if (flags.getValue()['new-checkout']) {
return renderNewCheckout();
} else {
return renderOldCheckout();
}

Rate Limiting

const limits = await client.watchConfig<Record<string, number>>('rate-limits');

// Value is always up-to-date via SSE
const rpm = limits.getValue()['api-requests-per-minute'];
rateLimiter.setLimit(rpm);

Multiple Projects

Each API key is tied to a specific project. For multiple projects, create separate clients:

const projectAClient = createReplaneClient({
apiKey: process.env.PROJECT_A_API_KEY!,
baseUrl: 'https://config.company.com',
});

const projectBClient = createReplaneClient({
apiKey: process.env.PROJECT_B_API_KEY!,
baseUrl: 'https://config.company.com',
});

const flagsA = await projectAClient.watchConfig('flags');
const flagsB = await projectBClient.watchConfig('flags');

Next Steps