{"id":43961942,"url":"https://github.com/vnykmshr/rollout-bucket","last_synced_at":"2026-02-07T05:33:54.516Z","repository":{"id":325440166,"uuid":"1101174551","full_name":"vnykmshr/rollout-bucket","owner":"vnykmshr","description":"Deterministic bucketing for feature flags and A/B testing using MurmurHash3. Stateless, fast, TypeScript-native rollout library with zero dependencies.","archived":false,"fork":false,"pushed_at":"2025-11-21T10:10:07.000Z","size":77,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-11-21T12:08:11.052Z","etag":null,"topics":["ab-testing","bucketing","experimentation","feature-flags","feature-toggles","murmurhash","nodejs","rollout","stateless","typescript"],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/vnykmshr.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":".github/CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":".github/SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-11-21T09:51:52.000Z","updated_at":"2025-11-21T10:10:10.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/vnykmshr/rollout-bucket","commit_stats":null,"previous_names":["vnykmshr/rollout-bucket"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/vnykmshr/rollout-bucket","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vnykmshr%2Frollout-bucket","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vnykmshr%2Frollout-bucket/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vnykmshr%2Frollout-bucket/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vnykmshr%2Frollout-bucket/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vnykmshr","download_url":"https://codeload.github.com/vnykmshr/rollout-bucket/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vnykmshr%2Frollout-bucket/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29187229,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-07T05:07:31.176Z","status":"ssl_error","status_checked_at":"2026-02-07T05:06:15.227Z","response_time":63,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ab-testing","bucketing","experimentation","feature-flags","feature-toggles","murmurhash","nodejs","rollout","stateless","typescript"],"created_at":"2026-02-07T05:33:54.415Z","updated_at":"2026-02-07T05:33:54.498Z","avatar_url":"https://github.com/vnykmshr.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# rollout-bucket\n\nDeterministic percentage-based bucketing for feature flags and A/B testing.\n\n[![npm version](https://img.shields.io/npm/v/rollout-bucket.svg)](https://www.npmjs.com/package/rollout-bucket)\n[![CI](https://github.com/vnykmshr/rollout-bucket/actions/workflows/ci.yml/badge.svg)](https://github.com/vnykmshr/rollout-bucket/actions)\n[![License](https://img.shields.io/npm/l/rollout-bucket.svg)](https://github.com/vnykmshr/rollout-bucket/blob/main/LICENSE)\n\n## Overview\n\n`rollout-bucket` uses MurmurHash3 to deterministically assign users to buckets (0-99) for percentage-based feature rollouts and A/B testing. Same user + feature combination always produces the same bucket, enabling consistent experimentation without external state.\n\n**Features:**\n- Deterministic bucketing with feature isolation\n- Statistically uniform distribution (chi-square validated)\n- TypeScript with full generic support\n- Zero runtime dependencies (only `murmur-hash`)\n- Dual module (ESM + CommonJS)\n- Lightweight (\u003c 5KB minified)\n\n## Installation\n\n```bash\nnpm install rollout-bucket\n```\n\n## Quick Start\n\n```typescript\nimport { RolloutBucket } from 'rollout-bucket';\n\nconst rollout = new RolloutBucket();\n\n// Feature flags with percentage rollout\nif (rollout.isEnabled('new-ui', userId, 25)) {\n  // Enabled for 25% of users\n}\n\n// A/B testing\nconst variant = rollout.getVariant('checkout-flow', userId, [\n  { name: 'control', weight: 50 },\n  { name: 'variant-a', weight: 30 },\n  { name: 'variant-b', weight: 20 },\n]);\n\n// Canary deployments\nif (rollout.isEnabled('api-v2', requestId, 5)) {\n  return callApiV2();\n}\n\n// Custom seeds for independent distributions\nconst testRollout = new RolloutBucket(42);\n```\n\n## API\n\n### `new RolloutBucket(seed?: number)`\n\nCreates a bucketing instance. Optional seed (default: 0) creates different distributions.\n\n### `getBucket(feature: string, identifier: string): number`\n\nReturns bucket number (0-99) for a feature + identifier combination.\n\n```typescript\nconst bucket = rollout.getBucket('new-search', 'user-123');\n// Always returns same bucket for this combination\n```\n\nGuarantees:\n- Deterministic: same inputs → same output\n- Feature isolated: different features → uncorrelated buckets\n- Uniform: each bucket has ~1% probability\n\n### `isEnabled(feature: string, identifier: string, percentage: number): boolean`\n\nReturns true if user's bucket is below the percentage threshold.\n\n```typescript\nif (rollout.isEnabled('beta-feature', userId, 25)) {\n  // Runs for users in buckets 0-24 (25%)\n}\n```\n\nEdge cases:\n- `percentage \u003c= 0`: always false\n- `percentage \u003e= 100`: always true\n\n### `getVariant\u003cT\u003e(feature: string, identifier: string, variants: Variant\u003cT\u003e[]): T | null`\n\nSelects a variant from weighted options. Returns null for empty variants array.\n\n```typescript\ninterface Variant\u003cT\u003e {\n  name: T;\n  weight: number; // 0-100\n}\n\n// A/B test\nconst variant = rollout.getVariant('pricing-page', userId, [\n  { name: 'control', weight: 50 },\n  { name: 'treatment', weight: 50 },\n]);\n\n// With TypeScript generics\nconst tier = rollout.getVariant('service-tier', userId, [\n  { name: 1, weight: 60 },\n  { name: 2, weight: 30 },\n  { name: 3, weight: 10 },\n]);\n// Type: number | null\n```\n\nWeights are cumulative: `[{w: 30}, {w: 30}, {w: 40}]` assigns buckets 0-29, 30-59, 60-99. If weights don't sum to 100, remaining buckets get the last variant.\n\n## TypeScript\n\nFully typed with TypeScript. Types are bundled - no separate `@types` installation needed.\n\n```typescript\nimport { RolloutBucket, Variant } from 'rollout-bucket';\n\n// Full type inference\nconst rollout = new RolloutBucket();\nconst bucket: number = rollout.getBucket('feature', 'user');\nconst enabled: boolean = rollout.isEnabled('feature', 'user', 50);\n\n// Generic support for variant names\nconst variant: 'control' | 'treatment' | null = rollout.getVariant(\n  'experiment',\n  'user',\n  [\n    { name: 'control' as const, weight: 50 },\n    { name: 'treatment' as const, weight: 50 },\n  ]\n);\n```\n\n## How It Works\n\nUses MurmurHash3 (32-bit) to convert `feature:identifier` into a deterministic hash, then maps to bucket via modulo:\n\n```\nhash(\"feature:user-123\") → 2847562934 → mod 100 → 34\n```\n\nThis ensures consistency (same user always gets same bucket), independence (different features produce uncorrelated assignments), and uniformity (validated with chi-square tests across 10,000 users).\n\nFeature names are part of the hash input, preventing \"lock-in\" where users always see all new features or none.\n\n## Limitations\n\n**Not cryptographically secure**: MurmurHash3 is designed for speed, not security. Do not use for password hashing, token generation, or any security-sensitive operations.\n\n**No user targeting**: Bucketing is purely hash-based. No support for targeting specific users, segments, or attributes.\n\n**No persistence or analytics**: This is a stateless bucketing library. No feature flag management, event tracking, or remote configuration.\n\nUse when you need deterministic percentage-based rollouts without external dependencies. For advanced feature flag systems with targeting and analytics, consider LaunchDarkly, Optimizely, or similar services.\n\n## Performance\n\n- Hash computation: ~50-100ns per call (Node.js v20)\n- Memory: Negligible overhead (stateless)\n- Bundle size: \u003c 5KB minified\n\n## License\n\nMIT © vnykmshr\n\n## Contributing\n\nSee [CONTRIBUTING.md](.github/CONTRIBUTING.md) for development setup and guidelines. See [CHANGELOG.md](docs/CHANGELOG.md) for version history.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvnykmshr%2Frollout-bucket","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvnykmshr%2Frollout-bucket","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvnykmshr%2Frollout-bucket/lists"}