first commit
This commit is contained in:
103
docs/foundation/data/bytes/src/encoding/encoding.md
Normal file
103
docs/foundation/data/bytes/src/encoding/encoding.md
Normal 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).
|
||||
Reference in New Issue
Block a user