mod.ts

A comprehensive module providing type-safe binary structure encoding and decoding utilities for TypeScript.

Examples

Reading and writing WAV (RIFF) file format:

import { assertEquals } from "@std/assert";
import { struct, array, string } from "@hertzg/binstruct";
import { u16le, u32le, u8le } from "@hertzg/binstruct/numeric";

// Define WAV file structure following RIFF format specification
const riffChunkCoder = struct({
  chunkID: string(4),           // "RIFF" (fixed 4-byte string)
  chunkSize: u32le(),           // File size - 8 bytes
  format: string(4),            // "WAVE" (fixed 4-byte string)
});

const fmtChunkCoder = struct({
  chunkID: string(4),           // "fmt " (fixed 4-byte string)
  chunkSize: u32le(),           // Size of fmt chunk (16 for PCM)
  audioFormat: u16le(),         // Audio format (1 = PCM)
  numChannels: u16le(),         // Number of channels (1 = mono, 2 = stereo)
  sampleRate: u32le(),          // Sample rate (e.g., 44100 Hz)
  byteRate: u32le(),            // Byte rate (sampleRate * numChannels * bitsPerSample / 8)
  blockAlign: u16le(),          // Block align (numChannels * bitsPerSample / 8)
  bitsPerSample: u16le(),       // Bits per sample (8, 16, 24, 32)
});

const dataChunkCoder = struct({
  chunkID: string(4),           // "data" (fixed 4-byte string)
  chunkSize: u32le(),           // Size of audio data
  audioData: array(u8le(), u32le()), // Audio samples as length-prefixed array
});

// Complete WAV file structure
const wavFileCoder = struct({
  riff: riffChunkCoder,
  fmt: fmtChunkCoder,
  data: dataChunkCoder,
});

// Create sample WAV data (8kHz, 8-bit, mono, 0.1 second - small example)
const sampleRate = 8000;
const numChannels = 1;
const bitsPerSample = 8;
const durationSeconds = 0.1;
const numSamples = Math.floor(sampleRate * durationSeconds);

// Generate a simple sine wave (440 Hz) with 8-bit samples
const audioData = new Array(numSamples);
for (let i = 0; i < numSamples; i++) {
  const t = i / sampleRate;
  audioData[i] = Math.floor(Math.sin(2 * Math.PI * 440 * t) * 127) + 128; // 8-bit amplitude (0-255)
}

const wavData = {
  riff: {
    chunkID: "RIFF",
    chunkSize: 0, // Will be calculated
    format: "WAVE",
  },
  fmt: {
    chunkID: "fmt ",
    chunkSize: 16,
    audioFormat: 1, // PCM
    numChannels,
    sampleRate,
    byteRate: sampleRate * numChannels * bitsPerSample / 8,
    blockAlign: numChannels * bitsPerSample / 8,
    bitsPerSample,
  },
  data: {
    chunkID: "data",
    chunkSize: 0, // Will be calculated
    audioData,
  },
};

// Calculate chunk sizes
const dataSize = audioData.length * (bitsPerSample / 8);
wavData.data.chunkSize = dataSize;
wavData.riff.chunkSize = 36 + dataSize; // 36 = 12 (RIFF) + 24 (fmt) + data size

// Encode WAV data to binary
const buffer = new Uint8Array(1024);
const bytesWritten = wavFileCoder.encode(wavData, buffer);

// Decode WAV data from binary
const [decoded, bytesRead] = wavFileCoder.decode(buffer);

// Verify the data matches using assertions
assertEquals(decoded.riff.chunkID, "RIFF", 'RIFF chunk ID should be "RIFF"');
assertEquals(decoded.riff.format, "WAVE", 'RIFF format should be "WAVE"');
assertEquals(decoded.fmt.chunkID, "fmt ", 'fmt chunk ID should be "fmt "');
assertEquals(decoded.fmt.audioFormat, 1, 'Audio format should be PCM (1)');
assertEquals(decoded.fmt.numChannels, numChannels, 'Number of channels should match');
assertEquals(decoded.fmt.sampleRate, sampleRate, 'Sample rate should match');
assertEquals(decoded.fmt.bitsPerSample, bitsPerSample, 'Bits per sample should match');
assertEquals(decoded.data.chunkID, "data", 'Data chunk ID should be "data"');
assertEquals(decoded.data.audioData.length, audioData.length, 'Audio data length should match');
assertEquals(bytesWritten, bytesRead, 'Bytes written should equal bytes read');
assertEquals(decoded.riff.chunkSize, wavData.riff.chunkSize, 'RIFF chunk size should match');
assertEquals(decoded.data.chunkSize, wavData.data.chunkSize, 'Data chunk size should match');

// Verify audio data integrity
for (let i = 0; i < Math.min(100, audioData.length); i++) {
  assertEquals(decoded.data.audioData[i], audioData[i], `Audio sample at index ${i} should match`);
}

Parsing complete network stack (Ethernet + IP + TCP):

import { assertEquals } from "@std/assert";
import { struct, array, string, refine } from "@hertzg/binstruct";
import { u16be, u32be, u8be } from "@hertzg/binstruct/numeric";

const macAddr = refine(array(u8be(), 6), {
  refine: (arr: number[]) => arr.map(b => b.toString(16).padStart(2, '0').toUpperCase()).join(':'),
  unrefine: (mac: string) => mac.split(':').map(hex => parseInt(hex, 16)),
});

const ipAddr = refine(array(u8be(), 4), {
  refine: (arr: number[]) => arr.join('.'),
  unrefine: (ip: string) => ip.split('.').map(octet => parseInt(octet, 10)),
});

// Define Ethernet frame structure (IEEE 802.3)
const ethernetFrameCoder = struct({
  destinationMAC: macAddr(),            // MAC address as colon-separated hex string
  sourceMAC: macAddr(),                 // MAC address as colon-separated hex string
  etherType: u16be(),                   // EtherType (0x0800 for IPv4)
});

// Define IPv4 header structure (RFC 791)
const ipv4HeaderCoder = struct({
  version: u8be(),                      // Version (4) and IHL (5 words = 20 bytes)
  tos: u8be(),                          // Type of Service
  totalLength: u16be(),                 // Total packet length
  identification: u16be(),              // Packet identification
  flags: u16be(),                       // Flags and fragment offset
  ttl: u8be(),                          // Time to Live
  protocol: u8be(),                     // Protocol (6 for TCP)
  checksum: u16be(),                    // Header checksum
  sourceIP: ipAddr(),                   // IP address as dot-separated decimal string
  destIP: ipAddr(),                     // IP address as dot-separated decimal string
  options: array(u8be(), 0),            // IP options (empty for this example)
});

