UNPKG

better-auth-feature-flags

Version:

Ship features safely with feature flags, A/B testing, and progressive rollouts - Better Auth plugin for modern release management

191 lines (186 loc) 6.49 kB
import { BetterAuthClientPlugin } from 'better-auth/client'; /** * Type utilities for flag schema validation and inference */ /** * Extracts boolean-only flag keys from a schema. * Used to constrain isEnabled() to only accept boolean flags. */ type BooleanFlags<Schema extends Record<string, any>> = { [K in keyof Schema]: Schema[K] extends boolean ? K : never; }[keyof Schema]; /** * Validates that a flag schema only contains valid flag value types. * Prevents using complex objects that can't be properly serialized. */ type ValidateFlagSchema<T> = T extends Record<string, any> ? { [K in keyof T]: T[K] extends boolean | string | number | null | undefined ? T[K] : T[K] extends Array<infer U> ? U extends boolean | string | number | null | undefined ? T[K] : never : never; } : never; /** * Helper type to infer the value type of a specific flag */ type InferFlagValue<Schema extends Record<string, any>, K extends keyof Schema> = Schema[K]; /** * Secure override management for feature flags. * * @security Critical: Prevents debug features from leaking to production. * - Disabled in production environments * - Automatic expiration to prevent persistent overrides * - Optional localStorage with encryption consideration */ interface OverrideConfig { /** Allow overrides in production (dangerous!) */ allowInProduction?: boolean; /** Override expiration time in ms (default: 1 hour) */ ttl?: number; /** Persist overrides to localStorage */ persist?: boolean; /** Storage key prefix */ keyPrefix?: string; /** Environment detection override for testing */ environment?: "development" | "production"; } interface FeatureFlagVariant { key: string; value: any; percentage?: number; } interface FeatureFlagResult { value: any; variant?: FeatureFlagVariant; reason: "default" | "rule_match" | "override" | "percentage" | "not_found" | "disabled"; } /** * Feature flags client configuration options. * * @template Schema - Optional flag schema for type safety */ interface FeatureFlagsClientOptions<Schema extends Record<string, any> = Record<string, any>> { /** * Cache configuration. * Controls client-side flag caching for performance and offline support. */ cache?: { enabled?: boolean; ttl?: number; storage?: "memory" | "localStorage" | "sessionStorage"; keyPrefix?: string; version?: string; include?: string[]; exclude?: string[]; }; /** * Smart polling with exponential backoff and jitter. * Prevents thundering herd and gracefully handles server issues. */ polling?: { enabled?: boolean; interval?: number; }; /** * Default flag values */ defaults?: Partial<Schema>; /** * Debug mode */ debug?: boolean; /** * Error handler */ onError?: (error: Error) => void; /** * Evaluation callback */ onEvaluation?: (flag: string, result: any) => void; /** * Context sanitization settings. * Prevents PII leakage and enforces size limits. */ contextSanitization?: { enabled?: boolean; strict?: boolean; allowedFields?: string[]; maxUrlSize?: number; maxBodySize?: number; warnOnDrop?: boolean; }; /** * Override configuration for local testing. * @security Automatically disabled in production unless explicitly allowed. */ overrides?: OverrideConfig; } interface EvaluationContext { attributes?: Record<string, any>; device?: string; browser?: string; version?: string; [key: string]: any; } /** * Type-safe feature flags client interface. * * @template Schema - Optional flag schema for type safety. * Defaults to Record<string, any> for backward compatibility. * * @example * ```typescript * // Define your flag schema * interface MyFlags { * "feature.darkMode": boolean; * "experiment.algorithm": "A" | "B" | "C"; * "config.maxItems": number; * } * * // Use with type safety * const client: FeatureFlagsClient<MyFlags> = createAuthClient(); * ``` */ interface FeatureFlagsClient<Schema extends Record<string, any> = Record<string, any>> { featureFlags: { isEnabled: <K extends keyof Schema>(flag: K & { [P in K]: Schema[P] extends boolean ? K : never; }[K], defaultValue?: boolean) => Promise<boolean>; getValue: <K extends keyof Schema>(flag: K, defaultValue?: Schema[K]) => Promise<Schema[K]>; getVariant: <K extends keyof Schema>(flag: K) => Promise<FeatureFlagVariant | null>; getAllFlags: () => Promise<Partial<Schema>>; evaluateBatch: <K extends keyof Schema>(flags: K[]) => Promise<Record<K, FeatureFlagResult>>; track: <K extends keyof Schema>(flag: K, event: string, value?: number | Record<string, any>) => Promise<void>; setContext: (context: Partial<EvaluationContext>) => void; getContext: () => EvaluationContext; prefetch: <K extends keyof Schema>(flags: K[]) => Promise<void>; clearCache: () => void; setOverride: <K extends keyof Schema>(flag: K, value: Schema[K]) => void; clearOverrides: () => void; refresh: () => Promise<void>; subscribe: (callback: (flags: Partial<Schema>) => void) => () => void; dispose?: () => void; }; } /** * Creates a type-safe feature flags client plugin for Better Auth. * * @template Schema - Optional flag schema for full type safety. * When provided, all flag operations will be type-checked. * * @example * ```typescript * // Without schema (backward compatible) * const client = createAuthClient({ * plugins: [featureFlagsClient()] * }); * * // With type-safe schema * interface MyFlags { * "feature.darkMode": boolean; * "experiment.variant": "A" | "B" | "C"; * } * * const client = createAuthClient({ * plugins: [featureFlagsClient<MyFlags>()] * }); * ``` */ declare function featureFlagsClient<Schema extends Record<string, any> = Record<string, any>>(options?: FeatureFlagsClientOptions<Schema>): BetterAuthClientPlugin; export { type BooleanFlags, type EvaluationContext, type FeatureFlagResult, type FeatureFlagVariant, type FeatureFlagsClient, type FeatureFlagsClientOptions, type InferFlagValue, type ValidateFlagSchema, featureFlagsClient as default, featureFlagsClient };