A thin Rust client for the Yeti host-import RPC. Call
Store::put/get/scanagainst a remote Yeti and never touch a socket, a length prefix, or MessagePack. The carrier and the server live inyeti-host-rpc; this crate is the typed facade over its client surface.
This SDK is for code that wants a remote Yeti's persistence imports over the
network binding (YTC-449) — the same wire every Yeti polyglot binding speaks
(YTC-411). It opens one multiplexed connection and hands out cheap Store
handles that share it. Keys are namespaced per table, so distinct tables never
collide and every binding shares one keyspace against the same server.
Demo, not the product SDK (ADR-025). This is the small, readable hand-rolled client kept as a demonstration of the wire. The published product SDK is the full capability core; don't add capability surface here. The crate is
publish = falseand does not collide with the publishedyeti-sdk.
The crate path-depends on two workspace crates (yeti-host-rpc, yeti-tls), so
it builds from inside the Yeti source tree. Add it as a path dependency:
[dependencies]
yeti-sdk = { path = "sdks/sdk-rust" }Requires Rust 1.85+ and the Tokio runtime. The SDK depends only on
yeti-host-rpc (the protocol crate), yeti-tls (the pinned-roots TLS client
config), tokio, and the MessagePack stack (serde, rmp-serde,
serde_bytes).
Connect to a running yeti-host-rpc endpoint, then put / get / scan through a
Store:
# async fn demo() -> yeti_sdk::Result<()> {
let client = yeti_sdk::Client::connect("127.0.0.1:7799").await?;
let store = client.store();
store.put("notes", "greeting", b"hello").await?;
let v = store.get("notes", "greeting").await?;
assert_eq!(v.as_deref(), Some(&b"hello"[..]));
store.put("notes", "lang-py", b"3").await?;
store.put("notes", "lang-rs", b"1").await?;
let keys = store.scan("notes", "lang-").await?; // ["lang-py", "lang-rs"]
assert_eq!(keys.len(), 2);
# Ok(())
# }Over TLS, pinning exactly the CA you pass (ADR-014 transport.tls):
# async fn demo(ca_pem: &str) -> yeti_sdk::Result<()> {
let client =
yeti_sdk::Client::connect_tls(ca_pem, "localhost", "host:7443").await?;
let store = client.store();
# let _ = store;
# Ok(())
# }The public surface is three types and one error model.
| Item | What it does |
|---|---|
Client::connect(addr) |
Open a plaintext TCP connection to a host-rpc endpoint. |
Client::connect_tls(ca_pem, server_name, addr) |
Open a TLS connection, trusting exactly the CA in ca_pem. |
Client::from_rpc(rpc) |
Wrap an already-built RpcClient over a custom transport. |
Client::store() |
Hand out a Store over the connection (cheap to clone). |
Client::raw() |
The underlying RpcClient, for interfaces this SDK does not yet wrap. |
Store::put(table, key, value) |
Store raw value bytes under (table, key). |
Store::get(table, key) |
Fetch the bytes under (table, key), or None if absent. |
Store::scan(table, prefix) |
List keys under table that start with prefix. |
Error / Result |
Re-exported from yeti-host-rpc, so callers handle one error type. |
The crate is split into focused modules, re-exported from the crate root so the
public path is unchanged (yeti_sdk::Client, yeti_sdk::Store):
| Module | Responsibility |
|---|---|
client |
Client — connection setup (TCP / TLS / custom transport). |
store |
Store — the typed persistence/store facade and key namespacing. |
wire |
The MessagePack request/reply payloads (crate-private). |
Values are carried as MessagePack bin — no base64, the whole wire is binary.
Keys are namespaced as a \x1f-joined table + key, matching the Python SDK
and the harness runners, and scan strips the table\x1f prefix back off the
results it returns.
cargo testThe suite covers each module's behavior: key namespacing and the scan-prefix
strip (store), the MessagePack payload encode/decode round-trips and codec
error mapping (wire), connection sharing across cloned handles (client), and
an end-to-end put / get / scan round-trip driven through the public
surface against an in-memory server over a duplex transport (lib).
Apache-2.0. See LICENSE.
