async only.Expand description
Runtime-agnostic async surface — Phase 5 (issue #10).
obj::asynchronous mirrors the blocking crate::Db /
crate::Collection / crate::Query API. Every async method
hands its synchronous body to the blocking crate’s process-wide
thread pool via blocking::unblock(...).await; the engine itself
stays blocking. This composes with any async runtime: Tokio,
async-std, smol, glommio, and friends — no per-runtime sub-features.
The module is gated under the async Cargo feature. With the
feature off, the baseline build adds no new transitive dependencies
and no async overhead — the entire module is #[cfg(...)]-excised.
§Design
- Wrapping pattern.
AsyncDbwrapsArc<Db>internally. Each async method clones theArcand moves the clone into the blocking task. The public blockingcrate::Dbdoes not deriveClone— async opt-in does not impose a public-API change on blocking users. - Closure-bodied methods.
AsyncDb::transactionandAsyncDb::read_transactiontake a closure that runs synchronously inside the blocking task. The closure must beSend + 'staticso it can move across the thread boundary. Noasync fninside the transaction body — this is the standard “async-over-blocking” contract (sqlx, rusqlite/tokio-rusqlite, etc.). - Iterators. Streaming iteration (
Db::iter_all/Stream<Item = Result<T>>) is not wrapped in this phase.AsyncDb::allcollects the entire collection in the blocking task and returnsVec<T>. A futureAsyncDb::stream_allwould add aStreamadapter — left as a follow-up. - Tracing. When the
tracingfeature is also on, every async method capturestracing::Span::current()beforeblocking::unblockand re-enters it inside the closure via a guard, so spans propagate across the thread-pool hop. Withtracingoff, the wrapper is a pure pass-through.
§Example (Tokio)
use obj::asynchronous::AsyncDb;
use serde::{Deserialize, Serialize};
use tempfile::tempdir;
#[derive(Debug, Serialize, Deserialize, obj::Document)]
struct Order {
customer_id: u64,
total_cents: u64,
}
#[tokio::main(flavor = "multi_thread")]
async fn main() -> obj::Result<()> {
let dir = tempdir()?;
let path = dir.path().join("app.obj");
let db = AsyncDb::open(path).await?;
let id = db.insert(Order { customer_id: 1, total_cents: 999 }).await?;
let back: Option<Order> = db.get(id).await?;
assert!(back.is_some());
Ok(())
}The same AsyncDb value drives identically from #[async_std::main]
or any other runtime — see
crates/obj/tests/async_async_std.rs
for the worked async-std fixture.
Structs§
- Async
Collection - Async-facing wrapper over a runtime-named read-only collection handle.
- AsyncDb
- Async-facing wrapper around the blocking
Db. - Async
Query - Async-facing query builder. See
crate::Queryfor the surface semantics; this wrapper only changes the terminal methods toasync fnand addsSendto every stored closure.