Development

Copy Markdown

Back to the README

Use the toolchain in .tool-versions for development. RustQ generation requires Elixir 1.19+ and Rust 1.91+; consumers do not need RustQ. Generated Rust and Elixir stubs are checked in, so normal dependency builds do not run the generator.

Native boundary ownership

SourceOwns
codegen/rewrite.exsTyped NIF declarations, codecs, resource registration
lib/laughter/rewriter/content.ex + codegen/content.exsContent-operation list, builders, native dispatch
codegen/events.exsTyped worker-event and envelope encoders
codegen/nif.exsElixir stubs derived from NIF declarations and legacy Rust signatures

Do not edit native/laughter_nif/src/generated_*.rs or lib/laughter/nif/generated_stubs.ex by hand. Legacy export policy lists names, not arities; RustQ reads signatures and excludes the injected Env argument. Ownership, synchronization, buffering, and cancellation remain explicit Rust.

mix deps.get
mix rustq.gen
mix ci
mix docs --warnings-as-errors
cargo clippy --manifest-path native/laughter_nif/Cargo.toml --locked -- -D warnings

mix ci checks compilation, tests, generated-source freshness, Credo, Dialyzer, and duplication. See test/README.md in the repository for suite organization.

Package checks

From a repository checkout:

mix hex.build --output /tmp/laughter.tar
scripts/check-package.sh /tmp/laughter.tar

The script extracts the archive into a fresh consumer project, resolves consumer dependencies, compiles from scratch, and exercises parsing, callbacks, plans, streams, and sessions without RustQ. Its fixture is in test/fixtures/package_consumer/; the fixture and script are repository tooling, not part of the published package. CI is configured to test the same archive on Elixir 1.15/OTP 26 and Elixir 1.19/OTP 27.

Benchmarks and diagnostics

mix run bench/rewrite.exs
JOBS=2000 ROUNDS=3 ELEMENTS=100 CONCURRENCY=1,8,32 mix run bench/load.exs

See benchmark methodology and local results.

The internal Laughter.Nif.rewrite_stats() reports tracked dynamic worker lifetimes, bounded output buffers, and their reserved capacity. It excludes legacy workers, parser/input/BEAM memory, and whole-document output allocations. Concurrent snapshots are eventually consistent, not an atomic memory profile. Lifecycle tests wait for all three counters to return to zero.