falkordb 0.10.3

A FalkorDB Rust client
Documentation
# Justfile — dev-cycle automation for falkordb-rs.
#
# Run `just` (or `just --list`) to see every available recipe.
# Most test/coverage/bench recipes need a running FalkorDB server; use the
# `db-*` recipes (or the `*-local` wrappers) to manage one via Docker.

set shell := ["bash", "-uc"]

# --- Configuration (override on the CLI, e.g. `just port=6380 test`) ---------

# Host/port the tests, benches and DB helpers target.
host := env_var_or_default("FALKORDB_HOST", "127.0.0.1")
port := env_var_or_default("FALKORDB_PORT", "6379")

# Docker image and container name used by the `db-*` helpers.
image := "falkordb/falkordb:edge"
container := "falkordb-rs-dev"

# Feature set exercised by the full local suite (mirrors the coverage CI job).
features := "tokio,embedded,serde,tracing,metrics"

# Default recipe: list everything.
default:
    @just --list

# === Format ==================================================================

# Format all code in place.
fmt:
    cargo fmt --all

# Check formatting without modifying files (CI gate).
fmt-check:
    cargo fmt --all --check

# === Lint ====================================================================

# Clippy over the whole workspace (matches the `check-clippy` CI gate).
clippy:
    cargo clippy --all

# Strict clippy over all targets and dev features, warnings denied.
clippy-all:
    cargo clippy --all-targets --features {{features}} -- -D warnings

# Dependency/license/advisory audit (matches the `check-deny` CI gate).
deny:
    cargo deny check bans licenses sources

# === Build ===================================================================

# Default build (matches the `check-build` CI gate).
build:
    cargo build

# Build every target with the dev feature set.
build-all:
    cargo build --all-targets --features {{features}}

# Compile every example under the features it needs. CI gate; no server needed. Examples aren't
# built by `just build`, so this is what keeps them compiling. The TLS example is built in a
# separate pass with the sync `rustls` backend, which can't coexist with `tokio` in one build.
build-examples:
    cargo build --examples --features {{features}}
    cargo build --example tls --features rustls

# Build the API docs (matches the `check-doc` CI gate).
doc:
    cargo doc --all

# Build docs (no deps) and open them in a browser.
doc-open:
    cargo doc --all --no-deps --open

# Run documentation tests — the crate-level `//!` docs in src/lib.rs (the source the README is
# generated from) and every doc-comment code block — with the dev feature set. No server needed
# (blocks are `no_run`/pure).
doctest:
    cargo test --doc --features {{features}}

# === Test ====================================================================
# These need a reachable FalkorDB server. Use `just test-local` to manage one.

# Run the full unit + integration suite (nextest) against a running server.
test:
    FALKORDB_HOST={{host}} FALKORDB_PORT={{port}} \
        cargo nextest run --all --features {{features}}

# Run only the cross-strategy parity/regression suite.
test-parity:
    FALKORDB_HOST={{host}} FALKORDB_PORT={{port}} \
        cargo nextest run --features {{features}} --test multiplexing_parity

# Run only the embedded-server integration suite.
test-embedded:
    FALKORDB_HOST={{host}} FALKORDB_PORT={{port}} \
        cargo nextest run --features {{features}} --test embedded_integration

# Validate the `embedded-bundle` feature (build-time module embed) hermetically:
# build the lib, example and tests with a fixture `.so` via FALKORDB_EMBEDDED_MODULE_PATH
# (no network, no live server) and run the bundle unit tests. This exercises build.rs,
# the feature wiring, `include_bytes!` and runtime extraction. CI runs this same recipe.
check-embedded-bundle:
    #!/usr/bin/env bash
    set -euo pipefail
    fixture_dir="$(mktemp -d)"
    trap 'rm -rf "$fixture_dir"' EXIT
    fixture="$fixture_dir/falkordb-fixture.so"
    # Deterministic placeholder bytes; the bundle path never loads it into redis.
    printf 'falkordb-embedded-bundle-fixture-module\n' > "$fixture"
    export FALKORDB_EMBEDDED_MODULE_PATH="$fixture"
    cargo build --features embedded-bundle
    cargo build --features embedded-bundle --example embedded_usage
    cargo test --features embedded-bundle --lib embedded::bundle

