@vibeorm/adapter-pg 2.0.0 → 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 +19 -1
- package/dist/index.d.ts +29 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +134 -27
- package/dist/index.js.map +6 -5
- 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/package.json +2 -2
package/README.md
CHANGED
|
@@ -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/index.d.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
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;
|
|
@@ -75,13 +76,36 @@ export type PgAdapterOptions = {
|
|
|
75
76
|
readonly connectionTimeout?: number;
|
|
76
77
|
/**
|
|
77
78
|
* Send ORM-path queries as named prepared statements (default false).
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
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.
|
|
81
93
|
*/
|
|
82
94
|
readonly preparedStatements?: boolean;
|
|
83
95
|
/** Maximum entries in the prepared-statement name cache (default 1000, LRU). */
|
|
84
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;
|
|
85
109
|
};
|
|
86
110
|
/**
|
|
87
111
|
* Create a VibeORM adapter backed by node-postgres.
|
|
@@ -107,6 +131,8 @@ export { createSavepointCounter, nextSavepointName } from "./savepoints.ts";
|
|
|
107
131
|
export type { SavepointCounter } from "./savepoints.ts";
|
|
108
132
|
export { createStatementNameCache } from "./statement-cache.ts";
|
|
109
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";
|
|
110
136
|
export { ISOLATION_LEVEL_SQL, beginStatement, classifyRawTransactionControl, refuseNestedTransactionOptions, statementTimeoutStatement, } from "./transaction-sql.ts";
|
|
111
137
|
export type { RawTransactionControl } from "./transaction-sql.ts";
|
|
112
138
|
export { DEFAULT_DEADLINE_CLOCK, engineStatementBudgetMs, isTransactionDeadlineError, startTransactionBudget, transactionClosedError, transactionDeadlineError, transactionOutcomeUnknownError, validateTransactionDeadline, } from "./transaction-budget.ts";
|
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,EAEV,eAAe,EAIhB,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// packages/adapter-pg/src/index.ts
|
|
3
|
+
import { VibeError as VibeError5 } from "@vibeorm/schema";
|
|
3
4
|
import pg from "pg";
|
|
4
5
|
|
|
5
6
|
// packages/adapter-pg/src/errors.ts
|
|
@@ -70,30 +71,100 @@ class PgArrayParam {
|
|
|
70
71
|
this.values = values;
|
|
71
72
|
}
|
|
72
73
|
}
|
|
74
|
+
function serializeDate(params) {
|
|
75
|
+
const { value } = params;
|
|
76
|
+
const time = value.getTime();
|
|
77
|
+
if (Number.isNaN(time))
|
|
78
|
+
return value;
|
|
79
|
+
const year = value.getUTCFullYear();
|
|
80
|
+
if (year < 1 || year > 9999)
|
|
81
|
+
return value;
|
|
82
|
+
return value.toISOString();
|
|
83
|
+
}
|
|
73
84
|
function isDriverNativeValue(value) {
|
|
74
|
-
return value === null || value === undefined || typeof value !== "object" ||
|
|
85
|
+
return value === null || value === undefined || typeof value !== "object" || ArrayBuffer.isView(value) || value instanceof ArrayBuffer;
|
|
75
86
|
}
|
|
76
87
|
function serializeParams(params) {
|
|
77
88
|
return params.values.map((value) => {
|
|
78
89
|
if (value instanceof PgArrayParam)
|
|
79
|
-
return value.values;
|
|
90
|
+
return serializeList({ values: value.values });
|
|
91
|
+
if (value instanceof Date)
|
|
92
|
+
return serializeDate({ value });
|
|
80
93
|
if (isDriverNativeValue(value))
|
|
81
94
|
return value;
|
|
82
95
|
return JSON.stringify(value);
|
|
83
96
|
});
|
|
84
97
|
}
|
|
98
|
+
function serializeList(params) {
|
|
99
|
+
const { values } = params;
|
|
100
|
+
return values.some((value) => value instanceof Date) ? values.map((value) => value instanceof Date ? serializeDate({ value }) : value) : values;
|
|
101
|
+
}
|
|
85
102
|
function serializeParamsRaw(params) {
|
|
86
103
|
return params.values.map((value) => {
|
|
87
104
|
if (value instanceof PgArrayParam)
|
|
88
|
-
return value.values;
|
|
105
|
+
return serializeList({ values: value.values });
|
|
89
106
|
if (Array.isArray(value))
|
|
90
|
-
return value;
|
|
107
|
+
return serializeList({ values: value });
|
|
108
|
+
if (value instanceof Date)
|
|
109
|
+
return serializeDate({ value });
|
|
91
110
|
if (isDriverNativeValue(value))
|
|
92
111
|
return value;
|
|
93
112
|
return JSON.stringify(value);
|
|
94
113
|
});
|
|
95
114
|
}
|
|
96
115
|
|
|
116
|
+
// packages/adapter-pg/src/pool-config.ts
|
|
117
|
+
import { VibeError as VibeError2 } from "@vibeorm/schema";
|
|
118
|
+
var PLAN_CACHE_MODES = ["auto", "force_custom_plan", "force_generic_plan"];
|
|
119
|
+
function isPlanCacheMode(value) {
|
|
120
|
+
return typeof value === "string" && PLAN_CACHE_MODES.includes(value);
|
|
121
|
+
}
|
|
122
|
+
function assertPlanCacheMode(params) {
|
|
123
|
+
const { value } = params;
|
|
124
|
+
if (isPlanCacheMode(value))
|
|
125
|
+
return value;
|
|
126
|
+
throw new VibeError2({
|
|
127
|
+
code: "VIBE_CONFIG",
|
|
128
|
+
message: `planCacheMode must be one of ${PLAN_CACHE_MODES.map((mode) => `"${mode}"`).join(", ")}, received ${JSON.stringify(value)}`,
|
|
129
|
+
meta: { planCacheMode: value, allowed: PLAN_CACHE_MODES }
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
function placePlanCacheMode(params) {
|
|
133
|
+
const { connectionString, planCacheMode } = params;
|
|
134
|
+
if (planCacheMode === "auto")
|
|
135
|
+
return { connectionString, options: undefined };
|
|
136
|
+
const setting = `-c plan_cache_mode=${planCacheMode}`;
|
|
137
|
+
if (connectionString === undefined || !URL.canParse(connectionString)) {
|
|
138
|
+
return { connectionString, options: setting };
|
|
139
|
+
}
|
|
140
|
+
const url = new URL(connectionString);
|
|
141
|
+
const existing = url.searchParams.get("options");
|
|
142
|
+
if (existing === null) {
|
|
143
|
+
return { connectionString, options: setting };
|
|
144
|
+
}
|
|
145
|
+
if (/(^|\s)-c\s*plan_cache_mode=/.test(existing)) {
|
|
146
|
+
return { connectionString, options: undefined };
|
|
147
|
+
}
|
|
148
|
+
const merged = existing.trim().length === 0 ? setting : `${existing.trim()} ${setting}`;
|
|
149
|
+
url.searchParams.set("options", merged);
|
|
150
|
+
return { connectionString: url.toString(), options: merged };
|
|
151
|
+
}
|
|
152
|
+
function buildPoolConfig(params) {
|
|
153
|
+
const { max, statementTimeout, connectionTimeout } = params;
|
|
154
|
+
const placement = placePlanCacheMode({
|
|
155
|
+
connectionString: params.connectionString,
|
|
156
|
+
planCacheMode: params.planCacheMode ?? "auto"
|
|
157
|
+
});
|
|
158
|
+
const config = { connectionString: placement.connectionString, max };
|
|
159
|
+
if (statementTimeout !== undefined)
|
|
160
|
+
config.statement_timeout = statementTimeout;
|
|
161
|
+
if (connectionTimeout !== undefined)
|
|
162
|
+
config.connectionTimeoutMillis = connectionTimeout;
|
|
163
|
+
if (placement.options !== undefined)
|
|
164
|
+
config.options = placement.options;
|
|
165
|
+
return config;
|
|
166
|
+
}
|
|
167
|
+
|
|
97
168
|
// packages/adapter-pg/src/savepoint-gate.ts
|
|
98
169
|
import { AsyncLocalStorage } from "async_hooks";
|
|
99
170
|
|
|
@@ -232,7 +303,7 @@ function createStatementNameCache(params) {
|
|
|
232
303
|
}
|
|
233
304
|
|
|
234
305
|
// packages/adapter-pg/src/transaction-budget.ts
|
|
235
|
-
import { VibeError as
|
|
306
|
+
import { VibeError as VibeError3 } from "@vibeorm/schema";
|
|
236
307
|
var DEFAULT_DEADLINE_CLOCK = () => Date.now();
|
|
237
308
|
function validateTransactionDeadline(params) {
|
|
238
309
|
const { options, support, provider } = params;
|
|
@@ -240,14 +311,14 @@ function validateTransactionDeadline(params) {
|
|
|
240
311
|
if (deadline === undefined)
|
|
241
312
|
return;
|
|
242
313
|
if (support === "unsupported") {
|
|
243
|
-
throw new
|
|
314
|
+
throw new VibeError3({
|
|
244
315
|
code: "VIBE_UNSUPPORTED_CAPABILITY",
|
|
245
316
|
message: `${provider} cannot enforce a transaction deadline \u2014 remove the \`deadline\` option`,
|
|
246
317
|
meta: { provider, feature: "transactionDeadline", supported: support }
|
|
247
318
|
});
|
|
248
319
|
}
|
|
249
320
|
if (!Number.isInteger(deadline.totalMs) || deadline.totalMs <= 0) {
|
|
250
|
-
throw new
|
|
321
|
+
throw new VibeError3({
|
|
251
322
|
code: "VIBE_VALIDATION",
|
|
252
323
|
message: `transaction deadline \`totalMs\` must be a positive integer of milliseconds, received ${String(deadline.totalMs)}`,
|
|
253
324
|
meta: { provider, feature: "transactionDeadline", totalMs: deadline.totalMs }
|
|
@@ -255,7 +326,7 @@ function validateTransactionDeadline(params) {
|
|
|
255
326
|
}
|
|
256
327
|
const requested = deadline.enforcement ?? "between-statements";
|
|
257
328
|
if (requested === "cancel-running-statements" && support !== "cancel-running-statements") {
|
|
258
|
-
throw new
|
|
329
|
+
throw new VibeError3({
|
|
259
330
|
code: "VIBE_UNSUPPORTED_CAPABILITY",
|
|
260
331
|
message: `${provider} cannot cancel a statement that is already running, so the requested ` + "`cancel-running-statements` deadline guarantee is refused rather than silently downgraded \u2014 " + 'ask for "between-statements" if a budget checked between statements is enough',
|
|
261
332
|
meta: { provider, feature: "transactionDeadline", requested, supported: support }
|
|
@@ -316,7 +387,7 @@ function engineStatementBudgetMs(params) {
|
|
|
316
387
|
function transactionDeadlineError(params) {
|
|
317
388
|
const { provider, stage, totalMs, overdueMs } = params;
|
|
318
389
|
const what = stage === "commit" ? "the transaction was rolled back instead of committed" : "the statement was never sent and the transaction is being rolled back";
|
|
319
|
-
return new
|
|
390
|
+
return new VibeError3({
|
|
320
391
|
code: "VIBE_TRANSACTION",
|
|
321
392
|
message: `transaction deadline of ${totalMs} ms expired ${Math.max(0, Math.round(overdueMs))} ms ago \u2014 ${what}. ` + "Nothing is retried automatically: work the callback did outside the database may already have happened.",
|
|
322
393
|
meta: { provider, reason: "transaction-deadline", stage, totalMs, overdueMs: Math.max(0, Math.round(overdueMs)) }
|
|
@@ -324,7 +395,7 @@ function transactionDeadlineError(params) {
|
|
|
324
395
|
}
|
|
325
396
|
function transactionClosedError(params) {
|
|
326
397
|
const { provider, outcome, stage } = params;
|
|
327
|
-
return new
|
|
398
|
+
return new VibeError3({
|
|
328
399
|
code: "VIBE_TRANSACTION",
|
|
329
400
|
message: `this transaction has already finished (${outcome}), so its client can no longer be used \u2014 ` + "a handle kept past the end of `$transaction` would run outside the transaction, on a connection " + "that now belongs to someone else",
|
|
330
401
|
meta: { provider, reason: "transaction-closed", outcome, stage }
|
|
@@ -332,7 +403,7 @@ function transactionClosedError(params) {
|
|
|
332
403
|
}
|
|
333
404
|
function transactionOutcomeUnknownError(params) {
|
|
334
405
|
const { provider, totalMs, cause } = params;
|
|
335
|
-
return new
|
|
406
|
+
return new VibeError3({
|
|
336
407
|
code: "VIBE_TRANSACTION",
|
|
337
408
|
message: `transaction deadline of ${totalMs} ms expired and the rollback that should have cleaned up failed \u2014 ` + "the commit state of this transaction is UNKNOWN. It is not retried automatically; inspect the data " + "before deciding.",
|
|
338
409
|
meta: {
|
|
@@ -351,7 +422,7 @@ function isTransactionDeadlineError(error) {
|
|
|
351
422
|
}
|
|
352
423
|
|
|
353
424
|
// packages/adapter-pg/src/transaction-sql.ts
|
|
354
|
-
import { VibeError as
|
|
425
|
+
import { VibeError as VibeError4 } from "@vibeorm/schema";
|
|
355
426
|
var ISOLATION_LEVEL_SQL = Object.freeze({
|
|
356
427
|
ReadCommitted: "READ COMMITTED",
|
|
357
428
|
RepeatableRead: "REPEATABLE READ",
|
|
@@ -364,7 +435,7 @@ function beginStatement(params) {
|
|
|
364
435
|
function statementTimeoutStatement(params) {
|
|
365
436
|
const { timeout } = params;
|
|
366
437
|
if (!Number.isInteger(timeout) || timeout < 0) {
|
|
367
|
-
throw new
|
|
438
|
+
throw new VibeError4({
|
|
368
439
|
code: "VIBE_VALIDATION",
|
|
369
440
|
message: `transaction timeout must be a non-negative integer of milliseconds, received ${String(timeout)}`,
|
|
370
441
|
meta: { timeout }
|
|
@@ -401,7 +472,7 @@ function refuseNestedTransactionOptions(params) {
|
|
|
401
472
|
meta.timeout = options.timeout;
|
|
402
473
|
if (options.deadline !== undefined)
|
|
403
474
|
meta.deadline = options.deadline;
|
|
404
|
-
throw new
|
|
475
|
+
throw new VibeError4({
|
|
405
476
|
code: "VIBE_VALIDATION",
|
|
406
477
|
message: `nested transactions run as savepoints and cannot honor ${passed.join(" or ")} \u2014 a savepoint cannot change the isolation level of the transaction it joins, a nested timeout is not enforceable, and a nested deadline would need its own session setting on a connection it does not own (it inherits the top-level one instead); pass options on the top-level $transaction`,
|
|
407
478
|
meta
|
|
@@ -432,18 +503,55 @@ var BUDGETS = Object.freeze({
|
|
|
432
503
|
});
|
|
433
504
|
var NUMERIC_ARRAY_OID = 1231;
|
|
434
505
|
var TEXT_ARRAY_OID = 1009;
|
|
506
|
+
var TIMESTAMP_OID = 1114;
|
|
507
|
+
var TIMESTAMP_ARRAY_OID = 1115;
|
|
508
|
+
var DATE_OID = 1082;
|
|
509
|
+
var DATE_ARRAY_OID = 1182;
|
|
510
|
+
var driverTypeParser = pg.types.getTypeParser;
|
|
511
|
+
var ZONE_LESS_TEXT = /^\d{4}-\d{2}-\d{2}(?: \d{2}:\d{2}:\d{2}(?:\.\d{1,6})?)?$/;
|
|
512
|
+
function parseZoneLessAsUtc(value) {
|
|
513
|
+
if (ZONE_LESS_TEXT.test(value)) {
|
|
514
|
+
const iso = value.includes(" ") ? `${value.replace(" ", "T").replace(/(\.\d{3})\d+$/, "$1")}Z` : value;
|
|
515
|
+
const parsed = new Date(iso);
|
|
516
|
+
if (!Number.isNaN(parsed.getTime()))
|
|
517
|
+
return parsed;
|
|
518
|
+
}
|
|
519
|
+
return driverTypeParser(TIMESTAMP_OID, "text")(value);
|
|
520
|
+
}
|
|
521
|
+
function parseZoneLessArrayAsUtc(value) {
|
|
522
|
+
const mapElements = (element) => {
|
|
523
|
+
if (element === null)
|
|
524
|
+
return null;
|
|
525
|
+
if (Array.isArray(element))
|
|
526
|
+
return element.map(mapElements);
|
|
527
|
+
return parseZoneLessAsUtc(String(element));
|
|
528
|
+
};
|
|
529
|
+
return mapElements(driverTypeParser(TEXT_ARRAY_OID, "text")(value));
|
|
530
|
+
}
|
|
435
531
|
var EXACT_TYPES = {
|
|
436
532
|
getTypeParser(oid, format) {
|
|
437
|
-
if (
|
|
438
|
-
|
|
533
|
+
if (format === undefined || format === "text") {
|
|
534
|
+
if (oid === NUMERIC_ARRAY_OID)
|
|
535
|
+
return driverTypeParser(TEXT_ARRAY_OID, "text");
|
|
536
|
+
if (oid === TIMESTAMP_OID || oid === DATE_OID)
|
|
537
|
+
return parseZoneLessAsUtc;
|
|
538
|
+
if (oid === TIMESTAMP_ARRAY_OID || oid === DATE_ARRAY_OID)
|
|
539
|
+
return parseZoneLessArrayAsUtc;
|
|
439
540
|
}
|
|
440
541
|
return driverTypeParser(oid, format);
|
|
441
542
|
}
|
|
442
543
|
};
|
|
443
|
-
var driverTypeParser = pg.types.getTypeParser;
|
|
444
544
|
function pgAdapter(options) {
|
|
445
545
|
const usePrepared = options?.preparedStatements ?? false;
|
|
446
546
|
const stmtCacheMax = options?.stmtCacheMax ?? 1000;
|
|
547
|
+
const planCacheMode = options?.planCacheMode === undefined ? "auto" : assertPlanCacheMode({ value: options.planCacheMode });
|
|
548
|
+
if (planCacheMode !== "auto" && options?.pool !== undefined) {
|
|
549
|
+
throw new VibeError5({
|
|
550
|
+
code: "VIBE_CONFIG",
|
|
551
|
+
message: "planCacheMode configures the pool the adapter builds \u2014 it cannot be applied to a caller-supplied `pool`; " + 'set `options: "-c plan_cache_mode=\u2026"` on that pool instead',
|
|
552
|
+
meta: { planCacheMode }
|
|
553
|
+
});
|
|
554
|
+
}
|
|
447
555
|
let pool = null;
|
|
448
556
|
let ownsPool = false;
|
|
449
557
|
let rawSession = null;
|
|
@@ -456,16 +564,13 @@ function pgAdapter(options) {
|
|
|
456
564
|
ownsPool = false;
|
|
457
565
|
return pool;
|
|
458
566
|
}
|
|
459
|
-
const config = {
|
|
567
|
+
const config = buildPoolConfig({
|
|
460
568
|
connectionString: options?.connectionString ?? process.env.DATABASE_URL,
|
|
461
|
-
max: options?.max ?? 10
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
}
|
|
466
|
-
if (options?.connectionTimeout !== undefined) {
|
|
467
|
-
config.connectionTimeoutMillis = options.connectionTimeout;
|
|
468
|
-
}
|
|
569
|
+
max: options?.max ?? 10,
|
|
570
|
+
...options?.statementTimeout === undefined ? {} : { statementTimeout: options.statementTimeout },
|
|
571
|
+
...options?.connectionTimeout === undefined ? {} : { connectionTimeout: options.connectionTimeout },
|
|
572
|
+
planCacheMode
|
|
573
|
+
});
|
|
469
574
|
const PoolCtor = pg.Pool;
|
|
470
575
|
pool = new PoolCtor(config);
|
|
471
576
|
pool.on?.("error", () => {});
|
|
@@ -666,6 +771,7 @@ export {
|
|
|
666
771
|
serializeParamsRaw,
|
|
667
772
|
serializeParams,
|
|
668
773
|
refuseNestedTransactionOptions,
|
|
774
|
+
placePlanCacheMode,
|
|
669
775
|
pgErrorSqlstate,
|
|
670
776
|
pgAdapter,
|
|
671
777
|
nextSavepointName,
|
|
@@ -675,6 +781,7 @@ export {
|
|
|
675
781
|
createStatementNameCache,
|
|
676
782
|
createSavepointCounter,
|
|
677
783
|
classifyRawTransactionControl,
|
|
784
|
+
buildPoolConfig,
|
|
678
785
|
beginStatement,
|
|
679
786
|
PgArrayParam,
|
|
680
787
|
PG_SQLSTATE_REASONS,
|
|
@@ -683,4 +790,4 @@ export {
|
|
|
683
790
|
DEFAULT_DEADLINE_CLOCK
|
|
684
791
|
};
|
|
685
792
|
|
|
686
|
-
//# debugId=
|
|
793
|
+
//# debugId=ABBE5E12E130DBA764756E2164756E21
|
package/dist/index.js.map
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../src/index.ts", "../src/errors.ts", "../src/params.ts", "../src/savepoint-gate.ts", "../src/savepoints.ts", "../src/statement-cache.ts", "../src/transaction-budget.ts", "../src/transaction-sql.ts"],
|
|
3
|
+
"sources": ["../src/index.ts", "../src/errors.ts", "../src/params.ts", "../src/pool-config.ts", "../src/savepoint-gate.ts", "../src/savepoints.ts", "../src/statement-cache.ts", "../src/transaction-budget.ts", "../src/transaction-sql.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"/**\n * @vibeorm/adapter-pg — node-postgres (`pg`) adapter for VibeORM v2.\n *\n * Runs on Bun and Node. Owns pooling, optional named prepared statements,\n * transactions (nested → savepoints on the same client) and driver-error\n * mapping. Only `VibeError` ever escapes.\n */\n\nimport type {\n AdapterBudgetSupport,\n DatabaseAdapter,\n QueryResult,\n TransactionOptions,\n WireFidelity,\n} from \"@vibeorm/runtime\";\nimport type { Dialect, Provider } from \"@vibeorm/schema\";\nimport pg from \"pg\";\nimport { mapPgDriverError, runMapped } from \"./errors.ts\";\nimport { PgArrayParam, serializeParams, serializeParamsRaw } from \"./params.ts\";\nimport {\n createSavepointScope,\n runNestedSavepoint,\n runRootScope,\n scopeTurn,\n type SavepointScope,\n} from \"./savepoint-gate.ts\";\nimport { createStatementNameCache, type StatementNameCache } from \"./statement-cache.ts\";\nimport {\n engineStatementBudgetMs,\n isTransactionDeadlineError,\n startTransactionBudget,\n type TransactionBudget,\n transactionOutcomeUnknownError,\n validateTransactionDeadline,\n} from \"./transaction-budget.ts\";\nimport {\n beginStatement,\n classifyRawTransactionControl,\n refuseNestedTransactionOptions,\n statementTimeoutStatement,\n} from \"./transaction-sql.ts\";\n\n// ─── Minimal pg surface ───────────────────────────────────────────\n\n/** The single `pg` query form this adapter uses (config object, never overloads). */\nexport type PgQueryConfig = {\n readonly text: string;\n readonly values?: unknown[];\n readonly name?: string;\n /**\n * QUERY-LOCAL result parsers ({@link EXACT_TYPES}). node-postgres reads this\n * off the query config, so nothing outside VibeORM's own statements is\n * affected — a caller's pool keeps the driver's documented behaviour.\n */\n readonly types?: PgTypeParsers;\n};\n\n/** The `pg-types` surface a query config may carry. */\nexport type PgTypeParsers = {\n getTypeParser(oid: number, format?: \"text\" | \"binary\"): (value: string) => unknown;\n};\n\n/** The parts of a `pg` result this adapter reads. */\nexport type PgQueryResultLike = {\n readonly rows: Record<string, unknown>[];\n readonly rowCount: number | null;\n};\n\n/** The parts of a `pg` PoolClient this adapter uses. */\nexport type PgPoolClientLike = {\n query(config: PgQueryConfig): Promise<PgQueryResultLike>;\n /**\n * Hand the client back. A TRUTHY argument tells node-postgres to DESTROY it\n * instead of pooling it — used when a rollback failed and the session state\n * can no longer be asserted (EPIC 6). Optional so existing test doubles that\n * declare `release(): void` still satisfy this type.\n */\n release(destroy?: boolean): void;\n /**\n * EventEmitter surface. A real `pg.PoolClient` has it; test doubles need not,\n * so both members are optional and the adapter feature-detects them.\n */\n on?(event: \"error\", listener: (error: unknown) => void): void;\n removeListener?(event: \"error\", listener: (error: unknown) => void): void;\n};\n\n/** The parts of a `pg` Pool this adapter uses — satisfied by a real `pg.Pool`. */\nexport type PgPoolLike = {\n connect(): Promise<PgPoolClientLike>;\n query(config: PgQueryConfig): Promise<PgQueryResultLike>;\n end(): Promise<void>;\n /** EventEmitter surface; optional for the same reason as on {@link PgPoolClientLike}. */\n on?(event: \"error\", listener: (error: unknown, client: PgPoolClientLike) => void): void;\n};\n\n/** Anything that can run a query: the pool itself or a checked-out client. */\ntype Queryable = { query(config: PgQueryConfig): Promise<PgQueryResultLike> };\n\n/**\n * Keep a CHECKED-OUT client's background failures from killing the process,\n * returning the detach to run before the client goes back to the pool.\n *\n * node-postgres listens for `'error'` on a pooled client only while that client\n * sits IDLE, and removes the listener the moment the client is checked out\n * (restoring it on release). A client this adapter holds — for a transaction,\n * or as a pinned raw session — therefore has NO listener, so a backend that\n * dies mid-transaction reaches `client.emit('error', …)` with none. An\n * `'error'` event without a listener is an uncaught exception: fatal under\n * Node, which turns a database failover into a dead application process.\n *\n * The listener deliberately does nothing with the error and is NOT an error\n * channel: the in-flight query already rejects on its own with the mapped\n * `VibeError`, and the pool discards the broken client. Its only job is to keep\n * the process alive. Detaching before `release()` matters just as much — a\n * listener left attached would shadow pg's own idle listener and stop the pool\n * evicting a client that fails later, while idle.\n */\n/**\n * Prepared-statement name registries, one per POOL (F16/F18) — see\n * ./statement-cache.ts for why they are shared and bounded. Weakly held, so a\n * pool that goes out of scope takes its registry with it.\n */\nconst POOL_STATEMENT_NAMES: WeakMap<PgPoolLike, StatementNameCache> = new WeakMap();\n\nfunction guardBackgroundErrors(params: { client: PgPoolClientLike }): () => void {\n const { client } = params;\n if (typeof client.on !== \"function\") return (): void => undefined;\n const swallow = (): void => {\n /* the failing call rejects with a mapped VibeError; nothing to report here */\n };\n client.on(\"error\", swallow);\n return (): void => {\n client.removeListener?.(\"error\", swallow);\n };\n}\n\n// ─── Options ──────────────────────────────────────────────────────\n\n/**\n * Options for {@link pgAdapter}. Either point it at a connection string (the\n * adapter then owns the pool it creates) or hand it a pool you already own.\n */\nexport type PgAdapterOptions = {\n /** PostgreSQL connection URL. Defaults to `process.env.DATABASE_URL`. */\n readonly connectionString?: string;\n /**\n * An existing `pg.Pool` to use instead of creating one. The adapter never\n * ends a pool it did not create, so `disconnect()` is a no-op in this mode.\n */\n readonly pool?: PgPoolLike;\n /** Maximum pooled connections (default 10). Ignored when `pool` is given. */\n readonly max?: number;\n /**\n * Pool-wide `statement_timeout` in milliseconds. Every connection inherits\n * it; a per-transaction `timeout` overrides it for that transaction.\n */\n readonly statementTimeout?: number;\n /** Milliseconds to wait for a pool checkout before failing (pg's `connectionTimeoutMillis`). */\n readonly connectionTimeout?: number;\n /**\n * Send ORM-path queries as named prepared statements (default false).\n * Named statements save planning time but let PostgreSQL fall back to a\n * generic plan after ~5 executions, which can pick worse indexes for range\n * filters — leave off unless profiling says otherwise.\n */\n readonly preparedStatements?: boolean;\n /** Maximum entries in the prepared-statement name cache (default 1000, LRU). */\n readonly stmtCacheMax?: number;\n};\n\n// ─── Adapter ──────────────────────────────────────────────────────\n\nconst DIALECT: Dialect = \"postgres\";\nconst PROVIDER: Provider = \"postgresql\";\n\n/**\n * node-postgres wire (board #30, live-verified on both execute paths):\n * timestamptz → `Date`, json/jsonb → parsed values, int4/float8 → numbers.\n * (int8 stays a string — BigInt decoding is real work and never pruned.)\n */\nconst WIRE: WireFidelity = Object.freeze({ dateTime: \"native\", json: \"parsed\", numbers: \"native\" } as const);\n\n/**\n * Timing budgets this adapter genuinely enforces (EPIC 6).\n *\n * - `acquireTimeout: true` — {@link PgAdapterOptions.connectionTimeout} maps to\n * node-postgres' `connectionTimeoutMillis`, which bounds the checkout wait\n * and rejects when the pool stays exhausted. The capability is declared here;\n * it is OFF until the option is set (pg's own default is \"wait forever\").\n * - `statementTimeout: \"all-statements\"` — PostgreSQL's `statement_timeout`\n * cancels ANY over-long statement, writes included (SQLSTATE 57014).\n * - `transactionDeadline: \"cancel-running-statements\"` — the strongest level:\n * the remaining budget is handed to the SERVER before each statement, so the\n * database itself stops work that would outlive the deadline. Nothing here is\n * a `Promise.race` over a statement that keeps running.\n */\nconst BUDGETS: AdapterBudgetSupport = Object.freeze({\n acquireTimeout: true,\n statementTimeout: \"all-statements\",\n transactionDeadline: \"cancel-running-statements\",\n} as const);\n\n// ─── Exact numeric arrays (F12) ───────────────────────────────\n\n/** `_numeric`: the array form of `numeric`/`DECIMAL`. */\nconst NUMERIC_ARRAY_OID = 1231;\n/** `_text`: its parser splits a postgres array literal into RAW element strings. */\nconst TEXT_ARRAY_OID = 1009;\n\n/**\n * Query-local type parsers that keep `numeric[]` EXACT.\n *\n * pg-types decodes `_numeric` with `parseFloat` per element, so\n * `9007199254740993` comes back as `9007199254740992` and `12.340` as the\n * number `12.34` — while the SCALAR `numeric` correctly stays a string and\n * VibeORM's Decimal contract is \"string in, string out\" on every dialect. The\n * array is the anomaly, and the loss happens inside the driver, before any\n * codec can see the value.\n *\n * The fix reuses the driver's OWN array-literal parser (`_text`), so array\n * syntax, quoting and NULL elements stay node-postgres' business; only the\n * per-element float conversion is dropped. It is attached PER QUERY (F12's\n * acceptance criterion), never by mutating `pg.types` globally: an injected\n * pool is shared property, and a global override would silently change every\n * other consumer in the process.\n */\nconst EXACT_TYPES: PgTypeParsers = {\n getTypeParser(oid: number, format?: \"text\" | \"binary\"): (value: string) => unknown {\n if (oid === NUMERIC_ARRAY_OID && (format === undefined || format === \"text\")) {\n return driverTypeParser(TEXT_ARRAY_OID, \"text\");\n }\n return driverTypeParser(oid, format);\n },\n};\n\n/**\n * `pg.types.getTypeParser` behind a plain numeric-OID signature. `@types/pg`\n * narrows the two-argument overload to its own `TypeId` union of well-known\n * OIDs, which does not include every array OID this function must look up.\n */\nconst driverTypeParser: (oid: number, format?: \"text\" | \"binary\") => (value: string) => unknown =\n pg.types.getTypeParser as unknown as (oid: number, format?: \"text\" | \"binary\") => (value: string) => unknown;\n\n/**\n * Create a VibeORM adapter backed by node-postgres.\n *\n * @example\n * ```ts\n * const adapter = pgAdapter({ connectionString: process.env.DATABASE_URL, max: 20 });\n * await adapter.connect();\n * const rows = await adapter.execute({ text: 'SELECT * FROM \"User\" WHERE \"id\" = $1', values: [1] });\n * ```\n *\n * @example Reusing a pool you already own\n * ```ts\n * import pg from \"pg\";\n * const pool = new pg.Pool({ connectionString });\n * const adapter = pgAdapter({ pool });\n * ```\n */\nexport function pgAdapter(options?: PgAdapterOptions): DatabaseAdapter {\n const usePrepared = options?.preparedStatements ?? false;\n const stmtCacheMax = options?.stmtCacheMax ?? 1000;\n\n let pool: PgPoolLike | null = null;\n let ownsPool = false;\n\n /**\n * The checked-out client of an OPEN raw transaction — session affinity for\n * the raw path. @vibeorm/migrate wraps transactional DDL in literal `BEGIN`\n * / `COMMIT` / `ROLLBACK` sent through `executeUnsafe`; without pinning,\n * `pool.query(\"BEGIN\")` sequences stay on one client only by LIFO checkout\n * luck, and any concurrent traffic silently splits the transaction across\n * connections. A pure `BEGIN` checks a client out, every raw statement until\n * the matching `COMMIT`/`ROLLBACK` rides it, and the close releases it. The\n * ORM path (`execute`) and `transaction()` stay on the pool. Concurrent raw\n * transactions on one adapter are unsupported.\n */\n let rawSession: PgPoolClientLike | null = null;\n /** Detach for {@link rawSession}'s background-error guard; runs before its release. */\n let rawSessionUnguard: (() => void) | null = null;\n\n function getPool(): PgPoolLike {\n if (pool !== null) return pool;\n\n if (options?.pool !== undefined) {\n pool = options.pool;\n ownsPool = false;\n return pool;\n }\n\n const config: Record<string, unknown> = {\n connectionString: options?.connectionString ?? process.env.DATABASE_URL,\n max: options?.max ?? 10,\n };\n if (options?.statementTimeout !== undefined) {\n config.statement_timeout = options.statementTimeout;\n }\n if (options?.connectionTimeout !== undefined) {\n config.connectionTimeoutMillis = options.connectionTimeout;\n }\n\n const PoolCtor = pg.Pool as unknown as new (poolConfig: Record<string, unknown>) => PgPoolLike;\n pool = new PoolCtor(config);\n /**\n * The IDLE half of the same hazard {@link guardBackgroundErrors} covers: a\n * pooled client that loses its socket while idle makes pg re-emit on the\n * POOL, and an unhandled `'error'` there is equally fatal. The pool already\n * evicts that client, so this listener only keeps the process alive.\n *\n * Attached ONLY to a pool this adapter constructed. A caller-supplied pool\n * (the `pool` option) has its own owner: the adapter never ends it either,\n * node-postgres documents attaching an `'error'` listener as that owner's\n * job, and registering one here that is never removed would stack up a\n * listener per adapter sharing that pool. The dangerous case — a client\n * checked out for a transaction or a raw session, which pg leaves\n * unlistened by construction — is guarded in BOTH modes.\n */\n pool.on?.(\"error\", () => {\n /* the pool has already discarded the failed client */\n });\n ownsPool = true;\n return pool;\n }\n\n /**\n * Prepared-statement names for the pool this adapter runs on (F18): the\n * registry is keyed by the POOL, so two adapters sharing one pool share one\n * name space instead of both starting at `vibeorm_0` and colliding on a\n * connection. It also bounds how many names that pool can ever put on a\n * backend (F16) no matter how many adapters come and go over it.\n */\n function statementNamesFor(params: { pool: PgPoolLike }): StatementNameCache {\n const existing = POOL_STATEMENT_NAMES.get(params.pool);\n if (existing !== undefined) return existing;\n const created = createStatementNameCache({ max: stmtCacheMax });\n POOL_STATEMENT_NAMES.set(params.pool, created);\n return created;\n }\n\n /** Build the config for one ORM-path query (statement name only when enabled). */\n function buildQueryConfig(params: { text: string; values: unknown[] }): PgQueryConfig {\n const values = serializeParams({ values: params.values });\n if (!usePrepared) return { text: params.text, values, types: EXACT_TYPES };\n // `undefined` once the pool's name budget is spent: the statement runs\n // unnamed rather than growing the server's prepared-statement retention.\n const name = statementNamesFor({ pool: getPool() }).getName({ text: params.text });\n return name === undefined\n ? { text: params.text, values, types: EXACT_TYPES }\n : { name, text: params.text, values, types: EXACT_TYPES };\n }\n\n async function runExecute(params: {\n queryable: Queryable;\n text: string;\n values: unknown[];\n }): Promise<Record<string, unknown>[]> {\n const config = buildQueryConfig({ text: params.text, values: params.values });\n const result = await runMapped(() => params.queryable.query(config));\n return result.rows;\n }\n\n async function runExecuteUnsafe(params: {\n queryable: Queryable;\n text: string;\n values?: unknown[];\n }): Promise<QueryResult> {\n const values = params.values === undefined ? undefined : serializeParamsRaw({ values: params.values });\n const result = await runMapped(() => params.queryable.query({ text: params.text, values, types: EXACT_TYPES }));\n return { rows: result.rows, affectedRows: result.rowCount ?? 0 };\n }\n\n /**\n * Adapter bound to one checked-out client that is already inside a\n * transaction. Nested `transaction()` calls open savepoints on that same\n * client, and their LIFETIMES are serialized by the savepoint gate (F14):\n * overlapping siblings share one savepoint stack, so the earlier one's\n * rollback would erase the later one's already released work.\n */\n function createClientAdapter(params: {\n client: PgPoolClientLike;\n scope: SavepointScope;\n budget: TransactionBudget;\n }): DatabaseAdapter {\n const { client, scope, budget } = params;\n\n /**\n * Hand the SERVER the remaining budget before a statement runs, when the\n * caller bought `\"cancel-running-statements\"`. `SET LOCAL` is\n * transaction-scoped, so PostgreSQL reverts it at COMMIT/ROLLBACK and the\n * connection returns to the pool with its session settings intact.\n *\n * Costs one extra round trip per statement, and only while a deadline is\n * active — the price of a deadline the database itself enforces.\n */\n async function applyEngineBudget(): Promise<void> {\n const ms = engineStatementBudgetMs({ budget });\n if (ms === null) return;\n await runMapped(() => client.query({ text: statementTimeoutStatement({ timeout: ms }) }));\n }\n\n return {\n dialect: DIALECT,\n provider: PROVIDER,\n wire: WIRE,\n budgets: BUDGETS,\n\n async execute(execParams) {\n const turn = scopeTurn({ scope });\n if (turn !== undefined) await turn;\n // After the turn, not before: waiting for a sibling savepoint spends\n // the same budget the statement is about to run against.\n budget.assertUsable({ stage: \"statement\" });\n await applyEngineBudget();\n return runExecute({ queryable: client, ...execParams });\n },\n\n async executeUnsafe(execParams) {\n const turn = scopeTurn({ scope });\n if (turn !== undefined) await turn;\n budget.assertUsable({ stage: \"statement\" });\n await applyEngineBudget();\n return runExecuteUnsafe({ queryable: client, ...execParams });\n },\n\n async transaction<T>(\n fn: (txAdapter: DatabaseAdapter) => Promise<T>,\n nestedOptions?: TransactionOptions,\n ): Promise<T> {\n refuseNestedTransactionOptions({ options: nestedOptions, provider: PROVIDER });\n budget.assertUsable({ stage: \"nested\" });\n return runNestedSavepoint({\n scope,\n execute: (statement) => runMapped(() => client.query({ text: statement })),\n // The child shares the ROOT budget by reference: a savepoint never\n // gets an independent clock or session setting.\n body: (child) => fn(createClientAdapter({ client, scope: child, budget })),\n });\n },\n\n async connect(): Promise<void> {\n // Already connected — this adapter is bound to a live client.\n },\n\n async disconnect(): Promise<void> {\n // The pool owns the connection lifecycle; releasing happens at the\n // top-level transaction boundary.\n },\n\n formatArrayParam(values: unknown[]): unknown {\n return new PgArrayParam(values);\n },\n };\n }\n\n return {\n dialect: DIALECT,\n provider: PROVIDER,\n wire: WIRE,\n budgets: BUDGETS,\n\n async execute(params) {\n return runExecute({ queryable: getPool(), ...params });\n },\n\n async executeUnsafe(params) {\n const control = classifyRawTransactionControl({ text: params.text });\n if (rawSession !== null) {\n if (control === \"close\") {\n const session = rawSession;\n const unguard = rawSessionUnguard;\n rawSession = null;\n rawSessionUnguard = null;\n try {\n return await runExecuteUnsafe({ queryable: session, ...params });\n } finally {\n unguard?.();\n session.release();\n }\n }\n // A nested raw BEGIN lands here too — postgres treats it as a warning\n // no-op on the open session, exactly like a single connection would.\n return runExecuteUnsafe({ queryable: rawSession, ...params });\n }\n if (control === \"open\") {\n const client = await runMapped(() => getPool().connect());\n const unguard = guardBackgroundErrors({ client });\n try {\n const result = await runExecuteUnsafe({ queryable: client, ...params });\n rawSession = client;\n rawSessionUnguard = unguard;\n return result;\n } catch (error) {\n unguard();\n client.release();\n throw error;\n }\n }\n return runExecuteUnsafe({ queryable: getPool(), ...params });\n },\n\n async transaction<T>(\n fn: (txAdapter: DatabaseAdapter) => Promise<T>,\n transactionOptions?: TransactionOptions,\n ): Promise<T> {\n // Refuse an unsupportable budget BEFORE checking a connection out, so a\n // bad option never costs a checkout and never leaves a transaction open.\n validateTransactionDeadline({\n options: transactionOptions,\n support: BUDGETS.transactionDeadline,\n provider: PROVIDER,\n });\n const client = await runMapped(() => getPool().connect());\n const unguard = guardBackgroundErrors({ client });\n const scope = createSavepointScope();\n // The deadline clock starts HERE: the connection is already in hand, so\n // acquisition (bounded separately by `connectionTimeout`) is not charged\n // against the caller's transaction budget.\n const budget = startTransactionBudget({ options: transactionOptions, provider: PROVIDER });\n /** Set when the session's state can no longer be asserted (see below). */\n let discard = false;\n try {\n await runMapped(() =>\n client.query({ text: beginStatement({ isolationLevel: transactionOptions?.isolationLevel }) }),\n );\n if (transactionOptions?.timeout !== undefined) {\n const text = statementTimeoutStatement({ timeout: transactionOptions.timeout });\n await runMapped(() => client.query({ text }));\n }\n const result = await runRootScope({\n scope,\n body: () => fn(createClientAdapter({ client, scope, budget })),\n });\n // An expired budget must never commit, however the callback ended.\n budget.assertUsable({ stage: \"commit\" });\n await runMapped(() => client.query({ text: \"COMMIT\" }));\n budget.close({ outcome: \"committed\" });\n return result;\n } catch (error) {\n try {\n await client.query({ text: \"ROLLBACK\" });\n budget.close({ outcome: \"rolled-back\" });\n } catch {\n // The rollback that should have cleaned up failed: this connection may\n // still be inside a transaction with `SET LOCAL` settings that were\n // never reverted, so it is DESTROYED rather than pooled, and the\n // commit state is reported as genuinely unknown.\n discard = true;\n budget.close({ outcome: \"unknown\" });\n if (budget.totalMs !== null && isTransactionDeadlineError(error)) {\n throw transactionOutcomeUnknownError({\n provider: PROVIDER,\n totalMs: budget.totalMs,\n cause: error,\n });\n }\n }\n throw error;\n } finally {\n unguard();\n client.release(discard);\n }\n },\n\n async connect(): Promise<void> {\n const client = await runMapped(() => getPool().connect());\n client.release();\n },\n\n async disconnect(): Promise<void> {\n if (rawSession !== null) {\n // An abandoned raw transaction must not leak an in-transaction client;\n // roll it back best-effort before the pool ends.\n const session = rawSession;\n const unguard = rawSessionUnguard;\n rawSession = null;\n rawSessionUnguard = null;\n try {\n await session.query({ text: \"ROLLBACK\" });\n } catch {\n /* the pool is closing; the session is going away regardless */\n }\n unguard?.();\n session.release();\n }\n if (pool !== null && ownsPool) {\n const closing = pool;\n pool = null;\n await runMapped(() => closing.end());\n }\n },\n\n formatArrayParam(values: unknown[]): unknown {\n return new PgArrayParam(values);\n },\n };\n}\n\n// ─── Re-exports (driver-error table, markers, helpers) ────────────\n\nexport { PG_SQLSTATE_ERROR_CODES, PG_SQLSTATE_REASONS, mapPgDriverError, pgErrorSqlstate } from \"./errors.ts\";\nexport { PgArrayParam, serializeParams, serializeParamsRaw } from \"./params.ts\";\nexport { createSavepointCounter, nextSavepointName } from \"./savepoints.ts\";\nexport type { SavepointCounter } from \"./savepoints.ts\";\nexport { createStatementNameCache } from \"./statement-cache.ts\";\nexport type { StatementNameCache } from \"./statement-cache.ts\";\nexport {\n ISOLATION_LEVEL_SQL,\n beginStatement,\n classifyRawTransactionControl,\n refuseNestedTransactionOptions,\n statementTimeoutStatement,\n} from \"./transaction-sql.ts\";\nexport type { RawTransactionControl } from \"./transaction-sql.ts\";\n// ─── EPIC 6: transaction budgets ──────────────────────────────────\nexport {\n DEFAULT_DEADLINE_CLOCK,\n engineStatementBudgetMs,\n isTransactionDeadlineError,\n startTransactionBudget,\n transactionClosedError,\n transactionDeadlineError,\n transactionOutcomeUnknownError,\n validateTransactionDeadline,\n} from \"./transaction-budget.ts\";\nexport type { BudgetStage, DeadlineClock, TransactionBudget, TransactionOutcome } from \"./transaction-budget.ts\";\n",
|
|
5
|
+
"/**\n * @vibeorm/adapter-pg — node-postgres (`pg`) adapter for VibeORM v2.\n *\n * Runs on Bun and Node. Owns pooling, optional named prepared statements,\n * transactions (nested → savepoints on the same client) and driver-error\n * mapping. Only `VibeError` ever escapes.\n */\n\nimport type {\n AdapterBudgetSupport,\n DatabaseAdapter,\n QueryResult,\n TransactionOptions,\n WireFidelity,\n} from \"@vibeorm/runtime\";\nimport { VibeError } from \"@vibeorm/schema\";\nimport type { Dialect, Provider } from \"@vibeorm/schema\";\nimport pg from \"pg\";\nimport { mapPgDriverError, runMapped } from \"./errors.ts\";\nimport { PgArrayParam, serializeParams, serializeParamsRaw } from \"./params.ts\";\nimport { assertPlanCacheMode, buildPoolConfig, type PlanCacheMode } from \"./pool-config.ts\";\nimport {\n createSavepointScope,\n runNestedSavepoint,\n runRootScope,\n scopeTurn,\n type SavepointScope,\n} from \"./savepoint-gate.ts\";\nimport { createStatementNameCache, type StatementNameCache } from \"./statement-cache.ts\";\nimport {\n engineStatementBudgetMs,\n isTransactionDeadlineError,\n startTransactionBudget,\n type TransactionBudget,\n transactionOutcomeUnknownError,\n validateTransactionDeadline,\n} from \"./transaction-budget.ts\";\nimport {\n beginStatement,\n classifyRawTransactionControl,\n refuseNestedTransactionOptions,\n statementTimeoutStatement,\n} from \"./transaction-sql.ts\";\n\n// ─── Minimal pg surface ───────────────────────────────────────────\n\n/** The single `pg` query form this adapter uses (config object, never overloads). */\nexport type PgQueryConfig = {\n readonly text: string;\n readonly values?: unknown[];\n readonly name?: string;\n /**\n * QUERY-LOCAL result parsers ({@link EXACT_TYPES}). node-postgres reads this\n * off the query config, so nothing outside VibeORM's own statements is\n * affected — a caller's pool keeps the driver's documented behaviour.\n */\n readonly types?: PgTypeParsers;\n};\n\n/** The `pg-types` surface a query config may carry. */\nexport type PgTypeParsers = {\n getTypeParser(oid: number, format?: \"text\" | \"binary\"): (value: string) => unknown;\n};\n\n/** The parts of a `pg` result this adapter reads. */\nexport type PgQueryResultLike = {\n readonly rows: Record<string, unknown>[];\n readonly rowCount: number | null;\n};\n\n/** The parts of a `pg` PoolClient this adapter uses. */\nexport type PgPoolClientLike = {\n query(config: PgQueryConfig): Promise<PgQueryResultLike>;\n /**\n * Hand the client back. A TRUTHY argument tells node-postgres to DESTROY it\n * instead of pooling it — used when a rollback failed and the session state\n * can no longer be asserted (EPIC 6). Optional so existing test doubles that\n * declare `release(): void` still satisfy this type.\n */\n release(destroy?: boolean): void;\n /**\n * EventEmitter surface. A real `pg.PoolClient` has it; test doubles need not,\n * so both members are optional and the adapter feature-detects them.\n */\n on?(event: \"error\", listener: (error: unknown) => void): void;\n removeListener?(event: \"error\", listener: (error: unknown) => void): void;\n};\n\n/** The parts of a `pg` Pool this adapter uses — satisfied by a real `pg.Pool`. */\nexport type PgPoolLike = {\n connect(): Promise<PgPoolClientLike>;\n query(config: PgQueryConfig): Promise<PgQueryResultLike>;\n end(): Promise<void>;\n /** EventEmitter surface; optional for the same reason as on {@link PgPoolClientLike}. */\n on?(event: \"error\", listener: (error: unknown, client: PgPoolClientLike) => void): void;\n};\n\n/** Anything that can run a query: the pool itself or a checked-out client. */\ntype Queryable = { query(config: PgQueryConfig): Promise<PgQueryResultLike> };\n\n/**\n * Keep a CHECKED-OUT client's background failures from killing the process,\n * returning the detach to run before the client goes back to the pool.\n *\n * node-postgres listens for `'error'` on a pooled client only while that client\n * sits IDLE, and removes the listener the moment the client is checked out\n * (restoring it on release). A client this adapter holds — for a transaction,\n * or as a pinned raw session — therefore has NO listener, so a backend that\n * dies mid-transaction reaches `client.emit('error', …)` with none. An\n * `'error'` event without a listener is an uncaught exception: fatal under\n * Node, which turns a database failover into a dead application process.\n *\n * The listener deliberately does nothing with the error and is NOT an error\n * channel: the in-flight query already rejects on its own with the mapped\n * `VibeError`, and the pool discards the broken client. Its only job is to keep\n * the process alive. Detaching before `release()` matters just as much — a\n * listener left attached would shadow pg's own idle listener and stop the pool\n * evicting a client that fails later, while idle.\n */\n/**\n * Prepared-statement name registries, one per POOL (F16/F18) — see\n * ./statement-cache.ts for why they are shared and bounded. Weakly held, so a\n * pool that goes out of scope takes its registry with it.\n */\nconst POOL_STATEMENT_NAMES: WeakMap<PgPoolLike, StatementNameCache> = new WeakMap();\n\nfunction guardBackgroundErrors(params: { client: PgPoolClientLike }): () => void {\n const { client } = params;\n if (typeof client.on !== \"function\") return (): void => undefined;\n const swallow = (): void => {\n /* the failing call rejects with a mapped VibeError; nothing to report here */\n };\n client.on(\"error\", swallow);\n return (): void => {\n client.removeListener?.(\"error\", swallow);\n };\n}\n\n// ─── Options ──────────────────────────────────────────────────────\n\n/**\n * Options for {@link pgAdapter}. Either point it at a connection string (the\n * adapter then owns the pool it creates) or hand it a pool you already own.\n */\nexport type PgAdapterOptions = {\n /** PostgreSQL connection URL. Defaults to `process.env.DATABASE_URL`. */\n readonly connectionString?: string;\n /**\n * An existing `pg.Pool` to use instead of creating one. The adapter never\n * ends a pool it did not create, so `disconnect()` is a no-op in this mode.\n */\n readonly pool?: PgPoolLike;\n /** Maximum pooled connections (default 10). Ignored when `pool` is given. */\n readonly max?: number;\n /**\n * Pool-wide `statement_timeout` in milliseconds. Every connection inherits\n * it; a per-transaction `timeout` overrides it for that transaction.\n */\n readonly statementTimeout?: number;\n /** Milliseconds to wait for a pool checkout before failing (pg's `connectionTimeoutMillis`). */\n readonly connectionTimeout?: number;\n /**\n * Send ORM-path queries as named prepared statements (default false).\n *\n * Measured on this repo's benchmark (postgres 16, interleaved rounds):\n * −17…−25 % on reads, −6 % on a by-key update — the server skips the\n * parse/analyze and the protocol Parse/Describe round on every repeat. The\n * gain survives {@link PgAdapterOptions.planCacheMode} `force_custom_plan`,\n * which removes the classic downside (a generic plan after ~5 executions\n * picking worse indexes for range/LIKE/skewed filters).\n *\n * Off by default because named statements live on the BACKEND connection:\n * behind a transaction-mode pooler (PgBouncer, Supavisor, Neon's pooler)\n * without prepared-statement support a client that remembers a name from an\n * earlier transaction is handed another backend later — SQLSTATE 26000,\n * 42P05, or, worst, another client's statement under the same name executed\n * without any error. Turn it on for direct connections and session pooling.\n */\n readonly preparedStatements?: boolean;\n /** Maximum entries in the prepared-statement name cache (default 1000, LRU). */\n readonly stmtCacheMax?: number;\n /**\n * PostgreSQL `plan_cache_mode` for every connection of the pool this adapter\n * builds (default `\"auto\"`, the server default — nothing is sent). Pair\n * `\"force_custom_plan\"` with {@link PgAdapterOptions.preparedStatements}: the\n * statement is still prepared once, but planned with the real parameter\n * values on every execution. Rides the `options` startup parameter, merged\n * with whatever the connection string already carries (a URL that sets\n * `plan_cache_mode` itself wins). Refused with `VIBE_CONFIG` together with\n * a caller-supplied `pool` — the adapter never reconfigures a pool it does\n * not own; set it on that pool's own `options` instead.\n */\n readonly planCacheMode?: PlanCacheMode;\n};\n\n// ─── Adapter ──────────────────────────────────────────────────────\n\nconst DIALECT: Dialect = \"postgres\";\nconst PROVIDER: Provider = \"postgresql\";\n\n/**\n * node-postgres wire (board #30, live-verified on both execute paths):\n * timestamptz → `Date`, json/jsonb → parsed values, int4/float8 → numbers.\n * (int8 stays a string — BigInt decoding is real work and never pruned.)\n */\nconst WIRE: WireFidelity = Object.freeze({ dateTime: \"native\", json: \"parsed\", numbers: \"native\" } as const);\n\n/**\n * Timing budgets this adapter genuinely enforces (EPIC 6).\n *\n * - `acquireTimeout: true` — {@link PgAdapterOptions.connectionTimeout} maps to\n * node-postgres' `connectionTimeoutMillis`, which bounds the checkout wait\n * and rejects when the pool stays exhausted. The capability is declared here;\n * it is OFF until the option is set (pg's own default is \"wait forever\").\n * - `statementTimeout: \"all-statements\"` — PostgreSQL's `statement_timeout`\n * cancels ANY over-long statement, writes included (SQLSTATE 57014).\n * - `transactionDeadline: \"cancel-running-statements\"` — the strongest level:\n * the remaining budget is handed to the SERVER before each statement, so the\n * database itself stops work that would outlive the deadline. Nothing here is\n * a `Promise.race` over a statement that keeps running.\n */\nconst BUDGETS: AdapterBudgetSupport = Object.freeze({\n acquireTimeout: true,\n statementTimeout: \"all-statements\",\n transactionDeadline: \"cancel-running-statements\",\n} as const);\n\n// ─── Exact numeric arrays (F12) ───────────────────────────────\n\n/** `_numeric`: the array form of `numeric`/`DECIMAL`. */\nconst NUMERIC_ARRAY_OID = 1231;\n/** `_text`: its parser splits a postgres array literal into RAW element strings. */\nconst TEXT_ARRAY_OID = 1009;\n/** `timestamp without time zone` and `date` — the zone-less types — and their array forms. */\nconst TIMESTAMP_OID = 1114;\nconst TIMESTAMP_ARRAY_OID = 1115;\nconst DATE_OID = 1082;\nconst DATE_ARRAY_OID = 1182;\n\n/**\n * `pg.types.getTypeParser` behind a plain numeric-OID signature. `@types/pg`\n * narrows the two-argument overload to its own `TypeId` union of well-known\n * OIDs, which does not include every array OID this function must look up.\n */\nconst driverTypeParser: (oid: number, format?: \"text\" | \"binary\") => (value: string) => unknown =\n pg.types.getTypeParser as unknown as (oid: number, format?: \"text\" | \"binary\") => (value: string) => unknown;\n\n/** postgres' text output for the zone-less types: `2026-03-08 19:38:08.538` or `2026-03-08`. */\nconst ZONE_LESS_TEXT = /^\\d{4}-\\d{2}-\\d{2}(?: \\d{2}:\\d{2}:\\d{2}(?:\\.\\d{1,6})?)?$/;\n\n/**\n * Zone-less date/timestamp text read as **UTC** — the same rule as the codec\n * table's `parseDateTimeText` (@vibeorm/sql), applied at the driver level\n * because node-postgres hands these columns to the ORM as `Date` objects\n * already (`wire.dateTime: \"native\"` prunes the codec decode). pg-types reads\n * them as host-LOCAL time, so one stored row decoded to a different instant\n * on every host TZ, and to a different instant than bun:sql, PGlite's ORM\n * path and Prisma (all UTC) return. `infinity`, BC dates and anything else\n * outside the plain form keep the driver's own parser.\n */\nfunction parseZoneLessAsUtc(value: string): unknown {\n if (ZONE_LESS_TEXT.test(value)) {\n const iso = value.includes(\" \") ? `${value.replace(\" \", \"T\").replace(/(\\.\\d{3})\\d+$/, \"$1\")}Z` : value;\n const parsed = new Date(iso);\n if (!Number.isNaN(parsed.getTime())) return parsed;\n }\n return driverTypeParser(TIMESTAMP_OID, \"text\")(value);\n}\n\n/** The array forms: the driver's `_text` parser splits the literal, elements go through {@link parseZoneLessAsUtc}. */\nfunction parseZoneLessArrayAsUtc(value: string): unknown {\n const mapElements = (element: unknown): unknown => {\n if (element === null) return null;\n if (Array.isArray(element)) return element.map(mapElements);\n return parseZoneLessAsUtc(String(element));\n };\n return mapElements(driverTypeParser(TEXT_ARRAY_OID, \"text\")(value));\n}\n\n/**\n * Query-local type parsers that keep `numeric[]` EXACT and read zone-less\n * timestamps as UTC.\n *\n * pg-types decodes `_numeric` with `parseFloat` per element, so\n * `9007199254740993` comes back as `9007199254740992` and `12.340` as the\n * number `12.34` — while the SCALAR `numeric` correctly stays a string and\n * VibeORM's Decimal contract is \"string in, string out\" on every dialect. The\n * array is the anomaly, and the loss happens inside the driver, before any\n * codec can see the value.\n *\n * The fix reuses the driver's OWN array-literal parser (`_text`), so array\n * syntax, quoting and NULL elements stay node-postgres' business; only the\n * per-element float conversion is dropped. It is attached PER QUERY (F12's\n * acceptance criterion), never by mutating `pg.types` globally: an injected\n * pool is shared property, and a global override would silently change every\n * other consumer in the process. The zone-less parsers ride the same hook for\n * the same reason.\n */\nconst EXACT_TYPES: PgTypeParsers = {\n getTypeParser(oid: number, format?: \"text\" | \"binary\"): (value: string) => unknown {\n if (format === undefined || format === \"text\") {\n if (oid === NUMERIC_ARRAY_OID) return driverTypeParser(TEXT_ARRAY_OID, \"text\");\n if (oid === TIMESTAMP_OID || oid === DATE_OID) return parseZoneLessAsUtc;\n if (oid === TIMESTAMP_ARRAY_OID || oid === DATE_ARRAY_OID) return parseZoneLessArrayAsUtc;\n }\n return driverTypeParser(oid, format);\n },\n};\n\n/**\n * Create a VibeORM adapter backed by node-postgres.\n *\n * @example\n * ```ts\n * const adapter = pgAdapter({ connectionString: process.env.DATABASE_URL, max: 20 });\n * await adapter.connect();\n * const rows = await adapter.execute({ text: 'SELECT * FROM \"User\" WHERE \"id\" = $1', values: [1] });\n * ```\n *\n * @example Reusing a pool you already own\n * ```ts\n * import pg from \"pg\";\n * const pool = new pg.Pool({ connectionString });\n * const adapter = pgAdapter({ pool });\n * ```\n */\nexport function pgAdapter(options?: PgAdapterOptions): DatabaseAdapter {\n const usePrepared = options?.preparedStatements ?? false;\n const stmtCacheMax = options?.stmtCacheMax ?? 1000;\n const planCacheMode = options?.planCacheMode === undefined ? \"auto\" : assertPlanCacheMode({ value: options.planCacheMode });\n if (planCacheMode !== \"auto\" && options?.pool !== undefined) {\n throw new VibeError({\n code: \"VIBE_CONFIG\",\n message:\n \"planCacheMode configures the pool the adapter builds — it cannot be applied to a caller-supplied `pool`; \" +\n \"set `options: \\\"-c plan_cache_mode=…\\\"` on that pool instead\",\n meta: { planCacheMode },\n });\n }\n\n let pool: PgPoolLike | null = null;\n let ownsPool = false;\n\n /**\n * The checked-out client of an OPEN raw transaction — session affinity for\n * the raw path. @vibeorm/migrate wraps transactional DDL in literal `BEGIN`\n * / `COMMIT` / `ROLLBACK` sent through `executeUnsafe`; without pinning,\n * `pool.query(\"BEGIN\")` sequences stay on one client only by LIFO checkout\n * luck, and any concurrent traffic silently splits the transaction across\n * connections. A pure `BEGIN` checks a client out, every raw statement until\n * the matching `COMMIT`/`ROLLBACK` rides it, and the close releases it. The\n * ORM path (`execute`) and `transaction()` stay on the pool. Concurrent raw\n * transactions on one adapter are unsupported.\n */\n let rawSession: PgPoolClientLike | null = null;\n /** Detach for {@link rawSession}'s background-error guard; runs before its release. */\n let rawSessionUnguard: (() => void) | null = null;\n\n function getPool(): PgPoolLike {\n if (pool !== null) return pool;\n\n if (options?.pool !== undefined) {\n pool = options.pool;\n ownsPool = false;\n return pool;\n }\n\n const config = buildPoolConfig({\n connectionString: options?.connectionString ?? process.env.DATABASE_URL,\n max: options?.max ?? 10,\n ...(options?.statementTimeout === undefined ? {} : { statementTimeout: options.statementTimeout }),\n ...(options?.connectionTimeout === undefined ? {} : { connectionTimeout: options.connectionTimeout }),\n planCacheMode,\n });\n\n const PoolCtor = pg.Pool as unknown as new (poolConfig: Record<string, unknown>) => PgPoolLike;\n pool = new PoolCtor(config);\n /**\n * The IDLE half of the same hazard {@link guardBackgroundErrors} covers: a\n * pooled client that loses its socket while idle makes pg re-emit on the\n * POOL, and an unhandled `'error'` there is equally fatal. The pool already\n * evicts that client, so this listener only keeps the process alive.\n *\n * Attached ONLY to a pool this adapter constructed. A caller-supplied pool\n * (the `pool` option) has its own owner: the adapter never ends it either,\n * node-postgres documents attaching an `'error'` listener as that owner's\n * job, and registering one here that is never removed would stack up a\n * listener per adapter sharing that pool. The dangerous case — a client\n * checked out for a transaction or a raw session, which pg leaves\n * unlistened by construction — is guarded in BOTH modes.\n */\n pool.on?.(\"error\", () => {\n /* the pool has already discarded the failed client */\n });\n ownsPool = true;\n return pool;\n }\n\n /**\n * Prepared-statement names for the pool this adapter runs on (F18): the\n * registry is keyed by the POOL, so two adapters sharing one pool share one\n * name space instead of both starting at `vibeorm_0` and colliding on a\n * connection. It also bounds how many names that pool can ever put on a\n * backend (F16) no matter how many adapters come and go over it.\n */\n function statementNamesFor(params: { pool: PgPoolLike }): StatementNameCache {\n const existing = POOL_STATEMENT_NAMES.get(params.pool);\n if (existing !== undefined) return existing;\n const created = createStatementNameCache({ max: stmtCacheMax });\n POOL_STATEMENT_NAMES.set(params.pool, created);\n return created;\n }\n\n /** Build the config for one ORM-path query (statement name only when enabled). */\n function buildQueryConfig(params: { text: string; values: unknown[] }): PgQueryConfig {\n const values = serializeParams({ values: params.values });\n if (!usePrepared) return { text: params.text, values, types: EXACT_TYPES };\n // `undefined` once the pool's name budget is spent: the statement runs\n // unnamed rather than growing the server's prepared-statement retention.\n const name = statementNamesFor({ pool: getPool() }).getName({ text: params.text });\n return name === undefined\n ? { text: params.text, values, types: EXACT_TYPES }\n : { name, text: params.text, values, types: EXACT_TYPES };\n }\n\n async function runExecute(params: {\n queryable: Queryable;\n text: string;\n values: unknown[];\n }): Promise<Record<string, unknown>[]> {\n const config = buildQueryConfig({ text: params.text, values: params.values });\n const result = await runMapped(() => params.queryable.query(config));\n return result.rows;\n }\n\n async function runExecuteUnsafe(params: {\n queryable: Queryable;\n text: string;\n values?: unknown[];\n }): Promise<QueryResult> {\n const values = params.values === undefined ? undefined : serializeParamsRaw({ values: params.values });\n const result = await runMapped(() => params.queryable.query({ text: params.text, values, types: EXACT_TYPES }));\n return { rows: result.rows, affectedRows: result.rowCount ?? 0 };\n }\n\n /**\n * Adapter bound to one checked-out client that is already inside a\n * transaction. Nested `transaction()` calls open savepoints on that same\n * client, and their LIFETIMES are serialized by the savepoint gate (F14):\n * overlapping siblings share one savepoint stack, so the earlier one's\n * rollback would erase the later one's already released work.\n */\n function createClientAdapter(params: {\n client: PgPoolClientLike;\n scope: SavepointScope;\n budget: TransactionBudget;\n }): DatabaseAdapter {\n const { client, scope, budget } = params;\n\n /**\n * Hand the SERVER the remaining budget before a statement runs, when the\n * caller bought `\"cancel-running-statements\"`. `SET LOCAL` is\n * transaction-scoped, so PostgreSQL reverts it at COMMIT/ROLLBACK and the\n * connection returns to the pool with its session settings intact.\n *\n * Costs one extra round trip per statement, and only while a deadline is\n * active — the price of a deadline the database itself enforces.\n */\n async function applyEngineBudget(): Promise<void> {\n const ms = engineStatementBudgetMs({ budget });\n if (ms === null) return;\n await runMapped(() => client.query({ text: statementTimeoutStatement({ timeout: ms }) }));\n }\n\n return {\n dialect: DIALECT,\n provider: PROVIDER,\n wire: WIRE,\n budgets: BUDGETS,\n\n async execute(execParams) {\n const turn = scopeTurn({ scope });\n if (turn !== undefined) await turn;\n // After the turn, not before: waiting for a sibling savepoint spends\n // the same budget the statement is about to run against.\n budget.assertUsable({ stage: \"statement\" });\n await applyEngineBudget();\n return runExecute({ queryable: client, ...execParams });\n },\n\n async executeUnsafe(execParams) {\n const turn = scopeTurn({ scope });\n if (turn !== undefined) await turn;\n budget.assertUsable({ stage: \"statement\" });\n await applyEngineBudget();\n return runExecuteUnsafe({ queryable: client, ...execParams });\n },\n\n async transaction<T>(\n fn: (txAdapter: DatabaseAdapter) => Promise<T>,\n nestedOptions?: TransactionOptions,\n ): Promise<T> {\n refuseNestedTransactionOptions({ options: nestedOptions, provider: PROVIDER });\n budget.assertUsable({ stage: \"nested\" });\n return runNestedSavepoint({\n scope,\n execute: (statement) => runMapped(() => client.query({ text: statement })),\n // The child shares the ROOT budget by reference: a savepoint never\n // gets an independent clock or session setting.\n body: (child) => fn(createClientAdapter({ client, scope: child, budget })),\n });\n },\n\n async connect(): Promise<void> {\n // Already connected — this adapter is bound to a live client.\n },\n\n async disconnect(): Promise<void> {\n // The pool owns the connection lifecycle; releasing happens at the\n // top-level transaction boundary.\n },\n\n formatArrayParam(values: unknown[]): unknown {\n return new PgArrayParam(values);\n },\n };\n }\n\n return {\n dialect: DIALECT,\n provider: PROVIDER,\n wire: WIRE,\n budgets: BUDGETS,\n\n async execute(params) {\n return runExecute({ queryable: getPool(), ...params });\n },\n\n async executeUnsafe(params) {\n const control = classifyRawTransactionControl({ text: params.text });\n if (rawSession !== null) {\n if (control === \"close\") {\n const session = rawSession;\n const unguard = rawSessionUnguard;\n rawSession = null;\n rawSessionUnguard = null;\n try {\n return await runExecuteUnsafe({ queryable: session, ...params });\n } finally {\n unguard?.();\n session.release();\n }\n }\n // A nested raw BEGIN lands here too — postgres treats it as a warning\n // no-op on the open session, exactly like a single connection would.\n return runExecuteUnsafe({ queryable: rawSession, ...params });\n }\n if (control === \"open\") {\n const client = await runMapped(() => getPool().connect());\n const unguard = guardBackgroundErrors({ client });\n try {\n const result = await runExecuteUnsafe({ queryable: client, ...params });\n rawSession = client;\n rawSessionUnguard = unguard;\n return result;\n } catch (error) {\n unguard();\n client.release();\n throw error;\n }\n }\n return runExecuteUnsafe({ queryable: getPool(), ...params });\n },\n\n async transaction<T>(\n fn: (txAdapter: DatabaseAdapter) => Promise<T>,\n transactionOptions?: TransactionOptions,\n ): Promise<T> {\n // Refuse an unsupportable budget BEFORE checking a connection out, so a\n // bad option never costs a checkout and never leaves a transaction open.\n validateTransactionDeadline({\n options: transactionOptions,\n support: BUDGETS.transactionDeadline,\n provider: PROVIDER,\n });\n const client = await runMapped(() => getPool().connect());\n const unguard = guardBackgroundErrors({ client });\n const scope = createSavepointScope();\n // The deadline clock starts HERE: the connection is already in hand, so\n // acquisition (bounded separately by `connectionTimeout`) is not charged\n // against the caller's transaction budget.\n const budget = startTransactionBudget({ options: transactionOptions, provider: PROVIDER });\n /** Set when the session's state can no longer be asserted (see below). */\n let discard = false;\n try {\n await runMapped(() =>\n client.query({ text: beginStatement({ isolationLevel: transactionOptions?.isolationLevel }) }),\n );\n if (transactionOptions?.timeout !== undefined) {\n const text = statementTimeoutStatement({ timeout: transactionOptions.timeout });\n await runMapped(() => client.query({ text }));\n }\n const result = await runRootScope({\n scope,\n body: () => fn(createClientAdapter({ client, scope, budget })),\n });\n // An expired budget must never commit, however the callback ended.\n budget.assertUsable({ stage: \"commit\" });\n await runMapped(() => client.query({ text: \"COMMIT\" }));\n budget.close({ outcome: \"committed\" });\n return result;\n } catch (error) {\n try {\n await client.query({ text: \"ROLLBACK\" });\n budget.close({ outcome: \"rolled-back\" });\n } catch {\n // The rollback that should have cleaned up failed: this connection may\n // still be inside a transaction with `SET LOCAL` settings that were\n // never reverted, so it is DESTROYED rather than pooled, and the\n // commit state is reported as genuinely unknown.\n discard = true;\n budget.close({ outcome: \"unknown\" });\n if (budget.totalMs !== null && isTransactionDeadlineError(error)) {\n throw transactionOutcomeUnknownError({\n provider: PROVIDER,\n totalMs: budget.totalMs,\n cause: error,\n });\n }\n }\n throw error;\n } finally {\n unguard();\n client.release(discard);\n }\n },\n\n async connect(): Promise<void> {\n const client = await runMapped(() => getPool().connect());\n client.release();\n },\n\n async disconnect(): Promise<void> {\n if (rawSession !== null) {\n // An abandoned raw transaction must not leak an in-transaction client;\n // roll it back best-effort before the pool ends.\n const session = rawSession;\n const unguard = rawSessionUnguard;\n rawSession = null;\n rawSessionUnguard = null;\n try {\n await session.query({ text: \"ROLLBACK\" });\n } catch {\n /* the pool is closing; the session is going away regardless */\n }\n unguard?.();\n session.release();\n }\n if (pool !== null && ownsPool) {\n const closing = pool;\n pool = null;\n await runMapped(() => closing.end());\n }\n },\n\n formatArrayParam(values: unknown[]): unknown {\n return new PgArrayParam(values);\n },\n };\n}\n\n// ─── Re-exports (driver-error table, markers, helpers) ────────────\n\nexport { PG_SQLSTATE_ERROR_CODES, PG_SQLSTATE_REASONS, mapPgDriverError, pgErrorSqlstate } from \"./errors.ts\";\nexport { PgArrayParam, serializeParams, serializeParamsRaw } from \"./params.ts\";\nexport { createSavepointCounter, nextSavepointName } from \"./savepoints.ts\";\nexport type { SavepointCounter } from \"./savepoints.ts\";\nexport { createStatementNameCache } from \"./statement-cache.ts\";\nexport type { StatementNameCache } from \"./statement-cache.ts\";\nexport { buildPoolConfig, placePlanCacheMode } from \"./pool-config.ts\";\nexport type { PlanCacheMode, StartupOptionsPlacement } from \"./pool-config.ts\";\nexport {\n ISOLATION_LEVEL_SQL,\n beginStatement,\n classifyRawTransactionControl,\n refuseNestedTransactionOptions,\n statementTimeoutStatement,\n} from \"./transaction-sql.ts\";\nexport type { RawTransactionControl } from \"./transaction-sql.ts\";\n// ─── EPIC 6: transaction budgets ──────────────────────────────────\nexport {\n DEFAULT_DEADLINE_CLOCK,\n engineStatementBudgetMs,\n isTransactionDeadlineError,\n startTransactionBudget,\n transactionClosedError,\n transactionDeadlineError,\n transactionOutcomeUnknownError,\n validateTransactionDeadline,\n} from \"./transaction-budget.ts\";\nexport type { BudgetStage, DeadlineClock, TransactionBudget, TransactionOutcome } from \"./transaction-budget.ts\";\n",
|
|
6
6
|
"/**\n * Driver-error mapping for node-postgres (`pg`).\n *\n * Constitution rule 5: a raw driver error never escapes an adapter. Every\n * failure becomes a `VibeError` with a stable code, the driver error attached\n * as `cause`, and the SQLSTATE (plus constraint/table/column when the server\n * reported them) in `meta`.\n */\n\nimport { VibeError, type VibeErrorCode } from \"@vibeorm/schema\";\n\n// ─── SQLSTATE table ───────────────────────────────────────────────\n\n/**\n * PostgreSQL SQLSTATE → stable VibeError code. Frozen and mirrored by tests;\n * anything absent from this table maps to `VIBE_ADAPTER`.\n */\nexport const PG_SQLSTATE_ERROR_CODES: Readonly<Record<string, VibeErrorCode>> = Object.freeze({\n /** unique_violation */\n \"23505\": \"VIBE_UNIQUE_VIOLATION\",\n /** foreign_key_violation */\n \"23503\": \"VIBE_FK_VIOLATION\",\n /** not_null_violation */\n \"23502\": \"VIBE_VALIDATION\",\n /** check_violation */\n \"23514\": \"VIBE_VALIDATION\",\n /**\n * invalid_text_representation — a value the server cannot parse into the\n * column's type (out-of-range enum value, `\"abc\"` for an integer). Bad input,\n * so the same code sqlite reports for its enum CHECK violation.\n */\n \"22P02\": \"VIBE_VALIDATION\",\n /**\n * insufficient_privilege — a denied table/column privilege, and the SQLSTATE a\n * native row-level-security policy raises when its WITH CHECK refuses a write.\n * Callers identify it by this code, never by message text.\n */\n \"42501\": \"VIBE_PERMISSION_DENIED\",\n /** serialization_failure */\n \"40001\": \"VIBE_TRANSACTION\",\n /** deadlock_detected */\n \"40P01\": \"VIBE_TRANSACTION\",\n /** query_canceled — statement_timeout fired */\n \"57014\": \"VIBE_TRANSACTION\",\n});\n\n/** SQLSTATEs that additionally explain themselves through `meta.reason`. */\nexport const PG_SQLSTATE_REASONS: Readonly<Record<string, string>> = Object.freeze({\n \"57014\": \"timeout\",\n});\n\n// ─── Reading driver errors ────────────────────────────────────────\n\n/** A PostgreSQL SQLSTATE is exactly five digits/uppercase letters (e.g. `40P01`). */\nconst SQLSTATE_PATTERN = /^[0-9A-Z]{5}$/;\n\nfunction readStringField(params: { source: Record<string, unknown>; key: string }): string | undefined {\n const value = params.source[params.key];\n return typeof value === \"string\" && value.length > 0 ? value : undefined;\n}\n\n/**\n * Extract the SQLSTATE from a pg driver error. `pg` puts the server's SQLSTATE\n * on `error.code`; client-side failures (`ECONNREFUSED`, `ETIMEDOUT`, …) use\n * the same field for a non-SQLSTATE string, which this rejects.\n */\nexport function pgErrorSqlstate(params: { error: unknown }): string | undefined {\n const { error } = params;\n if (error === null || typeof error !== \"object\") return undefined;\n const code = readStringField({ source: error as Record<string, unknown>, key: \"code\" });\n return code !== undefined && SQLSTATE_PATTERN.test(code) ? code : undefined;\n}\n\n// ─── Mapping ──────────────────────────────────────────────────────\n\n/**\n * Map any error thrown by `pg` onto a `VibeError`.\n *\n * A `VibeError` passes through unchanged, so an error raised deeper inside the\n * ORM (or by a nested adapter call) is never re-wrapped.\n */\nexport function mapPgDriverError(params: { error: unknown }): VibeError {\n const { error } = params;\n if (error instanceof VibeError) return error;\n\n const source: Record<string, unknown> =\n error !== null && typeof error === \"object\" ? (error as Record<string, unknown>) : {};\n const sqlstate = pgErrorSqlstate({ error });\n const code: VibeErrorCode =\n (sqlstate === undefined ? undefined : PG_SQLSTATE_ERROR_CODES[sqlstate]) ?? \"VIBE_ADAPTER\";\n\n const meta: Record<string, unknown> = {};\n if (sqlstate !== undefined) meta.sqlstate = sqlstate;\n const constraint = readStringField({ source, key: \"constraint\" });\n if (constraint !== undefined) meta.constraint = constraint;\n const table = readStringField({ source, key: \"table\" });\n if (table !== undefined) meta.table = table;\n const column = readStringField({ source, key: \"column\" });\n if (column !== undefined) meta.column = column;\n const reason = sqlstate === undefined ? undefined : PG_SQLSTATE_REASONS[sqlstate];\n if (reason !== undefined) meta.reason = reason;\n\n const message =\n readStringField({ source, key: \"message\" }) ??\n (typeof error === \"string\" ? error : \"unknown postgres driver error\");\n\n return new VibeError({ code, message, meta, cause: error });\n}\n\n/** Run a driver call, mapping anything it throws onto a `VibeError`. */\nexport async function runMapped<T>(fn: () => Promise<T>): Promise<T> {\n try {\n return await fn();\n } catch (error) {\n throw mapPgDriverError({ error });\n }\n}\n",
|
|
7
|
-
"/**\n * Parameter serialization for node-postgres — the ORM path and the raw path\n * differ, deliberately (see LEARNINGS.md).\n *\n * `pg` natively turns a JS array into a PostgreSQL array literal (`{1,2,3}`),\n * which is exactly what a raw query wants. The ORM hot path cannot tell whether\n * a JS array is destined for a `jsonb` column or for a scalar-list / `= ANY($n)`\n * parameter, so it JSON-stringifies arrays and objects — scalar-list values are\n * tagged with `PgArrayParam` by `formatArrayParam` and unwrapped here, reaching\n * the driver as the untouched JS array.\n */\n\n// ─── Scalar-list marker ───────────────────────────────────────────\n\n/**\n * Opaque token returned by `pgAdapter().formatArrayParam` marking a value as a\n * PostgreSQL array parameter (rather than a JSON value that happens to be an\n * array). The wrapped array reaches `pg` as-is; the driver serializes it.\n */\nexport class PgArrayParam {\n /** The untouched scalar-list values. */\n readonly values: unknown[];\n\n constructor(values: unknown[]) {\n this.values = values;\n }\n}\n\n// ─── Serialization ────────────────────────────────────────────────\n\n/** True for values `pg` must receive verbatim (
|
|
7
|
+
"/**\n * Parameter serialization for node-postgres — the ORM path and the raw path\n * differ, deliberately (see LEARNINGS.md).\n *\n * `pg` natively turns a JS array into a PostgreSQL array literal (`{1,2,3}`),\n * which is exactly what a raw query wants. The ORM hot path cannot tell whether\n * a JS array is destined for a `jsonb` column or for a scalar-list / `= ANY($n)`\n * parameter, so it JSON-stringifies arrays and objects — scalar-list values are\n * tagged with `PgArrayParam` by `formatArrayParam` and unwrapped here, reaching\n * the driver as the untouched JS array.\n */\n\n// ─── Scalar-list marker ───────────────────────────────────────────\n\n/**\n * Opaque token returned by `pgAdapter().formatArrayParam` marking a value as a\n * PostgreSQL array parameter (rather than a JSON value that happens to be an\n * array). The wrapped array reaches `pg` as-is; the driver serializes it.\n */\nexport class PgArrayParam {\n /** The untouched scalar-list values. */\n readonly values: unknown[];\n\n constructor(values: unknown[]) {\n this.values = values;\n }\n}\n\n// ─── Serialization ────────────────────────────────────────────────\n\n/**\n * `Date` → UTC ISO-8601 text (`2026-03-08T19:38:08.538Z`).\n *\n * node-postgres' own serializer writes the host's LOCAL wall-clock plus its\n * offset (`2026-03-08T20:38:08.538+01:00` under Europe/Oslo). A `timestamptz`\n * parameter keeps the instant either way, but a `timestamp without time zone`\n * or `date` parameter DISCARDS the offset (\"PostgreSQL will silently ignore\n * any time zone indication\"), so the stored wall-clock would depend on the\n * host's TZ — and differ from what bun:sql, PGlite and Prisma store for the\n * same `Date` (all UTC). The UTC spelling stores one value on every host and\n * matches the UTC read side (`EXACT_TYPES` in index.ts). Dates the ISO form\n * cannot spell (invalid, BC, beyond year 9999) keep the driver's serializer.\n */\nexport function serializeDate(params: { value: Date }): unknown {\n const { value } = params;\n const time = value.getTime();\n if (Number.isNaN(time)) return value;\n const year = value.getUTCFullYear();\n if (year < 1 || year > 9999) return value;\n return value.toISOString();\n}\n\n/** True for values `pg` must receive verbatim (binary, primitives, null). */\nfunction isDriverNativeValue(value: unknown): boolean {\n return (\n value === null ||\n value === undefined ||\n typeof value !== \"object\" ||\n ArrayBuffer.isView(value) ||\n value instanceof ArrayBuffer\n );\n}\n\n/**\n * ORM hot path: unwrap scalar-list markers, spell `Date` as UTC, JSON-stringify\n * plain objects **and** plain arrays (they can only be `json`/`jsonb` values\n * at this point).\n */\nexport function serializeParams(params: { values: readonly unknown[] }): unknown[] {\n return params.values.map((value) => {\n if (value instanceof PgArrayParam) return serializeList({ values: value.values });\n if (value instanceof Date) return serializeDate({ value });\n if (isDriverNativeValue(value)) return value;\n return JSON.stringify(value);\n });\n}\n\n/**\n * A scalar-list parameter: `Date` elements spelled as UTC (the driver's array\n * serializer would otherwise write them in local time); a list without dates\n * reaches `pg` as the untouched array.\n */\nfunction serializeList(params: { values: unknown[] }): unknown[] {\n const { values } = params;\n return values.some((value) => value instanceof Date)\n ? values.map((value) => (value instanceof Date ? serializeDate({ value }) : value))\n : values;\n}\n\n/**\n * Raw path (`$queryRaw*` / `$executeRaw*` / DDL): identical to\n * {@link serializeParams} except that plain JS arrays pass straight through, so\n * `db.$queryRawUnsafe('… WHERE \"id\" = ANY($1)', [1, 2, 3])` matches rows.\n *\n * Trade-off (v1 lesson): binding a JS array **into** a `jsonb` column from a raw\n * query means calling `JSON.stringify` yourself.\n */\nexport function serializeParamsRaw(params: { values: readonly unknown[] }): unknown[] {\n return params.values.map((value) => {\n if (value instanceof PgArrayParam) return serializeList({ values: value.values });\n if (Array.isArray(value)) return serializeList({ values: value });\n if (value instanceof Date) return serializeDate({ value });\n if (isDriverNativeValue(value)) return value;\n return JSON.stringify(value);\n });\n}\n",
|
|
8
|
+
"/**\n * Pool configuration for the pool {@link pgAdapter} builds itself.\n *\n * Per-connection PostgreSQL settings travel in the `options` startup\n * parameter (`-c key=value` pairs the server parses at connect time), so every\n * pooled connection inherits them without a per-query round trip. Two\n * node-postgres facts shape the merge below, both read from the installed\n * driver (pg 8.23):\n *\n * - `ConnectionParameters` parses `connectionString` OVER the explicit config\n * (`Object.assign({}, config, parse(connectionString))`), so a URL that\n * already carries `?options=…` silently wins over `config.options`.\n * - `pg-connection-string` turns EVERY query parameter into a config key, so\n * `?options=-c search_path=x` arrives as `config.options`.\n *\n * Hence: with no `options` in the URL the setting rides `config.options`\n * (nothing to collide with); with one, the URL's value is EXTENDED in place\n * and a `plan_cache_mode` the URL already sets is left alone — the caller's\n * connection string is the more explicit statement of intent.\n */\n\nimport { VibeError } from \"@vibeorm/schema\";\n\n/**\n * PostgreSQL plan-cache behaviour for named prepared statements\n * (`preparedStatements: true`). `auto` (the server default) lets PostgreSQL\n * switch to a generic plan after ~5 executions — cheaper per call, but it can\n * pick worse indexes for range/LIKE/skewed filters; `force_custom_plan`\n * replans with the real parameter values every time and keeps almost the whole\n * prepared-statement gain (the saving is the parse/analyze and the protocol\n * round, not the plan — measured −17…−25 % on reads either way, see\n * `.ai/plans/26.09.08 - performance-join-strategy.md` §1.3).\n */\nexport type PlanCacheMode = \"auto\" | \"force_custom_plan\" | \"force_generic_plan\";\n\nconst PLAN_CACHE_MODES: readonly PlanCacheMode[] = [\"auto\", \"force_custom_plan\", \"force_generic_plan\"];\n\nfunction isPlanCacheMode(value: unknown): value is PlanCacheMode {\n return typeof value === \"string\" && (PLAN_CACHE_MODES as readonly string[]).includes(value);\n}\n\n/** Validate a `planCacheMode` option value; the string ends up inside a startup parameter verbatim. */\nexport function assertPlanCacheMode(params: { value: unknown }): PlanCacheMode {\n const { value } = params;\n if (isPlanCacheMode(value)) return value;\n throw new VibeError({\n code: \"VIBE_CONFIG\",\n message: `planCacheMode must be one of ${PLAN_CACHE_MODES.map((mode) => `\"${mode}\"`).join(\", \")}, received ${JSON.stringify(value)}`,\n meta: { planCacheMode: value, allowed: PLAN_CACHE_MODES },\n });\n}\n\n/** The `options` startup parameter and (possibly rewritten) connection string carrying it. */\nexport type StartupOptionsPlacement = {\n readonly connectionString: string | undefined;\n /** `undefined` when nothing needs setting, or the URL already sets the parameter itself. */\n readonly options: string | undefined;\n};\n\n/**\n * Place `plan_cache_mode` into the startup options, honouring what the URL\n * already carries (see the module header for the precedence rule).\n */\nexport function placePlanCacheMode(params: {\n connectionString: string | undefined;\n planCacheMode: PlanCacheMode;\n}): StartupOptionsPlacement {\n const { connectionString, planCacheMode } = params;\n if (planCacheMode === \"auto\") return { connectionString, options: undefined };\n const setting = `-c plan_cache_mode=${planCacheMode}`;\n\n if (connectionString === undefined || !URL.canParse(connectionString)) {\n return { connectionString, options: setting };\n }\n const url = new URL(connectionString);\n const existing = url.searchParams.get(\"options\");\n if (existing === null) {\n // Absent from the URL → the parsed URL carries no `options` key and\n // `config.options` survives the driver's merge untouched.\n return { connectionString, options: setting };\n }\n if (/(^|\\s)-c\\s*plan_cache_mode=/.test(existing)) {\n // The URL is the more explicit statement of intent; leave it alone.\n return { connectionString, options: undefined };\n }\n const merged = existing.trim().length === 0 ? setting : `${existing.trim()} ${setting}`;\n // The URL's own value wins in the driver, so the merge has to live THERE.\n url.searchParams.set(\"options\", merged);\n return { connectionString: url.toString(), options: merged };\n}\n\n/** Build the `pg.Pool` constructor config for an adapter-owned pool. */\nexport function buildPoolConfig(params: {\n connectionString: string | undefined;\n max: number;\n statementTimeout?: number;\n connectionTimeout?: number;\n planCacheMode?: PlanCacheMode;\n}): Record<string, unknown> {\n const { max, statementTimeout, connectionTimeout } = params;\n const placement = placePlanCacheMode({\n connectionString: params.connectionString,\n planCacheMode: params.planCacheMode ?? \"auto\",\n });\n const config: Record<string, unknown> = { connectionString: placement.connectionString, max };\n if (statementTimeout !== undefined) config.statement_timeout = statementTimeout;\n if (connectionTimeout !== undefined) config.connectionTimeoutMillis = connectionTimeout;\n if (placement.options !== undefined) config.options = placement.options;\n return config;\n}\n",
|
|
8
9
|
"/**\n * Savepoint lifetimes inside one top-level transaction (F14).\n *\n * A savepoint stack lives on ONE connection: `ROLLBACK TO SAVEPOINT s` undoes\n * everything done after `s`, including work a LATER savepoint already released.\n * Two `transaction()` callbacks opened from the same handle and overlapping in\n * time therefore share that one stack, and the earlier one's rollback silently\n * erases the later one's already committed work. Unique savepoint names do not\n * help — the names never collided, the lifetimes did.\n *\n * The rule enforced here, on every handle of a transaction:\n *\n * > A handle runs when nothing deeper than itself is open. Otherwise it waits\n * > for the innermost open savepoint to settle — unless the call is being made\n * > from INSIDE that savepoint's own callback, which is genuine nesting and\n * > proceeds immediately.\n *\n * The \"from inside\" test is an {@link AsyncLocalStorage} lookup, so the common\n * nesting shapes never wait on themselves: a child opened from its parent's\n * callback, and the parent handle used from within an open child's callback,\n * both proceed. Only genuinely concurrent siblings serialize.\n *\n * Not covered: a callback that awaits a sibling it did not start. That\n * arrangement asks for two savepoints to be open at once, which is exactly what\n * loses work; it now blocks instead. Deliberate — see the notes for this fix.\n */\n\nimport { AsyncLocalStorage } from \"node:async_hooks\";\nimport { type SavepointCounter, createSavepointCounter, nextSavepointName } from \"./savepoints.ts\";\n\n// ─── Types ────────────────────────────────────────────────────────\n\n/**\n * One handle's position in the savepoint stack of a top-level transaction.\n * `state` is shared by reference across every handle of that transaction.\n */\nexport type SavepointScope = {\n /** Physical savepoint depth; 0 is the top-level transaction itself. */\n readonly depth: number;\n /** Shared state: name allocation plus the innermost open savepoint. */\n readonly state: SavepointState;\n};\n\n/** Mutable state shared by every scope of one top-level transaction. */\nexport type SavepointState = {\n /** Savepoint name counter (shared, so names never collide). */\n readonly counter: SavepointCounter;\n /** The innermost savepoint currently open, or `null` when none is. */\n open: OpenSavepoint | null;\n};\n\n/** An open savepoint: its handle, its name, and when it finished. */\ntype OpenSavepoint = {\n readonly scope: SavepointScope;\n readonly name: string;\n readonly parent: OpenSavepoint | null;\n readonly settled: Promise<void>;\n readonly settle: () => void;\n};\n\n/**\n * The savepoint whose callback the current async context is running inside.\n * This is the discriminator between genuine nesting (proceed) and an\n * overlapping sibling (wait).\n */\nconst CURRENT = new AsyncLocalStorage<SavepointScope>();\n\n// ─── Public API ───────────────────────────────────────────────────\n\n/** Fresh scope for one top-level transaction: depth 0, nothing open. */\nexport function createSavepointScope(): SavepointScope {\n return { depth: 0, state: { counter: createSavepointCounter(), open: null } };\n}\n\n/**\n * Run a top-level transaction callback inside its scope's async context, so\n * nested calls made from it can be told apart from calls made inside an open\n * child.\n */\nexport function runRootScope<T>(params: { scope: SavepointScope; body: () => Promise<T> }): Promise<T> {\n return CURRENT.run(params.scope, params.body);\n}\n\n/**\n * The wait a statement on `scope` owes before it may run, or `undefined` when\n * it may run now. Returning `undefined` keeps the uncontended path — nothing\n * deeper open — free of an extra promise and microtask.\n */\nexport function scopeTurn(params: { scope: SavepointScope }): Promise<void> | undefined {\n const open = params.scope.state.open;\n if (mayProceed({ scope: params.scope, open })) return undefined;\n return waitForTurn({ scope: params.scope });\n}\n\n/**\n * Open a nested savepoint under `scope`, run `body` inside it and release it —\n * rolling back to it if `body` throws, exactly as before this gate existed.\n *\n * `execute` runs the savepoint control SQL (already wrapped in the adapter's\n * own driver-error mapping). Omit it when the driver manages the savepoint\n * itself (bun:sql's `savepoint()`), in which case only the lifetime is gated.\n */\nexport async function runNestedSavepoint<T>(params: {\n readonly scope: SavepointScope;\n readonly execute?: (statement: string) => Promise<unknown> | unknown;\n readonly body: (scope: SavepointScope) => Promise<T>;\n}): Promise<T> {\n const { scope, execute, body } = params;\n const node = await acquire({ scope });\n try {\n if (execute !== undefined) await execute(`SAVEPOINT ${node.name}`);\n let result: T;\n try {\n result = await CURRENT.run(node.scope, () => body(node.scope));\n } catch (error) {\n // A descendant the callback started but never awaited must finish before\n // this savepoint may be rolled back — releasing out of order corrupts\n // the stack.\n await drainDescendants({ node });\n if (execute !== undefined) {\n // Best effort: never mask the original failure with a rollback failure.\n try {\n await execute(`ROLLBACK TO SAVEPOINT ${node.name}`);\n } catch {\n /* rollback is best-effort */\n }\n }\n throw error;\n }\n await drainDescendants({ node });\n if (execute !== undefined) await execute(`RELEASE SAVEPOINT ${node.name}`);\n return result;\n } finally {\n closeNode({ node });\n }\n}\n\n// ─── Internals ────────────────────────────────────────────────────\n\n/** Whether `scope` may act while `open` is the innermost open savepoint. */\nfunction mayProceed(params: { scope: SavepointScope; open: OpenSavepoint | null }): boolean {\n const { scope, open } = params;\n if (open === null) return true;\n // This handle IS the innermost open savepoint.\n if (open.scope === scope) return true;\n // This handle's own savepoint has already closed (a retained child handle):\n // it can only act at the connection's current depth, and waiting for a\n // shallower savepoint it does not own would never resolve.\n if (open.scope.depth < scope.depth) return true;\n // Genuine nesting: the call is being made from inside the open callback.\n return CURRENT.getStore() === open.scope;\n}\n\nasync function waitForTurn(params: { scope: SavepointScope }): Promise<void> {\n for (;;) {\n const open = params.scope.state.open;\n if (open === null || mayProceed({ scope: params.scope, open })) return;\n await open.settled;\n }\n}\n\n/**\n * Wait for this handle's turn and take it in the SAME synchronous step — the\n * check and the push must not be separated by an await, or two waiters woken\n * together would both open a savepoint.\n */\nasync function acquire(params: { scope: SavepointScope }): Promise<OpenSavepoint> {\n const { scope } = params;\n for (;;) {\n const open = scope.state.open;\n if (mayProceed({ scope, open })) return pushNode({ scope, parent: open });\n // `mayProceed` returns true for a null `open`, so this is never null here.\n await (open as OpenSavepoint).settled;\n }\n}\n\nfunction pushNode(params: { scope: SavepointScope; parent: OpenSavepoint | null }): OpenSavepoint {\n const { scope, parent } = params;\n const state = scope.state;\n let settle: () => void = () => undefined;\n const settled = new Promise<void>((resolve) => {\n settle = () => resolve();\n });\n // Physical depth follows the connection's stack, not the handle that opened\n // it: a savepoint opened from an outer handle inside a child's callback still\n // nests inside that child.\n const node: OpenSavepoint = {\n scope: { depth: (parent === null ? 0 : parent.scope.depth) + 1, state },\n name: nextSavepointName({ counter: state.counter }),\n parent,\n settled,\n settle,\n };\n state.open = node;\n return node;\n}\n\nfunction closeNode(params: { node: OpenSavepoint }): void {\n const { node } = params;\n const state = node.scope.state;\n if (state.open === node) state.open = node.parent;\n node.settle();\n}\n\n/** Wait out savepoints a callback opened below this node and never awaited. */\nasync function drainDescendants(params: { node: OpenSavepoint }): Promise<void> {\n const state = params.node.scope.state;\n for (;;) {\n const open = state.open;\n if (open === null || open === params.node || open.scope.depth <= params.node.scope.depth) return;\n await open.settled;\n }\n}\n",
|
|
9
10
|
"/**\n * Savepoint naming for nested transactions.\n *\n * v1 lesson (LEARNINGS.md): the counter is shared **by reference** across every\n * adapter created for one top-level transaction, so sibling and deeply nested\n * savepoints can never collide on a name.\n */\n\n/** Mutable counter shared by reference across one top-level transaction. */\nexport type SavepointCounter = { n: number };\n\n/** Fresh counter — one per top-level transaction. */\nexport function createSavepointCounter(): SavepointCounter {\n return { n: 0 };\n}\n\n/**\n * Allocate the next savepoint name (`vibeorm_sp_0`, `vibeorm_sp_1`, …) and\n * advance the shared counter. The name is an identifier by construction, so it\n * is safe to interpolate into `SAVEPOINT` / `RELEASE` / `ROLLBACK TO` SQL.\n */\nexport function nextSavepointName(params: { counter: SavepointCounter }): string {\n return `vibeorm_sp_${params.counter.n++}`;\n}\n",
|
|
10
11
|
"/**\n * Bounded prepared-statement NAME registry, shared per pool.\n *\n * With `preparedStatements: true` every distinct SQL text gets a stable name, so\n * PostgreSQL keeps a cached plan for it. Two properties matter, and the first\n * version of this file had neither:\n *\n * 1. **The bound has to be a bound on the SERVER** (F16). Evicting a local\n * mapping and minting a fresh name leaves the old prepared statement on\n * every backend that ever saw it — the local map stayed small while server\n * retention grew without limit. Nor can eviction be repaired with\n * `DEALLOCATE`: a name is prepared per CONNECTION, the adapter does not know\n * which pooled connections hold it, and deallocating behind node-postgres'\n * back invalidates its own per-client name registry (it would keep sending\n * the name and the server would refuse it).\n *\n * So the registry never re-uses a name for another text and never evicts:\n * it allocates at most `max` names, and once that budget is spent a new text\n * runs UNNAMED — bounded preparation fallback. Server retention is then\n * `max` per connection, permanently. Statements still bind their parameters;\n * only the server-side plan cache is skipped.\n *\n * 2. **Adapters sharing one pool must not collide** (F18). Each adapter used to\n * start its own counter at zero, so two adapters on one pool both sent\n * `vibeorm_0` for different SQL and postgres refused the second\n * (\"Prepared statements must be unique\"). Names are therefore allocated by a\n * registry shared per pool, which also stops N adapters from preparing the\n * same text N times.\n */\n\n/** Registry mapping SQL text to the prepared-statement name `pg` should send. */\nexport type StatementNameCache = {\n /**\n * Stable statement name for `text`, allocating one on first sight — or\n * `undefined` once the name budget is spent, meaning \"send this one unnamed\".\n */\n getName(params: { text: string }): string | undefined;\n /** Number of names allocated — diagnostics and tests. */\n size(): number;\n};\n\n/**\n * Create a statement-name registry that allocates at most `max` names\n * (`vibeorm_0`, `vibeorm_1`, …). Texts beyond the budget get no name and run\n * unprepared, so the server's prepared-statement retention stays bounded.\n */\nexport function createStatementNameCache(params: { max: number }): StatementNameCache {\n const max = Math.max(0, params.max);\n const names = new Map<string, string>();\n let counter = 0;\n\n return {\n getName(nameParams: { text: string }): string | undefined {\n const { text } = nameParams;\n const existing = names.get(text);\n if (existing !== undefined) return existing;\n if (names.size >= max) return undefined;\n const name = `vibeorm_${counter++}`;\n names.set(text, name);\n return name;\n },\n\n size(): number {\n return names.size;\n },\n };\n}\n",
|
|
11
12
|
"/**\n * Transaction budgets — the total-deadline clock and the handle-closure gate.\n *\n * BYTE-IDENTICAL in all six adapter packages, exactly like `savepoint-gate.ts`:\n * adapters depend on `@vibeorm/runtime` for TYPES only, so a shared value\n * module there would invert the dependency direction, and `@vibeorm/schema` is\n * the IR package, not a home for a transaction clock. Keep the six copies in\n * lockstep — a divergence here is a silent per-engine behaviour difference.\n *\n * WHAT A DEADLINE IS, AND IS NOT\n *\n * `TransactionOptions.timeout` is unchanged: a PER-STATEMENT engine-side bound.\n * `TransactionOptions.deadline` is new and different — a TOTAL wall-clock bound\n * on one top-level transaction. Its clock starts immediately before `BEGIN` is\n * sent, so connection acquisition (which has its own budget on the pooled\n * adapters) is deliberately NOT counted.\n *\n * Enforcement is never `Promise.race` over a statement that keeps running:\n *\n * - `\"between-statements\"` (the default, and all six adapters can deliver it):\n * before every statement, every nested `transaction()` and the `COMMIT`, an\n * expired budget refuses. The statement is never sent and the transaction is\n * rolled back. A statement ALREADY IN FLIGHT is not interrupted.\n * - `\"cancel-running-statements\"` (postgres servers only): additionally hands\n * the engine the remaining budget as `statement_timeout`, so the SERVER\n * cancels an over-long statement. An adapter that cannot do this refuses the\n * request outright rather than quietly delivering the weaker level.\n *\n * JavaScript cannot forcibly stop a running callback. A callback that keeps\n * going past the deadline finds its later database calls refused; work it does\n * outside the database (an HTTP call, an SMS) is not stopped and cannot be.\n * There is no automatic retry: a deadline failure is terminal for that\n * transaction, because external side effects may already have happened.\n */\n\nimport type {\n TransactionDeadlineEnforcement,\n TransactionDeadlineSupport,\n TransactionOptions,\n} from \"@vibeorm/runtime\";\nimport { VibeError } from \"@vibeorm/schema\";\n\n// ─── Types ────────────────────────────────────────────────────────\n\n/** Where a budget check happened, reported as `meta.stage`. */\nexport type BudgetStage = \"statement\" | \"nested\" | \"commit\";\n\n/**\n * What is known about a finished transaction. `\"unknown\"` is a first-class\n * outcome, not a failure to decide: when a rollback itself fails the commit\n * state genuinely cannot be asserted, and claiming \"rolled back\" would be a lie.\n */\nexport type TransactionOutcome = \"committed\" | \"rolled-back\" | \"unknown\";\n\n/** Wall clock, injectable so tests are deterministic instead of sleep-timed. */\nexport type DeadlineClock = () => number;\n\n/** The ambient clock, used whenever no seam is injected. */\nexport const DEFAULT_DEADLINE_CLOCK: DeadlineClock = (): number => Date.now();\n\n/**\n * One top-level transaction's budget. Nested savepoint handles share this\n * object BY REFERENCE — they never get an independent clock, connection or\n * session setting.\n */\nexport type TransactionBudget = {\n /** The enforcement level in force, or `null` when no deadline was requested. */\n readonly enforcement: TransactionDeadlineEnforcement | null;\n /** The requested total budget in milliseconds, or `null` when none was. */\n readonly totalMs: number | null;\n /** Existing per-statement limit, independent of the total deadline. Zero disables it. */\n readonly statementTimeoutMs: number | null;\n /** Milliseconds left (never negative), or `null` when no deadline is set. */\n remainingMs(): number | null;\n /** True only when a deadline is set and has passed. */\n expired(): boolean;\n /** Refuse a closed handle or an expired budget. Called BEFORE anything is sent. */\n assertUsable(params: { stage: BudgetStage }): void;\n /** Record the terminal outcome; the first call wins and later ones are ignored. */\n close(params: { outcome: TransactionOutcome }): void;\n /** The recorded outcome, or `null` while the transaction is still open. */\n readonly outcome: TransactionOutcome | null;\n};\n\n// ─── Validation (before BEGIN, before any write) ──────────────────\n\n/**\n * Check `options.deadline` against what this adapter can honestly deliver.\n * Called BEFORE `BEGIN` — a budget this engine cannot honour must never leave\n * a transaction open behind it.\n *\n * @throws VibeError `VIBE_VALIDATION` when `totalMs` is not a positive integer.\n * @throws VibeError `VIBE_UNSUPPORTED_CAPABILITY` when the requested\n * `enforcement` is stronger than `support` — refused, never silently degraded.\n */\nexport function validateTransactionDeadline(params: {\n options?: TransactionOptions;\n support: TransactionDeadlineSupport;\n provider: string;\n}): void {\n const { options, support, provider } = params;\n const deadline = options?.deadline;\n if (deadline === undefined) return;\n\n if (support === \"unsupported\") {\n throw new VibeError({\n code: \"VIBE_UNSUPPORTED_CAPABILITY\",\n message: `${provider} cannot enforce a transaction deadline — remove the \\`deadline\\` option`,\n meta: { provider, feature: \"transactionDeadline\", supported: support },\n });\n }\n\n if (!Number.isInteger(deadline.totalMs) || deadline.totalMs <= 0) {\n throw new VibeError({\n code: \"VIBE_VALIDATION\",\n message: `transaction deadline \\`totalMs\\` must be a positive integer of milliseconds, received ${String(deadline.totalMs)}`,\n meta: { provider, feature: \"transactionDeadline\", totalMs: deadline.totalMs },\n });\n }\n\n const requested: TransactionDeadlineEnforcement = deadline.enforcement ?? \"between-statements\";\n if (requested === \"cancel-running-statements\" && support !== \"cancel-running-statements\") {\n throw new VibeError({\n code: \"VIBE_UNSUPPORTED_CAPABILITY\",\n message:\n `${provider} cannot cancel a statement that is already running, so the requested ` +\n \"`cancel-running-statements` deadline guarantee is refused rather than silently downgraded — \" +\n 'ask for \"between-statements\" if a budget checked between statements is enough',\n meta: { provider, feature: \"transactionDeadline\", requested, supported: support },\n });\n }\n}\n\n// ─── The budget ───────────────────────────────────────────────────\n\n/**\n * Start the clock for one top-level transaction. Call it immediately before\n * `BEGIN`; {@link validateTransactionDeadline} must already have run.\n */\nexport function startTransactionBudget(params: {\n options?: TransactionOptions;\n provider: string;\n clock?: DeadlineClock;\n}): TransactionBudget {\n const { options, provider } = params;\n const clock = params.clock ?? DEFAULT_DEADLINE_CLOCK;\n const deadline = options?.deadline;\n const totalMs = deadline === undefined ? null : deadline.totalMs;\n const enforcement: TransactionDeadlineEnforcement | null =\n deadline === undefined ? null : (deadline.enforcement ?? \"between-statements\");\n const expiresAt = totalMs === null ? null : clock() + totalMs;\n let outcome: TransactionOutcome | null = null;\n\n const budget: TransactionBudget = {\n enforcement,\n totalMs,\n statementTimeoutMs: options?.timeout ?? null,\n remainingMs(): number | null {\n return expiresAt === null ? null : Math.max(0, expiresAt - clock());\n },\n expired(): boolean {\n return expiresAt !== null && clock() >= expiresAt;\n },\n assertUsable(assertParams: { stage: BudgetStage }): void {\n if (outcome !== null) {\n throw transactionClosedError({ provider, outcome, stage: assertParams.stage });\n }\n if (expiresAt !== null && clock() >= expiresAt) {\n throw transactionDeadlineError({\n provider,\n stage: assertParams.stage,\n totalMs: totalMs as number,\n overdueMs: clock() - expiresAt,\n });\n }\n },\n close(closeParams: { outcome: TransactionOutcome }): void {\n if (outcome === null) outcome = closeParams.outcome;\n },\n get outcome(): TransactionOutcome | null {\n return outcome;\n },\n };\n return budget;\n}\n\n/**\n * The engine-side bound for the NEXT statement, or `null` when the caller did\n * not buy `\"cancel-running-statements\"`. Floored at 1 ms: on postgres `0` means\n * \"no limit\", so a spent budget must never be handed over as a zero.\n */\nexport function engineStatementBudgetMs(params: { budget: TransactionBudget }): number | null {\n const { budget } = params;\n if (budget.enforcement !== \"cancel-running-statements\") return null;\n const remaining = budget.remainingMs();\n if (remaining === null) return null;\n const totalBound = Math.max(1, Math.ceil(remaining));\n return budget.statementTimeoutMs !== null && budget.statementTimeoutMs > 0\n ? Math.min(totalBound, budget.statementTimeoutMs) : totalBound;\n}\n\n// ─── Errors ───────────────────────────────────────────────────────\n\n/**\n * The refusal an expired budget raises. `VIBE_TRANSACTION` with a stable\n * `meta.reason` — no SQL text, no parameter values, no credentials.\n */\nexport function transactionDeadlineError(params: {\n provider: string;\n stage: BudgetStage;\n totalMs: number;\n overdueMs: number;\n}): VibeError {\n const { provider, stage, totalMs, overdueMs } = params;\n const what =\n stage === \"commit\"\n ? \"the transaction was rolled back instead of committed\"\n : \"the statement was never sent and the transaction is being rolled back\";\n return new VibeError({\n code: \"VIBE_TRANSACTION\",\n message:\n `transaction deadline of ${totalMs} ms expired ${Math.max(0, Math.round(overdueMs))} ms ago — ${what}. ` +\n \"Nothing is retried automatically: work the callback did outside the database may already have happened.\",\n meta: { provider, reason: \"transaction-deadline\", stage, totalMs, overdueMs: Math.max(0, Math.round(overdueMs)) },\n });\n}\n\n/**\n * The refusal an ESCAPED transaction handle raises — a transactional adapter\n * kept past the end of its transaction. Without this it would run on a\n * connection that is back in the pool, outside any transaction.\n */\nexport function transactionClosedError(params: {\n provider: string;\n outcome: TransactionOutcome;\n stage: BudgetStage;\n}): VibeError {\n const { provider, outcome, stage } = params;\n return new VibeError({\n code: \"VIBE_TRANSACTION\",\n message:\n `this transaction has already finished (${outcome}), so its client can no longer be used — ` +\n \"a handle kept past the end of `$transaction` would run outside the transaction, on a connection \" +\n \"that now belongs to someone else\",\n meta: { provider, reason: \"transaction-closed\", outcome, stage },\n });\n}\n\n/**\n * The stable typed outcome for \"the deadline expired and the rollback that was\n * supposed to clean up failed too\". Promising a rollback here would be false;\n * promising a retry would be worse.\n *\n * `cause` is the CAUSAL error (the deadline refusal), so the reason the\n * transaction was being abandoned survives. The rollback failure itself is\n * reported only as `meta.rollbackFailed`: a raw driver failure can carry\n * statement text, and this error is meant to be logged.\n */\nexport function transactionOutcomeUnknownError(params: {\n provider: string;\n totalMs: number;\n cause: unknown;\n}): VibeError {\n const { provider, totalMs, cause } = params;\n return new VibeError({\n code: \"VIBE_TRANSACTION\",\n message:\n `transaction deadline of ${totalMs} ms expired and the rollback that should have cleaned up failed — ` +\n \"the commit state of this transaction is UNKNOWN. It is not retried automatically; inspect the data \" +\n \"before deciding.\",\n meta: {\n provider,\n reason: \"transaction-outcome-unknown\",\n outcome: \"unknown\",\n totalMs,\n rollbackFailed: true,\n },\n cause,\n });\n}\n\n/** Whether `error` is this module's deadline refusal (used to pick the cleanup path). */\nexport function isTransactionDeadlineError(error: unknown): boolean {\n const reason = (error as { meta?: { reason?: unknown } } | null)?.meta?.reason;\n return reason === \"transaction-deadline\";\n}\n",
|
|
12
13
|
"/**\n * Transaction-control SQL text (postgres dialect).\n *\n * Kept apart from the adapter so the mapping from `TransactionOptions` to SQL\n * is unit-testable without a server, and so the timeout value is validated\n * before it is ever interpolated into a statement.\n */\n\nimport type { IsolationLevel, TransactionOptions } from \"@vibeorm/runtime\";\nimport { VibeError } from \"@vibeorm/schema\";\n\n/** VibeORM isolation level → PostgreSQL isolation level keyword. */\nexport const ISOLATION_LEVEL_SQL: Readonly<Record<IsolationLevel, string>> = Object.freeze({\n ReadCommitted: \"READ COMMITTED\",\n RepeatableRead: \"REPEATABLE READ\",\n Serializable: \"SERIALIZABLE\",\n});\n\n/** `BEGIN`, with an `ISOLATION LEVEL` clause when the caller asked for one. */\nexport function beginStatement(params: { isolationLevel?: IsolationLevel }): string {\n const { isolationLevel } = params;\n return isolationLevel === undefined\n ? \"BEGIN\"\n : `BEGIN ISOLATION LEVEL ${ISOLATION_LEVEL_SQL[isolationLevel]}`;\n}\n\n/**\n * `SET LOCAL statement_timeout = <ms>` — transaction-scoped, so PostgreSQL\n * cancels an over-long statement with SQLSTATE 57014 (mapped to\n * `VIBE_TRANSACTION` with `meta.reason = \"timeout\"`).\n *\n * @throws VibeError `VIBE_VALIDATION` when the timeout is not a non-negative\n * integer — the value is interpolated into SQL, so it is never trusted blindly.\n */\nexport function statementTimeoutStatement(params: { timeout: number }): string {\n const { timeout } = params;\n if (!Number.isInteger(timeout) || timeout < 0) {\n throw new VibeError({\n code: \"VIBE_VALIDATION\",\n message: `transaction timeout must be a non-negative integer of milliseconds, received ${String(timeout)}`,\n meta: { timeout },\n });\n }\n return `SET LOCAL statement_timeout = ${timeout}`;\n}\n\n// ─── Raw transaction control (session affinity) ─────────────────\n\n/** How a raw statement steers the adapter's session-affine raw transaction. */\nexport type RawTransactionControl = \"open\" | \"close\" | null;\n\nconst RAW_OPEN_PATTERN: RegExp = /^\\s*(?:begin|start\\s+transaction)\\b[^;]*;?\\s*$/i;\nconst RAW_CLOSE_PATTERN: RegExp = /^\\s*(?:commit|end|rollback|abort)(?:\\s+(?:transaction|work))?\\s*;?\\s*$/i;\n\n/**\n * Classify a raw statement as transaction control.\n *\n * @vibeorm/migrate drives transactional DDL by sending literal `BEGIN` /\n * `COMMIT` / `ROLLBACK` through its stateless `SqlExecutor` — sound only when\n * every statement in between rides the SAME connection. Without pinning, a\n * pooled `pool.query(\"BEGIN\")` sequence stays on one client only by LIFO\n * checkout luck; any concurrent traffic silently splits the transaction\n * across connections. The adapter uses this to pin one client for the whole\n * raw transaction (\"open\" checks out, \"close\" releases).\n *\n * Deliberately strict: only PURE single-statement control text matches\n * (`BEGIN`, `BEGIN ISOLATION LEVEL …;`, `COMMIT`, `ROLLBACK WORK`, `END`,\n * `ABORT`). Compound scripts, `ROLLBACK TO SAVEPOINT`, two-phase\n * `COMMIT PREPARED` and `COMMIT AND CHAIN` (which keeps a transaction open)\n * never engage the pinning.\n */\nexport function classifyRawTransactionControl(params: { text: string }): RawTransactionControl {\n if (RAW_OPEN_PATTERN.test(params.text)) return \"open\";\n if (RAW_CLOSE_PATTERN.test(params.text)) return \"close\";\n return null;\n}\n\n// ─── Nested transaction options (the cross-adapter contract) ──────\n\n/**\n * Refuse options on a NESTED `transaction()` call — the same contract on every\n * adapter (see the `DatabaseAdapter.transaction` JSDoc in @vibeorm/runtime):\n * a nested transaction is a SAVEPOINT, and a savepoint can neither change the\n * isolation level of the transaction it joins nor enforce its own timeout.\n * Silently dropping the option (the pre-#3 behaviour) hid exactly that.\n *\n * @throws VibeError `VIBE_VALIDATION` when `isolationLevel` or `timeout` is\n * present (an `undefined` or empty options object is accepted).\n */\nexport function refuseNestedTransactionOptions(params: {\n options?: TransactionOptions;\n provider: string;\n}): void {\n const { options, provider } = params;\n if (options === undefined) return;\n\n const passed: string[] = [];\n if (options.isolationLevel !== undefined) passed.push(\"isolationLevel\");\n if (options.timeout !== undefined) passed.push(\"timeout\");\n if (options.deadline !== undefined) passed.push(\"deadline\");\n if (passed.length === 0) return;\n\n const meta: Record<string, unknown> = { provider, nested: true };\n if (options.isolationLevel !== undefined) meta.isolationLevel = options.isolationLevel;\n if (options.timeout !== undefined) meta.timeout = options.timeout;\n if (options.deadline !== undefined) meta.deadline = options.deadline;\n\n throw new VibeError({\n code: \"VIBE_VALIDATION\",\n message: `nested transactions run as savepoints and cannot honor ${passed.join(\" or \")} — a savepoint cannot change the isolation level of the transaction it joins, a nested timeout is not enforceable, and a nested deadline would need its own session setting on a connection it does not own (it inherits the top-level one instead); pass options on the top-level $transaction`,\n meta,\n });\n}\n"
|
|
13
14
|
],
|
|
14
|
-
"mappings": "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",
|
|
15
|
-
"debugId": "
|
|
15
|
+
"mappings": "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",
|
|
16
|
+
"debugId": "ABBE5E12E130DBA764756E2164756E21",
|
|
16
17
|
"names": []
|
|
17
18
|
}
|
package/dist/params.d.ts
CHANGED
|
@@ -20,8 +20,25 @@ export declare class PgArrayParam {
|
|
|
20
20
|
constructor(values: unknown[]);
|
|
21
21
|
}
|
|
22
22
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
23
|
+
* `Date` → UTC ISO-8601 text (`2026-03-08T19:38:08.538Z`).
|
|
24
|
+
*
|
|
25
|
+
* node-postgres' own serializer writes the host's LOCAL wall-clock plus its
|
|
26
|
+
* offset (`2026-03-08T20:38:08.538+01:00` under Europe/Oslo). A `timestamptz`
|
|
27
|
+
* parameter keeps the instant either way, but a `timestamp without time zone`
|
|
28
|
+
* or `date` parameter DISCARDS the offset ("PostgreSQL will silently ignore
|
|
29
|
+
* any time zone indication"), so the stored wall-clock would depend on the
|
|
30
|
+
* host's TZ — and differ from what bun:sql, PGlite and Prisma store for the
|
|
31
|
+
* same `Date` (all UTC). The UTC spelling stores one value on every host and
|
|
32
|
+
* matches the UTC read side (`EXACT_TYPES` in index.ts). Dates the ISO form
|
|
33
|
+
* cannot spell (invalid, BC, beyond year 9999) keep the driver's serializer.
|
|
34
|
+
*/
|
|
35
|
+
export declare function serializeDate(params: {
|
|
36
|
+
value: Date;
|
|
37
|
+
}): unknown;
|
|
38
|
+
/**
|
|
39
|
+
* ORM hot path: unwrap scalar-list markers, spell `Date` as UTC, JSON-stringify
|
|
40
|
+
* plain objects **and** plain arrays (they can only be `json`/`jsonb` values
|
|
41
|
+
* at this point).
|
|
25
42
|
*/
|
|
26
43
|
export declare function serializeParams(params: {
|
|
27
44
|
values: readonly unknown[];
|
package/dist/params.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"params.d.ts","sourceRoot":"","sources":["../src/params.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH;;;;GAIG;AACH,qBAAa,YAAY;IACvB,wCAAwC;IACxC,QAAQ,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC;IAE3B,YAAY,MAAM,EAAE,OAAO,EAAE,EAE5B;CACF;
|
|
1
|
+
{"version":3,"file":"params.d.ts","sourceRoot":"","sources":["../src/params.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH;;;;GAIG;AACH,qBAAa,YAAY;IACvB,wCAAwC;IACxC,QAAQ,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC;IAE3B,YAAY,MAAM,EAAE,OAAO,EAAE,EAE5B;CACF;AAID;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE;IAAE,KAAK,EAAE,IAAI,CAAA;CAAE,GAAG,OAAO,CAO9D;AAaD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE;IAAE,MAAM,EAAE,SAAS,OAAO,EAAE,CAAA;CAAE,GAAG,OAAO,EAAE,CAOjF;AAcD;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE;IAAE,MAAM,EAAE,SAAS,OAAO,EAAE,CAAA;CAAE,GAAG,OAAO,EAAE,CAQpF"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pool configuration for the pool {@link pgAdapter} builds itself.
|
|
3
|
+
*
|
|
4
|
+
* Per-connection PostgreSQL settings travel in the `options` startup
|
|
5
|
+
* parameter (`-c key=value` pairs the server parses at connect time), so every
|
|
6
|
+
* pooled connection inherits them without a per-query round trip. Two
|
|
7
|
+
* node-postgres facts shape the merge below, both read from the installed
|
|
8
|
+
* driver (pg 8.23):
|
|
9
|
+
*
|
|
10
|
+
* - `ConnectionParameters` parses `connectionString` OVER the explicit config
|
|
11
|
+
* (`Object.assign({}, config, parse(connectionString))`), so a URL that
|
|
12
|
+
* already carries `?options=…` silently wins over `config.options`.
|
|
13
|
+
* - `pg-connection-string` turns EVERY query parameter into a config key, so
|
|
14
|
+
* `?options=-c search_path=x` arrives as `config.options`.
|
|
15
|
+
*
|
|
16
|
+
* Hence: with no `options` in the URL the setting rides `config.options`
|
|
17
|
+
* (nothing to collide with); with one, the URL's value is EXTENDED in place
|
|
18
|
+
* and a `plan_cache_mode` the URL already sets is left alone — the caller's
|
|
19
|
+
* connection string is the more explicit statement of intent.
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* PostgreSQL plan-cache behaviour for named prepared statements
|
|
23
|
+
* (`preparedStatements: true`). `auto` (the server default) lets PostgreSQL
|
|
24
|
+
* switch to a generic plan after ~5 executions — cheaper per call, but it can
|
|
25
|
+
* pick worse indexes for range/LIKE/skewed filters; `force_custom_plan`
|
|
26
|
+
* replans with the real parameter values every time and keeps almost the whole
|
|
27
|
+
* prepared-statement gain (the saving is the parse/analyze and the protocol
|
|
28
|
+
* round, not the plan — measured −17…−25 % on reads either way, see
|
|
29
|
+
* `.ai/plans/26.09.08 - performance-join-strategy.md` §1.3).
|
|
30
|
+
*/
|
|
31
|
+
export type PlanCacheMode = "auto" | "force_custom_plan" | "force_generic_plan";
|
|
32
|
+
/** Validate a `planCacheMode` option value; the string ends up inside a startup parameter verbatim. */
|
|
33
|
+
export declare function assertPlanCacheMode(params: {
|
|
34
|
+
value: unknown;
|
|
35
|
+
}): PlanCacheMode;
|
|
36
|
+
/** The `options` startup parameter and (possibly rewritten) connection string carrying it. */
|
|
37
|
+
export type StartupOptionsPlacement = {
|
|
38
|
+
readonly connectionString: string | undefined;
|
|
39
|
+
/** `undefined` when nothing needs setting, or the URL already sets the parameter itself. */
|
|
40
|
+
readonly options: string | undefined;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Place `plan_cache_mode` into the startup options, honouring what the URL
|
|
44
|
+
* already carries (see the module header for the precedence rule).
|
|
45
|
+
*/
|
|
46
|
+
export declare function placePlanCacheMode(params: {
|
|
47
|
+
connectionString: string | undefined;
|
|
48
|
+
planCacheMode: PlanCacheMode;
|
|
49
|
+
}): StartupOptionsPlacement;
|
|
50
|
+
/** Build the `pg.Pool` constructor config for an adapter-owned pool. */
|
|
51
|
+
export declare function buildPoolConfig(params: {
|
|
52
|
+
connectionString: string | undefined;
|
|
53
|
+
max: number;
|
|
54
|
+
statementTimeout?: number;
|
|
55
|
+
connectionTimeout?: number;
|
|
56
|
+
planCacheMode?: PlanCacheMode;
|
|
57
|
+
}): Record<string, unknown>;
|
|
58
|
+
//# sourceMappingURL=pool-config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pool-config.d.ts","sourceRoot":"","sources":["../src/pool-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAIH;;;;;;;;;GASG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,mBAAmB,GAAG,oBAAoB,CAAC;AAQhF,uGAAuG;AACvG,wBAAgB,mBAAmB,CAAC,MAAM,EAAE;IAAE,KAAK,EAAE,OAAO,CAAA;CAAE,GAAG,aAAa,CAQ7E;AAED,8FAA8F;AAC9F,MAAM,MAAM,uBAAuB,GAAG;IACpC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9C,4FAA4F;IAC5F,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;CACtC,CAAC;AAEF;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE;IACzC,gBAAgB,EAAE,MAAM,GAAG,SAAS,CAAC;IACrC,aAAa,EAAE,aAAa,CAAC;CAC9B,GAAG,uBAAuB,CAuB1B;AAED,wEAAwE;AACxE,wBAAgB,eAAe,CAAC,MAAM,EAAE;IACtC,gBAAgB,EAAE,MAAM,GAAG,SAAS,CAAC;IACrC,GAAG,EAAE,MAAM,CAAC;IACZ,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,aAAa,CAAC,EAAE,aAAa,CAAC;CAC/B,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAW1B"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vibeorm/adapter-pg",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "node-postgres adapter for VibeORM v2 — Bun or Node.js",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"orm",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"bun": ">=1.2.0"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@vibeorm/runtime": "2.0.
|
|
46
|
+
"@vibeorm/runtime": "2.0.1",
|
|
47
47
|
"@vibeorm/schema": "2.0.0"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|