Skip to content

Node Protobuf Codecs and Types

Contents | Previous: Node Protobuf Messaging

After reading this chapter

You can distinguish a generated-class codec from an envelope codec, and packet names from decoding types. Runnable code comes from the same StreamClient tutorial as Protobuf Messaging.

Protobuf bytes alone do not identify their message class. They contain field numbers and values, but no class name such as Ping or Pong. Selecting a handler by packet name and choosing a class to decode the bytes are separate operations.

1. Handler Types and Fallback Types

createZlinkStreamProtobufCodec(Type) calls the decode of the constructor registered with the handler. on(Ping, handler) and on(Pong, handler) receive different types through the same codec. A name-only registration uses the fallback type supplied to the factory. Encoding uses the generated instance's encode. Values whose constructor has no encode use the fallback type.

createZlinkStreamProtobufEnvelopeCodec(options) supports protocols that already use an envelope format. An envelope is an outer message containing information that distinguishes message kinds. The factory forwards the handler type to options.decode(payload, messageType). The application's decoder controls how the inner message is read. The factory does not automatically register message kinds.

Surface Where the type is selected Receiving behavior
createZlinkStreamProtobufCodec(Type) Handler registration, or factory fallback Calls the supplied constructor's decode
createZlinkStreamProtobufEnvelopeCodec(options) Handler registration and application decoder Delegates to options.decode(payload, messageType)
fromProto(payload, ReplyType) At the call Reads an encoded payload using the supplied ReplyType

2. Server Packet Names and Generated Classes

A packet name is a separate name sent by the connector, not a field in the Protobuf schema. For type-based sending and receiving, the default name resolver uses the constructor's name. The example's .proto package is tutorial, but its packet name is Ping, not tutorial.Ping.

The names match when the server handler accepts Ping and uses Ping for pushes. For a server name such as player.ping, specify it with the sending builder's packetName('player.ping') and on('player.ping', handler, Ping). If bundling shortens constructor names, specify wire names explicitly or preserve constructor names.

3. Request and Reply Types

The constructor in request(new Ping(...)) is used to encode the request and select its packet name. The generated class passed to submit(Pong) selects the reply type. The request builder reads the reply through the existing codec's decode(payload, Pong). The callback form, submitCallback(Pong, callback), uses the same decoding path. Without a type, submit() and submit(signal) keep the codec's default decoding, and submitCallback(callback) delivers the encoded payload. The former submit(callback) is renamed to submitCallback(callback). The low-level alternative is submitEncoded() followed by fromProto(encodedReply, Pong).

↗ Open larger

StreamClient/protobuf.ts
// Pass the generated constructor to the existing codec's decode method.
const reply = await connector.request(new Ping({ text: 'rank' })).submit(Pong);

Replies correlate with requests through their sequence, so the connector does not automatically find a decoding type from the Pong packet name alone. The server and client need matching .proto field numbers and types for replies too. Reading with the wrong schema does not always fail: unknown fields may be ignored and default values returned.