domino
A high-performance Rust implementation of True Affected — semantic change detection for monorepos using the Oxc parser.
Installation #
domino is distributed as an npm package with native binaries for all major platforms.
npx (no install required)
The easiest way to use domino — no installation needed:
npx @front-ops/domino@latest affected
Global install
# npm
npm install -g @front-ops/domino
# yarn
yarn global add @front-ops/domino
# pnpm
pnpm add -g @front-ops/domino
Binary from source
If you prefer a standalone binary built from source:
git clone https://github.com/frontops-dev/domino.git
cd domino
cargo build --release
# Binary available at ./target/release/domino
Quick Start #
Run domino from the root of any supported monorepo. It compares against origin/main by default.
# Find all projects affected by your current changes
npx @front-ops/domino affected
# Output (JSON)
["packages/ui", "apps/web", "packages/utils"]
# Show all projects in the workspace
npx @front-ops/domino affected --all
# Generate an interactive HTML report
npx @front-ops/domino affected --report report.html
domino only reports projects that are truly affected — it inspects which symbols were actually modified and follows their references. Renaming an unused export won't mark every importer as affected.
CLI Reference #
Examples
# Compare against a different branch
domino affected --base origin/develop
# Compare specific commits (useful in CI patch pipelines)
domino affected --base abc123 --head def456
# Run on a different directory
domino affected --cwd /path/to/monorepo
# JSON output for CI scripts
domino affected --json
# Generate report and open it
domino affected --report report.html
# Open the report (macOS)
open report.html
# Open the report (Linux)
xdg-open report.html
# Open the report (Windows)
start report.html
Lockfile Change Detection #
domino automatically detects when your lockfile changes and identifies which projects are affected by dependency version updates. This works across all major package managers.
Strategies
Three strategies control how deeply domino traces dependency changes via
--lockfile-strategy:
nonedirect
default
fulldirect, but also traces the complete symbol reference chain to find all indirectly
affected projects.
Detection is transitive: if a deeply nested dependency changes, domino walks the reverse dependency graph to identify which direct dependency was updated, then traces which projects import it.
Usage
# Default: direct strategy
domino affected
# Disable lockfile detection
domino affected --lockfile-strategy none
# Full reference chain tracing
domino affected --lockfile-strategy full
Node.js API #
domino ships native N-API bindings so you can call it programmatically from Node.js without spawning a subprocess.
import { findAffected } from '@front-ops/domino'
const affected = await findAffected({
cwd: '/path/to/monorepo',
base: 'origin/main',
head: 'abc123', // optional — defaults to working tree
lockfileStrategy: 'direct', // 'none' | 'direct' | 'full'
})
console.log(affected)
// ['packages/ui', 'apps/web']
The Node.js API loads the correct pre-compiled binary for your platform automatically. No native compilation required at install time.
How It Works #
domino runs a six-step pipeline to compute the true set of affected projects:
Architecture #
domino is a Rust crate that also ships as an npm package via N-API bindings.
| Module | File | Responsibility |
|---|---|---|
| Core Algorithm | src/core.rs |
Orchestrates the 6-step detection pipeline |
| Git Integration | src/git.rs |
Parses diffs, extracts changed files and line ranges |
| Semantic Analyzer | src/semantic/analyzer.rs |
Parses all files with Oxc, builds import/export index |
| Reference Finder | src/semantic/reference_finder.rs |
Follows symbol references across files via oxc_resolver |
| Lockfile Analyzer | src/lockfile.rs |
Parses all lockfile formats, builds reverse dep graph |
| Workspace | src/workspace/ |
Project discovery for Nx, Turbo, and generic workspaces |
| CLI | src/cli.rs |
Command-line interface (clap) |
| N-API Bindings | src/lib.rs |
Node.js integration via napi-rs |
Key Technologies
| Crate | Purpose |
|---|---|
oxc_parser |
Fast JavaScript/TypeScript parsing |
oxc_semantic |
Semantic analysis and symbol tables |
oxc_resolver |
Module resolution (same engine as Rolldown and Nova) |
napi-rs |
Node.js native addon bindings |
serde / serde_yaml |
JSON/YAML lockfile parsing |
rustc-hash |
FxHashMap/FxHashSet for performance-critical lookups |
Performance #
domino is significantly faster than the original TypeScript implementation, primarily because Rust and Oxc avoid the overhead of the TypeScript compiler.
| Metric | TypeScript (traf) | Rust (domino) |
|---|---|---|
| Parser | ts-morph (TypeScript compiler) | Oxc parser |
| Parsing speed | Baseline | 3–5× faster |
| Memory usage | Baseline | ~50% less |
| Startup time | ~1–2 s (Node.js + deps) | <100 ms |
| Binary size | Requires Node.js runtime | Single binary |
| Lockfile detection | Not available | Built-in |
Internal optimisations
Several data-structure choices make the hot path as fast as possible:
-
Import index — maps
(source_file, symbol_name)→ importers for O(1) reverse lookup instead of scanning all files. -
Resolution cache — caches
(from_file, specifier)→ resolved path to avoid redundant module resolution. - FxHashMap / FxHashSet throughout the lockfile module for performance-critical lookups.
- Arena allocator — Oxc uses an arena allocator for AST nodes, reducing allocator pressure.
Workspace Support #
| Tool | Detection | Config |
|---|---|---|
| Nx | Detects nx.json |
Reads project.json per project |
| Turborepo | Detects turbo.json |
Reads workspace config from root package.json |
| npm / yarn / pnpm / bun | Reads workspaces in root package.json |
Glob-based package discovery |
Nx tsconfig alias mismatch — In Nx monorepos the project
name in project.json often differs from the npm package name or tsconfig path alias used
in imports (e.g. project ui-widgets vs import
@acme/shared-ui-widgets). domino accounts for this by checking both project names and
tsconfig path alias keys when deciding whether a specifier refers to a workspace package.
Comparison with traf #
domino is a drop-in replacement for traf, the original TypeScript implementation, with expanded capabilities.
| Feature | traf (TypeScript) | domino (Rust) |
|---|---|---|
| Semantic AST analysis | ✓ | ✓ |
| Cross-file reference tracking | ✓ | ✓ |
| Nx support | ✓ | ✓ |
| Turborepo support | ✓ | ✓ |
| Lockfile change detection | ✗ | ✓ (all PMs) |
| Node.js API | ✓ | ✓ (N-API) |
| Standalone binary | ✗ | ✓ |
| Performance | Baseline | 3–5× faster |
Development #
Build
# Debug build
cargo build
# Release build (optimised)
cargo build --release
# Node.js bindings
yarn build
Tests
# Unit tests
cargo test --lib
# Integration tests (must be serial — modify git state)
cargo test --test integration_test -- --test-threads=1
# JavaScript tests
yarn test
Lint & Format
cargo fmt --all
cargo clippy --all-targets --all-features
yarn lint
Integration tests modify git state and must run serially:
always pass --test-threads=1.