Automatic type-safe interfaces for your feature flags with complete client code
FlagFlow automatically generates complete TypeScript definitions for your feature flags, providing full type safety and IntelliSense support. The generated code includes type definitions, default values, hash constants, and ready-to-use client functions.
Zero Configuration: Simply download the generated TypeScript file and start using fully-typed feature flags in your application immediately.
When you access /type/typescript
, FlagFlow generates a complete TypeScript file
containing:
FlagFlow maps its internal flag types to appropriate TypeScript types:
FlagFlow Type | TypeScript Type | Example |
---|---|---|
BOOLEAN | boolean | true | false |
INTEGER | number | 42 |
STRING | string | "hello world" |
ENUM | Union Type | "red" | "green" | "blue" |
TAG | Array of Union | ("red" | "green" | "blue")[] |
AB-TEST | string | "A" | "B" (runtime) |
Here's an example of the TypeScript types generated for a hierarchical flag structure:
// Generated TypeScript Definitions // Root type for all flags export type FlagFlow = { readonly enableNewFeature: boolean; readonly maxRetries: number; readonly theme: "light" | "dark" | "auto"; readonly enabledFeatures: ("analytics" | "chat" | "notifications")[]; readonly abTestVariant: string; // AB-TEST type readonly accounting: FlagFlow__Accounting; }; // Nested group type export type FlagFlow__Accounting = { readonly enableCurrencyExchange: boolean; readonly defaultCurrency: "USD" | "EUR" | "HUF"; readonly huf: FlagFlow__Accounting__Huf; }; // Deeply nested group type export type FlagFlow__Accounting__Huf = { readonly exchangeRate: number; readonly allowExchange: boolean; }; // Complete type mapping for all groups export type FlagFlow_DescriptorTypeMap = { '#root': FlagFlow; 'accounting': FlagFlow__Accounting; 'accounting/huf': FlagFlow__Accounting__Huf; };
// Generated default value objects for runtime use export const defaultFlagFlow: FlagFlow = { enableNewFeature: false, maxRetries: 3, theme: "auto", enabledFeatures: ["analytics"], abTestVariant: "A", // Default for AB-TEST accounting: defaultFlagFlow__Accounting, }; export const defaultFlagFlow__Accounting: FlagFlow__Accounting = { enableCurrencyExchange: true, defaultCurrency: "USD", huf: defaultFlagFlow__Accounting__Huf, }; export const defaultFlagFlow__Accounting__Huf: FlagFlow__Accounting__Huf = { exchangeRate: 380.5, allowExchange: false, };
The TypeScript file includes complete, ready-to-use client code with full type safety:
import axios from "axios"; import { flagFlow_Descriptors, FlagFlow_DescriptorTypeMap } from "./flagflowTypes"; const FLAGFLOW_BASE_URL = 'http://localhost:3000/flags'; // Basic fetch function with full type safety export const fetchData = async <K extends keyof FlagFlow_DescriptorTypeMap>( key: K ): Promise<FlagFlow_DescriptorTypeMap[K]> => { const { uri, hash } = flagFlow_Descriptors[key]; const { data } = await axios.get<FlagFlow_DescriptorTypeMap[K]>( FLAGFLOW_BASE_URL + uri, { responseType: 'json', headers: { 'Content-Type': 'application/json', 'x-accept-flaggroup-hash': hash, } }) return data } // Factory function for creating fetch functions export const createFetchFunction = <K extends keyof FlagFlow_DescriptorTypeMap>( key: K ): (() => Promise<FlagFlow_DescriptorTypeMap[K]>) => () => fetchData(key); // Cached fetch function with TTL support export const createFetchFunctionWithCache = <K extends keyof FlagFlow_DescriptorTypeMap>( key: K, ttlSeconds: number = 60 ) => { const fetchDataFn = createFetchFunction(key); let data: FlagFlow_DescriptorTypeMap[K] | undefined; let lastFetchTime: number = 0; return async (): Promise<FlagFlow_DescriptorTypeMap[K]> => { const now = Date.now(); if (data && (now - lastFetchTime < ttlSeconds * 1000)) return data; data = await fetchDataFn(); lastFetchTime = now; return data; } }
import { fetchData, FlagFlow } from './flagflowTypes'; // Fetch all root flags with full type safety const flags: FlagFlow = await fetchData('#root'); // Access flags with IntelliSense support if (flags.enableNewFeature) { console.log('New feature is enabled!'); } // Type-safe enum access console.log(`Current theme: ${flags.theme}`); // "light" | "dark" | "auto" // Array type support if (flags.enabledFeatures.includes('analytics')) { initializeAnalytics(); } // Nested group access const accountingFlags = await fetchData('accounting'); console.log(`Exchange enabled: ${accountingFlags.enableCurrencyExchange}`);
import { createFetchFunctionWithCache } from './flagflowTypes'; // Create cached fetch function (30 second TTL) const getCachedFlags = createFetchFunctionWithCache('#root', 30); // Use in your application async function checkFeatures() { const flags = await getCachedFlags(); // Uses cache if fresh return { showNewUI: flags.enableNewFeature, theme: flags.theme, maxRetries: flags.maxRetries }; } // Multiple calls within 30 seconds use cached data const features1 = await checkFeatures(); // Network request const features2 = await checkFeatures(); // From cache const features3 = await checkFeatures(); // From cache
import React, { useState, useEffect } from 'react'; import { createFetchFunctionWithCache, FlagFlow } from './flagflowTypes'; // Create cached fetcher outside component const getCachedFlags = createFetchFunctionWithCache('#root', 60); export function useFeatureFlags() { const [flags, setFlags] = useState<FlagFlow | null>(null); const [loading, setLoading] = useState(true); const [error, setError] = useState<Error | null>(null); useEffect(() => { let mounted = true; getCachedFlags() .then(data => { if (mounted) { setFlags(data); setLoading(false); } }) .catch(err => { if (mounted) { setError(err); setLoading(false); } }); return () => { mounted = false; }; }, []); return { flags, loading, error }; } // Usage in component function MyComponent() { const { flags, loading, error } = useFeatureFlags(); if (loading) return <div>Loading flags...</div>; if (error) return <div>Error: {error.message}</div>; if (!flags) return null; return ( <div className={flags.theme}> {flags.enableNewFeature && <NewFeatureComponent />} <Analytics enabled={flags.enabledFeatures.includes('analytics')} /> </div> ); }
# Get TypeScript definitions curl http://localhost:3000/type/typescript # Save to file curl http://localhost:3000/type/typescript > flagflowTypes.ts # Download as file attachment curl http://localhost:3000/type/typescript?download=1
The generated file includes helpful metadata:
// This file is auto-generated by FlagFlow // Generated at: 2024-08-10T14:30:25.123Z // FlagFlow Version: 1.0.0 // Total Flags: 15 // Total Groups: 4 // // To regenerate this file: // curl http://localhost:3000/type/typescript > flagflowTypes.ts // // ⚠️ Do not edit this file manually - changes will be lost!
// flagService.ts import { createFetchFunctionWithCache, FlagFlow } from './flagflowTypes'; class FlagService { private getCachedFlags = createFetchFunctionWithCache('#root', 300); // 5 minutes private flags: FlagFlow | null = null; async initialize(): Promise<void> { try { this.flags = await this.getCachedFlags(); console.log('✅ Feature flags loaded successfully'); } catch (error) { console.error('❌ Failed to load feature flags:', error); throw error; } } isEnabled(feature: keyof FlagFlow): boolean { return this.flags?.[feature] === true; } getValue<K extends keyof FlagFlow>(key: K): FlagFlow[K] | null { return this.flags?.[key] || null; } async refresh(): Promise<void> { // Force refresh by creating new fetcher this.getCachedFlags = createFetchFunctionWithCache('#root', 300); await this.initialize(); } } export const flagService = new FlagService(); // Initialize at app startup flagService.initialize().catch(console.error);
// config.ts const FLAGFLOW_URLS = { development: 'http://localhost:3000', staging: 'https://staging-flags.example.com', production: 'https://flags.example.com' }; const environment = process.env.NODE_ENV || 'development'; export const FLAGFLOW_BASE_URL = FLAGFLOW_URLS[environment] + '/flags'; // flagService.ts - Updated with environment config import { FLAGFLOW_BASE_URL } from './config'; // Override the generated base URL const originalFetchData = fetchData; export const fetchData = async <K extends keyof FlagFlow_DescriptorTypeMap>( key: K ): Promise<FlagFlow_DescriptorTypeMap[K]> => { const { uri, hash } = flagFlow_Descriptors[key]; const { data } = await axios.get<FlagFlow_DescriptorTypeMap[K]>( FLAGFLOW_BASE_URL + uri, { responseType: 'json', headers: { 'Content-Type': 'application/json', 'x-accept-flaggroup-hash': hash, } }); return data; };
curl /type/typescript > flagflowTypes.ts