Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Add pg-transaction module #3516

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

Closed
brianc wants to merge 7 commits into master from bmc/pg-txn
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion .eslintrc
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"extends": ["eslint:recommended", "plugin:prettier/recommended", "prettier"],
"ignorePatterns": ["node_modules", "coverage", "packages/pg-protocol/dist/**/*", "packages/pg-query-stream/dist/**/*"],
"parserOptions": {
"ecmaVersion": 2017,
"ecmaVersion": 2022,
"sourceType": "module"
},
"env": {
Expand All @@ -30,6 +30,15 @@
"rules": {
"no-undef": "off"
}
},
{
"files": ["packages/pg-transaction/src/**/*.ts"],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 2022,
"sourceType": "module",
"project": "./packages/pg-transaction/tsconfig.eslint.json"
}
}
]
}
6 changes: 4 additions & 2 deletions .github/workflows/ci.yml
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ jobs:
cache: yarn
- run: yarn install --frozen-lockfile
- run: yarn lint

build:
timeout-minutes: 15
needs: lint
Expand All @@ -38,7 +39,6 @@ jobs:
fail-fast: false
matrix:
node:
- '16'
- '18'
- '20'
- '22'
Expand Down Expand Up @@ -73,4 +73,6 @@ jobs:
node-version: ${{ matrix.node }}
cache: yarn
- run: yarn install --frozen-lockfile
- run: yarn test
- name: Run tests (skip pg-transaction under Node < 18)
run: |
yarn test
6 changes: 4 additions & 2 deletions package.json
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,17 @@
"lint": "eslint --cache 'packages/**/*.{js,ts,tsx}'"
},
"devDependencies": {
"@types/node": "^20.0.0",
"@typescript-eslint/eslint-plugin": "^7.0.0",
"@typescript-eslint/parser": "^6.17.0",
"@typescript-eslint/parser": "^7.0.0",
"eslint": "^8.56.0",
"eslint-config-prettier": "^10.1.2",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^5.1.2",
"lerna": "^3.19.0",
"prettier": "3.0.3",
"typescript": "^4.0.3"
"ts-node": "^10.9.0",
"typescript": "^5.2.0"
},
"prettier": {
"semi": false,
Expand Down
25 changes: 25 additions & 0 deletions packages/pg-transaction/package.json
View file Open in desktop
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "pg-transaction",
"version": "2.0.0",
"main": "dist/index.js",
"license": "MIT",
"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"pretest": "tsc",
"test": "mocha dist/**/*.test.js"
},
"dependencies": {
"@types/node": "^20.0.0",
"typescript": "^5.8.3"
},
"engines": {
"node": ">=18.0.0"
},
"devDependencies": {
"@types/mocha": "^10.0.10",
"@types/pg": "^8.10.9",
"mocha": "^10.8.2",
"pg": "^8.11.3"
}
}
74 changes: 74 additions & 0 deletions packages/pg-transaction/src/index.test.ts
View file Open in desktop
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { strict as assert } from 'assert'
import { Client } from 'pg'
import { transaction } from '.'

class DisposableClient extends Client {
// overwrite the query method and log the arguments and then dispatch to the original method
override query(...args: any[]): any {
// console.log('Executing query:', ...args);
// @ts-ignore
return super.query(...args)
}

async [Symbol.asyncDispose]() {
await this.end()
}
}

async function getClient(): Promise<DisposableClient> {
const client = new DisposableClient()
await client.connect()
await client.query('CREATE TEMP TABLE test_table (id SERIAL PRIMARY KEY, name TEXT)')
return client
}

describe('transaction', () => {
it('should create a client with an empty temp table', async () => {
await using client = await getClient()
const { rowCount } = await client.query('SELECT * FROM test_table')
assert.equal(rowCount, 0, 'Temp table should be empty on creation')
})

it('automatically commits on success', async () => {
await using client = await getClient()

const result = await transaction(client, async () => {
await client.query('INSERT INTO test_table (name) VALUES (1ドル)', ['test'])
const { rows } = await client.query('SELECT * FROM test_table')
return rows[0].name // Should return 'test'
})

assert.equal(result, 'test')
})

it('automatically rolls back on error', async () => {
await using client = await getClient()

// Assert that the transaction function rejects with the expected error
await assert.rejects(
async () => {
await transaction(client, async () => {
await client.query('INSERT INTO test_table (name) VALUES (1ドル)', ['test'])
await client.query('SELECT * FROM test_table')
throw new Error('Simulated error') // This will trigger a rollback
})
},
{
name: 'Error',
message: 'Simulated error',
}
)

// Verify that the transaction rolled back
const { rowCount } = await client.query('SELECT * FROM test_table')
assert.equal(rowCount, 0, 'Table should be empty after rollback')
})

it('can return nothing from the transaction with correct type', async () => {
await using client = await getClient()

const _: void = await transaction(client, async () => {
await client.query('INSERT INTO test_table (name) VALUES (1ドル)', ['test'])
})
})
})
42 changes: 42 additions & 0 deletions packages/pg-transaction/src/index.ts
View file Open in desktop
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Client } from 'pg'

async function doTransaction(client: Client) {
await client.query('BEGIN')

let shouldRollback = false
let disposed = false

return {
async [Symbol.asyncDispose]() {
if (disposed) return
disposed = true

if (shouldRollback) {
await client.query('ROLLBACK')
} else {
await client.query('COMMIT')
}
},

rollback() {
shouldRollback = true
},
}
}

// Auto-rollback wrapper that catches errors automatically
async function transaction<T>(client: Client, fn: () => Promise<T>): Promise<T> {
await using txn = await doTransaction(client)

try {
const result = await fn()
// If we get here, success - transaction will auto-commit
return result
} catch (error) {
// If error occurs, mark for rollback
txn.rollback()
throw error
}
}

export { transaction as transaction }
16 changes: 16 additions & 0 deletions packages/pg-transaction/tsconfig.eslint.json
View file Open in desktop
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"compilerOptions": {
"target": "es2022",
"module": "commonjs",
"lib": ["es2022"],
"outDir": "./dist",
"rootDir": "./src",
"declaration": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "test"]
}
16 changes: 16 additions & 0 deletions packages/pg-transaction/tsconfig.json
View file Open in desktop
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"compilerOptions": {
"target": "es2022",
"module": "commonjs",
"lib": ["es2022", "ESNext.Disposable"],
"outDir": "./dist",
"rootDir": "./src",
"declaration": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "test"]
}
Loading

AltStyle によって変換されたページ (->オリジナル) /