/** * Feature Flag Management System * Centralized feature flag handling with type safety and runtime updates */ import type { z } from 'zod'; /** * Type for feature flag values */ export type FeatureFlagValue = boolean | string | number; /** * Interface for a feature flag definition */ export interface FeatureFlag { key: string; defaultValue: T; description?: string; allowedValues?: T[]; // For enum-like flags category?: string; } /** * Feature flag registry - stores all defined flags */ export interface FeatureFlagRegistry { [key: string]: FeatureFlag; } /** * Feature flag resolver - handles flag resolution logic */ export class FeatureFlagResolver { private flags: FeatureFlagRegistry; private resolvedCache: Map = new Map(); constructor(flags: FeatureFlagRegistry) { this.flags = flags; } /** * Resolve a feature flag value * Priority: Environment > Cache > Default */ resolve(key: string, defaultValue: T): T { // Check cache first if (this.resolvedCache.has(key)) { return this.resolvedCache.get(key)! as T; } // Check environment variable (allows runtime updates) const envValue = process.env[`FLAG_${key.toUpperCase()}`]; if (envValue !== undefined) { // Try to parse as JSON first, then as boolean, then as string let parsed: FeatureFlagValue; try { parsed = JSON.parse(envValue); } catch { parsed = envValue.toLowerCase() === 'true' ? true : envValue.toLowerCase() === 'false' ? false : envValue; } // Validate against allowed values if defined const flag = this.flags[key]; if (flag && flag.allowedValues && !flag.allowedValues.includes(parsed)) { console.warn(`Invalid value for flag ${key}: ${parsed}. Using default.`); parsed = defaultValue as FeatureFlagValue; } this.resolvedCache.set(key, parsed); return parsed as T; } // Use cached value if available if (this.resolvedCache.has(key)) { return this.resolvedCache.get(key)! as T; } // Return default this.resolvedCache.set(key, defaultValue as FeatureFlagValue); return defaultValue as T; } /** * Check if a flag is enabled (boolean check) */ isEnabled(key: string, defaultValue: T): T { return this.resolve(key, defaultValue) as T; } /** * Get flag definition */ getDefinition(key: string): FeatureFlag | undefined { return this.flags[key]; } /** * List all registered flags */ getAllFlags(): FeatureFlagRegistry { return { ...this.flags }; } /** * Clear the resolution cache (useful for testing) */ clearCache(): void { this.resolvedCache.clear(); } } /** * Feature flag configuration with pre-defined flags */ export const featureFlags: FeatureFlagRegistry = { // SpamShield Feature Flags 'spamshield.enable.number.reputation': { key: 'spamshield_enable_number_reputation', defaultValue: true, description: 'Enable number reputation checking (Hiya API integration)', category: 'spamshield', }, 'spamshield.enable.content.classification': { key: 'spamshield_enable_content_classification', defaultValue: true, description: 'Enable SMS content classification (BERT model)', category: 'spamshield', }, 'spamshield.enable.behavioral.analysis': { key: 'spamshield_enable_behavioral_analysis', defaultValue: true, description: 'Enable call behavioral analysis', category: 'spamshield', }, 'spamshield.enable.community.intelligence': { key: 'spamshield_enable_community_intelligence', defaultValue: true, description: 'Enable community intelligence sharing', category: 'spamshield', }, 'spamshield.enable.real.time.blocking': { key: 'spamshield_enable_real_time_blocking', defaultValue: true, description: 'Enable real-time spam blocking', category: 'spamshield', }, 'spamshield.enable.multiple.sources': { key: 'spamshield_enable_multiple_sources', defaultValue: false, description: 'Enable multiple reputation source aggregation (Truecaller, etc.)', category: 'spamshield', }, 'spamshield.enable.ml.classifier': { key: 'spamshield_enable_ml_classifier', defaultValue: false, description: 'Enable ML-based spam classification', category: 'spamshield', }, // VoicePrint Feature Flags 'voiceprint.enable.ml.service': { key: 'voiceprint_enable_ml_service', defaultValue: false, description: 'Enable ML service integration for voice analysis', category: 'voiceprint', }, 'voiceprint.enable.faiss.index': { key: 'voiceprint_enable_faiss_index', defaultValue: true, description: 'Enable FAISS index for voice matching', category: 'voiceprint', }, 'voiceprint.enable.batch.analysis': { key: 'voiceprint_enable_batch_analysis', defaultValue: true, description: 'Enable batch voice analysis', category: 'voiceprint', }, 'voiceprint.enable.realtime.analysis': { key: 'voiceprint_enable_realtime_analysis', defaultValue: false, description: 'Enable real-time voice analysis', category: 'voiceprint', }, 'voiceprint.enable.mock.model': { key: 'voiceprint_enable_mock_model', defaultValue: true, description: 'Enable mock model for development', category: 'voiceprint', }, // General Platform Flags 'platform.enable.audit.logs': { key: 'platform_enable_audit_logs', defaultValue: true, description: 'Enable comprehensive audit logging', category: 'platform', }, 'platform.enable.kpi.tracking': { key: 'platform_enable_kpi_tracking', defaultValue: true, description: 'Enable KPI snapshot tracking', category: 'platform', }, }; /** * Create a resolver instance with the default flags */ export const featureFlagResolver = new FeatureFlagResolver(featureFlags); /** * Convenience function for quick flag checks */ export function isFeatureEnabled(key: string, defaultValue: T): T { return featureFlagResolver.isEnabled(key, defaultValue); } /** * Check if a flag is enabled with type safety */ export function checkFlag(key: string, defaultValue: T): T { return featureFlagResolver.resolve(key, defaultValue); }