Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Add pg-transaction module #3518

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open

Add pg-transaction module #3518

wants to merge 8 commits into from

Conversation

brianc
Copy link
Owner

@brianc brianc commented Jul 18, 2025

I'm taking over the old pg-transaction module, and turning it into a simpler async/await affair. I basically write this same utility in every project I do, so thought it'd be helpful. Pass the transaction function a connected client or an instance of a pool and it will call back with the client (or one checked out from the pool) already within a BEGIN block. If your async callback doesn't throw, it will COMMIT after, and if it does throw, it will ROLLBACK. It also releases the client back to the pool regardless of if the your async callback function throws or not.

@brianc
Copy link
Owner Author

brianc commented Jul 18, 2025

todo: add docs done

@brianc brianc requested a review from Copilot July 18, 2025 14:31
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR introduces a new pg-transaction module that provides a simplified async/await wrapper for PostgreSQL transactions. The module handles automatic transaction management with BEGIN/COMMIT/ROLLBACK operations and supports both individual clients and connection pools.

Key changes:

  • Creates a transaction utility function that accepts either a PostgreSQL client or pool
  • Implements automatic transaction lifecycle management with proper error handling and resource cleanup
  • Provides comprehensive test coverage for various transaction scenarios

Reviewed Changes

Copilot reviewed 4 out of 6 changed files in this pull request and generated 3 comments.

File Description
packages/pg-transaction/src/index.ts Core transaction implementation with type guards and transaction management logic
packages/pg-transaction/src/index.test.ts Comprehensive test suite covering client/pool usage, commit/rollback scenarios, and bound functions
packages/pg-transaction/package.json Package configuration with dependencies and build scripts
packages/pg-transaction/tsconfig.json TypeScript configuration for the module

Copy link

cloudflare-workers-and-pages bot commented Jul 18, 2025

Deploying node-postgres with  Cloudflare Pages  Cloudflare Pages

Latest commit: bc627be
Status: ✅  Deploy successful!
Preview URL: https://e885314f.node-postgres.pages.dev
Branch Preview URL: https://bmc-pg-transaction-2.node-postgres.pages.dev

View logs

throw error
} finally {
if (isPoolClient(client)) {
client.release()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the rollback failed, client could be returned to the pool while still in a transaction or in a broken-connection state. I don’t know if the former is actually possible in practice (ROLLBACK failing without breaking the connection), but the latter definitely is (when error doesn’t crash the process, e.g. because the user is attaching error listeners to all clients using the pool’s connect event). Might want to add another catch for the ROLLBACK and .release(rollbackError).

let client: Client | PoolClient
if (isPool(clientOrPool)) {
// It's a Pool
client = await clientOrPool.connect()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn’t do the error listener dance, so connection errors can crash a Node process here. It might be surprising that the behavior is different from pool.query. But maybe pg should add a default handler for the error event in a minor version update anyway?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn’t do the error listener dance, so connection errors can crash a Node process here

Sorry I'm not following! What do you mean by error listener dance? I thought await pool.connect() would reject on connection errors? not have to result to listeners?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pool.connect() itself does, but if there’s a connection-level error after connecting (e.g. during the BEGIN query), the default behavior is to crash the process (because it’s an error event). (I think a lot of people probably use pg without doing this part strictly right…)

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a lot of people probably use pg without doing this part strictly right…)

yeah, honestly myself included....for yeras, in multiple apps. 😬

the whole async/evented error stuff is so so tricky to get "really just right" - would love to see an example if you have one. I'm totally cool working on some kind of "buffer the error(s) if no default .on('error') listener is added and throw it on the next call to the library" feature instead of what it is now which is effectively "if its not an in-flight query, then you're probably just gonna crash, which doesn't happen much, but async race conditions are their own kind of personal hell." I do think that's a minor version bump fix too because it could probably be backwards-comp where if there is an error listener things proceede as normal but if there isn't we don't just explode the universe w/ an unhandledError and instead buffer it.

Honestly it could potentially be a lot to take on and needs some thinking about where the right place to handle these errors is (because legacy reasons we do pool -> client -> connection -> pg-protocol) callstacks, but its probably worth spending more time there myself.

Copy link
Collaborator

@charmander charmander Jul 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I already did the error replay part in #1503; it’d just be a matter of adding this.on('error', () => {}) to Client’s constructor (and Pool’s too) *actually, I don’t remember if the pool automatically drops broken clients that are released to it without an explicit error, and there might be a question of whether .end() should start rejecting with the connection error… maybe only if there are no error listeners… it might not be that simple, and a new major version could be the safest option.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants