# feltdb-client
A native async Rust client for a [FeltDB](https://github.com/rkendel1/flow_db) server: the Rust
counterpart of `@feltdb/core`'s server runtime. It holds no state of its own; the server is the
authority for every read and write.
```toml
[dependencies]
feltdb-client = "0.2"
serde_json = "1"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
```
```rust
use feltdb_client::{Client, Operation, Precondition, Transaction};
use serde_json::json;
#[tokio::main]
async fn main() -> feltdb_client::Result<()> {
let client = Client::builder("http://127.0.0.1:7700")
.token(std::env::var("FELTDB_TOKEN").unwrap_or_default())
.build()?;
// One atomic commit. The id makes a retry after an uncertain outcome safe.
let tx = Transaction::new("ship-o-1")
.operation(Operation::put("orders", "o-1", json!({ "status": "shipped" })).expect_version(1))
.operation(Operation::create("audit", "a-1", json!({ "order": "o-1" })))
.precondition(Precondition::absent("locks", "o-1"));
match client.commit(&tx).await {
Ok(receipt) => println!("committed at revision {}", receipt.commit_revision),
Err(error) if error.is_conflict() => println!("lost a race: {:?}", error.precondition_failure()),
Err(error) if error.is_outcome_unknown() => { /* resubmit `tx` unchanged */ }
Err(error) => return Err(error),
}
Ok(())
}
```
## What it covers
| Records: `GET/POST /collections/…`, `PATCH`, `DELETE` | `get`, `list`, `create`, `update`, `delete` |
| Atomic, idempotent transactions with preconditions and fences: `POST /transactions` | `commit` with `Transaction`, `Operation`, `Precondition` |
| Read bases: `POST /transactions/read-basis` | `read_basis`, then `Transaction::fence` |
| Compare-and-set: `POST /collections/{c}/{id}/cas` | `compare_and_set` → `CasOutcome` |
| Bounded query: `POST /query` | `query` with `BoundedQuery` |
| Committed revision: `GET /revision` | `revision` |
| Live events: `GET /events` (SSE) | `events` → `EventStream` |
| Canonical application API under `/v1` | `client.application(id)`: `discover`, `query`, `commit` (per-operation `if_version` via `update_if_version`/`delete_if_version`), `read_basis`, `events`, `replay`, `provenance`, `sign_in`/`sign_up`/`session`/`sign_out` |
| Committed state version: `GET /v1/state/version` | `application.state_version()` |
| Control plane: `/api/tenants`, `/api/applications/…` drafts, revisions, promotion; `POST /admin/backups` | `client.control()`: `create_tenant`, `create_application`, `create_draft`, `update_draft`, `validate_draft`, `commit_draft`, `revision`, `promote`, `backup` |
| Health: `GET /health` | `health` |
| Capability discovery | `capabilities` |
| Anything else | `request(Method, path, body)` |
Errors carry the server's stable `code`, its `request_id`, and for a refused transaction the
structured `PreconditionFailure`. The canonical query and transaction documents are `feltdb`'s
own types (re-exported under `feltdb_client::wire`), so the client and server serialize the same
structs.
A server behind a private CA is trusted with
`Client::builder(url).add_root_certificate_pem(pem)`, in addition to the bundled public roots; verification is never
disabled.
The event stream does not reconnect by itself; reopen it from `EventStream::last_event_id()`.
For an embedded store in your own process, use the [`feltdb`](https://crates.io/crates/feltdb)
crate instead; to run the server, `cargo install feltdb-server`.
License: MIT.