Build on the supported package contract
Use @absolutejs/sync-bus-pg through its supported public entry points.
@absolutejs/sync-bus-pgv0.2.4betaData & SyncTyped Postgres LISTEN/NOTIFY channel and @absolutejs/sync cluster bus — horizontal scale without standing up Redis
bun add @absolutejs/sync-bus-pgPostgres LISTEN/NOTIFY cluster bus for @absolutejs/sync. Run sync horizontally across several Bun processes without standing up Redis or Kafka — your existing Postgres carries the cross-instance change feed.
@absolutejs/sync ships a ClusterBus seam: an in-memory bus for single-process dev + tests, and a contract you implement against your bus of choice for production. Until now that meant writing 50 lines of LISTEN/NOTIFY plumbing yourself. This package is the first-party implementation, with the 8000-byte NOTIFY payload limit handled cleanly.
Every instance of your Elysia app does the same. A mutation committed on instance A now fans out to subscribers on B/C/... via Postgres pg_notify.
spill strategies:
'overflow' (default) — inline JSON when small, table-backed when oversized. Best for typical workloads.
'always' — every message goes through the sync_cluster_spill table (durable, slightly slower; useful when you want every cross-instance change to survive a NOTIFY drop).
'never' — throws if a message exceeds the inline budget. Useful in tests to assert payload-size discipline.
postgres.js automatically recreates its dedicated listener connection and reissues LISTEN after a disconnect. A query-pool health check cannot prove that recovery has completed, so this adapter also sends a private probe through the full pg_notify → dedicated listener path every 15 seconds.
Probe envelopes never reach application callbacks and do not inflate message publish/receive counters. Monitoring starts with the first subscription and stops with the last unsubscribe. Set listenerHealth: false only when another owner calls probeListener() on its own cadence.
Oversized messages spill to sync_cluster_spill. Rows aren't auto-deleted on consume (every listener on the channel needs to read them, including the publisher's own listener which fetches but doesn't double-apply via the engine's origin filter). Sweep periodically:
For workloads where messages stay small (the common case), the spill table never gets touched and vacuum() always returns 0.
Per-instance version cursors. A client that reconnects to a _different_ instance falls back to a fresh snapshot (cold-hydration cost, not catch-up diff). Use sticky sessions if cross-instance reconnect-with-since matters.
Best-effort delivery. Inline NOTIFY can be lost if a listener connection drops mid-stream — every instance also has its own change log for resume, so a missed cross-instance fan-out is recovered on the next subscribe. For at-least-once cross-instance, run with spill: 'always'.
Outcomes
Use @absolutejs/sync-bus-pg through its supported public entry points.
Hardening checklist
Follow in order
Working example for Use.
import postgres from 'postgres';
import { createSyncEngine } from '@absolutejs/sync/engine';
import { createPostgresClusterBus } from '@absolutejs/sync-bus-pg';
const sql = postgres(process.env.DATABASE_URL!);
const engine = createSyncEngine();
// ...registerReader/Writer/Reactive/Mutation as usual...
const bus = createPostgresClusterBus({ sql });
await engine.connectCluster(bus);Working example for Options.
createPostgresClusterBus({
sql, // your postgres client
channel: 'absolutejs_sync_cluster', // override to scope multiple engines on the same PG
spill: 'overflow', // 'overflow' (default) | 'always' | 'never'
listenerHealth: {
// optional; these are the defaults
probeIntervalMs: 15_000,
probeTimeoutMs: 5_000
},
onError: (e) => log.warn(e) // listener-side errors
});Supported entry points declared by this package manifest.
Package entry point declared in package.json.
Scripts declared by this package manifest.