// Define TCP header structure (RFC 793)
const tcpHeaderCoder = struct({
  sourcePort: u16be(),                   // Source port
  destPort: u16be(),                     // Destination port
  sequenceNumber: u32be(),               // Sequence number
  ackNumber: u32be(),                    // Acknowledgment number
  dataOffset: u8be(),                    // Data offset and flags
  flags: u8be(),                         // Control flags
  windowSize: u16be(),                   // Window size
  checksum: u16be(),                     // TCP checksum
  urgentPointer: u16be(),                // Urgent pointer
  options: array(u8be(), 0),             // TCP options (empty for this example)
});

// Define complete network packet structure
const networkPacketCoder = struct({
  ethernet: ethernetFrameCoder,
  ip: ipv4HeaderCoder,
  tcp: tcpHeaderCoder,
  payload: array(u8be(), u16be()),       // Length-prefixed payload
});

// Create sample network packet data
const networkPacket = {
  ethernet: {
    destinationMAC: "00:1B:21:BB:0F:3B",                    // Router MAC as string
    sourceMAC: "00:0C:29:2E:84:5A",                         // Host MAC as string
    etherType: 0x0800,                                      // IPv4
  },
  ip: {
    version: 0x45,                       // IPv4, 5 words header
    tos: 0x00,                          // Normal precedence
    totalLength: 0,                      // Will be calculated
    identification: 0x1234,              // Packet ID
    flags: 0x4000,                       // Don't fragment
    ttl: 64,                            // Time to live
    protocol: 6,                         // TCP
    checksum: 0,                         // Will be calculated
    sourceIP: "192.168.1.100",           // Source IP as string
    destIP: "10.0.0.50",                 // Destination IP as string
    options: [],                         // No IP options
  },
  tcp: {
    sourcePort: 49152,                   // Dynamic port
    destPort: 80,                        // HTTP port
    sequenceNumber: 0x12345678,          // Initial sequence
    ackNumber: 0,                        // No acknowledgment
    dataOffset: 0x50,                    // 5 words header
    flags: 0x02,                         // SYN flag
    windowSize: 65535,                   // Maximum window
    checksum: 0,                         // Will be calculated
    urgentPointer: 0,                    // No urgent data
    options: [],                         // No TCP options
  },
  payload: [0x48, 0x65, 0x6c, 0x6c, 0x6f], // "Hello" payload
};

// Calculate packet lengths
const tcpHeaderLength = 20;              // Standard TCP header
const payloadLength = networkPacket.payload.length;
const payloadLengthPrefix = 2;           // u16 length prefix for array
const totalTcpLength = tcpHeaderLength + payloadLength + payloadLengthPrefix;
const ipHeaderLength = 20;               // Standard IP header
const totalPacketLength = ipHeaderLength + totalTcpLength;
const ethernetHeaderLength = 14;         // Ethernet header (6+6+2 bytes)
const totalFrameLength = ethernetHeaderLength + totalPacketLength;

// Update length fields
networkPacket.ip.totalLength = totalPacketLength;
networkPacket.tcp.dataOffset = 0x50;     // 5 words header

// Encode complete network packet
const buffer = new Uint8Array(2048);
const bytesWritten = networkPacketCoder.encode(networkPacket, buffer);

// Decode complete network packet
const [decoded, bytesRead] = networkPacketCoder.decode(buffer);

// Verify Ethernet frame using assertions
assertEquals(decoded.ethernet.destinationMAC, "00:1B:21:BB:0F:3B", 'Destination MAC should match router');
assertEquals(decoded.ethernet.sourceMAC, "00:0C:29:2E:84:5A", 'Source MAC should match host');
assertEquals(decoded.ethernet.etherType, 0x0800, 'EtherType should be IPv4 (0x0800)');

// Verify IP header using assertions
assertEquals(decoded.ip.version, 0x45, 'IP version should be IPv4 with 5 words header');
assertEquals(decoded.ip.protocol, 6, 'Protocol should be TCP (6)');
assertEquals(decoded.ip.ttl, 64, 'TTL should be 64');
assertEquals(decoded.ip.sourceIP, "192.168.1.100", 'Source IP should match');
assertEquals(decoded.ip.destIP, "10.0.0.50", 'Destination IP should match');
assertEquals(decoded.ip.totalLength, totalPacketLength, 'IP total length should match calculated value');
assertEquals(decoded.ip.identification, 0x1234, 'Packet ID should match');
assertEquals(decoded.ip.flags, 0x4000, 'Flags should indicate no fragmentation');

// Verify TCP header using assertions
assertEquals(decoded.tcp.sourcePort, 49152, 'Source port should be 49152');
assertEquals(decoded.tcp.destPort, 80, 'Destination port should be 80 (HTTP)');
assertEquals(decoded.tcp.sequenceNumber, 0x12345678, 'Sequence number should match');
assertEquals(decoded.tcp.ackNumber, 0, 'Acknowledgment number should be 0');
assertEquals(decoded.tcp.dataOffset, 0x50, 'Data offset should be 5 words');
assertEquals(decoded.tcp.flags, 0x02, 'SYN flag should be set');
assertEquals(decoded.tcp.windowSize, 65535, 'Window size should be maximum');
assertEquals(decoded.tcp.urgentPointer, 0, 'Urgent pointer should be 0');

// Verify payload using assertions
assertEquals(decoded.payload.length, 5, 'Payload should have 5 bytes');
assertEquals(decoded.payload, [0x48, 0x65, 0x6c, 0x6c, 0x6f], 'Payload should be "Hello"');
assertEquals(String.fromCharCode(...decoded.payload), "Hello", 'Payload should decode to "Hello"');

// Verify complete packet integrity using assertions
assertEquals(bytesWritten, bytesRead, 'Bytes written should equal bytes read');
assertEquals(decoded.ip.totalLength, totalPacketLength, 'IP total length should match calculated value');
assertEquals(decoded.tcp.dataOffset & 0xf0, 0x50, 'TCP data offset should be 5 words');

