Skip to content

fix(microservices): unwrap records returned by message handlers - #18008

Merged
kamilmysliwiec merged 1 commit into
nestjs:masterfrom
johnnyhuirilef:fix/record-replies
Oct 8, 2026
Merged

kamilmysliwiec merged 1 commit into
nestjs:masterfrom
johnnyhuirilef:fix/record-replies

Conversation

@johnnyhuirilef

Copy link
Copy Markdown
Contributor

PR Checklist

PR Type

  • Bugfix

What is the current behavior?

Issue Number: #18007

Closes #18007

Hi! πŸ‘‹ A @MessagePattern() handler that returns an MqttRecord, NatsRecord or RmqRecord sends the whole record to the client, and the record options are dropped.

The server builds the reply as { response, isDisposed, id }. The record checks and the record serializers read only packet.data, so they never see the record in packet.response. ServerMqtt.getPublisher() already tries to handle an MqttRecord, but it reads response.data, which is always undefined on a reply.

I ran the repro on 12.1.2 with real brokers. The three clients got { data, options } or { data, headers }, and the replies had no record options:

RESULT: BUG: 3 of 6 cases did not end as expected (the record is not unwrapped, and its options are dropped).

What is the new behavior?

  • MQTT: the serializer unwraps the record in response. The publisher passes the record options (QoS, retain, properties) to publish. This change removes the dead delete of data.options.
  • NATS: the serializer unwraps the record in response and returns its headers. The reply sends them with respond.
  • RMQ: the serializer unwraps the record in response and returns its options. The reply sends them with sendToQueue. The correlation id of the request wins over a record option, so the client still matches the reply.

The same repro against a local build of this branch:

RESULT: FIXED: every reply carries the record data, and the record options apply on MQTT, NATS and RMQ.

Does this PR introduce a breaking change?

  • Yes
  • No

A handler that returns a plain object, and a client that sends a record, take the same path as before.

One type note: the MQTT and RMQ record serializer types now accept a reply packet. The NATS serializer already took any. The serializers are not exported from the package root. A deep import of RmqRecordSerializer now sees data and pattern as optional in its output type.

Other information

The new record specs use the real reply packet shape. They cover these cases:

  • Server.send() with a handler that returns a record: the transport gets the record data, isDisposed and the record options.
  • A plain object reply stays as before, also when the object has data and options or headers keys. The MQTT and NATS specs also cover a primitive reply. The serializers also accept an undefined or null packet.
  • The handler's record is not changed.
  • RMQ: the request correlation id wins over a record option.

The older ServerMqtt and ServerNats specs used a packet shape that the server never builds, so they did not catch this. The new specs fix that shape.

The docs show record builders only on the client, with client.send(). I am happy to open a docs PR that shows a record in a handler too. πŸ™‚

A handler that returns an MqttRecord, NatsRecord or RmqRecord sent the
whole record to the client. The record options were dropped.

The server builds the reply as { response, isDisposed, id }. The record
checks and the record serializers read only the data field, so they
never saw the record in response.

MQTT: the serializer unwraps the record in response. The publisher
passes the record options (QoS, retain, properties) to publish. This
change removes the dead delete of data.options.

NATS: the serializer unwraps the record in response and returns its
headers. The reply sends them with respond.

RMQ: the serializer unwraps the record in response and returns its
options. The reply sends them with sendToQueue. The correlation id of
the request wins over a record option, so the client still matches the
reply. Before this fix, record options never reached sendToQueue.

The MQTT and RMQ record serializer types now accept a reply packet.

Closes nestjs#18007
@kamilmysliwiec
kamilmysliwiec merged commit b38cf79 into nestjs:master Oct 8, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A record returned by a message handler is sent whole, and its options are dropped (MQTT, NATS, RMQ)

2 participants