JavaScript implementation of the BSER Binary Serialization protocol for efficient local inter-process communication
npx @tessl/cli install tessl/npm-bser@2.1.0BSER (Binary Serialization) is a JavaScript library that provides binary serialization as an alternative to JSON. It uses a framed encoding that makes it simpler to stream sequences of encoded values. Designed for local inter-process communication, strings are represented as binary data matching operating system filename conventions.
npm install bserconst bser = require('bser');For ES modules:
import * as bser from 'bser';Individual imports:
const { loadFromBuffer, dumpToBuffer, BunserBuf, Accumulator } = require('bser');const bser = require('bser');
// Synchronous encoding and decoding
const data = { message: "hello", numbers: [1, 2, 3], active: true };
const encoded = bser.dumpToBuffer(data);
const decoded = bser.loadFromBuffer(encoded);
console.log(decoded); // { message: "hello", numbers: [1, 2, 3], active: true }
// Asynchronous streaming decoding
const bunser = new bser.BunserBuf();
bunser.on('value', (obj) => {
console.log('Received:', obj);
});
// In your socket data handler
socket.on('data', (buf) => {
bunser.append(buf);
});BSER is built around several key components:
loadFromBuffer, dumpToBuffer) for simple operationsBunserBuf) for handling partial data and streaming scenariosAccumulator) for managing expandable data storageConverts JavaScript values to BSER binary format in a single operation.
/**
* Synchronously encodes a value as BSER
* @param {any} val - JavaScript value to encode
* @returns {Buffer} BSER-encoded data
* @throws {Error} If value cannot be serialized
*/
function dumpToBuffer(val);Usage Examples:
const bser = require('bser');
// Encode various data types
const encoded1 = bser.dumpToBuffer(42);
const encoded2 = bser.dumpToBuffer("hello world");
const encoded3 = bser.dumpToBuffer([1, 2, 3]);
const encoded4 = bser.dumpToBuffer({ name: "Alice", age: 30 });
// Encode with 64-bit integers
const Int64 = require('node-int64');
const bigNum = new Int64('0x0123456789abcdef');
const encodedBig = bser.dumpToBuffer(bigNum);Decodes BSER binary data back to JavaScript values in a single operation.
/**
* Synchronously decodes BSER data from a buffer
* @param {Buffer} input - Buffer containing BSER-encoded data
* @returns {any} Decoded JavaScript value
* @throws {Error} If input is invalid or has excess data
*/
function loadFromBuffer(input);Usage Examples:
const bser = require('bser');
// Decode BSER data
const encoded = bser.dumpToBuffer({ message: "test", count: 5 });
const decoded = bser.loadFromBuffer(encoded);
console.log(decoded); // { message: "test", count: 5 }
// Error handling
try {
const invalid = Buffer.from([0x01, 0x02, 0x03]); // Invalid BSER data
const result = bser.loadFromBuffer(invalid);
} catch (error) {
console.error('Decoding failed:', error.message);
}Event-driven decoder for processing BSER data incrementally, ideal for network streams and large data sets.
/**
* Asynchronous BSER decoder for streaming/incremental processing
* @extends EventEmitter
*/
class BunserBuf extends EventEmitter {
constructor();
/**
* Append data to the buffer for processing
* @param {Buffer} buf - Data to append
* @param {boolean} [synchronous] - If true, processes synchronously and returns result
* @returns {any} Decoded value if synchronous, undefined otherwise
*/
append(buf, synchronous);
}Events:
value: Emitted when a complete value is decodederror: Emitted when an error occurs during processingUsage Examples:
const bser = require('bser');
const net = require('net');
// Stream processing
const bunser = new bser.BunserBuf();
bunser.on('value', (obj) => {
console.log('Received complete object:', obj);
});
bunser.on('error', (err) => {
console.error('BSER decoding error:', err);
});
// Process data from a socket
const socket = net.connect('/some/socket/path');
socket.on('data', (chunk) => {
bunser.append(chunk);
});
// Synchronous processing
const encoded = bser.dumpToBuffer({ test: true });
const result = bunser.append(encoded, true);
console.log('Synchronous result:', result);Expandable buffer utility for efficient data accumulation with automatic resizing and read/write position tracking.
/**
* Expandable buffer for efficient data accumulation
*/
class Accumulator {
/**
* Create a new accumulator
* @param {number} [initsize=8192] - Initial buffer size
*/
constructor(initsize);
/**
* Returns available write space
* @returns {number} Available bytes for writing
*/
writeAvail();
/**
* Returns available read data
* @returns {number} Available bytes for reading
*/
readAvail();
/**
* Ensure buffer has enough space for size bytes
* @param {number} size - Required space in bytes
*/
reserve(size);
/**
* Append buffer or string data
* @param {Buffer|string} buf - Data to append
*/
append(buf);
/**
* Read string without advancing read position
* @param {number} size - Number of bytes to read
* @returns {string} UTF-8 decoded string
* @throws {Error} If not enough data available
*/
peekString(size);
/**
* Read string and advance read position
* @param {number} size - Number of bytes to read
* @returns {string} UTF-8 decoded string
* @throws {Error} If not enough data available
*/
readString(size);
/**
* Read integer without advancing read position
* @param {number} size - Size in bytes (1, 2, 4, or 8)
* @returns {number|Int64} Integer value
* @throws {Error} If invalid size or not enough data
*/
peekInt(size);
/**
* Read integer and advance read position
* @param {number} bytes - Size in bytes (1, 2, 4, or 8)
* @returns {number} Integer value
* @throws {Error} If invalid size or not enough data
*/
readInt(bytes);
/**
* Read double without advancing read position
* @returns {number} Double-precision floating point value
* @throws {Error} If not enough data available
*/
peekDouble();
/**
* Read double and advance read position
* @returns {number} Double-precision floating point value
* @throws {Error} If not enough data available
*/
readDouble();
/**
* Advance read position by size bytes
* @param {number} size - Bytes to advance (can be negative)
* @throws {Error} If would seek beyond buffer bounds
*/
readAdvance(size);
/**
* Write single byte value
* @param {number} value - Byte value to write
*/
writeByte(value);
/**
* Write integer value of specified byte size
* @param {number} value - Integer value to write
* @param {number} size - Size in bytes (1, 2, or 4)
* @throws {Error} If unsupported size
*/
writeInt(value, size);
/**
* Write double-precision floating point value
* @param {number} value - Double value to write
*/
writeDouble(value);
}Usage Examples:
const bser = require('bser');
// Manual buffer operations
const acc = new bser.Accumulator(1024);
// Write data
acc.append("Hello");
acc.writeByte(0x20); // Space character
acc.append("World");
// Read data
console.log(acc.readAvail()); // 11
console.log(acc.peekString(5)); // "Hello" (doesn't advance)
console.log(acc.readString(5)); // "Hello" (advances position)
console.log(acc.readString(6)); // " World"
// Integer operations
acc.writeInt(42, 4);
acc.writeDouble(3.14159);BSER can serialize and deserialize the following JavaScript types:
// Primitive types
type SerializableValue =
| number // Integers and floating-point numbers
| string // UTF-8 strings (stored as binary)
| boolean // true/false values
| null // null value
| Int64 // 64-bit integers (from node-int64 package)
| Array<SerializableValue>
| { [key: string]: SerializableValue };
// Integer limits for automatic type selection
const MAX_INT8 = 127;
const MAX_INT16 = 32767;
const MAX_INT32 = 2147483647;// Standard Error objects are thrown for various conditions:
// - "wanted to read N bytes but only have M" - Insufficient buffer data
// - "excess data found after input buffer" - Extra data after valid BSER
// - "no bser found in string and no error raised" - No valid BSER data
// - "cannot serialize type X to BSER" - Unsupported data type
// - "invalid bser int encoding N" - Corrupted integer encoding
// - "unhandled bser opcode N" - Unknown BSER type codeconst bser = require('bser');
const Int64 = require('node-int64');
// Create 64-bit integers
const bigNum = new Int64('0x0123456789abcdef');
const encoded = bser.dumpToBuffer(bigNum);
const decoded = bser.loadFromBuffer(encoded);
console.log(decoded instanceof Int64); // true
console.log(decoded.toString(16)); // "123456789abcdef"BSER supports template encoding for arrays of objects with similar structures:
// Template data is automatically handled by the decoder
const templateData = [
{ name: "fred", age: 20 },
{ name: "pete", age: 30 },
{ age: 25 } // Missing 'name' field
];
const encoded = bser.dumpToBuffer(templateData);
const decoded = bser.loadFromBuffer(encoded);
// Templates are transparently decoded back to regular arraysconst bser = require('bser');
function safeDecode(buffer) {
try {
return { success: true, data: bser.loadFromBuffer(buffer) };
} catch (error) {
return { success: false, error: error.message };
}
}
function safeEncode(value) {
try {
return { success: true, data: bser.dumpToBuffer(value) };
} catch (error) {
return { success: false, error: error.message };
}
}
// Stream error handling
const bunser = new bser.BunserBuf();
bunser.on('error', (err) => {
console.error('Stream decoding failed:', err.message);
// Reset or recreate bunser as needed
});