# Run the integration_tests binary with the given cargo args, exactly as the
# integration CI jobs do. Examples: `just integration`,
# `just integration --features tokio`, `just integration --all-features`.
integration *args:
    FALKORDB_HOST={{host}} FALKORDB_PORT={{port}} \
        cargo test --test integration_tests {{args}} --verbose

# Run a single test by name filter, e.g. `just test-one test_borrow_connection`.
test-one filter:
    FALKORDB_HOST={{host}} FALKORDB_PORT={{port}} \
        cargo nextest run --all --features {{features}} {{filter}}

# Run only the `serde` property-based tests (no server); `cases` sets PROPTEST_CASES (default 256).
proptest cases="256":
    PROPTEST_CASES={{cases}} cargo nextest run --lib --features serde proptest

# Spin up a server, populate the fixture, run the full suite, then tear it down.
test-local: db-up db-populate
    @just test || (just db-down && exit 1)
    @just db-down

# === Coverage ================================================================

# Generate Codecov JSON coverage (matches the `coverage` CI job).
#
# Two merged passes so every feature's lines are measured: (1) the full suite
# against a live server with the dev features, and (2) the build-time
# `embedded-bundle` path, hermetically (a fixture `.so` via
# FALKORDB_EMBEDDED_MODULE_PATH — no server, no network). `embedded-bundle`
# bundles `bundle.rs` and the bundle-mode resolution branch that the dev feature
# set compiles out.
coverage:
    #!/usr/bin/env bash
    set -euo pipefail
    fixture_dir="$(mktemp -d)"
    trap 'rm -rf "$fixture_dir"' EXIT
    printf 'falkordb-embedded-bundle-fixture-module\n' > "$fixture_dir/falkordb-fixture.so"
    cargo llvm-cov clean --workspace
    FALKORDB_HOST={{host}} FALKORDB_PORT={{port}} \
        cargo llvm-cov nextest --no-report --all --features {{features}}
    FALKORDB_EMBEDDED_MODULE_PATH="$fixture_dir/falkordb-fixture.so" \
        cargo llvm-cov nextest --no-report --lib --features embedded-bundle
    cargo llvm-cov report --codecov --output-path codecov.json

# Generate an HTML coverage report and open it.
coverage-html:
    FALKORDB_HOST={{host}} FALKORDB_PORT={{port}} \
        cargo llvm-cov nextest --all --features {{features}} --open

# Spin up a server, populate the fixture, collect coverage, then tear it down.
coverage-local: db-up db-populate
    @just coverage || (just db-down && exit 1)
    @just db-down

# === Benchmarks ==============================================================
# Benchmarks need the `tokio` feature and a reachable server. They are
# developer/PR tools, not a CI gate. See the README "Benchmarks" section.

# Throughput benchmark: Pooled vs Multiplexed across counts/concurrency.
bench:
    FALKORDB_HOST={{host}} FALKORDB_PORT={{port}} \
        cargo bench --features tokio --bench async_strategies

# Run a single throughput case, e.g. `just bench-one 'async_read_throughput/multiplexed_8/8'`.
bench-one filter:
    FALKORDB_HOST={{host}} FALKORDB_PORT={{port}} \
        cargo bench --features tokio --bench async_strategies -- '{{filter}}'

# Peak memory (RSS) and CPU-time benchmark per strategy.
bench-resource:
    FALKORDB_HOST={{host}} FALKORDB_PORT={{port}} \
        cargo bench --features tokio --bench resource_usage

# Run both benchmark harnesses.
bench-all: bench bench-resource

# Regenerate blog/data/benchmarks.toml from a focused async_strategies run (a dated SAMPLE RUN
# with provenance metadata rendered by the blog). Needs a reachable server. Not a CI gate —
# criterion numbers vary by machine/run. Use `just bench-export-local` to manage Docker.
bench-export:
    FALKORDB_HOST={{host}} FALKORDB_PORT={{port}} ./scripts/bench-export.sh

# Spin up a server, regenerate the blog benchmark numbers, then tear it down.
bench-export-local: db-up
    @just bench-export || (just db-down && exit 1)
    @just db-down

# Spin up a server, run all benchmarks, then tear it down.
bench-local: db-up
    @just bench-all || (just db-down && exit 1)
    @just db-down

# === Spellcheck ==============================================================

# Spellcheck the Markdown docs (CI gate). Requires `pyspelling` and `aspell` locally.
spellcheck:
    pyspelling -c .github/spellcheck-settings.yml -n Markdown