// Verify frame size calculations
const calculatedFrameSize = ethernetHeaderLength + ipHeaderLength + tcpHeaderLength + payloadLength + payloadLengthPrefix;
assertEquals(bytesWritten, calculatedFrameSize, 'Total frame size should match calculated value');
assertEquals(calculatedFrameSize, 61, 'Frame size should be 61 bytes (14+20+20+2+5)');

// Verify protocol stack hierarchy
assertEquals(decoded.ethernet.etherType, 0x0800, 'Ethernet should carry IPv4');
assertEquals(decoded.ip.protocol, 6, 'IPv4 should carry TCP');
assertEquals(decoded.tcp.destPort, 80, 'TCP should be destined for HTTP');

Functions

f
array<TDecoded>(
elementType: Coder<TDecoded>,
lengthCoderOrLengthTypeOrCondition: Coder<number> | LengthOrRef | ArrayWhileCondition<TDecoded>
): Coder<TDecoded[]>
2 overloads

Creates a Coder for arrays that automatically chooses between length-prefixed and fixed-length based on the arguments provided.

f
arrayFL<TDecoded>(
elementType: Coder<TDecoded>,
lengthOrRef: LengthOrRef
): Coder<TDecoded[]>

Creates a Coder for fixed-length arrays of a given element type.

f
arrayLP<TDecoded>(
elementType: Coder<TDecoded>,
lengthType: Coder<number>
): Coder<TDecoded[]>

Creates a Coder for length-prefixed arrays of a given element type.

f
arrayWhile<TDecoded>(
elementType: Coder<TDecoded>,
condition: ArrayWhileCondition<TDecoded>
): Coder<TDecoded[]>

Creates a Coder for arrays using a custom condition function to determine when to stop.

f
autoGrowBuffer<T>(
tryEncodeFn: (buffer: Uint8Array) => T,
autogrowOptions?: AutogrowOptions
): T

Automatically grows a buffer until the encoding function succeeds.

f
bitStruct<T extends BitSchema>(schema: T): Coder<BitStructDecoded<T>>

Creates a Coder for bit-packed structures with MSB-first ordering.

f
createContext(direction: "encode" | "decode"): Context

Creates a default context for encoding or decoding operations.

f
decode<T>(
coder: Coder<T>,
buffer: Uint8Array,
context?: Context
): T

Decodes data using the provided coder, returning the decoded value.

f
encode<T>(
coder: Coder<T>,
data: T,
context?: Context,
target?: Uint8Array,
autogrowOptions?: AutogrowOptions
): Uint8Array

Encodes data using the provided coder, handling buffer allocation automatically.

f
f16(endianness?: Endianness): Coder<number>

Creates a coder for 16-bit floating-point numbers.

f
f16be(): Coder<number>

Convenience function for 16-bit floating point with big-endian byte order.

f
f16le(): Coder<number>

Convenience function for 16-bit floating point with little-endian byte order.

f
f32(endianness?: Endianness): Coder<number>

Creates a coder for 32-bit floating-point numbers.

f
f32be(): Coder<number>

Convenience function for 32-bit floating point with big-endian byte order.

f
f32le(): Coder<number>

Convenience function for 32-bit floating point with little-endian byte order.

f
f64(endianness?: Endianness): Coder<number>

Creates a coder for 64-bit floating-point numbers.

f
f64be(): Coder<number>

Convenience function for 64-bit floating point with big-endian byte order.

f
f64le(): Coder<number>

Convenience function for 64-bit floating point with little-endian byte order.

f
isCoder<TDecoded>(value: unknown): value is Coder<TDecoded>

Type guard to check if a value is a Coder.

f
isLengthOrRef(value: unknown): value is LengthOrRef

Type guard to check if a value is a valid length or a reference to a length.

f
isRef<T>(value: unknown): value is RefValue<T>

Checks if a value is a reference created by the ref function.

f
isValidLength(length: number): boolean

Validates if a length value is valid for binary encoding.

f
lazy<TDecoded>(factory: () => Coder<TDecoded>): Coder<TDecoded>

Wraps a coder factory so the coder it produces is built on first use instead of when lazy() is called, and only ever built once.

f
lengthRefGet(
ctx: Context | undefined | null,
lengthOrRef: LengthOrRef
): number | undefined

Attempts to resolve a length value from a reference or literal.

f
ref<TDecoded>(coder: Coder<TDecoded>): RefValue<TDecoded>

Creates a reference value that can be resolved during encoding/decoding.

f
refGetValue<T>(
ctx: Context | null | undefined,
refOrValue: RefValue<T> | NoInfer<T>
): NoInfer<T> | undefined

Retrieves the value from a reference or returns the value directly if it's not a reference.

f
refineFields<
TCoders extends FieldCoders,
THost extends [K in keyof TCoders]: Uint8Array
>
(coders: TCoders): Refiner<THost, Omit<THost, keyof TCoders> & DecodedFields<TCoders>, []>

Creates a Refiner that swaps named Uint8Array fields of a host record for the typed values produced by their sub-coders. Fields not in coders are passed through unchanged.

f
refSetValue<T>(
ctx: Context | null | undefined,
coder: Coder<T>,
value: T
): void

Sets a value in the context for a specific coder reference.

f
s16(endianness?: Endianness): Coder<number>

Creates a coder for 16-bit signed integers.

f
s16be(): Coder<number>

Convenience function for 16-bit signed integer with big-endian byte order.

f
s16le(): Coder<number>

Convenience function for 16-bit signed integer with little-endian byte order.

f
s32(endianness?: Endianness): Coder<number>

Creates a coder for 32-bit signed integers.

f
s32be(): Coder<number>

Convenience function for 32-bit signed integer with big-endian byte order.

f
s32le(): Coder<number>

Convenience function for 32-bit signed integer with little-endian byte order.

f
s64(endianness?: Endianness): Coder<bigint>

Creates a coder for 64-bit signed integers.

f
s64be(): Coder<bigint>

Convenience function for 64-bit signed integer with big-endian byte order.

f
s64le(): Coder<bigint>

Convenience function for 64-bit signed integer with little-endian byte order.

f
s8(endianness?: Endianness): Coder<number>

Creates a coder for 8-bit signed integers.

f
s8be(): Coder<number>

Convenience function for 8-bit signed integer with big-endian byte order.

f
s8le(): Coder<number>

Convenience function for 8-bit signed integer with little-endian byte order.

f
string(
lengthOrLengthType?: Coder<number> | LengthOrRef | null,
decoderEncoding?: string,
decoderOptions?: TextDecoderOptions
): Coder<string>

