Move docs/ package mirror into document-navigator plugin

Relocate the ~6k-file docs tree from the repo root into
plugins/document-navigator/docs/ so the plugin is self-contained and
portable. docs/superpowers/ (specs and plans) stays at the repo root.

Moved via cp+rm rather than git mv because directory rename() fails with
EPERM on this WSL /mnt DrvFs mount; git rename-detection pairs the
identical-content files at commit time.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-30 01:10:00 +01:00
parent fe3fedf42e
commit 9bf56a806b
6022 changed files with 12437 additions and 0 deletions

View File

@@ -0,0 +1,95 @@
# @suaveplan/bytes - Low-level byte utilities: hex/base64/utf8 encoding, Uint8Array helpers, UUID conversion
## Summary
Low-level byte utilities for Suaveplan: lossless hex/base64/UTF-8 encoding, `Uint8Array` manipulation, and UUID conversion. No native addons; depends only on platform globals (`TextEncoder`, `atob`/`btoa`, `crypto.randomUUID`) and `@suaveplan/error` for typed errors.
<!-- begin:auto-docs -->
## Documentation tree
- [encoding](./src/encoding/encoding.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [encoding](./src/encoding/encoding.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [errors](./src/errors/bytes-error.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [bytes-error](./src/errors/bytes-error.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [uint8](./src/uint8/uint8.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [uint8](./src/uint8/uint8.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [uuid](./src/uuid/uuid.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [uuid](./src/uuid/uuid.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
<!-- end:auto-docs --> - [encoding](./src/encoding/encoding.md) — <!-- begin:auto-docs --> _(no submodules)_- [errors](./src/errors/bytes-error.md) — <!-- begin:auto-docs --> _(no submodules)_- [bytes-error](./src/errors/bytes-error.md) — <!-- begin:auto-docs --> _(no submodules)_- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_- [uint8](./src/uint8/uint8.md) — <!-- begin:auto-docs --> _(no submodules)_- [uint8](./src/uint8/uint8.md) — <!-- begin:auto-docs --> _(no submodules)_- [uuid](./src/uuid/uuid.md) — <!-- begin:auto-docs --> _(no submodules)_- [uuid](./src/uuid/uuid.md) — <!-- begin:auto-docs --> _(no submodules)_- [encoding](./src/encoding/encoding.md) — <!-- begin:auto-docs --> _(no submodules)_- [errors](./src/errors/bytes-error.md) — <!-- begin:auto-docs --> _(no submodules)_- [bytes-error](./src/errors/bytes-error.md) — <!-- begin:auto-docs --> _(no submodules)_- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_- [uint8](./src/uint8/uint8.md) — <!-- begin:auto-docs --> _(no submodules)_- [uint8](./src/uint8/uint8.md) — <!-- begin:auto-docs --> _(no submodules)_- [uuid](./src/uuid/uuid.md) — <!-- begin:auto-docs --> _(no submodules)_- [uuid](./src/uuid/uuid.md) — <!-- begin:auto-docs --> _(no submodules)_- [encoding](./src/encoding/encoding.md) — <!-- begin:auto-docs --> _(no submodules)_- [errors](./src/errors/bytes-error.md) — <!-- begin:auto-docs --> _(no submodules)_- [bytes-error](./src/errors/bytes-error.md) — <!-- begin:auto-docs --> _(no submodules)_- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_- [uint8](./src/uint8/uint8.md) — <!-- begin:auto-docs --> _(no submodules)_- [uint8](./src/uint8/uint8.md) — <!-- begin:auto-docs --> _(no submodules)_- [uuid](./src/uuid/uuid.md) — <!-- begin:auto-docs --> _(no submodules)_- [uuid](./src/uuid/uuid.md) — <!-- begin:auto-docs --> _(no submodules)_- [encoding](./src/encoding/encoding.md) — <!-- begin:auto-docs --> _(no submodules)_- [errors](./src/errors/bytes-error.md) — <!-- begin:auto-docs --> _(no submodules)_ - [bytes-error](./src/errors/bytes-error.md) — <!-- begin:auto-docs --> _(no submodules)_- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_ - [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_- [uint8](./src/uint8/uint8.md) — <!-- begin:auto-docs --> _(no submodules)_ - [uint8](./src/uint8/uint8.md) — <!-- begin:auto-docs --> _(no submodules)_- [uuid](./src/uuid/uuid.md) — <!-- begin:auto-docs --> _(no submodules)_ - [uuid](./src/uuid/uuid.md) — <!-- begin:auto-docs --> _(no submodules)_
## Install
```bash
bun add @suaveplan/bytes
```
## Quick Start
```ts
import {
bytesToHex, hexToBytes,
bytesToBase64, base64ToBytes,
bytesToUtf8, utf8ToBytes,
concatUint8Arrays,
convertDataFormat,
generateUuid, uuidToUint8Array, uint8ArrayToUuid,
} from "@suaveplan/bytes";
// Encoding
const raw = utf8ToBytes("Hello"); // Uint8Array [72,101,108,108,111]
const hex = bytesToHex(raw); // "48656c6c6f"
const b64 = bytesToBase64(raw); // "SGVsbG8="
const text = bytesToUtf8(raw); // "Hello"
// Uint8 helpers
const frame = concatUint8Arrays(raw, new Uint8Array([0x21]));
// Unified conversion
const asBase64 = convertDataFormat("48656c6c6f", "base64", "hex"); // "SGVsbG8="
// UUID
const id = generateUuid(); // "550e8400-e29b-41d4-a716-446655440000"
const bytes = uuidToUint8Array(id); // Uint8Array (16 bytes)
const back = uint8ArrayToUuid(bytes); // → id
```
## Modules
| Module | Description | Docs |
|--------|-------------|------|
| `@suaveplan/bytes/encoding` | Hex, base64, UTF-8 encode/decode | [encoding.md](src/encoding/encoding.md) |
| `@suaveplan/bytes/uint8` | Uint8Array utilities and format conversion | [uint8.md](src/uint8/uint8.md) |
| `@suaveplan/bytes/uuid` | UUID generation and binary conversion | [uuid.md](src/uuid/uuid.md) |
| `@suaveplan/bytes/errors` | `BytesError`, `BYTES_CODES` | — |
| `@suaveplan/bytes/schemas` | Zod schemas (HexStringSchema, etc.) | — |
## Benchmarks
Run with `bun src/__benchmarks__/encoding.bench.ts`.
```text
clk: ~2.74 GHz
cpu: Intel(R) Xeon(R) CPU E5-2696 v4 @ 2.20GHz
runtime: bun 1.3.13 (x64-linux)
benchmark avg (min … max)
bytesToHex (32 bytes) 9.31 µs/iter
bytesToHex (64 KB) 5.42 ms/iter
hexToBytes (32 bytes) 7.43 µs/iter
bytesToBase64 (32 bytes) 4.29 µs/iter
bytesToBase64 (64 KB) 983.62 µs/iter
```
## Error Codes
| Code | Thrown by |
|------|-----------|
| `BYTES.ENCODE.INVALID_HEX` | `hexToBytes` — odd length or non-hex chars |
| `BYTES.ENCODE.INVALID_BASE64` | `base64ToBytes` — invalid base64 alphabet |
| `BYTES.ENCODE.INVALID_UTF8` | Reserved |
| `BYTES.UINT8.INVALID_INPUT` | `inputToUint8Array` — unknown input type |
| `BYTES.UINT8.FORMAT_MISMATCH` | `validateInputTypeFormat` — type/format clash |
| `BYTES.UUID.INVALID_FORMAT` | `uuidToUint8Array` — malformed UUID string |
| `BYTES.UUID.INVALID_LENGTH` | `uint8ArrayToUuid` — not 16 bytes |

View File

@@ -0,0 +1,103 @@
# encoding
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
The `encoding` module provides lossless, bidirectional conversion between raw `Uint8Array` byte sequences and three ubiquitous text representations: lowercase hexadecimal, standard base64, and UTF-8. It is the lowest-level I/O surface in `@suaveplan/bytes` and is intentionally free of any dependency beyond `@opentelemetry/api` and the `@suaveplan/error` error substrate.
Every conversion function validates its input strictly and throws a typed `BytesError` on malformed data, making it safe to use directly in protocol parsers, cryptographic pipelines, and data serialisation layers without additional guard code at the call site.
## Features
- **Hex encoding/decoding** — `bytesToHex` emits lowercase two-character hex pairs; `hexToBytes` accepts both upper- and lowercase and rejects odd-length or non-hex inputs with `BYTES.ENCODE.INVALID_HEX`.
- **Base64 encoding/decoding** — `bytesToBase64` uses the standard (non-URL-safe) alphabet; `base64ToBytes` validates the input against the standard alphabet before delegating to `atob`, rethrowing any decode error as `BYTES.ENCODE.INVALID_BASE64`.
- **UTF-8 encoding/decoding** — `utf8ToBytes` uses `TextEncoder` and `bytesToUtf8` uses `TextDecoder`, both handling multi-byte Unicode code points correctly.
- **OTel tracing** — every exported function wraps its body in a named active span under the `@suaveplan/bytes` tracer so encoding operations appear in distributed traces.
- **Zod schemas** — `HexStringSchema` and `Base64StringSchema` are exported from `encoding.schemas.ts` for use in Zod-based validation pipelines.
- **Property-tested** — fast-check verifies lossless round-trips for arbitrary byte sequences for all three codecs.
## Usage: Basic Example
```ts
import { bytesToHex, hexToBytes, bytesToBase64, base64ToBytes, utf8ToBytes, bytesToUtf8 } from "@suaveplan/bytes/encoding";
const raw = new Uint8Array([72, 101, 108, 108, 111]);
// Hex
const hex = bytesToHex(raw); // "48656c6c6f"
const back = hexToBytes(hex); // Uint8Array [72, 101, 108, 108, 111]
// Base64
const b64 = bytesToBase64(raw); // "SGVsbG8="
const back2 = base64ToBytes(b64); // Uint8Array [72, 101, 108, 108, 111]
// UTF-8
const text = bytesToUtf8(raw); // "Hello"
const back3 = utf8ToBytes(text); // Uint8Array [72, 101, 108, 108, 111]
```
## Usage: Advanced Example
```ts
import { hexToBytes, bytesToBase64, BytesError } from "@suaveplan/bytes";
// Safely parse an untrusted hex payload from an HTTP header
function parseHexHeader(value: string): Uint8Array {
try {
return hexToBytes(value.trim().toLowerCase());
} catch (err) {
if (err instanceof BytesError) {
// err.code === "BYTES.ENCODE.INVALID_HEX"
throw new Error(`Malformed X-Signature header: ${err.message}`);
}
throw err;
}
}
// Convert the raw bytes to base64 for JSON transport
const signature = parseHexHeader("deadbeef");
const forJson = bytesToBase64(signature); // "3q2+7w=="
```
## API Reference
### `bytesToHex(bytes: Uint8Array): string`
Converts `bytes` to a lowercase hexadecimal string. Each byte becomes exactly two hex characters. Returns `""` for an empty array.
### `hexToBytes(hex: string): Uint8Array`
Converts a hex string to a `Uint8Array`. Accepts upper- and lowercase characters. Returns an empty array for an empty string.
**Throws** `BytesError(BYTES.ENCODE.INVALID_HEX)` when:
- `hex.length` is odd.
- `hex` contains characters outside `[0-9a-fA-F]`.
### `bytesToBase64(bytes: Uint8Array): string`
Converts `bytes` to a standard base64 string (with `=` padding). Returns `""` for an empty array.
### `base64ToBytes(base64: string): Uint8Array`
Converts a standard base64 string to a `Uint8Array`. Returns an empty array for an empty string.
**Throws** `BytesError(BYTES.ENCODE.INVALID_BASE64)` when `base64` contains characters outside the standard base64 alphabet or the decode otherwise fails.
### `bytesToUtf8(bytes: Uint8Array): string`
Decodes `bytes` as a UTF-8 string using `TextDecoder`. Invalid sequences are replaced with the Unicode replacement character (U+FFFD) per platform behaviour.
### `utf8ToBytes(str: string): Uint8Array`
Encodes `str` as UTF-8 bytes using `TextEncoder`. Every Unicode code point, including surrogate pairs and multi-byte characters, is encoded correctly.
## Implementation Notes
The hex codec avoids regular-expression replacement in the hot encode path in favour of a simple loop over byte values with `toString(16).padStart(2, "0")`, which is both readable and cache-friendly on V8/JavaScriptCore. The decode path uses a single `HEX_RE.test()` validation pass before looping — this avoids per-character error checking while still catching all invalid inputs in one branch.
The base64 codec delegates to the platform `btoa`/`atob` globals (available natively in Node 18+ and Bun), avoiding any third-party encoding library. The encode path materialises a binary string from the byte array; the decode path pre-validates with a regex before calling `atob` and wraps any thrown exception in a `BytesError` so callers never see a raw `DOMException`.
OTel spans are started synchronously and ended in `finally` blocks, ensuring spans are always closed even when an exception propagates. The tracer is module-level so instrumentation is zero-cost when no SDK is installed (the `@opentelemetry/api` noop tracer is used automatically).

View File

@@ -0,0 +1,89 @@
# bytes-error
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`bytes-error.ts` defines the typed error substrate for `@suaveplan/bytes`. It registers all stable error codes under the `"BYTES"` namespace using `defineCodes` from `@suaveplan/error`, exposes the `BytesCode` union type that constrains which codes `BytesError` instances can carry, and re-exports the concrete `BytesError` class whose constructor widens visibility from `protected` to `public`.
By funnelling all package errors through a single typed class with a stable code registry, callers can narrow on `err instanceof BytesError` and then switch on `err.code` without any string fragility.
## Features
- **`BYTES_CODES`** — immutable record mapping human-readable dotted keys (e.g. `"ENCODE.INVALID_HEX"`) to stable prefixed string codes (`"BYTES.ENCODE.INVALID_HEX"`), produced by `defineCodes`.
- **`BytesCode`** — TypeScript union type of all values in `BYTES_CODES`; used as the generic parameter of `BytesError`.
- **`BytesError`** — extends `SuaveplanError<BytesCode>`; carries a stable `code`, a human-readable `message`, and an optional `cause` chain via `SuaveplanErrorOptions`.
- **Stable code registry** — codes are registered once and never change; adding a new code is a non-breaking addition; removing one is a breaking change.
## Usage: Basic Example
```ts
import { BytesError, BYTES_CODES } from "@suaveplan/bytes/errors";
throw new BytesError(
BYTES_CODES["ENCODE.INVALID_HEX"],
"Hex string has odd length: 3 characters"
);
```
## Usage: Advanced Example
```ts
import { BytesError, BYTES_CODES } from "@suaveplan/bytes/errors";
function assertHex(value: string): void {
if (value.length % 2 !== 0) {
throw new BytesError(
BYTES_CODES["ENCODE.INVALID_HEX"],
`Invalid hex string: odd length (${value.length})`,
{ cause: new RangeError("Length must be even") }
);
}
}
// Callers can narrow:
try {
assertHex("abc");
} catch (err) {
if (err instanceof BytesError) {
console.error(err.code); // "BYTES.ENCODE.INVALID_HEX"
}
}
```
## API Reference
### `BYTES_CODES`
`Record<string, BytesCode>` — maps dotted keys to prefixed code strings:
| Key | Code |
|-----|------|
| `"ENCODE.INVALID_HEX"` | `"BYTES.ENCODE.INVALID_HEX"` |
| `"ENCODE.INVALID_BASE64"` | `"BYTES.ENCODE.INVALID_BASE64"` |
| `"ENCODE.INVALID_UTF8"` | `"BYTES.ENCODE.INVALID_UTF8"` |
| `"UINT8.INVALID_INPUT"` | `"BYTES.UINT8.INVALID_INPUT"` |
| `"UINT8.FORMAT_MISMATCH"` | `"BYTES.UINT8.FORMAT_MISMATCH"` |
| `"UUID.INVALID_LENGTH"` | `"BYTES.UUID.INVALID_LENGTH"` |
| `"UUID.INVALID_FORMAT"` | `"BYTES.UUID.INVALID_FORMAT"` |
### `BytesCode`
`type BytesCode = (typeof BYTES_CODES)[keyof typeof BYTES_CODES]`
Union type of all code strings. Used as the generic argument to `SuaveplanError`.
### `BytesError`
```ts
class BytesError extends SuaveplanError<BytesCode> {
public constructor(code: BytesCode, message: string, options?: SuaveplanErrorOptions);
}
```
Typed error for all `@suaveplan/bytes` failures. The constructor widens visibility from the protected base to public.
## Implementation Notes
`defineCodes` from `@suaveplan/error` produces a frozen object whose values are the exact strings that will be serialised over the wire. The `"BYTES."` prefix guarantees no collision with codes from other packages. The `as const` assertion on the code-name array preserves tuple literal types so TypeScript can verify exhaustiveness in `switch (err.code)` statements.

View File

@@ -0,0 +1,78 @@
# types
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`types.ts` defines the shared TypeScript types used across all modules in `@suaveplan/bytes`. These types form the public contract for format identifiers and accepted input values, allowing all three modules (`encoding`, `uint8`, `uuid`) to share a consistent vocabulary without any runtime code or circular dependencies.
Keeping shared types in a dedicated file — rather than scattering them across implementation files — makes the public API surface explicit and gives TypeScript enough information to provide accurate autocompletion and type narrowing at call sites.
## Features
- **`InputFormat`** — string literal union identifying the four recognised input encodings: `"hex"`, `"base64"`, `"utf8"`, `"uint8array"`.
- **`OutputFormat`** — string literal union identifying the four recognised output encodings (same set as `InputFormat`).
- **`BytesInput`** — union type accepting either a plain `string` or a `Uint8Array`; used as the input type for `inputToUint8Array`, `detectInputFormat`, and `convertDataFormat`.
## Usage: Basic Example
```ts
import type { InputFormat, OutputFormat, BytesInput } from "@suaveplan/bytes";
function toBytes(input: BytesInput, fmt: InputFormat): Uint8Array {
// TypeScript narrows input based on fmt
if (input instanceof Uint8Array) return input;
// ...
}
```
## Usage: Advanced Example
```ts
import type { OutputFormat, BytesInput } from "@suaveplan/bytes";
import { convertDataFormat } from "@suaveplan/bytes";
// A generic transform pipeline step
interface TransformStep {
input: BytesInput;
outputFormat: OutputFormat;
inputFormat?: import("@suaveplan/bytes").InputFormat;
}
function runStep(step: TransformStep): string | Uint8Array {
return convertDataFormat(step.input, step.outputFormat, step.inputFormat);
}
```
## API Reference
### `InputFormat`
```ts
type InputFormat = "hex" | "base64" | "utf8" | "uint8array";
```
Identifies the encoding of an input value passed to conversion functions. Used by `inputToUint8Array`, `detectInputFormat`, `validateInputTypeFormat`, and `convertDataFormat`.
### `OutputFormat`
```ts
type OutputFormat = "hex" | "base64" | "utf8" | "uint8array";
```
Identifies the encoding to produce from conversion functions. Used by `formatOutput` and `convertDataFormat`.
### `BytesInput`
```ts
type BytesInput = string | Uint8Array;
```
Union of the two accepted raw input types. When a `Uint8Array` is provided, no decoding step is needed. When a `string` is provided, the caller supplies an `InputFormat` or lets `detectInputFormat` infer it.
## Implementation Notes
`InputFormat` and `OutputFormat` are intentionally identical sets. They are defined as separate types rather than a single alias so that function signatures self-document the intended direction of flow (`input` vs. `output`), and so that future versions can diverge — for example, adding an `"url-base64"` output-only format — without affecting the input union.
There is no runtime code in this file. TypeScript erases all type declarations at compile time, so this module contributes zero bytes to the final bundle.

View File

@@ -0,0 +1,113 @@
# uint8
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
The `uint8` module provides ergonomic, format-agnostic utilities for working with `Uint8Array` values: concatenation, slicing, equality testing, and format detection and conversion. It builds on the `encoding` module to offer a unified façade — `convertDataFormat` — that accepts any mix of hex, base64, UTF-8, or raw byte inputs and produces any requested output format, with automatic format detection when the caller omits an explicit hint.
The module is designed to sit at the heart of serialisation pipelines, protocol parsers, and data-transformation layers where the input format is heterogeneous or unknown at compile time.
## Features
- **`concatUint8Arrays`** — allocates a single result buffer and copies all source arrays in a single pass; accepts variadic arguments including zero arrays.
- **`sliceUint8Array`** — thin wrapper around `Uint8Array.prototype.slice` that returns a new copy (not a view) with consistent semantics.
- **`uint8ArraysEqual`** — linear-scan byte equality; safe for general-purpose comparison (not constant-time).
- **`detectInputFormat`** — heuristic: `Uint8Array` → `"uint8array"`, even-length all-hex string → `"hex"`, everything else → `"utf8"`.
- **`inputToUint8Array`** — normalise any `BytesInput` to a `Uint8Array`, calling the appropriate codec.
- **`validateInputTypeFormat`** — assert type/format compatibility and throw a typed `BytesError` on mismatch.
- **`formatOutput`** — serialise a `Uint8Array` to the desired `OutputFormat`.
- **`convertDataFormat`** — unified one-stop conversion with auto-detection, OTel instrumentation.
## Usage: Basic Example
```ts
import {
concatUint8Arrays,
uint8ArraysEqual,
convertDataFormat,
} from "@suaveplan/bytes/uint8";
// Concatenation
const header = new Uint8Array([0x01, 0x02]);
const body = new Uint8Array([0x03, 0x04, 0x05]);
const frame = concatUint8Arrays(header, body);
// → Uint8Array [1, 2, 3, 4, 5]
// Equality
uint8ArraysEqual(frame, new Uint8Array([1, 2, 3, 4, 5])); // → true
// Format conversion
const asHex = convertDataFormat(frame, "hex"); // → "0102030405"
const back = convertDataFormat(asHex, "uint8array", "hex");
// → Uint8Array [1, 2, 3, 4, 5]
```
## Usage: Advanced Example
```ts
import { convertDataFormat, detectInputFormat } from "@suaveplan/bytes/uint8";
// A pipeline that receives data in an unknown text format and normalises it
function normalise(raw: string): Uint8Array {
const fmt = detectInputFormat(raw);
console.log("detected format:", fmt); // "hex" | "utf8"
return convertDataFormat(raw, "uint8array") as Uint8Array;
}
// Produce multiple representations from one call
function multiView(input: Uint8Array) {
return {
hex: convertDataFormat(input, "hex") as string,
base64: convertDataFormat(input, "base64") as string,
utf8: convertDataFormat(input, "utf8") as string,
};
}
```
## API Reference
### `concatUint8Arrays(...arrays: Uint8Array[]): Uint8Array`
Concatenate zero or more `Uint8Array` values into a single new `Uint8Array`. Allocates exactly one buffer sized to the sum of all input lengths.
### `sliceUint8Array(arr: Uint8Array, start: number, end?: number): Uint8Array`
Return a copy of `arr[start..end)`. If `end` is omitted, slices to the end of the array. Returns an empty `Uint8Array` when `start === end`.
### `uint8ArraysEqual(a: Uint8Array, b: Uint8Array): boolean`
Return `true` iff `a.length === b.length` and every byte is identical. Not constant-time.
### `detectInputFormat(input: BytesInput): InputFormat`
Heuristically determine the format of `input`. `Uint8Array` → `"uint8array"`. Even-length all-hex string → `"hex"`. Otherwise → `"utf8"`.
### `inputToUint8Array(input: BytesInput, format?: InputFormat): Uint8Array`
Convert `input` to a `Uint8Array`. If `input` is already a `Uint8Array`, returns it unchanged. For strings, uses `format` or `detectInputFormat(input)`.
**Throws** `BytesError(BYTES.UINT8.FORMAT_MISMATCH)` when `format === "uint8array"` but `input` is a string.
### `validateInputTypeFormat(input: BytesInput, format: InputFormat): void`
Assert that the runtime type of `input` is compatible with `format`.
**Throws** `BytesError(BYTES.UINT8.FORMAT_MISMATCH)` on mismatch.
### `formatOutput(bytes: Uint8Array, format: OutputFormat): string | Uint8Array`
Serialise `bytes` to the given output format. Returns the same reference for `"uint8array"`.
### `convertDataFormat(input: BytesInput, outputFormat: OutputFormat, inputFormat?: InputFormat): string | Uint8Array`
Convert `input` from `inputFormat` (or auto-detected) to `outputFormat`. Wraps the operation in an OTel span named `bytes.uint8.convertDataFormat`.
## Implementation Notes
`concatUint8Arrays` pre-computes the total length in a single reduce pass and writes all sub-arrays via `Uint8Array.prototype.set`, which is optimised as a memory copy by V8 and JavaScriptCore. This is significantly faster than repeated array spreading or `Buffer.concat`-style approaches.
`detectInputFormat` uses a two-condition check: even length and a regex over the full string. The hex heuristic will misclassify a legitimate UTF-8 string that happens to be even-length and consist entirely of hex chars (e.g., `"deadbeef"`). Callers that know the format should pass it explicitly. A future version may accept a `strict` option that requires a `0x` prefix for unambiguous hex detection.
`uint8ArraysEqual` is a simple linear scan and is deliberately **not** constant-time. Do not use it to compare MACs or cryptographic secrets — use the constant-time comparison from `@suaveplan/crypto-utils` for that purpose.

View File

@@ -0,0 +1,97 @@
# uuid
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
The `uuid` module provides three focused primitives for working with Universally Unique Identifiers in `@suaveplan/bytes`: generating cryptographically random UUIDv4 strings, converting canonical UUID strings to their compact 16-byte binary representation, and converting a 16-byte `Uint8Array` back to the canonical hyphen-separated string. These operations are essential for any system that stores entity identifiers as binary blobs in databases, network protocols, or binary serialisation formats while presenting them as human-readable strings at the API boundary.
All three functions validate their inputs strictly and throw typed `BytesError` values on malformed data, making them safe to use directly in request handlers and protocol parsers.
## Features
- **`generateUuid`** — generates a UUIDv4 string using the platform `crypto.randomUUID()` — no third-party uuid library, no custom random-number manipulation.
- **`uuidToUint8Array`** — parses a canonical UUID string into exactly 16 bytes; validates format with a regex before any byte parsing.
- **`uint8ArrayToUuid`** — formats a 16-byte `Uint8Array` as a canonical lowercase UUID string; validates length before formatting.
- **OTel tracing** — every function is wrapped in a named active span under the `@suaveplan/bytes` tracer.
- **Zod schema** — `UuidStringSchema` validates the canonical UUID format and is composable with any Zod object schema.
## Usage: Basic Example
```ts
import { generateUuid, uuidToUint8Array, uint8ArrayToUuid } from "@suaveplan/bytes/uuid";
// Generate a fresh UUIDv4
const id = generateUuid();
// → "550e8400-e29b-41d4-a716-446655440000" (random each call)
// Convert to binary for compact storage
const bytes = uuidToUint8Array(id);
// → Uint8Array of length 16
// Restore the canonical string
const restored = uint8ArrayToUuid(bytes);
// → same as `id`
```
## Usage: Advanced Example
```ts
import { uuidToUint8Array, uint8ArrayToUuid, BytesError } from "@suaveplan/bytes";
import { UuidStringSchema } from "@suaveplan/bytes/uuid";
import z from "zod";
// Validate and convert an incoming UUID header to binary for DB storage
const RequestSchema = z.object({
entityId: UuidStringSchema,
});
function handleRequest(raw: unknown): Uint8Array {
const { entityId } = RequestSchema.parse(raw);
try {
return uuidToUint8Array(entityId);
} catch (err) {
if (err instanceof BytesError) {
// err.code === "BYTES.UUID.INVALID_FORMAT"
throw new Error(`Malformed entity ID: ${err.message}`);
}
throw err;
}
}
// Convert back to string for API responses
function toApiResponse(raw: Uint8Array): string {
return uint8ArrayToUuid(raw);
}
```
## API Reference
### `generateUuid(): string`
Generate a UUID version 4 string using `crypto.randomUUID()`. Output is always lowercase and matches the pattern `/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/`.
### `uuidToUint8Array(uuid: string): Uint8Array`
Parse a canonical UUID string (upper- or lowercase) into a 16-byte `Uint8Array`. Strips the four hyphens, then reads each pair of hex digits as a byte.
**Throws** `BytesError(BYTES.UUID.INVALID_FORMAT)` when `uuid` does not match the canonical pattern `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`.
### `uint8ArrayToUuid(bytes: Uint8Array): string`
Format a 16-byte `Uint8Array` as a canonical lowercase UUID string in the form `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`.
**Throws** `BytesError(BYTES.UUID.INVALID_LENGTH)` when `bytes.length !== 16`.
### `UuidStringSchema` (from `uuid.schemas.ts`)
Zod schema that validates the canonical UUID format. Accepts both upper- and lowercase hex digits. Does not verify UUID version or variant bits.
## Implementation Notes
`generateUuid` delegates entirely to the platform `crypto.randomUUID()` (Node 14.17+, Bun, all modern browsers), ensuring the UUID is generated from a cryptographically secure source of randomness with no custom bit-twiddling. A polyfill that sets `globalThis.crypto = require("node:crypto").webcrypto` is included in the package test setup so that Node `forks`-pool Vitest runs have access to the `crypto` global.
`uuidToUint8Array` validates the input with a compiled regex before any byte parsing. This rejects inputs that are structurally wrong (wrong group lengths, non-hex chars, missing hyphens) with a single check rather than per-byte error detection.
`uint8ArrayToUuid` materialises a hex string from the byte array, then splices in the four hyphens at the standard offsets (8, 12, 16, 20 hex chars). Output is always lowercase because `Number.prototype.toString(16)` produces lowercase hex on all platforms.