Skip to main content

evento_core/
lib.rs

1//! Core types and traits for the Evento event sourcing library.
2//!
3//! This crate provides the foundational abstractions for building event-sourced applications
4//! with Evento. It defines the core traits, types, and builders used throughout the framework.
5//!
6//! # Features
7//!
8//! - **`macro`** (default) - Procedural macros from `evento-macro`
9//! - **`group`** - Multi-executor support via `EventoGroup`
10//! - **`rw`** - Read-write split executor pattern via `Rw`
11//! - **`sqlite`**, **`mysql`**, **`postgres`** - Database support via sqlx
12//! - **`fjall`** - Embedded key-value storage with Fjall
13//!
14//! # Core Concepts
15//!
16//! ## Events
17//!
18//! Events are immutable facts that represent something that happened in your domain.
19//! The [`Event`] struct stores serialized event data with metadata:
20//!
21//! ```rust,ignore
22//! // Define events using the aggregator macro
23//! #[evento::aggregator]
24//! pub enum BankAccount {
25//!     AccountOpened { owner_id: String, initial_balance: i64 },
26//!     MoneyDeposited { amount: i64 },
27//! }
28//! ```
29//!
30//! ## Executor
31//!
32//! The [`Executor`] trait abstracts event storage and retrieval. Implementations
33//! handle persisting events, querying, and managing subscriptions.
34//!
35//! ## Aggregator Builder
36//!
37//! Use [`create()`] or [`aggregator()`] to build and commit events:
38//!
39//! ```rust,ignore
40//! use evento::metadata::Metadata;
41//!
42//! let id = evento::create()
43//!     .event(&AccountOpened { owner_id: "user1".into(), initial_balance: 1000 })
44//!     .metadata(&Metadata::default())
45//!     .commit(&executor)
46//!     .await?;
47//! ```
48//!
49//! ## Projections
50//!
51//! Build read models by replaying events. Use the [`projection`] module for loading
52//! aggregate state:
53//!
54//! ```rust,ignore
55//! use evento::projection::Projection;
56//!
57//! #[evento::projection]
58//! #[derive(Debug)]
59//! pub struct AccountView {
60//!     pub balance: i64,
61//! }
62//!
63//! #[evento::handler]
64//! async fn on_deposited(
65//!     event: Event<MoneyDeposited>,
66//!     projection: &mut AccountView,
67//! ) -> anyhow::Result<()> {
68//!     projection.balance += event.data.amount;
69//!     Ok(())
70//! }
71//!
72//! let result = Projection::<_, AccountView>::new::<BankAccount>()
73//!     .handler(on_deposited())
74//!     .load("account-123")
75//!     .execute(&executor)
76//!     .await?;
77//! ```
78//!
79//! ## Subscriptions
80//!
81//! Process events continuously in real-time. See the [`subscription`] module:
82//!
83//! ```rust,ignore
84//! use evento::subscription::SubscriptionBuilder;
85//!
86//! #[evento::subscription]
87//! async fn on_deposited<E: Executor>(
88//!     context: &Context<'_, E>,
89//!     event: Event<MoneyDeposited>,
90//! ) -> anyhow::Result<()> {
91//!     // Perform side effects
92//!     Ok(())
93//! }
94//!
95//! let subscription = SubscriptionBuilder::<Sqlite>::new("deposit-processor")
96//!     .handler(on_deposited())
97//!     .routing_key("accounts")
98//!     .start(&executor)
99//!     .await?;
100//! ```
101//!
102//! ## Cursor-based Pagination
103//!
104//! GraphQL-style pagination for querying events. See the [`cursor`] module.
105//!
106//! # Modules
107//!
108//! - [`context`] - Type-safe request context for storing arbitrary data
109//! - [`cursor`] - Cursor-based pagination types and traits
110//! - [`metadata`] - Standard event metadata types
111//! - [`projection`] - Projections for loading aggregate state
112//! - [`subscription`] - Continuous event processing with subscriptions
113//!
114//! # Example
115//!
116//! ```rust,ignore
117//! use evento::{Executor, metadata::Metadata, cursor::Args, ReadAggregator};
118//!
119//! // Create and persist an event
120//! let id = evento::create()
121//!     .event(&AccountOpened { owner_id: "user1".into(), initial_balance: 1000 })
122//!     .metadata(&Metadata::default())
123//!     .commit(&executor)
124//!     .await?;
125//!
126//! // Query events with pagination
127//! let events = executor.read(
128//!     Some(vec![ReadAggregator::id("myapp/Account", &id)]),
129//!     None,
130//!     Args::forward(10, None),
131//! ).await?;
132//! ```
133
134mod aggregator;
135pub mod context;
136pub mod cursor;
137mod executor;
138pub mod metadata;
139pub mod projection;
140pub mod subscription;
141
142#[cfg(feature = "macro")]
143pub use evento_macro::*;
144
145pub use aggregator::*;
146pub use executor::*;
147pub use subscription::RoutingKey;
148
149use std::fmt::Debug;
150use ulid::Ulid;
151
152use crate::{cursor::Cursor, metadata::Metadata};
153
154/// Cursor data for event pagination.
155///
156/// Used internally for base64-encoded cursor values in paginated queries.
157/// Contains the essential fields needed to uniquely identify an event's position.
158#[derive(Debug, bitcode::Encode, bitcode::Decode)]
159pub struct EventCursor {
160    /// Event ID (ULID string)
161    pub i: String,
162    /// Event version
163    pub v: u16,
164    /// Event timestamp (Unix timestamp in seconds)
165    pub t: u64,
166    /// Sub-second precision (milliseconds)
167    pub s: u32,
168}
169
170/// A stored event in the event store.
171///
172/// Events are immutable records of facts that occurred in your domain.
173/// They contain serialized data and metadata, along with positioning
174/// information for the aggregate they belong to.
175///
176/// # Fields
177///
178/// - `id` - Unique event identifier (ULID format for time-ordering)
179/// - `aggregator_id` - The aggregate instance this event belongs to
180/// - `aggregator_type` - Type name like `"myapp/BankAccount"`
181/// - `version` - Sequence number within the aggregate (for optimistic concurrency)
182/// - `name` - Event type name like `"AccountOpened"`
183/// - `routing_key` - Optional key for event distribution/partitioning
184/// - `data` - Serialized event payload (bitcode format)
185/// - `metadata` - Event metadata (see [`metadata::Metadata`])
186/// - `timestamp` - When the event occurred (Unix seconds)
187/// - `timestamp_subsec` - Sub-second precision (milliseconds)
188///
189/// # Serialization
190///
191/// Event data is serialized using [bitcode](https://crates.io/crates/bitcode)
192/// for compact binary representation. Use [`metadata::Event`] to deserialize typed events.
193#[derive(Debug, Clone, PartialEq, Default)]
194pub struct Event {
195    /// Unique event identifier (ULID)
196    pub id: Ulid,
197    /// ID of the aggregate this event belongs to
198    pub aggregator_id: String,
199    /// Type name of the aggregate (e.g., "myapp/User")
200    pub aggregator_type: String,
201    /// Version number of the aggregate after this event
202    pub version: u16,
203    /// Event type name
204    pub name: String,
205    /// Optional routing key for event distribution
206    pub routing_key: Option<String>,
207    /// Serialized event data (bitcode format)
208    pub data: Vec<u8>,
209    /// Event metadata
210    pub metadata: Metadata,
211    /// Unix timestamp when the event occurred (seconds)
212    pub timestamp: u64,
213    /// Sub-second precision (milliseconds)
214    pub timestamp_subsec: u32,
215}
216
217impl Cursor for Event {
218    type T = EventCursor;
219
220    fn serialize(&self) -> Self::T {
221        EventCursor {
222            i: self.id.to_string(),
223            v: self.version,
224            t: self.timestamp,
225            s: self.timestamp_subsec,
226        }
227    }
228}
229
230impl cursor::Bind for Event {
231    type T = Self;
232
233    fn sort_by(data: &mut Vec<Self::T>, is_order_desc: bool) {
234        if !is_order_desc {
235            data.sort_by(|a, b| {
236                if a.timestamp_subsec != b.timestamp_subsec {
237                    return a.timestamp_subsec.cmp(&b.timestamp_subsec);
238                }
239
240                if a.timestamp != b.timestamp {
241                    return a.timestamp.cmp(&b.timestamp);
242                }
243
244                if a.version != b.version {
245                    return a.version.cmp(&b.version);
246                }
247
248                a.id.cmp(&b.id)
249            });
250        } else {
251            data.sort_by(|a, b| {
252                if a.timestamp_subsec != b.timestamp_subsec {
253                    return b.timestamp_subsec.cmp(&a.timestamp_subsec);
254                }
255
256                if a.timestamp != b.timestamp {
257                    return b.timestamp.cmp(&a.timestamp);
258                }
259
260                if a.version != b.version {
261                    return b.version.cmp(&a.version);
262                }
263
264                b.id.cmp(&a.id)
265            });
266        }
267    }
268
269    fn retain(
270        data: &mut Vec<Self::T>,
271        cursor: <<Self as cursor::Bind>::T as Cursor>::T,
272        is_order_desc: bool,
273    ) {
274        data.retain(|event| {
275            if is_order_desc {
276                event.timestamp < cursor.t
277                    || (event.timestamp == cursor.t
278                        && (event.timestamp_subsec < cursor.s
279                            || (event.timestamp_subsec == cursor.s
280                                && (event.version < cursor.v
281                                    || (event.version == cursor.v
282                                        && event.id.to_string() < cursor.i)))))
283            } else {
284                event.timestamp > cursor.t
285                    || (event.timestamp == cursor.t
286                        && (event.timestamp_subsec > cursor.s
287                            || (event.timestamp_subsec == cursor.s
288                                && (event.version > cursor.v
289                                    || (event.version == cursor.v
290                                        && event.id.to_string() > cursor.i)))))
291            }
292        });
293    }
294}