or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.md
tile.json

tessl/npm-bser

JavaScript implementation of the BSER Binary Serialization protocol for efficient local inter-process communication

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
npmpkg:npm/bser@2.1.x

To install, run

npx @tessl/cli install tessl/npm-bser@2.1.0

index.mddocs/

BSER

BSER (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.

Package Information

  • Package Name: bser
  • Package Type: npm
  • Language: JavaScript
  • Installation: npm install bser

Core Imports

const bser = require('bser');

For ES modules:

import * as bser from 'bser';

Individual imports:

const { loadFromBuffer, dumpToBuffer, BunserBuf, Accumulator } = require('bser');

Basic Usage

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);
});

Architecture

BSER is built around several key components:

  • Synchronous API: Direct encoding/decoding functions (loadFromBuffer, dumpToBuffer) for simple operations
  • Asynchronous API: Event-driven streaming decoder (BunserBuf) for handling partial data and streaming scenarios
  • Buffer Management: Efficient buffer handling (Accumulator) for managing expandable data storage
  • Type System: Support for JavaScript primitives, arrays, objects, and 64-bit integers via node-int64
  • Endianness Handling: Automatic detection and handling of system byte order for optimal performance

Capabilities

Synchronous Encoding

Converts 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);

Synchronous Decoding

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);
}

Asynchronous Streaming Decoding

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 decoded
  • error: Emitted when an error occurs during processing

Usage 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);

Buffer Management

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);

Types

Supported Data Types

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;

Error Types

// 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 code

Advanced Usage

64-bit Integer Support

const 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"

Template Optimization

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 arrays

Error Handling Best Practices

const 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
});