Creates a Coder for strings that automatically chooses between length-prefixed, null-terminated, and fixed-length based on the arguments provided.

f
stringLP(lengthType: Coder<number>): Coder<string>

Creates a Coder for length-prefixed strings.

f
stringNT(): Coder<string>

Creates a Coder for null-terminated strings.

f
struct<T extends Record<string, Coder<any>>>(schema: T): Coder<StructDecoded<T>>

Creates a Coder for structured data from an object of property names to coders.

f
u16(endianness?: Endianness): Coder<number>

Creates a coder for 16-bit unsigned integers.

f
u16be(): Coder<number>

Convenience function for 16-bit unsigned integer with big-endian byte order.

f
u16le(): Coder<number>

Convenience function for 16-bit unsigned integer with little-endian byte order.

f
u32(endianness?: Endianness): Coder<number>

Creates a coder for 32-bit unsigned integers.

f
u32be(): Coder<number>

Convenience function for 32-bit unsigned integer with big-endian byte order.

f
u32le(): Coder<number>

Convenience function for 32-bit unsigned integer with little-endian byte order.

f
u64(endianness?: Endianness): Coder<bigint>

Creates a coder for 64-bit unsigned integers.

f
u64be(): Coder<bigint>

Convenience function for 64-bit unsigned integer with big-endian byte order.

f
u64le(): Coder<bigint>

Convenience function for 64-bit unsigned integer with little-endian byte order.

f
u8(endianness?: Endianness): Coder<number>

Creates a coder for 8-bit unsigned integers.

f
u8be(): Coder<number>

Convenience function for 8-bit unsigned integer with big-endian byte order.

f
u8le(): Coder<number>

Convenience function for 8-bit unsigned integer with little-endian byte order.

f
withRefsInContext(ctx: Context): Context

Ensures that a context has the necessary reference storage initialized.

Interfaces

I
AutogrowOptions

Configuration options for automatic buffer growth.

  • growthFactor: number

    Growth factor multiplier for buffer resizing. Must be greater than 1. Each resize multiplies the current size by this factor. Defaults to 2 (doubling).

  • initialSize: number

    Initial buffer size in bytes. Must be greater than 0 and less than or equal to maxByteLength. Defaults to 4096 bytes (4KB).

  • maxByteLength: number

    Maximum buffer size in bytes. The buffer will not grow beyond this limit. When reached, a RangeError will be thrown. Defaults to 400MB.

I
Context

Context for encoding/decoding operations.

I
RefsWeakMap

A weak map interface for storing references in the encoding/decoding context.

Type Aliases

T
ArrayWhileCondition<TDecoded> = (params: { index: number; array: TDecoded[]; buffer: Uint8Array; context: Context; }) => boolean

Condition function type for arrayWhile that determines when to continue processing array elements.

T
BitSchema = Record<string, number>

Schema type for bitStruct, mapping field names to bit counts.

T
BitStructDecoded<T extends BitSchema> = [K in keyof T]: number

Type of the decoded value from a bitStruct. All fields are decoded as numbers.

T
Coder<TDecoded> = { [kCoderKind]: symbol; encode: Encoder<TDecoded>; decode: Decoder<TDecoded>; }

Interface for coders that can encode and decode values.

T
DecodedFields<TCoders extends FieldCoders> = [K in keyof TCoders]: TCoders[K] extends Coder<infer T> ? T : never

Mapped type extracting the decoded value type for each entry in a FieldCoders map. Given { payload: Coder<Ipv4> } it yields { payload: Ipv4 }.

T
Endianness = "be" | "le"

Endianness type for numeric data encoding and decoding.

T
FieldCoders = Record<string, Coder<any>>

Map of host field names to the sub-coder that decodes/encodes that field's bytes. Used as the input to refineFields.

T
LengthOrRef = number | RefValue<number>

A type representing a length value that can be either a number or a reference to a number.

T
RefValue<TDecoded> = { (ctx: Context): TDecoded; [kIsRefValue]: true; }

A type representing a reference value that can be resolved during encoding/decoding.

T
StructDecoded<T extends Record<string, Coder<any>>> = [K in keyof T]: T[K] extends Coder<infer U> ? U : never

The decoded object type of a struct schema: each property's coder Coder<U> contributes a property of type U.

T
ValueWithBytes<T> = [T, number]

Type representing a value with its byte count.

Variables

v
kCoderKind: symbol

Symbol identifier for coder kind.

v
kCtxRefs: symbol

Symbol identifier for context references.

v
kIsRefValue: symbol

Symbol identifier for reference values.

v
kKindArrayFL: symbol

Symbol identifier for fixed-length array coders.

v
kKindArrayLP: symbol

Symbol identifier for length-prefixed array coders.

v
kKindArrayWhile: symbol

Symbol identifier for conditional while-loop array coders.

v
kKindStringFL: symbol

Symbol identifier for fixed-length string coders.

v
kKindStringLP: symbol

Symbol identifier for length-prefixed string coders.

v
kKindStringNT: symbol

Symbol identifier for null-terminated string coders.

bits/mod.ts

Bit-level encoding and decoding utilities for binary structures.

Examples

Basic bit-packed structure

import { assertEquals } from "@std/assert";
import { bitStruct } from "@hertzg/binstruct/bits";

// Define a bit-packed header (total 8 bits = 1 byte)
const flags = bitStruct({
  enabled: 1,      // 1 bit
  priority: 3,     // 3 bits
  category: 4,     // 4 bits
});

// Encode
const value = { enabled: 1, priority: 5, category: 2 };
const buffer = new Uint8Array(1);
const bytesWritten = flags.encode(value, buffer);

// Buffer contains: 0b1_101_0010 = 0xD2
assertEquals(buffer[0], 0xD2);
assertEquals(bytesWritten, 1);

// Decode
const [decoded, bytesRead] = flags.decode(buffer);
assertEquals(decoded, value);
assertEquals(bytesRead, 1);

Network protocol header (multi-byte)

import { assertEquals } from "@std/assert";
import { bitStruct } from "@hertzg/binstruct/bits";

// 32-bit protocol header
const header = bitStruct({
  version: 4,      // 4 bits
  type: 4,         // 4 bits
  flags: 8,        // 8 bits
  length: 16,      // 16 bits
});                // Total: 32 bits = 4 bytes

const buffer = new Uint8Array(4);
const data = { version: 1, type: 2, flags: 0xFF, length: 1024 };

