Mnesis is an event-sourcing and DDD kernel for Rust. It gives you composable traits, derive macros, and storage adapters to model a domain as a stream of events — with the type system, not a runtime message bus, holding it together.
The name is Greek for memory. That is what an event-sourced system keeps: an append-only record of everything that happened, from which current state is a pure fold.
use mnesis::*;
#[derive(Debug, Clone, DomainEvent)]
enum Event {
Deposited(Deposited),
Withdrawn(Withdrawn),
}
#[derive(Debug, Clone)]
struct Deposited { amount: u64 }
#[derive(Debug, Clone)]
struct Withdrawn { amount: u64 }
#[derive(Default, Debug, Clone)]
struct Account { balance: u64 }
impl AggregateState for Account {
type Event = Event;
fn initial() -> Self { Self::default() }
fn apply(mut self, event: &Event) -> Self {
match event {
Event::Deposited(e) => self.balance += e.amount,
Event::Withdrawn(e) => self.balance -= e.amount,
}
self
}
}
#[mnesis::aggregate(state = Account, error = BankError, id = AccountId)]
struct BankAccount;
struct Deposit { amount: u64 }
impl Handle<Deposit> for BankAccount {
fn handle(state: &Account, cmd: Deposit) -> Result<Events<Event>, BankError> {
Ok(events![Event::Deposited(Deposited { amount: cmd.amount })])
}
}Concrete event enums, not trait objects. Events are plain Rust enums.
match is exhaustive — the compiler catches every missing handler at build
time. No Box<dyn Any>, no runtime downcasting, no message-bus plumbing.
no_std down to the persistence layer. Not just your domain types — the
whole core stack builds for bare-metal ARM (thumbv7em) and WASM. The kernel
is no_std and pure core — no allocator at all: Events<E, N> is
ArrayVec-backed (stack, any N), so it never touches the heap (N = 0, the
default, is just a single event). mnesis-store — codecs, envelopes, the
subscription loop,
backup/restore, snapshots, projections — is no_std + alloc, and
mnesis-wake-nostd tails events on-device under an embassy executor. CI builds all
of it on thumbv7em on every push. See Embedded & no_std.
A zero-copy read path. One Decode<E> trait with an Output<'a> GAT covers
both owning codecs (serde returns E) and borrowing codecs (rkyv returns
&'a Archived<E>, bytemuck returns &'a E). Event streams are plain
futures::Stream<Item = Result<PersistedEnvelope, _>>, so the whole
futures::StreamExt / TryStreamExt surface is free. The on-disk row format
aligns every payload to a 16-byte boundary, so zero-copy decoders get sound
&T references with no copy and no realignment.
Mnesis is a small stack of crates layered by a load-bearing boundary: pure kernel → persistence edge → infrastructure adapters. You depend on only the layers you need.
| Crate | Layer | What it gives you |
|---|---|---|
mnesis | Kernel | Aggregates, events, versioning, command handling, sagas — pure core, no_std, no-alloc |
mnesis-macros | Kernel | Derive macros: DomainEvent, #[aggregate], #[transforms] |
mnesis-store | Edge | Codecs, envelopes, repositories, subscriptions, snapshots, backup/restore — no_std + alloc |
mnesis-wake / mnesis-wake-nostd | Core | Wake registries that drive live subscriptions (-nostd is no_std + alloc, for on-device tailing) |
mnesis-fjall / mnesis-postgres / mnesis-inmemory | Adapter | Concrete stores implementing the persistence seam |
Projection is provided as primitives (Projector, PersistTrigger,
Subscription, SnapshotStore). Mnesis ships no event-loop runner — the
loop is the consumer's, so mnesis stays a kernel and never a runtime.
- You want event sourcing in Rust without a framework taking over your runtime.
- You are targeting IoT / mobile and care about allocation and binary size —
the kernel and the persistence layer are
no_std, down to bare-metal ARM (see Embedded &no_std). - You want the compiler to enforce your domain invariants, not a bag of
dyn Anydowncasts.
- Quickstart — a bank account, end to end, in memory.
- Embedded &
no_std— what runs on bare-metal ARM, and what still pullsstd. - Closing the Books — the modeling discipline that keeps streams short so you rarely need snapshots.
- Stability & the 1.0 Promise — exactly what pinning
a
1.xmnesis crate will buy you. - API docs on docs.rs — the reference track.
Status: Mnesis is experimental with an unstable API. The kernel is well-tested (proptest, miri, mutation testing, trybuild); the store and adapters are under active development.