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

Rust Applications

Rust applications use the public riffdb-client-rust facade plus a generated module for the exact application lock. They do not depend on server, storage, policy, compiler, Tonic, Prost, or raw Protobuf crates.

For riffdb dev --run, the runner supplies the protected local values as both RIFFDB_ENDPOINT, RIFFDB_CREDENTIAL_FILE, and RIFFDB_DATABASE and the legacy positional arguments ENDPOINT CREDENTIAL_FILE DATABASE. Generated starters accept either form and fail closed if both are present but disagree. Ambient values cannot redirect the runner-owned child process.

For a remote daemon, construct the closed riffdb_config::TlsClientConfig and call RiffDbClient::connect_verified_tls. The client loads either an explicit protected CA root (TlsClientConfig::new) or the platform system CA bundle (TlsClientConfig::system_trust / from_pem_or_system), verifies the exact DNS/IP identity, and has no trust-all, cleartext-fallback, client-certificate, cipher-suite, or provider selector. Long-lived hosts can retain VerifiedTlsConnector; it atomically adopts only a complete valid trust-root replacement for new connections and retains the last valid snapshot otherwise. See Remote and Local Application Ingress.

StableApplicationClient::connect_verified_tls constructs the application-only facade directly from the same closed TLS configuration. The first-party Driver Host uses that path to centralize trust, pooling, retry, cancellation, uncertainty, and reactive resumption for target languages without exposing the kernel client.

Generated boundary

Generate from the reviewed lock:

riffdb application generate --locked

The generated module contains exact contract, query-module, command-plan, and artifact identities; checked input and result types; declared outcomes; and the operation-specific client methods. The stable facade owns transport validation, retry classification, fresh request IDs, and uncertain-outcome recovery.

When the compiler proves that a named page is fully served by an explicit covering index, the generated Rust operation accepts a compact positional wire result and decodes it directly into the same public result type. The field layout is compiled into the operation and is never a caller option. Any identity, field-order, cardinality, width, enum, bound, or value mismatch fails closed; the same decoder still accepts the legacy named-record response from an older compatible server.

A compiler-typed Cursor? generated parameter is Option<String>; None requests the first page and a present opaque cursor resumes that exact method. An exact Cursor remains a required String. Generated code routes the value through QueryOptions without encoding it as symbolic input and rejects a simultaneous low-level cursor option. Each method performs one bounded request; no generated helper walks pages for count, offset, filtering, or sorting.

let outcome = client
    .create_item(CreateItemInput {
        item_id,
        title: "Review contract".to_owned(),
    })
    .await?;

match outcome {
    CreateItemOutcome::Created(value) => println!("{}", value.item_id),
    CreateItemOutcome::AlreadyExists(_) => {}
}

The exact generated names come from your contract and lock. Treat a changed generated diff like an API change: review the source change and new lock before accepting it.

Generated role clients also embed their compiled role and lock identity constants, provide connect_env for one-line environment-backed setup with a default AttemptBudget::new(3), generate a fresh IdempotencyKey per command invocation when omitted, track causal read-your-writes watermarks by default on each client handle (causal() / with_default_read_after_commit), expose direct .outcome_tag(), .unwrap_found(), .or_none(), and .items() result helpers, emit contract enum types in query parameter and row structs, and provide bounded <query>_pages / <query>_items pagination helpers with a default 64-page ceiling.

Optional bounded application session

Rust generated clients expose open_bounded_session as an explicit alpha transport candidate. Unary application RPCs remain the default. The call takes the SHA-256 identity of the exact canonical riffdb.application.lock.json that produced the generated client; the generated module supplies its compiled contract and query-module identities itself.

let application_lock_hash = load_reviewed_application_lock_hash()?;
client.open_bounded_session(application_lock_hash).await?;
assert!(client.bounded_session_enabled());
// Before a controlled colocated-server shutdown:
client.close_bounded_session();

The session is transport multiplexing only. Every command and query is still independently authenticated, authorized, bounded, and executed through the ordinary application service. Stream order grants no transaction, snapshot, freshness, or command ordering: use generated *_after_commit methods for causal reads exactly as on unary transport. Command retries retain the original idempotency input, and stream loss retains the existing OutcomeUnknown classification when the durable result cannot be resolved.

One session is bound to one database, credential presentation, exact contract, and finite set of query modules. Metadata or application-identity drift fails closed. The alpha ceiling is 128 independently in-flight operations; response state, cancellation state, total lifetime work, and output stalls are bounded. There is no automatic fallback after a session has opened. If establishment is unavailable, the application may deliberately keep the unchanged unary client; diagnostics and benchmarks must report which transport was selected. close_bounded_session explicitly ends the request stream, releases pending operations under the ordinary uncertainty rules, and returns the facade to unary transport. Drop the client or call it before waiting for a colocated server’s graceful shutdown.

Operational applications can preflight the exact authorized server feature registry with StableApplicationClient::preflight_application_features before constructing the generated facade. An unavailable feature never authorizes a client-side scan or filter. See Generated Operational Queries.

Generated entity and outcome decoders consume every wire field exactly once. An optional<T> field maps a wire null to None and a present value to Some(T) through the same typed decoder used for required fields. This applies to command outcomes as well as query results; application code never probes or removes a field by its compiler ID.

Application Source V4 also generates typed durable event consumers and live named queries. Event deliveries retain exact lease evidence for generated acknowledge and negative-acknowledge methods. Live updates use a closed union; persist each applied cursor and clear retained results on Terminal. See Reactive Application Clients.

Generated command batch methods accept GeneratedBatchOptions concurrency from 1 through 384 and at most 4,096 inputs. The setting bounds independent in-flight items; each public ExecuteBatch request still carries at most 16 ordinary commands, with separate identities, outcomes, and recovery. At the maximum, Rust opens at most 24 bounded transport exchanges; it does not create one 384-command RPC or application transaction.

Retry rule

Use one caller-owned idempotency key for one logical command and retain it until the outcome is known. The stable client may retry only according to its bounded attempt policy. OutcomeUnknown means the server result could not be resolved; it does not mean the command failed.

Migration administration

Contract migration is a kernel administration surface, not generated application API. The stable facade exposes CheckContractMigration, ApplyContractMigration, generate_contract_migration_operation_id, and the three migration client methods. Construct one immutable submission and reuse it across bounded retries; only the outer request ID changes.

let check_id = generate_contract_migration_operation_id()?;
let check = CheckContractMigration::new(
    check_id,
    candidate_bundle.clone(),
    migration_bundle.clone(),
)?;
let checked = client
    .check_contract_migration_with_retry(&check, attempts, &metadata)
    .await?;

let apply_id = generate_contract_migration_operation_id()?;
let apply = ApplyContractMigration::new(
    apply_id,
    candidate_bundle,
    migration_bundle,
    reviewed_migration_hash,
)?;
let accepted = client
    .apply_contract_migration_with_retry(&apply, attempts, &metadata)
    .await?;

Check and apply require different operation IDs because their canonical input identities differ. After an uncertain apply, retain the apply ID and call get_contract_migration_operation after the selected database reopens. The current Go, TypeScript, and Python facades intentionally do not expose this administrative surface.

Public API

The built handbook publishes Rustdoc only for riffdb-client-rust. See the Rust API reference. Internal workspace crates are implementation boundaries, not supported application APIs.