header.encode(data, buffer);
const [decoded, bytesRead] = header.decode(buffer);

assertEquals(decoded, data);
assertEquals(bytesRead, 4);

Padding to byte boundaries

import { assertEquals } from "@std/assert";
import { bitStruct } from "@hertzg/binstruct/bits";

// Fields don't align to byte boundary - add explicit padding
const flags = bitStruct({
  ready: 1,
  error: 1,
  mode: 2,
  _reserved: 4,    // Padding to reach 8 bits
});

const buffer = new Uint8Array(1);
flags.encode({ ready: 1, error: 0, mode: 3, _reserved: 0 }, buffer);

assertEquals(buffer[0], 0b1_0_11_0000);

Integration with struct

import { assertEquals } from "@std/assert";
import { bitStruct } from "@hertzg/binstruct/bits";
import { struct } from "@hertzg/binstruct/struct";
import { u32le } from "@hertzg/binstruct/numeric";

const flags = bitStruct({
  compressed: 1,
  encrypted: 1,
  version: 6,
});

const packet = struct({
  flags: flags,
  payloadSize: u32le(),
});

const buffer = new Uint8Array(5);
const data = {
  flags: { compressed: 1, encrypted: 0, version: 2 },
  payloadSize: 1024,
};

packet.encode(data, buffer);
const [decoded] = packet.decode(buffer);

assertEquals(decoded.flags.compressed, 1);
assertEquals(decoded.flags.version, 2);
assertEquals(decoded.payloadSize, 1024);

Real-world: PNG/Zlib header

import { assertEquals } from "@std/assert";
import { bitStruct } from "@hertzg/binstruct/bits";

// Zlib header (RFC 1950): 2 bytes with bit fields
// MSB-first ordering means fields are listed in bit order (7→0)
const zlibHeader = bitStruct({
  compressionInfo: 4,      // CMF bits 7-4: Compression info
  compressionMethod: 4,    // CMF bits 3-0: Compression method
  fcheck: 5,               // FLG bits 7-3: Check bits
  fdict: 1,                // FLG bit 2: Preset dictionary
  flevel: 2,               // FLG bits 1-0: Compression level
});

// Decode a common zlib header: 0x78 0x9C
const header = new Uint8Array([0x78, 0x9C]);
const [decoded] = zlibHeader.decode(header);

assertEquals(decoded.compressionMethod, 8);   // Deflate
assertEquals(decoded.compressionInfo, 7);     // 32KB window

Real-world: Ethernet VLAN tag

import { assertEquals } from "@std/assert";
import { bitStruct } from "@hertzg/binstruct/bits";

// 802.1Q VLAN tag: 2 bytes
const vlanTCI = bitStruct({
  pcp: 3,              // Priority Code Point (0-7)
  dei: 1,              // Drop Eligible Indicator
  vid: 12,             // VLAN Identifier (0-4095)
});

// VLAN 100 with priority 5
const value = { pcp: 5, dei: 0, vid: 100 };
const buffer = new Uint8Array(2);

vlanTCI.encode(value, buffer);
const [decoded] = vlanTCI.decode(buffer);

assertEquals(decoded.pcp, 5);
assertEquals(decoded.vid, 100);

Functions

f
bitStruct<T extends BitSchema>(schema: T): Coder<BitStructDecoded<T>>

Creates a Coder for bit-packed structures with MSB-first ordering.

Type Aliases

T
BitSchema = Record<string, number>

Schema type for bitStruct, mapping field names to bit counts.

T
BitStructDecoded<T extends BitSchema> = [K in keyof T]: number

Type of the decoded value from a bitStruct. All fields are decoded as numbers.

buffer.ts

Buffer management utilities for automatic buffer growth during encoding operations.

Functions

f
autoGrowBuffer<T>(
tryEncodeFn: (buffer: Uint8Array) => T,
autogrowOptions?: AutogrowOptions
): T

Automatically grows a buffer until the encoding function succeeds.

Interfaces

I
AutogrowOptions

Configuration options for automatic buffer growth.

  • growthFactor: number

    Growth factor multiplier for buffer resizing. Must be greater than 1. Each resize multiplies the current size by this factor. Defaults to 2 (doubling).

  • initialSize: number

    Initial buffer size in bytes. Must be greater than 0 and less than or equal to maxByteLength. Defaults to 4096 bytes (4KB).

  • maxByteLength: number

    Maximum buffer size in bytes. The buffer will not grow beyond this limit. When reached, a RangeError will be thrown. Defaults to 400MB.

numeric/numeric.ts

Numeric data encoding and decoding utilities for binary structures.

Examples

Basic numeric encoding and decoding:

import { assertEquals } from "@std/assert";
import { u16le, s32be, f64le } from "@hertzg/binstruct/numeric";
import { struct } from "@hertzg/binstruct/struct";

// Create a simple numeric structure
const numericStruct = struct({
  smallNumber: u16le(),      // 16-bit unsigned, little-endian
  signedNumber: s32be(),     // 32-bit signed, big-endian
  floatNumber: f64le(),      // 64-bit float, little-endian
});

// Test data
const testData = {
  smallNumber: 12345,        // Fits in 16-bit unsigned (0-65535)
  signedNumber: -1000000,    // 32-bit signed range
  floatNumber: 3.14159,      // Pi approximation
};

// Encode to binary
const buffer = new Uint8Array(100);
const bytesWritten = numericStruct.encode(testData, buffer);

// Decode from binary
const [decoded, bytesRead] = numericStruct.decode(buffer);

// Verify the results
assertEquals(decoded.smallNumber, testData.smallNumber);
assertEquals(decoded.signedNumber, testData.signedNumber);
assertEquals(decoded.floatNumber, testData.floatNumber);
assertEquals(bytesWritten, bytesRead);

Network protocol with mixed endianness:

import { assertEquals } from "@std/assert";
import { u16be, u32le, u64be } from "@hertzg/binstruct/numeric";
import { struct } from "@hertzg/binstruct/struct";

// Network packet header with mixed endianness
const packetHeader = struct({
  magic: u32le(),            // Magic number (little-endian)
  version: u16be(),          // Protocol version (big-endian, network order)
  flags: u16be(),            // Control flags (big-endian)
  timestamp: u64be(),        // Timestamp (big-endian)
  payloadSize: u32le(),      // Payload size (little-endian)
});

