Embedded (Rust)
The namidb façade crate is the stable umbrella API. It re-exports
the types you reach for most often from namidb-core, namidb-storage,
namidb-graph and namidb-query, so downstream code only needs one line
in Cargo.toml.
Add the dependency
[dependencies]namidb = "0.4"tokio = { version = "1", features = ["full"] }object_store = "0.13"anyhow = "1"You also depend directly on object_store
because that’s the trait the storage layer takes — picking a backend (S3,
GCS, Azure, local, memory) is a object_store instantiation.
Minimal example
use std::sync::Arc;
use namidb::core::id::NamespaceId;use namidb::query::{execute, lower, parse, Params};use namidb::storage::{NamespacePaths, WriterSession};use object_store::{memory::InMemory, ObjectStore};
#[tokio::main]async fn main() -> anyhow::Result<()> { // 1. Pick a backend. `InMemory` here for the demo; for production // use object_store::aws::AmazonS3, GoogleCloudStorage, etc. let store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
// 2. Resolve the on-bucket layout for a namespace. let paths = NamespacePaths::new("tenants", NamespaceId::new("demo")?);
// 3. Open a writer for the namespace (single writer per namespace). let mut writer = WriterSession::open(store, paths).await?;
// ... upsert nodes / edges, then commit_batch + flush ...
// 4. Pin a read snapshot, parse, lower and execute a Cypher query. let snap = writer.snapshot(); let q = parse("MATCH (a:Person) RETURN count(*) AS n")?; let plan = lower(&q)?; let rows = execute(&plan, &snap, &Params::new()).await?;
println!("{rows:?}"); Ok(())}The shape of the API
The umbrella namidb crate re-exports four namespaces. The most-used
items per namespace:
| Re-export | What you get |
|---|---|
namidb::core | id::NamespaceId, runtime values, schema types |
namidb::storage | WriterSession, Snapshot, NamespacePaths, URI parser |
namidb::graph | property columns + CSR adjacency (read-side helpers) |
namidb::query | parse, lower, execute, Params, LogicalPlan |
The full re-export list is in crates/namidb/src/lib.rs. The individual
crates remain on crates.io if you need a tighter dependency surface, but
linking only against namidb is the supported path.
URI-style opens
If you’d rather drive the writer from a URI string (the same shape the
Python client, CLI, and server use), use the storage crate’s parse_uri
helper instead of building the ObjectStore by hand:
use namidb::storage::{parse_uri, WriterSession};
let (store, paths) = parse_uri("s3://my-bucket/data?ns=prod®ion=us-east-1")?;let mut writer = WriterSession::open(store, paths).await?;Every URI scheme documented in Storage backends works here.
Write path
The WriterSession owns staged mutations. Two ways to write:
- Cypher writes via
execute_write—CREATE,MERGE,SET,DELETE,REMOVE. Auto-commit at the end of each statement. - Typed staging API —
writer.upsert_node,writer.upsert_edge,writer.tombstone_node,writer.tombstone_edge. You batch as many mutations as you want and callwriter.commit_batch().awaitto make them durable (WAL append + manifest CAS), thenwriter.flush().awaitto push the memtable into L0 SSTs.
See the staging API surface in crates/namidb-storage/src/lib.rs.
Single-writer-per-namespace
Two processes can both open WriterSession::open(...) against the same
namespace. The first one to issue a commit_batch wins; later commits
from a stale epoch are rejected via manifest CAS (If-Match on object
stores, flock + atomic rename on file://). No external lock service.
The current namespace is fully described by the bucket URI plus the
namespace name, so backups are aws s3 sync and a tenant is a folder.
What’s next
- Reading data — Cypher read clauses.
- Writing data — Cypher write clauses and the auto-commit model.
- Rust library — the complete public Rust surface.
- Storage backends — URI grammar per backend.