abmeter
Version:
ABMeter browser SDK — feature flags and A/B experiments with server-side pre-evaluated assignments
115 lines (108 loc) • 3.81 kB
text/typescript
interface UserInput {
userId?: string;
email?: string;
}
interface User {
userId: string;
email?: string;
}
type Logger = (message: string, payload?: unknown) => void;
type ErrorCallback = (error: unknown) => void;
interface ConfigInput {
apiKey: string;
baseUrl?: string;
user?: UserInput;
/** Milliseconds between background flushes. */
flushInterval?: number;
logger?: Logger;
errorCallback?: ErrorCallback;
}
interface Config {
apiKey: string;
baseUrl: string;
flushIntervalMs: number;
logger: Logger;
errorCallback?: ErrorCallback;
}
interface ExposureRecord {
parameter_id: number;
space_id: number;
resolved_value: unknown;
user_id: string;
exposable_type: 'Experiment';
exposable_id: number;
audience_id: number;
resolved_at: string;
}
/**
* Initialize the SDK singleton. Cache-then-network: hydrates synchronously
* from localStorage when a cached map exists, then refreshes in the background
* (await ready() to know the refresh settled). Throws on misconfiguration —
* a bad apiKey must be loud, not error-safe.
*/
declare function configure(input: ConfigInput): void;
/** Resolves when the background assignments refresh has settled (fetched or failed). */
declare function ready(): Promise<void>;
/**
* Resolved value for this user, or undefined when unknown/unconfigured. Queues
* an exposure lazily — only experiment resolutions carry exposure metadata,
* and repeats inside the dedup window are not re-queued.
*/
declare function resolveParameter(slug: string): unknown;
/** The exposure record resolveParameter would submit, or null — without queueing anything. */
declare function getExposure(slug: string): ExposureRecord | null;
/**
* Queue an event for the configured user.
*
* Deliberately takes no user id, unlike the server-side SDKs: there one
* process serves every user, so each call must say who it is for, while a
* page has exactly the one user configure() established. An override would
* only ever detach the event — results attribute events to a visitor by
* matching the id their exposure was recorded under, so an event under any
* other id is stored, counted, and never joined.
*/
declare function trackEvent(eventSlug: string, customFields?: Record<string, unknown>): void;
/** Drain the queue now (e.g. on SPA route changes). */
declare function flush(): Promise<void>;
/**
* Drain fully and tear down timers/listeners; configure() again to restart.
* force drops the queue instead of draining it.
*/
declare function reset(options?: {
force?: boolean;
}): Promise<void>;
declare const VERSION = "0.2.2";
interface ExposureMetadata {
exposable_type: 'Experiment';
exposable_id: number;
audience_id: number;
}
interface Assignment {
value: unknown;
parameter_id: number;
space_id: number;
/** null for feature-flag and default resolutions — never report those as exposures. */
exposure: ExposureMetadata | null;
}
interface AssignmentsPayload {
user: {
user_id: string;
email?: string | null;
};
assignments: Record<string, Assignment>;
}
interface ErrorBody {
error?: unknown;
details?: {
failures?: unknown[];
invalid_count?: number;
};
}
declare class ApiError extends Error {
readonly status: number;
readonly details: ErrorBody['details'];
constructor(status: number, body: unknown);
get retryable(): boolean;
get partialFailure(): boolean;
}
export { ApiError, type Assignment, type AssignmentsPayload, type Config, type ConfigInput, type ErrorCallback, type ExposureMetadata, type ExposureRecord, type Logger, type User, type UserInput, VERSION, configure, flush, getExposure, ready, reset, resolveParameter, trackEvent };