obj/asynchronous/mod.rs
1//! Runtime-agnostic async surface — Phase 5 (issue #10).
2//!
3//! `obj::asynchronous` mirrors the blocking [`crate::Db`] /
4//! [`crate::Collection`] / [`crate::Query`] API. Every async method
5//! hands its synchronous body to the [`blocking`] crate's process-wide
6//! thread pool via `blocking::unblock(...).await`; the engine itself
7//! stays blocking. This composes with **any** async runtime: Tokio,
8//! async-std, smol, glommio, and friends — no per-runtime sub-features.
9//!
10//! The module is gated under the `async` Cargo feature. With the
11//! feature off, the baseline build adds no new transitive dependencies
12//! and no async overhead — the entire module is `#[cfg(...)]`-excised.
13//!
14//! # Design
15//!
16//! - **Wrapping pattern.** [`AsyncDb`] wraps `Arc<Db>` internally.
17//! Each async method clones the `Arc` and moves the clone into the
18//! blocking task. The public blocking [`crate::Db`] does **not**
19//! derive `Clone` — async opt-in does not impose a public-API
20//! change on blocking users.
21//! - **Closure-bodied methods.**
22//! [`AsyncDb::transaction`](AsyncDb::transaction) and
23//! [`AsyncDb::read_transaction`](AsyncDb::read_transaction) take a
24//! closure that runs **synchronously** inside the blocking task.
25//! The closure must be `Send + 'static` so it can move across the
26//! thread boundary. No `async fn` inside the transaction body —
27//! this is the standard "async-over-blocking" contract (sqlx,
28//! rusqlite/tokio-rusqlite, etc.).
29//! - **Iterators.** Streaming iteration (`Db::iter_all` /
30//! `Stream<Item = Result<T>>`) is **not** wrapped in this phase.
31//! [`AsyncDb::all`] collects the entire collection in the blocking
32//! task and returns `Vec<T>`. A future `AsyncDb::stream_all` would
33//! add a `Stream` adapter — left as a follow-up.
34//! - **Tracing.** When the `tracing` feature is also on, every async
35//! method captures `tracing::Span::current()` before
36//! `blocking::unblock` and re-enters it inside the closure via a
37//! guard, so spans propagate across the thread-pool hop. With
38//! `tracing` off, the wrapper is a pure pass-through.
39//!
40//! # Example (Tokio)
41//!
42//! ```no_run
43//! use obj::asynchronous::AsyncDb;
44//! use serde::{Deserialize, Serialize};
45//! use tempfile::tempdir;
46//!
47//! #[derive(Debug, Serialize, Deserialize, obj::Document)]
48//! struct Order {
49//! customer_id: u64,
50//! total_cents: u64,
51//! }
52//!
53//! #[tokio::main(flavor = "multi_thread")]
54//! async fn main() -> obj::Result<()> {
55//! let dir = tempdir()?;
56//! let path = dir.path().join("app.obj");
57//! let db = AsyncDb::open(path).await?;
58//! let id = db.insert(Order { customer_id: 1, total_cents: 999 }).await?;
59//! let back: Option<Order> = db.get(id).await?;
60//! assert!(back.is_some());
61//! Ok(())
62//! }
63//! ```
64//!
65//! The same `AsyncDb` value drives identically from `#[async_std::main]`
66//! or any other runtime — see
67//! [`crates/obj/tests/async_async_std.rs`](https://github.com/uname-n/obj/blob/master/crates/obj/tests/async_async_std.rs)
68//! for the worked async-std fixture.
69
70mod collection;
71mod db;
72mod query;
73
74pub use collection::AsyncCollection;
75pub use db::AsyncDb;
76pub use query::AsyncQuery;