Skip to main content

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;