Skip to main content

Crate tephra

Crate tephra 

Source
Expand description

A DCB-compliant, immutable event store with global ordering.

Tephra is a Dynamic Consistency Boundary (DCB) event store. Instead of a static consistency boundary baked into an aggregate, the boundary is derived per decision from a Query. Events carry an EventType plus a set of Tags, so one event can belong to several entities at once, and a decision reads exactly the events it depends on and guards exactly those on append (an AppendCondition).

This crate is the embedded engine: the durable log, the single writer, the index, and the read paths. Use it directly in-process, or reach it over the network with the tephra-server TCP server and the tephra-client client.

§Design

The log is the source of truth and everything else is derived. Data is written once, never updated and never deleted, keyed by a dense monotonic Position assigned by the single writer. Indexes need no write-ahead log and no fsync on the write path, because they can be rebuilt by replaying the log. The ARCHITECTURE.md document in the repository records the full rationale and the alternatives that were rejected.

§Example

use tephra::{
    AppendCondition, Event, EventType, Position, Query, QueryItem, SegmentConfig,
    SegmentSet, Tag, Tags, WriteCoordinator, WriterConfig,
};

// Open (or create) a log directory and start the single-writer coordinator.
let set = SegmentSet::open("tephra-data", SegmentConfig::new(256 * 1024 * 1024))?;
let (coordinator, handle) = WriteCoordinator::start(set, WriterConfig::default())?;

// Build a packed event, then append it guarded so it fails if course:c1 already exists.
let ty = EventType::new("CourseOpened")?;
let tags = Tags::new([Tag::new("course:c1")?])?;
let event = Event::new(&ty, &tags, br#"{"course":"c1","seats":30}"#)?;
let guard = AppendCondition::new(Query::item(QueryItem::with_tags(
    Tags::new([Tag::new("course:c1")?])?,
)));
handle.append(vec![event], Some(guard))?;

// Reads run on the caller's thread over a snapshot published at each commit. `read` returns
// a lending iterator, so it is consumed with `while let`, not a `for` loop.
let query = Query::item(QueryItem::with_tags(Tags::new([Tag::new("course:c1")?])?));
let mut reads = handle.read(&query, Position::ZERO, None);
while let Some(item) = reads.next() {
    let seq = item?;
    println!("{} {}", seq.position, seq.event.event_type());
}

// Shutdown joins the writer thread and flushes cleanly.
coordinator.shutdown();

Re-exports§

pub use event::Event;
pub use event::EventRef;
pub use log::set::PositionRange;
pub use log::set::SegmentConfig;
pub use log::set::SegmentSet;
pub use query::Matches;
pub use read::DEFAULT_MAX_BATCH_EVENTS;
pub use read::ReadConfig;
pub use read::ReadError;
pub use read::ReadHandle;
pub use read::Subscription;
pub use read::WaitOutcome;
pub use writer::AppendError;
pub use writer::ConflictSite;
pub use writer::WriteCoordinator;
pub use writer::WriteHandle;
pub use writer::WriterConfig;

Modules§

event
Event codec.
index
Layer 3: the derived index.
log
query
Query match predicate over an encoded event.
read
Layer 5: off-thread read paths.
writer
Layer 2: the write coordinator.

Structs§

AppendCondition
The guard on an append call.
EventType
An event type. An arbitrary opaque, non-empty string (the spec never parses it), stored as an exact-sized Box<str>.
Position
QueryItem
One alternative in a Query: a type constraint AND a tag constraint.
Tag
A tag, e.g. course:c1. An arbitrary opaque, non-empty string (the spec does not split it into key/value), stored as an exact-sized Box<str>. Like EventType, it has no Deref; use as_str, AsRef<str>, or Display.
Tags
A sorted, duplicate-free set of tags.

Enums§

NameError
Error constructing an EventType or Tag.
Query
A query: a set of QueryItems OR’d together, or the catch-all Query::All.
TagsError
Error constructing Tags.

Constants§

MAX_NAME_LEN
Maximum length, in bytes, of an EventType or Tag. Each is stored with a fixed-width u16 length in the engine’s encoded header, so the field capacity is the limit.