6.5 KiB
Design: unify-rng-across-packages
Goal
Single canonical Rng interface across every @suaveplan/* package, with
zero runtime behaviour change and a one-release-cycle deprecation window
for the local aliases that are being phased out.
Strategy
- Structural conformance over re-declaration. The canonical
Rngin@suaveplan/types(src/rng.ts) is the contract. Local types that exactly match this shape become deprecated re-exports of the canonical. Local types that diverge are evaluated case-by-case (see compatibility table below). - Deprecated forwarders, not deletions. Per CLAUDE.md §2, JSDoc
@deprecatedtags are banned. The aliases are kept as named re-exports with prose comments ("Backwards-compatibility alias for@suaveplan/typesRng— prefer the canonical import; this re-export will be removed in a follow-up change.") so existing consumers continue to compile. - Type-only migration. No runtime behaviour changes. Every package's
full test suite must remain green with zero assertion edits before and
after the swap. Verified with
bunx turbo typecheck test --filter=@suaveplan/<pkg>(rule 1: nevertscdirectly).
Compatibility table
| Package | Local symbol | Local surface | Canonical surface | Compatible? | Action |
|---|---|---|---|---|---|
@suaveplan/types |
Rng |
{ nextUint32(): number; nextFloat(): number; nextBytes?(n): Uint8Array; nextIntInRange?(min, maxExclusive): number } |
— (canonical) | — | None — source of truth. |
@suaveplan/crypto-core |
type Rng = ByteSource where ByteSource = (n: number) => Uint8Array |
(n) => Uint8Array (a function, not an object with methods) |
object with nextUint32/nextFloat |
No — structurally incompatible | Drop the Rng alias entirely; keep ByteSource as the package's own concept (distinct contract: a byte-source callable, not a uniform-distribution number source). Re-export the canonical Rng as a deprecated forwarder so any external code that imported crypto-core's Rng keeps compiling but now sees the canonical shape. Note: any consumer that was actually using crypto-core's Rng as a callable will fail typecheck — this is intentional and the migration note in proposal.md flags it. |
@suaveplan/crypto |
re-exports Rng from crypto-core |
same as crypto-core | object | inherits from crypto-core | Update src/index.ts to forward the canonical Rng (via crypto-core's new deprecated re-export). |
@suaveplan/testing |
interface Rng { nextUint32(): number; nextFloat(): number } |
exact subset of canonical | object | Yes — structural match (canonical adds two OPTIONAL members; required members are identical) | Import canonical Rng; re-export as deprecated forwarder. cryptoRng/seededRng implementations satisfy the canonical type without code changes (they implement the required members and omit the optional ones). Add @suaveplan/types as a workspace:^ dependency (rule 8 exception: testing-infrastructure may depend on @suaveplan/types for types-only, byte-size-test guarded). |
Why crypto-core's ByteSource is not the canonical Rng
The canonical Rng is an object with nextUint32 / nextFloat (uniform
number sources). crypto-core's ByteSource is a function that returns
random bytes — a fundamentally different shape and use case (key material,
nonces, AEAD entropy). These should never have shared the Rng name. The
migration cleans up the naming collision: ByteSource stays under its real
name, and Rng becomes the canonical numeric RNG everywhere.
Back-compat strategy
For one release cycle (until the follow-up removal change archives):
@suaveplan/crypto-core/random-bytesexports a deprecatedRngre-export pointing at@suaveplan/types'Rng. The originaltype Rng = ByteSourcealias is removed because keeping both shapes under the same name would break the typecheck.@suaveplan/crypto/indexforwards the same deprecated alias.@suaveplan/testing/contextexports a deprecatedRngre-export of the canonical type. The localinterface Rngdeclaration is removed.
The deprecation prose comment names the canonical import path and the removal change ID (TBD when the follow-up is opened).
Removal follow-up (not in scope here)
A separate OpenSpec change titled
remove-deprecated-rng-aliases (opened at least one release cycle after
this change archives) will:
- Delete the
Rngre-export from@suaveplan/crypto-core/primitives/random-bytes/random-bytes.ts. - Delete the
Rngre-export from@suaveplan/crypto/src/index.ts. - Delete the
Rngre-export from@suaveplan/testing/src/context/context.ts. - Run dependent sweep (
bunx turbo test --filter=...@suaveplan/<pkg>) for each of the three packages. - Update consumers caught by the sweep with the canonical import.
This is tracked as a follow-up item — not a phase-2 split of this change (per CLAUDE.md §2b, this change is complete on its own surface).
Error codes
No new error codes are introduced. The migration is structural-types-only; no new throw sites, no new error paths.
Verification commands (per CLAUDE.md rule 1)
# Per affected package — full pre-publish gate
bunx turbo lint typecheck test --filter=@suaveplan/crypto-core
bunx turbo lint typecheck test --filter=@suaveplan/crypto
bunx turbo lint typecheck test --filter=@suaveplan/testing
# Dependent sweep for testing (factory consumers)
bunx turbo test --filter=...@suaveplan/testing
# Repo-wide grep — only the canonical declaration plus the three deprecated
# re-exports should remain
grep -rln "interface Rng\b\|type Rng = " packages/ --include="*.ts" \
| grep -v "/dist/\|/node_modules/"
File layout (per affected package)
No new files. The migration edits exactly three implementation files and
their three sibling .md files (doc-drift gate, CLAUDE.md §4a):
packages/crypto/crypto-core/src/primitives/random-bytes/random-bytes.ts # swap alias
packages/crypto/crypto-core/src/primitives/random-bytes/random-bytes.md # update API reference
packages/crypto/crypto/src/index.ts # update forwarder
packages/crypto/crypto/src/index.md # update API reference (if mentions Rng)
packages/testing/testing/src/context/context.ts # swap interface for re-export
packages/testing/testing/src/context/context.md # update API reference
packages/testing/testing/package.json # add @suaveplan/types: workspace:^