# `Capstan.Binlog.TransactionPayload`
[🔗](https://github.com/baselabs/capstan/blob/v1.3.1/lib/capstan/binlog/transaction_payload.ex#L1)

Decodes a `TRANSACTION_PAYLOAD_EVENT` (type 40) body into the inner event
stream of the transaction it wraps — the `binlog_transaction_compression`
consumer arm (ADR-0011).

Body layout (MySQL server source, `libbinlogevents/src/codecs/binary.cpp`,
read first-hand): a `net_field_length` TLV header — triples of
`(type, value_length, value)` closed by a bare `0` end mark — followed by the
payload bytes. Field types: `0` end mark, `1` payload size, `2` compression
type, `3` uncompressed size; unrecognized fields are skipped by their length.
The compression type value is `0` for ZSTD and `255` for NONE
(`compression/base.h`) — only ZSTD is consumed; anything else fails closed.

The payload is one or more zstd frames (`Capstan.Zstd`). The inflated bytes
are the transaction's events — `QUERY(BEGIN)`, `TABLE_MAP`, row events,
terminator — each a plain 19-byte header + body with **no per-event CRC
trailer** (the outer type-40 event's CRC32 covers the compressed payload as a
whole; observed live, the inner headers' `event_size` values sum exactly to
the inflated length). Splitting is driven by each inner header's `event_size`.

Every divergence from the declared shape — a missing field, a size that does
not fit, an unknown compression type, a zstd corruption signal, an
uncompressed-size mismatch, a malformed inner header — is `{:error, reason}`,
never a guess. All reasons are value-free (Rule 1): the payload and inner
bytes carry row values and DDL text and never enter an error term.

# `decode`

```elixir
@spec decode(binary()) :: {:ok, [Capstan.Binlog.Event.t()]} | {:error, term()}
```

Decodes a type-40 body to its inner event list: parses the TLV header,
inflates the ZSTD payload, and splits the inner event stream.

The uncompressed size, when the server declares it, is checked against the
inflated length — a server-declared oracle over the decompressor's output.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