const testPacket = {
  magic: 0x12345678,
  version: 1,
  flags: 0x8000,
  timestamp: 1234567890n,
  payloadSize: 1024,
};

const buffer = new Uint8Array(100);
const bytesWritten = packetHeader.encode(testPacket, buffer);
const [decoded, bytesRead] = packetHeader.decode(buffer);

assertEquals(decoded.magic, testPacket.magic);
assertEquals(decoded.version, testPacket.version);
assertEquals(decoded.flags, testPacket.flags);
assertEquals(decoded.timestamp, testPacket.timestamp);
assertEquals(decoded.payloadSize, testPacket.payloadSize);
assertEquals(bytesWritten, bytesRead);

Functions

f
f16(endianness?: Endianness): Coder<number>

Creates a coder for 16-bit floating-point numbers.

f
f16be(): Coder<number>

Convenience function for 16-bit floating point with big-endian byte order.

f
f16le(): Coder<number>

Convenience function for 16-bit floating point with little-endian byte order.

f
f32(endianness?: Endianness): Coder<number>

Creates a coder for 32-bit floating-point numbers.

f
f32be(): Coder<number>

Convenience function for 32-bit floating point with big-endian byte order.

f
f32le(): Coder<number>

Convenience function for 32-bit floating point with little-endian byte order.

f
f64(endianness?: Endianness): Coder<number>

Creates a coder for 64-bit floating-point numbers.

f
f64be(): Coder<number>

Convenience function for 64-bit floating point with big-endian byte order.

f
f64le(): Coder<number>

Convenience function for 64-bit floating point with little-endian byte order.

f
s16(endianness?: Endianness): Coder<number>

Creates a coder for 16-bit signed integers.

f
s16be(): Coder<number>

Convenience function for 16-bit signed integer with big-endian byte order.

f
s16le(): Coder<number>

Convenience function for 16-bit signed integer with little-endian byte order.

f
s32(endianness?: Endianness): Coder<number>

Creates a coder for 32-bit signed integers.

f
s32be(): Coder<number>

Convenience function for 32-bit signed integer with big-endian byte order.

f
s32le(): Coder<number>

Convenience function for 32-bit signed integer with little-endian byte order.

f
s64(endianness?: Endianness): Coder<bigint>

Creates a coder for 64-bit signed integers.

f
s64be(): Coder<bigint>

Convenience function for 64-bit signed integer with big-endian byte order.

f
s64le(): Coder<bigint>

Convenience function for 64-bit signed integer with little-endian byte order.

f
s8(endianness?: Endianness): Coder<number>

Creates a coder for 8-bit signed integers.

f
s8be(): Coder<number>

Convenience function for 8-bit signed integer with big-endian byte order.

f
s8le(): Coder<number>

Convenience function for 8-bit signed integer with little-endian byte order.

f
u16(endianness?: Endianness): Coder<number>

Creates a coder for 16-bit unsigned integers.

f
u16be(): Coder<number>

Convenience function for 16-bit unsigned integer with big-endian byte order.

f
u16le(): Coder<number>

Convenience function for 16-bit unsigned integer with little-endian byte order.

f
u32(endianness?: Endianness): Coder<number>

Creates a coder for 32-bit unsigned integers.

f
u32be(): Coder<number>

Convenience function for 32-bit unsigned integer with big-endian byte order.

f
u32le(): Coder<number>

Convenience function for 32-bit unsigned integer with little-endian byte order.

f
u64(endianness?: Endianness): Coder<bigint>

Creates a coder for 64-bit unsigned integers.

f
u64be(): Coder<bigint>

Convenience function for 64-bit unsigned integer with big-endian byte order.

f
u64le(): Coder<bigint>

Convenience function for 64-bit unsigned integer with little-endian byte order.

f
u8(endianness?: Endianness): Coder<number>

Creates a coder for 8-bit unsigned integers.

f
u8be(): Coder<number>

Convenience function for 8-bit unsigned integer with big-endian byte order.

f
u8le(): Coder<number>

Convenience function for 8-bit unsigned integer with little-endian byte order.

Type Aliases

T
Endianness = "be" | "le"

Endianness type for numeric data encoding and decoding.

array/array.ts

Array coders for binary structures.

Examples

Length-prefixed and fixed-length arrays

import { assertEquals } from "@std/assert";
import { array, arrayLP, arrayFL } from "@hertzg/binstruct/array";
import { struct } from "@hertzg/binstruct/struct";
import { u8le, u16le } from "@hertzg/binstruct/numeric";

// A structure mixing both array kinds
const coder = struct({
  lenPref: arrayLP(u8le(), u16le()), // [len:u16] followed by items
  fixed: arrayFL(u8le(), 3),         // exactly 3 items
  auto: array(u8le(), 2),            // auto-selects fixed-length
  while: array(u8le(), ({ index }) => index < 2), // while index < 2
});

const value = { lenPref: [1, 2, 3], fixed: [4, 5, 6], auto: [7, 8], while: [9, 10] };
const buf = new Uint8Array(1024);
const written = coder.encode(value, buf);
const [decoded, read] = coder.decode(buf);

assertEquals(decoded, value);
assertEquals(written, read);

Functions

f
array<TDecoded>(
elementType: Coder<TDecoded>,
lengthCoderOrLengthTypeOrCondition: Coder<number> | LengthOrRef | ArrayWhileCondition<TDecoded>
): Coder<TDecoded[]>
2 overloads

Creates a Coder for arrays that automatically chooses between length-prefixed and fixed-length based on the arguments provided.

f
arrayFL<TDecoded>(
elementType: Coder<TDecoded>,
lengthOrRef: LengthOrRef
): Coder<TDecoded[]>

Creates a Coder for fixed-length arrays of a given element type.

f
arrayLP<TDecoded>(
elementType: Coder<TDecoded>,
lengthType: Coder<number>
): Coder<TDecoded[]>

Creates a Coder for length-prefixed arrays of a given element type.

f
arrayWhile<TDecoded>(
elementType: Coder<TDecoded>,
condition: ArrayWhileCondition<TDecoded>
): Coder<TDecoded[]>

Creates a Coder for arrays using a custom condition function to determine when to stop.

Type Aliases

T
ArrayWhileCondition<TDecoded> = (params: { index: number; array: TDecoded[]; buffer: Uint8Array; context: Context; }) => boolean

Condition function type for arrayWhile that determines when to continue processing array elements.

Variables

v
kKindArrayFL: symbol

