5.4 KiB
encoding
(no submodules)
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 —
bytesToHexemits lowercase two-character hex pairs;hexToBytesaccepts both upper- and lowercase and rejects odd-length or non-hex inputs withBYTES.ENCODE.INVALID_HEX. - Base64 encoding/decoding —
bytesToBase64uses the standard (non-URL-safe) alphabet;base64ToBytesvalidates the input against the standard alphabet before delegating toatob, rethrowing any decode error asBYTES.ENCODE.INVALID_BASE64. - UTF-8 encoding/decoding —
utf8ToBytesusesTextEncoderandbytesToUtf8usesTextDecoder, both handling multi-byte Unicode code points correctly. - OTel tracing — every exported function wraps its body in a named active span under the
@suaveplan/bytestracer so encoding operations appear in distributed traces. - Zod schemas —
HexStringSchemaandBase64StringSchemaare exported fromencoding.schemas.tsfor 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
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
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.lengthis odd.hexcontains 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).