ministate 0.1.0

A minimal, embeddable state manager with durable WAL logging and optional snapshot support. Ideal for component registries, metadata stores, and local state machines in edge applications.
Documentation

A minimal, in-memory state manager with durable WAL logging.

ministate provides a simple yet robust way to maintain mutable application state that survives process restarts, using an append-only Write-Ahead Log (WAL). It builds directly on [ministore] for reliable, human-readable journaling.

Core Features

  • In-memory state: Fast reads via RwLock, full Clone on demand.
  • Durable mutations: Every change is first written to disk (fsync-ed) before being applied.
  • Crash recovery: On startup, the state is reconstructed by replaying the WAL from the beginning.
  • Logical ordering: Each mutation is assigned a monotonically increasing sequence number.

Optional Snapshot Support (snapshot feature)

When the snapshot Cargo feature is enabled, ministate integrates with [minisnap] to:

  • Save full state snapshots to disk for faster recovery.
  • Enable future WAL compaction (truncating the log prefix after a snapshot is taken).

⚠️ Snapshotting is explicit — you must call create_snapshot() manually. WAL compaction is not yet implemented but will be added in a future release.

Concurrency Model

  • Reads: Concurrent via snapshot() (uses RwLock::read).
  • Writes: Serialized via apply() (uses RwLock::write).
  • No hidden threads: All I/O is explicit and await-driven.

Guarantees

  • Durability: If apply().await returns Ok(_), the mutation is guaranteed to be on disk.
  • Atomicity: The in-memory state is updated only if the WAL write succeeds.
  • Ordering: Mutations are applied in the exact order they appear in the WAL.
  • Recoverability: Full state restoration is possible from the WAL alone (or WAL + snapshot).

Use Cases

  • Stateful services that must recover after a crash (e.g., component registries, deployment specs).
  • Embedded systems with limited resources but strict durability requirements.
  • Local coordination primitives (e.g., leader election state, queue metadata).

Example

use ministate::{Mutator, StateManager};
use serde::{Deserialize, Serialize};

#[derive(Default, Clone, Serialize, Deserialize)]
struct Counter { value: u32 }

#[derive(Serialize, Deserialize)]
struct Inc { by: u32 }

impl Mutator<Counter> for Inc {
    fn apply(&self, state: &mut Counter) {
        state.value += self.by;
    }
}

#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let tmp = tempfile::tempdir()?;
    let dir = tmp.path();

    let mgr = StateManager::open(dir, "counter.wal.jsonl").await?;
    mgr.apply(Inc { by: 10 }).await?;
    assert_eq!(mgr.snapshot().await.value, 10);
    Ok(())
}