mire 0.2.1

A small, generic PostgreSQL event-sourcing library: append-only event streams, aggregates with optimistic concurrency, and subscription-based projections (requires tokio + sqlx)
Documentation
# mire

A small **PostgreSQL event-sourcing** library for Rust. Append-only
event streams, optimistic concurrency, snapshots, replica-safe
projections via lease + fence-token, and an escape hatch for the
rare read-before-write case. Backed by `sqlx` + `tokio`. Only
dependency: Postgres.

## Install

```toml
[dependencies]
mire = "0.1"
```

Pre-1.0 — the API can shift between minor releases. Pin a patch
(`= "0.1.0"`) if you need stability across the next change.

## Quickstart

```rust
use mire::{Aggregate, EventData, EventStore};
use serde::{Deserialize, Serialize};
use sqlx::PgPool;

#[derive(Debug, Clone, Serialize, Deserialize, EventData)]
#[serde(tag = "type")]
#[mire(entity = "account")]
enum AccountEvent {
    Opened { owner: String },
    Deposited { amount: i64 },
}

#[derive(Debug, Default)]
struct Account {
    owner: String,
    balance: i64,
}

impl Aggregate for Account {
    type Event = AccountEvent;
    fn stream_category() -> &'static str { "account" }
    fn apply(&mut self, event: &AccountEvent) {
        match event {
            AccountEvent::Opened { owner } => self.owner = owner.clone(),
            AccountEvent::Deposited { amount } => self.balance += amount,
        }
    }
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let pool = PgPool::connect("postgres://…").await?;
    let store = EventStore::new(pool);
    store.migrate().await?;                          // once at startup

    let mut account = store.load_or_default::<Account>("acc-123").await?;
    account.record(AccountEvent::Opened { owner: "Ada".into() });
    account.record(AccountEvent::Deposited { amount: 100 });
    store.save(&mut account).await?;

    let reloaded = store.load::<Account>("acc-123").await?.unwrap();
    assert_eq!(reloaded.state.balance, 100);
    Ok(())
}
```

That's the shape of every interaction with mire: **load → record →
save**, and **load again** to replay state from the log.

## Patterns

Each example below is a self-contained slice — one concept, one
runnable program, a short "why this pattern exists" block at the top.
Pick the one matching what you're trying to do.

| Pattern | When to use | Example |
|---|---|---|
| **Aggregate lifecycle** | The minimum shape. Defining events, an aggregate, load/record/save. Every other pattern builds on this. | [`bank_account`]crates/mire/examples/bank_account.rs |
| **Commands & validation** | Refusing invalid operations *before* recording events. The right place for "can't overdraft", "can't operate on a closed account", etc. | [`commands`]crates/mire/examples/commands.rs |
| **Concurrency conflicts** | Two writers race on the same aggregate. mire uses optimistic concurrency — no locks; the loser retries. | [`concurrency`]crates/mire/examples/concurrency.rs |
| **Projections (read models)** | Aggregates are great for writing; terrible for querying. Build a query-optimised table the runner keeps current. | [`projection`]crates/mire/examples/projection.rs |
| **Snapshots** | A single aggregate has accumulated >1k events and load is on a hot path. Cache the folded state so loads stay O(1). | [`snapshot`]crates/mire/examples/snapshot.rs |
| **Read-before-write across aggregates** | One business decision spans two aggregates (e.g. atomic money transfer). The escape hatch — used sparingly. | [`transaction_scope`]crates/mire/examples/transaction_scope.rs |
| **Multi-replica deployment** | Production runs multiple replicas. Exactly one drives each projection at a time, with automatic failover. | [`multi_replica`]crates/mire/examples/multi_replica.rs |
| **High-throughput writes** | Bulk ingest. Calling `save` once per event is 10× slower than necessary; batch them. | [`batched_write`]crates/mire/examples/batched_write.rs |

## Running the examples

```sh
mise run pg:up           # starts a local Postgres on :5434 via docker compose
cargo run --example bank_account
cargo run --example commands
# … etc.
```

If you don't use `mise`:

```sh
docker compose up -d postgres
export DATABASE_URL=postgres://mire:mire@localhost:5434/mire
cargo run --example bank_account
```

## What mire is not

- **Not a queue.** Events go into a per-stream log, not a Kafka-style
  topic. Use mire when your durable state IS your event log.
- **Not multi-region.** Single Postgres cluster, with the usual
  Postgres-replication options for read replicas / DR.
- **Not a CRUD ORM.** State lives in events; aggregates are
  reconstructed by replay. If you want to `UPDATE … SET …`, use sqlx
  directly.

## License

Licensed under [MIT](./LICENSE-MIT)