# SDK Methods

> The typed Data pub/sub surface: the Data client, publish, subscribe with + / # filters, DataMessage, retained messages, and the wire helpers.

The data modality ships as a subpath of the FirstByte SDK. Import the `Data`
client. Bind a `clientId`. Then `publish` and `subscribe` against hierarchical
topics. One `Data` instance reuses a single MoQT session across every publish
and subscribe.

## Import

**TypeScript:**
```ts
import { Data } from "clutchcall_connect/data";
```

**Python:**
```python
from clutchcall.data import Data
```

Other languages expose the same surface under the equivalent subpath
(`github.com/clutchcall/clutchcall-sdk/go`, `clutchcall`, `com.clutchcall:clutchcall-sdk`, `ClutchCall.SDK`).
The method names and wire format are identical.

## `Data`

The client. Construct one per process or device.

**TypeScript:**
```ts
const data = new Data({
  relayHost: "relay.firstbyte.fast",   // optional; defaults to the platform relay
  token:     process.env.CLUTCHCALL_CREDENTIALS!,
  clientId:  "device-7",
});
```

**Python:**
```python
import os
data = Data(
    relay_host="relay.firstbyte.fast",   # optional
    token=os.environ["CLUTCHCALL_CREDENTIALS"],
    client_id="device-7",
)
```

### Constructor options

- **`token`** (`string`, required) — Bearer token for the relay session. It carries the tenant and the `clientId`   claims. The same token authorizes every other modality.

- **`clientId`** (`string`, required) — This client's stable id. The SDK attaches it as `fromClientId` on every   published message. Subscribers use it to attribute the source.

- **`relayHost`** (`string`) — Relay hostname. Defaults to the platform relay.

- **`onState`** (`(state, reason?) => void`) — Optional connection-state callback. It fires as the underlying QUIC/MoQT   session moves through `Connecting → Connected → Reconnecting → Closed /   Failed`. The session reconnects automatically and replays subscriptions.

- **`webTransport`** (`WebTransportFactory`) — Inject a custom WebTransport factory: a Node polyfill or a test double. This   is optional. The browser SDK uses the native implementation.

## Methods

### `publish(args)`

Publish one message. The SDK adds the `(fromClientId, topic)` header and selects
the MoQT track from the topic's top-level segment.

**TypeScript:**
```ts
await data.publish({
  topic:    "sensors/room1/temperature",
  payload:  new TextEncoder().encode("23.5"),
  reliable: false,   // default: lossy datagram lane
  retained: false,   // default: not cached
});
```

**Python:**
```python
data.publish(
    topic="sensors/room1/temperature",
    payload=b"23.5",
    reliable=False,
    retained=False,
)
```

- **`topic`** (`string`, required) — Full hierarchical topic, e.g. `sensors/room1/temperature`. UTF-8, ≤ 255 bytes.

- **`payload`** (`Uint8Array | bytes`, required) — Opaque payload bytes. Encode JSON / text / protobuf yourself.

- **`reliable`** (`boolean`, default `false`) — `false` → lossy QUIC datagram lane (lowest latency). `true` → ordered, retried   subgroup-stream lane (guaranteed in-order per publisher).

- **`retained`** (`boolean`, default `false`) — `true` → the relay caches the latest payload on this topic and delivers it to   every late-joining subscriber on attach. To clear it, publish a   **zero-length** payload with `retained: true`.

**Returns** a `Promise<void>` (TS) / `None` (Python). The promise resolves when
the SDK hands the frame to the transport.

> **WARNING:**
> The top-level segment must be concrete. `publish({ topic: "sensors/..." })` is
> valid. A topic that begins with `+` or `#` throws. The first segment selects
> the track.

### `subscribe(args, onMessage)`

Subscribe with an MQTT-style filter. The callback fires for every matching
message. The SDK opens one MoQT subscription on the filter's top-level segment
and filters the rest of the path client-side.

**TypeScript:**
```ts
const sub = await data.subscribe(
  { topicFilter: "sensors/+/temperature" },
  (msg) => {
    console.log(
      msg.topic, "←", msg.fromClientId, "=",
      new TextDecoder().decode(msg.payload),
      msg.retained ? "(retained)" : "",
    );
  },
);
```

**Python:**
```python
sub = data.subscribe(
    topic_filter="sensors/+/temperature",
    on_message=lambda m: print(
        m.topic, "←", m.from_client_id, "=", m.payload,
        "(retained)" if m.retained else "",
    ),
)
```

