better-experiments
Version:
Developer-first A/B testing library
223 lines (218 loc) • 7.29 kB
TypeScript
/**
* Core types for Better-Experiments A/B testing framework
*/
interface ABTestConfig {
/** Unique identifier for the test */
testId: string;
/** Array of variant names/identifiers */
variants: VariantValue[];
/** Weight distribution for variants (optional, defaults to equal distribution) */
weights?: number[];
/** Whether the test is currently active */
active?: boolean;
/** Test metadata */
metadata?: {
name?: string;
description?: string;
createdAt?: Date;
createdBy?: string;
};
}
interface UserAssignment {
/** Unique identifier for this assignment */
id: string;
/** Test identifier */
testId: string;
/** User identifier (cookie ID, user ID, etc.) */
userId: string;
/** Assigned variant */
variant: VariantValue;
/** Timestamp of assignment */
assignedAt: Date;
/** Optional metadata for this assignment */
metadata?: Record<string, any>;
}
interface TestAssignment<T = any> {
/** The variant value assigned to the user */
variant: T;
/** Full assignment details */
assignment: UserAssignment;
/** Method to track conversions for this assignment */
convert(event?: string, metadata?: Record<string, any>): Promise<void>;
}
interface ConversionEvent {
/** Unique identifier for this conversion */
id: string;
/** Test identifier */
testId: string;
/** User identifier */
userId: string;
/** Event name/type */
event: string;
/** Assigned variant at time of conversion */
variant: VariantValue;
/** Assignment ID that led to this conversion */
assignmentId: string;
/** Timestamp of conversion */
convertedAt: Date;
/** Optional metadata */
metadata?: Record<string, any>;
}
interface TestResults {
/** Test configuration */
config: ABTestConfig;
/** Results per variant */
variants: VariantResults[];
/** Test statistics */
stats: TestStats;
}
interface VariantResults {
/** Variant name */
variant: VariantValue;
/** Total users assigned to this variant */
totalUsers: number;
/** Total conversions for this variant */
totalConversions: number;
/** Conversion rate (conversions / users) */
conversionRate: number;
/** Conversion events breakdown */
events: Record<string, number>;
}
interface TestStats {
/** Total test duration in days */
durationDays: number;
/** Statistical significance (if calculable) */
significance?: number;
/** Confidence interval */
confidenceInterval?: number;
/** Whether results are statistically significant */
isSignificant?: boolean;
/** Recommended winner variant (if any) */
winner?: VariantValue;
}
interface StorageAdapter {
/** Save test configuration */
saveTest(config: ABTestConfig): Promise<void>;
/** Get test configuration */
getTest(testId: string): Promise<ABTestConfig | null>;
/** Get all tests */
getAllTests(): Promise<ABTestConfig[]>;
/** Save user assignment */
saveAssignment(assignment: UserAssignment): Promise<void>;
/** Get user assignment for a test */
getAssignment(testId: string, userId: string): Promise<UserAssignment | null>;
/** Get assignment by unique ID */
getAssignmentById(assignmentId: string): Promise<UserAssignment | null>;
/** Save conversion event */
saveConversion(event: ConversionEvent): Promise<void>;
/** Get all conversions for a test */
getConversions(testId: string): Promise<ConversionEvent[]>;
/** Get test results with statistics */
getTestResults(testId: string): Promise<TestResults | null>;
}
interface BetterExperimentConfig {
/** Storage adapter for persisting data */
storage?: StorageAdapter;
/** Cookie configuration for browser environments */
cookie?: {
name?: string;
domain?: string;
path?: string;
secure?: boolean;
sameSite?: "strict" | "lax" | "none";
maxAge?: number;
};
/** Debug mode */
debug?: boolean;
}
type VariantValue = string | number | boolean | object;
/**
* BetterExperiments client - the main interface for A/B testing
*/
declare class BetterExperiments {
private storage;
private debug;
private cookieConfig;
constructor(config?: BetterExperimentConfig);
/**
* Run an A/B test - returns assignment object with convert method
*/
test(testId: string, variants: VariantValue[], options?: {
userId?: string;
weights?: number[];
metadata?: Partial<ABTestConfig["metadata"]>;
}): Promise<TestAssignment<VariantValue>>;
/**
* Create a new test manually (optional - tests are auto-created)
*/
createTest(config: Omit<ABTestConfig, "metadata"> & {
metadata?: Partial<ABTestConfig["metadata"]>;
}): Promise<ABTestConfig>;
/**
* Get test results and statistics
*/
getResults(testId: string): Promise<TestResults | null>;
/**
* Get all tests
*/
getTests(): Promise<ABTestConfig[]>;
/**
* Stop/deactivate a test
*/
stopTest(testId: string): Promise<void>;
/**
* Get user's assignment for a specific test
*/
getAssignment(testId: string, userId?: string): Promise<UserAssignment | null>;
/**
* Track conversion using assignment (internal method)
*/
private trackConversion;
/**
* Private methods
*/
private resolveUserId;
private defaultGetUserId;
private getCookieUserId;
private setCookieUserId;
private assignVariant;
private generateEqualWeights;
private validateTestConfig;
}
/**
* In-memory storage adapter for development and testing
* Data is lost when the process restarts
*/
declare class MemoryStorage implements StorageAdapter {
private tests;
private assignments;
private conversions;
saveTest(config: ABTestConfig): Promise<void>;
getTest(testId: string): Promise<ABTestConfig | null>;
getAllTests(): Promise<ABTestConfig[]>;
saveAssignment(assignment: UserAssignment): Promise<void>;
getAssignment(testId: string, userId: string): Promise<UserAssignment | null>;
getAssignmentById(assignmentId: string): Promise<UserAssignment | null>;
saveConversion(event: ConversionEvent): Promise<void>;
getConversions(testId: string): Promise<ConversionEvent[]>;
getTestResults(testId: string): Promise<TestResults | null>;
/**
* Utility methods for debugging/testing
*/
clear(): Promise<void>;
getStats(): Promise<{
testsCount: number;
assignmentsCount: number;
conversionsCount: number;
}>;
}
/**
* Create a deterministic numeric hash from a string using MurmurHash3.
* This ensures the same user gets the same variant across sessions.
* The output is an unsigned 32-bit integer.
* @param input The string to hash (e.g., testId + userId).
* @returns An unsigned 32-bit integer hash.
*/
declare function createUserHash(input: string): number;
export { BetterExperiments, MemoryStorage, createUserHash };
export type { ABTestConfig, BetterExperimentConfig, ConversionEvent, StorageAdapter, TestResults, TestStats, UserAssignment, VariantResults, VariantValue };