Symbol identifier for fixed-length array coders.

v
kKindArrayLP: symbol

Symbol identifier for length-prefixed array coders.

v
kKindArrayWhile: symbol

Symbol identifier for conditional while-loop array coders.

struct/struct.ts

Struct coder for binary structures.

Examples

Minimal usage

import { assertEquals } from "@std/assert";
import { struct } from "@hertzg/binstruct/struct";
import { u16le, u8le } from "@hertzg/binstruct/numeric";

const coder = struct({ id: u16le(), flag: u8le() });
const value = { id: 513, flag: 7 };
const buf = new Uint8Array(32);
const written = coder.encode(value, buf);
const [decoded, read] = coder.decode(buf);

assertEquals(decoded, value);
assertEquals(written, read);

Functions

f
struct<T extends Record<string, Coder<any>>>(schema: T): Coder<StructDecoded<T>>

Creates a Coder for structured data from an object of property names to coders.

Type Aliases

T
StructDecoded<T extends Record<string, Coder<any>>> = [K in keyof T]: T[K] extends Coder<infer U> ? U : never

The decoded object type of a struct schema: each property's coder Coder<U> contributes a property of type U.

string/string.ts

String coders for binary structures.

Examples

Using all string variants

import { assertEquals } from "@std/assert";
import { string, stringLP, stringNT, stringFL } from "@hertzg/binstruct/string";
import { struct } from "@hertzg/binstruct/struct";
import { u8le, u16le } from "@hertzg/binstruct/numeric";

const coder = struct({
  lp: stringLP(u16le()), // [len:u16] followed by UTF-8
  nt: stringNT(),        // UTF-8 bytes followed by 0x00
  fl: stringFL(5),       // exactly 5 bytes
  age: u8le(),
});

const value = { lp: "alpha", nt: "beta", fl: "gamma", age: 42 };
const buf = new Uint8Array(256);
const written = coder.encode(value, buf);
const [decoded, read] = coder.decode(buf);

assertEquals(decoded.lp, value.lp);
assertEquals(decoded.nt, value.nt);
assertEquals(decoded.fl, value.fl);
assertEquals(decoded.age, value.age);
assertEquals(written, read);

Functions

f
string(
lengthOrLengthType?: Coder<number> | LengthOrRef | null,
decoderEncoding?: string,
decoderOptions?: TextDecoderOptions
): Coder<string>

Creates a Coder for strings that automatically chooses between length-prefixed, null-terminated, and fixed-length based on the arguments provided.

f
stringLP(lengthType: Coder<number>): Coder<string>

Creates a Coder for length-prefixed strings.

f
stringNT(): Coder<string>

Creates a Coder for null-terminated strings.

Variables

v
kKindStringFL: symbol

Symbol identifier for fixed-length string coders.

v
kKindStringLP: symbol

Symbol identifier for length-prefixed string coders.

v
kKindStringNT: symbol

Symbol identifier for null-terminated string coders.

bytes/bytes.ts

Byte-slice coder for binary structures.

Examples

Fixed and variable length

import { assertEquals } from "@std/assert";
import { bytes } from "@hertzg/binstruct/bytes";

const fixed = bytes(4);
const variable = bytes();

const input = new Uint8Array([1, 2, 3, 4, 5]);
const buf = new Uint8Array(32);

const w1 = fixed.encode(input, buf);
const [d1] = fixed.decode(buf);
assertEquals(Array.from(d1), [1, 2, 3, 4]);

const w2 = variable.encode(input, buf);
const [d2] = variable.decode(buf);
assertEquals(Array.from(d2.slice(0, input.length)), Array.from(input));
assertEquals(typeof w1, "number");
assertEquals(typeof w2, "number");

Functions

refine/refine.ts

This module provides a refinement system for binary structure coders. It allows you to transform decoded values into refined types and vice versa during encoding/decoding operations.

Examples

Example 1

import { assertEquals } from "@std/assert";
import { u8, refine } from "@hertzg/binstruct";

const bitfield = refine(u8(), {
  refine: (unrefined: number) =>
    unrefined.toString(2)
      .padStart(8, "0")
      .split("")
      .map(Number),
  unrefine: (refined) => parseInt(refined.join(""), 2),
});

const coder = bitfield();
const buffer = new Uint8Array(10);

const bytesWritten = coder.encode([1, 0, 1, 0, 1, 0, 1, 0], buffer);
const [decoded, bytesRead] = coder.decode(buffer);

assertEquals(bytesWritten, 1);
assertEquals(bytesRead, bytesWritten);
assertEquals(buffer[0], 0b10101010);
assertEquals(decoded, [1, 0, 1, 0, 1, 0, 1, 0]);

Example 2

import { assertEquals } from "@std/assert";
import { u8, refine } from "@hertzg/binstruct";
import type { Context } from "@hertzg/binstruct";

const u8Mapped = refine(u8(), {
  refine: (unrefined, _context, min: number, max: number) : number =>
    (min + (max - min) * unrefined / 0xff) >>> 0,
  unrefine: (refined, _context, min: number, max: number) => ((refined - min) / (max - min) * 0xff) >>> 0,
});

const coder = u8Mapped(-100, 100);
const buffer = new Uint8Array(100);

const bytesWritten = coder.encode(0, buffer);
const [decoded, bytesRead] = coder.decode(buffer);

assertEquals(bytesWritten, 1);
assertEquals(bytesRead, bytesWritten);
assertEquals(buffer[0], 0x7f);
assertEquals(decoded, 0);

Functions

Type Aliases

T
DecodedFields<TCoders extends FieldCoders> = [K in keyof TCoders]: TCoders[K] extends Coder<infer T> ? T : never

Mapped type extracting the decoded value type for each entry in a FieldCoders map. Given { payload: Coder<Ipv4> } it yields { payload: Ipv4 }.

T
FieldCoders = Record<string, Coder<any>>

Map of host field names to the sub-coder that decodes/encodes that field's bytes. Used as the input to refineFields.

ref/ref.ts

Reference system for binary data encoding and decoding.

Examples

Example 1

import { assertEquals } from "@std/assert";
import { ref, computedRef, struct, u16le, u8, array } from "@hertzg/binstruct";

// Create references for shared lengths
const channelsLength = u16le();
const pointsLength = u16le();