- **`topicFilter`** (`string`, required) — MQTT-style filter. `+` matches one segment. `#` matches the rest (trailing   only). The top-level segment must be concrete.

- **`onMessage`** (`(msg: DataMessage) => void`, required) — The SDK invokes it for every message whose topic matches the filter. The SDK   silently drops malformed frames before the callback.

**Returns** a `DataSubscription` (TS: `Promise<DataSubscription>`). Call
`.close()` to stop receiving.

### `close()`

Stop every publication and subscription. Close the MoQT session. The method is
idempotent.

**TypeScript:**
```ts
await data.close();
```

**Python:**
```python
data.close()
```

## Handles and types

### `DataMessage`

What `onMessage` receives.

**TypeScript:**
```ts
interface DataMessage {
  topic:        string;      // full topic the publisher sent
  fromClientId: string;      // publisher's clientId
  payload:      Uint8Array;  // opaque bytes
  retained:     boolean;     // true if a cached/bootstrap value, not live
}
```

**Python:**
```python
@dataclass
class DataMessage:
    topic: str
    from_client_id: str
    payload: bytes
    retained: bool
```

- **`topic`** (`string`) — The full topic string that the publisher sent (not the filter).
- **`fromClientId`** (`string`) — The `clientId` of the publisher. Use it for attribution and per-client demux.
- **`payload`** (`Uint8Array | bytes`) — The opaque message bytes.
- **`retained`** (`boolean`) — `true` when this is a retained/bootstrap value that the relay re-emitted on   attach. `false` for live traffic. The flag lets you treat a snapshot   differently from updates.

### `DataSubscription`

The handle that `subscribe` returns. It carries `topicFilter` and exposes:

**TypeScript:**
```ts
sub.close();   // stop receiving for this filter
```

**Python:**
```python
sub.close()
```

## Connection state and events

The data client does not raise per-message events beyond your `onMessage`
callback. The optional `onState` constructor callback shows the lifecycle. It
mirrors the underlying session:

| State          | Meaning                                                   |
| -------------- | --------------------------------------------------------- |
| `Connecting`   | The first QUIC/MoQT handshake is in flight.               |
| `Connected`    | The session is up. Publishes and subscribes flow.         |
| `Reconnecting` | The transport dropped. The SDK reconnects and will replay subscriptions. |
| `Closed`       | You called `close()`.                                     |
| `Failed`       | Unrecoverable: the token was rejected, or the relay is unreachable. |

> **NOTE:**
> On reconnect, the SDK replays your subscriptions automatically. The relay
> re-emits matching retained values. A transient drop thus restores current
> state without app code.

## Wire helpers (advanced)

The subpath also exports the raw codec and matcher. Use them for interop
tests, or for callers that must stay wire-compatible without the high-level
client:

**TypeScript:**
```ts
import {
  encodeDataFrame, decodeDataFrame,
  topicMatches, topLevelSegment,
} from "clutchcall_connect/data";

const frame = encodeDataFrame("device-7", "sensors/room1/temp", payload);
const decoded = decodeDataFrame(frame);        // { fromClientId, topic, payload }
topicMatches("sensors/room1/temp", "sensors/+/temp");   // true
topLevelSegment("sensors/+/temp");                       // "sensors"
```

**Python:**
```python
from clutchcall.data import (
    encode_data_frame, decode_data_frame,
    topic_matches, top_level_segment,
)

frame = encode_data_frame("device-7", "sensors/room1/temp", payload)
decoded = decode_data_frame(frame)             # DecodedDataFrame
topic_matches("sensors/room1/temp", "sensors/+/temp")    # True
top_level_segment("sensors/+/temp")                       # "sensors"
```

Both `from_client_id` and `topic` are bounded at 255 bytes. `topLevelSegment`
throws on a leading wildcard. These helpers mirror the frame layout in
[Details](/modalities/data/details#wire-model).

## Dropping to the substrate

The data modality is a thin convention over MoQT frames. If you need a shape
that it does not cover, import `clutchcall_connect/moqt`. Use
`MoqtClient.publishFrame` / `subscribeFrame` directly under your own namespace.
The same auto-reconnect, capability routing, and relay fan-out apply. See
[Realtime Tracks](/concepts/realtime-tracks).
