From e308652b3cf2065b626185e5f27f5ac5d8f6a616 Mon Sep 17 00:00:00 2001 From: Chow Loong Jin Date: Fri, 24 Apr 2026 17:51:22 +0800 Subject: [PATCH 1/5] pool: Add Disposable support to PoolClient When `Symbol.dispose` is defined, define a disposer function that simply calls `this.release()`. This lets the `PoolClient` with the `using` syntax work with any downstream-overridden `release` functions. This makes `PoolClient` support Explicit Resource Management when the runtime supports it. Fixes: #3515 --- packages/pg-pool/index.js | 6 +++++ packages/pg-pool/test/disposable-clients.js | 25 +++++++++++++++++++ .../pg-pool/test/disposable-clients/using.js | 20 +++++++++++++++ 3 files changed, 51 insertions(+) create mode 100644 packages/pg-pool/test/disposable-clients.js create mode 100644 packages/pg-pool/test/disposable-clients/using.js diff --git a/packages/pg-pool/index.js b/packages/pg-pool/index.js index 2fbdb78d5..530bc4b00 100644 --- a/packages/pg-pool/index.js +++ b/packages/pg-pool/index.js @@ -341,6 +341,12 @@ class Pool extends EventEmitter { client.release = this._releaseOnce(client, idleListener) + if (Symbol.dispose) { + client[Symbol.dispose] = function () { + this.release() + } + } + client.removeListener('error', idleListener) if (!pendingItem.timedOut) { diff --git a/packages/pg-pool/test/disposable-clients.js b/packages/pg-pool/test/disposable-clients.js new file mode 100644 index 000000000..57ea0d338 --- /dev/null +++ b/packages/pg-pool/test/disposable-clients.js @@ -0,0 +1,25 @@ +const Pool = require('../') + +const expect = require('expect.js') + +describe('disposable clients', () => { + it('defines a callable [Symbol.dispose]() when symbol is present', async () => { + const pool = new Pool({ max: 1 }) + const client = await pool.connect() + + if (Symbol?.dispose) { + expect(client[Symbol.dispose]).to.be.a('function') + } + + // ensure we don't define an `undefined` function when Symbol.dispose + // doesn't exist + expect(client).not.to.have.property('undefined') + + client.release() + await pool.end() + }) + + if (process.version.slice(1).split('.')[0] >= 24) { + require('./disposable-clients/using.js') + } +}) diff --git a/packages/pg-pool/test/disposable-clients/using.js b/packages/pg-pool/test/disposable-clients/using.js new file mode 100644 index 000000000..865a7e709 --- /dev/null +++ b/packages/pg-pool/test/disposable-clients/using.js @@ -0,0 +1,20 @@ +const Pool = require('../..') + +const expect = require('expect.js') + +it('supports releasing clients via `using`', async () => { + const pool = new Pool({ max: 1 }) + expect(pool.totalCount).to.eql(0) + + { + using client = await pool.connect() + expect(pool.totalCount).to.eql(1) + expect(pool.idleCount).to.eql(0) + await client.query('SELECT NOW()') + } + + expect(pool.totalCount).to.eql(1) + expect(pool.idleCount).to.eql(1) + + await pool.end() +}) From 2b4879c4c0dffaca083c6cea6f589e2b602c7202 Mon Sep 17 00:00:00 2001 From: Chow Loong Jin Date: Fri, 24 Apr 2026 19:34:50 +0800 Subject: [PATCH 2/5] pool: Update docs for disposable pool client --- docs/pages/apis/pool.mdx | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/pages/apis/pool.mdx b/docs/pages/apis/pool.mdx index 123bc8ba4..352f85a82 100644 --- a/docs/pages/apis/pool.mdx +++ b/docs/pages/apis/pool.mdx @@ -187,6 +187,22 @@ assert(pool.idleCount === 0) assert(pool.totalCount === 0) ``` +Alternatively, pool clients support [Explicit Resource Management](https://tc39.es/proposal-explicit-resource-management/), which means you can use the `using` syntax to release them if your runtime supports it. + +```js + +import { Pool } from 'pg' + +const pool = new Pool() +{ + + // check out a single client + using client = await pool.connect() + + // client.release() implicitly called at the end of scope +} +``` +
You must release a client when you are finished with it. From c482a1270be96c138a228cae780fcff4000fcbc8 Mon Sep 17 00:00:00 2001 From: Chow Loong Jin Date: Mon, 27 Apr 2026 12:13:58 +0800 Subject: [PATCH 3/5] pool: Add support for client.release(true) with Symbol.dispose Add a `destroyOnDispose` property on the client to signal that `[Symbol.dispose]()` should call `client.release(true)` instead of `client.release()`. --- docs/pages/apis/pool.mdx | 15 ++++++++++++++- packages/pg-pool/index.js | 3 ++- .../pg-pool/test/disposable-clients/using.js | 18 ++++++++++++++++++ 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/docs/pages/apis/pool.mdx b/docs/pages/apis/pool.mdx index 352f85a82..218b2c25f 100644 --- a/docs/pages/apis/pool.mdx +++ b/docs/pages/apis/pool.mdx @@ -187,6 +187,10 @@ assert(pool.idleCount === 0) assert(pool.totalCount === 0) ``` +`client.destroyOnDispose: boolean` + +`client[Symbol.dispose]() => void` + Alternatively, pool clients support [Explicit Resource Management](https://tc39.es/proposal-explicit-resource-management/), which means you can use the `using` syntax to release them if your runtime supports it. ```js @@ -195,14 +199,23 @@ import { Pool } from 'pg' const pool = new Pool() { - // check out a single client using client = await pool.connect() // client.release() implicitly called at the end of scope } + +{ + // check out a single client + using client = await pool.connnect() + client.destroyOnDispose = true; + + // client.release(true) implicitly called at the end of scope +} ``` +If you want the client to be destroyed instead of being returned to the pool at the end of the scope, (i.e. calling `client.release(true)` at the end of the scope), set the `destroyOnRelease` property to `true`. +
You must release a client when you are finished with it. diff --git a/packages/pg-pool/index.js b/packages/pg-pool/index.js index 530bc4b00..56ae978a7 100644 --- a/packages/pg-pool/index.js +++ b/packages/pg-pool/index.js @@ -342,8 +342,9 @@ class Pool extends EventEmitter { client.release = this._releaseOnce(client, idleListener) if (Symbol.dispose) { + client.destroyOnDispose = false client[Symbol.dispose] = function () { - this.release() + this.release(this.destroyOnDispose) } } diff --git a/packages/pg-pool/test/disposable-clients/using.js b/packages/pg-pool/test/disposable-clients/using.js index 865a7e709..284335962 100644 --- a/packages/pg-pool/test/disposable-clients/using.js +++ b/packages/pg-pool/test/disposable-clients/using.js @@ -18,3 +18,21 @@ it('supports releasing clients via `using`', async () => { await pool.end() }) + +it('supports destroying clients via `using`', async () => { + const pool = new Pool({ max: 1 }) + expect(pool.totalCount).to.eql(0) + + { + using client = await pool.connect() + client.destroyOnDispose = true + expect(pool.totalCount).to.eql(1) + expect(pool.idleCount).to.eql(0) + await client.query('SELECT NOW()') + } + + expect(pool.totalCount).to.eql(0) + expect(pool.idleCount).to.eql(0) + + await pool.end() +}) From 1f7549258f630cec7a9f750908df143f5c8ecfd1 Mon Sep 17 00:00:00 2001 From: Chow Loong Jin Date: Thu, 30 Apr 2026 12:11:54 +0800 Subject: [PATCH 4/5] Drop unnecessary ?. in Symbol.dispose check Co-authored-by: Charmander <~@charmander.me> --- packages/pg-pool/test/disposable-clients.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pg-pool/test/disposable-clients.js b/packages/pg-pool/test/disposable-clients.js index 57ea0d338..548ca9ff2 100644 --- a/packages/pg-pool/test/disposable-clients.js +++ b/packages/pg-pool/test/disposable-clients.js @@ -7,7 +7,7 @@ describe('disposable clients', () => { const pool = new Pool({ max: 1 }) const client = await pool.connect() - if (Symbol?.dispose) { + if (Symbol.dispose) { expect(client[Symbol.dispose]).to.be.a('function') } From f56d4488bf64bbc79aad4e81fff50741d54e1501 Mon Sep 17 00:00:00 2001 From: Chow Loong Jin Date: Thu, 30 Apr 2026 12:13:52 +0800 Subject: [PATCH 5/5] Fix typo in docs `connect` has two `n`s, and we don't like semicolons Co-authored-by: Charmander <~@charmander.me> --- docs/pages/apis/pool.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/pages/apis/pool.mdx b/docs/pages/apis/pool.mdx index 218b2c25f..be0aeeb19 100644 --- a/docs/pages/apis/pool.mdx +++ b/docs/pages/apis/pool.mdx @@ -207,8 +207,8 @@ const pool = new Pool() { // check out a single client - using client = await pool.connnect() - client.destroyOnDispose = true; + using client = await pool.connect() + client.destroyOnDispose = true // client.release(true) implicitly called at the end of scope }