No results
3
TypeScript SDK
Insidious Fiddler edited this page 2026-02-06 21:04:12 +00:00
TypeScript SDK
Feature flag evaluation for TypeScript and JavaScript applications.
Installation
npm install @hoist/sdk
# or
pnpm add @hoist/sdk
# or
yarn add @hoist/sdk
Quick Start
import { HoistClient } from '@hoist/sdk'
const client = new HoistClient({
apiKey: 'hoist_live_xxx',
baseURL: 'https://hoist.example.com',
})
// Wait for initialization
await client.waitForInitialization()
// Evaluate a flag
const enabled = await client.booleanValue('new-feature', false, {
targetingKey: 'user-123',
attributes: { plan: 'pro' },
})
if (enabled) {
// New feature code path
}
// Cleanup when done
client.close()
Configuration
const client = new HoistClient({
// Required
apiKey: 'hoist_live_xxx',
baseURL: 'https://hoist.example.com',
// Optional
streaming: true, // Enable WebSocket streaming (default: true for server keys)
timeout: 5000, // Request timeout in ms (default: 5000)
// Hooks for observability
hooks: [
{
before: async (key, context) => {
console.log(`Evaluating: ${key}`)
},
after: async (key, result) => {
console.log(`Result: ${key} = ${result.value}`)
},
error: async (key, error) => {
console.error(`Error: ${key}`, error)
},
},
],
// Custom logger
logger: {
debug: (msg, ...args) => console.debug(msg, ...args),
info: (msg, ...args) => console.info(msg, ...args),
warn: (msg, ...args) => console.warn(msg, ...args),
error: (msg, ...args) => console.error(msg, ...args),
},
})
API Key Types
Server Keys (hoist_live_* / hoist_test_*)
For Node.js backends and server-side rendering:
- Full configuration fetched on startup
- Local evaluation (fast, no network latency per evaluation)
- Real-time updates via WebSocket streaming
- Access to all flags
const client = new HoistClient({
apiKey: 'hoist_live_xxx', // Server key
streaming: true,
})
Client Keys
For browser applications where targeting rules should be protected:
- Server-side evaluation (API call per evaluation)
- Targeting rules not exposed to client
- No local flag configuration
const client = new HoistClient({
apiKey: 'hoist_pk_xxx', // Client key
})
Evaluation Methods
Boolean
// Simple evaluation
const enabled = await client.booleanValue('dark-mode', false, {
targetingKey: 'user-123',
})
// With full details
const result = await client.evaluate('dark-mode', false, {
targetingKey: 'user-123',
})
console.log(result.value, result.reason, result.variantKey)
String
const version = await client.stringValue('api-version', 'v1', {
targetingKey: 'user-123',
})
Number
const limit = await client.numberValue('rate-limit', 100, {
targetingKey: 'user-123',
})
JSON
interface FeatureConfig {
maxItems: number
enabled: boolean
allowlist: string[]
}
const config = await client.jsonValue<FeatureConfig>('feature-config', {
maxItems: 10,
enabled: false,
allowlist: [],
}, {
targetingKey: 'user-123',
})
console.log(config.maxItems)
Evaluation Context
Provide user context for targeting:
const context: EvaluationContext = {
// Required for consistent targeting and percentage rollouts
targetingKey: 'user-123',
// Optional attributes for targeting rules
attributes: {
email: 'user@example.com',
plan: 'enterprise',
country: 'US',
betaTester: true,
accountAge: 365,
teams: ['engineering', 'platform'],
},
}
const enabled = await client.booleanValue('feature', false, context)
Evaluation Results
Full evaluation results include:
const result = await client.evaluate('feature', false, context)
result.flagKey // The evaluated flag key
result.value // The evaluated value
result.variantKey // The variant key (if multivariate)
result.reason // Why this value was returned
result.version // Flag configuration version
result.error // Error if evaluation failed
Reasons
| Reason | Description |
|---|---|
DEFAULT |
No rules matched, using default |
TARGETING_MATCH |
Matched a targeting rule |
SEGMENT_MATCH |
Matched via segment |
ROLLOUT |
Percentage rollout |
IDENTITY_OVERRIDE |
User-specific override |
DISABLED |
Flag is disabled |
NOT_FOUND |
Flag doesn't exist |
ERROR |
Evaluation error |
Hooks
Add hooks for logging, metrics, and error tracking:
const client = new HoistClient({
apiKey: 'hoist_live_xxx',
baseURL: 'https://hoist.example.com',
hooks: [
{
before: async (key, context) => {
// Called before evaluation
console.log(`Evaluating: ${key}`)
},
after: async (key, result) => {
// Called after successful evaluation
analytics.track('flag_evaluated', {
flag: key,
value: result.value,
reason: result.reason,
})
},
error: async (key, error) => {
// Called on evaluation error
errorTracker.capture(error, { flag: key })
},
},
],
})
Initialization
Waiting for Initialization
const client = new HoistClient(config)
// Wait for the client to be ready
await client.waitForInitialization()
// Now safe to evaluate flags
const enabled = await client.booleanValue('feature', false, context)
Handling Initialization Errors
const client = new HoistClient(config)
try {
await client.waitForInitialization()
} catch (error) {
console.error('Failed to initialize Hoist:', error)
// Client will still work, returning defaults
}
Real-Time Streaming
Server keys automatically connect to WebSocket for real-time updates:
const client = new HoistClient({
apiKey: 'hoist_live_xxx',
baseURL: 'https://hoist.example.com',
streaming: true, // Default for server keys
})
// Flags are automatically updated when changed in dashboard
Disable Streaming
For environments where WebSocket isn't available:
const client = new HoistClient({
apiKey: 'hoist_live_xxx',
baseURL: 'https://hoist.example.com',
streaming: false, // Disable streaming
})
Node.js Usage
import { HoistClient } from '@hoist/sdk'
// Initialize once on startup
const flagClient = new HoistClient({
apiKey: process.env.HOIST_API_KEY!,
baseURL: process.env.HOIST_URL!,
})
await flagClient.waitForInitialization()
// Use in request handlers
export async function handler(req: Request): Promise<Response> {
const userId = getUserId(req)
const newCheckout = await flagClient.booleanValue('new-checkout', false, {
targetingKey: userId,
attributes: {
plan: getUserPlan(userId),
},
})
if (newCheckout) {
return handleNewCheckout(req)
}
return handleLegacyCheckout(req)
}
// Cleanup on shutdown
process.on('SIGTERM', () => {
flagClient.close()
})
Browser Usage
For browser applications, use client keys to protect targeting rules:
import { HoistClient } from '@hoist/sdk'
const client = new HoistClient({
apiKey: 'hoist_pk_xxx', // Client key
baseURL: 'https://hoist.example.com',
})
// Evaluate flags (server-side evaluation)
const showBanner = await client.booleanValue('promo-banner', false, {
targetingKey: getCurrentUserId(),
})
if (showBanner) {
document.getElementById('banner')?.classList.remove('hidden')
}
React Integration
import { createContext, useContext, useEffect, useState, ReactNode } from 'react'
import { HoistClient, EvaluationContext } from '@hoist/sdk'
// Create context
const HoistContext = createContext<HoistClient | null>(null)
// Provider component
export function HoistProvider({
children,
apiKey,
baseURL,
}: {
children: ReactNode
apiKey: string
baseURL: string
}) {
const [client, setClient] = useState<HoistClient | null>(null)
useEffect(() => {
const hoistClient = new HoistClient({ apiKey, baseURL })
hoistClient.waitForInitialization().then(() => {
setClient(hoistClient)
})
return () => {
hoistClient.close()
}
}, [apiKey, baseURL])
return (
<HoistContext.Provider value={client}>
{children}
</HoistContext.Provider>
)
}
// Hook for flag evaluation
export function useFlag(
key: string,
defaultValue: boolean,
context: EvaluationContext
): boolean {
const client = useContext(HoistContext)
const [value, setValue] = useState(defaultValue)
useEffect(() => {
if (client) {
client.booleanValue(key, defaultValue, context).then(setValue)
}
}, [client, key, defaultValue, context])
return value
}
// Usage
function MyComponent() {
const showFeature = useFlag('new-feature', false, {
targetingKey: userId,
})
return showFeature ? <NewFeature /> : <OldFeature />
}
Error Handling
try {
const enabled = await client.booleanValue('feature', false, context)
// Use the value
} catch (error) {
// Handle error
console.error('Flag evaluation failed:', error)
}
// Or check result for errors
const result = await client.evaluate('feature', false, context)
if (result.error) {
console.error('Evaluation error:', result.error)
}
// result.value is the default on error
TypeScript Types
import type {
HoistConfig,
EvaluationContext,
EvaluationResult,
Hook,
Logger,
Reason,
} from '@hoist/sdk'
// All types are exported for use in your application
const config: HoistConfig = {
apiKey: 'hoist_live_xxx',
baseURL: 'https://hoist.example.com',
}
const context: EvaluationContext = {
targetingKey: 'user-123',
attributes: { plan: 'pro' },
}
Best Practices
1. Initialize Once
// Good: Single instance
const client = new HoistClient(config)
export { client }
// Bad: New instance per evaluation
async function getFlag() {
const client = new HoistClient(config) // Don't do this!
return client.booleanValue('feature', false, context)
}
2. Always Provide Targeting Key
// Good: Consistent targeting
await client.booleanValue('feature', false, {
targetingKey: userId,
})
// Bad: No targeting key
await client.booleanValue('feature', false, {})
3. Handle Cleanup
// Cleanup on shutdown
process.on('SIGTERM', () => client.close())
process.on('SIGINT', () => client.close())
4. Use Type-Safe Defaults
// Good: TypeScript enforces correct default type
const count = await client.numberValue('max-items', 10, context)
// Type error: default must be number
const count = await client.numberValue('max-items', 'ten', context)
Next Steps
- Go SDK - Server-side Go SDK
- OpenFeature Provider - Vendor-neutral interface
- Core Concepts - Understanding targeting