From d9b0eb412e1511024aeb930abdf5916d03d0c4d6 Mon Sep 17 00:00:00 2001 From: askalf <263217947+askalf@users.noreply.github.com> Date: Mon, 14 Sep 2026 01:12:25 +0000 Subject: [PATCH] fix(pg-cloudflare): emit write errors that have no callback `Connection._send()` calls `stream.write(buffer)` with no callback, so `CloudflareSocket.write()` fell back to its no-op default callback and a rejected `WritableStreamDefaultWriter.write()` was discarded. The query then hung until the socket closed and the real failure surfaced only as "Connection terminated unexpectedly". Emit the rejection as an `error` event when no callback was supplied, matching what a `net.Socket` does, so `Connection` reports it. Writes that do pass a callback keep receiving the error there. --- packages/pg-cloudflare/src/index.ts | 16 +++++++++---- packages/pg-esm-test/pg-cloudflare.test.js | 28 ++++++++++++++++++++++ 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/packages/pg-cloudflare/src/index.ts b/packages/pg-cloudflare/src/index.ts index 527b433f5..911c1cde9 100644 --- a/packages/pg-cloudflare/src/index.ts +++ b/packages/pg-cloudflare/src/index.ts @@ -87,22 +87,30 @@ export class CloudflareSocket extends EventEmitter { write( data: Uint8Array | string, encodingOrCallback: BufferEncoding | ((error?: unknown) => void) = 'utf8', - callback: (error?: unknown) => void = () => {} + callback?: (error?: unknown) => void ): true | void { const encoding = typeof encodingOrCallback === 'function' ? 'utf8' : encodingOrCallback if (typeof encodingOrCallback === 'function') callback = encodingOrCallback - if (data.length === 0) return callback() + if (data.length === 0) return callback?.() if (typeof data === 'string') data = Buffer.from(data, encoding) log('sending data direct:', data) this._cfWriter!.write(data).then( () => { log('data sent') - callback() + callback?.() }, (err) => { log('send error', err) - callback(err) + // `Connection._send()` writes without a callback, so a rejected write has + // nowhere to be reported: the query hangs until the socket closes and the + // real reason is lost behind "Connection terminated unexpectedly". Emit + // the failure like a net.Socket would so the connection can report it. + if (callback) { + callback(err) + } else { + this.emit('error', err) + } } ) return true diff --git a/packages/pg-esm-test/pg-cloudflare.test.js b/packages/pg-esm-test/pg-cloudflare.test.js index 683229158..0a425ccc5 100644 --- a/packages/pg-esm-test/pg-cloudflare.test.js +++ b/packages/pg-esm-test/pg-cloudflare.test.js @@ -38,6 +38,34 @@ describe('pg-cloudflare', () => { await promise }) + it('should emit error when a write without a callback fails', async () => { + const socket = new CloudflareSocket() + const writeError = new Error('write failed') + socket._cfWriter = { write: () => Promise.reject(writeError) } + + const emitted = new Promise((resolve, reject) => { + const timer = setTimeout(() => reject(new Error('error event was not emitted')), 100) + socket.once('error', (error) => { + clearTimeout(timer) + resolve(error) + }) + socket.write(Buffer.from('x')) + }) + + assert.equal(await emitted, writeError) + }) + + it('should report a failed write to the callback rather than emitting error', async () => { + const socket = new CloudflareSocket() + const writeError = new Error('write failed') + socket._cfWriter = { write: () => Promise.reject(writeError) } + socket.once('error', () => assert.fail('error event was emitted despite a callback')) + + const reported = await new Promise((resolve) => socket.write(Buffer.from('x'), resolve)) + + assert.equal(reported, writeError) + }) + it('should emit close when ending a socket whose closed promise never settles', async () => { const socket = new CloudflareSocket() socket._cfSocket = {