Deterministic sticky A/B variant assignment and deduped exposure tracking as a Convex component, with GDPR erasure and multi-tenant scoping built in.
npm install @vllnt/convex-experimentsA/B Experiments is deterministic sticky assignment and deduped exposure tracking as a Convex component: define experiments with weighted variants, enroll subjects via `logExposure`, and read per-variant tallies from `results` — no external experimentation service required. Assignment is computed from `(salt, subjectRef)` and is immutable once set, so `peek` resolves the same variant without writing (flicker-free first paint), exposures are deduped, and `results` reads maintained O(variants) tallies without scanning the exposure table. Definitions, assignments, and tallies live in sandboxed component tables; scopes namespace per tenant or surface; the host owns auth and gates all lifecycle mutations; and it runs identically on Convex Cloud and self-hosted convex-backend.
The @vllnt/convex-experiments component integrates directly into your Convex app via defineApp. You call experiments.define to create an experiment with weighted variants, then call experiments.logExposure inside any mutation to enroll a subject and record the exposure in one step. The variant string is returned immediately and can gate behavior in the same function call.
The @vllnt/convex-experiments component derives the variant deterministically from a hash of the experiment salt and subjectRef, so the same subject always lands in the same bucket even before anything is stored. The peek method exposes this as a read-only query with no writes, which is useful for SSR or flicker-free first paint scenarios.
Calling logExposure deduplicates writes so each subject produces exactly one tallied row per experiment. The results method returns per-variant counts for assigned, subjects, exposures, and weight without scanning the raw exposure table, keeping reads efficient as the experiment scales.
The @vllnt/convex-experiments component accepts an optional scope parameter on every method. Passing a tenant or surface identifier folds it into the hash so bucketing is independent per namespace. All enrollment, assignment, and erasure operations respect the scope, letting a single component installation serve multiple tenants.
No. In @vllnt/convex-experiments, once a subject is assigned to a variant that assignment is immutable. The variants and salt fields are also locked after the first enrollment, and attempting to change them throws an error. To re-randomize a cohort you define a new experiment key.
The @vllnt/convex-experiments component exposes a forgetSubject mutation that deletes a single subject's assignment and exposure records for a given experiment key and optional scope. The deleteExperiment mutation performs a full cascade delete of all data for an experiment. Neither operation affects the per-variant result tallies for other subjects.
The @vllnt/convex-experiments component is auth-agnostic. The host application is responsible for resolving identity and deciding who may call define, setStatus, logExposure, or forgetSubject. The component receives an opaque subjectRef string and never inspects it, so you pass in whatever identifier your auth layer provides.
Yes. The component exposes a peek query that returns the deterministic variant for a subject without writing any data. The optional @vllnt/convex-experiments/react package provides a useVariant hook that wraps a host-re-exported peek query reference, enabling flicker-free first paint. The React package is tree-shakeable and is not pulled in for backend-only consumers.
The @vllnt/convex-experiments component requires convex@^1.41.0 as a peer dependency. It is registered by importing the convex.config entry point and calling app.use(experiments) inside your convex/convex.config.ts file, following the standard Convex component installation pattern.