npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@toolcase/serializer

v3.0.2

Published

Compact protobuf-based binary serializer — fast schema-driven encode/decode for Node.js and the browser.

Downloads

129

Readme

@toolcase/serializer

GitHub npm version

Protobuf-based binary serializer built on protobufjs. Define message schemas at runtime — no .proto files, no codegen — and encode/decode compact binary buffers.

Why

Use this when you need to send structured, type-checked, compact payloads over the wire (WebSocket, WebRTC datachannel, IPC, savegames, IndexedDB blobs) without committing to a build step or losing the ability to evolve the schema in JS.

Install

npm install @toolcase/serializer

Quick start

import Serializer from '@toolcase/serializer'

const serializer = new Serializer()

serializer.define('Player', [
    { key: 'name', type: Serializer.FieldType.STRING, rule: 'required' },
    { key: 'score', type: Serializer.FieldType.INT32, rule: 'optional', default: 0 },
    { key: 'alive', type: Serializer.FieldType.BOOL, rule: 'optional', default: true }
])

const buffer = serializer.encode('Player', { name: 'Alice', score: 42 })
const player = serializer.decode('Player', buffer)
// → { name: 'Alice', score: 42, alive: true }

Real-world usage

WebSocket multiplayer

const proto = new Serializer('game')

proto.define('Move', [
    { key: 'x', type: Serializer.FieldType.FLOAT, rule: 'required' },
    { key: 'y', type: Serializer.FieldType.FLOAT, rule: 'required' },
    { key: 'dt', type: Serializer.FieldType.UINT32, rule: 'required' }
])

socket.binaryType = 'arraybuffer'
socket.send(proto.encode('Move', { x: 12.4, y: 8.1, dt: 16 }))

socket.onmessage = (event) => {
    const move = proto.decode('Move', new Uint8Array(event.data))
}

Savegame on disk / localStorage

const save = new Serializer('save')

save.define('Save', [
    { key: 'level', type: Serializer.FieldType.UINT32, rule: 'required' },
    { key: 'inventory', type: Serializer.FieldType.PACKED_ARRAY('uint32'), rule: 'repeated' },
    { key: 'flags', type: Serializer.FieldType.MAP('string', 'bool') }
])

localStorage.setItem('save', btoa(String.fromCharCode(...save.encode('Save', state))))

API

new Serializer(id?)

Create a serializer instance. Optional id becomes the protobuf namespace name; if omitted, a random 16-char ID is used.

serializer.define(key, fields)

Define a message type.

| Field property | Type | Description | |----------------|------|-------------| | key | string | Field name. | | type | string \| EnumMarker \| MapMarker \| PackedMarker | One of Serializer.FieldType.* (or marker helpers — see below). | | rule | 'required' \| 'optional' \| 'repeated' | Cardinality. | | default | any | Optional default value. |

serializer.enum(name, values)

Register a top-level enum that can be referenced from multiple types.

serializer.enum('Tier', ['BRONZE', 'SILVER', 'GOLD'])

serializer.encode(key, message): Uint8Array

Encode a message to a Uint8Array. Throws with a descriptive error if validation fails.

serializer.decode(key, buffer): Message

Decode a Uint8Array back into a message object.

serializer.safeEncode(key, message) / safeDecode(key, buffer)

Non-throwing variants that return { ok: true, value } or { ok: false, error }.

const result = serializer.safeDecode('Player', buffer)
if (!result.ok) console.error(result.error)
else useIt(result.value)

serializer.validate(key, message): string | null

Run protobuf validation only — returns an error message string, or null if valid.

serializer.types() / serializer.fields(key)

Introspect the schema. Useful for debug tooling.

Field types

Serializer.FieldType.{ DOUBLE, FLOAT,
                       INT32, UINT32, SINT32, FIXED32, SFIXED32,
                       INT64, UINT64, SINT64, FIXED64, SFIXED64,
                       STRING, BOOL, BYTES }

Composite markers

Serializer.FieldType.ENUM(['DRAFT', 'PUBLISHED'])
Serializer.FieldType.ENUM({ DRAFT: 0, PUBLISHED: 1 })

Serializer.FieldType.MAP('string', 'int32')

Serializer.FieldType.PACKED_ARRAY('uint32') // packed encoding for repeated scalars

| Constant | Protobuf type | Description | |----------|---------------|-------------| | DOUBLE / FLOAT | double / float | 64- / 32-bit float. | | INT32 / INT64 | int32 / int64 | Variable-length signed. | | UINT32 / UINT64 | uint32 / uint64 | Variable-length unsigned. | | SINT32 / SINT64 | sint32 / sint64 | ZigZag-encoded signed (efficient for negatives). | | FIXED32 / FIXED64 | fixed32 / fixed64 | Fixed-width unsigned. | | SFIXED32 / SFIXED64 | sfixed32 / sfixed64 | Fixed-width signed. | | STRING | string | UTF-8 string. | | BOOL | bool | Boolean. | | BYTES | bytes | Raw byte buffer. |

Schema versioning + migrations

Tag each frame with a 2-byte version header and migrate older frames forward when decoding.

const proto = new Serializer('game')

proto.define('Save', [
    { key: 'level', type: Serializer.FieldType.UINT32, rule: 'required' },
    { key: 'energy', type: Serializer.FieldType.UINT32, rule: 'required' }
])

proto.version(2, 0) // current is v2

// frames written under v1 only had `level`
proto.migrate('Save', 1, (msg) => ({ ...msg, energy: 100 }))

const buffer = proto.encodeVersioned('Save', { level: 5, energy: 80 })
const { version, message } = proto.decodeVersioned('Save', buffer)

major and minor are each in [0, 255]. Decoding a frame newer than the current major version throws.

Fragmenting large payloads

For UDP-like transports with size limits (WebRTC datachannel ~ 16 KB), split a buffer into ordered chunks and reassemble on the other side.

const buffer = serializer.encode('Snapshot', huge)
const chunks = serializer.fragment(buffer, 16384)

chunks.forEach(chunk => transport.send(chunk))

// receiving side
const reassembled = serializer.reassemble(receivedChunks)
const snap = serializer.decode('Snapshot', reassembled)

Each chunk has an 8-byte header: 4-byte frame ID, 2-byte index, 2-byte total. The reassembler validates frame ID, indices, and counts — it throws on mismatched/duplicate/missing chunks.

License

MIT