Skip to content

feat: add onidle and a Vercel attachDatabasePool adapter - #14

Merged
okomarov merged 1 commit into
mainfrom
feat/onidle-vercel
Oct 9, 2026
Merged

okomarov merged 1 commit into
mainfrom
feat/onidle-vercel

Conversation

@okomarov

@okomarov okomarov commented Oct 9, 2026

Copy link
Copy Markdown

Adds an onidle(connId) option and a @steadybrew/postgresjs/vercel adapter, so a pool can be passed to attachDatabasePool from @vercel/functions on Fluid Compute. Addresses porsager#1100, based on porsager#1147.

  • onidle is called from move() each time a connection is filed in open and its idle timer starts. It can be set after creation through sql.options.onidle. The internal listen and subscribe pools do not inherit it.
  • A throw from onidle is rethrown in a microtask, so it surfaces as an uncaught exception and never reaches the phase table. Unguarded, it is swallowed on the ReadyForQuery path. The existing callbacks are tracked in A throwing user callback breaks connection state #13.
  • vercelPool(sql) returns the pg-shaped object attachDatabasePool already detects. It requires a numeric idle_timeout and chains with an existing onidle.

Local validation (Docker, Node 24.21.0, PostgreSQL 17.11):

  • ESM and CJS integration suites pass.
  • Startup harness passes in ESM and CJS.
  • phase:idle-throw fails without the guard, and the listen test fails without the listen override.
  • The adapter typechecks against @vercel/functions 3.9.11 from ESM and CJS consumers.

Deno and workerd run here in CI only. Behaviour on a real Vercel deployment has not been checked yet.

Vercel Fluid Compute can suspend an instance while a pool connection is
idle and its idle timer has not fired, leaving the connection open on the
server. attachDatabasePool from @vercel/functions keeps the instance
alive for idleTimeout after each pool release event, but recognises pools
by duck typing and rejects postgres.js, which had no idle signal.

onidle(connId) is called from move() each time a connection is filed in
open and its idle timer starts, so it tracks exactly what Vercel's wait
needs to cover. It is read at call time, so it can be set through
sql.options.onidle after creation. The listen and subscribe pools set it
to null: otherwise the never-idling LISTEN connection would keep
re-arming the wait and report ids from a different pool.

The call is guarded: a throw is rethrown in a microtask, as
diagnostics_channel does for subscribers. Unguarded, a throw on the
ReadyForQuery path reaches protocolError, and errored() has no query to
reject, so the error vanishes; on release() it would throw at an
unrelated call site. The existing callbacks have worse failure modes
(#13).

@steadybrew/postgresjs/vercel exports vercelPool(sql), which returns the
pg-shaped object attachDatabasePool already accepts. It requires a
numeric idle_timeout, since without one idle connections never close and
keeping the instance alive does nothing, and it chains with an existing
onidle. It lives in its own subpath so it can be deprecated if Vercel
detects postgres.js natively.

Based on porsager#1147. Closes porsager#1100.

Tests: phase:idle-throw fails without the guard; the listen test fails
without the listen override (onidle fired twice). Integration cases cover
pool, queued, reserve, begin, late assignment and the adapter shape.
Typechecked against @vercel/functions 3.9.11 from ESM and CJS consumers.
@okomarov
okomarov merged commit d7e9b2d into main Oct 9, 2026
10 checks passed
@okomarov
okomarov deleted the feat/onidle-vercel branch October 9, 2026 21:02
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.

1 participant