pub struct MiniStore { /* private fields */ }Expand description
A durable, append-only log store for serializable records.
MiniStore manages a journal file (or set of rotated segments) on disk.
It provides two core operations:
§Concurrency
MiniStore is not thread-safe by itself. To share it across tasks, wrap it in a
synchronization primitive like Arc<RwLock<MiniStore>> (for write-heavy workloads)
or Arc<Mutex<MiniStore>>.
§Durability & Rotation
- Every
append()performs anfsyncbefore returning. - When the active segment reaches
max_bytes_per_segment, it is rotated:- Closed, renamed to
journal.jsonl.001, etc. - A new active file is created.
- Closed, renamed to
- Only
max_segmentsfiles are kept; older ones are deleted. replay()automatically reads all segments in correct order.
Implementations§
Source§impl MiniStore
impl MiniStore
Sourcepub async fn open<P: AsRef<Path>>(path: P) -> Result<Self>
pub async fn open<P: AsRef<Path>>(path: P) -> Result<Self>
Opens a ministore journal at the given path with default options.
Equivalent to MiniStoreOptions::new().open(path).
§Behavior
- If the file does not exist, it is created and initialized with the magic header.
- If the file exists and is empty, the magic header is written.
- If the file exists and is non-empty, it is assumed to be a valid journal
(the magic header must already be present; validated during [
replay]).
§Errors
Returns an error if:
- The path is not writable.
- Parent directories cannot be created.
- Disk I/O fails during magic header write.
Sourcepub async fn append<R>(&mut self, record: &R) -> Result<()>where
R: Serialize,
pub async fn append<R>(&mut self, record: &R) -> Result<()>where
R: Serialize,
Appends a serializable record to the journal and ensures it is durably stored.
The record is serialized as a single JSON line and immediately fsynced to disk.
This operation is atomic either the entire record is written, or nothing is.
If the active segment exceeds max_bytes_per_segment, it is rotated before return.
§Guarantees
After this method returns Ok(()):
- The record is visible in subsequent [
replay] calls. - The record will survive process termination or system crash.
§Errors
Returns an error if:
- Serialization fails (e.g., unsupported type).
- Disk write fails (e.g., full disk).
fsyncfails (e.g., I/O error).
§Performance
This is a slow operation due to the fsync. Use it for critical metadata,
not high-frequency data.
Sourcepub async fn replay<R, P: AsRef<Path>>(path: P) -> Result<Vec<R>>where
R: DeserializeOwned,
pub async fn replay<R, P: AsRef<Path>>(path: P) -> Result<Vec<R>>where
R: DeserializeOwned,
Replays all records from a journal (including rotated segments) as a Vec of strongly-typed values.
This is a static method it does not require an open MiniStore instance.
§Behavior
- Scans the directory for files matching
base.001,base.002, etc. - Reads segments in ascending order (
.001�.002� … � active file). - Validates magic header in each file.
- If the journal does not exist or is empty, returns an empty
Vec.
§Errors
Returns an error if:
- Any segment has a missing or invalid magic header.
- Any line fails to deserialize as type
R. - File I/O fails (e.g., permission denied).
§Example
let events: Vec<Event> = MiniStore::replay("/tmp/events.log").await?;Sourcepub async fn stream<T>(path: impl AsRef<Path>) -> Result<JournalStream<T>>where
T: for<'de> Deserialize<'de>,
pub async fn stream<T>(path: impl AsRef<Path>) -> Result<JournalStream<T>>where
T: for<'de> Deserialize<'de>,
Returns a stream (line iterator) over the records in a single journal file.
Each line is parsed on-demand as Result<T, MiniStoreError>.
This avoids loading the entire journal into memory.
Note: this function reads only one file, not rotated segments.
Use [replay] to read the full journal history.