/** * Supported flag value types for multi-variant flags */ export type FlagValue = boolean | string | number | Record; /** * Feature flag definition */ export interface FeatureFlag { id: string; slug: string; name: string; description?: string; category: 'frontend' | 'backend' | 'both'; defaultValue: FlagValue; valueType: FlagValueType; targetServices?: string[]; tags?: string[]; version: number; createdAt: string; updatedAt: string; targetingRules?: TargetingRule[]; rollout?: RolloutConfig; experiment?: Experiment; } /** * Allowed value types for a flag */ export type FlagValueType = 'boolean' | 'string' | 'number' | 'json'; /** * Workspace-level override for a flag */ export interface WorkspaceFeatureFlag { id: string; workspaceId: string; flagId: string; value: FlagValue; version: number; createdAt: string; updatedAt: string; flag?: FeatureFlag; } export type TargetingOperator = 'equals' | 'not_equals' | 'contains' | 'not_contains' | 'starts_with' | 'ends_with' | 'in' | 'not_in' | 'gt' | 'gte' | 'lt' | 'lte' | 'regex' | 'semver_gt' | 'semver_lt' | 'semver_eq'; export interface TargetingCondition { attribute: string; operator: TargetingOperator; value: string | number | boolean | string[]; } export interface TargetingRule { id: string; priority: number; conditions: TargetingCondition[]; value: FlagValue; rolloutPercentage?: number; } export interface RolloutConfig { percentage: number; stickinessKey?: string; salt?: string; buckets?: number; } export interface Variation { id: string; value: FlagValue; weight: number; } export interface Experiment { id: string; name?: string; variations: Variation[]; stickinessKey?: string; salt?: string; } export interface ExperimentAssignment { experimentId: string; variationId: string; value: FlagValue; context: EvaluationContext; } export type TrackingCallback = (assignment: ExperimentAssignment) => void; export interface StreamingConfig { url?: string; reconnectDelayMs?: number; maxReconnectDelayMs?: number; } export interface FlagDocument { flags: FeatureFlag[]; version: number; fetchedAt: string; } export interface CreateFlagData { slug: string; name: string; description?: string; category: 'frontend' | 'backend' | 'both'; valueType?: FlagValueType; defaultValue?: FlagValue; targetServices?: string[]; tags?: string[]; } export interface UpdateFlagData { name?: string; description?: string; category?: 'frontend' | 'backend' | 'both'; defaultValue?: FlagValue; targetServices?: string[]; tags?: string[]; } export interface SetWorkspaceFlagData { value: FlagValue; } /** * Context passed during flag evaluation for targeting/segmentation */ export interface EvaluationContext { workspaceId?: string; userId?: string; attributes?: Record; } export interface FeatureFlagEvaluation { slug: string; value: FlagValue; reason: EvaluationReason; context?: EvaluationContext; evaluatedAt: string; } export type EvaluationReason = 'DEFAULT' | 'WORKSPACE_OVERRIDE' | 'TARGETING_MATCH' | 'PERCENTAGE_ROLLOUT' | 'EXPERIMENT_ASSIGNMENT' | 'FALLBACK' | 'ERROR' | 'LOCAL_OVERRIDE' | 'CACHE_HIT' | 'NOT_FOUND'; export interface BatchEvaluation { flags: Record; context?: EvaluationContext; evaluatedAt: string; } export interface FeatureFlagStats { total: number; byCategory: Record; byTargetService: Record; byValueType: Record; activeWorkspaces: number; } export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'silent'; /** * Injectable logger interface. Users can provide their own logger (e.g. pino, winston). * Defaults to console-based logging. */ export interface ILogger { debug(message: string, ...args: unknown[]): void; info(message: string, ...args: unknown[]): void; warn(message: string, ...args: unknown[]): void; error(message: string, ...args: unknown[]): void; } /** * Circuit breaker configuration */ export interface CircuitBreakerConfig { /** Number of consecutive failures before opening the circuit (default: 5) */ failureThreshold: number; /** Time in ms to wait before attempting a request after circuit opens (default: 30000) */ resetTimeoutMs: number; } /** * Retry configuration */ export interface RetryConfig { /** * Total number of execution attempts (initial + retries). * - Values <= 0 are treated as 1 (with a warning logged) * - NaN uses the default of 3 * - Example: maxAttempts=1 means run once, no retries; maxAttempts=2 means run once, retry once * (default: 3) */ maxAttempts: number; /** Base delay in ms between retries, doubles each attempt (default: 1000) */ baseDelayMs: number; /** Max delay cap in ms (default: 10000) */ maxDelayMs: number; } /** * Callback invoked before every HTTP request. * Return a headers object to merge into the request. * Useful for dynamic auth (JWT, session tokens) that change over time. * * @example * ```ts * requestInterceptor: () => ({ * Authorization: `Bearer ${getToken()}`, * 'x-workspace-id': getWorkspaceId(), * }) * ``` */ export type RequestInterceptor = () => Record | Promise>; /** * Main SDK configuration */ export interface FeatureFlagsConfig { /** Base URL of the feature flags API */ baseUrl: string; /** Optional API key for authentication (sent as `Authorization: Bearer `) */ apiKey?: string; /** HTTP request timeout in ms (default: 10000) */ timeout?: number; /** Enable/disable in-memory cache (default: true) */ cacheEnabled?: boolean; /** Cache TTL in ms (default: 60000) */ cacheTtlMs?: number; /** Retry configuration */ retry?: Partial; /** Circuit breaker configuration */ circuitBreaker?: Partial; /** Log level (default: 'warn') */ logLevel?: LogLevel; /** Prefix for the default ConsoleLogger messages (default: '[FeatureFly]'). Ignored when a custom `logger` is provided. */ logPrefix?: string; /** Custom logger implementation */ logger?: ILogger; /** Local flag overrides — useful for development/testing. These skip HTTP entirely. */ localOverrides?: Record; /** Default values when the server is unreachable and no cache exists */ fallbackDefaults?: Record; /** * Custom HTTP headers merged into every request. * Useful for static auth tokens, workspace IDs, or custom metadata. * * @example * ```ts * headers: { 'x-workspace-id': 'ws-123', 'x-custom': 'value' } * ``` */ headers?: Record; /** * Dynamic request interceptor invoked before every HTTP request. * Returns headers to merge into the request. Supports async for token refresh flows. * Takes precedence over static `headers` for overlapping keys. * * @example * ```ts * requestInterceptor: () => ({ * Authorization: `Bearer ${Cookies.get('accessToken')}`, * }) * ``` */ requestInterceptor?: RequestInterceptor; /** Send cookies with cross-origin requests (default: false) */ withCredentials?: boolean; /** Configure SSE streaming for real-time updates */ streaming?: boolean | StreamingConfig; /** Pass a flag document to enable Edge mode (offline local evaluation) */ edgeDocument?: FlagDocument; /** Pre-evaluated flags to instantly hydrate the client cache (useful for SSR to avoid initial loading states) */ bootstrapFlags?: Record; /** Hook for A/B testing variable assignments */ trackingCallback?: TrackingCallback; } export type FeatureFlyEvent = 'flagEvaluated' | 'flagChanged' | 'cacheHit' | 'cacheMiss' | 'cacheCleared' | 'requestFailed' | 'circuitOpen' | 'circuitClosed' | 'circuitHalfOpen' | 'flagsUpdated' | 'streamConnected' | 'streamDisconnected' | 'experimentAssigned' | 'listenerError'; export interface FlagEvaluatedPayload { slug: string; value: FlagValue; reason: EvaluationReason; durationMs: number; } export interface FlagChangedPayload { slug: string; previousValue: FlagValue; newValue: FlagValue; } export interface RequestFailedPayload { endpoint: string; error: string; attempt: number; } export interface CircuitStatePayload { state: 'open' | 'closed' | 'half-open'; failures: number; } export type EventPayloadMap = { flagEvaluated: FlagEvaluatedPayload; flagChanged: FlagChangedPayload; cacheHit: { key: string; }; cacheMiss: { key: string; }; cacheCleared: void; requestFailed: RequestFailedPayload; circuitOpen: CircuitStatePayload; circuitClosed: CircuitStatePayload; circuitHalfOpen: CircuitStatePayload; flagsUpdated: { source: 'stream' | 'fetch'; slugs?: string[]; count: number; hasVersionGap?: boolean; }; streamConnected: void; streamDisconnected: { error?: Error; }; experimentAssigned: ExperimentAssignment; listenerError: { event: FeatureFlyEvent; error: Error; handler: symbol; }; }; export type EventHandler = (payload: EventPayloadMap[E]) => void;