4.1 KiB
@suaveplan/packer/runtime — Clock + Rng injection
(no submodules)
Purpose
Every production code path in @suaveplan/packer reads wall-clock time and
randomness through an injected PackerRuntime bundle instead of calling
Date.now(), performance.now(), or crypto.getRandomValues directly.
This module hosts the platform defaults plus the resolveRuntime(opts)
factory that every option bundle (PackerOptions, PackChunkedOptions,
UnpackChunkedOptions) consumes when it constructs its resolved option
bundle. The audit line in enterprise/packer.md (L43) makes this a hard
requirement: "no Math.random() / Date.now() at module scope; injectable
clock and RNG".
The contracts (Clock, MonotonicClock, Rng) are imported from
@suaveplan/types — they are the shared cross-package interface every
Suaveplan library agrees on for time and randomness. Defaults wire to real
platform implementations; tests pass in FakeClock-backed values and a
seeded Rng from @suaveplan/testing to drive deterministic pack output.
Features
defaultClock—() => Date.now(); wall-clock source formetadata.timestamp.defaultMonotonicClock—performance.now()cast to nanosecondbigint; used for histogram observations.defaultRng— wrapscrypto.getRandomValuesand exposes the fullRngtriplet (nextUint32,nextFloat,nextBytes).resolveRuntime(opts?)— produces aPackerRuntimebundle, falling back to defaults for absent fields. Upgrades a seededRnglackingnextBytesby deriving bytes fromnextUint32.elapsedMs(monotonicClock, startNanos)— converts a monotonic-clock delta into milliseconds for histogram observations.
Usage: Basic Example
import { Packer } from "@suaveplan/packer";
// Default — no options needed; defaults pull from the platform.
const packer = new Packer();
const bundle = await packer.pack(new Uint8Array([1, 2, 3]));
console.log(bundle.metadata.timestamp); // current Date.now() value
Usage: Advanced Example
import { Packer } from "@suaveplan/packer";
import { FakeClock, seededRng } from "@suaveplan/testing";
// Deterministic pack: byte-identical output for byte-identical input.
const fake = new FakeClock({ now: 1_700_000_000_000 });
const rng = seededRng(0xdeadbeef);
const packer = new Packer({
clock: () => fake.now,
monotonicClock: () => BigInt(fake.now) * 1_000_000n,
rng,
encryption: {
algorithm: "xchacha20-poly1305",
key: new Uint8Array(32),
},
});
const a = await packer.pack(new Uint8Array([1, 2, 3]));
const b = await packer.pack(new Uint8Array([1, 2, 3]));
// a.data === b.data byte-by-byte because the RNG is seeded and the clock is fixed.
API Reference
defaultClock: Clock—() => Date.now().defaultMonotonicClock: MonotonicClock—performance.now()× 1e6 asbigint.defaultRng: Required<Pick<Rng, "nextBytes">> & Rng— CSPRNG-backedRngwith mandatorynextBytes.resolveRuntime(opts?: RuntimeOptions): PackerRuntime— produces the resolved bundle;opts.clock,opts.monotonicClock,opts.rngeach default to the matching platform default.elapsedMs(monotonicClock, startNanos): number— millisecond delta from the monotonic-clock sample atstartNanos.
Implementation Notes
defaultMonotonicClockroundsperformance.now()to the nearest nanosecond integer; the float→bigint cast is lossless for any process uptime under ~584 years.ensureNextBytes(internal) extracts four bytes pernextUint32call, big-endian, when the injectedRnglacksnextBytes. This keeps the AEAD-nonce derivation deterministic under any seeded RNG.- The runtime carries no state between calls — the bundle is a plain object holding the three injected functions. Re-resolving with the same inputs is cheap and side-effect-free.
resolveRuntimeis the single funnel every production option bundle in the package consumes. The grep audit pinned by PACKER-25's fourth scenario verifies that no other production file referencesDate.now/Math.random/crypto.getRandomValues/performance.now.