Files
suaveplan/openspec/specs/random-utilities/spec.md
2026-05-27 23:29:53 +01:00

4.3 KiB

Capability: random-utilities

Requirements

Package identity

  • REQ-RAND-001: Package name @suaveplan/random, packages/universal/random.
  • REQ-RAND-002: Runtime deps: @suaveplan/types, @suaveplan/error, tslib, zod. Zero crypto-specific deps.
  • REQ-RAND-003: "sideEffects": false. Subpath exports per feature module.

CSPRNG source

  • REQ-RAND-010: Exports cryptoRng(): Rng — factory returning a WebCrypto-backed Rng object: { nextUint32, nextFloat }. crypto.getRandomValues is the only callsite in this package, located exclusively in src/source/crypto-rng.ts.
  • REQ-RAND-011: Exports getDefaultRng(): Rng and setDefaultRng(rng: Rng): void. Default is cryptoRng() at module init.
  • REQ-RAND-012: setDefaultRng is documented as a test hook. Tests must restore the previous value in afterEach.

Bytes and numbers

  • REQ-RAND-020: Exports randomBytes(n: number, rng?: Rng): Uint8Array — fills via nextUint32() calls.
  • REQ-RAND-021: Exports randomInt(min: number, max: number, rng?: Rng): number — uniform integer in [min, max] inclusive. Throws if min > max.
  • REQ-RAND-022: Exports randomFloat(rng?: Rng): number — uniform float in [0, 1).

String generation

  • REQ-RAND-030: Exports randomString(length: number, opts?: { type?: StringType; characters?: string; rng?: Rng }): string.
  • REQ-RAND-031: StringType = "hex" | "base64" | "base64url" | "url-safe" | "numeric" | "alphanumeric" | "distinguishable" | "ascii-printable".
  • REQ-RAND-032: distinguishable charset: CDEHKMPRTUWXY012458 (no visually ambiguous characters).
  • REQ-RAND-033: type and characters are mutually exclusive; providing both throws RandomError("RANDOM.STRING.INVALID_OPTIONS").

ID generation

  • REQ-RAND-040: Exports randomUUID(rng?: Rng): string — RFC 4122 v4 UUID.
  • REQ-RAND-041: Exports randomUUIDv7(rng?: Rng): string — time-ordered UUID v7: 48-bit ms timestamp + 74 random bits. Monotonically increasing within the same millisecond via sequence counter.
  • REQ-RAND-042: Exports randomULID(rng?: Rng): string — Crockford Base32 encoded ULID.
  • REQ-RAND-043: Exports randomNanoid(size?: number, rng?: Rng): string — URL-safe, default 21 characters.

Collection operations

  • REQ-RAND-050: Exports shuffle<T>(array: T[], rng?: Rng): T[] — Fisher-Yates, immutable.
  • REQ-RAND-051: Exports sample<T>(array: T[], n: number, rng?: Rng): T[] — n distinct elements.
  • REQ-RAND-052: Exports choice<T>(array: T[], rng?: Rng): T — single random element.
  • REQ-RAND-053: Exports weightedChoice<T>(items: T[], weights: number[], rng?: Rng): T.
  • REQ-RAND-054: Exports weightedSample<T>(items: T[], weights: number[], n: number, rng?: Rng): T[] — weighted sampling without replacement.
  • REQ-RAND-055: All collection functions throw RandomError("RANDOM.COLLECTION.EMPTY") on empty input.

Object/Record operations

  • REQ-RAND-060: Exports randomKey<T extends object>(obj: T, rng?: Rng): keyof T.
  • REQ-RAND-061: Exports randomValue<T extends object>(obj: T, rng?: Rng): T[keyof T].
  • REQ-RAND-062: Exports randomEntry<T extends object>(obj: T, rng?: Rng): [keyof T, T[keyof T]].
  • REQ-RAND-063: Exports randomSubset<T extends object>(obj: T, n: number, rng?: Rng): Partial<T>.
  • REQ-RAND-064: All object functions throw RandomError("RANDOM.OBJECT.EMPTY") on empty object.

Game primitives

  • REQ-RAND-070: Exports coin(p?: number, rng?: Rng): boolean — Bernoulli trial. Default p = 0.5. Throws if p outside [0, 1].
  • REQ-RAND-071: Exports dice(sides: number, rng?: Rng): number — uniform integer in [1, sides].
  • REQ-RAND-072: Exports roll(count: number, sides: number, rng?: Rng): number — sum of count dice.

Error surface

  • REQ-RAND-080: Exports RandomError extends SuaveplanError<RandomCode>.
  • REQ-RAND-081: Codes: RANDOM.STRING.INVALID_OPTIONS, RANDOM.COLLECTION.EMPTY, RANDOM.COLLECTION.INSUFFICIENT, RANDOM.OBJECT.EMPTY, RANDOM.GAME.INVALID_SIDES, RANDOM.GAME.INVALID_PROBABILITY.

Quality

  • REQ-RAND-090: 100% statement + branch coverage.
  • REQ-RAND-091: tests/no-disallowed.test.ts: no Math.random, Date.now, crypto.getRandomValues outside src/source/crypto-rng.ts.
  • REQ-RAND-092: fast-check: all collection operations preserve input length constraints; UUID/ULID format invariants.