Skip to main content

EventStore

Trait EventStore 

Source
pub trait EventStore: Send + Sync {
Show 16 methods // Required methods fn stream_id(&self) -> &str; fn append<'life0, 'async_trait>( &'life0 mut self, event: Map<String, Value>, ) -> Pin<Box<dyn Future<Output = Result<Committed>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait; fn append_if<'life0, 'life1, 'life2, 'async_trait>( &'life0 mut self, kinds: Option<&'life1 [&'life2 str]>, decide: Decision, ) -> Pin<Box<dyn Future<Output = Result<Option<Committed>>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait; fn read_kinds<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, kinds: Option<&'life1 [&'life2 str]>, from_seq: u64, limit: usize, ) -> Pin<Box<dyn Future<Output = Result<Vec<Current>>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait; fn head<'life0, 'async_trait>( &'life0 self, ) -> Pin<Box<dyn Future<Output = Result<Option<u64>>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait; fn len<'life0, 'async_trait>( &'life0 self, ) -> Pin<Box<dyn Future<Output = Result<usize>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait; // Provided methods fn append_many<'life0, 'async_trait>( &'life0 mut self, events: Vec<Map<String, Value>>, ) -> Pin<Box<dyn Future<Output = Result<Vec<Committed>>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait { ... } fn append_at<'life0, 'async_trait>( &'life0 mut self, epoch_ms: u64, event: Map<String, Value>, ) -> Pin<Box<dyn Future<Output = Result<Committed>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait { ... } fn append_expecting<'life0, 'async_trait>( &'life0 mut self, expected: Expected, event: Map<String, Value>, ) -> Pin<Box<dyn Future<Output = Result<Committed>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait { ... } fn detach_append(&self, event: Map<String, Value>) -> Result<()> { ... } fn read<'life0, 'async_trait>( &'life0 self, from_seq: u64, limit: usize, ) -> Pin<Box<dyn Future<Output = Result<Vec<Current>>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait { ... } fn read_last<'life0, 'async_trait>( &'life0 self, n: usize, ) -> Pin<Box<dyn Future<Output = Result<Vec<Current>>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait { ... } fn is_empty<'life0, 'async_trait>( &'life0 self, ) -> Pin<Box<dyn Future<Output = Result<bool>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait { ... } fn database(&self) -> Option<&str> { ... } fn query<'life0, 'life1, 'async_trait>( &'life0 self, sql: &'life1 str, params: Vec<Value>, ) -> Pin<Box<dyn Future<Output = Result<Vec<Map<String, Value>>>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait { ... } fn query_timeout<'life0, 'life1, 'async_trait>( &'life0 self, sql: &'life1 str, params: Vec<Value>, timeout: Duration, ) -> Pin<Box<dyn Future<Output = Result<Vec<Map<String, Value>>>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait { ... }
}

Required Methods§

Source

fn stream_id(&self) -> &str

Which stream this handle is.

Source

