Feature flag management with multi-layer priority system (client-side)
npm install @lovart-open/flags

Type-safe Feature Flag and Parameter Store library built on Statsig, with a fully synchronous architecture.
``bash`
npm install @lovart-open/flagsor
pnpm add @lovart-open/flags
Initialize the Statsig client at your app entry point:
`ts
import { initStatsigClient } from '@lovart-open/flags/statsig';
initStatsigClient('your-statsig-client-key', { userID: 'user-123' }, {
environment: { tier: 'production' },
});
`
---
- Type-safe: Full TypeScript type inference and autocomplete
- Synchronous: No loading states, no skeleton screens
- Multi-layer priority: URL > testOverride > override > remote > fallback(false)
`ts
import { createFlagStore, type FlagDefinition } from '@lovart-open/flags/statsig';
// 1. Define your flags
const MY_FLAGS = {
dark_mode: {
description: 'Dark mode toggle'
},
new_checkout: {
description: 'New checkout flow',
testOverride: true, // Force enable in E2E tests
},
beta_feature: {
description: 'Beta feature',
override: false, // Static override, ignores remote
},
} as const satisfies Record
// 2. Create type-safe store and hooks
export const {
flagStore,
useFlag,
useFlagState
} = createFlagStore(MY_FLAGS);
// 3. Export types (optional)
export type MyFlagKey = keyof typeof MY_FLAGS;
`
`tsx
import { useFlag, useFlagState } from './my-flags';
function App() {
// Get boolean value directly
const isDark = useFlag('dark_mode'); // ✓ autocomplete
// Get full state with source info
const state = useFlagState('new_checkout');
console.log(state.flag, state.source); // true, 'remote'
return isDark ?
}
`
`ts
import { flagStore } from './my-flags';
// Get single flag
const enabled = flagStore.getFlag('dark_mode');
// Get snapshot of all flags
const snapshot = flagStore.snapshot;
`
``
?ff.dark_mode=1 → Force enable
?ff.dark_mode=0 → Force disable
---
- Type-safe: Zod schema validation + TypeScript inference
- Synchronous: Same as Feature Flags
- Multi-layer priority: URL > testOverride > override > remote > fallback
`ts
import { z } from 'zod';
import {
createParamStore,
defineParam,
type ParamStoreDefinition
} from '@lovart-open/flags/statsig';
// 1. Define your param stores
const MY_PARAMS = {
homepage_cta: {
description: 'Homepage CTA button',
params: {
text: defineParam({
schema: z.enum(['Learn More', 'Get Started', 'Sign Up']),
fallback: 'Learn More',
description: 'Button text',
}),
color: defineParam({
schema: z.enum(['gray', 'red', 'blue']),
fallback: 'gray',
testOverride: 'blue', // Use in E2E tests
}),
visible: defineParam({
schema: z.boolean(),
fallback: true,
}),
},
},
pricing: {
description: 'Pricing config',
params: {
discount: defineParam({
schema: z.number().min(0).max(100),
fallback: 0,
}),
currency: defineParam({
schema: z.enum(['USD', 'CNY', 'EUR']),
fallback: 'USD',
}),
},
},
} as const satisfies Record
// 2. Create type-safe store and hooks
export const {
paramStore,
useParam,
useParamState,
useParamStore
} = createParamStore(MY_PARAMS);
`
`tsx
import { useParam, useParamStore } from './my-params';
function CTAButton() {
// Get value directly (with full type hints)
const text = useParam('homepage_cta', 'text'); // 'Learn More' | 'Get Started' | 'Sign Up'
const color = useParam('homepage_cta', 'color'); // 'gray' | 'red' | 'blue'
// Or get entire store handle
const store = useParamStore('homepage_cta');
const visible = store.get('visible'); // boolean
if (!visible) return null;
return ;
}
`
`ts
import { paramStore } from './my-params';
// Get single param
const discount = paramStore.getParam('pricing', 'discount'); // number
// Get store handle
const store = paramStore.getStore('pricing');
store.get('currency'); // 'USD' | 'CNY' | 'EUR'
`
`Single param override
?fp.homepage_cta.text=Get Started
?fp.pricing.discount=20
---
Advanced Configuration
Custom Logger
`ts
import { initStatsigClient, setLogger } from '@lovart-open/flags/statsig';// Option 1: Pass during init
initStatsigClient('client-xxx', { userID: 'user-123' }, {
logger: (message) => myLogger.info(message),
});
// Option 2: Set globally
setLogger((message) => myLogger.info(message));
`E2E Test Support
Configure
isTestEnv in initialization to enable testOverride values:`ts
initStatsigClient('client-xxx', { userID: 'user-123' }, {
isTestEnv: () => Boolean(window.__E2E__),
});// playwright/cypress tests
await page.addInitScript(() => {
window.__E2E__ = true;
});
`Server-Side Bootstrap (Zero-Network Init)
`ts
initStatsigClient('client-xxx', { userID: 'user-123' }, {
bootstrap: {
data: bootstrapDataFromServer, // Pre-fetched from BFF
},
});
`FlagDefinition Options
| Property | Type | Description |
|----------|------|-------------|
|
description | string | Human-readable description |
| testOverride | boolean | Fixed value in E2E tests |
| override | boolean | Static override (priority over remote) |
| keep | boolean | Mark as kept locally (no remote needed) |ParamDefinition Options
| Property | Type | Description |
|----------|------|-------------|
|
schema | z.ZodType | Zod schema (required) |
| fallback | T | Default value (required) |
| description | string | Human-readable description |
| testOverride | T | Fixed value in E2E tests |
| override | T` | Static override |---
MIT