@vibeorm/adapter-pg 4.3.0 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -3
- package/dist/batch.d.ts +8 -0
- package/dist/client-turns.d.ts +10 -0
- package/dist/connection-group.d.ts +62 -0
- package/dist/connection-string.d.ts +55 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +704 -96
- package/dist/index.js.map +9 -7
- package/dist/pool-config.d.ts +13 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -41,19 +41,36 @@ const db = VibeClient({ adapter: pgAdapter({ pool }) });
|
|
|
41
41
|
|
|
42
42
|
`pgAdapter(options: PgAdapterOptions)` — `connectionString` or `pool` is required. Without either, the factory throws `VIBE_CONFIG` (`meta.reason: "missing-address"`) before any pool or connection exists. The adapter never reads `DATABASE_URL`. Give a complete URL: node-postgres fills parts a URL leaves out (host, user, database, …) from the `PG*` environment variables.
|
|
43
43
|
|
|
44
|
+
**TLS with `sslrootcert=system`.** Managed providers such as PlanetScale hand out URLs ending in `?sslmode=verify-full&sslrootcert=system`. In libpq, `sslrootcert=system` means "trust the CA roots of the system"; node-postgres reads it as a file named `system` and fails with `ENOENT`. The adapter accepts it: the connection verifies the server certificate's chain and host name (`sslmode=verify-full`) against the runtime's default trust store — Node's bundled Mozilla roots plus `NODE_EXTRA_CA_CERTS`. To use the roots libpq itself would load, start Node with `--use-openssl-ca` (OpenSSL's default locations, honouring `SSL_CERT_FILE` / `SSL_CERT_DIR`), or with `--use-system-ca` for the operating system's store. As in libpq, an omitted `sslmode` means `verify-full`, and a weaker `sslmode` next to `sslrootcert=system` is refused at construction with `VIBE_CONFIG` (`meta.reason: "sslrootcert-system-weak-sslmode"`). `cancellationConnectionString` follows the same rule. A caller-supplied `pool` is configured by its owner: build it from the URL with `sslmode=verify-full` and without `sslrootcert=system`.
|
|
45
|
+
|
|
44
46
|
| Option | Type | Default | Meaning |
|
|
45
47
|
|---|---|---|---|
|
|
46
48
|
| `connectionString` | `string` | — (required without `pool`) | PostgreSQL connection URL |
|
|
47
|
-
| `pool` | `PgPoolLike` | — (required without `connectionString`) | An existing `pg.Pool` to use instead of creating one; it is never ended by the adapter, and it wins when both are given |
|
|
49
|
+
| `pool` | `PgPoolLike` | — (required without `connectionString`) | An existing `pg.Pool` to use instead of creating one; it is never ended by the adapter, and it wins when both are given. A pool-like object must return a distinct client object from `connect()` for each physical connection, never the same object for another one (`pg.Pool` does): native RLS `verify: "per-connection"` and `mode: "operator"` key their verdicts on it |
|
|
48
50
|
| `max` | `number` | `10` | Maximum pooled connections; ignored when `pool` is given |
|
|
51
|
+
| `min` | `number` | pg-pool's `0` | Connections the pool keeps open however long they sit idle (pg-pool's `min`). `db.$connect()` opens this many at once, so the first burst finds them warm (see [A database far away](#a-database-far-away)). An integer from `0` through `max`; other values throw `VIBE_CONFIG`. Refused with `VIBE_CONFIG` next to a caller-supplied `pool`: set `min` on that pool instead |
|
|
49
52
|
| `statementTimeout` | `number` (ms) | — | Pool-wide `statement_timeout`; a per-transaction `timeout` overrides it |
|
|
50
53
|
| `connectionTimeout` | `number` (ms) | — | Wait for a pool checkout before failing (pg's `connectionTimeoutMillis`) |
|
|
51
54
|
| `idleTimeout` | `number` (ms) | pg-pool's `10000` | How long a pooled connection may sit idle before the pool closes it (pg's `idleTimeoutMillis`). Integer milliseconds from `0` through `2147483647`; other values throw `VIBE_CONFIG`. The next query after a quiet spell opens a new connection (TCP, startup, authentication — about 2.4 ms locally, more over TLS); a longer value, or `0` (never close), keeps connections open at the cost of held server connections. Ignored when `pool` is given |
|
|
52
55
|
| `cancellationConnectionString` | `string` | Owned `connectionString`; none for an injected `pool` | Separate control endpoint for running read cancellation; same PostgreSQL server/database, with permission to signal the target role |
|
|
53
|
-
| `preparedStatements` | `boolean` | `false` | Send ORM-path queries as named prepared statements. Measured −17…−25 % on reads and −6 % on a by-key update (see below). Off by default because of transaction-mode poolers — turn it on for direct connections, session pooling, or PgBouncer 1.21+ with `max_prepared_statements > 0` |
|
|
56
|
+
| `preparedStatements` | `boolean` | `false` | Send ORM-path queries as named prepared statements. Measured −17…−25 % on reads and −6 % on a by-key update (see below). Off by default because of transaction-mode poolers — turn it on for direct connections, session pooling, or PgBouncer 1.21+ with `max_prepared_statements > 0`. Works with an injected `pool` too; the name budget (`stmtCacheMax`) then belongs to that pool and is shared by every adapter on it |
|
|
54
57
|
| `stmtCacheMax` | `number` | `1000` | Budget of prepared-statement names per pool. Names never evict (an evicted name stays prepared on the backend), so this bounds server-side retention per connection; a text arriving after the budget is spent runs unnamed |
|
|
55
58
|
| `planCacheMode` | `"auto" \| "force_custom_plan" \| "force_generic_plan"` | `"auto"` | PostgreSQL `plan_cache_mode` for every connection of the pool the adapter builds; sent in the `options` startup parameter, merged with an `options` the URL already carries (a URL that sets `plan_cache_mode` itself wins). Refused with `VIBE_CONFIG` next to a caller-supplied `pool` |
|
|
56
59
|
|
|
60
|
+
### A database far away
|
|
61
|
+
|
|
62
|
+
A new connection costs TCP, the startup message and authentication: about **three round trips** before the first query, more with TLS. On a local database that is noise (≈ 2.4 ms); across a network it is not. The framework team's report measured three parallel `findUnique` calls at 35 ms per round trip: **150 ms on a cold pool, 37 ms on a warm one** — the cold pool opened three connections first.
|
|
63
|
+
|
|
64
|
+
- Keep connections open: set `idleTimeout` long, or `0` (never close an idle connection). pg-pool's default closes a connection after 10 s idle, so the first query after a quiet spell pays the connect again.
|
|
65
|
+
- Keep a floor: `min: 3` keeps three connections open however long they idle.
|
|
66
|
+
- Warm up at start: `await db.$connect()` opens `min` connections at once (at least one) and returns them to the pool, so the first burst does not pay for them.
|
|
67
|
+
- With your own `pool`, set `min` and `idleTimeoutMillis` on that `pg.Pool`; the adapter's `min` is refused there, and `$connect()` opens one connection of it.
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
const db = VibeClient({ adapter: pgAdapter({ connectionString, max: 10, min: 3, idleTimeout: 0 }) });
|
|
71
|
+
await db.$connect(); // three connections open before the first request
|
|
72
|
+
```
|
|
73
|
+
|
|
57
74
|
### Prepared statements: the measured trade
|
|
58
75
|
|
|
59
76
|
Interleaved rounds on this repository's benchmark database (postgres 16, `apps/benchmark/scratch/probe-join-agg.ts`, p50 of 7 rounds):
|
|
@@ -104,7 +121,9 @@ The timings above are historical measurements, not a schema-recovery or current
|
|
|
104
121
|
|
|
105
122
|
This is a postgres-dialect adapter, so the full capability set is available: `RETURNING`, scalar array columns, native enums, `ILIKE`, `DISTINCT ON`, partial indexes, transactional DDL, `createManyAndReturn`, a real JSON column type, native `NULLS FIRST`/`NULLS LAST`, and the `"join"` relation strategy (`LEFT JOIN LATERAL` plus JSON aggregation). The generated capability table lives in [docs/dialects.md](https://github.com/vibeorm/vibeorm/blob/master/docs/dialects.md).
|
|
106
123
|
|
|
107
|
-
Transactions run on one checked-out client; nested `transaction()` calls become savepoints on that same client. Statements issued at the same time on one transaction (`Promise.all` on a transaction handle, a statement the callback did not await) wait in the adapter's own queue and reach node-postgres one at a time, in the order they were issued: the adapter never calls `client.query()` while that client still runs a query (node-postgres' own queue for such calls is deprecated and goes away in pg@9). The top-level transaction handle of a transaction without a `deadline
|
|
124
|
+
Transactions run on one checked-out client; nested `transaction()` calls become savepoints on that same client. Statements issued at the same time on one transaction (`Promise.all` on a transaction handle, a statement the callback did not await) wait in the adapter's own queue and reach node-postgres one at a time, in the order they were issued: the adapter never calls `client.query()` while that client still runs a query (node-postgres' own queue for such calls is deprecated and goes away in pg@9). When the statement in flight returns, every statement that waited for it leaves as ONE batch (one Sync), in the order issued: a callback whose whole body is `Promise.all` of three creates costs 3 round trips instead of 4. Nothing waits for more statements to arrive — a statement issued while nothing runs goes at once, and a single waiting statement goes alone, as before. Not under a `deadline`, on a client with a local `query_timeout`, behind a read with `cancellation: "running"`, or for raw SQL that may hold several statements (it keeps its own round trip, and what was issued after it waits for it). A statement that fails on the server rejects with its own error; the statements the server skipped behind it in the same batch, and two or more statements still waiting when it failed, get the aborted-transaction refusal and never run (a single waiting statement is still sent, as before). The top-level transaction handle of a transaction without a `deadline`, on a client without a local `query_timeout`, and the savepoint handles it opens (a nested `tx.$transaction([...])`), declare `orderedPipeline` — also under a `timeout`, which is only the server's `SET LOCAL statement_timeout` and is applied to every statement of a batch on its own: the array form of `$transaction` then sends a count-only write (`createMany`, `updateMany`, `deleteMany`, `upsertMany` as one statement) and the next operation's first statement in one round trip, and `COMMIT` travels with the last statement when the last operation is such a write — `[deleteMany, createMany, updateMany]` costs 1 round trip instead of 4, with or without a `timeout`. Otherwise `COMMIT` is sent alone after the last operation settled; the callback form can end with one statement of your own and `COMMIT` in one round trip through the `commitWith` option (see [COMMIT travels with the last statement](https://github.com/vibeorm/vibeorm/blob/master/docs/queries.md#transactions)). Its lanes keep lane order, never send a statement behind a lane that closed without issuing one, and never send anything behind a failed statement, also when that failure had no server answer (no SQLSTATE `25P02` ever reaches a caller); see `DatabaseAdapter.pipeline` and [Transactions](https://github.com/vibeorm/vibeorm/blob/master/docs/queries.md#transactions) for what a caller can observe. Inside a transaction, raw SQL without bound values that is provably ONE statement (no `;`, no `$`, no comment, not transaction control and not `COPY`) travels with a pending `BEGIN` or `SAVEPOINT` like a bound statement, so a framework's `SELECT set_config('lock_timeout', '5000', true)` no longer costs a round trip of its own; raw SQL that may hold several statements keeps its own round trip. Raw transaction control (`BEGIN` / `COMMIT` / `ROLLBACK` sent through the raw path, as `@vibeorm/migrate` does for transactional DDL) pins one client for the duration, so a raw transaction never splits across connections. Concurrent raw transactions on a single adapter are unsupported.
|
|
125
|
+
|
|
126
|
+
Outside transactions the adapter declares `connectionGroup` (when the driver can batch and no local `query_timeout` is configured — the same conditions as `oneRoundTripTransactions`): the `"query"` strategy's sibling relation loads of one read leave together on ONE pooled connection as one batch (one Sync), so `findUnique` with three sibling includes costs 2 round trips instead of 4. The statements of one such batch run in one implicit transaction of the server: under `READ COMMITTED` each still reads its own snapshot, a statement that fails makes PostgreSQL skip the rest of its batch (the read rejects with the same first failure as before), and a server-side `transaction_timeout` (PostgreSQL 17+) covers the whole batch. A checked-out client that cannot batch is handed back unused and the statements go one after another. See [Sibling relation loads outside transactions](https://github.com/vibeorm/vibeorm/blob/master/docs/queries.md#sibling-relation-loads-outside-transactions-adapter-pg).
|
|
108
127
|
|
|
109
128
|
A raw script that produces multiple PostgreSQL results returns only the **final command's** rows and affected-row count. Earlier results are not concatenated or summed; a final DDL command returns empty rows and zero affected rows.
|
|
110
129
|
|
package/dist/batch.d.ts
CHANGED
|
@@ -25,6 +25,12 @@
|
|
|
25
25
|
export type BatchStep = {
|
|
26
26
|
readonly text: string;
|
|
27
27
|
readonly values?: readonly unknown[];
|
|
28
|
+
/**
|
|
29
|
+
* Nobody reads this step's rows (statement-count pass EPIC 5: the final
|
|
30
|
+
* statement that rides with COMMIT): they are not decoded, so no type parser
|
|
31
|
+
* can fail on them; the step reports no rows and its command tag's count.
|
|
32
|
+
*/
|
|
33
|
+
readonly discardRows?: true;
|
|
28
34
|
};
|
|
29
35
|
/** A step's decoded result, shaped like the node-postgres result the adapter reads. */
|
|
30
36
|
export type BatchStepResult = {
|
|
@@ -62,6 +68,8 @@ type RowDescriptionField = {
|
|
|
62
68
|
readonly dataTypeID: number;
|
|
63
69
|
readonly format?: "text" | "binary";
|
|
64
70
|
};
|
|
71
|
+
/** Whether this `pg` build exports what a one-Sync batch needs (`utils.prepareValue`); per-client checks stay in {@link supportsBatch}. */
|
|
72
|
+
export declare function batchDriverAvailable(): boolean;
|
|
65
73
|
/**
|
|
66
74
|
* Whether `client` can run a one-Sync batch: a node-postgres client whose
|
|
67
75
|
* connection exposes the protocol writers, NOT in pipeline mode (pipeline mode
|
package/dist/client-turns.d.ts
CHANGED
|
@@ -28,6 +28,16 @@ export type ClientTurns = {
|
|
|
28
28
|
readonly run: <T>(send: () => Promise<T>) => Promise<T>;
|
|
29
29
|
/** Calls handed in that have not settled yet (the running one included). */
|
|
30
30
|
readonly outstanding: () => number;
|
|
31
|
+
/**
|
|
32
|
+
* The rejection of the call that settled LAST, or `undefined` when it
|
|
33
|
+
* fulfilled (or nothing settled yet). Read inside a turn, it is the outcome
|
|
34
|
+
* of the call right before it — already known there, before any of that
|
|
35
|
+
* call's own continuations ran (a burst of waiting statements reads it to
|
|
36
|
+
* refuse what the server would answer with 25P02).
|
|
37
|
+
*/
|
|
38
|
+
readonly lastFailure: () => {
|
|
39
|
+
readonly error: unknown;
|
|
40
|
+
} | undefined;
|
|
31
41
|
};
|
|
32
42
|
/** Start the turn keeper for one checked-out client. */
|
|
33
43
|
export declare function createClientTurns(): ClientTurns;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Root connection groups (round-trip follow-up, lane A2;
|
|
3
|
+
* `DatabaseAdapter.connectionGroup`).
|
|
4
|
+
*
|
|
5
|
+
* A read's sibling relation loads outside any transaction: the runtime opens
|
|
6
|
+
* one sub-lane per statement, each sub-lane issues ONE statement, and once the
|
|
7
|
+
* group is sealed and every sub-lane issued or closed, the statements leave
|
|
8
|
+
* TOGETHER, in sub-lane order, on ONE pooled connection — one one-Sync batch
|
|
9
|
+
* (`./batch.ts`), so one round trip instead of one per sibling.
|
|
10
|
+
*
|
|
11
|
+
* This module is the group's bookkeeping only: the adapter hands it how to
|
|
12
|
+
* prepare a statement and how to send the prepared steps (checkout, batch,
|
|
13
|
+
* release — or one statement after another where the checked-out client cannot
|
|
14
|
+
* batch). Failure semantics are those of sequential dispatch in sub-lane
|
|
15
|
+
* order: a failed step's caller gets its mapped error; the steps PostgreSQL
|
|
16
|
+
* skipped behind it reject with the aborted refusal naming that failure.
|
|
17
|
+
*/
|
|
18
|
+
import { VibeError } from "@vibeorm/schema";
|
|
19
|
+
import type { DatabaseAdapter, PipelineGroup } from "@vibeorm/runtime";
|
|
20
|
+
import type { BatchOutcome, BatchStep } from "./batch.ts";
|
|
21
|
+
/** The `execute` parameters a sub-lane receives (the root's `execute` contract). */
|
|
22
|
+
export type ConnectionGroupExecute = Parameters<DatabaseAdapter["execute"]>[0];
|
|
23
|
+
/** One statement of a group, ready to send: its batch step and the call it came from. */
|
|
24
|
+
export type ConnectionGroupEntry = {
|
|
25
|
+
/** The step for the one-Sync batch (values already serialized for the driver). */
|
|
26
|
+
readonly step: BatchStep;
|
|
27
|
+
/** The sub-lane's original `execute` call (for a send that cannot batch: `root.execute`). */
|
|
28
|
+
readonly call: ConnectionGroupExecute;
|
|
29
|
+
};
|
|
30
|
+
/** What the adapter provides to a group. */
|
|
31
|
+
export type ConnectionGroupDriver = {
|
|
32
|
+
/** The fields every sub-lane adapter carries (the root's declarations). */
|
|
33
|
+
readonly base: Pick<DatabaseAdapter, "dialect" | "provider" | "wire" | "budgets" | "roundTrips" | "formatArrayParam">;
|
|
34
|
+
/**
|
|
35
|
+
* Serialize one statement for the batch. A value the driver refuses throws
|
|
36
|
+
* (mapped): that sub-lane's call rejects with it and it holds nothing.
|
|
37
|
+
*/
|
|
38
|
+
readonly prepare: (call: ConnectionGroupExecute) => BatchStep;
|
|
39
|
+
/**
|
|
40
|
+
* Send the group's statements, in order, as one round trip — or one after
|
|
41
|
+
* another where the checked-out connection cannot batch, reporting the
|
|
42
|
+
* statements after the first failure as skipped. Rejects only when nothing
|
|
43
|
+
* was sent (a failed checkout, a closed adapter): every statement then
|
|
44
|
+
* rejects with that error, mapped.
|
|
45
|
+
*/
|
|
46
|
+
readonly send: (entries: readonly ConnectionGroupEntry[]) => Promise<BatchOutcome>;
|
|
47
|
+
/** Map a driver failure to the adapter's typed error. */
|
|
48
|
+
readonly mapError: (error: unknown) => VibeError;
|
|
49
|
+
};
|
|
50
|
+
/** A broken use of a connection group — a runtime defect, refused before anything is sent. */
|
|
51
|
+
export declare function connectionGroupMisuse(params: {
|
|
52
|
+
message: string;
|
|
53
|
+
}): VibeError;
|
|
54
|
+
/**
|
|
55
|
+
* Open one connection group ({@link PipelineGroup}). Synchronous and free:
|
|
56
|
+
* nothing is checked out until the group is sealed and every sub-lane issued
|
|
57
|
+
* its statement or closed; a group that ends without a statement sends nothing.
|
|
58
|
+
*/
|
|
59
|
+
export declare function openConnectionGroup(params: {
|
|
60
|
+
driver: ConnectionGroupDriver;
|
|
61
|
+
}): PipelineGroup;
|
|
62
|
+
//# sourceMappingURL=connection-group.d.ts.map
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* libpq's `sslrootcert=system` for node-postgres.
|
|
3
|
+
*
|
|
4
|
+
* Managed PostgreSQL providers (PlanetScale, among others) hand out URLs such
|
|
5
|
+
* as `postgresql://…@host:5432/postgres?sslmode=verify-full&sslrootcert=system`.
|
|
6
|
+
* In libpq (PostgreSQL 16+) the value `system` means "load the trusted CA roots
|
|
7
|
+
* of the SSL implementation"; libpq then defaults `sslmode` to `verify-full`
|
|
8
|
+
* and refuses any weaker mode (libpq-connect, `sslrootcert`). node-postgres
|
|
9
|
+
* (pg 8.23 / pg-connection-string 2.14.1, the latest releases on 18.10.26, and
|
|
10
|
+
* its master branch) reads every `sslrootcert` as a FILE path —
|
|
11
|
+
* `fs.readFileSync("system")` — so the provider's URL fails before connecting
|
|
12
|
+
* with `ENOENT: no such file or directory, open 'system'`.
|
|
13
|
+
*
|
|
14
|
+
* {@link honourSystemRootCert} rewrites such a URL into node-postgres' own
|
|
15
|
+
* spelling of the same contract: `sslrootcert` removed, `sslmode=verify-full`.
|
|
16
|
+
* pg-connection-string then builds `ssl: {}` (plus any `sslcert`/`sslkey`),
|
|
17
|
+
* and `tls.connect` runs with its defaults: `rejectUnauthorized: true` (chain
|
|
18
|
+
* verified), the default `checkServerIdentity` against the host name
|
|
19
|
+
* node-postgres passes as `servername` (an IP address is matched against the
|
|
20
|
+
* certificate's IP entries, as in libpq), and — no `ca` given — the runtime's
|
|
21
|
+
* default trust store.
|
|
22
|
+
*
|
|
23
|
+
* "System roots" in that store: Node's default store is its bundled Mozilla
|
|
24
|
+
* root set (the program most operating-system stores derive from) plus
|
|
25
|
+
* `NODE_EXTRA_CA_CERTS`; started with `--use-openssl-ca` it loads OpenSSL's
|
|
26
|
+
* default locations, honouring `SSL_CERT_FILE` / `SSL_CERT_DIR` exactly as
|
|
27
|
+
* libpq-with-OpenSSL does, and with `--use-system-ca` the operating system's
|
|
28
|
+
* store. Bun's `node:tls` default is likewise its bundled root set plus
|
|
29
|
+
* `NODE_EXTRA_CA_CERTS`. The adapter passes no `ca`, so whichever of these the
|
|
30
|
+
* process chose applies — and connections reuse the runtime's parsed root
|
|
31
|
+
* store instead of re-parsing ~150 certificates per connection.
|
|
32
|
+
*/
|
|
33
|
+
/** The connection-string option a value came from, named in a refusal. */
|
|
34
|
+
export type ConnectionStringOption = "connectionString" | "cancellationConnectionString";
|
|
35
|
+
/**
|
|
36
|
+
* Rewrite a connection string carrying `sslrootcert=system` (libpq's "trust the
|
|
37
|
+
* system CA roots") into the form node-postgres understands: the parameter
|
|
38
|
+
* removed and `sslmode=verify-full` set, so the server certificate's chain and
|
|
39
|
+
* host name are both verified against the runtime's default trust store (see
|
|
40
|
+
* the module header). Any other string — no query, another `sslrootcert`, no
|
|
41
|
+
* `sslrootcert` — is returned byte-for-byte unchanged.
|
|
42
|
+
*
|
|
43
|
+
* Follows libpq's rule: with `system`, an omitted `sslmode` means
|
|
44
|
+
* `verify-full`, and every weaker mode is an error. node-postgres' own weaker
|
|
45
|
+
* spellings count too: `sslmode=no-verify`, and an `ssl` parameter other than
|
|
46
|
+
* `true` / `1`.
|
|
47
|
+
*
|
|
48
|
+
* @throws VibeError `VIBE_CONFIG` (`meta.reason: "sslrootcert-system-weak-sslmode"`)
|
|
49
|
+
* for a weaker mode. Neither the message nor `meta` carries the URL.
|
|
50
|
+
*/
|
|
51
|
+
export declare function honourSystemRootCert(params: {
|
|
52
|
+
connectionString: string;
|
|
53
|
+
option: ConnectionStringOption;
|
|
54
|
+
}): string;
|
|
55
|
+
//# sourceMappingURL=connection-string.d.ts.map
|
package/dist/index.d.ts
CHANGED
|
@@ -78,6 +78,11 @@ export type PgAdapterSettings = {
|
|
|
78
78
|
* other environment variable) itself: pass the URL you mean. Give a complete
|
|
79
79
|
* URL — node-postgres fills parts the URL omits (host, user, database, …)
|
|
80
80
|
* from the `PG*` environment variables.
|
|
81
|
+
*
|
|
82
|
+
* libpq's `sslrootcert=system` (as in PlanetScale's URLs) is accepted:
|
|
83
|
+
* the adapter verifies the server certificate's chain and host name against
|
|
84
|
+
* the runtime's default trust store (`sslmode=verify-full`), and refuses a
|
|
85
|
+
* weaker `sslmode` next to it with `VIBE_CONFIG`, as libpq does.
|
|
81
86
|
*/
|
|
82
87
|
readonly connectionString?: string;
|
|
83
88
|
/**
|
|
@@ -85,10 +90,26 @@ export type PgAdapterSettings = {
|
|
|
85
90
|
* ends a pool it did not create: `disconnect()` only ends this adapter (and
|
|
86
91
|
* rolls back and releases its own raw session); the pool stays usable.
|
|
87
92
|
* Wins over `connectionString` when both are given.
|
|
93
|
+
*
|
|
94
|
+
* `connect()` must return a distinct client object for each physical
|
|
95
|
+
* connection and never hand the same object out for another one (node-postgres
|
|
96
|
+
* `pg.Pool` does: a `Client` never reconnects). The returned object is the
|
|
97
|
+
* transaction's `connectionIdentity`, which native-RLS per-connection
|
|
98
|
+
* verdicts and the operator login check are keyed on.
|
|
88
99
|
*/
|
|
89
100
|
readonly pool?: PgPoolLike;
|
|
90
101
|
/** Maximum pooled connections (default 10). Ignored when `pool` is given. */
|
|
91
102
|
readonly max?: number;
|
|
103
|
+
/**
|
|
104
|
+
* Connections the pool keeps open however long they sit idle (pg-pool's
|
|
105
|
+
* `min`; default 0). `adapter.connect()` (`db.$connect()`) opens this many
|
|
106
|
+
* at once, so a burst of parallel queries finds them warm instead of paying
|
|
107
|
+
* TCP + startup + authentication per connection — about three round trips
|
|
108
|
+
* each, more with TLS. Must be an integer from 0 through `max`, else the
|
|
109
|
+
* factory throws `VIBE_CONFIG`. Refused next to `pool` (`VIBE_CONFIG`): the
|
|
110
|
+
* adapter cannot change a caller's pool — set `min` on that pool instead.
|
|
111
|
+
*/
|
|
112
|
+
readonly min?: number;
|
|
92
113
|
/**
|
|
93
114
|
* Pool-wide `statement_timeout` in milliseconds. Every connection inherits
|
|
94
115
|
* it; a per-transaction `timeout` overrides it for that transaction.
|