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

@saltify/typeproto

v0.3.0

Published

Fast, code-first protobuf serialization library for TypeScript

Readme

Typeproto

Fast, code-first protobuf serialization library for TypeScript.

Important: The library uses Buffer API, which is only available in Node.js and recent versions of Deno. It is not compatible with browser environments.

Basic Usage

A message can be defined as follows:

import { ProtoField, ProtoMessage } from '@saltify/typeproto';

const TestMessage = ProtoMessage.of({
    uint32Field: ProtoField(1, 'uint32'),
    fixed32Field: ProtoField(2, 'fixed32'),
    sint32Field: ProtoField(3, 'sint32'),
    boolField: ProtoField(4, 'bool'),
    stringField: ProtoField(5, 'string'),
    nestedMessageField: ProtoField(6, {
        nestedField: ProtoField(1, 'uint32'),
    }),
    repeatedMessageField: ProtoField(7, {
        nestedField: ProtoField(1, 'uint32'),
    }, 'repeated'),
    repeatedPackedField: ProtoField(8, 'uint32', 'repeated'),
    repeatedNotPackedField: ProtoField(9, 'uint32', 'repeated', { packed: false }),
});

... and serialized/deserialized as follows:

// The type of message is inferred automatically
// If you break the structure, TypeScript will report compile-time errors
const encoded = TestMessage.encode({
    uint32Field: 1,
    fixed32Field: 2,
    sint32Field: -1,
    boolField: true,
    stringField: 'test',
    nestedMessageField: {
        nestedField: 1,
    },
    repeatedMessageField: [
        { nestedField: 1 },
        {}, // Even an empty object is valid
    ],
    repeatedPackedField: [1, 2, 3],
    repeatedNotPackedField: [4, 5, 6],
});

// The decoded message also has the correct type
const decoded = TestMessage.decode(encoded);

Note that all fields are not required when you pass an object to encode. If a non-optional field is not provided, it will be encoded as the default value (zero / empty string / false) of the field type.

Reuse a message

You can define a message model and reuse it in multiple places:

const TestMessageA = ProtoMessage.of({
    uint32Field: ProtoField(1, 'uint32'),
});

const TestMessageB = ProtoMessage.of({
    reusedField: ProtoField(1, TestMessageA),
});

You can also provide your model in a lambda:

const TestMessageA = ProtoMessage.of({
    uint32Field: ProtoField(1, 'uint32'),
});

const TestMessageB = ProtoMessage.of({
    reusedField: ProtoField(1, () => TestMessageA),
});

This could be useful when you want to circularly reference a message model in another file.

Advanced Usage

The model property

The ProtoMessage class has a property model, which is the model object provided to the of method. You can use it to get the model object, which is useful for some advanced use cases.

For example, you can refer to this object to compile the model object to protobuf file or other formats:

const TestMessage = ProtoMessage.of({
    uint32Field: ProtoField(1, 'uint32'),
});

for (const field in TestMessage.model) {
    // Do something with every field
}

Utility types

InferProtoModel and InferProtoModelInput are the utility types that can be used to infer output and input types from the model object. When you pass an object to ProtoMessage<T>.encode, it will be checked against InferProtoModelInput<T>; and when you decode a buffer, the result will satisfy the type InferProtoModel<T>. This is useful when you want to use the model in a more generic way.

Assume that you defined a message model, and want to create a list which you can push objects of the model into. You then use the list to generate a list of encoded buffers:

const TestMessage = ProtoMessage.of({
    uint32Field: ProtoField(1, 'uint32'),
});

const messages = InferProtoModelInput<typeof TestMessage>[];
// this is a shorthand for InferProtoModelInput<typeof TestMessage.model>

messages.push({ uint32Field: 1 });
messages.push({}); // All fields are optional, so this is also valid

const buffers = messages.map((msg) => TestMessage.encode(msg));

Performance

@saltify/typeproto is 3x faster than unoptimized @protobuf-ts/runtime when serializing 100000 same messages in a row, and 1.4x faster when deserializing, according to the benchmark test.

The efficiency of this package comes from the fact that it "compiles" the model written in TypeScript and tends to expand all the logic ahead of use, instead of determining which part of the logic to use at runtime with tons of if and switch statements. The latter approach is the one used by @protobuf-ts/runtime, which is a general-purpose library that can be used in any environment, but it is not optimized for the specific use case of this project. protobuf-ts can also optimize the code by expanding the logic, but only when compiling proto files. This project did not use raw proto files, but instead used DSL-like code to describe the model, so it cannot benefit from the optimizations of protobuf-ts.

Special Thanks

The DSL is inspired by @napneko/nap-proto-core, which uses @protobuf-ts/runtime as the backend. The DSL describes the model in an elegant way and is still valid TypeScript code, and can be used directly for type inference. This package fully adopted this style of model representation.