Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Build a symbolic application in ten minutes

RiffDB applications read with RiffQL and mutate with compiled contract commands. The supported kernel gRPC protocol remains available for low-level tools and conformance tests, but application code does not need entity IDs, field IDs, index IDs, masks, encoded keys, or protobuf record construction.

From the repository root, start the complete disposable TicketDesk environment:

cargo run -p riffdb-cli -- dev --seed

riffdb dev:

  1. starts riffdbd on a random loopback port;
  2. bootstraps an operator through the production capability path;
  3. deploys the TicketDesk contract and immutable query module;
  4. creates a named-only capability from the default ticketdesk-application role preset;
  5. creates 276 rows through compiled commands; and
  6. prints the endpoint and protected application credential path.

Nothing in this path bypasses authorization, command semantics, audit, the commit coordinator, or durable redb storage. The disposable directory and credentials are removed when the process exits.

Use --watch to redeploy changed contract and .riffq sources. Query-only changes increment the query-module version without requiring client regeneration:

cargo run -p riffdb-cli -- dev --watch

Use --run instead when the application repository should execute against the disposable server:

riffdb dev --seed --run

The workflow accepts exactly one repository runner (Cargo.toml, go.mod, package.json, or pyproject.toml), supplies only the protected language-appropriate application connection, streams its stdout, and tears down the server when the runner exits or is interrupted. Go and TypeScript use the private first-party driver-host socket; no runner receives operator or kernel authority.

For a Go library module whose executable is a nested main package, select it explicitly with --go-runner-package cmd/server. The path is repository-confined and is validated before the local driver or credentials are created; the module-root default remains . after the runner detects a Go repository. When the option is omitted, the CLI forwards no Go-only configuration: the runner detects package.json applications and executes npm run build followed by npm run start as usual.

When the selected development role declares "tenant_scope": "tenant", riffdb dev binds the disposable role credential to --tenant <TENANT_ID> (defaulting to "dev-tenant" or RIFFDB_TENANT when --tenant is omitted), records the bound tenant in .riffdb/dev/session.json, and exports RIFFDB_TENANT to child application runners and generated SDK transports.

When authoring new contract commands or adding .riffq files under riffdb/queries/, pass --sync-manifest to riffdb application check, riffdb application lock, riffdb generate, or riffdb dev to discover unregistered riffdb/queries/*.riffq files and grant newly compiled commands and queries to the sole application role in canonical sorted order before refreshing the lock and generated clients. Multi-role manifests require --sync-role <ROLE> alongside --sync-manifest and fail closed with RDB-AR012 when --sync-role is omitted.

Use --role ticketdesk-agent for the separately named agent allowlist. It still receives no ad-hoc query or kernel authority. Use --role ticketdesk-kernel only for low-level diagnosis; it receives no application command or RiffQL authority and cannot seed. There is no combined preset. See Safe application profiles.

Use a directory of JSONL command batches with bounded concurrency. Each file contains complete inputs for one exact command and is named NN-CommandName.jsonl so dependency stages sort explicitly:

cargo run -p riffdb-cli -- dev \
  --seed-dir ./my-seed \
  --seed-concurrency 4

The development concurrency bound is 1..8. Every line is submitted through the resumable riffdb command batch scheduler as an ordinary symbolic command; seed files are not storage imports or collection transactions. See Resumable command batches.

Write a read

Save a bounded query as queries/my_page.riffq:

query MyPage(
    $organization_id: Organization.organization_id,
    $ticket_id: Ticket.ticket_id,
) {
    one ticket from Ticket
        where organization_id == $organization_id
            && ticket_id == $ticket_id
        else NotFound

    return Found {
        ticket: ticket {
            ticket_id
            title
            status
        }
    }

    outcomes Found | NotFound
}

Check and explain before execution:

riffdb query check queries/my_page.riffq
riffdb query explain queries/my_page.riffq

Deploy a directory and run by name:

riffdb query deploy queries/ \
  --module-name my_app \
  --module-version 1 \
  --contract-lineage TicketDesk \
  --contract-version 1

riffdb query run-named MyPage \
  --contract-lineage TicketDesk \
  --contract-version 1 \
  --parameters parameters.json

While riffdb dev is running in a workspace, it writes bounded local session metadata to .riffdb/dev/session.json and .riffdb/dev/profile.credential (mode 0600) and removes them on exit. Read-only inspection commands (riffdb query run, riffdb query run-named, and riffdb entity get) automatically discover --endpoint, --database, --credential-file, --contract-lineage, and --contract-version from .riffdb/dev/session.json when those flags are omitted, and support --inline <RIFFQL>, repeatable --param <KEY=VALUE>, and --params-json <JSON_OBJECT>:

riffdb query run \
  --inline 'query Adhoc($ticket_id: Ticket.ticket_id) { one t from Ticket where ticket_id == $ticket_id else Missing return Found { ticket: t { ticket_id title } } outcomes Found | Missing }' \
  --param 'ticket_id={"$uuid":"535bbe7a-3f53-4792-97bb-5d9a692be0ef"}'

The server resolves symbols, type-checks parameters, derives the complete authorization request, verifies one partition and bounded access, chooses the index plan, executes the page in one authoritative snapshot, and returns name-addressed typed records.

Write a mutation

Application writes remain compiled commands:

riffdb command run CreateTicket --input create-ticket.json

The input object uses contract field names. Idempotency, declared outcomes, provenance, events, and the commit record retain their existing atomic semantics.

Generated application clients

Stable named operations can generate Rust, Go, TypeScript, Python, and MCP artifacts:

./scripts/generate-query-clients
./scripts/generate-query-clients --check

Generated query requests pin the exact contract lineage, contract version, contract bundle hash, query-module hash, and query name. The facade owns parameter encoding, result decoding, typed outcomes, cursors, read fences, public errors, and command uncertainty recovery. A command retry retains its original idempotency identity and uses outcome resolution when the commit result is uncertain.

Rust application code calls only generated names and types:

use riffdb_client_rust::{AttemptBudget, CallMetadata, QueryOptions};
use riffdb_ticketdesk::{TicketDeskClient, TicketPageParams};

async fn example(
    application: riffdb_client_rust::StableApplicationClient,
    metadata: CallMetadata,
) -> Result<(), riffdb_client_rust::ApplicationClientError> {
let mut db = TicketDeskClient::new(
    application,
    metadata,
    AttemptBudget::new(3).expect("positive configured attempt budget"),
);
let page = db
    .ticket_page_with_options(
        TicketPageParams {
            organization_id: "c93bf186-4901-4cb6-8af2-fec65eb928e6".to_owned(),
            ticket_id: "535bbe7a-3f53-4792-97bb-5d9a692be0ef".to_owned(),
            comments_after: None,
        },
        QueryOptions::new()
            .read_after_commit(1843)
            .at_least_admission_head(),
    )
    .await?;
let _ = page;
Ok(())
}

read_after_commit is the exact causal choice when the caller has a commit sequence. at_least_admission_head is the stronger server-resolved choice when the caller has only a consistency preference. If both are present, RiffDB uses the greater floor. The latter may return a typed freshness failure rather than serve a provider epoch behind the head observed after admission.

TypeScript exposes the same named operations and observations, including bigint application heads and commit sequences and the closed consistency: "admissionHead" query option. MCP query and command schemas are generated from the same registry and carry the same exact identities.

Code generation is optional for exploration: ad-hoc RiffQL and named CLI/MCP execution remain first-class. It is the normal boundary for stable application code. See Application manifest.

See RiffQL language, planning, and query modules.