protobuf

js/format/protobuf.ts

fino:format/protobuf - TypeScript-defined Protocol Buffers messages.

This module implements the Protocol Buffers binary wire format directly. A message schema is an ordinary TypeScript object whose keys are checked against the application's TypeScript type and whose field descriptors carry the stable numeric indexes written on the wire. Encoded messages therefore contain field numbers and values, never JavaScript property-name strings.

The schema object is the intermediate representation: a future TypeScript AST extractor or .proto parser can generate the same MessageSchema<T> without changing the encoder, decoder, or callers.

Supported wire behavior:

import { defineMessage } from 'fino:format/protobuf';

interface Metric {
  name: string;
  values: number[];
}

const Metric = defineMessage<Metric>({
  name: { number: 1, type: 'string' },
  values: { number: 2, type: 'double', repeated: true },
});

const bytes = Metric.encode({ name: 'latency', values: [1.5, 2.25] });
Metric.decode(bytes);

Conformance map:

Authoritative references:

Types

type ScalarType = NumberType | BigIntType | 'bool' | 'string' | 'bytes'

Scalar field names understood by the Protocol Buffers wire codec.

The 64-bit integer forms use bigint; all other numeric forms use number. Enum values are their numeric wire values.

type FieldSchema<T> = Present<T> extends readonly (infer Item)[] ? { number: number; type: SchemaType<Item>; repeated: true; packed?: boolean; optional?: false } : { number: number; type: SchemaType<Present<T>>; repeated?: false; packed?: never; optional?: boolean; }

One named TypeScript property and its stable Protocol Buffers field number.

Repeated properties must set repeated: true. Packable repeated scalar fields are packed by default; set packed: false to emit expanded records. Set optional: true to preserve absence instead of materializing the scalar type's protobuf default during decoding.

type MessageSchema<T extends object> = { [Key in keyof T]-?: FieldSchema<T[Key]> }

Runtime schema for a TypeScript message type.

Every property in T has one descriptor, so renaming or adding a property requires updating the shared schema at compile time. Field numbers are the stable compatibility boundary and must not be reused after publication.

Interfaces

interface MessageCodec<T extends object> {

Compiled encoder and decoder for one TypeScript message type.

Readonly Properties

readonly schema: Readonly<MessageSchema<T>>

The checked TypeScript schema used to build this codec.

Methods

encode(value: T): Uint8Array

Encode a value as a protobuf binary message.

decode(bytes: Uint8Array | ArrayBuffer): T

Decode a protobuf binary message.

Unknown fields are ignored. Missing required-looking scalar properties receive protobuf defaults because wire format presence alone cannot enforce application-level requiredness.

Classes

class ProtobufError extends Error {

Error thrown for an invalid schema, unsupported value, or malformed wire message.

Properties

override name

Error name reported by ProtobufError instances.

Readonly Properties

readonly offset?: number

Byte offset associated with malformed input, when available.

Constructors

constructor(message: string, offset?: number)

Functions

function defineMessage<T extends object>(schema: MessageSchema<T>): MessageCodec<T>

Compile a TypeScript-keyed schema into a protobuf encoder and decoder.

The schema is validated once and fields are encoded in ascending numeric order for predictable local output. Protocol Buffers does not define canonical serialization, so callers must not use the resulting bytes as a cross-implementation content hash.

interface Ready {
  owner: number;
  descriptors: number[];
}

const Ready = defineMessage<Ready>({
  owner: { number: 1, type: 'uint32' },
  descriptors: { number: 2, type: 'int32', repeated: true },
});