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

@selfage/message

v3.5.11

Published

Runtime lib for generated messages to parse, copy, serialize and deserialize messages.

Downloads

195

Readme

@selfage/message

Utilities that keep plain JavaScript/TypeScript data in sync with a shared schema. Define the shape of a "message" once and use the helpers in this package to validate, clone, compare, stringify, and turn that data into a compact binary buffer.

Why use it

  • Schema-aware tools without a heavy runtime: describe fields once and reuse the same descriptor everywhere.
  • Deterministic binary format for moving structured data between workers, processes, or services.
  • Safer handling of untrusted input by filtering to known fields and types.
  • Convenient helpers for deep copy, equality checks, and stable JSON output, all driven by the same descriptor.
  • Optional CLI to serialize/deserialize from the command line for debugging or quick integrations.

Install

npm install @selfage/message

Core idea

A message descriptor is a plain object that lists every field in a message, its numeric index, and how that field should be treated (number, boolean, string, enum, nested message, or array). Descriptors can be generated or hand-written. Once you have one, every API in this package accepts that descriptor to understand the shape of your data.

import {
  PrimitiveType,
  MessageDescriptor,
  serializeMessage,
  deserializeMessage,
  parseMessage,
} from "@selfage/message";

interface User {
  id?: number;
  nickname?: string;
  active?: boolean;
}

const USER: MessageDescriptor<User> = {
  name: "User",
  fields: [
    { name: "id", index: 1, primitiveType: PrimitiveType.NUMBER },
    { name: "nickname", index: 2, primitiveType: PrimitiveType.STRING },
    { name: "active", index: 3, primitiveType: PrimitiveType.BOOLEAN },
  ],
};

const illFormedUser: any = {
  id: "12",
  nickname: "Ada",
  active: "yes",
  extraField: "ignored",
};

const parsed = parseMessage(illFormedUser, USER);
// parsed becomes { id: undefined, nickname: "Ada", active: undefined }

// If sent over the wire:
const user: User = { id: 12, nickname: "Ada", active: true };
const binary = serializeMessage(user, USER);
const roundTrip = deserializeMessage(binary, USER);
// roundTrip is now { id: 12, nickname: "Ada", active: true }

Generating descriptors

Rather than hand-writing descriptor files, you can also generate them with @selfage/generator_cli. The tool reads a schema file and emits the TypeScript interface alongside the matching descriptor object.

npm install --save-dev @selfage/generator_cli

# definition.yaml must sit in the working directory; omit the .yaml suffix if you like
npx geneage ./definition.yaml

And the definition.yaml looks like:

- kind: Message
  name: User
  fields:
    - { name: id, type: number, index: 1 }
    - { name: nickname, type: string, index: 2 }
    - { name: active, type: boolean, index: 3 }

The generated module exports the same pair as the example above (a User interface and USER descriptor), ready to import into your project.

Everyday helpers

  • parseMessage(raw, descriptor) filters an arbitrary object, keeping only fields that match the descriptor and coercing enums/primitive types.
  • copyMessage(from, descriptor, to?) deep-clones a message (optionally into an existing object) while reusing typed arrays and nested objects safely.
  • equalMessage(left, right, descriptor) performs structural equality on messages, including nested objects and arrays.
  • stringifyMessage(message, descriptor) produces an index-based JSON string that is stable and language-neutral; destringifyMessage(raw, descriptor) reverses the process.
  • serializeMessage(message, descriptor) and deserializeMessage(binary, descriptor) convert between objects and the compact binary wire format shown in serializer.ts.

Helpers ignore unknown fields, drop singular fields that are null/undefined, and keep array positions even when the element value becomes undefined.

Command line usage

Install locally (as shown above) or run via npx:

# Serialize a JSON object using a descriptor exported from ./descriptor.js
npx message serialize '{"id":1,"nickname":"Ada"}' \
  --encoding hex \
  --descriptor-file ./descriptor \
  --descriptor-name USER

# Deserialize a base64 string back into JSON
npx message deserialize "AQAAAA..." \
  --encoding base64 \
  --descriptor-file ./descriptor \
  --descriptor-name USER

The CLI dynamically imports the descriptor file, so it works with either .js or .ts sources (set --descriptor-file without the extension).

Behavior notes

  • Default buffer size is 16 MB; call initBuffer(newSize) before serializing if you need to handle larger messages.
  • Field and enum indexes must fit inside a 32-bit unsigned integer. String lengths share the same limit (max 2^32 - 1 bytes).
  • The library expects descriptors to list fields in ascending index order. This ensures fast lookups during deserialization.
  • Singular fields skip serialization when the value is undefined or null, so deserialization produces undefined. Within arrays the element slot is preserved, but null or undefined entries come back as undefined.

With these building blocks you can keep your data definitions in one place and rely on a single set of utilities—from validation to binary transport—to keep messages consistent across your project.