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