This file provides guidance for AI coding agents when working with code in this repository.
MoQ (Media over QUIC) is a next-generation live media delivery protocol providing real-time latency at massive scale. It's a polyglot monorepo with Rust (server/native) and TypeScript/JavaScript (browser) implementations.
# Code quality and testing
just check # Run all tests and linting
just fix # Auto-fix linting issues
just build # Build all packagesIf just is unavailable, use cargo or bun directly.
The project contains multiple layers of protocols:
- quic - Does all the networking.
- web-transport - A small layer on top of QUIC/HTTP3 for browser support. Provided by the browser or the
web-transportcrates. - moq-lite - A generic pub/sub protocol on top of
web-transportimplemented by CDNs, splitting content into:- broadcast: a collection of tracks produced by a publisher
- track: a live stream of groups within a broadcast.
- group: a live stream of frames within a track, each delivered independently over a QUIC stream.
- frame: a sized payload of bytes.
- hang - Media-specific encoding/decoding on top of
moq-lite. Contains:- catalog: a JSON track containing a description of other tracks and their properties (for WebCodecs).
- container: each frame consists of a timestamp and codec bitstream
- watch/publish: dedicated packages for subscribing/publishing with optional SolidJS UI overlays
- application - Users building on top of
moq-liteorhang
Key architectural rule: The CDN/relay does not know anything about media. Anything in the moq layer should be generic, using rules on the wire on how to deliver content.
/rs/ # Rust crates
moq-lite/ # Core pub/sub protocol (published as moq-lite)
moq-native/ # QUIC/WebTransport connection helpers for native apps
moq-relay/ # Clusterable relay server (binary: moq-relay)
moq-token/ # JWT authentication library
moq-token-cli/ # JWT token CLI tool (binary: moq-token-cli)
moq-cli/ # CLI tool for media operations (binary: moq)
moq-clock/ # Clock synchronization example (binary: moq-clock)
moq-mux/ # Media muxers/demuxers (fMP4, CMAF, HLS)
hang/ # Media encoding/streaming (catalog/container format)
libmoq/ # C bindings (staticlib)
moq-boy/ # MoQ Boy emulator publisher (binary: moq-boy)
moq-gst/ # GStreamer plugin (moqsink/moqsrc elements)
/js/ # TypeScript/JavaScript packages
lite/ # Core protocol for browsers (published as @moq/lite)
signals/ # Reactive signals library (published as @moq/signals)
token/ # JWT token generation (published as @moq/token)
clock/ # Clock example (published as @moq/clock)
hang/ # Core media layer: catalog, container, support (published as @moq/hang)
ui-core/ # Shared UI components (published as @moq/ui-core)
watch/ # Watch/subscribe to streams + UI (published as @moq/watch)
publish/ # Publish media to streams + UI (published as @moq/publish)
moq-boy/ # MoQ Boy web viewer (published as @moq/boy)
/demo/ # Demos and test media
boy/ # MoQ Boy demo (ROM hosting, orchestration justfile)
relay/ # Relay server configs (relay.toml, root.toml, leaf*.toml)
pub/ # Media hosting (vid.moq.dev)
web/ # Web demo (watch/publish examples)
throttle/ # Network throttle script for testing
/doc/ # Documentation site (VitePress, deployed via Cloudflare)
/cdn/ # CDN infrastructure (Terraform)
- When adding new dependencies, always use the newest stable version available.
- The project uses
justas the task runner - checkjustfilefor all available commands - For Rust development, the workspace is configured in the root
Cargo.toml - For JS/TS development, bun workspaces are used with configuration in the root
package.json - Consult
doc/for documentation and the IETF datatracker for specification drafts when working on protocol-level code
When matching on Version enums, default to the newest draft behavior so future versions default forward. Explicitly list older versions:
// CORRECT: future versions get draft-17+ behavior
match version {
Version::Draft14 | Version::Draft15 | Version::Draft16 => { /* old behavior */ }
_ => { /* newest/draft-17 behavior */ }
}- Error handling: Use
thiserrorwith#[from]for library crates,anyhowfor binaries. Always add#[non_exhaustive]to publicthiserrorenums. - Use
anyhow::Context(.context("msg")) instead of.map_err(|_| anyhow::anyhow!("msg"))for error conversion
- TypeScript: Always use
bunfor all package management and script execution (not npm, yarn, or pnpm) - Common: Use
justfor common development tasks - Rust: Use
cargofor Rust-specific operations - Formatting/Linting: Biome for JS/TS formatting and linting
- UI: Solid.js for Web Components in
@moq/watch/uiand@moq/publish/ui - Builds: Nix flake for reproducible builds (optional)
- JS async patterns: Use
Effect.interval(),Effect.timer(), andEffect.event()helpers from@moq/signalsinstead of rawsetInterval,setTimeout,addEventListener. These handle cleanup automatically when the Effect is closed.
- Run
just checkto execute all tests and linting. - Run
just fixto automatically fix formating and easy things. - Rust tests are integrated within source files
- Async tests that sleep should call
tokio::time::pause()at the start to simulate time instantly
main: Stable branch for patch releases. Only non-breaking fixes and additions.dev: Development branch for breaking API changes. PRs with major API changes should targetdev.- When ready for a new minor/major release, merge
devintomain. cargo-semver-checksenforces this on PRs tomain.
When making changes to the codebase:
- Make your code changes
- Run
just fixto auto-format and fix linting issues - Run
just checkto verify everything passes - Update relevant documentation (CLAUDE.md, doc/, README) when making major changes
- Add unit tests for any changes that are easy enough to test
- Commit and push changes