{"id":17470786,"url":"https://github.com/golergka/pg-tx","last_synced_at":"2025-04-21T16:34:43.549Z","repository":{"id":41933680,"uuid":"359977067","full_name":"golergka/pg-tx","owner":"golergka","description":"Transactions for node-postgres","archived":false,"fork":false,"pushed_at":"2021-06-28T11:18:56.000Z","size":660,"stargazers_count":5,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-04-16T09:41:54.689Z","etag":null,"topics":["nodejs","postgresql","subtle-bugs","transactions"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/golergka.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-04-20T23:28:31.000Z","updated_at":"2023-01-31T01:07:26.000Z","dependencies_parsed_at":"2022-08-22T11:40:47.316Z","dependency_job_id":null,"html_url":"https://github.com/golergka/pg-tx","commit_stats":null,"previous_names":[],"tags_count":12,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/golergka%2Fpg-tx","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/golergka%2Fpg-tx/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/golergka%2Fpg-tx/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/golergka%2Fpg-tx/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/golergka","download_url":"https://codeload.github.com/golergka/pg-tx/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249524889,"owners_count":21285899,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["nodejs","postgresql","subtle-bugs","transactions"],"created_at":"2024-10-18T16:10:15.978Z","updated_at":"2025-04-20T01:01:29.626Z","avatar_url":"https://github.com/golergka.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# pg-tx - Transactions for node-postgres\n\n[![npm version](https://badge.fury.io/js/pg-tx.svg)](https://badge.fury.io/js/pg-tx) [![DeepScan grade](https://deepscan.io/api/teams/14626/projects/17725/branches/413894/badge/grade.svg)](https://deepscan.io/dashboard#view=project\u0026tid=14626\u0026pid=17725\u0026bid=413894)\n\nThis package implements transactions on top of [node-postgres](http://node-postgres.com), based on [this answer](https://stackoverflow.com/a/65588782/312725), but improved to remove a class of subtle bugs.\n\n## Features\n\n* No use-after-release bugs\n* Automatic transactions-inside-transactions with savepoints\n* Can be used with either `Pool` or `PoolClient`\n\n## Usage\n\n```Typescript\nimport tx from `pg-tx`\n\nconst pg = new Pool()\n\nawait tx(pg, async (db) =\u003e {\n  await db.query(`UPDATE accounts SET money = money - 50 WHERE name = 'bob'`)\n  await db.query(`UPDATE accounts SET money = money + 50 WHERE name = 'alice'`)\n})\n\nawait tx(pg, async (db) =\u003e {\n  await db.query(`UPDATE accounts SET money = money - 50 WHERE name = 'bob'`)\n  await db.query(`UPDATE accounts SET money = money + 50 WHERE name = 'debbie'`)\n\n  // Any errors thrown inside the callback will terminate the transaction\n  throw new Error(`screw Debbie`)\n})\n\n// You can also use it with other packages that use Pool or PoolClient, like pgtyped\nimport { sql } from '@pgtyped/query'\n\nconst updateAccount = sql\u003cIUpdateAccountQuery\u003e`\n  UPDATE accounts\n  SET money = momey + $delta\n  WHERE name = $name\n`\n\nawait tx(pg, async(db) =\u003e {\n  await udpateAccount.run({ name: 'bob', delta: -50 })\n  await udpateAccount.run({ name: 'charlie', delta: 50 })\n})\n\n```\n\n## Why use this package\n\nNaive approach to pg transactions, featured in [this answer](https://stackoverflow.com/a/65588782/312725) and used in many projects looks like this:\n\n```Typescript\n// DO NOT USE THIS CODE\nexport default async function tx\u003cT\u003e(\n\tpg: Pool,\n\tcallback: (db: PoolClient) =\u003e Promise\u003cT\u003e\n): Promise\u003cT\u003e {\n\tconst client = await pg.connect()\n\tawait client.query(`BEGIN`)\n\n\ttry {\n\t\tconst result = await callback(client)\n\t\tawait client.query(`COMMIT`)\n\t\treturn result\n\t} catch (e) {\n\t\tawait client.query(`ROLLBACK`)\n\t\tthrow e\n\t} finally {\n\t\tclient.release()\n\t}\n}\n```\n\nHowever, this approach contains a subtle bug, because the `client` it passes to the callback stays valid after transaction finishes (successfully or not), and can be unknowingly used. In essence, it's a variation of use-after-free bug, but with database clients instead of memory.\n\nHere's a demonstration of code that can trigger this condition:\n\n```Typescript\nasync function failsQuickly(db: PoolClient) {\n  await db.query(`This query has an error`)\n}\n\nasync function executesSlowly(db: PoolClient) {\n  // Takes a couple of seconds to complete\n  await externalApiCall()\n  // This operation will be executed OUTSIDE of transaction block!\n  await db.query(`\n    UPDATE external_api_calls \n    SET amount = amount + 1 \n    WHERE service = 'some_service'\n  `)\n}\n\nawait tx(pg, async (db) =\u003e {\n  await Promise.all([\n    failsQuickly(db),\n    executesSlowly(db)\n  ])\n})\n```\n\nTo prevent this, we use ProxyClient, which implements a disposable pattern. After the client has been released, any attempts to use it will throw an error.\n\n## Development\n\nTo run tests, specify `POSTGRES_URL` in `.env` file like this:\n\n```\nPOSTGRES_URL=\"postgres://postgres:password@127.0.0.1:5432/test-db\"\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgolergka%2Fpg-tx","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgolergka%2Fpg-tx","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgolergka%2Fpg-tx/lists"}