Skip to main content

Crate ministore

Crate ministore 

Source
Expand description

A minimal, durable, append-only log store for serializable records.

ministore is not a state manager. It is a Write-Ahead Log (WAL) engine that provides:

  1. Durability: every record is written to disk and fsynced before the write returns.
  2. Replay: the entire log can be read back as a sequence of strongly-typed records.

The caller is responsible for:

  • Defining the record type (e.g., mutations, events, commands).
  • Applying records to in-memory state.
  • Managing concurrency (e.g., via Arc<RwLock<MiniStore>>).

This design makes ministore ideal for building:

  • Event-sourced systems
  • State machines with durable logs
  • Metadata stores (like Arcella’s component registry)

§Guarantees

  • Atomicity: each append() call writes exactly one record (as one JSON line).
  • Durability: after append() returns Ok(()), the record is on stable storage.
  • Ordering: records are replayed in the exact order they were appended.
  • Replay Safety: the journal format includes a magic header to prevent misuse.

§Journal Format

The on-disk journal is a text file in JSONL format:

// MINISTORE JOURNAL v0.1.4
{"Set":{"value":10}}
{"Inc":{"by":5}}
  • Line 1: magic header (for versioning and validation).
  • Line N (N e 2): one JSON-serialized record per line.

The format is human-readable and easy to inspect/debug with standard tools (cat, jq, etc.).

§Segmented Rotation

To prevent unbounded growth, ministore supports segmented WAL rotation:

  • When a segment reaches max_bytes_per_segment, it is renamed to journal.jsonl.001, etc.
  • Only up to max_segments files are retained. Oldest are deleted automatically.
  • replay() reads all segments in order: .001, .002, …, then active journal.jsonl.

§Example: Simple Counter

use ministore::MiniStore;
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
enum CounterMutation {
    Set { value: u32 },
    Inc { delta: u32 },
}

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

impl Counter {
    fn apply(&mut self, mutation: &CounterMutation) {
        match mutation {
            CounterMutation::Set { value } => self.value = *value,
            CounterMutation::Inc { delta } => self.value += *delta,
        }
    }
}

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

    // 1. Open the store
    let mut store = MiniStore::open(&path).await?;

    // 2. Append mutations
    store.append(&CounterMutation::Set { value: 100 }).await?;
    store.append(&CounterMutation::Inc { delta: 25 }).await?;

    // 3. Rebuild state from log
    let mut counter = Counter::default();
    let records: Vec<CounterMutation> = MiniStore::replay(&path).await?;
    for record in records {
        counter.apply(&record);
    }

    assert_eq!(counter.value, 125);
    Ok(())
}

Structs§

JournalStream
A stream over records in a single journal file.
MiniStore
A durable, append-only log store for serializable records.
MiniStoreOptions
Configuration for MiniStore with support for segmented WAL rotation.

Enums§

MiniStoreError

Type Aliases§

Result
A specialized Result type for ministore operations.