# Details

> Publish and subscribe like MQTT without a broker. You get topic filters, retained state, and reliable and datagram lanes.

<img src="/images/diagrams/data.png" alt="data data flow" />

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.

```
[u8 from_len][from_client_id][u8 topic_len][topic][payload]
```

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:

```
sensors/room1/temperature   →   track  data/sensors
home/lights/kitchen/level   →   track  data/home
events/orders/shipped       →   track  data/events
```

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`.

> **NOTE:**
> 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:

| Filter                | Matches                                            |
| --------------------- | -------------------------------------------------- |
| `sensors/room1/temp`  | exactly that topic                                 |
| `sensors/+/temp`      | `sensors/<any one segment>/temp`                   |
| `sensors/#`           | `sensors` and everything below it, any depth       |
| `sensors/room1/#`     | `sensors/room1` and everything under it            |
| `events/+/+`          | any two segments after `events`                    |

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.
  
  
    `reliable: true` rides an ordered, retried **MoQT subgroup stream**.
    Delivery is guaranteed and in order per publisher. Use it for application
    events, commands, settlements, and "the alarm latched". Use it for any
    message that you cannot drop.
  

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:

```ts
// Device announces it is online — sticks until replaced or cleared.
await data.publish({
  topic:    "devices/device-7/state",
  payload:  new TextEncoder().encode(JSON.stringify({ online: true })),
  retained: true,
});
```

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.

> **TIP:**
> 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 FirstByte 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.

## Related

- [Realtime Tracks](/concepts/realtime-tracks) — the MoQT primitive underneath.
- [Modalities overview](/modalities/overview) — the shared substrate and the
  full set.
- [SDK Methods](/modalities/data/sdk-methods) — the typed `Data` surface.