// Define a structure with shared length references
const coder = struct({
  channelsLength: channelsLength,
  channels: struct({
    r: array(u8(), ref(channelsLength)),
    g: array(u8(), ref(channelsLength)),
    b: array(u8(), ref(channelsLength)),
  }),
  pointsLength: pointsLength,
  points: array(u16le(), ref(pointsLength)),
});

// Test data
const testData = {
  channelsLength: 3,
  channels: {
    r: [255, 128, 64],
    g: [0, 255, 128],
    b: [0, 0, 255],
  },
  pointsLength: 2,
  points: [100, 200],
};

// Encode and decode
const buffer = new Uint8Array(1000);
const bytesWritten = coder.encode(testData, buffer);
const [decoded, bytesRead] = coder.decode(buffer);

// Verify the data
assertEquals(decoded.channelsLength, testData.channelsLength);
assertEquals(decoded.channels.r, testData.channels.r);
assertEquals(decoded.channels.g, testData.channels.g);
assertEquals(decoded.channels.b, testData.channels.b);
assertEquals(decoded.pointsLength, testData.pointsLength);
assertEquals(decoded.points, testData.points);
assertEquals(bytesWritten, bytesRead);

Functions

f
isRef<T>(value: unknown): value is RefValue<T>

Checks if a value is a reference created by the ref function.

f
ref<TDecoded>(coder: Coder<TDecoded>): RefValue<TDecoded>

Creates a reference value that can be resolved during encoding/decoding.

f
refGetValue<T>(
ctx: Context | null | undefined,
refOrValue: RefValue<T> | NoInfer<T>
): NoInfer<T> | undefined

Retrieves the value from a reference or returns the value directly if it's not a reference.

f
refSetValue<T>(
ctx: Context | null | undefined,
coder: Coder<T>,
value: T
): void

Sets a value in the context for a specific coder reference.

f
withRefsInContext(ctx: Context): Context

Ensures that a context has the necessary reference storage initialized.

Interfaces

I
RefsWeakMap

A weak map interface for storing references in the encoding/decoding context.

Type Aliases

T
RefValue<TDecoded> = { (ctx: Context): TDecoded; [kIsRefValue]: true; }

A type representing a reference value that can be resolved during encoding/decoding.

Variables

v
kIsRefValue: symbol

Symbol identifier for reference values.

lazy/lazy.ts

Lazily-built coder for mutually-recursive coder graphs.

Examples

Breaking a build-time cycle between two struct coders

import { assertEquals } from "@std/assert";
import { struct, lazy, type Coder } from "@hertzg/binstruct";
import { u8 } from "@hertzg/binstruct/numeric";

interface Ping {
  kind: 0;
  next: Pong;
}
interface Pong {
  kind: 1;
  ttl: number;
}

// `pingCoder` needs `pongCoder` while building its own `next` field, but
// `pongCoder` is declared afterwards and does not exist yet at that point.
// Without `lazy()` this is a ReferenceError (reading `pongCoder` inside
// its own temporal dead zone) once the graph grows past two coders and
// closes an actual cycle, as it does for tunneling protocols.
const pingCoder: Coder<Ping> = struct({
  kind: u8() as unknown as Coder<0>,
  next: lazy(() => pongCoder),
});

const pongCoder: Coder<Pong> = struct({
  kind: u8() as unknown as Coder<1>,
  ttl: u8(),
});

const buffer = new Uint8Array(8);
const value: Ping = { kind: 0, next: { kind: 1, ttl: 64 } };

const written = pingCoder.encode(value, buffer);
const [decoded, read] = pingCoder.decode(buffer);

assertEquals(decoded, value);
assertEquals(written, read);

Functions

f
lazy<TDecoded>(factory: () => Coder<TDecoded>): Coder<TDecoded>

Wraps a coder factory so the coder it produces is built on first use instead of when lazy() is called, and only ever built once.

helpers.ts

Helper functions for simplified binary encoding and decoding operations.

Examples

Basic encoding and decoding

import { assertEquals } from "@std/assert";
import { encode, decode, struct, u16le, u8le } from "@hertzg/binstruct";

const coder = struct({ id: u16le(), flag: u8le() });
const data = { id: 42, flag: 7 };

// Encode without providing a buffer - auto-allocates
const encoded = encode(coder, data);
assertEquals(encoded.length, 3);

// Decode and get the decoded data
const decodedData = decode(coder, encoded);
assertEquals(decodedData.id, 42);
assertEquals(decodedData.flag, 7);

Using provided target buffer

import { assertEquals } from "@std/assert";
import { encode, decode, struct, u32le } from "@hertzg/binstruct";

const coder = struct({ value: u32le() });
const data = { value: 12345 };
const buffer = new Uint8Array(100);

// Encode to provided buffer
const encoded = encode(coder, data, undefined, buffer);
assertEquals(encoded.length, 4);
assertEquals(encoded.buffer, buffer.buffer);

// Decode from buffer
const decodedData = decode(coder, encoded);
assertEquals(decodedData.value, 12345);

Round-trip encoding and decoding

import { assertEquals } from "@std/assert";
import { encode, decode, struct, u16le, u8le, string } from "@hertzg/binstruct";

const coder = struct({
  id: u16le(),
  name: string(u16le()), // Length-prefixed string
  active: u8le(),
});

const originalData = { id: 1001, name: "test", active: 1 };

// Encode
const encoded = encode(coder, originalData);
assertEquals(encoded.length, 9); // 2 (id) + 2 (name length) + 4 (name bytes) + 1 (active)

// Decode
const decodedData = decode(coder, encoded);

assertEquals(decodedData.id, originalData.id);
assertEquals(decodedData.name, originalData.name);
assertEquals(decodedData.active, originalData.active);

Using custom context

import { assertEquals } from "@std/assert";
import { encode, decode, createContext, struct, u16le } from "@hertzg/binstruct";

const coder = struct({ value: u16le() });
const data = { value: 42 };
const context = createContext("encode");

const encoded = encode(coder, data, context);
assertEquals(encoded.length, 2);

const decodeContext = createContext("decode");
const decodedData = decode(coder, encoded, decodeContext);
assertEquals(decodedData.value, 42);

Functions

f
decode<T>(
coder: Coder<T>,
buffer: Uint8Array,
context?: Context
): T

Decodes data using the provided coder, returning the decoded value.

f
encode<T>(
coder: Coder<T>,
data: T,
context?: Context,
target?: Uint8Array,
autogrowOptions?: AutogrowOptions
): Uint8Array

Encodes data using the provided coder, handling buffer allocation automatically.