micro-packed
Version:
Define complex binary structures using composable primitives
1,178 lines • 51.2 kB
TypeScript
import type { TArg, TRet } from '@scure/base';
import { type Coder as BaseCoder } from '@scure/base';
export type { TArg, TRet } from '@scure/base';
/**
* Define complex binary structures using composable primitives.
* Main ideas:
* - Encode / decode can be chained, same as in `scure-base`
* - A complex structure can be created from an array and struct of primitive types
* - Strings / bytes are arrays with specific optimizations: we can just read bytes directly
* without creating plain array first and reading each byte separately.
* - Types are inferred from definition
* @module
* @example
* Define a struct with numbers, strings, bytes, and nested arrays.
* ```ts
* import * as P from 'micro-packed';
* const s = P.struct({
* field1: P.U32BE, // 32-bit unsigned big-endian integer
* field2: P.string(P.U8), // String with U8 length prefix
* field3: P.bytes(32), // 32 bytes
* field4: P.array(P.U16BE, P.struct({ // Array of structs with U16BE length
* subField1: P.U64BE, // 64-bit unsigned big-endian integer
* subField2: P.string(10) // 10-byte string
* }))
* });
* ```
*/
/**
* Shortcut to zero-length (empty) byte array.
* Keep public Bytes typing, not TRet<Bytes>, so variables inferred from this
* constant can later accept caller-owned Bytes backed by any ArrayBufferLike.
*/
export declare const EMPTY: Bytes;
/**
* Shortcut to one-element (element is 0) byte array.
* Keep the same public Bytes typing rationale as EMPTY.
*/
export declare const NULL: Bytes;
/** Checks if two Uint8Arrays are equal. Not constant-time. */
declare function equalBytes(a: TArg<Uint8Array>, b: TArg<Uint8Array>): boolean;
declare const findBytes: (needle: TArg<Uint8Array>, data: TArg<Uint8Array>, pos?: number) => number | undefined;
/** Checks if the given value is a Uint8Array. */
declare function isBytes(a: unknown): a is Bytes;
/**
* Concatenates multiple Uint8Arrays.
* Engines limit functions to 65K+ arguments.
* @param arrays Array of Uint8Array elements
* @returns Concatenated Uint8Array
*/
declare function concatBytes(...arrays: TArg<Uint8Array[]>): TRet<Uint8Array>;
/**
* Checks if the provided value is object-like for option/schema bags.
* This intentionally matches noble-curves and noble-hashes by using the
* `[object Object]` tag instead of rejecting class/proxy/env objects by prototype;
* stricter checks caused compatibility reports in proxied environments.
* Array, Uint8Array and others are not plain objects.
* @param obj - The value to be checked.
*/
declare function isPlainObject(obj: any): boolean;
/**
* Miscellaneous helpers reused by the coder internals and tests.
* @example
* Reuse a couple of byte helpers without pulling in the full namespace.
* ```ts
* import { utils } from 'micro-packed';
* const left = Uint8Array.of(1);
* const right = Uint8Array.of(2);
* utils.equalBytes(utils.concatBytes(left, right), Uint8Array.of(1, 2));
* ```
*/
export declare const utils: TRet<{
equalBytes: typeof equalBytes;
isBytes: typeof isBytes;
isCoder: typeof isCoder;
checkBounds: typeof checkBounds;
concatBytes: typeof concatBytes;
createView: (arr: TArg<Uint8Array>) => DataView;
isPlainObject: typeof isPlainObject;
}>;
/** Byte-array alias used throughout the public API. */
export type Bytes = Uint8Array;
/** Optional value helper used by conditional coders. */
export type Option<T> = T | undefined;
/** Coder encodes and decodes between two types. */
export interface Coder<F, T> {
/**
* Encodes (converts) a decoded value into its serialized representation.
* @param from - Value to encode.
* @returns Encoded representation.
*/
encode(from: F): T;
/**
* Decodes (converts) a serialized value back into its decoded representation.
* @param to - Encoded representation to decode.
* @returns Decoded value.
*/
decode(to: T): F;
}
/** BytesCoder converts value between a type and a byte array. */
export interface BytesCoder<T> extends Coder<T, Bytes> {
/** Fixed-size hint in bytes, when known. */
size?: number;
/**
* Encodes a value into a byte array.
* @param data - Value to encode.
* @returns Encoded bytes.
*/
encode: (data: T) => Bytes;
/**
* Decodes a byte array into a value.
* @param data - Bytes to decode.
* @param opts - Reader options used while decoding. See {@link ReaderOpts}.
* @returns Decoded value.
*/
decode: (data: Bytes, opts?: ReaderOpts) => T;
}
/** BytesCoderStream converts value between a type and a byte array, using streams. */
export interface BytesCoderStream<T> {
/** Fixed-size hint in bytes, when known. */
size?: number;
/**
* Encodes a value into a Writer stream.
* @param w - Writer stream.
* @param value - Value to encode.
*/
encodeStream: (w: Writer, value: T) => void;
/**
* Decodes a value from a Reader stream.
* @param r - Reader stream.
* @returns Decoded value.
*/
decodeStream: (r: Reader) => T;
}
/** Full coder interface with both stream and byte-array helpers. */
export type CoderType<T> = BytesCoderStream<T> & BytesCoder<T>;
/** CoderType with a known fixed byte size. */
export type Sized<T> = CoderType<T> & {
size: number;
};
/** Extract the decoded value type from a coder. */
export type UnwrapCoder<T> = T extends CoderType<infer U> ? U : T;
/**
* Validation function. Should return value after validation.
* Can be used to narrow types
*/
export type Validate<T> = (elm: T) => T;
/** Length descriptor accepted by variable-size coders. */
export type Length = CoderType<number> | CoderType<bigint> | number | Bytes | string | null;
type ArrLike<T> = Array<T> | ReadonlyArray<T>;
/** Typed arrays supported by the utility helper types. */
export type TypedArray = Uint8Array | Int8Array | Uint8ClampedArray | Uint16Array | Int16Array | Uint32Array | Int32Array;
/** Writable version of a type, where readonly properties are made writable. */
export type Writable<T> = T extends {} ? T extends TypedArray ? T : {
-readonly [P in keyof T]: Writable<T[P]>;
} : T;
/** Union of object value types. */
export type Values<T> = T[keyof T];
/** Key helper that removes fields whose values are exactly `undefined`. */
export type NonUndefinedKey<T, K extends keyof T> = T[K] extends undefined ? never : K;
/** Key helper that keeps only nullable fields. */
export type NullableKey<T, K extends keyof T> = T[K] extends NonNullable<T[K]> ? never : K;
/** Key helper for optional-but-present struct fields. */
export type OptKey<T, K extends keyof T> = NullableKey<T, K> & NonUndefinedKey<T, K>;
/** Key helper for required struct fields. */
export type ReqKey<T, K extends keyof T> = T[K] extends NonNullable<T[K]> ? K : never;
/** Object containing only optional keys from a struct shape. */
export type OptKeys<T> = Pick<T, {
[K in keyof T]: OptKey<T, K>;
}[keyof T]>;
/** Object containing only required keys from a struct shape. */
export type ReqKeys<T> = Pick<T, {
[K in keyof T]: ReqKey<T, K>;
}[keyof T]>;
/** Input object type accepted by `struct()`. */
export type StructInput<T extends Record<string, any>> = {
[P in keyof ReqKeys<T>]: T[P];
} & {
[P in keyof OptKeys<T>]?: T[P];
};
/** Record of field names to coder instances for `struct()`. */
export type StructRecord<T extends Record<string, any>> = {
[P in keyof T]: CoderType<T[P]>;
};
/** Generic decoded object bag used internally by nested coders. */
export type StructOut = Record<string, any>;
/** Padding function that takes an index and returns a padding value. */
export type PadFn = (i: number) => number;
/** Path related utils (internal) */
type Path = {
obj: StructOut;
field?: string | number;
};
type PathStack = Path[];
export type _PathObjFn = () => void;
type PathUtils = {
pushObj: (stack: PathStack, obj: StructOut, objFn: _PathObjFn) => void;
path: (stack: PathStack) => string;
err: (name: string, stack: PathStack, msg: string | Error) => Error;
resolve: (stack: PathStack, path: string) => StructOut | undefined;
};
declare const Path: PathUtils;
/** Options for the Reader class. */
export type ReaderOpts = {
/** Allow decoding to finish with unread trailing bytes. */
allowUnreadBytes?: boolean;
/** Allow the same byte range to be read more than once through pointers. */
allowMultipleReads?: boolean;
};
/** Reader interface passed into stream decoders. */
export type Reader = {
/** Current position in the buffer. */
readonly pos: number;
/** Number of bytes left in the buffer. */
readonly leftBytes: number;
/** Total number of bytes in the buffer. */
readonly totalBytes: number;
/**
* Checks if the end of the buffer has been reached.
* @returns `true` when the reader consumed the whole buffer.
*/
isEnd(): boolean;
/**
* Creates an error with the given message. Adds information about current field path.
* If Error object provided, saves original stack trace.
* @param msg - The error message or an Error object.
* @returns The created Error object.
*/
err(msg: string | Error): Error;
/**
* Reads a specified number of bytes from the buffer.
*
* WARNING: Uint8Array is subarray of original buffer. Do not modify.
* @param n - The number of bytes to read.
* @param peek - If `true`, the bytes are read without advancing the position.
* @returns The read bytes as a Uint8Array.
*/
bytes(n: number, peek?: boolean): Uint8Array;
/**
* Reads a single byte from the buffer.
* @param peek - If `true`, the byte is read without advancing the position.
* @returns The read byte as a number.
*/
byte(peek?: boolean): number;
/**
* Reads a specified number of bits from the buffer.
* @param bits - The number of bits to read.
* @returns The read bits as a number.
*/
bits(bits: number): number;
/**
* Finds the first occurrence of a needle in the buffer.
* @param needle - The needle to search for.
* @param pos - The starting position for the search.
* @returns The position of the first occurrence of the needle, or `undefined` if not found.
*/
find(needle: Bytes, pos?: number): number | undefined;
/**
* Creates a new Reader instance at the specified offset.
* Complex and unsafe API: currently only used in eth ABI parsing of pointers.
* Required to break pointer boundaries inside arrays for complex structure.
* Please use only if absolutely necessary!
* @param n - The offset to create the new Reader at.
* @returns A new Reader instance at the specified offset.
*/
offsetReader(n: number): Reader;
};
/** Writer interface passed into stream encoders. */
export type Writer = {
/**
* Creates an error with the given message. Adds information about current field path.
* If Error object provided, saves original stack trace.
* @param msg - The error message or an Error object.
* @returns The created Error object.
*/
err(msg: string | Error): Error;
/**
* Writes a byte array to the buffer.
* @param b - The byte array to write.
*/
bytes(b: Bytes): void;
/**
* Writes a single byte to the buffer.
* @param b - The byte to write.
*/
byte(b: number): void;
/**
* Writes a specified number of bits to the buffer.
* @param value - The value to write.
* @param bits - The number of bits to write.
*/
bits(value: number, bits: number): void;
};
/**
* Internal structure. Reader class for reading from a byte array.
* `stack` is internal: for debugger and logging
* @class Reader
*/
declare class _Reader implements Reader {
pos: number;
readonly data: Bytes;
readonly opts: ReaderOpts;
readonly stack: PathStack;
private parent;
private parentOffset;
private bitBuf;
private bitPos;
private bs;
private view;
constructor(data: Bytes, opts?: ReaderOpts, stack?: PathStack, parent?: _Reader | undefined, parentOffset?: number);
/** Internal method for pointers. */
_enablePointers(): void;
private markBytesBS;
private markBytes;
pushObj(obj: StructOut, objFn: _PathObjFn): void;
enterField(field: string | number): void;
exitField(): void;
readView(n: number, fn: (view: DataView, pos: number) => number): number;
absBytes(n: number): Uint8Array;
finish(): void;
err(msg: string | Error): Error;
offsetReader(n: number): _Reader;
bytes(n: number, peek?: boolean): Uint8Array;
byte(peek?: boolean): number;
get leftBytes(): number;
get totalBytes(): number;
isEnd(): boolean;
progress(): number;
bits(bits: number): number;
find(needle: Bytes, pos?: number): number | undefined;
}
declare class _Writer implements Writer {
pos: number;
readonly stack: PathStack;
private buffers;
private chunks;
private chunk;
private chunkView;
private chunkPos;
private run;
private nextChunkSize;
ptrs: {
pos: number;
ptr: CoderType<number>;
buffer: Bytes;
}[];
private bitBuf;
private bitPos;
private finished;
constructor(stack?: PathStack);
private carve;
pushObj(obj: StructOut, objFn: _PathObjFn): void;
enterField(field: string | number): void;
exitField(): void;
writeView(len: number, fn: (view: DataView, pos: number) => void): void;
err(msg: string | Error): Error;
bytes(b: Bytes): void;
byte(b: number): void;
finish(clean?: boolean): Bytes;
bits(value: number, bits: number): void;
}
/** Internal function for checking bit bounds of bigint in signed/unsinged form */
declare function checkBounds(value: bigint, bits: bigint, signed: boolean): void;
/**
* Validates a value before encoding and after decoding using a provided function.
* @param inner - The inner CoderType.
* @param fn - The validation function.
* @returns CoderType which check value with validation function.
* @throws On wrong inner coder or validator argument types. {@link TypeError}
* @example
* Reject values outside the accepted range during both encode and decode.
* ```ts
* import * as P from 'micro-packed';
* const val = (n: number) => {
* if (n > 10) throw new Error(`${n} > 10`);
* return n;
* };
*
* // Checks that values are <= 10 during encoding and decoding.
* const RangedInt = P.validate(P.U32LE, val);
* ```
*/
export declare function validate<T>(inner: CoderType<T>, fn: Validate<T>): CoderType<T>;
/**
* Wraps a stream encoder into a generic encoder and optionally validation function
* @param inner - Stream coder with optional validation hook.
* @returns The wrapped CoderType.
* @throws On wrong wrapped stream-coder shapes. {@link TypeError}
* @example
* Start from stream methods, then add validation if needed.
* ```ts
* import * as P from 'micro-packed';
* const U8 = P.wrap({
* encodeStream: (w, value) => w.byte(value),
* decodeStream: (r) => r.byte(),
* });
* const checkedU8 = P.wrap({
* encodeStream: (w, value) => w.byte(value),
* decodeStream: (r) => r.byte(),
* validate: (n: number) => {
* if (n > 10) throw new Error(`${n} > 10`);
* return n;
* }
* });
* ```
*/
export declare const wrap: <T>(inner: {
size?: number;
encodeStream: (w: Writer, value: T) => void;
decodeStream: (r: Reader) => T;
validate?: Validate<T>;
}) => CoderType<T>;
/**
* Checks if the given value is a CoderType.
* @param elm - The value to check.
* @returns True if the value is a CoderType, false otherwise.
* @example
* Guard unknown values before calling encode/decode helpers on them.
* ```ts
* import { isCoder, U8 } from 'micro-packed';
* isCoder(U8);
* ```
*/
export declare function isCoder<T>(elm: any): elm is CoderType<T>;
/**
* Base coder for working with dictionaries (records, objects, key-value map)
* Dictionary is dynamic type like: `[key: string, value: any][]`
* @returns base coder that encodes/decodes between arrays of key-value tuples and dictionaries.
* @example
* Convert between tuple entries and a plain object record.
* ```ts
* import * as P from 'micro-packed';
* const dict: P.CoderType<Record<string, number>> = P.apply(
* P.array(P.U16BE, P.tuple([P.cstring, P.U32LE] as const)),
* P.coders.dict()
* );
* ```
*/
declare function dict<T>(): BaseCoder<[string, T][], Record<string, T>>;
type Enum = {
[k: string]: number | string;
} & {
[k: number]: string;
};
type EnumKeys<T extends Enum> = keyof T;
/**
* Base coder for working with TypeScript enums.
* @param e - TypeScript enum.
* @returns base coder that encodes/decodes between numbers and enum keys.
* @example
* Map enum numbers to their string keys and back.
* ```ts
* import * as P from 'micro-packed';
* enum Color { Red, Green, Blue }
* const colorCoder = P.coders.tsEnum(Color);
* colorCoder.encode(Color.Red); // 'Red'
* colorCoder.decode('Green'); // 1
* ```
*/
declare function tsEnum<T extends Enum>(e: T): BaseCoder<number, EnumKeys<T>>;
/**
* Base coder for working with decimal numbers.
* @param precision - Number of decimal places.
* @param round - Round fraction part if bigger than precision (throws error by default)
* @returns base coder that encodes/decodes between bigints and decimal strings.
* @example
* Convert bigint amounts into fixed-precision decimal strings.
* ```ts
* import * as P from 'micro-packed';
* const decimal8 = P.coders.decimal(8);
* decimal8.encode(630880845n); // '6.30880845'
* decimal8.decode('6.30880845'); // 630880845n
* ```
*/
declare function decimal(precision: number, round?: boolean): Coder<bigint, string>;
type BaseInput<F> = F extends BaseCoder<infer T, any> ? T : never;
type BaseOutput<F> = F extends BaseCoder<any, infer T> ? T : never;
/**
* Combines multiple coders into a single coder, allowing conditional
* encoding/decoding based on input.
* Acts as a parser combinator, splitting complex conditional coders into smaller parts.
*
* `encode = [Ae, Be]; decode = [Ad, Bd]`
* ->
* `match([{encode: Ae, decode: Ad}, {encode: Be; decode: Bd}])`
*
* @param lst - Array of coders to match.
* @returns Combined coder for conditional encoding/decoding.
*/
declare function match<L extends BaseCoder<unknown | undefined, unknown | undefined>[], I = {
[K in keyof L]: NonNullable<BaseInput<L[K]>>;
}[number], O = {
[K in keyof L]: NonNullable<BaseOutput<L[K]>>;
}[number]>(lst: L): BaseCoder<I, O>;
/**
* Collection of reusable base coders and helpers.
* @example
* Build a reusable decimal-string adapter.
* ```ts
* import { coders } from 'micro-packed';
* const decimal2 = coders.decimal(2);
* decimal2.encode(123n); // '1.23'
* ```
*/
export declare const coders: {
dict: typeof dict;
numberBigint: BaseCoder<bigint, number>;
tsEnum: typeof tsEnum;
decimal: typeof decimal;
match: typeof match;
reverse: <F, T>(coder: Coder<F, T>) => Coder<T, F>;
};
/**
* CoderType for parsing individual bits.
* NOTE: Structure should parse whole amount of bytes before it can start parsing byte-level elements.
* @param len - Number of bits to parse.
* @returns CoderType representing the parsed bits.
* @throws On invalid bit-length configuration or bit values. {@link Error}
* @throws On wrong argument types forwarded into wrapped numeric validators. {@link TypeError}
* @example
* Pack several bit fields into a single byte.
* ```ts
* import * as P from 'micro-packed';
* const s = P.struct({ magic: P.bits(1), version: P.bits(1), tag: P.bits(4), len: P.bits(2) });
* ```
*/
export declare const bits: (len: number) => CoderType<number>;
/**
* CoderType for working with bigint values.
* Unsized bigint values should be wrapped in a container (e.g., bytes or string).
*
* `0n = Uint8Array.of()`
*
* `1n = new Uint8Array([1n])`
*
* Please open issue, if you need different behavior for zero.
*
* @param size - Size of the bigint in bytes.
* @param le - Whether to use little-endian byte order.
* @param signed - Whether the bigint is signed.
* @param sized - Whether the bigint should have a fixed size.
* @param minimal - Whether unsized decoding should reject redundant zero/sign-extension bytes.
* Without it, `01` and `00 01` decode to the same value (a malleability vector): set
* `minimal: true` whenever encodings must be canonical (consensus data, signed payloads).
* @returns CoderType representing the bigint value.
* @throws On invalid bigint coder configuration or out-of-bounds bigint values. {@link Error}
* @throws On wrong builder argument or wrapped numeric value types. {@link TypeError}
* @example
* Define a 512-bit unsigned big-endian integer coder.
* ```ts
* import * as P from 'micro-packed';
* // Define a CoderType for a 512-bit unsigned big-endian integer.
* const U512BE = P.bigint(64, false, false, true);
* ```
*/
export declare const bigint: (size: number, le?: boolean, signed?: boolean, sized?: boolean, minimal?: boolean) => CoderType<bigint>;
/** Unsigned 256-bit little-endian integer CoderType. */
export declare const U256LE: CoderType<bigint>;
/** Unsigned 256-bit big-endian integer CoderType. */
export declare const U256BE: CoderType<bigint>;
/** Signed 256-bit little-endian integer CoderType. */
export declare const I256LE: CoderType<bigint>;
/** Signed 256-bit big-endian integer CoderType. */
export declare const I256BE: CoderType<bigint>;
/** Unsigned 128-bit little-endian integer CoderType. */
export declare const U128LE: CoderType<bigint>;
/** Unsigned 128-bit big-endian integer CoderType. */
export declare const U128BE: CoderType<bigint>;
/** Signed 128-bit little-endian integer CoderType. */
export declare const I128LE: CoderType<bigint>;
/** Signed 128-bit big-endian integer CoderType. */
export declare const I128BE: CoderType<bigint>;
/** Unsigned 64-bit little-endian integer CoderType. */
export declare const U64LE: CoderType<bigint>;
/** Unsigned 64-bit big-endian integer CoderType. */
export declare const U64BE: CoderType<bigint>;
/** Signed 64-bit little-endian integer CoderType. */
export declare const I64LE: CoderType<bigint>;
/** Signed 64-bit big-endian integer CoderType. */
export declare const I64BE: CoderType<bigint>;
/**
* CoderType for working with number values (up to 6 bytes/48 bits).
* Unsized int values should be wrapped in a container (e.g., bytes or string).
*
* `0 = Uint8Array.of()`
*
* `1 = new Uint8Array([1n])`
*
* Please open issue, if you need different behavior for zero.
*
* @param size - Size of the number in bytes.
* @param le - Whether to use little-endian byte order.
* @param signed - Whether the number is signed.
* @param sized - Whether the number should have a fixed size.
* @param minimal - Whether unsized decoding should reject redundant zero/sign-extension bytes.
* Without it, `01` and `00 01` decode to the same value (a malleability vector): set
* `minimal: true` whenever encodings must be canonical (consensus data, signed payloads).
* @returns CoderType representing the number value.
* @throws On invalid number-coder configuration or out-of-bounds values. {@link Error}
* @throws On wrong builder argument or wrapped numeric value types. {@link TypeError}
* @example
* Create a coder for JavaScript numbers up to 48 bits wide.
* ```ts
* import * as P from 'micro-packed';
* const int24 = P.int(3, false); // Define a coder for a 24-bit unsigned big-endian integer
* ```
*/
export declare const int: (size: number, le?: boolean, signed?: boolean, sized?: boolean, minimal?: boolean) => CoderType<number>;
/** Unsigned 32-bit little-endian integer CoderType. */
export declare const U32LE: CoderType<number>;
/** Unsigned 32-bit big-endian integer CoderType. */
export declare const U32BE: CoderType<number>;
/** Signed 32-bit little-endian integer CoderType. */
export declare const I32LE: CoderType<number>;
/** Signed 32-bit big-endian integer CoderType. */
export declare const I32BE: CoderType<number>;
/** Unsigned 16-bit little-endian integer CoderType. */
export declare const U16LE: CoderType<number>;
/** Unsigned 16-bit big-endian integer CoderType. */
export declare const U16BE: CoderType<number>;
/** Signed 16-bit little-endian integer CoderType. */
export declare const I16LE: CoderType<number>;
/** Signed 16-bit big-endian integer CoderType. */
export declare const I16BE: CoderType<number>;
/** Unsigned 8-bit integer CoderType. */
export declare const U8: CoderType<number>;
/** Signed 8-bit integer CoderType. */
export declare const I8: CoderType<number>;
/** 32-bit big-endian floating point CoderType ("binary32", IEEE 754-2008). */
export declare const F32BE: CoderType<number>;
/** 32-bit little-endian floating point CoderType ("binary32", IEEE 754-2008). */
export declare const F32LE: CoderType<number>;
/** 64-bit big-endian floating point type ("binary64", IEEE 754-2008). */
export declare const F64BE: CoderType<number>;
/** 64-bit little-endian floating point type ("binary64", IEEE 754-2008). */
export declare const F64LE: CoderType<number>;
/** Boolean CoderType. */
export declare const bool: CoderType<boolean>;
/**
* Bytes CoderType with a specified length and endianness.
* The bytes can have:
* - Dynamic size (prefixed with a length CoderType like U16BE)
* - Fixed size (specified by a number)
* - Unknown size (null, will parse until end of buffer)
* - Zero-terminated (terminator can be any Uint8Array)
* @param len - Length mode: CoderType for dynamic size, number for fixed size,
* Uint8Array for terminator mode, or null to parse until end of buffer.
* @param le - Whether to use little-endian byte order.
* @returns CoderType representing the bytes.
* @throws If the byte layout or terminator handling is invalid. {@link Error}
* @throws On wrong byte-coder argument or value types. {@link TypeError}
* @example
* Use fixed-size, length-prefixed, or trailing byte arrays.
* ```ts
* import * as P from 'micro-packed';
* const dynamicBytes = P.bytes(P.U16BE, false);
* const fixedBytes = P.bytes(32, false); // Fixed size bytes
* const unknownBytes = P.bytes(null, false); // Unknown size bytes, will parse until end of buffer
* const zeroTerminatedBytes = P.bytes(Uint8Array.of(0), false); // Zero-terminated bytes
* ```
*/
declare const createBytes: (len: Length, le?: boolean) => CoderType<Bytes>;
export { createBytes as bytes, createHex as hex };
/**
* Prefix-encoded value using a length prefix and an inner CoderType.
* The prefix can have:
* - Dynamic size (prefixed with a length CoderType like U16BE)
* - Fixed size (specified by a number)
* - Unknown size (null, will parse until end of buffer)
* - Zero-terminated (terminator can be any Uint8Array)
* @param len - Length mode: CoderType for dynamic size, number for fixed size,
* Uint8Array for terminator mode, or null to parse until end of buffer.
* @param inner - CoderType for the actual value to be prefix-encoded.
* @returns CoderType representing the prefix-encoded value.
* @throws If the prefix configuration or wrapped coding step is invalid. {@link Error}
* @throws On wrong prefix-coder argument types. {@link TypeError}
* @example
* Prefix a payload with either a dynamic or fixed byte count.
* ```ts
* import * as P from 'micro-packed';
* // Dynamic size prefix: prefixed with P.U16BE byte length.
* const dynamicPrefix = P.prefix(P.U16BE, P.bytes(null));
* // Fixed size prefix: always 10 bytes.
* const fixedPrefix = P.prefix(10, P.bytes(null));
* ```
*/
export declare function prefix<T>(len: Length, inner: CoderType<T>): CoderType<T>;
/**
* String CoderType with a specified length and endianness.
* The string can be:
* - Dynamic size (prefixed with a length CoderType like U16BE)
* - Fixed size (specified by a number)
* - Unknown size (null, will parse until end of buffer)
* - Zero-terminated (terminator can be any Uint8Array)
* @param len - Length mode: CoderType for dynamic size, number for fixed size,
* Uint8Array for terminator mode, or null to parse until end of buffer.
* @param le - Whether to use little-endian byte order.
* Note: UTF-8 has no endian variant; `le` reverses the encoded byte sequence
* via the underlying byte coder.
* @returns CoderType representing the string.
* @throws If the underlying byte layout is invalid. {@link Error}
* @throws On wrong string-coder argument or value types. {@link TypeError}
* @example
* Use fixed-size, length-prefixed, or trailing UTF-8 strings.
* ```ts
* import * as P from 'micro-packed';
* // Dynamic string prefixed with P.U16BE string length.
* const dynamicString = P.string(P.U16BE, false);
* const fixedString = P.string(10, false);
* // Unknown size string, parsed until end of buffer.
* const unknownString = P.string(null, false);
* const nullTerminatedString = P.cstring; // NUL-terminated string
* const _cstring = P.string(Uint8Array.of(0)); // Same thing
* ```
*/
export declare const string: (len: Length, le?: boolean) => CoderType<string>;
/** NUL-terminated string CoderType. */
export declare const cstring: CoderType<string>;
type HexOpts = {
isLE?: boolean;
with0x?: boolean;
};
/**
* Hexadecimal string CoderType with a specified length, endianness, and optional 0x prefix.
* @param len - Length mode: CoderType for dynamic size, number for fixed size,
* Uint8Array for terminator mode, or null to parse until end of buffer.
* @param options - Hex-specific endianness and prefix options. See {@link HexOpts}.
* Use `isLE` to decode bytes as little-endian before converting to hex, and
* `with0x` to add and require a `0x` prefix.
* @returns CoderType representing the hexadecimal string.
* @throws If the underlying byte layout or `0x` prefix handling is invalid. {@link Error}
* @throws On wrong hex-coder argument or value types. {@link TypeError}
* @example
* Encode bytes as hex, optionally little-endian and with a `0x` prefix.
* ```ts
* import * as P from 'micro-packed';
* // Hex string with 0x prefix and U16BE length.
* const dynamicHex = P.hex(P.U16BE, {isLE: false, with0x: true});
* // Fixed-length 32-byte hex string without 0x prefix.
* const fixedHex = P.hex(32, {isLE: false, with0x: false});
* ```
*/
declare const createHex: (len: Length, options?: HexOpts) => CoderType<string>;
/**
* Applies a base coder to a CoderType.
* @param inner - The inner CoderType.
* @param base - The base coder to apply.
* @returns CoderType representing the transformed value.
* @throws On wrong inner-coder or base-coder argument types. {@link TypeError}
* @example
* Reuse a base coder on top of a binary bytes coder.
* ```ts
* import * as P from 'micro-packed';
* import { hex as baseHex } from '@scure/base';
* const hexCoder = P.apply(P.bytes(32), baseHex); // will decode bytes into a hex string
* ```
*/
export declare function apply<T, F>(inner: CoderType<T>, base: BaseCoder<T, F>): CoderType<F>;
/**
* Lazy CoderType that is evaluated at runtime.
* @param fn - A function that returns the CoderType.
* @returns CoderType representing the lazy value.
* @throws On wrong lazy-factory argument types. {@link TypeError}
* @example
* Define a recursive tree without referencing the coder before it exists.
* ```ts
* import * as P from 'micro-packed';
* type Tree = { name: string; children: Tree[] };
* const tree = P.struct({
* name: P.cstring,
* children: P.array(
* P.U16BE,
* P.lazy((): P.CoderType<Tree> => tree)
* ),
* });
* ```
*/
export declare function lazy<T>(fn: () => CoderType<T>): CoderType<T>;
/**
* Flag CoderType that encodes/decodes a boolean value based on the presence of a marker.
* @param flagValue - Marker value.
* @param xor - Whether to invert the flag behavior.
* @returns CoderType representing the flag value.
* @throws On wrong flag argument or value types. {@link TypeError}
* @throws If the marker is empty. {@link Error}
*
* WARNING: a flag is decoded by peeking at the next bytes, so the field that follows it
* must never be able to start with the marker bytes. Otherwise encode/decode is
* ambiguous: `{ flag: absent, data: <starts with marker> }` re-decodes as
* `{ flag: present, data: <rest> }`. There is no local guard against this (the flag
* cannot see the following coder); choose a marker that cannot collide, or place the
* flag where collision is impossible (e.g. before fixed-alphabet data).
* @example
* Toggle a boolean based on whether a marker is present.
* ```ts
* import * as P from 'micro-packed';
* // Encodes true as u8a([0x01, 0x02]), false as u8a([]).
* const flag = P.flag(new Uint8Array([0x01, 0x02]));
* // Encodes true as u8a([]), false as u8a([0x01, 0x02]).
* const flagXor = P.flag(new Uint8Array([0x01, 0x02]), true);
* const s = P.struct({ f: P.flag(new Uint8Array([0x0, 0x1])), f2: P.flagged('f', P.U32BE) });
* ```
*/
export declare const flag: (flagValue: TArg<Bytes>, xor?: boolean) => CoderType<boolean | undefined>;
/**
* Conditional CoderType that encodes/decodes a value only if a flag is present.
* @param path - Path to the flag value or a CoderType for the flag.
* @param inner - Inner CoderType for the value.
* @param def - Optional default value to use if the flag is not present.
* @returns CoderType representing the conditional value.
* @throws On wrong flag-path or inner-coder argument types. {@link TypeError}
* @example
* Decode a field only when a sibling flag is present.
* ```ts
* import * as P from 'micro-packed';
* const s = P.struct({
* f: P.flag(new Uint8Array([0x0, 0x1])),
* f2: P.flagged('f', P.U32BE)
* });
* ```
*
* @example
* Supply a default when the sibling flag is missing.
* ```ts
* import * as P from 'micro-packed';
* const s2 = P.struct({
* f: P.flag(new Uint8Array([0x0, 0x1])),
* f2: P.flagged('f', P.U32BE, 123)
* });
* ```
*/
export declare function flagged<T>(path: string | CoderType<boolean>, inner: CoderType<T>, def?: T): CoderType<Option<T>>;
/**
* Optional CoderType that encodes/decodes a value based on a flag.
* @param flag - CoderType for the flag value.
* @param inner - Inner CoderType for the value.
* @param def - Optional default value to use if the flag is not present.
* @returns CoderType representing the optional value.
* @throws On wrong flag-coder or inner-coder argument types. {@link TypeError}
* @example
* Decode a value only when a marker flag is present.
* ```ts
* import * as P from 'micro-packed';
* const optional = P.optional(P.flag(new Uint8Array([0x0, 0x1])), P.U32BE);
* ```
*
* @example
* Provide a fallback value when the marker flag is absent.
* ```ts
* import * as P from 'micro-packed';
* const optionalWithDefault = P.optional(P.flag(new Uint8Array([0x0, 0x1])), P.U32BE, 123);
* ```
*/
export declare function optional<T>(flag: CoderType<boolean>, inner: CoderType<T>, def?: T): CoderType<Option<T>>;
/**
* Magic value CoderType that encodes/decodes a constant value.
* This can be used to check for a specific magic value or byte sequence
* at the beginning of a data structure.
* @param inner - Inner CoderType for the value.
* @param constant - Constant value.
* @param check - Whether to check the decoded value against the constant.
* @returns CoderType representing the magic value.
* @throws On wrong magic-coder argument types. {@link TypeError}
* @example
* Require a specific encoded value at this position in the stream.
* ```ts
* import * as P from 'micro-packed';
* const magicU8 = P.magic(P.U8, 0x42);
* ```
*/
export declare function magic<T>(inner: CoderType<T>, constant: T, check?: boolean): CoderType<undefined>;
/**
* Magic bytes CoderType that encodes/decodes a constant byte array or string.
* @param constant - Constant byte array or string.
* @returns CoderType representing the magic bytes.
* @throws If the constant check fails or the wrapped coder rejects the bytes. {@link Error}
* @throws On wrong magic-bytes argument types. {@link TypeError}
* Note: Uint8Array constants are copied during construction.
* @example
* Match a fixed byte or string marker without producing a value.
* ```ts
* import * as P from 'micro-packed';
* const magicBytes = P.magicBytes('MAGIC');
* ```
*/
export declare const magicBytes: (constant: TArg<Bytes | string>) => CoderType<undefined>;
/**
* Creates a CoderType for a constant value. The function enforces this value during encoding,
* ensuring it matches the provided constant. During decoding, it always returns the constant value.
* The actual value is not written to or read from any byte stream; it's used only for validation.
*
* @param c - Constant value.
* @returns CoderType representing the constant value.
* @throws On wrong constant values passed during encoding. {@link TypeError}
* Note: object constants are compared and returned by reference.
* @example
* Hide an always-constant field behind a regular coder.
* ```ts
* import * as P from 'micro-packed';
* const constantU8 = P.constant(123);
* ```
*/
export declare function constant<T>(c: T): CoderType<T>;
/**
* Structure of composable primitives (C/Rust struct)
* @param fields - Object mapping field names to CoderTypes.
* @returns CoderType representing the structure.
* @throws If the structure definition or encoded struct value is invalid. {@link Error}
* @throws On wrong structure argument types. {@link TypeError}
* Note: the fields object is retained by reference; mutating it after
* construction can change encoding
* while leaving fixed-size metadata unchanged.
* @example
* Combine named fields into a single structured coder.
* ```ts
* import * as P from 'micro-packed';
* const myStruct = P.struct({
* id: P.U32BE,
* name: P.string(P.U8),
* nested: P.struct({
* flag: P.bool,
* value: P.I16LE
* })
* });
* ```
*/
export declare function struct<T extends Record<string, any>>(fields: StructRecord<T>): CoderType<StructInput<T>>;
/**
* Tuple (unnamed structure) of CoderTypes. Same as struct but with unnamed fields.
* @param fields - Array of CoderTypes.
* @returns CoderType representing the tuple.
* @throws If the tuple definition or encoded tuple value is invalid. {@link Error}
* @throws On wrong tuple argument types. {@link TypeError}
* Note: unbounded coders such as `array(null, ...)` should be last or length-prefixed; otherwise
* they can consume bytes intended for later fields.
* Note: the fields array is retained by reference; mutating it after construction can change encoding
* while leaving fixed-size metadata unchanged.
* @example
* Combine several coders into an ordered fixed-length tuple.
* ```ts
* import * as P from 'micro-packed';
* const myTuple = P.tuple([P.U8, P.U16LE, P.string(P.U8)]);
* ```
*/
export declare function tuple<T extends ArrLike<CoderType<any>>, O = Writable<{
[K in keyof T]: UnwrapCoder<T[K]>;
}>>(fields: T): CoderType<O>;
/**
* Array of items (inner type) with a specified length.
* @param len - Length mode: CoderType for dynamic size, number for fixed size,
* Uint8Array for terminator mode, or null to parse until end of buffer.
* @param inner - CoderType for encoding/decoding each array item.
* @returns CoderType representing the array.
* @throws If the array definition or encoded array elements are invalid. {@link Error}
* @throws On wrong array-coder argument types. {@link TypeError}
* Note: Uint8Array terminators are copied during construction.
* @example
* Build dynamic, fixed-size, and trailing arrays from one item coder.
* ```ts
* import * as P from 'micro-packed';
* const child = P.U8;
* // Dynamic array prefixed with P.U16BE array length.
* const a1 = P.array(P.U16BE, child);
* const a2 = P.array(4, child); // Fixed size array
* // Unknown size array, parsed until end of buffer.
* const a3 = P.array(null, child);
* // Zero-terminated array; terminator can be any buffer.
* const a4 = P.array(Uint8Array.of(0), child);
* ```
*/
export declare function array<T>(len: Length, inner: CoderType<T>): CoderType<T[]>;
/**
* Mapping between encoded values and string representations.
* @param inner - CoderType for encoded values.
* @param variants - Object mapping string representations to encoded values.
* @returns CoderType representing the mapping.
* @throws If mapping variants are invalid or raw variant values are duplicate. {@link Error}
* @throws On wrong mapping argument types. {@link TypeError}
* Note: variants are copied into lookup maps at construction; mutating the
* original object later does not update the coder.
* Note: construction does not run the inner coder. Path-dependent coders need
* encode/decode stack context that does not exist at construction, so selected
* variant values are validated by the inner coder only when encoded or decoded.
* @example
* Map encoded numbers to a small set of string labels.
* ```ts
* import * as P from 'micro-packed';
* const numberMap = P.map(P.U8, {
* 'one': 1,
* 'two': 2,
* 'three': 3
* });
*
* const byteMap = P.map(P.hex(2), {
* 'ab': '6162',
* 'cd': '6364'
* });
* ```
*/
export declare function map<T>(inner: CoderType<T>, variants: Record<string, T>): CoderType<string>;
/**
* Tagged union of CoderTypes, where the tag value determines which CoderType to use.
* The decoded value will have the structure `\{ TAG: number, data: ... \}`.
* @param tag - CoderType for the tag value.
* @param variants - Object mapping tag values to CoderTypes.
* @returns CoderType representing the tagged union.
* @throws On wrong tag-coder or variant-map argument types. {@link TypeError}
* Note: variants are copied into a lookup map at construction; mutating the
* original object later does not update the coder.
* Note: construction does not run the tag coder. Path-dependent tag coders need
* encode/decode stack context that does not exist at construction, so callers
* are responsible for providing tag keys that the tag coder can encode/decode.
* @example
* Switch between payload coders based on a leading tag byte.
* ```ts
* import * as P from 'micro-packed';
* const taggedUnion = P.tag(P.U8, {
* 0x01: P.array(P.U16LE, P.U8),
* 0x02: P.string(P.U8),
* 0x03: P.U32BE
* });
*
* const encoded = taggedUnion.encode({ TAG: 0x01, data: [1, 2] });
* const decoded = taggedUnion.decode(encoded);
* ```
*/
export declare function tag<T extends Values<{
[P in keyof Variants]: {
TAG: P;
data: UnwrapCoder<Variants[P]>;
};
}>, TagValue extends string | number, Variants extends Record<TagValue, CoderType<any>>>(tag: CoderType<TagValue>, variants: Variants): CoderType<T>;
/**
* Mapping between encoded values, string representations, and CoderTypes using a tag CoderType.
* @param tagCoder - CoderType for the tag value.
* @param variants - Object mapping string representations to [tag value, CoderType] pairs.
* @returns CoderType representing the mapping.
* @throws If the mapped-tag table is invalid, raw tag values are duplicate,
* or the selected variant is invalid. {@link Error}
* @throws On wrong tag-coder or variant-map argument types. {@link TypeError}
* Note: construction does not run the tag coder. Path-dependent tag coders need
* encode/decode stack context that does not exist at construction, so callers
* are responsible for providing tag values that the tag coder can encode/decode.
* Note: variant pairs are copied at construction; mutating the original variants
* object or pair arrays later does not update the coder.
* @example
* Use string tags in TypeScript while encoding them as compact numeric tags.
* ```ts
* import * as P from 'micro-packed';
* type Value =
* | { TAG: 'uint'; data: number }
* | { TAG: 'array'; data: Value[] };
* const value: P.CoderType<Value> = P.mappedTag(P.U8, {
* uint: [0, P.U8],
* array: [1, P.array(P.U8, P.lazy(() => value))],
* });
* value.encode({ TAG: 'array', data: [{ TAG: 'uint', data: 5 }] });
* ```
*/
export declare function mappedTag<T extends Values<{
[P in keyof Variants]: {
TAG: P;
data: UnwrapCoder<Variants[P][1]>;
};
}>, TagValue extends string | number, Variants extends Record<string, [TagValue, CoderType<any>]>>(tagCoder: CoderType<TagValue>, variants: Variants): CoderType<T>;
/**
* Bitset of boolean values with optional padding.
* @param names - An array of string names for the bitset values.
* @param pad - Whether to pad the bitset to a multiple of 8 bits.
* @param strict - Whether to reject duplicate names and non-zero padding bits.
* @returns CoderType representing the bitset.
* @typeParam Names - Bit names preserved in the returned record.
* @throws If the bitset definition or encoded bitset values are invalid. {@link Error}
* @throws On wrong bitset argument types. {@link TypeError}
* Note: bits follow `names` order and are written most-significant-bit first
* within each byte; non-byte-aligned `pad=false` bitsets must be composed with
* more bit-level coders.
* Note: strict mode is the default: it rejects duplicate names and non-zero padding
* bits, since ignored padding bits are a malleability vector (hidden data survives
* decode and vanishes on re-encode). Pass `strict: false` only for legacy data that
* requires repeated names or junk padding.
* Note: the names array is retained by reference. Do not mutate it after construction:
* doing so invalidates size metadata and breaks encoding and decoding.
* @example
* Pack several named booleans into a compact bitset.
* ```ts
* import * as P from 'micro-packed';
* const myBitset = P.bitset(['flag1', 'flag2', 'flag3', 'flag4'], true);
* ```
*/
export declare function bitset<Names extends readonly string[]>(names: Names, pad?: boolean, strict?: boolean): CoderType<Record<Names[number], boolean>>;
/**
* Padding function which always returns zero.
* @param i - Zero-based padding byte index.
* @returns Always returns `0`.
* @example
* Use the default zero padding helper with padLeft/padRight.
* ```ts
* import { U16BE, ZeroPad, padLeft } from 'micro-packed';
* padLeft(4, U16BE, ZeroPad);
* ```
*/
export declare const ZeroPad: PadFn;
declare function padLength(blockSize: number, len: number): number;
/**
* Pads a CoderType with a specified block size and padding function on the left side.
* @param blockSize - Block size for padding (positive safe integer).
* @param inner - Inner CoderType to pad.
* @param padFn - Padding function to use. If not provided, zero padding is used.
* @returns CoderType representing the padded value.
* Decode validates that padding bytes match `padFn`, preventing alternate encodings with hidden
* padding data.
* @throws If the padding configuration or wrapped coder is invalid. {@link Error}
* @throws On wrong padding argument types. {@link TypeError}
* @example
* Left-pad a value to the next block boundary.
* ```ts
* import * as P from 'micro-packed';
* const paddedU32BE = P.padLeft(4, P.U32BE);
*
* const paddedBytes = P.padLeft(16, P.bytes(8), (i) => i + 1);
* ```
*/
export declare function padLeft<T>(blockSize: number, inner: CoderType<T>, padFn: Option<PadFn>): CoderType<T>;
/**
* Pads a CoderType with a specified block size and padding function on the right side.
* @param blockSize - Block size for padding (positive safe integer).
* @param inner - Inner CoderType to pad.
* @param padFn - Padding function to use. If not provided, zero padding is used.
* @returns CoderType representing the padded value.
* Decode validates that padding bytes match `padFn`, preventing alternate encodings with hidden
* padding data.
* @throws If the padding configuration or wrapped coder is invalid. {@link Error}
* @throws On wrong padding argument types. {@link TypeError}
* @example
* Right-pad a value to the next block boundary.
* ```ts
* import * as P from 'micro-packed';
* const paddedU16BE = P.padRight(2, P.U16BE);
*
* const paddedBytes = P.padRight(8, P.bytes(null), (i) => i + 1);
* ```
*/
export declare function padRight<T>(blockSize: number, inner: CoderType<T>, padFn: Option<PadFn>): CoderType<T>;
/**
* Pointer to a value using a pointer CoderType and an inner CoderType.
* Pointers are scoped, and the next pointer in the dereference chain is offset by the previous one.
* By default (if no 'allowMultipleReads' in ReaderOpts is set) is safe, since
* same region of memory cannot be read multiple times.
* @param ptr - CoderType for the pointer value.
* @param inner - CoderType for encoding/decoding the pointed value.
* @param sized - Whether the in-place pointer slot should report a fixed size.
* @returns CoderType representing the pointer to the value.
* @throws If the pointer configuration or pointed value decoding is invalid. {@link Error}
* @throws On wrong pointer-coder argument types. {@link TypeError}
* @example
* Jump to a pointed value and decode it with another coder.
* ```ts
* import * as P from 'micro-packed';
* const pointerToU8 = P.pointer(P.U16BE, P.U8); // Pointer to a single U8 value
* ```
*/
export declare function pointer<T>(ptr: CoderType<number>, inner: CoderType<T>, sized?: boolean): CoderType<T>;
export declare const _TEST: {
_bitset: {
BITS: number;
FULL_MASK: number;
len: (len: number) => number;
create: (len: number) => Uint32Array;
clean: (bs: Uint32Array) => Uint32Array;
debug: (bs: Uint32Array) => string[];
checkLen: (bs: Uint32Array, len: number) => void;
chunkLen: (bsLen: number, pos: number, len: number) => void;
set: (bs: Uint32Array, chunk: number, value: number, allowRewrite?: boolean) => boolean;
pos: (pos: number, i: number) => {
chunk: number;
mask: number;
};
indices: (bs: Uint32Array, len: number, invert?: boolean)