8.1 KiB
Capability Spec Delta: utils (utils-completion)
Package: @suaveplan/utils
Status: PROPOSED
Every requirement below carries a
**Verified by:**line at authoring time. No exceptions. (Seeopenspec/AGENTS.mdSection 13.)
ADDED Requirements
Requirement: Hex / Uint8Array round-trip
The package SHALL expose bytesToHex(bytes: Uint8Array): string and hexToBytes(hex: string): Uint8Array functions that perform a stable round-trip. hexToBytes SHALL accept upper- and lower-case hex digits, MAY tolerate non-hex separator characters by extracting hex pairs, and SHALL throw UtilsError(UINT8_ARRAY.INVALID_HEX) when the input is the empty string, null, undefined, or a non-string value.
Scenario: round-trip preserves bytes
- WHEN a caller invokes
hexToBytes(bytesToHex(b))for anyb: Uint8Array - THEN the result equals
bbyte-for-byte
Scenario: empty hex throws structured error
- WHEN a caller invokes
hexToBytes("") - THEN the call throws
UtilsErrorwith codeUINT8_ARRAY.INVALID_HEX
Verified by: src/uint8-array/uint8-array.test.ts::"round-trips bytes through hex" AND src/uint8-array/uint8-array.test.ts::"throws UtilsError when hex input is empty" AND src/uint8-array/uint8-array.spec-conformance.test.ts::"hex error carries UINT8_ARRAY.INVALID_HEX code"
Requirement: Base64 / Uint8Array round-trip
The package SHALL expose bytesToBase64(bytes: Uint8Array): string and base64ToBytes(base64: string): Uint8Array that round-trip cleanly across browser (btoa / atob) and Node (Buffer) environments without relying on a specific runtime.
Scenario: round-trip preserves bytes
- WHEN a caller invokes
base64ToBytes(bytesToBase64(b))for anyb: Uint8Array - THEN the result equals
bbyte-for-byte
Verified by: src/uint8-array/uint8-array.test.ts::"round-trips bytes through base64" AND src/uint8-array/uint8-array.spec-conformance.test.ts::"base64 round-trip property holds for fast-check generated buffers"
Requirement: UTF-8 / Uint8Array round-trip
The package SHALL expose utf8ToBytes(str: string): Uint8Array and bytesToUtf8(bytes: Uint8Array): string backed by native TextEncoder / TextDecoder.
Scenario: round-trip preserves UTF-8 strings including surrogates
- WHEN a caller invokes
bytesToUtf8(utf8ToBytes(s))forscontaining emoji and non-BMP code points - THEN the result equals
s
Verified by: src/uint8-array/uint8-array.test.ts::"round-trips utf-8 strings including emoji"
Requirement: Format auto-detection
The package SHALL expose detectInputFormat(input: string | Uint8Array): DataFormat returning "uint8array" for Uint8Array inputs, "hex" for strings that match /^(?:0x)?[\da-f]*$/i with even length OR a 0x prefix, and "utf8" otherwise. Non-string non-Uint8Array inputs SHALL throw UtilsError(UINT8_ARRAY.INVALID_FORMAT).
Scenario: hex string detected
- WHEN a caller invokes
detectInputFormat("deadbeef") - THEN it returns
"hex"
Scenario: utf-8 string detected
- WHEN a caller invokes
detectInputFormat("hello world") - THEN it returns
"utf8"
Scenario: invalid input throws
- WHEN a caller invokes
detectInputFormat(42 as never) - THEN it throws
UtilsErrorwith codeUINT8_ARRAY.INVALID_FORMAT
Verified by: src/uint8-array/uint8-array.test.ts::"detects hex format from even-length hex string" AND src/uint8-array/uint8-array.test.ts::"defaults to utf-8 for non-hex strings" AND src/uint8-array/uint8-array.spec-conformance.test.ts::"throws UINT8_ARRAY.INVALID_FORMAT for non-string non-Uint8Array input"
Requirement: Format conversion
The package SHALL expose convertDataFormat(input, outputFormat, inputFormat?) that auto-detects the input format when omitted, validates the JS type matches the declared format when supplied, and produces output in the requested format. Underlying mechanics SHALL go through inputToUint8Array then formatOutput.
Scenario: hex string converts to base64
- WHEN a caller invokes
convertDataFormat("deadbeef", "base64") - THEN the result is the base64-encoded string for the bytes
[0xde, 0xad, 0xbe, 0xef]
Scenario: explicit input format mismatch throws
- WHEN a caller invokes
convertDataFormat(new Uint8Array([1, 2]), "hex", "utf8") - THEN it throws
UtilsErrorwith codeUINT8_ARRAY.INVALID_FORMAT
Verified by: src/uint8-array/uint8-array.test.ts::"converts hex to base64" AND src/uint8-array/uint8-array.test.ts::"throws when explicit inputFormat does not match input type"
Requirement: UUID interop
The package SHALL expose uint8ArrayToUuid(bytes: Uint8Array): string (requires exactly 16 bytes), uuidToUint8Array(uuid: string): Uint8Array, and generateUuid(options?: { rng?: Rng }): string returning RFC 4122 v4 UUIDs. The rng option SHALL accept a Rng from @suaveplan/types; when omitted, the implementation SHALL use crypto.getRandomValues. Module-scope Math.random() is forbidden.
Scenario: 16-byte array round-trips through UUID string
- WHEN a caller invokes
uuidToUint8Array(uint8ArrayToUuid(b))for anyb: Uint8Arrayof length 16 - THEN the result equals
b
Scenario: wrong-length array throws
- WHEN a caller invokes
uint8ArrayToUuid(new Uint8Array(15)) - THEN it throws
UtilsErrorwith codeUINT8_ARRAY.INVALID_ARGUMENT
Scenario: injected Rng is consumed deterministically
- WHEN a caller invokes
generateUuid({ rng: deterministicRng })wheredeterministicRng.bytes(16)returns a fixed buffer - THEN every invocation returns the same UUID string
Verified by: src/uint8-array/uint8-array.test.ts::"round-trips 16-byte array through UUID string" AND src/uint8-array/uint8-array.test.ts::"throws when uint8ArrayToUuid receives non-16-byte array" AND src/uint8-array/uint8-array.spec-conformance.test.ts::"generateUuid consumes injected Rng deterministically"
Requirement: Equality, concatenation, and slicing
The package SHALL expose uint8ArraysEqual(a, b) (non-constant-time, documented as such), concatUint8Arrays(...arrays) (accepts varargs OR a single array argument; throws UtilsError(UINT8_ARRAY.INVALID_ARGUMENT) on non-Uint8Array elements), and sliceUint8Array(data, splitAt) returning a [head, tail] tuple (throws UtilsError(UINT8_ARRAY.OUT_OF_BOUNDS) when splitAt < 0 || splitAt > data.length).
Scenario: equality returns false on length mismatch
- WHEN a caller invokes
uint8ArraysEqual(new Uint8Array([1, 2]), new Uint8Array([1, 2, 3])) - THEN it returns
false
Scenario: concat accepts both varargs and single-array forms
- WHEN a caller invokes
concatUint8Arrays(a, b)ANDconcatUint8Arrays([a, b]) - THEN both calls return identical bytes
Scenario: slice out-of-bounds throws
- WHEN a caller invokes
sliceUint8Array(new Uint8Array(4), 5) - THEN it throws
UtilsErrorwith codeUINT8_ARRAY.OUT_OF_BOUNDS
Verified by: src/uint8-array/uint8-array.test.ts::"equality returns false on length mismatch" AND src/uint8-array/uint8-array.test.ts::"concat accepts varargs and single-array argument" AND src/uint8-array/uint8-array.test.ts::"slice throws when splitAt is out of bounds" AND src/uint8-array/uint8-array.spec-conformance.test.ts::"non-Uint8Array element triggers UINT8_ARRAY.INVALID_ARGUMENT"
Requirement: Subpath export and tree-shaking
The package SHALL expose the new module under the ./uint8-array subpath in package.json::exports. Importing only convertDataFormat SHALL NOT pull in unrelated @suaveplan/utils modules (e.g. string, email).
Scenario: subpath import resolves
- WHEN a caller writes
import { convertDataFormat } from "@suaveplan/utils/uint8-array" - THEN TypeScript resolves the import and the bundled module size measured in a tree-shaking probe stays below the documented budget
Verified by: src/uint8-array/uint8-array.spec-conformance.test.ts::"subpath ./uint8-array resolves and is tree-shakeable"