stately/lib.rs
1#![cfg_attr(
2 feature = "axum",
3 allow(unused_crate_dependencies, reason = "axum and tokio used only in generated code")
4)]
5//! # Stately
6//!
7//! Type-safe state management with entity relationships and CRUD operations.
8//!
9//! ## Overview
10//!
11//! Stately is a framework for managing application configuration and state with built-in support
12//! for:
13//!
14//! - 🔗 **Entity Relationships** - Reference entities inline or by ID using [`Link<T>`]
15//! - 📝 **CRUD Operations** - Full create, read, update, delete for all entity types
16//! - 🔄 **Serialization** - Complete serde support for JSON, YAML, and more
17//! - 📚 **`OpenAPI` Schemas** - Automatic schema generation with `utoipa`
18//! - 🆔 **Time-Sortable IDs** - UUID v7 for naturally ordered entity identifiers
19//! - 🚀 **Web APIs** - Optional Axum integration with generated REST handlers
20//! - 🔍 **Search & Query** - Built-in entity search across collections
21//!
22//! ## Quick Start
23//!
24//! Define your entities using the [`entity`] macro:
25//!
26//! ```rust,ignore
27//! use stately::prelude::*;
28//!
29//! #[stately::entity]
30//! #[derive(Clone, serde::Serialize, serde::Deserialize)]
31//! pub struct Pipeline {
32//! pub name: String,
33//! pub source: Link<SourceConfig>,
34//! pub sink: Link<SinkConfig>,
35//! }
36//!
37//! #[stately::entity]
38//! #[derive(Clone, serde::Serialize, serde::Deserialize)]
39//! pub struct SourceConfig {
40//! pub name: String,
41//! pub url: String,
42//! }
43//!
44//! #[stately::entity]
45//! #[derive(Clone, serde::Serialize, serde::Deserialize)]
46//! pub struct SinkConfig {
47//! pub name: String,
48//! pub destination: String,
49//! }
50//! ```
51//!
52//! Create your application state using the [`state`] macro:
53//!
54//! ```rust,ignore
55//! #[stately::state]
56//! pub struct AppState {
57//! pipelines: Pipeline,
58//! sources: SourceConfig,
59//! sinks: SinkConfig,
60//! }
61//! ```
62//!
63//! The `state` macro generates:
64//! - `StateEntry` enum - discriminator for entity types
65//! - `Entity` enum - type-erased wrapper for all entities
66//! - `link_aliases` module - type aliases like `PipelineLink = Link<Pipeline>`
67//!
68//! Use your state with full type safety:
69//!
70//! ```rust,ignore
71//! let mut state = AppState::new();
72//!
73//! // Create entities
74//! let source_id = state.sources.create(SourceConfig {
75//! name: "my-source".to_string(),
76//! url: "http://example.com/data".to_string(),
77//! });
78//!
79//! // Reference entities by ID
80//! let pipeline = Pipeline {
81//! name: "my-pipeline".to_string(),
82//! source: Link::create_ref(source_id.to_string()),
83//! sink: Link::create_ref(sink_id.to_string()),
84//! };
85//!
86//! let pipeline_id = state.pipelines.create(pipeline);
87//!
88//! // Query entities
89//! let (id, entity) = state.get_entity(&pipeline_id.to_string(), StateEntry::Pipeline).unwrap();
90//!
91//! // List all entities
92//! let summaries = state.list_entities(None);
93//!
94//! // Search entities
95//! let results = state.search_entities("pipeline");
96//!
97//! // Update entities
98//! state.pipelines.update(&pipeline_id.to_string(), updated_pipeline)?;
99//!
100//! // Delete entities
101//! state.pipelines.remove(&pipeline_id.to_string())?;
102//! ```
103//!
104//! ## Web API Generation
105//!
106//! Generate complete REST APIs with `OpenAPI` documentation using the `axum` feature:
107//!
108//! ```rust,ignore
109//! #[stately::state(openapi)]
110//! pub struct State {
111//! pipelines: Pipeline,
112//! }
113//!
114//! #[stately::axum_api(State, openapi, components = [link_aliases::PipelineLink])]
115//! pub struct AppState {}
116//!
117//! #[tokio::main]
118//! async fn main() {
119//! let app_state = AppState::new(State::new());
120//!
121//! let app = axum::Router::new()
122//! .nest("/api/v1/entity", AppState::router(app_state.clone()))
123//! .with_state(app_state);
124//!
125//! // Routes automatically generated:
126//! // PUT /api/v1/entity - Create entity
127//! // GET /api/v1/entity - List entities
128//! // GET /api/v1/entity/{id}?type=<type> - Get entity
129//! // POST /api/v1/entity/{id} - Update entity
130//! // PATCH /api/v1/entity/{id} - Patch entity
131//! // DELETE /api/v1/entity/{entry}/{id} - Delete entity
132//!
133//! // OpenAPI spec available at:
134//! let openapi = AppState::openapi();
135//! }
136//! ```
137//!
138//! ### Event-Driven Persistence
139//!
140//! The `axum_api` macro generates a `ResponseEvent` enum and middleware for event-driven
141//! persistence:
142//!
143//! ```rust,ignore
144//! use tokio::sync::mpsc;
145//!
146//! #[tokio::main]
147//! async fn main() {
148//! let (event_tx, mut event_rx) = mpsc::channel(100);
149//!
150//! let app_state = AppState::new(State::new());
151//!
152//! // Attach event middleware - handlers emit events after state updates
153//! let app = axum::Router::new()
154//! .nest("/api/v1/entity", AppState::router(app_state.clone()))
155//! .layer(axum::middleware::from_fn(
156//! AppState::event_middleware(event_tx)
157//! ))
158//! .with_state(app_state);
159//!
160//! // Handle events in background task
161//! tokio::spawn(async move {
162//! while let Some(event) = event_rx.recv().await {
163//! match event {
164//! ResponseEvent::Created { id, entity } => {
165//! // Persist to database
166//! }
167//! ResponseEvent::Updated { id, entity } => {
168//! // Update in database
169//! }
170//! ResponseEvent::Deleted { id, entry } => {
171//! // Delete from database
172//! }
173//! }
174//! }
175//! });
176//! }
177//! ```
178//!
179//! The middleware is generic and can convert to your own event types:
180//!
181//! ```rust,ignore
182//! enum MyEvent {
183//! Api(ResponseEvent),
184//! // ... other variants
185//! }
186//!
187//! impl From<ResponseEvent> for MyEvent {
188//! fn from(event: ResponseEvent) -> Self {
189//! MyEvent::Api(event)
190//! }
191//! }
192//!
193//! // Use with your custom event type
194//! let app = axum::Router::new()
195//! .layer(axum::middleware::from_fn(
196//! AppState::event_middleware::<MyEvent>(event_tx)
197//! ))
198//! .with_state(app_state);
199//! ```
200//!
201//! The `axum_api` macro generates:
202//! - Handler methods on your struct (`create_entity`, `list_entities`, etc.)
203//! - `router()` method returning configured Axum router
204//! - `ResponseEvent` enum with Created, Updated, and Deleted variants
205//! - `event_middleware()` method for event-driven persistence
206//! - `OpenAPI` documentation when `openapi` parameter is specified
207//!
208//! ## Generated Code Reference
209//!
210//! ### What the `state` Macro Generates
211//!
212//! When you use `#[stately::state]` on your struct, the macro generates:
213//!
214//! 1. **`StateEntry` enum** - Used to specify entity types in queries: ```rust,ignore pub enum
215//! StateEntry { Pipeline, Source, Sink, } ```
216//!
217//! 2. **`Entity` enum** - Type-erased wrapper for all entity types: ```rust,ignore pub enum Entity
218//! { Pipeline(Pipeline), Source(SourceConfig), Sink(SinkConfig), } ```
219//!
220//! 3. **`link_aliases` module** - Convenient type aliases for `Link<T>`: ```rust,ignore pub mod
221//! link_aliases { pub type PipelineLink = ::stately::Link<Pipeline>; pub type SourceLink =
222//! ::stately::Link<SourceConfig>; pub type SinkLink = ::stately::Link<SinkConfig>; } ```
223//!
224//! ### What the `axum_api` Macro Generates
225//!
226//! When you use `#[stately::axum_api(State)]`, the macro generates:
227//!
228//! 1. **Handler methods** - REST API handlers as methods on your struct:
229//! - `create_entity()`, `list_entities()`, `get_entity_by_id()`
230//! - `update_entity()`, `patch_entity_by_id()`, `remove_entity()`
231//!
232//! 2. **`router()` method** - Returns configured Axum router with all routes
233//!
234//! 3. **`ResponseEvent` enum** - Events emitted after CRUD operations: ```rust,ignore pub enum
235//! ResponseEvent { Created { id: EntityId, entity: Entity }, Updated { id: EntityId, entity:
236//! Entity }, Deleted { id: EntityId, entry: StateEntry }, } ```
237//!
238//! 4. **`event_middleware()` method** - Generic middleware for event-driven persistence:
239//! ```rust,ignore pub fn event_middleware<T>( event_tx: tokio::sync::mpsc::Sender<T> ) -> impl
240//! Fn(...) + Clone where T: From<ResponseEvent> + Send + 'static ```
241//!
242//! 5. **`OpenAPI` trait** (when `openapi` parameter used) - Implements `utoipa::OpenApi`
243//!
244//! ## Feature Flags
245//!
246//! - `openapi` (default) - Enable `OpenAPI` schema generation via `utoipa`
247//! - `axum` - Enable Axum web framework integration (implies `openapi`)
248//!
249//! ## Examples
250//!
251//! See the [examples directory](https://github.com/georgeleepatterson/stately/tree/main/examples) for:
252//!
253//! - `basic.rs` - Core CRUD operations and entity relationships
254//! - `axum_api.rs` - Web API generation with Axum
255
256pub mod collection;
257pub mod entity;
258pub mod error;
259pub mod link;
260pub mod traits;
261
262// Re-export dependencies that are used in generated code
263// Re-export derive macros
264// Re-export key types
265pub use collection::{Collection, Singleton};
266pub use entity::{EntityId, Summary};
267pub use error::{Error, Result};
268pub use hashbrown;
269pub use link::Link;
270#[cfg(feature = "axum")]
271pub use stately_derive::axum_api;
272pub use stately_derive::{entity, state};
273#[cfg(feature = "axum")]
274pub use tokio;
275pub use traits::{StateCollection, StateEntity};
276
277/// Prelude module for convenient imports
278pub mod prelude {
279 pub use crate::collection::{Collection, Singleton};
280 pub use crate::entity::{EntityId, Summary};
281 pub use crate::link::Link;
282 pub use crate::traits::{StateCollection, StateEntity};
283 pub use crate::{Error, Result, entity, state};
284}