# Changelog
All notable changes to `moqx` will be documented in this file.
## [Unreleased]
## [0.7.1] - 2026-04-23
### Fixed
- Fixed `{:moqx_fetch_ok, ...}` never being delivered to the caller when the
relay drains the fetch data stream before queueing `FetchOk` on the control
stream. `handle_fetch_stream` now sends `{:moqx_fetch_ok, ...}` immediately
after reading the stream's `request_id`, before processing any objects.
## [0.7.0] - 2026-04-23
### Added
- Added draft-14 object datagram publish/subscribe support to the core client
contract via `MOQX.write_datagram/3`.
- Added a subscriber-side datagram receive loop in the NIF so datagram-delivered
objects are surfaced through the same `{:moqx_object, %MOQX.ObjectReceived{...}}`
message family used for subgroup delivery.
- Added `%MOQX.Object{transport: :subgroup | :datagram}` so callers can tell
which delivery path produced an object without changing mailbox contracts.
- Added relay-backed integration coverage for object datagram delivery,
including transport metadata, extension round-tripping, and datagram
end-of-group signaling.
- Added `%MOQX.NativeBinary{ref: reference(), size: non_neg_integer()}` — a lazy
payload type backed by a Rust `bytes::Bytes` resource. Object and fetch-object
payloads now arrive as `%MOQX.NativeBinary{}` instead of BEAM binaries, keeping
data on the native heap until the caller explicitly materialises it.
- Added `MOQX.NativeBinary.load/1` — the single copy point that materialises a
`NativeBinary` into a BEAM binary.
- Added `MOQX.NativeBinary.from_binary/1` — wraps an existing BEAM binary into a
`NativeBinary` for use in homogeneous-API or testing contexts.
- `MOQX.write_object/4` and `MOQX.write_datagram/3` now accept either `binary()`
or `%MOQX.NativeBinary{}` as the payload argument, enabling zero-copy
subscribe → re-publish pipelines without touching the BEAM heap.
### Changed
- `:moqx_end_of_group` now explicitly covers end-of-group signaled by either a
subgroup or an object datagram.
- README and module docs now document explicit subgroup-stream vs object-datagram
publishing semantics, including the fact that `write_frame/2` does not
auto-route to datagrams.
- Local integration-test guidance now documents the known stale-container cert
pitfall: after regenerating integration certs, the relay container must be
recreated to avoid TLS handshake failures such as
`invalid peer certificate: BadSignature`.
- **Breaking:** `%MOQX.Object{payload}` and `%MOQX.FetchObject{payload}` are now
`MOQX.NativeBinary.t()` instead of `binary()`. Callers that pattern-match or
compare the payload field must call `MOQX.NativeBinary.load/1` first.
## [0.6.1] - 2026-04-16
### Fixed
- Hardened the relay-backed integration coverage for the late-publisher subscribe
path so the suite no longer flakes when the current `moqtail` relay races an
early `Unknown track namespace` rejection before the namespace is published.
The test now retries the subscribe request within the delivery-timeout window
until the late publisher appears, while still failing on unexpected subscribe
errors.
- Fixed the tag-driven release workflow's version guard to read `mix.exs`
statically instead of invoking `mix run`, avoiding CI failures caused by
compiler output contaminating the captured version string.
## [0.6.0] - 2026-04-15
This release automates the tag-driven release path and hardens release metadata
validation so publishing fails fast if the tag, `mix.exs`, and changelog drift.
### Added
- Added a tag-triggered GitHub Actions release workflow that reruns the full
release preflight (`mix format --check-formatted`, `mix test`,
`mix test.integration`, `mix docs`, and `mix credo --strict`) before
publishing.
- Added automated Hex publishing on `v*` tags via `mix hex.publish --yes`,
using the repository `HEX_API_KEY` secret.
- Added automated GitHub release creation/update on `v*` tags, with release
notes extracted from the matching `CHANGELOG.md` section.
- Added release-time validation that the pushed tag version matches both
`mix.exs` and a `CHANGELOG.md` heading for the same version.
### Documentation
- Clarified project release guidance in `AGENTS.md` and the local release skill,
including `mix docs` in pre-release checks.
- Corrected the local Hex release skill to reflect actual Hex behavior:
`mix hex.publish` publishes package + docs by default, while
`mix hex.publish docs` remains the docs-only follow-up path.
## [0.5.0] - 2026-04-15
This release finalizes the low-level async core contract that `moqx` exposes.
`MOQX` is now explicitly documented as the thin async core layer, while
convenience flows live in `MOQX.Helpers` and future stateful ergonomics remain
out of the core contract.
### Migration notes
If you are upgrading from older `0.2.x`–`0.4.x` APIs:
- connect is now explicitly correlated by `connect_ref`
- publish namespace readiness is now explicit and correlated by `publish_ref`
- publisher writes are lifecycle-gated and no longer silently drop before
downstream activation
- subscribe/fetch lifecycle messages now use typed structs and shared typed
async error families
- helper catalog flows moved from `MOQX` into `MOQX.Helpers`
- `unsubscribe/1` now culminates in `{:moqx_publish_done, ...}` rather than the
old `:moqx_track_ended` tuple contract
- `delivery_timeout_ms` remains the correct draft-14 subscribe timeout option
- `rendezvous_timeout_ms` was a mistaken rename based on newer-draft terminology and should not be used on the draft-14 stack
- local integration guidance now uses `mix test.integration` against a relay you
keep running separately
### Changed
- **Breaking:** connect is now explicitly correlated. `connect/2`,
`connect_publisher/2`, and `connect_subscriber/2` return `{:ok, connect_ref}`.
Asynchronous connect outcomes are delivered as typed messages:
`{:moqx_connect_ok, %MOQX.ConnectOk{...}}`,
`{:moqx_request_error, %MOQX.RequestError{...}}`, and
`{:moqx_transport_error, %MOQX.TransportError{...}}`.
- **Breaking:** publish namespace readiness is now explicit and correlated.
`publish/2` returns `{:ok, publish_ref}` and the broadcast handle arrives only
via `{:moqx_publish_ok, %MOQX.PublishOk{...}}` after relay ack.
Failures are delivered as typed async request/transport errors with
`op: :publish` and the matching `publish_ref`.
- **Breaking:** publisher write lifecycle now has explicit synchronous gating.
Writes no longer silently drop before downstream activation.
`write_frame/2` and `open_subgroup/3` now fail synchronously with typed
`%MOQX.RequestError{code: :track_not_active | :track_closed}`.
- Subscriber data-path race handling now tolerates early subgroup streams that
arrive just before local `SubscribeOk` state installation, reducing first-frame
loss risk under control/data-plane reordering.
- **Breaking:** subgroup flush is now explicitly correlated.
`flush_subgroup/1` returns `{:ok, flush_ref}` and asynchronous completion is
delivered as `{:moqx_flush_ok, %MOQX.FlushDone{...}}` (or typed transport
failure).
- **Breaking:** subscribe/fetch/object lifecycle messages now use typed payload
structs and explicit message families.
`:moqx_subscribed`/`:moqx_track_ended`/`:moqx_fetch_started`/`:moqx_fetch_error`
tuple contracts are replaced by typed events such as
`:moqx_subscribe_ok`, `:moqx_publish_done`, `:moqx_fetch_ok`, and the shared
async error families.
- **Breaking:** asynchronous generic tuple errors (`{:error, reason}` and
`{:moqx_error, ..., reason}`) are no longer the primary public contract.
Async failures now flow through `%MOQX.RequestError{}` and
`%MOQX.TransportError{}`.
- Added explicit publisher track lifecycle events for track owners:
`{:moqx_track_active, %MOQX.TrackActive{...}}` and
`{:moqx_track_closed, %MOQX.TrackClosed{...}}`.
- **Breaking:** helper-level convenience APIs moved out of core `MOQX` into
`MOQX.Helpers` (`publish_catalog/2`, `update_catalog/2`, `fetch_catalog/2`,
`await_catalog/2`).
- Mix tasks now expose primary names `moqx.roundtrip` and `moqx.inspect`.
Legacy aliases `moqx.e2e.pubsub` and `moqx.moqtail.demo` remain available with
deprecation notices.
- `moqx.inspect` can probe catalog tracks named either `"catalog"` or
`".catalog"` (or an explicit `--catalog-track`), supports `--no-fetch` for
relays that do not implement fetch yet, and now includes named relay presets
plus interactive preset selection. This improves interop with Cloudflare
`moq-rs` style relays and other early deployments.
- `MOQX.Helpers.fetch_catalog/2` now accepts `:track` so callers can override
the catalog track name when relays do not use `"catalog"`.
- Mix tasks and integration helpers now follow the typed async contract.
- Integration tests now assume a relay endpoint provided by environment
(`MOQX_EXTERNAL_RELAY_URL`) and trusted CA path (`MOQX_RELAY_CACERTFILE`),
with Docker-based local/CI orchestration as the primary deterministic path.
- Removed `:public_relay_live` integration tests; public relay interop checks now
live in manual mix tasks.
- Subscribe request rejections now carry typed `RequestError.code` values
(for example `:track_does_not_exist` / `:timeout`).
- Corrected subscribe-timeout naming/docs back to draft-14 `delivery_timeout_ms`
after auditing the wire parameter mapping against the spec and `moqtail`.
- Pinned README spec references to the explicit draft-14 target instead of the
moving latest Internet-Draft URL.
- Hardened the early-subscribe integration coverage to use a short payload burst
rather than assuming the very first write always survives the current
`moqtail` relay's subscribe-activation race window.
### Documentation
- Rewrote README and module docs to align with the low-level async contract and
remove stale tuple-era examples (`:moqx_frame`, `:moqx_subscribed`,
`:moqx_track_ended`, `:moqx_error`, etc.).
- Clarified core vs helper-layer responsibilities and the intended separation
from any future managed/stateful ergonomics layer.
- Added `0.5.0` migration guidance covering message-shape, lifecycle, helper,
timeout-option, and integration-harness changes.
- Documented current moqtail standalone fetch behavior more explicitly:
relay-backed fetch succeeds from relay cache, while cache misses surface as
typed fetch request errors rather than hanging silently.
- Clarified core async message families and correlation behavior for connect,
subscribe, fetch, and subgroup flush.
## [0.4.1] - 2026-04-12
### Fixed
- `MOQX.unsubscribe/1` and subscription handle `Drop` no longer panic the
NIF with `send_and_clear: current thread is managed`. Environment work
(message send to the caller) is now performed inside the Tokio runtime
instead of on the BEAM scheduler thread invoking the NIF/GC.
- Added end-to-end integration coverage for `unsubscribe/1` (live relay
roundtrip verifying `:moqx_track_ended`, frame cut-off, and idempotent
double-unsubscribe).
## [0.4.0] - 2026-04-12
### Added
- `MOQX.unsubscribe/1` cancels an active track subscription by sending MOQ
`Unsubscribe` to the relay. Idempotent and fire-and-forget; the caller
subsequently receives `{:moqx_track_ended, handle}` when the relay
acknowledges.
- Dropping a subscription handle (GC) now automatically cancels the
subscription — short-lived subscribing processes no longer need to
unsubscribe explicitly to free relay-side resources.
- `{:moqx_track_ended, handle}` is now emitted on publisher-side
`PublishDone` (graceful `TrackEnded` / `SubscriptionEnded` status codes).
The corresponding local subscription state is removed in the same path,
fixing a small leak in `active_subscriptions`.
- `MOQX.Helpers.publish_catalog/2` helper to create and publish the initial `"catalog"` track object.
- `MOQX.Helpers.update_catalog/2` helper to publish subsequent catalog objects.
- Integration E2E coverage that verifies publisher-provided catalog objects are relayed downstream.
### Changed
- **Breaking:** `MOQX.subscribe/3,4` and `MOQX.subscribe_track/3,4` now
return an opaque subscription handle (Rustler resource) instead of a
bare `make_ref()`. Pattern-matching and `is_reference/1` continue to
work; callers that depended on `make_ref()` semantics (serialization,
external storage) need to adapt.
- README and module docs now explicitly document the publisher-driven catalog flow used with moqtail-style relays.
## [0.3.0] - 2026-04-09
### Added
- `MOQX.subscribe_track/3,4` convenience API for catalog-driven subscriptions.
- Track metadata helpers on `MOQX.Catalog.Track`:
- `explicit_metadata/1`
- `inferred_metadata/1`
- `extra_metadata/1`
- `describe/1`
- New relay debug task `mix moqx.moqtail.demo` (catalog listing, interactive selection, runtime stats, catalog fallback behavior).
### Changed
- **Breaking** subscription contract now returns and propagates `subscription_ref`:
- `subscribe/3,4` now return `{:ok, sub_ref}`.
- subscription messages now include `sub_ref`.
- **Breaking** subscription lifecycle now emits `{:moqx_track_init, sub_ref, init_data, track_meta}` once per subscription.
- Integration/E2E tasks and tests updated for the new subscription correlation model.
- README/docs/examples updated for the new API and task naming.
## [0.2.1] - 2026-04-09
### Added
- `mix moqx.e2e.pubsub` task for end-to-end publisher/subscriber relay smoke testing.
- README examples for running relay E2E smoke tests, including Cloudflare draft-14 relay endpoints.
### Changed
- integration test tagging split to keep CI deterministic (`:integration`) while keeping live public relay coverage opt-in (`:public_relay_live`).
- `mix test.integration` now excludes `:public_relay_live` tests by default.
## [0.2.0] - 2026-04-08
First release of the current Rustler + `moqtail-rs` based Elixir MOQ client library line.
### Added
- CMSF catalog decoding and media track discovery via `MOQX.Catalog`
- raw fetch and catalog retrieval APIs for subscriber sessions
- validated relay-backed integration coverage for v14/v17 interop and live relay behavior
### Changed
- native integration migrated to `moqtail-rs`
- publish flow aligned around `PublishNamespace` behavior
- docs and examples updated for the current client contract
### Notes
- `moqx` on Hex had prior unrelated releases; `0.2.0` marks this project line as the canonical continuation.
## [0.1.0] - 2026-03-30
Initial public client release.
### Added
- explicit split-role client API with `connect_publisher/1,2`, `connect_subscriber/1,2`, and `connect/2`
- Quinn-backed transport support for `:auto`, `:raw_quic`, `:webtransport`, and `:websocket`
- secure-by-default client TLS controls with optional custom root CA support and explicit local-dev insecure mode
- relay-authenticated client flows using rooted `?jwt=...` URLs
- relay-backed integration coverage for transport parity, TLS behavior, and authenticated rooted-path flows
- CI split between fast checks and relay-backed integration coverage
### Changed
- public docs now freeze the supported `v0.1` client contract and async message/error expectations
- package metadata now includes Hex-oriented description, license, source, changelog, and docs configuration