arcature 2026.2.1

Arcature application framework: a high-level Application facade over the certified Arcature subsystems, with the low-level Axum/Tower escape hatch preserved.
Documentation
//! The typed runtime [`Resources`] container — the live subsystem handles
//! built by `startup()` and torn down by `shutdown()`.
//!
//! `Resources` is the engine's answer to subsystem dependency injection
//! (engine spec §22): no `HashMap<TypeId, Box<dyn Any>>`, no runtime
//! reflection, no service locator (AGENTS.md §17/§20). Each subsystem handle
//! is a cfg-gated `Option<T>` field with a typed accessor. An application
//! receives `&Resources` in the state-building closure passed to
//! [`Application::run_with_lifecycle`](crate::Application::run_with_lifecycle)
//! and clones the handles it needs into its `AppState`.
//!
//! Fields are `Option<T>` because an application may enable a feature (so the
//! type compiles) without configuring that subsystem (so no connection is
//! made). The accessor returns `Option<&T>` so the application can decide how
//! to handle a missing subsystem (error, default, or skip).
//!
//! # What is NOT a resource
//!
//! The worker is not a resource — it is a running task managed by the engine
//! (`startup()` spawns it, `shutdown()` drains it). Auth has no resource type
//! (it is store-agnostic primitives, not a connected service). Observe has no
//! resource type (it is stateless middleware wired into the pipeline, not a
//! connected service).

/// The typed runtime container for live subsystem handles.
///
/// Built by `startup()` from the lifecycle config on
/// [`Application`](crate::Application), and torn down by `shutdown()`
/// in reverse startup order. An application accesses the handles via the
/// typed accessors (`db()`, `cache()`, etc.) inside the state-building closure.
///
/// Each accessor is cfg-gated: it exists only when the corresponding Cargo
/// feature is enabled. An accessor returns `Option<&T>` — `None` means the
/// subsystem was not configured (no connection was made), not that the feature
/// is off (in which case the accessor itself would not exist).
pub struct Resources {
    #[cfg(feature = "db")]
    pub(crate) db: Option<crate::db::Db>,
    #[cfg(feature = "cache")]
    pub(crate) cache: Option<crate::cache::Cache>,
    #[cfg(feature = "storage")]
    pub(crate) storage: Option<crate::storage::Storage>,
    #[cfg(feature = "mail")]
    pub(crate) mail: Option<crate::mail::Mailer>,
    #[cfg(feature = "jobs")]
    pub(crate) jobs: Option<crate::jobs::Jobs>,
}

impl Resources {
    /// The database handle, if the `db` feature is enabled and a database was
    /// configured. The handle shares the single `PgPool` (engine spec §28);
    /// `db.sqlx().clone()` is the seam for jobs, workers, and direct SQLx
    /// access.
    #[cfg(feature = "db")]
    #[must_use]
    pub fn db(&self) -> Option<&crate::db::Db> {
        self.db.as_ref()
    }

    /// The cache handle, if the `cache` feature is enabled and a cache was
    /// configured.
    #[cfg(feature = "cache")]
    #[must_use]
    pub fn cache(&self) -> Option<&crate::cache::Cache> {
        self.cache.as_ref()
    }

    /// The storage handle, if the `storage` feature is enabled and storage was
    /// configured.
    #[cfg(feature = "storage")]
    #[must_use]
    pub fn storage(&self) -> Option<&crate::storage::Storage> {
        self.storage.as_ref()
    }

    /// The mailer handle, if the `mail` feature is enabled and mail was
    /// configured.
    #[cfg(feature = "mail")]
    #[must_use]
    pub fn mail(&self) -> Option<&crate::mail::Mailer> {
        self.mail.as_ref()
    }

    /// The jobs handle (for enqueuing), if the `jobs` feature is enabled and a
    /// job queue was configured. The worker is not a resource — it is a
    /// running task managed by the engine.
    #[cfg(feature = "jobs")]
    #[must_use]
    pub fn jobs(&self) -> Option<&crate::jobs::Jobs> {
        self.jobs.as_ref()
    }
}

/// A handle to the running worker task, if jobs were configured. Created by
/// `startup()` and consumed by `shutdown()`: the shutdown
/// function cancels the token (the worker stops claiming and drains), then
/// awaits the join (confirming no in-flight job tasks before closing the
/// database pool).
#[cfg(feature = "jobs")]
pub(crate) struct WorkerHandle {
    pub(crate) join: tokio::task::JoinHandle<Result<(), crate::jobs::WorkerError>>,
    pub(crate) shutdown: tokio_util::sync::CancellationToken,
}