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

@sdxc/random

v2026.11.10

Published

Seeded and system random streams with integer, float, pick and shuffle draws, and resumable state

Readme

@sdxc/random

Seeded and system random streams with integer, float, pick and shuffle draws, and resumable state.

Installation

npm add @sdxc/random

The main entry point has no dependencies. The @sdxc/random/schema entry point needs remix for remix/data-schema.

Usage

Roll from a seed

import { createRandom } from "@sdxc/random";

let dice = createRandom("battle-42");

dice.int(1, 6); // the same values, in the same order, on every run
dice.float(0.85, 1);
dice.bool(0.1);
dice.pick(["rock", "paper", "scissors"]);
dice.shuffle([1, 2, 3, 4, 5]);

Accept randomness in your own API

Type the parameter as Random and default it to systemRandom(). Production passes nothing; a test passes a seeded stream instead of stubbing Math.random.

import type { Random } from "@sdxc/random";

import { createRandom, systemRandom } from "@sdxc/random";

function shouldSample(rate: number, random: Random = systemRandom()): boolean {
	return random.bool(rate);
}

shouldSample(0.1); // Web Crypto backed
shouldSample(0.1, createRandom("sampler-test")); // reproducible

Save and resume a stream

state() is plain JSON, and restoreRandom continues with the draw the original would have taken next.

import { createRandom, restoreRandom } from "@sdxc/random";

let encounters = createRandom("world-7");
encounters.int(1, 100);

let saved = JSON.stringify(encounters.state());
let resumed = restoreRandom(JSON.parse(saved));

resumed.int(1, 100) === encounters.int(1, 100); // true

Split one seed into independent streams

derive(label) opens a stream seeded from the parent's seed and the label, so extra draws in one part of a program leave the values of another part unchanged.

import { createRandom, systemSeed } from "@sdxc/random";

let session = createRandom(systemSeed());

let battle = session.derive("battle");
let loot = session.derive("loot");

API

createRandom(seed: Seed): SeededRandom

Opens a stream on a seed. The same seed produces the same values in the same order on any machine; 42 and "42" name the same stream.

restoreRandom(state: RandomState): SeededRandom

Rebuilds a stream from a state() snapshot, positioned exactly where the snapshot was taken.

systemRandom(): Random

An unseeded stream drawing from crypto.getRandomValues, for jitter, sampling and tie-breakers. It has no seed or state, so it never replays. Generate keys and secrets with Web Crypto directly.

systemSeed(): number

A fresh 32-bit seed from crypto.getRandomValues. Log it, and passing it back to createRandom replays the run.

RANDOM_STATE_SCHEMA: Schema<unknown, RandomState>

From @sdxc/random/schema. A remix/data-schema schema for a RandomState read from storage: the seed is a string or number, and each of the four words is an unsigned 32-bit integer.

Types

Random

The draws every stream offers:

  • next(): the raw draw, in [0, 1)
  • int(min, max): an integer with both ends included; throws RangeError when a bound is not a safe integer or max < min
  • float(min = 0, max = 1): a number in [min, max)
  • bool(chance = 0.5): true with probability chance
  • pick(items): one element, each equally likely; throws RangeError on an empty list
  • shuffle(items): a shuffled copy, leaving the input in its order

SeededRandom

A Random plus seed, derive(label: string): SeededRandom and state(): RandomState.

Seed

number | string.

RandomState

{ seed: Seed; words: [number, number, number, number] }, the seed and the generator's four unsigned 32-bit words.

Pattern: A Reproducible Fuzz Test

Draw a fresh seed per run unless one is supplied, and put it in the suite name, so a failure reports the number that replays it.

import { createRandom, systemSeed } from "@sdxc/random";
import { describe, expect, test } from "vitest";

import { parseCron } from "./cron.js";

let seed = Number(process.env.FUZZ_SEED) || systemSeed();

describe(`cron parsing (FUZZ_SEED=${seed})`, () => {
	test("every generated expression parses", () => {
		let random = createRandom(seed);

		for (let index = 0; index < 1000; index++) {
			let minute = random.int(0, 59);
			let hour = random.int(0, 23);
			let weekday = random.pick(["MON", "TUE", "WED", "THU", "FRI"]);
			expect(parseCron(`${minute} ${hour} * * ${weekday}`).success).toBe(true);
		}
	});
});

Pattern: Stable Procedural Generation

Give each feature of a generated world its own derived stream. Adding a draw to the terrain generator later keeps every existing world's loot exactly as it was.

import type { Random } from "@sdxc/random";

import { createRandom } from "@sdxc/random";

function terrain(random: Random): string[] {
	return Array.from({ length: 8 }, () => random.pick(["plains", "forest", "hills", "lake"]));
}

function loot(random: Random): { gold: number; items: string[] } {
	let gold = random.int(10, 100);
	let items = random.shuffle(["sword", "shield", "potion", "map"]).slice(0, random.int(1, 3));
	return { gold, items };
}

function generateWorld(seed: string) {
	let world = createRandom(seed);
	return {
		terrain: terrain(world.derive("terrain")),
		loot: loot(world.derive("loot")),
	};
}

generateWorld("world-7"); // identical output for "world-7" on every machine

Pattern: Persisting A Simulation

Store the stream's state next to the data it shaped, and validate it on load, since a save file is untrusted input.

import * as s from "remix/data-schema";

import { createRandom, restoreRandom } from "@sdxc/random";
import { RANDOM_STATE_SCHEMA } from "@sdxc/random/schema";

let saveSchema = s.object({ turn: s.number(), random: RANDOM_STATE_SCHEMA });

let random = createRandom("campaign");
let turn = 0;

function save(): string {
	return JSON.stringify({ turn, random: random.state() });
}

function load(text: string): boolean {
	let parsed = s.parseSafe(saveSchema, JSON.parse(text));
	if (!parsed.success) return false;

	turn = parsed.value.turn;
	random = restoreRandom(parsed.value.random);
	return true;
}

A save that fails the schema leaves the running simulation untouched, and load answers false.

Versioning

Releases are dated rather than semantic. A version is the UTC date it was published, written YYYY.M.D, so 2026.9.4 is the release from 4 September 2026. At most one release goes out per day.

Those numbers say when, not what: a later date means a later release and carries no compatibility promise. Any release may change or remove an export.

Depend on one exact date, and move it when you are ready to take the change:

{
	"dependencies": {
		"@sdxc/random": "2026.9.4"
	}
}

A caret or tilde range reads the date as major, minor and patch, so it accepts every later release in the same year. An exact version keeps the upgrade yours to schedule.

License

MIT

Author

Sergio Xalambrí