# Spellcheck the blog posts (CI gate). Same wordlist; a context filter strips TOML front matter
# and Zola shortcodes so only prose is checked. Requires `pyspelling` and `aspell` locally.
spellcheck-blog:
    pyspelling -c .github/spellcheck-settings.yml -n Blog

# Spellcheck a pull-request title exactly as the Spellcheck CI gate does. Catches
# technical words (e.g. type names) that release-plz would later copy verbatim into
# the changelog and fail the release PR. Set PR_TITLE first, e.g.
# `PR_TITLE='fix: handle ConnectionDown' just spellcheck-pr-title`.
spellcheck-pr-title:
    printf '# %s\n' "${PR_TITLE:?set PR_TITLE to the pull-request title}" > .pr-title.md && pyspelling -c .github/spellcheck-settings.yml -n PRTitle && rm -f .pr-title.md || { rm -f .pr-title.md; exit 1; }

# === PR title (Conventional Commits) =========================================

# Conventional-Commit types accepted in a PR title. The release-triggering subset
# `feat|fix|docs` mirrors release-plz's `release_commits` in release-plz.toml (so a fix/feat/docs
# PR actually cuts a release); the remaining types are valid but ride along with the next release.
pr_title_pattern := '^(feat|fix|docs|ci|chore|refactor|perf|test|build|style|revert)(\([^()[:space:]]+\))?!?: .*[^[:space:]]'

# Validate a PR title as a Conventional Commit, exactly as the `PR title format` CI gate does.
# The title becomes the squash-merge subject and its prefix drives the release, so a malformed
# title silently skips it — as happened with PR #297's "Fix spurious connection errors: ...",
# which matched none of release-plz's release_commits and cut no release. Set PR_TITLE first,
# e.g. `PR_TITLE='fix: handle ConnectionDown' just check-pr-title`.
check-pr-title: test-pr-title
    #!/usr/bin/env bash
    set -euo pipefail
    title="${PR_TITLE:?set PR_TITLE to the pull-request title}"
    if ! grep -Eq '{{pr_title_pattern}}' <<<"$title"; then
        printf '::error::PR title is not a Conventional Commit: %s\n' "$title" >&2
        printf 'Expected "<type>[(scope)][!]: <subject>", where <type> is one of:\n' >&2
        printf '  feat, fix, docs                                       (trigger a release via release-plz)\n' >&2
        printf '  ci, chore, refactor, perf, test, build, style, revert (ride along with the next release)\n' >&2
        printf 'e.g. "fix: override redis-rs default response timeout"\n' >&2
        exit 1
    fi
    printf 'PR title OK: %s\n' "$title"

# Self-test the check-pr-title pattern against known-good/known-bad titles (incl. PR #297's subject
# that slipped through and skipped a release). Needs no PR_TITLE; runs as part of check-pr-title.
test-pr-title:
    #!/usr/bin/env bash
    set -euo pipefail
    pattern='{{pr_title_pattern}}'
    good=(
        "fix: derive Clone for FalkorAsyncClient"
        "feat!: make replica routing for read-only queries opt-in"
        "feat(blog): add diagram zoom, a livelier theme"
        "docs: generate README from crate docs via cargo-rdme"
        "ci: compile README doctests and examples in CI"
        "build(deps): bump codeql-action to 4.37.0 and regex to 1.13.0"
        "chore: release v0.10.2"
    )
    bad=(
        "Fix spurious connection errors: override redis-rs 1.x default 500ms response timeout"
        "Bump redis from 1.2.0 to 1.3.0"
        "update the readme"
        "fix:no space after the colon"
        "feature: not a recognized type"
        "fix( ): whitespace-only scope"
        "fix(): empty scope"
        "feat(a b): whitespace inside the scope"
        "fix:   "
    )
    rc=0
    for t in "${good[@]}"; do
        if ! grep -Eq "$pattern" <<<"$t"; then printf 'want PASS, got FAIL: %s\n' "$t" >&2; rc=1; fi
    done
    for t in "${bad[@]}"; do
        if grep -Eq "$pattern" <<<"$t"; then printf 'want FAIL, got PASS: %s\n' "$t" >&2; rc=1; fi
    done
    if [ "$rc" -ne 0 ]; then exit 1; fi
    printf 'test-pr-title: all %d cases passed\n' "$(( ${#good[@]} + ${#bad[@]} ))"

