Writing a Store Adapter

Implement RawEventStore + WakeSource, then verify against the executable conformance kit — no tribal knowledge required.

Writing a Store Adapter

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.)

The two required ports

RawEventStore — move bytes, stamp versions.

rust
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&lt;..>;
    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.

rust
pub trait WakeSource: Send + Sync + 'static {
    type Registration: WakeRegistration;
    type Error: core::error::Error + Send + Sync + 'static;
    fn register(&self, stream: Option&lt;&[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 contract that isn't in the signatures

The traits compile long before the adapter is correct. The behaviour you must get right — and which the kit checks — includes:

  • read_stream from is inclusive; read_all from is exclusive (an intentional asymmetry).
  • Conflict rejects with nothing landing — a version conflict on append leaves the store byte-for-byte unchanged.
  • Catch-up → live ordering — a subscription delivers the full backlog, emits CaughtUp exactly once, then live events, in order.
  • Lost-wakeup defensewake is 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.

The conformance kit — the contract as runnable tests

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:

rust
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 implement AtomicAppend.
  • conformance_snapshot! — if you implement SnapshotStore (takes sample position/extreme pairs).
  • conformance_lifecycle! — persistent adapters only (open/reopen closures).

skip_unless: gates environment-dependent adapters (e.g. postgres without a DATABASE_URL).

Optional capability ports

Add these when your backend supports them, and unlock the matching features:

PortUnlocks
AtomicAppendWholeChunk import, cross-stream commits
SnapshotStore<Vec<u8>, P>aggregate & projection snapshots
StreamListerlist_streams, export

Where to next