Introduction

Event sourcing for Rust — no Box<dyn>, no runtime downcasting, no hidden allocations.

Mnesis

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.

rust
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 })])
    }
}

Why Mnesis?

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&lt;'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.

The crate map

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.

CrateLayerWhat it gives you
mnesisKernelAggregates, events, versioning, command handling, sagas — pure core, no_std, no-alloc
mnesis-macrosKernelDerive macros: DomainEvent, #[aggregate], #[transforms]
mnesis-storeEdgeCodecs, envelopes, repositories, subscriptions, snapshots, backup/restore — no_std + alloc
mnesis-wake / mnesis-wake-nostdCoreWake registries that drive live subscriptions (-nostd is no_std + alloc, for on-device tailing)
mnesis-fjall / mnesis-postgres / mnesis-inmemoryAdapterConcrete 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.

Who is this for?

  • 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 Any downcasts.

Where to next

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.