meridian-pg is a Postgres extension that exposes Meridian CRDT types directly in SQL. A trigger fires pg_notify on every write, and the Meridian server broadcasts the delta to all connected clients in real time.
Install the extension
Requires Postgres 14–17. Built with pgrx 0.13.
Add CRDT columns to a table
CRDT state is stored asBYTEA (msgpack-encoded). Add columns with standard ALTER TABLE:
Attach the sync trigger
The trigger accepts any number of CRDT columns as variadic arguments:pg_notify is emitted per changed column per row, on channel meridian_ops:
Write from SQL
Read from SQL
Merge two states
Useful for manual conflict resolution or backfills:Connect from the SDK
Thecrdt_id sent over pg_notify is "<prefix>:<pk>:<col>". Use it directly with the SDK:
Server setup
Start the server withDATABASE_URL pointing to the same Postgres instance:
meridian_ops and feeds incoming pg_notify payloads into the same apply + WebSocket broadcast pipeline as any other op.
WAL consumer (large documents)
pg_notify has an 8 KB payload limit — large RGA documents or deep Tree edits will be silently dropped by the trigger. For those, enable the WAL consumer in the Meridian server, which tails the logical replication stream instead.
The WAL consumer lives server-side (not in the extension). It opens a replication connection to Postgres, parses the pgoutput stream, and feeds BYTEA column changes through the same merge + broadcast path as pg_notify. Both paths coexist and are idempotent — for small documents you get both, for large ones you only get the WAL path.
1. Enable logical replication
2. Start the server with WAL replication enabled
SetMERIDIAN_WAL_CONNSTR to a connection string pointing at the same Postgres instance. The server creates the replication slot and publication automatically on first boot.
| Variable | Default | Description |
|---|---|---|
MERIDIAN_WAL_CONNSTR | — | Enables WAL replication. Same format as DATABASE_URL. |
MERIDIAN_WAL_SLOT | meridian_wal | Replication slot name (auto-created). |
MERIDIAN_WAL_PUB | meridian_pub | Publication name (auto-created). |
MERIDIAN_WAL_CONNSTR is not set, the server falls back to pg_notify only (no change in behaviour for documents under 8 KB).
Function reference
| Function | Returns | Description |
|---|---|---|
gcounter_increment(col, amount, client_id) | BYTEA | Increment a GCounter |
gcounter_value(col) | BIGINT | Current total |
gcounter_merge(a, b) | BYTEA | Lattice join of two GCounters |
pncounter_increment(col, amount, client_id) | BYTEA | Increment |
pncounter_decrement(col, amount, client_id) | BYTEA | Decrement |
pncounter_value(col) | BIGINT | Signed value |
pncounter_merge(a, b) | BYTEA | Lattice join |
orset_add(col, json, node_id, seq) | BYTEA | Add element |
orset_remove(col, json) | BYTEA | Remove element (add-wins) |
orset_elements(col) | TEXT | JSON array of live elements |
orset_merge(a, b) | BYTEA | Lattice join |
lww_set(col, json, wall_ms, author) | BYTEA | Set value (LWW) |
lww_value(col) | TEXT | Current value as JSON string |
lww_updated_at_ms(col) | BIGINT | Last-write timestamp (ms) |
rga_text(col) | TEXT | Current text content (read-only) |
rga_len(col) | BIGINT | Visible character count |
tree_json(col) | TEXT | Tree as JSON (read-only) |
tree_node_count(col) | BIGINT | Live node count |
notify_trigger(...) | trigger | Row-level sync trigger |
meridian_version() | TEXT | Extension version |
Building from source
Caveats
- pg_notify limit — 8 KB. Large RGA / Tree documents should use the WAL consumer instead (see above).
- RGA / Tree writes — require HLC timestamps; write exclusively via WebSocket clients or the HTTP API. SQL functions are read-only for these types.
- Clock drift — always pass
EXTRACT(EPOCH FROM now())::bigint * 1000aswall_mstolww_set. The server validates HLC drift within ±30 s. - Trusted ops — ops from
pg_notifybypass token auth. Treat DB write access as equivalent to Meridian admin access.