Documentation / Operations

Command-line client

Queries, mutations and streams.

ROM 0.1.0 · SOURCE CANDIDATE Markdown

rom-cli builds the rom executable. It uses the generic rom-http protocol. To add a Resource, register it on the server. No CLI command or client schema is needed. The CLI does not open application databases directly. Studio and login/session ceremonies are outside this release.

Build and check from the repository with Rust 1.99:

sh
cargo build -p rom-cli
./crates/rom-cli/verify
./target/debug/rom --help

If you set CARGO_TARGET_DIR, the executable is under that target instead. The source distribution includes the optional CLI alongside the libraries. To run the remaining examples from the repository without installing the binary, define:

sh
rom() { cargo run --quiet --locked -p rom-cli -- "$@"; }

This respects CARGO_TARGET_DIR. Alternatively, run cargo install --path crates/rom-cli --locked. Make sure that Cargo's bin directory is on your PATH.

Connect and inspect

Start the workshop in another terminal:

sh
./demo/run serve sqlite ./demo.db 8080
# redb uses the identical declarations and CLI protocol:
# ./demo/run serve redb ./demo.redb.db 8080

The workshop's Demo local header is a public synthetic marker, not a real login or a private credential. Create a local fixture file, then run:

sh
printf '%s\n' 'Demo local' > /tmp/rom-demo-auth
rom --endpoint http://127.0.0.1:8080 --auth-file /tmp/rom-demo-auth discover
rom --endpoint http://127.0.0.1:8080 --auth-file /tmp/rom-demo-auth discover tasks
rom --endpoint http://127.0.0.1:8080 --auth-file /tmp/rom-demo-auth query tasks

--endpoint is always explicit. A base path is preserved, so https://example.org/rom-api sends reads to /rom-api/read. HTTPS verifies the server certificate. Plain HTTP is accepted only for numeric loopback addresses (including IPv6), not hostnames. URL credentials, query strings and fragments are rejected. Redirects, proxies and automatic network retries are disabled.

--auth-file is optional. The host decides whether a request without credentials has authority. When used, the file contains the complete Authorization value (e.g. its host-defined scheme and credential), not a JSON document. The CLI removes a single final LF or CRLF. It rejects embedded line breaks, empty values and files larger than 8 KiB. Credentials never enter command-line flags or diagnostic messages. The CLI does not save, refresh or acquire credentials. Protect the input file according to the host's credential policy.

Discovery returns only deliberately disclosed metadata. It does not grant read, query, mutation or action access. discover KIND selects from that authorized snapshot; unavailable kinds are not distinguished from hidden kinds. Known-kind commands work without discovery. Custom action input remains opaque. Consult the application's action contract. Let its registered codec validate the value.

Commands and JSON input

Global connection/output options can appear before or after a command. --output human is the default and prints readable, indented JSON with IDs and revisions intact. --output json prints compact JSON: one value for a finite result, one complete value per line for live/journal frames. Human and machine output both escape terminal control characters. Diagnostics go to stderr.

CommandInput and meaning
discover [KIND]Authorized metadata snapshot or one disclosed kind.
read KIND IDCurrent projected Resource, including its revision.
query KIND [--query-file FILE]All by default, or a bounded equality/conjunction query.
create KIND ID --idempotency KEY --input-file FILEComplete Resource value; expected revision is null.
replace KIND ID --expected REV --idempotency KEY --input-file FILEComplete replacement, not a partial patch.
patch KIND ID --expected REV --idempotency KEY --input-file FILEExplicit set/remove field operations.
delete KIND ID --expected REV --idempotency KEYDelete at an explicit revision.
action KIND ID NAME --expected REV --idempotency KEY --input-file FILERegistered custom action, separate from standard operations.
invoke --request-file FILEExact generic Invocation envelope, including its original identity and expected revision.
live KIND [--query-file FILE]Whole authorized current snapshots; no replay cursor.
journal KIND [--after-file FILE]One bounded journal batch and its cursor.
journal-head KINDCurrent cursor; explicitly establishes a new checkpoint.
subscribe KIND [--after-file FILE]Ordered journal batches with their cursors.

Every JSON source flag accepts - for stdin. Requests and input files are limited to 64 KiB, with duplicate object keys rejected at every nesting level before conversion. Unknown request/query/invocation properties are rejected. Values retain exact signed/unsigned 64-bit integers. If downstream JSON tools will replay a request, they must also preserve those integers.

For example, create one Resource:

sh
rom --endpoint http://127.0.0.1:8080 --auth-file /tmp/rom-demo-auth \
  create tasks first --idempotency first-create --input-file - <<'JSON'
{"title":"Draft workshop","done":false}
JSON

