-
Notifications
You must be signed in to change notification settings - Fork 2
Drivers Postgres
Wire-protocol Postgres driver — written from scratch over compat.connect.
No external dependencies, runs on Deno, Bun, and Node.
Functional and tested against Postgres 12+ on the common path. Treat as
needing real-world soak testing before relying on it for production. A
30-minute soak (1.08M ops, 6 concurrent workers) runs clean in the test
suite (packages/drivers/engines/postgres/soak.ts).
- PostgreSQL wire protocol v3.0 (Postgres ≥ 7.4)
- SCRAM-SHA-256 auth (PG 10+ default) + cleartext password (warns when sent
over an unencrypted connection — see
allowCleartextPassword) -
Binary parameter format for
int4,int8,float8,bool,timestamptz,bytea,jsonb— text format for everything else - Result decoding: text format (binary decode is a v1.x add)
-
:name:named parameters (rewritten internally to$N) - Transactions (commit / rollback / auto-rollback / timeout)
- Server
NOTICEmessages emitted as thenoticeevent - Custom
applicationNameandstatementTimeoutMs
MD5 password auth is not supported — configure your pg_hba.conf to
use scram-sha-256.
SCRAM-SHA-256 is the recommended mechanism and provides mutual authentication (the server must prove it knows the stored key). MD5 is refused outright.
Cleartext-password auth (AuthenticationCleartextPassword) sends the
password in the clear. Over an unencrypted connection that leaks the
credential to any on-path attacker, and it is the lever a rogue/MITM server
pulls to downgrade away from SCRAM's mutual-auth guarantee.
The driver still authenticates in that situation — pg_hba password and
PgBouncer's auth_type = plain are real, working deployments, and libpq
does the same unless require_auth is pinned — but it is loud about it:
every cleartext handshake over a non-TLS socket emits a notice beginning
WARNING: server requested cleartext-password auth over an unencrypted connection …, the same treatment an ssl.enforce: false downgrade gets.
Subscribe to the notice event (or pass _onnotice) if you want that in
your logs.
To harden, set allowCleartextPassword: false: the driver then throws
INVALID_AUTH instead of sending the password over an unencrypted socket.
Combined with the unconditional MD5 refusal, that pins the connection to
SCRAM-SHA-256 (or to cleartext over TLS, which is always permitted — the
transport is already encrypted).
import { PostgresEngine } from '@tundralibs/drivers/postgres';
// Refuse to send a password unless the transport is encrypted.
const pg = new PostgresEngine('app', {
host: 'db.internal',
database: 'app',
username: 'app',
password: '...',
allowCleartextPassword: false,
});Password normalization follows PostgreSQL: SCRAM applies SASLprep (RFC 4013), and for a password SASLprep rejects (a prohibited code point or a bidi violation) the driver falls back to the raw password — matching what the server and libpq do, so such credentials keep authenticating.
import { PostgresEngine } from '@tundralibs/drivers/postgres';
const pg = new PostgresEngine('app', {
host: 'localhost',
port: 5432,
database: 'app',
username: 'app',
password: '...',
pool: { min: 2, max: 10 },
});
const r = await pg.execute({
sql: 'SELECT id, name FROM users WHERE id = :id:',
params: { id: 1 },
});
console.log(r.data);
await pg.disconnect();Extends SQLEngineOptions.
| Option | Type | Default | Notes |
|---|---|---|---|
host |
string |
— | Required. |
port |
number |
5432 |
|
database |
string |
— | Required. |
username |
string |
— | Required. |
password |
string |
— | Optional (e.g. local trust auth). |
applicationName |
string |
Name |
Sent in StartupMessage as application_name. |
statementTimeoutMs |
number |
— | Sent as statement_timeout GUC. |
ssl |
various | — | See SSL/TLS. |
allowCleartextPassword |
boolean |
true |
Permit cleartext-password auth over an unencrypted connection. Permitted by default but warns via notice on every such handshake; set false to throw INVALID_AUTH instead (see Authentication). |
Just point host / port at the pooler. Don't configure the driver's
pool option — single-connection mode keeps a single backend and lets
the server-side pooler do its job. With pool: { max: N } you'd be
running a pool on top of a pool.
The driver uses the unnamed prepared-statement slot, so it is compatible
with PgBouncer's transaction-pooling mode out of the box. Don't use
LISTEN/NOTIFY, advisory locks, or session-scoped SET under transaction
pooling — those break regardless of the driver.
On auth: PgBouncer's auth_type = md5 is not supported (MD5 is refused
outright — use scram-sha-256). auth_type = plain works, but if the hop
to the pooler is unencrypted every connection emits the cleartext-auth
warning described under Authentication; set
allowCleartextPassword: false if you would rather that be a hard failure.
| Postgres type | JS |
|---|---|
int2 / int4
|
number |
int8 (BIGINT) |
bigint (preserves precision) |
float4 / float8
|
number |
bool |
boolean |
text / varchar
|
string |
bytea |
Uint8Array |
json / jsonb
|
parsed object |
date / timestamp / timestamptz
|
Date |
numeric |
string (preserves precision) |
uuid |
string |
Comparable to node-pg on single queries; 1.92× faster than
node-pg on a 16-concurrent-over-pool-of-8 workload thanks to the
inline pool. See PERFORMANCE.md for head-to-head
numbers.
Postgres SQLSTATE codes are mapped to standard EngineError.code values.
See Drivers.md → Standardized SQL error codes.
# 30-minute soak with 6 concurrent workers:
deno run --allow-all packages/drivers/engines/postgres/soak.ts
SOAK_DURATION_S=120 deno run --allow-all packages/drivers/engines/postgres/soak.ts