@vibeorm/adapter-pg 2.0.0-alpha.9 → 2.0.1
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 +21 -3
- package/dist/errors.d.ts.map +1 -1
- package/dist/index.d.ts +48 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +451 -49
- package/dist/index.js.map +11 -8
- package/dist/params.d.ts +19 -2
- package/dist/params.d.ts.map +1 -1
- package/dist/pool-config.d.ts +58 -0
- package/dist/pool-config.d.ts.map +1 -0
- package/dist/savepoint-gate.d.ts +86 -0
- package/dist/savepoint-gate.d.ts.map +1 -0
- package/dist/statement-cache.d.ts +35 -9
- package/dist/statement-cache.d.ts.map +1 -1
- package/dist/transaction-budget.d.ts +144 -0
- package/dist/transaction-budget.d.ts.map +1 -0
- package/dist/transaction-sql.d.ts.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
This adapter runs VibeORM on [node-postgres](https://node-postgres.com/) (`pg`) against a PostgreSQL server. It works on both Bun and Node, and `pg` is a peer dependency you install yourself.
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
bun add @vibeorm/adapter-pg
|
|
8
|
+
bun add @vibeorm/adapter-pg pg
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
> **
|
|
11
|
+
> **Stable.** `2.0.0`, published under the `latest` dist-tag. The v1 line stays installable as `vibeorm@1`.
|
|
12
12
|
|
|
13
13
|
## Usage
|
|
14
14
|
|
|
@@ -44,8 +44,24 @@ const db = VibeClient({ adapter: pgAdapter({ pool }) });
|
|
|
44
44
|
| `max` | `number` | `10` | Maximum pooled connections; ignored when `pool` is given |
|
|
45
45
|
| `statementTimeout` | `number` (ms) | — | Pool-wide `statement_timeout`; a per-transaction `timeout` overrides it |
|
|
46
46
|
| `connectionTimeout` | `number` (ms) | — | Wait for a pool checkout before failing (pg's `connectionTimeoutMillis`) |
|
|
47
|
-
| `preparedStatements` | `boolean` | `false` | Send ORM-path queries as named prepared statements.
|
|
47
|
+
| `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 and session pooling |
|
|
48
48
|
| `stmtCacheMax` | `number` | `1000` | Maximum entries in the prepared-statement name cache (LRU) |
|
|
49
|
+
| `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` |
|
|
50
|
+
|
|
51
|
+
### Prepared statements: the measured trade
|
|
52
|
+
|
|
53
|
+
Interleaved rounds on this repository's benchmark database (postgres 16, `apps/benchmark/scratch/probe-join-agg.ts`, p50 of 7 rounds):
|
|
54
|
+
|
|
55
|
+
| shape | unnamed (default) | `preparedStatements: true` | + `planCacheMode: "force_custom_plan"` |
|
|
56
|
+
|---|---|---|---|
|
|
57
|
+
| findUnique × 10 | 1.28 ms | 1.01 ms (−21 %) | 1.06 ms (−17 %) |
|
|
58
|
+
| findMany where + orderBy + take | 0.175 ms | 0.143 ms (−18 %) | 0.144 ms (−18 %) |
|
|
59
|
+
| update by primary key | 0.82 ms | 0.77 ms (−6 %) | 0.76 ms (−8 %) |
|
|
60
|
+
| findMany + include (take 50) | 0.52 ms | 0.42 ms (−20 %) | 0.39 ms (−25 %) |
|
|
61
|
+
|
|
62
|
+
The saving is the server's parse/analyze and the protocol Parse/Describe round on every repeat, not the plan — which is why nearly all of it survives `force_custom_plan`, the setting that removes the classic downside of prepared statements (a generic plan after ~5 executions picking worse indexes for range, `LIKE` or skewed filters).
|
|
63
|
+
|
|
64
|
+
Why it stays opt-in: named statements live on the **backend** connection. Behind a transaction-mode pooler (PgBouncer, Supavisor, Neon's pooler) that is not configured for protocol-level prepared statements, a client that remembers a statement name from an earlier transaction is handed a different backend later. The outcomes are SQLSTATE `26000` (statement missing), `42P05` (name collision on Parse) or — the one no error reveals — another client's statement executed under the same `vibeorm_N` name when its parameter types happen to be compatible. Enable it only where every connection maps to one backend for the process lifetime.
|
|
49
65
|
|
|
50
66
|
## Runtime support
|
|
51
67
|
|
|
@@ -69,6 +85,8 @@ Transactions run on one checked-out client; nested `transaction()` calls become
|
|
|
69
85
|
- `serializeParams` (ORM path): plain objects **and** plain arrays are JSON-stringified, since at that point they can only be `json`/`jsonb` values. Scalar-list values are tagged by `formatArrayParam` with `PgArrayParam` and unwrapped, reaching the driver as the untouched array.
|
|
70
86
|
- `serializeParamsRaw` (raw path, `$queryRaw*` / `$executeRaw*` / DDL): plain JS arrays pass straight through, so `$queryRawUnsafe('… WHERE "id" = ANY($1)', [1, 2, 3])` matches rows. Binding an array *into* a `jsonb` column from a raw query means calling `JSON.stringify` yourself.
|
|
71
87
|
|
|
88
|
+
On both paths a `Date` is spelled as UTC ISO text (`2026-03-08T19:38:08.538Z`) rather than node-postgres' host-local form, and the result side attaches per-query parsers (never `pg.types` globally) that read `timestamp without time zone`, `date` and their arrays as **UTC** — pg-types reads them as host-local time. Together they give zone-less columns (Prisma's `@db.Timestamp(n)` / `@db.Date`) the UTC wall-clock contract shared with bun:sql, PGlite and Prisma, whatever the process `TZ` is; `timestamptz` is unaffected. See `docs/dialect-notes.md` "DateTime without time zone on postgres".
|
|
89
|
+
|
|
72
90
|
## Error mapping
|
|
73
91
|
|
|
74
92
|
Every driver failure becomes a `VibeError` with the driver error as `cause` and `meta` carrying `sqlstate` plus `constraint` / `table` / `column` when the server reported them. Table (`PG_SQLSTATE_ERROR_CODES`, frozen and mirrored by tests):
|
package/dist/errors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,SAAS,EAAE,KAAK,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAIhE;;;GAGG;AACH,eAAO,MAAM,uBAAuB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,SAAS,EAAE,KAAK,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAIhE;;;GAGG;AACH,eAAO,MAAM,uBAAuB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CA2B1E,CAAC;AAEH,4EAA4E;AAC5E,eAAO,MAAM,mBAAmB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAE/D,CAAC;AAYH;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE;IAAE,KAAK,EAAE,OAAO,CAAA;CAAE,GAAG,MAAM,GAAG,SAAS,CAK9E;AAID;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE;IAAE,KAAK,EAAE,OAAO,CAAA;CAAE,GAAG,SAAS,CA0BtE;AAED,wEAAwE;AACxE,wBAAsB,SAAS,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAMnE"}
|
package/dist/index.d.ts
CHANGED
|
@@ -6,11 +6,22 @@
|
|
|
6
6
|
* mapping. Only `VibeError` ever escapes.
|
|
7
7
|
*/
|
|
8
8
|
import type { DatabaseAdapter } from "@vibeorm/runtime";
|
|
9
|
+
import { type PlanCacheMode } from "./pool-config.ts";
|
|
9
10
|
/** The single `pg` query form this adapter uses (config object, never overloads). */
|
|
10
11
|
export type PgQueryConfig = {
|
|
11
12
|
readonly text: string;
|
|
12
13
|
readonly values?: unknown[];
|
|
13
14
|
readonly name?: string;
|
|
15
|
+
/**
|
|
16
|
+
* QUERY-LOCAL result parsers ({@link EXACT_TYPES}). node-postgres reads this
|
|
17
|
+
* off the query config, so nothing outside VibeORM's own statements is
|
|
18
|
+
* affected — a caller's pool keeps the driver's documented behaviour.
|
|
19
|
+
*/
|
|
20
|
+
readonly types?: PgTypeParsers;
|
|
21
|
+
};
|
|
22
|
+
/** The `pg-types` surface a query config may carry. */
|
|
23
|
+
export type PgTypeParsers = {
|
|
24
|
+
getTypeParser(oid: number, format?: "text" | "binary"): (value: string) => unknown;
|
|
14
25
|
};
|
|
15
26
|
/** The parts of a `pg` result this adapter reads. */
|
|
16
27
|
export type PgQueryResultLike = {
|
|
@@ -20,7 +31,13 @@ export type PgQueryResultLike = {
|
|
|
20
31
|
/** The parts of a `pg` PoolClient this adapter uses. */
|
|
21
32
|
export type PgPoolClientLike = {
|
|
22
33
|
query(config: PgQueryConfig): Promise<PgQueryResultLike>;
|
|
23
|
-
|
|
34
|
+
/**
|
|
35
|
+
* Hand the client back. A TRUTHY argument tells node-postgres to DESTROY it
|
|
36
|
+
* instead of pooling it — used when a rollback failed and the session state
|
|
37
|
+
* can no longer be asserted (EPIC 6). Optional so existing test doubles that
|
|
38
|
+
* declare `release(): void` still satisfy this type.
|
|
39
|
+
*/
|
|
40
|
+
release(destroy?: boolean): void;
|
|
24
41
|
/**
|
|
25
42
|
* EventEmitter surface. A real `pg.PoolClient` has it; test doubles need not,
|
|
26
43
|
* so both members are optional and the adapter feature-detects them.
|
|
@@ -59,13 +76,36 @@ export type PgAdapterOptions = {
|
|
|
59
76
|
readonly connectionTimeout?: number;
|
|
60
77
|
/**
|
|
61
78
|
* Send ORM-path queries as named prepared statements (default false).
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
79
|
+
*
|
|
80
|
+
* Measured on this repo's benchmark (postgres 16, interleaved rounds):
|
|
81
|
+
* −17…−25 % on reads, −6 % on a by-key update — the server skips the
|
|
82
|
+
* parse/analyze and the protocol Parse/Describe round on every repeat. The
|
|
83
|
+
* gain survives {@link PgAdapterOptions.planCacheMode} `force_custom_plan`,
|
|
84
|
+
* which removes the classic downside (a generic plan after ~5 executions
|
|
85
|
+
* picking worse indexes for range/LIKE/skewed filters).
|
|
86
|
+
*
|
|
87
|
+
* Off by default because named statements live on the BACKEND connection:
|
|
88
|
+
* behind a transaction-mode pooler (PgBouncer, Supavisor, Neon's pooler)
|
|
89
|
+
* without prepared-statement support a client that remembers a name from an
|
|
90
|
+
* earlier transaction is handed another backend later — SQLSTATE 26000,
|
|
91
|
+
* 42P05, or, worst, another client's statement under the same name executed
|
|
92
|
+
* without any error. Turn it on for direct connections and session pooling.
|
|
65
93
|
*/
|
|
66
94
|
readonly preparedStatements?: boolean;
|
|
67
95
|
/** Maximum entries in the prepared-statement name cache (default 1000, LRU). */
|
|
68
96
|
readonly stmtCacheMax?: number;
|
|
97
|
+
/**
|
|
98
|
+
* PostgreSQL `plan_cache_mode` for every connection of the pool this adapter
|
|
99
|
+
* builds (default `"auto"`, the server default — nothing is sent). Pair
|
|
100
|
+
* `"force_custom_plan"` with {@link PgAdapterOptions.preparedStatements}: the
|
|
101
|
+
* statement is still prepared once, but planned with the real parameter
|
|
102
|
+
* values on every execution. Rides the `options` startup parameter, merged
|
|
103
|
+
* with whatever the connection string already carries (a URL that sets
|
|
104
|
+
* `plan_cache_mode` itself wins). Refused with `VIBE_CONFIG` together with
|
|
105
|
+
* a caller-supplied `pool` — the adapter never reconfigures a pool it does
|
|
106
|
+
* not own; set it on that pool's own `options` instead.
|
|
107
|
+
*/
|
|
108
|
+
readonly planCacheMode?: PlanCacheMode;
|
|
69
109
|
};
|
|
70
110
|
/**
|
|
71
111
|
* Create a VibeORM adapter backed by node-postgres.
|
|
@@ -91,6 +131,10 @@ export { createSavepointCounter, nextSavepointName } from "./savepoints.ts";
|
|
|
91
131
|
export type { SavepointCounter } from "./savepoints.ts";
|
|
92
132
|
export { createStatementNameCache } from "./statement-cache.ts";
|
|
93
133
|
export type { StatementNameCache } from "./statement-cache.ts";
|
|
134
|
+
export { buildPoolConfig, placePlanCacheMode } from "./pool-config.ts";
|
|
135
|
+
export type { PlanCacheMode, StartupOptionsPlacement } from "./pool-config.ts";
|
|
94
136
|
export { ISOLATION_LEVEL_SQL, beginStatement, classifyRawTransactionControl, refuseNestedTransactionOptions, statementTimeoutStatement, } from "./transaction-sql.ts";
|
|
95
137
|
export type { RawTransactionControl } from "./transaction-sql.ts";
|
|
138
|
+
export { DEFAULT_DEADLINE_CLOCK, engineStatementBudgetMs, isTransactionDeadlineError, startTransactionBudget, transactionClosedError, transactionDeadlineError, transactionOutcomeUnknownError, validateTransactionDeadline, } from "./transaction-budget.ts";
|
|
139
|
+
export type { BudgetStage, DeadlineClock, TransactionBudget, TransactionOutcome } from "./transaction-budget.ts";
|
|
96
140
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|