pub trait ReadableEventStore:
Send
+ Sync
+ 'static {
Show 15 methods
// Required methods
fn read_history<'life0, 'life1, 'async_trait>(
&'life0 self,
workflow_id: &'life1 WorkflowId,
) -> Pin<Box<dyn Future<Output = Result<Vec<Event>, StoreError>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait;
fn read_history_from<'life0, 'life1, 'async_trait>(
&'life0 self,
workflow_id: &'life1 WorkflowId,
from_seq: u64,
) -> Pin<Box<dyn Future<Output = Result<Vec<Event>, StoreError>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait;
fn read_run_chain<'life0, 'life1, 'async_trait>(
&'life0 self,
workflow_id: &'life1 WorkflowId,
) -> Pin<Box<dyn Future<Output = Result<Vec<RunSummary>, StoreError>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait;
fn list_workflow_ids<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<Vec<WorkflowId>, StoreError>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait;
fn list_active<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<Vec<WorkflowId>, StoreError>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait;
fn list_paused<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<Vec<WorkflowId>, StoreError>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait;
fn query<'life0, 'life1, 'async_trait>(
&'life0 self,
filter: &'life1 WorkflowFilter,
) -> Pin<Box<dyn Future<Output = Result<Vec<WorkflowSummary>, StoreError>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait;
fn schedule_timer<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
workflow_id: &'life1 WorkflowId,
timer_id: &'life2 TimerId,
fire_at: DateTime<Utc>,
) -> Pin<Box<dyn Future<Output = Result<(), StoreError>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait;
fn expired_timers<'life0, 'async_trait>(
&'life0 self,
as_of: DateTime<Utc>,
) -> Pin<Box<dyn Future<Output = Result<Vec<TimerEntry>, StoreError>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait;
// Provided methods
fn set_owned_shards(&self, shards: Option<&[usize]>) { ... }
fn acquire_owned_shards(&self, shards: &[usize]) -> Result<(), StoreError> { ... }
fn acquire_owned_shard(&self, shard: usize) -> Result<(), StoreError> { ... }
fn is_current_owner(&self, shard: usize) -> bool { ... }
fn extend_owned_shards(&self, shards: &[usize]) { ... }
fn publish_shard_owner(&self, shard: usize) -> Result<(), StoreError> { ... }
}Expand description
Read and durable-timer contract for Aion event stores.
Required Methods§
Sourcefn read_history<'life0, 'life1, 'async_trait>(
&'life0 self,
workflow_id: &'life1 WorkflowId,
) -> Pin<Box<dyn Future<Output = Result<Vec<Event>, StoreError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn read_history<'life0, 'life1, 'async_trait>(
&'life0 self,
workflow_id: &'life1 WorkflowId,
) -> Pin<Box<dyn Future<Output = Result<Vec<Event>, StoreError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Reads the complete event history for workflow_id in ascending sequence order.
A workflow with no recorded events is observed as an empty history. This includes unknown
workflow identifiers: because the first append with expected_seq == 0 creates a workflow
implicitly, “unknown workflow” and “empty history” are the same observable state for reads.
This method must not return StoreError::NotFound for absent workflows.
Sourcefn read_history_from<'life0, 'life1, 'async_trait>(
&'life0 self,
workflow_id: &'life1 WorkflowId,
from_seq: u64,
) -> Pin<Box<dyn Future<Output = Result<Vec<Event>, StoreError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn read_history_from<'life0, 'life1, 'async_trait>(
&'life0 self,
workflow_id: &'life1 WorkflowId,
from_seq: u64,
) -> Pin<Box<dyn Future<Output = Result<Vec<Event>, StoreError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Reads the event history for workflow_id restricted to events with sequence number
greater than or equal to from_seq, in ascending sequence order.
This is the range-read primitive behind O(delta) WS resume: callers replaying from a cursor must not pay for the full history. Semantics:
from_seq <= 1is equivalent toSelf::read_history: sequence numbers start at 1, so every recorded event satisfies the bound.from_seqbeyond the current head returns an empty vector, never an error. Whether a beyond-head cursor is valid is protocol judgment, not store judgment: the WS resume protocol rejectsresume_from_seq > head + 1as an invalid cursor (ResumeCursorAheadOfHistory), but it makes that call by comparing the cursor against the head it observes — the store only answers which events exist at or after the requested sequence.- Unknown workflows behave exactly like
Self::read_historyfor unknown workflows: empty history, neverStoreError::NotFound, because “unknown workflow” and “empty history” are the same observable state for reads.
There is deliberately no default implementation: a read-all-then-filter fallback would
silently reintroduce O(history) behavior. Every backend must implement this as a real
range read (for SQL backends, an indexed seq >= ? range scan).
Sourcefn read_run_chain<'life0, 'life1, 'async_trait>(
&'life0 self,
workflow_id: &'life1 WorkflowId,
) -> Pin<Box<dyn Future<Output = Result<Vec<RunSummary>, StoreError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn read_run_chain<'life0, 'life1, 'async_trait>(
&'life0 self,
workflow_id: &'life1 WorkflowId,
) -> Pin<Box<dyn Future<Output = Result<Vec<RunSummary>, StoreError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Reads the concrete run chain for workflow_id in continuation order.
Sourcefn list_workflow_ids<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<Vec<WorkflowId>, StoreError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn list_workflow_ids<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<Vec<WorkflowId>, StoreError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Lists every workflow identifier that has at least one event in history.
Unlike Self::list_active, this includes terminal workflows and exists to let projection
repair jobs reconcile derived indexes against the authoritative event history.
Sourcefn list_active<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<Vec<WorkflowId>, StoreError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn list_active<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<Vec<WorkflowId>, StoreError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Lists workflow identifiers whose projected status is exactly
WorkflowStatus::Running.
Sourcefn list_paused<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<Vec<WorkflowId>, StoreError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn list_paused<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<Vec<WorkflowId>, StoreError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Lists workflow identifiers whose projected status is exactly
WorkflowStatus::Paused.
Mirrors Self::list_active with a == Paused exact-equality filter: it
is the durable source the dispatch-hold set is rebuilt from at startup and
at shard adoption, so a run paused before a kill -9 keeps its outbox rows
held after restart. A paused run is excluded from Self::list_active
(which filters == Running), so nothing else would repopulate the hold.
Sourcefn query<'life0, 'life1, 'async_trait>(
&'life0 self,
filter: &'life1 WorkflowFilter,
) -> Pin<Box<dyn Future<Output = Result<Vec<WorkflowSummary>, StoreError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn query<'life0, 'life1, 'async_trait>(
&'life0 self,
filter: &'life1 WorkflowFilter,
) -> Pin<Box<dyn Future<Output = Result<Vec<WorkflowSummary>, StoreError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Returns workflow summaries matching filter.
Sourcefn schedule_timer<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
workflow_id: &'life1 WorkflowId,
timer_id: &'life2 TimerId,
fire_at: DateTime<Utc>,
) -> Pin<Box<dyn Future<Output = Result<(), StoreError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
fn schedule_timer<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
workflow_id: &'life1 WorkflowId,
timer_id: &'life2 TimerId,
fire_at: DateTime<Utc>,
) -> Pin<Box<dyn Future<Output = Result<(), StoreError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Persists a durable timer for workflow_id that is due at fire_at.
Timer scheduling remains on the public store surface because timers are not workflow-history
appends and are used by the timer subsystem after the recorder has written TimerStarted.
Sourcefn expired_timers<'life0, 'async_trait>(
&'life0 self,
as_of: DateTime<Utc>,
) -> Pin<Box<dyn Future<Output = Result<Vec<TimerEntry>, StoreError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn expired_timers<'life0, 'async_trait>(
&'life0 self,
as_of: DateTime<Utc>,
) -> Pin<Box<dyn Future<Output = Result<Vec<TimerEntry>, StoreError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Returns durable timers whose fire_at is less than or equal to as_of.
Provided Methods§
Sourcefn set_owned_shards(&self, shards: Option<&[usize]>)
fn set_owned_shards(&self, shards: Option<&[usize]>)
Restrict every per-workflow enumeration (active workflows, timers, outbox
rows) to the named set of distribution shards this node owns, or restore
the own-all-shards default when shards is None.
This is the engine-lifecycle hook behind a multi-shard deployment: the
boot path tells the store which shards this node serves so recovery and
enumeration see only that node’s slice of the cluster’s state. The
default implementation is a deliberate no-op — single-shard backends
(in-memory, libSQL) own everything unconditionally, so a None or any
shard set leaves their behaviour byte-identical. Only a sharded backend
(haematite) overrides this to scope its enumeration. Decorators that wrap
another store must forward this call to their inner store.
Sourcefn acquire_owned_shards(&self, shards: &[usize]) -> Result<(), StoreError>
fn acquire_owned_shards(&self, shards: &[usize]) -> Result<(), StoreError>
Acquire-and-serve ownership of each named distribution shard BEFORE the boot path recovers or enumerates over them, so the node is the fenced owner and its replicated state is union-merged locally first.
This is the SS-2 election hook the engine boot path calls right after
Self::set_owned_shards and BEFORE startup recovery: a distributed
backend wins the per-shard election and becomes the live owner, so the
subsequent recovery reads see the full committed history for its shards.
The default implementation is a deliberate no-op returning Ok(()) —
single-shard / non-distributed backends (in-memory, libSQL, and the
single-node haematite mode) own everything unconditionally and elect
nothing, so boot stays byte-identical. Only a DISTRIBUTED sharded backend
overrides this to run the election. Decorators that wrap another store
must forward this call to their inner store.
§Errors
Returns StoreError::Backend when a distributed backend cannot win the
election or become the live owner of one of shards; the node must not
serve those shards in that case (fail-closed).
Sourcefn acquire_owned_shard(&self, shard: usize) -> Result<(), StoreError>
fn acquire_owned_shard(&self, shard: usize) -> Result<(), StoreError>
Acquire-and-serve ownership of a SINGLE distribution shard — the
per-shard primitive Self::acquire_owned_shards is a loop over, exposed
so the failover path can drive a per-shard abort seam: a clean election
loss on one shard (StoreError::NotOwner) drops only that shard rather
than failing the whole adoption batch (ADR-021 clean-partial).
The default implementation is a deliberate no-op returning Ok(()) —
single-shard / non-distributed backends own everything unconditionally and
elect nothing. Only a DISTRIBUTED sharded backend (haematite) overrides it.
Decorators that wrap another store must forward this call.
§Errors
Returns StoreError::NotOwner when a strictly higher ballot deposed this
candidate (a clean, droppable election loss), and StoreError::Backend
for a quorum-unavailable election or any transport fault (retryable).
Sourcefn is_current_owner(&self, shard: usize) -> bool
fn is_current_owner(&self, shard: usize) -> bool
Whether this node currently holds LIVE serve-authority for shard — it won
the per-shard election THIS process lifetime and has not been deposed
in-process.
This is the residual-window re-assertion the failover path uses to exclude a survivor that lost its epoch between winning acquire+publish and widening its enumeration scope (ADR-021 clean-partial). It is a POINT-IN-TIME ADVISORY, not a durable lock — the authoritative gate remains the per-write CAS fence.
The default implementation returns true — single-shard / non-distributed
backends own everything unconditionally, so the failover path’s
re-assertion is a no-op there and behaviour stays byte-identical. Only a
DISTRIBUTED sharded backend (haematite) overrides it. Decorators that wrap
another store must forward this call.
Sourcefn extend_owned_shards(&self, shards: &[usize])
fn extend_owned_shards(&self, shards: &[usize])
Add shards to this node’s owned-enumeration scope, UNIONING them with
the shards it already owns rather than replacing the set.
This is the SS-5 failover hook: when a live node absorbs a dead peer’s
shards it must KEEP serving its own shards while ALSO enumerating the
adopted ones. Self::set_owned_shards replaces the scope (the boot
path’s one-shot assignment); this widens it in place. The boot path uses
set_owned_shards; the failover path uses this.
The default implementation is a deliberate no-op — single-shard backends own everything unconditionally, so widening their scope is meaningless and leaves their behaviour byte-identical. Only a sharded backend (haematite) overrides this. Decorators that wrap another store must forward this call.
When the store currently owns ALL shards (the None / single-node
default), it already enumerates shards, so a sharded backend leaves the
own-all scope untouched.
Sourcefn publish_shard_owner(&self, shard: usize) -> Result<(), StoreError>
fn publish_shard_owner(&self, shard: usize) -> Result<(), StoreError>
Publish THIS node as the current owner of shard in the cluster’s
shard-owner directory, so other nodes’ request-routing edges resolve
shard to this node (SS-3).
This is the failover-publish hook the engine calls from adopt_shards
right after it has won shard’s election: it records, durably and
cluster-visibly, that this node has adopted shard, so a request reaching
a DIFFERENT survivor routes to this adopter rather than mis-resolving to
the dead declared owner (gap #2).
The default implementation is a deliberate no-op returning Ok(()) —
single-shard / non-distributed backends own everything unconditionally and
have no peers to coordinate, so boot and adoption stay byte-identical. Only
a DISTRIBUTED sharded backend overrides this. Decorators that wrap another
store must forward this call.
§Errors
Returns StoreError::NotOwner when a distributed backend’s fenced
directory write is out-voted (this node is not actually the owner), and
StoreError::Backend for any other replication/transport failure.
Dyn Compatibility§
This trait is dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".