npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

ransu

v0.1.1

Published

Convenient and easy-to-use random functions.

Readme

ransu

Convenient and easy-to-use random functions.

npm GitHub Workflow Status GitHub Node

ransu — from 乱数 (ransū), Japanese for "random number".

import ransu from "ransu";

ransu.random(); // 0.7263…  — a Math.random() drop-in
ransu.integer(1, 6); // 4        — both ends included
ransu.pick(["🍎", "🍊"]); // '🍊'
ransu.shuffle(deck); // a new array; the input is untouched
ransu.uuid.v7(); // '01997a2c-...' — sortable by time

Everything is synchronous, has zero dependencies, and runs unchanged on Node, Deno, Bun, browsers, Cloudflare Workers and Vercel Edge.

Install

npm install ransu

Three ways in, one name each

Pick whichever fits; they are the same functions.

// 1. Namespace — the shortest thing to type.
import ransu from "ransu";
ransu.integer(1, 6);

// 2. Named exports — tree-shakeable, for bundle-size-sensitive code.
import { integer, pick, shuffle } from "ransu";
integer(1, 6);

// 3. An instance — for libraries, tests and anything reproducible.
import { Random } from "ransu";
new Random(42).integer(1, 6);

There is no second vocabulary to learn: ransu.integer, the named export integer and Random#integer are literally the same function. Moving code from the global to a seeded instance is a one-word change.

CommonJS works the same way, and require gives you the namespace itself rather than a .default wrapper:

const ransu = require("ransu");
const { integer } = require("ransu");

Why not Math.random()

| | Math.random() | ransu | | ---------------------------- | ---------------------------------------------------------- | ------------------------------------------------- | | Uniform integers in a range | Math.floor(Math.random() * n) is biased for most n | unbiased by construction | | Reproducible | never | new Random(seed), plus save/restore/split | | Safe for tokens | no | ransu/secure and every ID API are CSPRNG-backed | | Picking, shuffling, sampling | write it yourself | built in, and correct |

What you get

Numbers

ransu.random(); // [0, 1)
ransu.float(-1, 1); // [min, max)
ransu.integer(1, 6); // [min, max] — inclusive
ransu.below(arr.length); // [0, n)     — what indices want
ransu.range(0, 100, 5); // Python's randrange
ransu.bigint(0n, 2n ** 128n);
ransu.bool(); // true or false, evenly
ransu.chance(0.25); // true a quarter of the time
ransu.bits(12); // Python's getrandbits
ransu.bytes(32); // Uint8Array
ransu.floats(1000); // Float64Array, bounds validated once

integer includes both ends; below, range and float exclude the upper one. Every bounded integer uses Lemire's nearly-divisionless method, so there is no modulo bias — and anything wider than 2^53 is refused rather than silently rounded.

Collections

ransu.pick(items); // one element (throws if empty)
ransu.tryPick(items); // …or undefined
ransu.choices(items, 5); // 5, with replacement
ransu.sample(items, 5); // 5 distinct, random order
ransu.combination(items, 5); // 5 distinct, original order
ransu.reservoir(stream, 5); // 5 from an iterable of unknown length
ransu.shuffle(items); // a new array
ransu.shuffleInPlace(items); // the only mutating one
ransu.takeOut(items); // remove one and return it
ransu.weightedPick(items, [1, 3, 6]); // proportional to weights
ransu.weightedSample(items, w, 3); // 3 distinct, weighted
ransu.subset(items, 0.25); // each element kept with probability p
ransu.pickValue(obj);
ransu.sampleIntegers(10, 0, 1e9); // distinct, without building the range

const loot = ransu.weightedTable(items, [1, 3, 6]);
loot.pick(); // O(1) per draw, via the alias method

Arrays, typed arrays, strings, Set, Map and generators all work.

Text

ransu.string(12); // 'k3Xq7bWm1zPd' — alphanumeric
ransu.string(8, ransu.alphabets.base58);
ransu.hex(32);

Uniform for any alphabet size, not just powers of two. Astral characters and grapheme clusters work too — length counts characters, never UTF-16 units:

ransu.string(5, "🍎🍊🍇"); // '🍇🍎🍇🍊🍎'
ransu.string(5, ransu.graphemes("👍🏽🎉")); // skin tones stay intact

Full Unicode is a first-class option, with named blocks, explicit ranges and sensible exclusions:

