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
//! Eager loading of typed relations over SeaORM.
//!
//! [`Relation`] is a thin marker that names a related entity so [`Query`]
//! can eager-load it without the caller spelling out the `Related` impl. The
//! underlying load delegates to SeaORM's `find_also_related` — Arcature does
//! not reimplement the relation engine.

use std::marker::PhantomData;

use arcature_db::sea_orm;

use crate::error::DataError;
use crate::query::Query;

/// A typed reference to a related entity `R`, for eager loading.
///
/// Construct with [`of`] and pass to [`Query::with`]. The relation
/// is resolved through SeaORM's `Related<R>` impl on the parent entity, so the
/// caller does not name the relation enum variant — the type alone selects it.
pub struct Relation<R>
where
    R: sea_orm::EntityTrait,
{
    // The related entity type is the relation selector. SeaORM's
    // `find_also_related::<R>` resolves the join through `Related<R>` on the
    // parent entity, so no relation-name string is needed. `PhantomData`
    // holds the type without requiring `R: Default`.
    _related: PhantomData<R>,
}

/// Reference the related entity `R` for eager loading. Generic over `R` so
/// the caller can name it explicitly:
///
/// ```ignore
/// use arcature_data::Relation;
/// query.with(Relation::of::<user::Entity>())
/// ```
///
/// `R` is resolved through SeaORM's `Related<R>` impl on the parent entity.
#[must_use]
pub fn of<R>() -> Relation<R>
where
    R: sea_orm::EntityTrait,
{
    Relation {
        _related: PhantomData,
    }
}

impl<'db, E> Query<'db, E>
where
    E: sea_orm::EntityTrait,
{
    /// Eager-load a related entity `R` alongside each row of `E`, returning
    /// `(parent, Option<related>)` pairs. Delegates to SeaORM's
    /// `find_also_related`.
    ///
    /// ```ignore
    /// let rows = post::Entity::query(db)
    ///     .with(arcature_data::Relation::of::<user::Entity>())
    ///     .await?; // Vec<(post::Model, Option<user::Model>)>
    /// ```
    ///
    /// `R` must be `Default` — SeaORM's generated `Entity` is a unit struct
    /// that derives `Default`, so this is satisfied by every
    /// `#[derive(DeriveEntityModel)]` entity without extra work.
    ///
    /// # Errors
    ///
    /// Returns [`DataError::Database`] if the underlying SeaORM query fails.
    pub async fn with<R>(
        self,
        _relation: Relation<R>,
    ) -> Result<Vec<(E::Model, Option<R::Model>)>, DataError>
    where
        R: sea_orm::EntityTrait + Default,
        E: sea_orm::Related<R>,
    {
        self.select
            .find_also_related(R::default())
            .all(self.db.orm())
            .await
            .map_err(DataError::from)
    }
}