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:
- starts
riffdbdon a random loopback port; - bootstraps an operator through the production capability path;
- deploys the TicketDesk contract and immutable query module;
- creates a named-only capability from the default
ticketdesk-applicationrole preset; - creates 276 rows through compiled commands; and
- 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.