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:
@@ -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 |
|
||||
|
||||
@@ -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).
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user