better-auth-feature-flags
Version:
Ship features safely with feature flags, A/B testing, and progressive rollouts - Better Auth plugin for modern release management
267 lines (251 loc) • 7.27 kB
text/typescript
// SPDX-FileCopyrightText: 2025-present Kriasoft
// SPDX-License-Identifier: MIT
import type { LRUCache } from "./lru-cache";
import type { ContextCollectionOptions } from "./middleware/context";
import type { HeaderConfig, ValidationConfig } from "./middleware/validation";
import type { EvaluationContext, EvaluationReason } from "./schema";
import type { StorageAdapter } from "./storage/types";
/** Type utilities for schema validation and type inference */
/** Extracts boolean flag keys, constrains isEnabled() to boolean flags only */
export type BooleanFlags<Schema extends Record<string, any>> = {
[K in keyof Schema]: Schema[K] extends boolean ? K : never;
}[keyof Schema];
/** Validates schema contains only serializable flag types */
export 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;
/** Infers specific flag value type from schema */
export type InferFlagValue<
Schema extends Record<string, any>,
K extends keyof Schema,
> = Schema[K];
/** Type guard for boolean-only flags */
export function isBooleanFlag<Schema extends Record<string, any>>(
schema: Schema,
key: keyof Schema,
): key is BooleanFlags<Schema> {
return typeof schema[key] === "boolean";
}
/** Plugin context shared across components, storage, and middleware */
export interface PluginContext {
/** Better Auth instance (type not exported directly) */
auth: any;
/** Storage adapter for flag persistence */
storage: StorageAdapter;
/** Normalized plugin configuration */
config: PluginConfig;
/** LRU cache for flag evaluations */
cache: LRUCache<CacheEntry>;
}
/** Normalized plugin configuration with defaults applied */
export interface PluginConfig {
storage: "memory" | "database" | "redis";
debug: boolean;
analytics: {
trackUsage: boolean;
trackPerformance: boolean;
};
adminAccess: {
enabled: boolean;
roles: string[];
};
multiTenant: {
enabled: boolean;
useOrganizations: boolean;
};
caching: {
enabled: boolean;
ttl: number;
maxSize?: number;
};
audit: {
enabled: boolean;
retentionDays: number;
};
contextCollection: ContextCollectionOptions;
customHeaders?: {
enabled: boolean;
whitelist?: HeaderConfig[];
strict?: boolean;
logInvalid?: boolean;
};
contextValidation?: ValidationConfig;
flags: Record<string, StaticFlagConfig>;
}
/** Static flag configuration defined in plugin options */
export interface StaticFlagConfig {
/** Flag enabled state */
enabled?: boolean;
/** Default value when no rules match */
default?: boolean;
/** Percentage rollout (0-100) */
rolloutPercentage?: number;
/** User targeting rules */
targeting?: {
/** Required user roles */
roles?: string[];
/** Specific user IDs */
userIds?: string[];
/** Custom attribute matching */
attributes?: Record<string, any>;
};
/** A/B test variants with weights */
variants?: Array<{
/** Variant identifier */
key: string;
/** Variant value */
value: any;
/** Traffic allocation percentage */
weight?: number;
}>;
}
/** Cache entry storing flag evaluation results with metadata */
export interface CacheEntry {
/** Evaluated flag value */
value: any;
/** A/B test variant if applicable */
variant?: string;
/** Evaluation reason (rule_match, percentage_rollout, etc.) */
reason: string;
/** Additional evaluation metadata */
metadata?: Record<string, any>;
/** Evaluation timestamp */
timestamp: number;
/** Cache TTL in milliseconds */
ttl: number;
}
/** Individual flag evaluation request */
export interface EvaluationRequest {
/** Flag key to evaluate */
key: string;
/** User ID for targeting */
userId?: string;
/** Additional evaluation context */
context?: EvaluationContext;
/** Fallback value */
defaultValue?: any;
}
/** Batch flag evaluation request for multiple flags */
export interface BatchEvaluationRequest {
/** Flag keys to evaluate */
keys: string[];
/** User ID for targeting */
userId?: string;
/** Shared evaluation context */
context?: EvaluationContext;
/** Default values by flag key */
defaults?: Record<string, any>;
}
/** Audit log entry for tracking flag operations */
export interface AuditLogEntry {
/** User performing action */
userId: string;
/** Action type (create, update, delete) */
action: string;
/** Flag key if applicable */
flagKey?: string;
/** Flag ID if applicable (takes precedence over flagKey) */
flagId?: string;
/** Organization ID for multi-tenant scoping */
organizationId?: string;
/** Additional context data */
metadata?: Record<string, any>;
/** Action timestamp */
timestamp?: Date;
}
/** Analytics tracking data for flag evaluations */
export interface EvaluationTracking {
/** Flag key that was evaluated */
flagKey: string;
/** User ID for tracking */
userId: string;
/** Organization ID for multi-tenant scoping */
organizationId?: string;
/** Evaluation context data */
context?: EvaluationContext;
/** Evaluation timestamp */
timestamp: Date;
/** Evaluated flag value */
value?: any;
/** A/B test variant if applicable */
variant?: string;
/** Evaluation reason */
reason?: EvaluationReason;
}
// Public plugin options (moved from src/index.ts to avoid circular deps)
export interface FeatureFlagsOptions {
flags?: {
[key: string]: {
enabled?: boolean;
default?: boolean;
rolloutPercentage?: number; // Percentage 0-100
targeting?: {
roles?: string[];
userIds?: string[];
attributes?: Record<string, any>;
};
variants?: Array<{
key: string;
value: any;
weight?: number; // Distribution weight, must sum to 100 if specified
}>;
};
};
storage?: "memory" | "database" | "redis";
debug?: boolean;
analytics?: {
trackUsage?: boolean;
trackPerformance?: boolean;
};
adminAccess?: {
enabled?: boolean;
roles?: string[];
};
multiTenant?: {
enabled?: boolean;
useOrganizations?: boolean;
};
caching?: {
enabled?: boolean;
ttl?: number; // seconds
maxSize?: number; // Maximum number of cache entries
};
audit?: {
enabled?: boolean;
retentionDays?: number;
};
/**
* Configure what context data to collect for flag evaluation.
* By default, only basic session data is collected for privacy.
*/
contextCollection?: ContextCollectionOptions;
/**
* Configure custom header processing for feature flag evaluation.
* Provides a secure whitelist-based approach for header extraction.
*/
customHeaders?: {
enabled?: boolean;
whitelist?: HeaderConfig[];
strict?: boolean;
logInvalid?: boolean;
};
/**
* Configure validation rules for context data.
* Helps prevent memory exhaustion and security issues.
*/
contextValidation?: ValidationConfig;
}