ransu.char({ blocks: "emoji" }); // '🚀'
ransu.chars(10, { blocks: "kana" });
ransu.codePoint({ blocks: ["greek", "cyrillic"] });
ransu.chars(20, { ranges: [[0x4e00, 0x9fff]] });

Surrogates can never come out, and controls, private-use areas and noncharacters are excluded unless you opt back in. Blocks available: ascii, latin, latin1, latinExtended, greek, cyrillic, hebrew, arabic, devanagari, thai, hiragana, katakana, kana, kanji, hangul, cjk, punctuation, currency, arrows, math, box, blockElements, geometric, braille, emoji, symbols, printable (the default), bmp, all.

| Option | Effect | | --------------------------------------------------------- | -------------------------------------------- | | ranges | Explicit inclusive [first, last] pairs | | blocks | One or more names from unicodeRanges | | bmpOnly | Keep every character to a single UTF-16 unit | | allowControl / allowPrivateUse / allowNoncharacters | Opt back in | | filter | Arbitrary per-code-point rejection |

Reusing the same options? Build a CodePointSet once and pass it instead.

Dice, shapes and colours

ransu.dice("3d6+2"); // 13
ransu.dice.detail("4d6"); // { total, dice, modifier }
ransu.d20();
ransu.coin(); // 'heads' | 'tails'

ransu.inCircle(5); // [x, y], uniform by area
ransu.onSphere(1); // [x, y, z]
ransu.inRect({ width: 800, height: 600 });

ransu.color(); // '#3f7ac2'
ransu.rgb(); // [63, 122, 194, 1]
ransu.color({ alpha: true }); // '#3f7ac2b3'
ransu.color({ format: "rgb" }); // 'rgb(63 122 194)'

Identifiers

ransu.uuid(); // v4
ransu.uuid.v7(); // time-sortable — the good default for keys
ransu.uuid.v5("python.org", ransu.uuid.NAMESPACE.DNS);
ransu.nanoid(); // 21 URL-safe characters
ransu.ulid({ monotonic: true });
ransu.token(32); // a base64url secret
ransu.otp(6); // '042317'
ransu.password(16, { symbols: true });

Every UUID version RFC 9562 defines is here — v1 through v8, plus NIL, MAX, parse, stringify, validate, version, compare and timestamp. Names and argument order match the uuid package, so it is a drop-in replacement. Identifiers draw from the platform CSPRNG unless you hand them an engine on purpose.

Distributions

Thirty-one of them, one call each:

ransu.normal(170, 6); // 168.42…
ransu.poisson(4); // 3
ransu.binomial(100, 0.3); // 28
ransu.exponential(2);
ransu.zipf(1.2, 1000);

Drawing repeatedly? Build the sampler once — it validates its parameters and precomputes its constants up front. Same name, two shapes: positional arguments give you a number, an options object gives you a sampler, and the sampler lives in ransu/distribution.

import { normal, categorical } from "ransu/distribution";

const height = normal({ mean: 170, sd: 6 });
height.sample(); // one draw
height.samples(10_000); // a Float64Array
height.mean; // 170
height.variance; // 36

const loot = categorical({ weights: [70, 25, 5] }); // O(1) per draw

Continuous: uniform, normal, logNormal, exponential, gamma, beta, chiSquared, studentT, fisherF, cauchy, laplace, logistic, gumbel, pareto, weibull, rayleigh, triangular, irwinHall, bates, vonMises, dirichlet.

Discrete: bernoulli, discreteUniform, categorical, binomial, poisson, geometric, negativeBinomial, hypergeometric, zipf, multinomial.

The normal uses a 128-level ziggurat, gamma uses Marsaglia–Tsang, binomial and Poisson switch between inversion and Hörmann's rejection methods by parameter. Every one is checked in CI against its exact CDF or pmf, not just its mean.

Pass { engine } to any factory to draw from a specific stream:

const r = new Random(42);
normal({ mean: 0, sd: 1, engine: r.engine }).sample();

Retries and scheduling

import { backoff, jitter, date } from "ransu/time";

backoff(attempt); // exponential, with full jitter
backoff(attempt, { strategy: "decorrelated", previous });
jitter(60_000, 0.1); // a poll interval, spread by ±10%
date(startOfYear, now);

Retrying on a fixed schedule makes every client in a fleet retry at the same instant. backoff implements the four standard strategies (full, equal, decorrelated, none) so you do not have to.

