arcature_data/lib.rs
1//! `arcature-data` — the Arcature high-level data layer over `arcature-db`.
2//!
3//! AP2.1-6 (ADR-0006): a high-level model/query experience for ordinary CRUD
4//! over SeaORM, preserving the SeaORM and SQLx escape hatches. **No new database
5//! engine, no ORM, no hidden global pool, no task-local connection, and no
6//! request-global DB.** Every public API takes the [`Db`] handle (or the
7//! `DatabaseConnection` / `PgPool` it exposes) by reference — ownership is
8//! explicit on every call.
9//!
10//! # What this crate owns
11//!
12//! * **Explicit-ownership query ergonomics** over SeaORM entities: a typed
13//! [`Query<E>`] bound to a `&Db`, reached on the golden path as
14//! `Entity::query(&db)` via the blanket [`QueryModel`] trait (no application
15//! trait implementation required — it works for any SeaORM `Entity`).
16//! * **Vertical slices:** find by primary key, filter, order, paginate
17//! ([`Page<T>`]), eager-load typed relations, and CRUD — all carrying the
18//! `&Db` so the caller never re-passes `db.orm()` on every terminal call.
19//! * **Typed transactions** ([`Transaction`]) over the SeaORM and SQLx paths,
20//! with explicit ownership and no hidden nesting/savepoint magic.
21//! * **Development/CI N+1 detection** behind the `n1` Cargo feature: a
22//! request-scoped tracker (`n1::Tracker`) that records query events with
23//! source attribution and reports likely relation N+1 patterns. **Zero
24//! production hot-path overhead when the feature is off** — the tracker, the
25//! recording calls, and the analyzer are `cfg`-gated out of the default
26//! build.
27//! * **Migration lint** ([`classify`] / [`classify_statements`]) — a
28//! deterministic, database-free classifier of real PostgreSQL migration risks
29//! (destructive drops, unsafe `NOT NULL`, type narrowing, dangerous renames,
30//! blocking indexes).
31//!
32//! # What this crate does not own
33//!
34//! It does not open a connection pool (it borrows [`Db`]), it does not
35//! reimplement SeaORM's query builder or relation engine, it does not add a
36//! second transaction abstraction with hidden savepoints, and it does not
37//! introduce any global, thread-local, or task-local state. Raw SeaORM and
38//! SQLx remain first-class escape hatches via `db.orm()` / `db.sqlx()`.
39//!
40//! This crate is an **internal** workspace member (`publish = false`); its
41//! public surface is re-exported through the `arcature` facade behind the
42//! existing `db` feature. ADR-0006 authorizes no new *public* crate for
43//! AP2.1-6 — `arcature-data` is not an independent crates.io release unit.
44//!
45//! # Raw Axum usage
46//!
47//! `Db` is `Clone + Send + Sync + 'static` (from `arcature-db`); this crate
48//! adds no state of its own. `Query<E>` borrows `&Db` for the duration of the
49//! query, so it composes with normal Axum state without a runtime dependency:
50//!
51//! ```ignore
52//! use arcature_data::QueryModel;
53//! # async fn show(db: &arcature_db::Db) -> Result<(), arcature_data::DataError> {
54//! let posts = post::Entity::query(db)
55//! .filter(post::Column::Active.eq(true))
56//! .order_by_desc(post::Column::CreatedAt)
57//! .all()
58//! .await?;
59//! # Ok(())
60//! # }
61//! ```
62//!
63//! # Security note
64//!
65//! Every query carries an explicit `&Db`; there is no hidden pool the
66//! framework resolves on the caller's behalf. Migration lint output is
67//! side-effect-free and never reads environment variables or connects to a
68//! database; the N+1 tracker is request-scoped and constructed explicitly.
69
70#![forbid(unsafe_code)]
71#![doc(html_root_url = "https://docs.rs/arcature")]
72
73/// The certified `arcature-db` engine, re-exported so downstream code targets
74/// the Arcature-pinned types through this crate. [`Db`] is the explicit
75/// ownership handle every API in this crate borrows.
76pub use arcature_db::Db;
77
78mod error;
79mod lint;
80mod query;
81mod transaction;
82
83#[cfg(feature = "n1")]
84pub mod n1;
85
86pub use error::{DataError, PaginationError};
87pub use lint::{
88 Finding, LintCategory, LintReport, LintSeverity, SqlStatement, classify, classify_statements,
89};
90pub use query::{Page, Paginated, Query, QueryModel, Relation};
91pub use query::{delete, find_by_pk, insert, of, update};
92pub use transaction::Transaction;