Type-safe Feature Flag and Parameter Store library built on Statsig, with a fully synchronous architecture.
npm install @lovart-open/flags
# or
pnpm add @lovart-open/flagsInitialize the Statsig client at your app entry point:
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)
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<string, FlagDefinition>;
// 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;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 ? <DarkTheme /> : <LightTheme />;
}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
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<string, ParamStoreDefinition<any>>;
// 2. Create type-safe store and hooks
export const {
paramStore,
useParam,
useParamState,
useParamStore
} = createParamStore(MY_PARAMS);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 <button style={{ color }}>{text}</button>;
}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
# Entire store JSON override
?fp.homepage_cta={"text":"Get Started","visible":false}
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));Configure isTestEnv in initialization to enable testOverride values:
initStatsigClient('client-xxx', { userID: 'user-123' }, {
isTestEnv: () => Boolean(window.__E2E__),
});
// playwright/cypress tests
await page.addInitScript(() => {
window.__E2E__ = true;
});initStatsigClient('client-xxx', { userID: 'user-123' }, {
bootstrap: {
data: bootstrapDataFromServer, // Pre-fetched from BFF
},
});| 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) |
| 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