data data flow

Data: hierarchical topics fan out through the relay mesh — no broker in the path.

Publish and subscribe to messages like you do with MQTT, without a broker to operate. You get hierarchical topics, MQTT-style + (one segment) and # (rest) wildcards, retained messages, and a choice of reliable or lossy delivery. All of this comes from one typed client. Use it for device telemetry, fleet state, config distribution, application event buses, and presence. It fits any “many publishers, many subscribers, routed by topic” shape.

At a glance

No broker

The relay mesh fans messages out. Every modality shares one operational model. You run nothing extra.

MQTT-style topics

Topics are hierarchical (a/b/c) with + (single segment) and # (multi-segment, trailing) filters. The semantics are the same as MQTT 3.1.1.

Two lanes

A lossy QUIC datagram lane serves high-rate telemetry. A reliable ordered lane serves events that must arrive. Pick the lane per message.

Retained messages

Publish current state once. Every late-joining subscriber gets the last retained value on attach.

Wire model

A Data client binds to a stable clientId and speaks hierarchical topics. Each published message carries a small header in front of your opaque payload. The header holds the publisher’s clientId and the full topic. Subscribers can thus filter and attribute messages without a track per publisher.
Both header fields are bounded at 255 bytes. The payload is arbitrary opaque bytes (Uint8Array / bytes). On the substrate, the SDK maps one MoQT track per top-level topic segment under the data/<segment> namespace:
All sensors/* traffic shares one track. All home/* traffic shares another track. The publisher sends the full topic in the frame header. The SDK filters the rest of the path MQTT-style on the subscriber side. The MoQT capability tag for the track is data.pubsub.
The top-level segment of a filter must be concrete. sensors/+/temp is valid. +/room1/temp and # are not valid. The top-level segment selects the track. A wildcard there would subscribe to every namespace at once. Pick a concrete first segment. Use wildcards below it.

Topic filters

The semantics are the same as MQTT 3.1.1: The rules match MQTT:
  • + matches exactly one path segment.
  • # matches the rest of the path and must be the final segment of the filter.
  • An exact topic string matches itself.

Lanes and QoS

Every publish picks a lane:
reliable: false (the default) rides the QUIC datagram lane. The lane is lossy and unordered, with the lowest latency and no head-of-line blocking. Choose it for high-rate sensor readings. Choose it for any signal where the freshest value matters more than delivery of every value.
Under the hood, the SDK sends reliable and retained messages at an elevated priority on the stream lane. Best-effort telemetry stays on the datagram lane. Each DataMessage carries a retained flag. A subscriber uses the flag to tell a retained bootstrap message from a live message.

Retained messages

The relay caches a message published with retained: true, keyed on (namespace, topic). A subscriber whose filter matches that topic receives the last retained value immediately on attach, before any live messages. This is the canonical “current state” pattern:
A later subscriber to devices/device-7/state (or devices/device-7/#) gets that payload on attach. To clear a retained topic, publish a zero-length payload with retained: true. Do this typically on clean shutdown. A dashboard then reads “offline” without a wait for a heartbeat timeout.
Pair a retained .../state topic (sticky current state) with a lossy .../telemetry topic (live readings). Subscribers get the snapshot instantly. Then they track the live stream.

Per-client demux

Every message carries the publisher’s fromClientId. A single subscriber can thus fan in from a whole fleet and still know which device sent each message. There is no track per device. One subscription and one filter handle the “thousands of devices report to one dashboard” shape.

When to use it

Reach for data when…

You want topic-routed pub/sub, MQTT wildcards, retained state, or a typed event bus. You do not want to run a broker.

Reach elsewhere when…

You need media tracks (use voice / streams), tick-rate game channels (use games), or ROS 2 / DDS QoS semantics (use robotics).

How it works

You skip the broker. The same QUIC connection and ClutchCall relay mesh that you already use for voice, streams, games, and robotics is the fan-out. You deploy, secure, and scale nothing extra. To add data to an existing app costs one more topic namespace, not one more server. This gives three results:
  • One auth path. The same tenant token that authorizes voice, streams, and games authorizes the data plane. The token’s claims carry the tenant and the clientId.
  • Native browser client. There is no MQTT-over-WebSocket bridge. The browser SDK speaks the same wire as your devices over WebTransport.
  • Mix with other modalities. A game client that publishes match telemetry, or a voice app that publishes call events, reuses its existing QUIC connection and token. There is no second stack.
Under the hood, data is frames over MoQT. The frames are multiplexed on one QUIC connection and fanned out by the relay mesh. Your messages stay fast because the relay data plane is kernel-bypass. Packets move between the NIC and userspace zero-copy. The relay uses lock-free mcache / dcache rings on a shard-per-core (thread-per-core) reactor driven by io_uring.