sortition
v0.1.5
Published
Deterministic percentage rollouts and A/B bucketing — sticky, monotonic, dependency-free, identical in every runtime.
Maintainers
Readme
sortition
Deterministic percentage rollouts and A/B bucketing — sticky, monotonic, dependency-free, identical in every runtime.
The problem
Random assignment breaks experiments — users see different variants per request. Cryptographic hashing adds unnecessary dependencies. You need stable assignment that works identically across all JavaScript runtimes.
Install
npm install sortition
# or
pnpm add sortition
# or
yarn add sortitionUse
import { rollout, bucket } from "sortition";
// Quick win: 10% rollout
const inExperiment = rollout("user-123", {
seed: "new-feature",
percent: 10,
});
// Realistic: A/B test with config
const config = { rollout: 25, weights: { control: 2, variant: 1 } };
if (rollout(userId, { seed: "feature-x", percent: config.rollout })) {
const variant = bucket(userId, { seed: "feature-x", buckets: config.weights });
}API
fraction(unit, seed)
function fraction(unit: string, seed: string): numberStable value in [0, 1) via FNV-1a 32-bit hash over UTF-8 bytes of ${seed}\u0000--\u0000${unit}. Same inputs = same output across all runtimes. NOT for cryptographic use.
rollout(unit, opts)
function rollout(unit: string, opts: { seed: string; percent: number }): booleanReturns true if fraction(unit, seed) < percent / 100. Same unit always gets same result. Throws RangeError if percent is not finite or not in [0, 100].
bucket(unit, opts) — array form
function bucket(unit: string, opts: { seed: string; buckets: readonly string[] }): stringDeterministically selects a bucket using fraction(unit, seed). Throws TypeError if buckets array is empty.
bucket(unit, opts) — weighted form
function bucket(unit: string, opts: { seed: string; buckets: Readonly<Record<string, number>> }): stringBuckets sorted lexicographically before selection (insertion-order independent). Throws TypeError if empty, RangeError if any weight is not positive or finite.
Non-goals
This package will never add: remote config, targeting rules, persistence, analytics, mutual exclusion, or cryptographic hashing. Compose with external tools for these needs:
// Your config layer — read percent from external source
import { rollout } from "sortition";
const percent = readFromFeatureFlagService();
rollout(userId, { seed: "feature-x", percent });Related Packages
Caching & Concurrency:
- @azghr/filterkit — Framework-agnostic, type-safe filtering for TypeScript
- @azghr/singlet — Deduplicate concurrent async calls
- staleness — Stale-while-revalidate caching for async functions
Text Processing:
- @azghr/shorn — Truncate strings by byte budget without breaking graphemes
- seriatim — Sequential processing utilities
HTTP & Network:
- forbear — Read server rate-limit instructions from HTTP responses
- forestall — Delay execution until a condition is met
- obviate — Render operations unnecessary through caching
System & Process:
- quiesce — Ordered, timeboxed graceful shutdown for Node
- stanch — Stop flows or operations based on conditions
Utilities:
- expunge — Remove or exclude items from collections
- occlude — Hide or mask data and functionality
- placemark — Geographic location and mapping utilities
- specie — Currency and financial calculations
License
MIT