Stable randomness per key

import { rollout, bucket, hashPick } from "ransu/hash";

rollout(userId, 0.1); // in the 10% rollout? same answer everywhere
bucket(userId, 16); // a stable shard
hashPick(userId, ["control", "blue", "green"]);

No shared state and no coordination: the key alone decides. Widening a rollout only ever adds users, never moves one out.

Reproducibility

import { Random, engines } from "ransu";

const r = new Random(42); // xoshiro128++ by default
const p = new Random(42, { engine: engines.pcg32 });

const snapshot = r.getState(); // plain JSON
r.setState(snapshot); // …exactly where it was
const [a, b] = r.split(2); // independent streams for workers

Same seed, same sequence — including across ESM and CommonJS, and across runtimes. Nine engines ship: xoshiro128pp (default), xoshiro256pp, pcg32 (with O(log n) advance), sfc32, mulberry32, mt19937 (for compatibility), chacha20, nativeMath and cryptoRandom.

You can also seed the global instance, which makes every top-level function deterministic:

import { seed, integer } from "ransu";
seed(42);
integer(1, 6);

Libraries should own a new Random() instead of calling the global, since an application may re-seed it at any time.

Security

import { integer, shuffle, token } from "ransu/secure";

ransu/secure mirrors the core API on top of the platform CSPRNG and refuses to be seeded. Use it for prize draws, invite codes, session tokens and any shuffle an adversary must not be able to predict.

Subpath entries

One subpath per thing, so an import says what it brings in.

| Import | Contents | | --------------------- | ----------------------------------------------------------------------------------------- | | ransu | Everything ergonomic: numbers, collections, strings, identifiers, distributions, Random | | ransu/engine | Every engine, as classes and seedable factories | | ransu/distribution | Sampler factories for all 31 distributions | | ransu/uuid | Every UUID version, plus parse / stringify / validate / compare | | ransu/nanoid | nanoid only | | ransu/ulid | ULID only | | ransu/token | Tokens, one-time codes, passwords | | ransu/unicode | Code points, blocks, grapheme-aware strings | | ransu/secure | The same API as the root, CSPRNG-backed and unseedable | | ransu/time | Random dates, jitter and retry backoff | | ransu/hash | Stable per-key randomness: A/B rollout, bucketing, sharding | | ransu/compat | Adapters to and from Math.random, seedrandom, pure-rand |

Plural names (engines, distributions) are bags of interchangeable implementations you pick one from; singular names are one thing each.

The root re-exports the whole ergonomic surface, so subpaths are for clarity and for consumers without a bundler — tree-shaking already keeps unused exports out of your build either way.

Drop-in for other libraries

import { toMathRandom } from "ransu/compat";

const rng = toMathRandom(new Random(42));
lodash.shuffle(deck, rng); // now deterministic

Requirements

Node.js >= 18, or any runtime with globalThis.crypto — browsers, Deno, Bun, Cloudflare Workers, Vercel Edge. ransu has no static dependency on any Node built-in, and never returns a Promise.

Performance

pnpm bench reports every case relative to Math.random() measured in the same run. Absolute nanoseconds move with machine load; ratios do not.

| | vs Math.random() | | --- | --- | | random(), seeded xoshiro128++ | 1.21x | | integer(1, 6), seeded | 0.99x | | Math.floor(Math.random()*6)+1, biased | 1.21x | | pick(arr) | 1.38x | | normal() | 1.56x | | bytes(n) in bulk, per byte | 0.09x |

An unbiased integer(1, 6) now costs about what the biased one-liner costs, so correctness is not something you trade speed for.

Raw engine throughput, nextUint32() relative to one Math.random():

| Engine | | | --- | --- | | mulberry32 | 0.36x | | xoshiro128pp (default) | 0.36x | | sfc32 | 0.38x | | nativeMath | 1.29x | | mt19937 | 1.73x | | pcg32 | 2.2x | | chacha20 | 7.6x |

Bulk fills reach roughly 600 MB/s from the seeded engine and 2 GB/s from the platform CSPRNG, so bytes(n) for large n is limited by memory rather than by generation.

Documentation

Guides and full reference: ransu.otnc.dev, built from pages/ with Astro Starlight.

Contributing

Contributions are welcome! See CONTRIBUTING.md for details.

License

Distributed under the MIT License.