arcature-data 2026.2.0

Arcature high-level data layer: explicit-ownership model/query ergonomics over SeaORM/SQLx, N+1 detection, and migration lint.
Documentation
//! `arcature-data` — the Arcature high-level data layer over `arcature-db`.
//!
//! AP2.1-6 (ADR-0006): a high-level model/query experience for ordinary CRUD
//! over SeaORM, preserving the SeaORM and SQLx escape hatches. **No new database
//! engine, no ORM, no hidden global pool, no task-local connection, and no
//! request-global DB.** Every public API takes the [`Db`] handle (or the
//! `DatabaseConnection` / `PgPool` it exposes) by reference — ownership is
//! explicit on every call.
//!
//! # What this crate owns
//!
//! * **Explicit-ownership query ergonomics** over SeaORM entities: a typed
//!   [`Query<E>`] bound to a `&Db`, reached on the golden path as
//!   `Entity::query(&db)` via the blanket [`QueryModel`] trait (no application
//!   trait implementation required — it works for any SeaORM `Entity`).
//! * **Vertical slices:** find by primary key, filter, order, paginate
//!   ([`Page<T>`]), eager-load typed relations, and CRUD — all carrying the
//!   `&Db` so the caller never re-passes `db.orm()` on every terminal call.
//! * **Typed transactions** ([`Transaction`]) over the SeaORM and SQLx paths,
//!   with explicit ownership and no hidden nesting/savepoint magic.
//! * **Development/CI N+1 detection** behind the `n1` Cargo feature: a
//!   request-scoped tracker (`n1::Tracker`) that records query events with
//!   source attribution and reports likely relation N+1 patterns. **Zero
//!   production hot-path overhead when the feature is off** — the tracker, the
//!   recording calls, and the analyzer are `cfg`-gated out of the default
//!   build.
//! * **Migration lint** ([`classify`] / [`classify_statements`]) — a
//!   deterministic, database-free classifier of real PostgreSQL migration risks
//!   (destructive drops, unsafe `NOT NULL`, type narrowing, dangerous renames,
//!   blocking indexes).
//!
//! # What this crate does not own
//!
//! It does not open a connection pool (it borrows [`Db`]), it does not
//! reimplement SeaORM's query builder or relation engine, it does not add a
//! second transaction abstraction with hidden savepoints, and it does not
//! introduce any global, thread-local, or task-local state. Raw SeaORM and
//! SQLx remain first-class escape hatches via `db.orm()` / `db.sqlx()`.
//!
//! This crate is an **internal** workspace member (`publish = false`); its
//! public surface is re-exported through the `arcature` facade behind the
//! existing `db` feature. ADR-0006 authorizes no new *public* crate for
//! AP2.1-6 — `arcature-data` is not an independent crates.io release unit.
//!
//! # Raw Axum usage
//!
//! `Db` is `Clone + Send + Sync + 'static` (from `arcature-db`); this crate
//! adds no state of its own. `Query<E>` borrows `&Db` for the duration of the
//! query, so it composes with normal Axum state without a runtime dependency:
//!
//! ```ignore
//! use arcature_data::QueryModel;
//! # async fn show(db: &arcature_db::Db) -> Result<(), arcature_data::DataError> {
//! let posts = post::Entity::query(db)
//!     .filter(post::Column::Active.eq(true))
//!     .order_by_desc(post::Column::CreatedAt)
//!     .all()
//!     .await?;
//! # Ok(())
//! # }
//! ```
//!
//! # Security note
//!
//! Every query carries an explicit `&Db`; there is no hidden pool the
//! framework resolves on the caller's behalf. Migration lint output is
//! side-effect-free and never reads environment variables or connects to a
//! database; the N+1 tracker is request-scoped and constructed explicitly.

#![forbid(unsafe_code)]
#![doc(html_root_url = "https://docs.rs/arcature")]

/// The certified `arcature-db` engine, re-exported so downstream code targets
/// the Arcature-pinned types through this crate. [`Db`] is the explicit
/// ownership handle every API in this crate borrows.
pub use arcature_db::Db;

mod error;
mod lint;
mod query;
mod transaction;

#[cfg(feature = "n1")]
pub mod n1;

pub use error::{DataError, PaginationError};
pub use lint::{
    Finding, LintCategory, LintReport, LintSeverity, SqlStatement, classify, classify_statements,
};
pub use query::{Page, Paginated, Query, QueryModel, Relation};
pub use query::{delete, find_by_pk, insert, of, update};
pub use transaction::Transaction;