Zero-dependency TypeScript library for deterministic binary message parsing, validation, and layout composition.
binlayout helps you describe binary protocols in a declarative way and turn them into strongly typed, predictable parsers without pulling in runtime dependencies.
- deterministic parsing for binary protocols
- strict TypeScript inference for schema outputs
- near-zero-allocation hot path via
parseInto()(exactly zero for numeric-only schemas; see below forbytes/asciifields) - support for validation, dynamic layouts, checksums, and framing helpers
- transport-independent core design that keeps framing concerns separate
npm install @matteophre/binlayoutimport { compileSchema, defineSchema } from '@matteophre/binlayout';
const schema = defineSchema({
name: 'ModbusRTUMessage',
endianness: 'LE',
fields: [
{ name: 'slaveId', type: 'uint8' },
{ name: 'functionCode', type: 'uint8' },
{ name: 'startAddr', type: 'uint16' },
{ name: 'quantity', type: 'uint16' },
{ name: 'crc', type: 'uint16' },
],
});
const compiled = compileSchema(schema);
const buffer = new Uint8Array([0x01, 0x03, 0x00, 0x00, 0x00, 0x0a, 0x44, 0x39]);
const message = compiled.parse(buffer);
console.log(message);The parsed object is inferred from the schema, so you get native TypeScript types without extra annotations.
For high-frequency polling, reuse a target object across calls instead of allocating a new result every time:
const target: typeof message = {} as typeof message;
for (let i = 0; i < 1_000_000; i++) {
compiled.parseInto(buffer, target);
// use target.slaveId, target.crc, etc.
}parseInto() reuses the target object: it performs zero additional allocations for schemas with only numeric fields. bytes fields also avoid allocating a new Uint8Array view when the target already holds one of the correct length from a previous call (the bytes are copied in place); ascii fields still allocate a string on every call, since JS strings are immutable. See src/benchmark.ts (npm run benchmark) for the methodology and measured numbers.
import { createValidationStrategy } from '@matteophre/binlayout';
import { crc16xmodem } from 'crc';
const strategy = createValidationStrategy<number>({
name: 'crc16-xmodem',
compute(data) {
return crc16xmodem(Buffer.from(data)) & 0xffff;
},
});
const payload = buffer.subarray(0, buffer.length - 2);
const expected = (buffer[6]! << 8) | buffer[7]!;
console.log(strategy.verify(payload, expected));- numeric fields:
uint8,uint16,uint32,int8,int16,int32,float32,float64 - binary payloads:
bytes - strings:
ascii - variable-length
bytes/asciifields vialengthFrom, which can point to any earlier numeric field in the schema, not just the immediately preceding one - dynamic layouts and conditional layouts
- checksum wrappers and validation strategies
- framing helpers for transport-specific envelopes
The current focus is stability and polish for the 1.x line. The public contract is intended to remain predictable, and experimental helpers are clearly separated from the core layout semantics.
MIT