Then patch it using the revision from the response:

sh
rom --endpoint http://127.0.0.1:8080 --auth-file /tmp/rom-demo-auth \
  patch tasks first --expected 1 --idempotency first-rename --input-file - <<'JSON'
{"title":{"op":"set","value":"Review workshop"}}
JSON

Omitted patch entries remain unchanged. {"op":"set","value":null} writes an explicit null where supported; {"op":"remove"} removes an optional field. False, zero and empty strings are values. Projected reads can omit protected fields, so do not convert a projected read blindly into a complete replacement.

A query file can contain:

json
{"filters":[{"field":"done","value":false}],"after_id":null,"limit":20}

Predicates are conjunctive equality or scalar comparisons. Optional-field absence uses {"field":"memo","value":null,"absent":true}. Query files can also supply comparisons, order and a client after anchor; see shared query semantics. after_id remains available for ID-only ordering. Moving pages retain no snapshot and provide no total count. Collection snapshot bounds still apply with a small page limit.

Mutations and uncertain outcomes

Supply an explicit idempotency key for each intended mutation. Non-create mutations also require the expected revision, including in a raw invocation. Keep the original request when reconciling an unavailable result. A repeat request with the same principal-scoped identity and semantic input recovers its authorized durable receipt. Changed input with the same identity is an identity mismatch. After a conflict, a deliberately revised mutation is a new intent and needs a new key.

Every failed remote mutation response is treated as unresolved. HTTP error categories do not identify the execution phase. Even conflict, validation, size-limit, denial or unavailable-resource errors can arise from a host authority check after commit. The CLI shows the safe category without claiming rollback. A malformed, oversized or lost success response is unresolved too. A local input error before submission is distinct.

Replay only as the same principal, with the original key, expected revision and semantic input. The CLI cannot certify that a replacement credential still represents the original principal. It never retries automatically or stores pending requests/credentials. invoke --request-file accepts the existing wire Invocation shown in HTTP documentation; it does not invent another mutation identity.

Streams and process behavior

live emits full snapshots, initially and when state changes; updates can coalesce. subscribe emits whole journal batches, including empty batches whose cursors advance. Before you consume another frame, flush each complete output frame. A successful pipe write does not prove the receiving application processed or persisted it. Persist a journal cursor only after your application has processed its batch.

Finite response bodies and individual SSE frames are limited to 2 MiB. Parsing handles arbitrary chunk boundaries, split UTF-8, CRLF, comments and multiline data. Response identities must match the requested kind/ID. Journal batches must preserve the supplied and preceding cursor generation, kind and forward position. Events inside each cursor range must be ordered. Malformed/oversized/incomplete streams stop visibly. There is no automatic reconnect, unbounded frame queue or implicit history reset. Keepalive bytes count as connection activity, not application progress.

Timeout optionDefaultAccepted seconds
--connect-timeout101–300
--request-timeout301–3600
--idle-timeout301–3600

The request timeout bounds response-header waiting and, separately, finite body consumption. Stream bodies instead use the inactivity timeout. A slow stdout consumer applies backpressure; no additional output frames are queued. Ctrl-C also exits while blocked on stdin/stdout. Closing stdout terminates cleanly. The interrupt handler does not write diagnostics, so a full stderr pipe cannot prevent exit; use its exit status. Stopping a mutation only stops waiting and does not cancel accepted server work.

A history gap needs an explicit decision about recovery. If you accept lost history, use this sequence to rebuild state. Obtain journal-head. Obtain an authorized snapshot. Subscribe from that head. Reconcile overlap by Resource revision. This cannot recover missing business-event processing, and retention can race recovery again.

ExitMeaning
0Success, intentional non-mutation interruption, or stdout pipe closure.
2Invalid arguments, credentials, endpoint or local request input.
3Explicit remote rejection of a read/discovery/observation operation.
4Read/stream transport, response protocol or output failure.
5Mutation submitted; outcome unresolved, including any non-success reply.
6Read/stream history gap.
130Interrupted while waiting for a submitted mutation; outcome unresolved.

The executable tests run two unrelated Resource kinds against real HTTP on SQLite and redb, with actual stream updates and revocation. Fault coverage includes lost commit replies, post-commit denial and arbitrary host-gate errors, identical retry, identity mismatch, exact integers and partial presence, malformed inputs/frames, redirect rejection, stream inactivity, broken output pipes and signal interruption.

Operator work recovery

The work capabilities, work list, work show, work retry and work reconcile commands use the shared operator contract. See operator recovery for exact request files, authority, receipt replay and support limits. These commands do not open the database, reset budgets or infer a compensation.

This page follows the captured repository source.View the source