fn append<'life0, 'async_trait>( &'life0 mut self, event: Map<String, Value>, ) -> Pin<Box<dyn Future<Output = Result<Committed>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Validate, stamp and append an event, returning its coordinates.

A rejected event leaves no trace and consumes no sequence number.

Source

fn append_if<'life0, 'life1, 'life2, 'async_trait>( &'life0 mut self, kinds: Option<&'life1 [&'life2 str]>, decide: Decision, ) -> Pin<Box<dyn Future<Output = Result<Option<Committed>>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait,

Decide inside the store’s serialization: read the stream, ask decide what to write, and append its answer in the same write.

kinds filters what the decision is shown, exactly as it filters EventStore::read_kinds (None = the whole stream), and the events arrive in seq order. What the decision writes is unfiltered — the event it returns is appended whatever its kind. Returning None records nothing and leaves the stream untouched.

Source

fn read_kinds<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, kinds: Option<&'life1 [&'life2 str]>, from_seq: u64, limit: usize, ) -> Pin<Box<dyn Future<Output = Result<Vec<Current>>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait,

Events of kinds with seq >= from_seq, at most limit, in seq order and already upcasted.

None reads every kind. Some(kinds) reads only those, and limit counts what came back rather than what was skipped. An empty slice selects nothing.

The kind selected on is the stored one, because the selection happens in the backend, before the upcaster chain runs. A step that renames a kind therefore obliges every filtered read of it to name both spellings — which is a cost the chain’s author pays knowingly, and the reason a rename is not free.

Source

fn head<'life0, 'async_trait>( &'life0 self, ) -> Pin<Box<dyn Future<Output = Result<Option<u64>>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

The highest seq, or None for an empty stream.

Fallible on purpose: a transient failure must not read as an empty stream, or a caller deciding open-vs-resume takes the wrong branch.

Source

fn len<'life0, 'async_trait>( &'life0 self, ) -> Pin<Box<dyn Future<Output = Result<usize>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Number of recorded events.

Provided Methods§

Source

fn append_many<'life0, 'async_trait>( &'life0 mut self, events: Vec<Map<String, Value>>, ) -> Pin<Box<dyn Future<Output = Result<Vec<Committed>>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Append events as one write, in the order given.

For facts that are one fact: two records of a single occurrence must not be separable by a reader, because a stream where the first is visible without the second is a stream that never existed.

All or nothing on a backend that can do it. The default below is the most a backend with no transaction can offer — it appends one at a time, so a failure part-way leaves what already landed. Both shipped backends override it.

Source

fn append_at<'life0, 'async_trait>( &'life0 mut self, epoch_ms: u64, event: Map<String, Value>, ) -> Pin<Box<dyn Future<Output = Result<Committed>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Record an event with a time it already has, rather than the wall clock of this call.

The backfill counterpart to EventStore::append: history from a system that has its own notion of when, brought in without discarding that timeline. Identical to append in every other respect — the envelope is validated, seq and the position are this store’s, the schema version is the author’s.

Use append for ordinary writes. For moving an eventsdb log, neither this nor append is the verb — import is, because it also carries the schema version each event was written under, which this cannot.

See crate::event for what the time coordinate means and why the verb rather than a field is what says which moment it is. In short: positions order the log, the coordinate never does, and this call does not enforce that the time it is given is at or after the stream’s head. Backfilling into an empty stream in chronological order keeps that property by construction.

Source

fn append_expecting<'life0, 'async_trait>( &'life0 mut self, expected: Expected, event: Map<String, Value>, ) -> Pin<Box<dyn Future<Output = Result<Committed>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Append event only if the stream’s head is expected, refusing with Error::HeadMismatch if it is not.

For a decision taken before this call, somewhere this process cannot reach: an HTTP client holding an ETag, a form somebody filled in, a message that sat in a queue. The caller read the stream at some head, went away, and is now saying “apply this only if nothing moved”.

EventStore::append_if is the other case and the better one wherever it applies — a decision folded from the stream at the write, which this cannot express because it compares one number and never looks at the events. Reach for this only when the decision could not have run under the lock.

The comparison happens inside the write. The head is read in the same transaction that inserts, so a writer arriving between them serializes behind the write lock rather than slipping past the check. A refused append leaves no trace and consumes no sequence number, exactly as a rejected EventStore::append does.

The default declines. A backend that cannot make the read and the insert one write has no honest answer here, and an imitation that checked separately would be worse than none — that gap between lookup and save is the documented weakness of the version-only form elsewhere.

Source

fn detach_append(&self, event: Map<String, Value>) -> Result<()>

Queue an append and return without waiting for it to land.

For a fact that has to be recorded from somewhere that cannot await: a Drop closing a session is the case this exists for, and there blocking is not allowed either, so neither .await nor block_on is available. Hence a plain fn — an async fn would need an executor the caller does not have.

&self, not &mut self. A Drop has whatever reference it has, and demanding a unique one is the difference between reachable from there and not. Every other write on this trait takes &mut self because it returns coordinates the caller is expected to use; this one returns nothing to hold.

The envelope is checked here; the write is not reported. A malformed event is refused synchronously, before the call returns. After that there is no channel back: a storage failure is dropped and nothing is woken, because the caller has already gone. Use it for the boundary record whose absence a reader would notice — the close of a session that would otherwise read as still open — not for a fact nothing else in the system knows.

Ordering is the backend’s to keep. The queued write must land before anything submitted after it; a backend that spawns a task which later calls EventStore::append has left the queue and races every other writer, which is worse than declining.

The default declines rather than dropping the event quietly. A store with nowhere to queue it has no way to keep that ordering, and silence here would leave a stream looking open for ever with nothing to say why.

Source

fn read<'life0, 'async_trait>( &'life0 self, from_seq: u64, limit: usize, ) -> Pin<Box<dyn Future<Output = Result<Vec<Current>>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Every event with seq >= from_seq, at most limit.

Source

fn read_last<'life0, 'async_trait>( &'life0 self, n: usize, ) -> Pin<Box<dyn Future<Output = Result<Vec<Current>>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

The last n events, in seq order.

A read from the end, which the range reads cannot express: they start at a seq and count forward, so “the last five” could only be asked for by reading the whole stream and throwing the front away. The default does exactly that, and both shipped backends override it.

Source

fn is_empty<'life0, 'async_trait>( &'life0 self, ) -> Pin<Box<dyn Future<Output = Result<bool>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Whether nothing has been recorded yet.

Source

fn database(&self) -> Option<&str>

Which database this stream lives in, or None for a backend that is not one.

Two handles answer with the same string exactly when they are the same database. It is an identity, not a path a caller should take apart.

Source

fn query<'life0, 'life1, 'async_trait>( &'life0 self, sql: &'life1 str, params: Vec<Value>, ) -> Pin<Box<dyn Future<Output = Result<Vec<Map<String, Value>>>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

Answer a caller’s own read-only SQL over the log.

Queries read the stored shape, not the upcasted one: every other read goes through the chain, but SQL runs against the bytes as they were written, because the chain is Rust and the query is SQLite’s. A caller reading across a schema change reads the versions it finds.

The limit is yours, and so is knowing whether it cut. There is no truncated flag here because the LIMIT is in your text, not in a parameter this store owns: ask for n + 1 rows and compare, which is the whole of what such a flag would tell you. Owning the limit instead would mean wrapping your statement to attach one, and this call deliberately never rewrites or parses what it is given — the read-only check above is SQLite’s answer about your text, not a reading of it.

Parameters are positional here, and only here. SQLite binds by name as well, and crate::Params is how a backend’s own query offers both — but this trait is used as Box<dyn EventStore>, a dispatchable method may not have type parameters, and argument-position impl Trait is one. So the trait takes the Vec and a caller wanting :name calls the concrete log.

The default refuses, because a store that is not a database has no answer to give.

Source

fn query_timeout<'life0, 'life1, 'async_trait>( &'life0 self, sql: &'life1 str, params: Vec<Value>, timeout: Duration, ) -> Pin<Box<dyn Future<Output = Result<Vec<Map<String, Value>>>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

EventStore::query with a bound on how long the statement may run.

Not the same thing as busy_timeout. That one bounds waiting for a lock; this bounds a statement that took its lock immediately and is simply expensive — a recursive CTE with a runaway bound, a join with no usable index. Nothing else stops one, and a caller that hands SQL to somebody else (a shell, a script, a user) cannot know in advance which kind it is getting.

A backend serving SQL from the same place it serves writes has the stronger reason: one expensive statement there stalls every append until it finishes.

The deadline is reported as Error::Timeout — the caller’s own bound arriving, not the database failing — so retrying a narrower query is the sensible next move.

The default declines rather than falling back to EventStore::query: running an unbounded statement for a caller who asked for a bound is the one answer that is worse than none.

Dyn Compatibility§

This trait is dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety".

Implementors§