# === llms.txt (AI-readable API surface) ======================================

# Regenerate the repo-root llms.txt from docs/llms.template.md + the public API parsed
# from src/lib.rs (via the standalone tools/llms-gen crate; stable toolchain, deterministic).
llms:
    cargo run --quiet --locked --manifest-path tools/llms-gen/Cargo.toml

# Unit-test the llms.txt generator (parsing, feature detection, marker splicing).
test-llms:
    cargo test --quiet --locked --manifest-path tools/llms-gen/Cargo.toml

# CI drift gate: run the generator's tests, regenerate llms.txt, and fail if the committed
# copy is stale or missing. If this fails you changed the public API — run `just llms` and
# commit the updated llms.txt.
check-llms: test-llms llms
    git ls-files --error-unmatch llms.txt > /dev/null
    git diff --exit-code -- llms.txt

# === README (generated from the crate docs) ==================================

# Regenerate README.md from the crate-level `//!` docs in src/lib.rs via cargo-rdme: it strips
# hidden `#` doctest lines and intra-doc links and annotates code blocks as `rust` so GitHub
# highlights them. Edit the docs in src/lib.rs (not README.md), then run this and commit the result.
# Install the pinned tool with `cargo install cargo-rdme --version 2.0.0` (must match CI; see
# scripts/install-cargo-rdme.sh).
readme:
    cargo rdme --force

# CI drift gate: regenerate README.md and fail if the committed copy is stale. If this fails you
# edited the crate docs — run `just readme` and commit README.md.
check-readme:
    cargo rdme --check

# === Blog (Zola "Dev Log" -> GitHub Pages) ===================================
# A static site under blog/, deployed by .github/workflows/pages.yml. Every Rust snippet in a
# post is a real examples/*.rs / benches/*.rs file (compiled by `just build-examples` / the
# benches build); `blog-sync` copies those byte-for-byte into blog/snippets/ so what the reader
# sees is exactly what CI compiled. Keep `zola_version` in sync with the CI install step in
# pr-checks.yml and pages.yml. Install locally from https://github.com/getzola/zola/releases.
zola_version := "0.22.1"

# Copy the canonical, CI-compiled example & bench sources into blog/snippets/ (gitignored).
blog-sync:
    ./scripts/blog-sync.sh

# Build the static site into blog/public (syncs snippets first).
blog-build: blog-sync
    cd blog && zola build

# Serve the blog locally with live reload at http://127.0.0.1:1111 (syncs snippets first).
blog-serve: blog-sync
    cd blog && zola serve

# CI gate: sync snippets then build the site, failing on bad templates, missing snippets or
# broken internal links. Mirrors the `check-blog` job in pr-checks.yml.
blog-check: blog-sync
    cd blog && zola build

# === Docker / FalkorDB lifecycle =============================================

# Start a FalkorDB server in Docker on the configured port.
db-up:
    docker rm -f {{container}} >/dev/null 2>&1 || true
    docker run -d --name {{container}} -p {{port}}:6379 {{image}} >/dev/null
    @echo "Waiting for FalkorDB on {{host}}:{{port}}..."
    @for i in $(seq 1 30); do \
        if docker exec {{container}} redis-cli ping >/dev/null 2>&1; then \
            echo "FalkorDB is ready."; exit 0; \
        fi; sleep 1; \
    done; echo "FalkorDB did not become ready in time" >&2; exit 1

# Stop and remove the FalkorDB container.
db-down:
    docker rm -f {{container}} >/dev/null 2>&1 || true

# Populate the IMDB fixture graph the lib tests rely on (needs python + falkordb).
db-populate:
    FALKORDB_HOST={{host}} FALKORDB_PORT={{port}} python3 resources/populate_graph.py

# === Aggregates ==============================================================

# Fast pre-commit loop: format, lint and build (no server required).
check: fmt clippy build

# Run every required CI gate locally (no server required).
ci: fmt-check clippy build doc doctest build-examples deny check-llms check-readme

# Full post-task gate (no server required): strict clippy-all plus every CI gate.
# Must be green before a task is declared done.
done:
    ./scripts/post-checks.sh

# Full local validation: CI gates plus the server-backed test suite.
verify: ci test-local

# === Housekeeping ============================================================

# Remove build artifacts and the generated coverage file.
clean:
    cargo clean
    rm -f codecov.json