eventfold
Your application state is a fold over an event log.
eventfold is a lightweight, append-only event log with derived views for Rust. Your application state is always a function of the log — computed by folding events through pure reducer functions. Snapshots cache the result for incremental performance. Zero infrastructure: just files in a directory.
state = events.reduce(reducer, initial_state)
Quick Example
use ;
use ;
use json;
This is the entire data layer. No schema, no migrations, no ORM. The log file is the database. The reducer is the schema.
Core Concepts
Events are append-only JSON lines in a log file. Each event has a type, arbitrary JSON data, and a timestamp — plus optional fields for event ID, actor, and metadata. The log never rewrites or deletes events.
Reducers are pure functions fn(State, &Event) -> State that give events meaning. They fold events into application state. Different reducers over the same log produce different views — same data, different lenses.
Views are derived state materialized by folding events through a reducer. Snapshots cache the result so subsequent refreshes only process new events. A view is always rebuildable from the full log.
Installation
cargo add eventfold
Features
- Append-only event log (JSONL)
- Derived views via pure reducer functions
- Incremental snapshots — only process new events on refresh
- Automatic log rotation with zstd compression
- Integrity checking via xxhash — auto-rebuild on corruption
- Crash-safe — atomic snapshot writes, graceful recovery from partial writes
- Structured events with optional ID, actor, and metadata fields
- Conditional append — optimistic concurrency via offset + hash checks
- File locking — exclusive writer prevents multi-process corruption
- Poll-based tailing — lightweight
has_new_eventscheck for change detection - Blocking tail — OS-level file watching via
wait_for_eventsfor sub-millisecond notification - Reader/writer separation — clone-friendly readers, exclusive writer
- Zero infrastructure — just files in a directory
- Single-crate, minimal dependencies, no async
Data Layout
data/
archive.jsonl.zst # compressed event history (zstd frames)
app.jsonl # active log, plain text, append-only
views/
todos.snapshot.json # {"state": {...}, "offset": 12840, "hash": "a3f2..."}
stats.snapshot.json
Only two data files: the compressed archive and the active log. Views are cached snapshots that are always rebuildable.
When to Use
- Personal tools, CLIs, small web apps
- Prototypes and MVPs where you want persistence without a database
- Applications where the event history is valuable (audit logs, undo, time travel)
- Embedded state in single-process applications
- Any case where "just files in a directory" is the right level of infrastructure
When Not to Use
- High-concurrency writers (file locking ensures single-writer safety, but throughput is limited)
- Distributed systems
- High write throughput (every append flushes to disk)
- Applications needing ad-hoc queries or indexes beyond what reducers build
- Anything requiring encryption or access control at the storage layer
Examples
cargo run --example todo_cli # minimal CLI todo app
cargo run --example multi_view # same log, multiple views
cargo run --example rebuild # changing a reducer and rebuilding
cargo run --example rotation # manual and auto rotation
cargo run --example time_travel # replaying to a specific point
cargo run --example notes_cli # tagged notes with search
cargo run --example poll_tail # poll-based tailing with has_new_events
cargo run --example blocking_tail # blocking tail with wait_for_events
A full-stack Leptos web app example lives in examples-leptos/todo-app/.
Documentation
- Concepts & Guide — how it works, writing reducers, schema evolution, crash safety, debugging
- API Reference — rustdoc for all public types and methods
License
MIT OR Apache-2.0