Want mnesis on a store it does not ship for — SQLite, Redis, S3, your own engine?
You implement two traits, and everything else (the typed repository,
subscriptions, snapshots, backup) comes from mnesis-store for free. Then you run
the conformance kit, and it tells you whether your adapter is correct.
The design goal: a third party can write a correct adapter against a short written contract, with no access to fjall/postgres source and no tribal knowledge. (This was proven — a toy HashMap adapter written against only the guide passed 34/34 checks on its first run.)
RawEventStore — move bytes, stamp versions.
pub trait RawEventStore: Send + Sync {
type Error: core::error::Error + Send + Sync + 'static;
type Stream: EventStream<Error = Self::Error> + 'static;
type AllStream: /* Stream of (AllPosition, PersistedEnvelope) */;
type AllPosition: AllPosition;
async fn append(/* stream id, expected version, pending envelopes */) -> Result<..>;
async fn read_stream(/* id, from: Version (INCLUSIVE) */) -> Result<Self::Stream, ..>;
async fn read_all(/* from: GlobalSeq */) -> Result<Self::AllStream, ..>;
}WakeSource — notify parked subscriptions after a durable commit.
pub trait WakeSource: Send + Sync + 'static {
type Registration: WakeRegistration;
type Error: core::error::Error + Send + Sync + 'static;
fn register(&self, stream: Option<&[u8]>) -> Result<Self::Registration, Self::Error>; // None = $all
fn wake(&self, stream: &[u8]); // call AFTER the durable commit
}In-process adapters can delegate WakeSource straight to mnesis-wake's
StreamNotifiers (as all three shipped adapters do); a distributed adapter
implements it over LISTEN/NOTIFY or similar.
The traits compile long before the adapter is correct. The behaviour you must get right — and which the kit checks — includes:
read_streamfromis inclusive;read_allfromis exclusive (an intentional asymmetry).- Conflict rejects with nothing landing — a version conflict on
appendleaves the store byte-for-byte unchanged. - Catch-up → live ordering — a subscription delivers the full backlog, emits
CaughtUpexactly once, then live events, in order. - Lost-wakeup defense —
wakeis called after the commit is durable, so an armed subscription cannot miss it. - Spurious wakes are permitted — an extra wake costs a re-scan, not a bug.
mnesis-store-testing pins all of that as executable checks. You wire it up with
one macro over a factory contract — || async { (store, guard) }, a fresh
store per test, the guard (a TempDir, or ()) keeping backing resources alive:
mnesis_store_testing::conformance!(
factory: || async { (MyStore::new(), ()) },
);That generates one named #[tokio::test] per check across four categories
(sequence, boundary, linearizability, lifecycle). Opt-in capability
macros cover the optional ports:
conformance_atomic_append!— if you implementAtomicAppend.conformance_snapshot!— if you implementSnapshotStore(takes sample position/extreme pairs).conformance_lifecycle!— persistent adapters only (open/reopenclosures).
skip_unless: gates environment-dependent adapters (e.g. postgres without a
DATABASE_URL).
Add these when your backend supports them, and unlock the matching features:
| Port | Unlocks |
|---|---|
AtomicAppend | WholeChunk import, cross-stream commits |
SnapshotStore<Vec<u8>, P> | aggregate & projection snapshots |
StreamLister | list_streams, export |
- Adapters — the shipped implementations to model yours on.
- Hexagonal Architecture — why the seam is two traits.
mnesis-store-testingdocs — the full adapter-author guide and pinned contract notes.