modrun 1.1.0

Modular application composer for Tokio: domain modules, typed wiring, lifecycle
Documentation
//! modrun — a modular application composer for Rust.
//!
//! Build large Tokio services as domain-oriented [`Module`]s with constructor
//! injection, explicit module boundaries, and coordinated lifecycle — wired once
//! at the composition root.
//!
//! Register constructors, pull the graph with invokers, and manage start/stop
//! via [`Lifecycle`].
//!
//! # Quick start
//!
//! ```no_run
//! use modrun::{Hook, Lifecycle, Modrun};
//!
//! #[derive(Clone)]
//! struct Config { port: u16 }
//!
//! #[derive(Clone)]
//! struct Server { cfg: Config }
//!
//! impl Hook for Server {
//!     async fn on_start(&mut self) -> modrun::Result<()> {
//!         println!("listening on {}", self.cfg.port);
//!         Ok(())
//!     }
//!
//!     async fn on_stop(&mut self) -> modrun::Result<()> {
//!         Ok(())
//!     }
//! }
//!
//! fn new_config() -> Config { Config { port: 8080 } }
//! fn new_server(cfg: Config) -> Server { Server { cfg } }
//!
//! fn boot(lc: Lifecycle, server: Server) -> modrun::Result<()> {
//!     lc.append(server)
//! }
//!
//! #[tokio::main]
//! async fn main() -> modrun::Result<()> {
//!     Modrun::builder()
//!         .provide(new_config)
//!         .provide(new_server)
//!         .invoke(boot)
//!         .run()
//!         .await
//! }
//! ```
//!
//! Dependencies must be `Clone` to inject by value. Singletons are cached by
//! type and cloned on inject. Prefer `Arc<T>` when a type is large, shared across
//! many constructors, or injected more than once — `Arc<T>` is also stored, so you
//! can inject `Arc<T>` even when `T` itself is not `Clone`. Put shared mutable
//! state behind `Arc` inside your type, e.g. `#[derive(Clone)] struct Db(Arc<DbInner>)`.
//!
//! Because the cache key is the type, a type can only be provided once. To wire
//! two of the same thing — a primary and a replica pool, say — give each its own
//! newtype wrapper.
//!
//! # Async constructors
//!
//! Anything that needs to `await` before it exists (connection pools, clients
//! that handshake) can be provided directly:
//!
//! ```
//! use modrun::Modrun;
//!
//! #[derive(Clone)]
//! struct Pool;
//!
//! async fn connect() -> Result<Pool, std::io::Error> {
//!     Ok(Pool)
//! }
//!
//! #[tokio::main]
//! async fn main() -> modrun::Result<()> {
//!     Modrun::builder()
//!         .provide_result_async(connect)
//!         .invoke(|_pool: Pool| {})
//!         .start()
//!         .await?
//!         .stop()
//!         .await
//! }
//! ```
//!
//! # Domain modules
//!
//! A [`Module`] groups related wiring under a name and gives it a private scope.
//! `provide_private` keeps a type invisible outside the module, so two modules
//! can each hold their own `Repo` without colliding.
//!
//! ```
//! use modrun::{Modrun, Module};
//!
//! #[derive(Clone)]
//! struct UserRepo;
//! #[derive(Clone)]
//! struct UserService;
//!
//! fn new_user_repo() -> UserRepo { UserRepo }
//! fn new_user_service(_repo: UserRepo) -> UserService { UserService }
//! fn boot_user(_svc: UserService) {}
//!
//! fn user_domain() -> Module {
//!     Module::builder("user")
//!         .provide_private(new_user_repo)
//!         .provide(new_user_service)
//!         .invoke(boot_user)
//! }
//!
//! #[tokio::main]
//! async fn main() -> modrun::Result<()> {
//!     Modrun::builder()
//!         .module(user_domain())
//!         .start()
//!         .await?
//!         .stop()
//!         .await
//! }
//! ```
//!
//! # Groups
//!
//! Several modules can each contribute one `T`; consumers inject [`Group<T>`]
//! (not `Vec<T>`). Register members with [`ModrunBuilder::provide_group`]. An
//! empty group needs [`ModrunBuilder::init_group`] (or
//! [`ModrunBuilder::require_group`], which also registers the group and then
//! rejects emptiness). Members do not occupy the singleton slot for `T`. Group
//! members and injected groups require `T: Clone`; prefer `Arc<Group<T>>` or
//! `Group<Arc<T>>` when many consumers or heavy values would otherwise clone.
//!
//! # Dependency graph
//!
//! Export the wiring graph with [`ModrunBuilder::render_dot`] or
//! [`.dot_graph(path)`](ModrunBuilder::dot_graph) (DOT is written after validation,
//! before constructors run). Built-in `Lifecycle` and `Shutdowner` are omitted from
//! the exported graph.
//!
//! # Startup banner
//!
//! [`ModrunBuilder::run`] and [`ModrunBuilder::start`] print a modrun ASCII banner
//! to stderr before wiring begins (Spring Boot style), and only when stderr is a
//! TTY. Override with [`.banner(text)`](ModrunBuilder::banner) (always printed to
//! stderr) or disable with [`.no_banner()`](ModrunBuilder::no_banner). Tests that
//! run in a terminal should call `.no_banner()` so captured stderr stays quiet.
//!
//! # Logging
//!
//! Framework events (provide / supply / invoke / construct / lifecycle) are
//! emitted through [`tracing`] with target `modrun`, using [uber/fx](https://github.com/uber-go/fx)-style
//! console lines such as `[modrun] PROVIDE ...`. Events also carry structured
//! fields (`constructor`, `module`, `elapsed`, …) for JSON subscribers.
//! Call `modrun::logging::init()` from examples and local binaries
//! (requires the `logging` feature); it is a no-op if a subscriber is already
//! installed, writes to stderr, and enables ANSI only when stderr is a terminal. Production
//! processes should install their own subscriber and
//! filter with `RUST_LOG=modrun=info`. Without a subscriber the events are
//! cheap no-ops. Debug builds also print to stderr if a `RunningApp` is dropped
//! without [`RunningApp::stop`].
//!
//! ```no_run
//! # #[cfg(feature = "logging")]
//! modrun::logging::init();
//! ```
//!
//! # Stability
//!
//! The public model is type-keyed singletons, [`Group<T>`] value groups, lazy
//! `provide`, `invoke` as the graph root, and start/stop via [`Lifecycle`].
//! Domain wiring uses [`Module::builder`] (symmetric with [`Modrun::builder`]).
//! There are no string qualifiers and no service locator after build. [`Hook`]
//! methods will only grow with defaults. Constructors and invokers accept at most
//! eight parameters; pack extra deps in a struct. MSRV is **1.85** (edition 2024).
//! **Application-facing API is stable since 1.0.0**; see CONTRIBUTING.md.
//! Startup overhead is mostly your constructors; the container is dropped after build.
//! Prefer `Arc<T>` and `Arc<Group<T>>` for heavy deps — see README「Performance」.
//! Examples may need a newer compiler via their own dependencies (the MSRV job
//! does not compile `--all-targets`).
//!
//! Application code should use [`ModrunBuilder::provide`] /
//! [`ModrunBuilder::invoke`]. Wrapper libraries use the hidden [`__wiring`] module
//! and `ModrunBuilder::provide_dyn` / `invoke_dyn` (also `#[doc(hidden)]`).
//!
//! # Crate features
//!
//! * **`signal`** *(enabled by default)* — OS signal listeners in
//!   [`ModrunBuilder::run`] on **Unix** (Ctrl-C / SIGTERM) and **Windows**
//!   (Ctrl-C / Ctrl-Break / Ctrl-Close / Ctrl-Shutdown). On other targets the
//!   feature is a no-op for OS signals; use [`Shutdowner`]. Disable with
//!   `default-features = false` when you only call [`ModrunBuilder::start`] or
//!   wait on [`Shutdowner`] yourself.
//! * **`logging`** — `modrun::logging::init` / `try_init` for fx-style console
//!   output (`fmt`-only, no regex crates). Skip it in production and install
//!   your own subscriber.
//! * **`env-filter`** — adds `RUST_LOG` / per-target filtering to
//!   `modrun::logging` (pulls `regex-automata`, `matchers`). Implies
//!   `logging`; on by default. Use `default-features = false` +
//!   `features = ["logging"]` for console output without the regex deps.

