Skip to content

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

Cargo.toml
[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-exportWhat you get
namidb::coreid::NamespaceId, runtime values, schema types
namidb::storageWriterSession, Snapshot, NamespacePaths, URI parser
namidb::graphproperty columns + CSR adjacency (read-side helpers)
namidb::queryparse, 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&region=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_writeCREATE, MERGE, SET, DELETE, REMOVE. Auto-commit at the end of each statement.
  • Typed staging APIwriter.upsert_node, writer.upsert_edge, writer.tombstone_node, writer.tombstone_edge. You batch as many mutations as you want and call writer.commit_batch().await to make them durable (WAL append + manifest CAS), then writer.flush().await to 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