ScopeDB SDK for Rust
scopedb-client is an async Rust client for ScopeDB. It supports statements,
read-only REST catalog discovery, direct NDJSON table appends, bounded concurrent
streaming writes, and transform-oriented JSON ingest.
Installation
Create a client
The SDK accepts a configured HTTP client, so applications can own TLS, timeouts,
authentication headers, and connection pooling. Use the compatible reqwest
version re-exported as scopedb_client::reqwest; a separate direct reqwest
dependency is not needed.
use Client;
let client = new?;
# Ok::
Run a statement
# async
Browse the catalog
Catalog list methods return one page and preserve the opaque continuation token. Fetch methods return the full database, schema, or table resource.
use CatalogListOptions;
# async
See examples/catalog.rs for complete database
pagination and table metadata discovery.
Streaming writes with NDJSON
The table write API accepts newline-delimited JSON only: each line is one JSON
row object. It does not accept a JSON array. The destination table must already
exist. Table uses scopedb and public when the database or schema is not
specified.
Direct append
Use direct append when the caller already owns one exact NDJSON request boundary.
use Client;
# async
A failed append exposes structured details through Error::append_details():
use AppendState;
use ErrorKind;
#
Unknown is deliberately different from a rejection: replaying the same rows
may insert duplicates.
Asynchronous append stream
Use append_stream() for continuous or large producers. The stream serializes
records to NDJSON, batches by size or time, bounds pending bytes, and sends a
bounded number of HTTP append requests concurrently.
use Duration;
# async
send() and send_all() wait for local admission capacity only; they do not
wait for a remote commit. Feed an iterator sequentially instead of spawning one
task per row, which would move the unbounded backlog outside the stream.
flush() and shutdown() are remote delivery barriers.
Once a flush() future has enqueued its barrier, dropping that future does not
cancel remote settlement. Keep it alive to receive the interval report; if a
task is cancelled, inspect stats().last_report and lifetime counters before
deciding how to reconcile the covered rows.
The default AppendFailurePolicy::Stop is strict: the first failed batch stops
admission, and a successful barrier confirms that its accepted prefix committed.
Use max_in_flight_requests(1) if request submission order matters; concurrent
batches have no defined commit order.
Best-effort telemetry and logs
Telemetry producers often cannot wait for admission or stop permanently after
one unavailable batch. Opt into Continue, use try_send() on the hot path,
and inspect settlement reports and lifetime stats.
use Duration;
use AppendDeliveryOutcome;
use AppendFailurePolicy;
# async
Ok(()) from try_send() still means local admission, not remote commit. It
returns immediately with an error when serialization fails, the row is invalid
or too large, the buffer is full, the circuit is open, or the stream is closed.
Continue mode releases failed batches after accounting for them; it is not an
in-memory retry queue.
The circuit breaker rejects non-blocking try_send() calls while open. The
backpressured send() path can still admit rows within the configured memory
budget; their dispatch waits for the circuit probe, so use try_send() on
latency-sensitive logging and telemetry paths.
A continue-mode report classifies rows as committed, failed, unknown, or locally dropped. For accepted rows in a completed report:
accepted_rows = committed_rows + failed_rows + unknown_rows
AppendDeliveryOutcome::Partial means at least one row committed while another
row failed, was dropped, or remains unknown. With no committed rows, the outcome
is Unknown when any batch may have committed and Failed otherwise. Only a
loss-free report is Ok.
The stream retries only the exact temporary HTTP batch explicitly reported as
Rejected. A timeout, transport failure, or invalid success response is
Unknown and is never automatically retried. An in-memory stream is not a
durable queue; use an outbox when payloads must survive process failure or be
available for reconciliation.
Choose a delivery path
| Workload | Admission and delivery | Example |
|---|---|---|
| One exact NDJSON payload | Caller owns the request boundary | append.rs |
| Basic asynchronous batching | SDK owns batches; strict barriers | append_stream.rs |
| Backfill or file import | Bounded producer memory and concurrent strict batches | bulk_append.rs |
| Long-running logs and events | Non-blocking continue mode with observable loss | telemetry.rs |
| SQL transformation before insert | Transform-oriented ingest stream | ingest_transform.rs |
Table helper
# async
Transform-oriented ingest
IngestStream remains useful when input JSON needs a SQL transform before
insertion. Prefer Table::append or Table::append_stream when records already
match the destination table.
# async
Examples and development
The example guide includes setup, safety guards, delivery contracts, and runnable commands.
The wire-level endpoint and payload reference is in
docs/rust-http-api.md.
Release history and the maintainer runbook are in CHANGELOG.md
and RELEASE.md.