Streams
FACTSTR exposes streams as the post-commit delivery surface behind projection updates.
The main use is still the same: a feature slice owns a query model, registers a stream for the facts relevant to that model, and updates it from committed batches.
Shared Stream Contract
stream_all(handle)observes all future committed batchesstream_to(&EventQuery, handle)observes only future committed facts that match that queryHandleStream::new(move |batch| async move { ... })registers an async-capable handler- notifications happen only after successful persistence
- each committed append batch is delivered as one batch
- mixed committed batches are delivered as one filtered batch when matches exist
- delivery order follows committed global sequence order
- failed conditional append emits nothing
- live handler failure does not roll back append success
EventStream::unsubscribe()stops future deliveries- a batch already snapshotted for asynchronous delivery may still arrive after
unsubscribe()
Durable Streams
stream_all_durable(&DurableStream, handle)resumes from that durable stream's stored cursor, replays committed batches after it, then continues with future committed batchesstream_to_durable(&DurableStream, &EventQuery, handle)does the same with filtering for the facts relevant to that durable stream- durable replay awaits the handler future for each delivered batch
- the durable cursor advances only after the handler succeeds
- handler error or panic leaves the durable cursor at the last successfully delivered sequence
- replay starts strictly after the stored cursor
- replay uses ascending committed order
- replay/live transition has no duplicates or gaps
- the durable cursor does not advance past undelivered committed facts
Shared reusable durable-stream conformance now exists in factstr-conformance.
Remaining store-specific tests prove only store-local boundaries such as restart persistence, in-memory lifetime limits, and sparse persistent append-boundary storage.
Current Store Status
factstr-memory- implements
stream_all,stream_to,stream_all_durable, andstream_to_durable - implements durable streams within one
MemoryStoreinstance only - keeps durable stream cursor state only for the lifetime of one
MemoryStoreinstance factstr-sqlite- implements
stream_all,stream_to,stream_all_durable, andstream_to_durable - persists durable stream cursors and replay state across restart
- replays committed batches from stored cursors before switching to future committed delivery
- persists
append_batchesrows only for committed multi-event appends - treats a missing
append_batchesrow as a single-event committed append during replay factstr-postgres- implements
stream_all,stream_to,stream_all_durable, andstream_to_durable - persists durable stream cursors and replay state across restart
- replays committed batches from stored cursors before switching to future committed delivery
- persists
append_batchesrows only for committed multi-event appends - treats a missing
append_batchesrow as a single-event committed append during replay
Projection Use Case
A feature slice can:
- define a read model it owns locally
- define an
EventQueryfor the facts that should update that model - call
stream_to(...)once for future-only projection updates - call
stream_to_durable(...)when it needs persisted replay/catch-up - update its own query model from each delivered committed batch and await any async persistence before durable delivery is acknowledged
This keeps unrelated facts out of that feature slice by contract instead of by ad-hoc manual filtering after delivery.