All notable changes to this project are documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
0.3.0 - 2026-09-22
Added
Sourced.EventStore.Postgres.Subscriber, a durable subscriber behaviour. It keeps the last sequence it processed in asourced_checkpointsrow and subscribes from it on start, andhandle_events/2runs inside the transaction that commits that position, so a read model written through the store's repo and its checkpoint can never disagree. A fingerprint of the query is stored with the position and a different query under the same name refuses to start; two processes under one name are detected on commit, and the second stops with{:checkpoint_moved, name}.:start_fromsets where a new name begins —:origin,:latest, or a sequence — and applies only until a position exists. Atransaction?/2callback,trueby default, lets a handler whose work is not in the database run outside the transaction, at-least-once.Sourced.EventStore.Postgres.Checkpoint, the schema and theclaim/4andadvance/4operations behind it.- Versioned migrations.
Sourced.EventStore.Postgres.Migrations.up/1anddown/1take a:version, andup/1runs only the steps a database is missing, recording where it got to as a comment onsourced_events. A database migrated by 0.2.x reads as version 1; version 2 addssourced_checkpoints, so an existing install upgrades with a migration callingup(version: 2)anddown(version: 2).
Fixed
query/2now orders results by ascending sequence. Nothing in the query asked Postgres for an order before, so a:limitwas applied to whatever rows the planner produced first rather than the lowest matching sequences, and thelast_sequencereported alongside the events depended on that same arbitrary order.
0.2.1 - 2026-09-14
Fixed
- A conditional append inside the caller's own transaction no longer fails with
25P02 in_failed_sql_transactionwhen it loses a serialization conflict. The adapter read the conflicting sequence back to build theOptimisticConcurrencyError, which works in a transaction it opened itself — that one has already rolled back by then — but not in the caller's, which the failure left open and aborted. Inside the caller's transaction thePostgrexserialization failure is now raised as-is: nothing can be read on that connection any more, and the whole transaction has to be rerun regardless.
0.2.0 - 2026-08-25
Fixed
- A transaction that appends and then reads now sees its own events. The read watermark sits at or below the sequence the appender started from, so its own appends were above it and invisible to it — a decision model, a read model projected in the same transaction, or a test asserting on what it just wrote would come back empty. Appends now publish the sequences they drew in a transaction-local setting that reads let through alongside the watermark.
Changed
- Requires
sourced ~> 0.2, up from~> 0.1, since the contract suite it compiles into its test build only ships from 0.2.0 onward.
0.1.0 - 2026-08-24
Initial release: a PostgreSQL event store adapter for Sourced, built on Ecto and Postgrex.
Added
Sourced.EventStore.Postgresadapter, running on a repo supplied by the host application rather than a pool of its own, so an append can commit alongside the writes it feeds.Sourced.EventStore.Postgres.Migrations—up/0anddown/0to delegate to from a host migration, creating thesourced_eventsandsourced_event_tagstables, their indexes and triggers, and the functions the read watermark is computed from. Both tables ship with autovacuum tuned for an append-only workload.- Global, monotonic
BIGINTsequences from an identity column, so a reader can always resume from the last sequence it saw. - A read watermark bounding every read at the highest sequence no in-flight append can still commit beneath, keeping reads gap-free despite rollbacks and out-of-order commits. Appenders publish their starting sequence as a shared advisory lock released on commit, abort, or connection death.
SERIALIZABLEappends, with a conditional append's serialization failure translated into aSourced.EventStore.OptimisticConcurrencyError.- Tag queries served by the
sourced_event_tagsside table, kept in step with thetagscolumn by a trigger. - Subscriptions over
LISTEN/NOTIFY, so a subscriber hears about appends from every node. A statement-levelAFTER INSERTtrigger announces the highest sequence an append inserted; each store keeps one listening connection. Subscribing starts with a catch-up read from:from, and the subscription owns its cursor from there, re-querying rather than reading the payload. Delivery is at-most-once and not durable. :nameconfig for running more than one store on the adapter.