Repository navigation
add a postgres transport to bus-postgres - #344
Merged
Merged
Conversation
adenhertog
force-pushed
the
issue-266-postgres-transport
branch
from
October 7, 2026 10:46
e7976d7 to
f2448fe
Compare
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…op ordering Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
adenhertog
force-pushed
the
issue-266-postgres-transport
branch
from
October 8, 2026 01:26
aebffd6 to
fef83e6
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #266
Summary
Adds
PostgresTransportto@node-ts/bus-postgres: the bus' queues kept in Postgres tables, so a service needs no message broker.Background
#266 asks for a database-as-queue transport:
SKIP LOCKEDqueue tables,LISTEN/NOTIFY, pub/sub through a subscription table, and passing the bus-test conformance suite. The design was agreed with the maintainer first. The accepted decisions are in this comment.Problem
Every transport today needs a broker (RabbitMQ, or SQS and SNS). A single service, or services that share one database, have to run a broker just to send messages to themselves.
Approach
Package:
PostgresTransportlives in bus-postgres. It shares the persistence's SQL helpers, which are moved to an internalsrc/postgres-sql.ts; the persistence's behaviour is unchanged.Tables: shared by every queue in
schemaName:transport_messages(index on(queue, visible_at)),transport_queues,transport_subscriptionsandtransport_dead_letters.Provisioning:
provision()creates the tables and, unless the bus is send-only, inserts the queue row and one subscription row per handled message and custom-handler topic. It never deletes anything.initialize()only checks that everything exists, and throwsResourcesNotProvisioned.<runtime_role>.Sending:
sendandpublishfan out to each subscribed queue in one statement, which also sendspg_notifyfor each queue reached.sendToAddressinserts straight into a provisioned queue, and throwsEndpointNotFoundotherwise.Receiving:
for update skip lockedclaim setsvisible_at = now + visibilityTimeoutMs(default 30 s, no heartbeat) and a new lease token.failedAttemptsis the number of earlier deliveries, as on SQS.Waiting for messages:
LISTENruns on a connection of its own. Polling everypollIntervalMs(default 1 s) is always on as a fallback, andlisten: falseturns listening off for PgBouncer in transaction mode.stop()waits for a listen that's still in flight, and a listen that ends up stale closes its own connection.pollIntervalMsandvisibilityTimeoutMsmust be above 0, or the constructor throwsInvalidTransportDuration.Retries and dead letters:
returnMessagesetsvisible_at.failmoves the row totransport_dead_letterswith thebus-failureheader, in one statement.Delayed delivery uses the existing outgoing-messages dispatcher, with no bus-core change. Joining the transport to the
withOutbox()transaction is follow-up postgres transport: exactly-once by enlisting the transport in the outbox transaction #343, and the docs say the transport isn't part of that transaction.Tests:
transportTestspasses.LISTENand through polling;stop()while a claim that finds nothing is in flight (a regression test for a hang found in review);withOutbox()on the same database: commit, rollback, inbox dedupe, a delayed send, and dead-letter redrive;outboxTestsandinboxTestswere not changed to take a transport. They run on in-memory queues they control, such as the failing queue inoutboxTests, so the outbox and inbox are covered with this transport in its own integration test instead.Docs: a new
docs/transports/postgres.md, which also shows how an external system publishes withpg_notify, with a sidebar entry, a card on the transports page, a row in the provisioning guide, the bus-postgres README, and the CLAUDE.md files. Minor changeset.This is original work under the clean-room policy, not ported, translated or copied from another messaging framework
Added a changeset (
pnpm changeset) for user-facing changes to published packages, or none is neededDocs: updated
docs/for user-facing changes, or none needed🤖 Generated with Claude Code