Serialization and Deserialization
Each schema has the following methods:
/** * Writes the value (according to the schema's structure) at the cursor. */write(cursor: ByteCursor, value: TIn): void;
/** * Reads a value (according to the schema's structure) at the cursor. */read(cursor: ByteCursor): TOut;
/** * Returns the number of bytes the value takes up when encoded. Given `MaxValue`, returns the * maximum number of bytes any value can take up, which is `NaN` if the schema is unbounded. */measure(value: TIn | MaxValue): number;TIn is the type of values the schema accepts when writing, and TOut the type of values it produces when reading.
They can be extracted from any schema with bin.ExtractIn<typeof Schema> and bin.ExtractOut<typeof Schema>.
They usually only differ in that TIn is readonly, so that readonly data can be written too.
Schemas read from and write to a bin.ByteCursor: a DataView over the buffer, the byte offset at which to read or write
next, and the endianness to use. bin.cursor() creates one:
import * as bin from 'typed-binary';
// Creating a fixed-length buffer of arbitrary size (64 bytes).const buffer = new ArrayBuffer(64); // Or Buffer.alloc(64); in Node.js
// Writing four bytes into the bufferconst writer = bin.cursor(buffer);bin.u8.write(writer, 'W'.charCodeAt(0));bin.u8.write(writer, 'o'.charCodeAt(0));bin.u8.write(writer, 'w'.charCodeAt(0));bin.u8.write(writer, 0);
const reader = bin.cursor(buffer);console.log(bin.string.read(reader)); // > WowEach read or write advances the cursor’s offset, which is why separate cursors are used above for writing and reading.
The offset can also be set directly, to seek (reader.offset = 0) or to skip bytes (reader.offset += 4).
bin.cursor() accepts an ArrayBuffer, or a view of one (like a typed array or a Node.js Buffer), in which case
offsets count from the start of that view. It also takes options:
const cursor = bin.cursor(buffer, { byteOffset: 4, // The offset to start at. Defaults to 0. endianness: 'big', // 'big', 'little' or 'system'. Defaults to 'system'.});Since a cursor is a plain object, it can also be created by hand:
const cursor: bin.ByteCursor = { view: new DataView(buffer), offset: 0, littleEndian: true };Writing to a stream
Section titled “Writing to a stream”A DataView can only view an ArrayBuffer (or a SharedArrayBuffer), so data meant for a stream is first written
into a buffer. A resizable ArrayBuffer can grow as needed, and a DataView created without a length follows its size:
const buffer = new ArrayBuffer(1024, { maxByteLength: 64 * 1024 });const cursor = bin.cursor(buffer);
function send(message: bin.ExtractIn<typeof Message>) { const size = Message.measure(message); if (buffer.byteLength < cursor.offset + size) { buffer.resize(cursor.offset + size); } Message.write(cursor, message);}
function flush() { stream.write(new Uint8Array(buffer.slice(0, cursor.offset))); cursor.offset = 0;}Creating a buffer with the most optimal size
Section titled “Creating a buffer with the most optimal size”Schemas can measure how many bytes a particular value will take up, which can be used to create a buffer that will fit that value perfectly.
import * as bin from 'typed-binary';
export const PlayerUpdatePacket = bin.struct({ id: bin.u32, x: bin.f32, y: bin.f32,});
const packet = { id: 0, x: 2.4, y: 1.6,} as const;
const packetSize = PlayerUpdatePacket.measure(packet);const buffer = new ArrayBuffer(packetSize);Creating a shared buffer that fits all possible values
Section titled “Creating a shared buffer that fits all possible values”If a schema’s size is bounded (there is a max size that no value encodings will surpass), we can create a shared buffer
of the maximum size the schema can take. Unbounded schemas (e.g. ones containing a bin.string or a bin.dynamicArray,
or recursive ones) have a max size of NaN.
import * as bin from 'typed-binary';
export const PlayerUpdatePacket = bin.struct({ id: bin.u32, x: bin.f32, y: bin.f32,});
const maxPacketSize = PlayerUpdatePacket.measure(bin.MaxValue); // or PlayerUpdatePacket.maxSizeconst sharedBuffer = new ArrayBuffer(maxPacketSize);Validation
Section titled “Validation”Mistakes in how schemas are defined (e.g. passing something that isn’t a schema to bin.struct) throw an
InvalidSchemaShapeError. Values that don’t match their schema when writing or measuring (e.g. an array of the wrong
length, or a value that isn’t part of an enum), and malformed data when reading (e.g. an unknown union variant or enum
index), throw a ValidationError.
These checks run in production too, but their detailed messages are left out of production builds (where bundlers replace
process.env.NODE_ENV with 'production'), so that they don’t add to the size of your app. The errors then have a
generic message instead.