#![forbid(unsafe_code)]

mod app;
mod banner;
mod container;
mod deps;
mod error;
mod future;
mod graph;
mod group;
mod invoke;
mod lifecycle;
mod module;
mod option;
mod provide;
mod provide_group;
mod scope;
mod shutdown;
mod supply;
mod supply_group;
mod task;
mod timeout;
mod trace;
mod wiring;

pub use app::{Modrun, ModrunBuilder, RunningApp};
pub use banner::DEFAULT_BANNER;
pub use error::{BoxError, Error, MultipleStopError, Result};
pub use group::Group;
pub use lifecycle::{Hook, HookFn, Lifecycle, hook};
pub use module::Module;
pub use shutdown::Shutdowner;
pub use task::{PreparedTask, Stopped, Task, task, task_with};
pub use timeout::DEFAULT_TIMEOUT;

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

/// Type-erased wiring traits and erased provider/invoker types.
///
/// For **wrapper libraries** that re-export modrun's builder API. Not part of the
/// application-facing model and omitted from docs.rs navigation (`#[doc(hidden)]`).
/// Semver: treat as unstable relative to the typed `provide` / `invoke` surface.
#[doc(hidden)]
pub mod __wiring {
    pub use crate::invoke::{AsyncInvokeFn, DynInvoker, InvokeFn};
    pub use crate::provide::{
        AsyncProviderFn, DynProvider, FallibleAsyncProviderFn, FallibleProviderFn, ProviderFn,
        ProviderMarker,
    };
}

#[cfg(doctest)]
#[doc = include_str!("../README.md")]
struct ReadmeDoctests;