Skip to content

TL Pack - Binary Serialization Library

licensenpm versionnpm bundle size

Binary serialization library, inspired by the TL (Type Language) format, created by the VK team. Unlike official TL, this version does not require a schema for serialization/deserialization. It provides a compact and fast alternative to other binary serialization formats like MessagePack.

Benchmark: Slightly faster and more compact than @msgpack/msgpack
⚠️ Note: Benchmark claims may vary.

🔧 Installation

sh
$ npm add -D @andrew_l/tl-pack
sh
$ pnpm add -D @andrew_l/tl-pack
sh
$ yarn add -D @andrew_l/tl-pack

✨ Features

  • No Schema Required: Unlike traditional serialization formats, this version does not require a predefined schema for object serialization.
  • Compact & Fast: Designed to be lightweight and fast, with smaller binary output.
  • Custom Extension Support: Easily extend serialization to custom types.
  • Stream Support: Supports streaming serialization/deserialization in Node.js.

🚀 Example Usage

Basic Example

javascript
import { BinaryWriter, BinaryReader } from '@andrew_l/tl-pack';

const writer = new BinaryWriter();

// Serialize an object with various data types
writer.writeObject({
  null: null,
  uint8: 255,
  uint16: 256,
  uint32: 65536,
  int8: -128,
  int16: -32768,
  int32: -2147483648,
  double: 3.14,
  string: 'Hello world',
  vector: [1, 2, 3, 4, 5, { text: 'hi' }],
  map: { foo: 'bar' },
  date: new Date(),
});

const reader = new BinaryReader(writer.getBuffer());

console.log(reader.readObject());
/**
{
  null: null,
  uint8: 255,
  uint16: 256,
  uint32: 65536,
  int8: -128,
  int16: -32768,
  int32: -2147483648,
  double: 3.14,
  string: 'Hello world',
  vector: [ 1, 2, 3, 4, 5, { text: 'hi' } ],
  map: { foo: 'bar' },
  date: 2023-07-03T12:22:26.000Z
}
 */

Stream Example (Node.js Only)

javascript
import { Readable } from 'node:stream';
import { TLEncode, TLDecode } from '@andrew_l/tl-pack/stream';

const values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

const dataStream = new Readable({ objectMode: true });

dataStream._read = () => {
  const chunk = values.shift();

  dataStream.push(chunk || null);
};

const encode = new TLEncode();
const decode = new TLDecode();

dataStream.pipe(encode).pipe(decode);

decode.on('data', data => console.log('stream', data));
decode.on('error', console.error);

Supported Types

Constructor IDByte Size
Binary5 + sizeof(object)
BoolFalse1
BoolTrue1
Null1
Date4
Vector5 + n * sizeof(n)
VectorDynamic2 + n * sizeof(n)
Int81
Int162
Int324
UInt81
UInt162
UInt324
Float4
Double8
Map2 + sizeof(object)
String5 + sizeof(object)
Repeat5
GZIP5 + sizeof(object)

Custom Types

You can extend tl-pack to handle custom types. For example, handling ObjectId from Mongoose:

javascript
import mongoose from 'mongoose';
import { BinaryWriter, BinaryReader, createExtension } from '@andrew_l/tl-pack';

const ObjectId = mongoose.Types.ObjectId;

const extensions = [
  // Reserve token for ObjectId type
  createExtension(100, {
    encode(value) {
      if (value instanceof ObjectId) {
        this.writeBytes(value.id);
      }
    },
    decode() {
      const bytes = this.readBytes();

      if (IS_BROWSER) {
        return hex(bytes);
      }

      return new ObjectId(bytes);
    },
  }),
];

const writer = new BinaryWriter({ extensions });

writer.writeObject({
  _id: new ObjectId('64a2be105e19f67e19a71a1d'),
  firstName: 'Andrew',
  lastName: 'L.',
});

const reader = new BinaryReader(writer.getBuffer(), { extensions });

console.log(reader.readObject());
/**
{
  _id: 64a2be105e19f67e19a71a1d,
  firstName: 'Andrew',
  lastName: 'L.'
}
 */

const byteToHex: string[] = [];

for (let n = 0; n <= 0xff; ++n) {
  const hexOctet = n.toString(16).padStart(2, '0');
  byteToHex.push(hexOctet);
}

function hex(arrayBuffer: Uint8Array) {
  const buff = new Uint8Array(arrayBuffer);
  const hexOctets = new Array(buff.length);

  for (let i = 0; i < buff.length; ++i) {
    hexOctets[i] = byteToHex[buff[i]];
  }

  return hexOctets.join('');
}

Dictionary Support

The dictionary helps optimize serialization by replacing strings with numeric indexes, saving buffer space.

  • Static Dictionary: Pre-defined dictionary initialized when creating the BinaryWriter/BinaryReader.
  • Dynamic Dictionary: Grows dynamically during encoding and decoding, especially useful for objects (Maps).

Production

No way!