Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,162 @@
# Spec: @suaveplan/bytes
## Capability
Low-level byte I/O for the Suaveplan monorepo. Provides lossless conversion between `Uint8Array` and the three ubiquitous text representations (hex, base64, UTF-8), ergonomic `Uint8Array` utilities (concat, slice, equality, format auto-detection, unified conversion façade), and UUID string ↔ 16-byte binary round-trip functions.
## Requirements
### Requirement: Hex encoding round-trips and rejects malformed input
The package SHALL export `bytesToHex(bytes: Uint8Array): string` producing lowercase hex (empty array → `""`), and `hexToBytes(hex: string): Uint8Array` accepting case-insensitive hex. `hexToBytes` MUST throw `BytesError` with code `BYTES.ENCODE.INVALID_HEX` on odd-length input or non-hex characters. Round-tripping arbitrary `Uint8Array` through `bytesToHex` → `hexToBytes` MUST be lossless.
#### Scenario: Round-trip preserves arbitrary bytes
- **WHEN** any `Uint8Array` is passed through `bytesToHex` then `hexToBytes`
- **THEN** the resulting bytes equal the input byte-for-byte
#### Scenario: Empty input maps to empty output
- **WHEN** `bytesToHex(new Uint8Array(0))` is called
- **THEN** the result is `""`
#### Scenario: Malformed hex is rejected with a structured error
- **WHEN** `hexToBytes` is called with odd-length input or non-hex characters
- **THEN** a `BytesError` with code `BYTES.ENCODE.INVALID_HEX` is thrown
**Verified by:** `src/encoding/encoding.test.ts::"round-trips arbitrary bytes"` AND `src/encoding/encoding.test.ts::"bytesToHex produces lowercase hex"` AND `src/encoding/encoding.test.ts::"bytesToHex handles empty array"` AND `src/encoding/encoding.test.ts::"hexToBytes handles empty string"` AND `src/encoding/encoding.test.ts::"hexToBytes rejects odd-length string"` AND `src/encoding/encoding.test.ts::"hexToBytes rejects non-hex chars"` AND `src/encoding/encoding.test.ts::"hexToBytes accepts uppercase hex letters"` AND `src/encoding/encoding.test.ts::"hexToBytes rejects uppercase letters that are not hex"` AND `src/encoding/encoding.test.ts::"hex round-trip for arbitrary bytes"`
### Requirement: Base64 encoding round-trips and rejects malformed input
The package SHALL export `bytesToBase64(bytes: Uint8Array): string` (standard base64 with `=` padding) and `base64ToBytes(base64: string): Uint8Array`. `base64ToBytes` MUST throw `BytesError` with code `BYTES.ENCODE.INVALID_BASE64` on syntactically invalid or malformed input. Round-tripping arbitrary `Uint8Array` is lossless.
#### Scenario: Round-trip preserves arbitrary bytes
- **WHEN** any `Uint8Array` is passed through `bytesToBase64` then `base64ToBytes`
- **THEN** the resulting bytes equal the input
#### Scenario: Malformed base64 is rejected
- **WHEN** `base64ToBytes` is called with non-base64 characters or bad padding
- **THEN** a `BytesError` with code `BYTES.ENCODE.INVALID_BASE64` is thrown
**Verified by:** `src/encoding/encoding.test.ts::"round-trips arbitrary bytes"` AND `src/encoding/encoding.test.ts::"handles empty array"` AND `src/encoding/encoding.test.ts::"rejects invalid base64"` AND `src/encoding/encoding.test.ts::"rejects syntactically valid but malformed base64 (bad padding)"` AND `src/encoding/encoding.test.ts::"base64 round-trip for arbitrary bytes"`
### Requirement: UTF-8 encoding round-trips arbitrary strings
The package SHALL export `bytesToUtf8(bytes: Uint8Array): string` backed by `TextDecoder("utf-8")` and `utf8ToBytes(str: string): Uint8Array` backed by `TextEncoder`. Round-tripping multibyte and ASCII strings is lossless.
#### Scenario: ASCII round-trip
- **WHEN** any ASCII string is passed through `utf8ToBytes` then `bytesToUtf8`
- **THEN** the result equals the input
#### Scenario: Multibyte round-trip
- **WHEN** strings containing non-ASCII characters are round-tripped
- **THEN** code points are preserved
**Verified by:** `src/encoding/encoding.test.ts::"round-trips ASCII"` AND `src/encoding/encoding.test.ts::"round-trips multibyte characters"` AND `src/encoding/encoding.test.ts::"handles empty string"` AND `src/encoding/encoding.test.ts::"utf8 round-trip for arbitrary strings"`
### Requirement: Uint8Array utility surface
The package SHALL export `concatUint8Arrays(...arrays: Uint8Array[]): Uint8Array`, `sliceUint8Array(arr, start, end?): Uint8Array`, and `uint8ArraysEqual(a, b): boolean` (NOT constant-time — documented). `concatUint8Arrays` MUST be associative under concatenation and tolerate zero arguments and empty arrays.
#### Scenario: Concatenation joins arrays end-to-end
- **WHEN** `concatUint8Arrays(a, b, c)` is called
- **THEN** the result has `a.length + b.length + c.length` bytes in order
#### Scenario: Concatenation handles edge cases
- **WHEN** called with no arguments, a single array, or arrays of length zero
- **THEN** the result is empty (or the lone non-empty array) without throwing
#### Scenario: Slicing returns a copy of the requested range
- **WHEN** `sliceUint8Array(arr, start, end?)` is called
- **THEN** the result contains bytes `[start, end)` (or `[start, arr.length)` if `end` is omitted)
#### Scenario: Equality compares length and bytes
- **WHEN** `uint8ArraysEqual(a, b)` is called
- **THEN** the result is `true` iff lengths match AND every byte at every index is equal
**Verified by:** `src/uint8/uint8.test.ts::"concatenates multiple arrays"` AND `src/uint8/uint8.test.ts::"handles empty arrays"` AND `src/uint8/uint8.test.ts::"handles single array"` AND `src/uint8/uint8.test.ts::"handles no arguments"` AND `src/uint8/uint8.test.ts::"concatUint8Arrays associativity"` AND `src/uint8/uint8.test.ts::"slices correctly"` AND `src/uint8/uint8.test.ts::"slices from start only"` AND `src/uint8/uint8.test.ts::"returns empty for zero-length slice"` AND `src/uint8/uint8.test.ts::"returns true for equal arrays"` AND `src/uint8/uint8.test.ts::"returns false for different arrays"` AND `src/uint8/uint8.test.ts::"returns false for different lengths"` AND `src/uint8/uint8.test.ts::"returns true for two empty arrays"`
### Requirement: Format detection and validation
The package SHALL export `detectInputFormat(input: BytesInput): InputFormat` returning `"uint8array"` for `Uint8Array`, `"hex"` for even-length all-hex strings, and `"utf8"` otherwise; `inputToUint8Array(input, format?)` coercing input into bytes (auto-detecting when `format` is omitted); and `validateInputTypeFormat(input, format)` which MUST throw `BytesError` with code `BYTES.UINT8.FORMAT_MISMATCH` when the runtime type does not match the declared format.
#### Scenario: Heuristic detects the three input formats
- **WHEN** `detectInputFormat` is called with a `Uint8Array`, an even-length hex string, or any other string
- **THEN** the result is `"uint8array"`, `"hex"`, or `"utf8"` respectively
#### Scenario: Coercion respects explicit format and falls back to auto-detection
- **WHEN** `inputToUint8Array(value, format?)` is called
- **THEN** the value is decoded using `format` (or the detected format) into a `Uint8Array`
#### Scenario: Validation throws on type/format mismatch
- **WHEN** `validateInputTypeFormat` is called with a value whose runtime type does not match the declared format (e.g. string with `"uint8array"` format)
- **THEN** a `BytesError` with code `BYTES.UINT8.FORMAT_MISMATCH` is thrown
**Verified by:** `src/uint8/uint8.test.ts::"detects Uint8Array"` AND `src/uint8/uint8.test.ts::"detects hex string"` AND `src/uint8/uint8.test.ts::"detects utf8 for general string"` AND `src/uint8/uint8.test.ts::"detects utf8 for non-hex even string"` AND `src/uint8/uint8.test.ts::"returns Uint8Array as-is"` AND `src/uint8/uint8.test.ts::"converts hex string to Uint8Array"` AND `src/uint8/uint8.test.ts::"converts utf8 string to Uint8Array"` AND `src/uint8/uint8.test.ts::"converts base64 string to Uint8Array"` AND `src/uint8/uint8.test.ts::"auto-detects format when not provided"` AND `src/uint8/uint8.test.ts::"throws when string given with uint8array format"` AND `src/uint8/uint8.test.ts::"validates Uint8Array with uint8array format"` AND `src/uint8/uint8.test.ts::"validates string with hex format"` AND `src/uint8/uint8.test.ts::"throws when Uint8Array given but string format requested"` AND `src/uint8/uint8.test.ts::"throws when string given but uint8array format requested"`
### Requirement: Unified conversion façade with OTel span
The package SHALL export `formatOutput(bytes, format)` returning a value in the requested representation (`uint8array` / `hex` / `base64` / `utf8`) and `convertDataFormat(input, outputFormat, inputFormat?)` which MUST emit an OpenTelemetry span around the conversion. Auto-detection of `inputFormat` MUST kick in when omitted.
#### Scenario: Output formatting covers every supported format
- **WHEN** `formatOutput(bytes, format)` is called with each of `"uint8array"`, `"hex"`, `"base64"`, `"utf8"`
- **THEN** the result is the corresponding representation of `bytes`
#### Scenario: Format conversion bridges any input to any output
- **WHEN** `convertDataFormat` is called with mismatched input/output formats (e.g. hex → utf8, utf8 → hex, Uint8Array → hex, hex → base64)
- **THEN** the conversion proceeds and the output matches the expected encoding
#### Scenario: Auto-detection fills in missing input format
- **WHEN** `convertDataFormat(value, outputFormat)` is called without `inputFormat`
- **THEN** the input format is detected via `detectInputFormat` before conversion
**Verified by:** `src/uint8/uint8.test.ts::"returns Uint8Array for uint8array format"` AND `src/uint8/uint8.test.ts::"returns hex string for hex format"` AND `src/uint8/uint8.test.ts::"returns base64 string for base64 format"` AND `src/uint8/uint8.test.ts::"returns utf8 string for utf8 format"` AND `src/uint8/uint8.test.ts::"converts hex to utf8"` AND `src/uint8/uint8.test.ts::"converts utf8 to hex"` AND `src/uint8/uint8.test.ts::"converts Uint8Array to hex"` AND `src/uint8/uint8.test.ts::"converts hex to base64"` AND `src/uint8/uint8.test.ts::"converts Uint8Array to uint8array (identity)"` AND `src/uint8/uint8.test.ts::"auto-detects input format for hex strings"`
### Requirement: UUID round-trip and validation
The package SHALL export `generateUuid(): string` (UUIDv4 via `crypto.randomUUID()`), `uuidToUint8Array(uuid: string): Uint8Array` returning 16 bytes, and `uint8ArrayToUuid(bytes: Uint8Array): string` returning canonical lowercase. Invalid UUIDs MUST throw `BytesError` with code `BYTES.UUID.INVALID_FORMAT`; non-16-byte arrays MUST throw `BYTES.UUID.INVALID_LENGTH`. Round-trip is lossless including all-zero and all-`f` corner cases.
#### Scenario: Generated UUIDs are valid and unique
- **WHEN** `generateUuid()` is called multiple times
- **THEN** every result matches the UUIDv4 lowercase canonical shape AND no two results are equal
#### Scenario: Round-trip preserves the canonical form
- **WHEN** a UUID string is passed through `uuidToUint8Array` then `uint8ArrayToUuid`
- **THEN** the output equals the (lowercased) input — including the all-zero and all-`f` corner cases
#### Scenario: Invalid UUID strings are rejected
- **WHEN** `uuidToUint8Array` is called with a malformed string
- **THEN** a `BytesError` with code `BYTES.UUID.INVALID_FORMAT` is thrown
#### Scenario: Wrong-length byte arrays are rejected
- **WHEN** `uint8ArrayToUuid` is called with an array whose length is not 16 (including empty)
- **THEN** a `BytesError` with code `BYTES.UUID.INVALID_LENGTH` is thrown
**Verified by:** `src/uuid/uuid.test.ts::"round-trips a known UUID"` AND `src/uuid/uuid.test.ts::"uuidToUint8Array produces 16 bytes"` AND `src/uuid/uuid.test.ts::"throws on invalid UUID"` AND `src/uuid/uuid.test.ts::"throws on UUID with wrong structure"` AND `src/uuid/uuid.test.ts::"uint8ArrayToUuid throws on non-16-byte array"` AND `src/uuid/uuid.test.ts::"uint8ArrayToUuid throws on empty array"` AND `src/uuid/uuid.test.ts::"round-trips all-zeros UUID"` AND `src/uuid/uuid.test.ts::"round-trips all-fs UUID"` AND `src/uuid/uuid.test.ts::"generates a valid UUID v4 string"` AND `src/uuid/uuid.test.ts::"generates unique values"` AND `src/uuid/uuid.test.ts::"output is lowercase"` AND `src/uuid/uuid.test.ts::"generated UUID can round-trip through uint8Array"`
## Error codes
`BYTES.ENCODE.INVALID_HEX`, `BYTES.ENCODE.INVALID_BASE64`, `BYTES.ENCODE.INVALID_UTF8` (reserved), `BYTES.UINT8.INVALID_INPUT`, `BYTES.UINT8.FORMAT_MISMATCH`, `BYTES.UUID.INVALID_LENGTH`, `BYTES.UUID.INVALID_FORMAT`
## Version
Published: `0.1.0` (2026-04-20). Spec heading conformance: `bytes-spec-conformance` (2026-04-26).