@vibeorm/adapter-pg 2.1.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -45,7 +45,7 @@ const db = VibeClient({ adapter: pgAdapter({ pool }) });
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
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
- | `stmtCacheMax` | `number` | `1000` | Maximum entries in the prepared-statement name cache (LRU) |
48
+ | `stmtCacheMax` | `number` | `1000` | Budget of prepared-statement names per pool. Names never evict (an evicted name stays prepared on the backend), so this bounds server-side retention per connection; a text arriving after the budget is spent runs unnamed |
49
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
50
 
51
51
  ### Prepared statements: the measured trade
@@ -61,8 +61,20 @@ Interleaved rounds on this repository's benchmark database (postgres 16, `apps/b
61
61
 
62
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
63
 
64
+ A name is allocated on a text's **second** sighting, never its first: statement names never evict (an evicted name stays prepared on every backend that saw it), so first-sight allocation let a stream of one-off texts — offset pagination emits a distinct text per page — spend the whole `stmtCacheMax` budget for the pool's lifetime and leave the genuinely hot statements unnamed. A repeated statement therefore runs unprepared exactly once; a one-off statement costs the server nothing.
65
+
64
66
  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.
65
67
 
68
+ ### Schema changes with prepared statements
69
+
70
+ **Recycle backend connections after a migration changes a prepared query's result shape.** PostgreSQL can reject the retained plan with SQLSTATE `0A000` (`cached plan must not change result type`), for example after changing the type of a selected column. `SELECT *` can also be affected by adding a column. `planCacheMode: "force_custom_plan"` does not prevent result-descriptor invalidation.
71
+
72
+ The adapter surfaces the failure as `VIBE_ADAPTER` with `meta.sqlstate: "0A000"` and the driver error as `cause`. It does not retry the statement, replay a transaction callback, or issue `DEALLOCATE`. Inside a transaction, the error leaves PostgreSQL in its aborted state until rollback. SQLSTATE `0A000` also covers other unsupported features, so it is not a safe generic retry signal.
73
+
74
+ For an incompatible schema deployment, stop admitting work on the old pool, let active transactions settle, apply the migration, then recycle the connections before resuming traffic. If the adapter owns its pool, await `disconnect()` after draining and reconnect it, or create a new adapter. If you supplied `pool`, its owner must await `pool.end()` and supply a new pool to a new adapter. Disconnecting or replacing only the adapter leaves an injected pool's retained statements intact. Never run `DEALLOCATE ALL` behind node-postgres' remembered statement-name registry.
75
+
76
+ The timings above are historical measurements, not a schema-recovery or current performance guarantee. Prepared statements remain opt-in; applications without a coordinated connection lifecycle should keep the default `preparedStatements: false`.
77
+
66
78
  ## Runtime support
67
79
 
68
80
  | Runtime | Supported |
@@ -78,6 +90,8 @@ This is a postgres-dialect adapter, so the full capability set is available: `RE
78
90
 
79
91
  Transactions run on one checked-out client; nested `transaction()` calls become savepoints on that same client. Raw transaction control (`BEGIN` / `COMMIT` / `ROLLBACK` sent through the raw path, as `@vibeorm/migrate` does for transactional DDL) pins one client for the duration, so a raw transaction never splits across connections. Concurrent raw transactions on a single adapter are unsupported.
80
92
 
93
+ A raw script that produces multiple PostgreSQL results returns only the **final command's** rows and affected-row count. Earlier results are not concatenated or summed; a final DDL command returns empty rows and zero affected rows.
94
+
81
95
  ## Parameter serialization: two paths
82
96
 
83
97
  `pg` turns a JS array into a PostgreSQL array literal natively, which is what a raw query wants but not what the ORM path can assume. The adapter therefore keeps two helpers:
@@ -85,6 +99,8 @@ Transactions run on one checked-out client; nested `transaction()` calls become
85
99
  - `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.
86
100
  - `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.
87
101
 
102
+ A string bound to a `::jsonb` slot on the raw path is sent as text and parsed by the server, so pre-encoded JSON text stores the document here — on `@vibeorm/adapter-bun` the same statement stores a jsonb *string*, because bun:sql JSON-encodes whatever is bound to a jsonb slot. Spell the slot `::text::jsonb` when a raw statement must run on both drivers; that is what the ORM path renders for `Json` fields (pinned live in `tests/raw-jsonb.live.test.ts` on both adapters).
103
+
88
104
  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
105
 
90
106
  ## Error mapping
package/dist/index.d.ts CHANGED
@@ -28,9 +28,11 @@ export type PgQueryResultLike = {
28
28
  readonly rows: Record<string, unknown>[];
29
29
  readonly rowCount: number | null;
30
30
  };
31
+ /** pg returns one result per command for simple-protocol scripts. */
32
+ export type PgQueryResultsLike = PgQueryResultLike | PgQueryResultLike[];
31
33
  /** The parts of a `pg` PoolClient this adapter uses. */
32
34
  export type PgPoolClientLike = {
33
- query(config: PgQueryConfig): Promise<PgQueryResultLike>;
35
+ query(config: PgQueryConfig): Promise<PgQueryResultsLike>;
34
36
  /**
35
37
  * Hand the client back. A TRUTHY argument tells node-postgres to DESTROY it
36
38
  * instead of pooling it — used when a rollback failed and the session state
@@ -48,7 +50,7 @@ export type PgPoolClientLike = {
48
50
  /** The parts of a `pg` Pool this adapter uses — satisfied by a real `pg.Pool`. */
49
51
  export type PgPoolLike = {
50
52
  connect(): Promise<PgPoolClientLike>;
51
- query(config: PgQueryConfig): Promise<PgQueryResultLike>;
53
+ query(config: PgQueryConfig): Promise<PgQueryResultsLike>;
52
54
  end(): Promise<void>;
53
55
  /** EventEmitter surface; optional for the same reason as on {@link PgPoolClientLike}. */
54
56
  on?(event: "error", listener: (error: unknown, client: PgPoolClientLike) => void): void;
@@ -90,9 +92,23 @@ export type PgAdapterOptions = {
90
92
  * earlier transaction is handed another backend later — SQLSTATE 26000,
91
93
  * 42P05, or, worst, another client's statement under the same name executed
92
94
  * without any error. Turn it on for direct connections and session pooling.
95
+ *
96
+ * Schema changes can invalidate a retained result descriptor (SQLSTATE
97
+ * 0A000). No statement or transaction callback is retried. Drain traffic and
98
+ * recycle backend connections across incompatible schema deployments. For
99
+ * an injected pool its owner must replace the pool; adapter.disconnect()
100
+ * does not end it. force_custom_plan does not remove this requirement.
93
101
  */
94
102
  readonly preparedStatements?: boolean;
95
- /** Maximum entries in the prepared-statement name cache (default 1000, LRU). */
103
+ /**
104
+ * Budget of prepared-statement NAMES per pool (default 1000). Names never
105
+ * evict — a name evicted here would stay prepared on every backend that saw
106
+ * it — so this is a bound on the server's retention per connection, and a
107
+ * text arriving after the budget is spent runs unnamed (it still binds its
108
+ * parameters; only the server-side plan cache is skipped). A name is
109
+ * allocated on a text's SECOND sighting, so one-off texts — offset pagination
110
+ * emits one per page — never spend the budget.
111
+ */
96
112
  readonly stmtCacheMax?: number;
97
113
  /**
98
114
  * PostgreSQL `plan_cache_mode` for every connection of the pool this adapter
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,8 +1,10 @@
1
1
  // @bun
2
2
  // packages/adapter-pg/src/index.ts
3
3
  import {
4
+ runPinnedSession,
4
5
  createSavepointScope,
5
6
  engineStatementBudgetMs,
7
+ isSavepointRollbackFailedError,
6
8
  isTransactionDeadlineError,
7
9
  runNestedSavepoint,
8
10
  runRootScope,
@@ -184,9 +186,14 @@ function buildPoolConfig(params) {
184
186
  }
185
187
 
186
188
  // packages/adapter-pg/src/statement-cache.ts
189
+ var DEFAULT_PROMOTE_AFTER = 2;
190
+ var SIGHTINGS_PER_NAME = 4;
187
191
  function createStatementNameCache(params) {
188
192
  const max = Math.max(0, params.max);
193
+ const promoteAfter = Math.max(1, params.promoteAfter ?? DEFAULT_PROMOTE_AFTER);
194
+ const sightingsMax = max * SIGHTINGS_PER_NAME;
189
195
  const names = new Map;
196
+ const sightings = new Map;
190
197
  let counter = 0;
191
198
  return {
192
199
  getName(nameParams) {
@@ -196,18 +203,35 @@ function createStatementNameCache(params) {
196
203
  return existing;
197
204
  if (names.size >= max)
198
205
  return;
206
+ const seen = (sightings.get(text) ?? 0) + 1;
207
+ if (seen < promoteAfter) {
208
+ sightings.delete(text);
209
+ sightings.set(text, seen);
210
+ while (sightings.size > sightingsMax) {
211
+ const oldest = sightings.keys().next();
212
+ if (oldest.done === true)
213
+ break;
214
+ sightings.delete(oldest.value);
215
+ }
216
+ return;
217
+ }
218
+ sightings.delete(text);
199
219
  const name = `vibeorm_${counter++}`;
200
220
  names.set(text, name);
201
221
  return name;
202
222
  },
203
223
  size() {
204
224
  return names.size;
225
+ },
226
+ sightings() {
227
+ return sightings.size;
205
228
  }
206
229
  };
207
230
  }
208
231
 
209
232
  // packages/adapter-pg/src/transaction-sql.ts
210
233
  import { VibeError as VibeError3 } from "@vibeorm/schema";
234
+ import { refuseNestedTransactionOptions } from "@vibeorm/runtime/adapter-kit";
211
235
  var ISOLATION_LEVEL_SQL = Object.freeze({
212
236
  ReadCommitted: "READ COMMITTED",
213
237
  RepeatableRead: "REPEATABLE READ",
@@ -237,32 +261,6 @@ function classifyRawTransactionControl(params) {
237
261
  return "close";
238
262
  return null;
239
263
  }
240
- function refuseNestedTransactionOptions(params) {
241
- const { options, provider } = params;
242
- if (options === undefined)
243
- return;
244
- const passed = [];
245
- if (options.isolationLevel !== undefined)
246
- passed.push("isolationLevel");
247
- if (options.timeout !== undefined)
248
- passed.push("timeout");
249
- if (options.deadline !== undefined)
250
- passed.push("deadline");
251
- if (passed.length === 0)
252
- return;
253
- const meta = { provider, nested: true };
254
- if (options.isolationLevel !== undefined)
255
- meta.isolationLevel = options.isolationLevel;
256
- if (options.timeout !== undefined)
257
- meta.timeout = options.timeout;
258
- if (options.deadline !== undefined)
259
- meta.deadline = options.deadline;
260
- throw new VibeError3({
261
- code: "VIBE_VALIDATION",
262
- 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`,
263
- meta
264
- });
265
- }
266
264
 
267
265
  // packages/adapter-pg/src/index.ts
268
266
  import { createSavepointCounter, nextSavepointName } from "@vibeorm/runtime/adapter-kit";
@@ -276,6 +274,9 @@ import {
276
274
  transactionOutcomeUnknownError as transactionOutcomeUnknownError2,
277
275
  validateTransactionDeadline as validateTransactionDeadline2
278
276
  } from "@vibeorm/runtime/adapter-kit";
277
+ function finalResult(params) {
278
+ return Array.isArray(params.result) ? params.result.at(-1) ?? { rows: [], rowCount: 0 } : params.result;
279
+ }
279
280
  var POOL_STATEMENT_NAMES = new WeakMap;
280
281
  function guardBackgroundErrors(params) {
281
282
  const { client } = params;
@@ -391,21 +392,17 @@ function pgAdapter(options) {
391
392
  async function runExecute(params) {
392
393
  const config = buildQueryConfig({ text: params.text, values: params.values });
393
394
  const result = await runMapped(() => params.queryable.query(config));
394
- return result.rows;
395
+ return finalResult({ result }).rows;
395
396
  }
396
397
  async function runExecuteUnsafe(params) {
397
398
  const values = params.values === undefined ? undefined : serializeParamsRaw({ values: params.values });
398
399
  const result = await runMapped(() => params.queryable.query({ text: params.text, values, types: EXACT_TYPES }));
399
- return { rows: result.rows, affectedRows: result.rowCount ?? 0 };
400
+ const final = finalResult({ result });
401
+ return { rows: final.rows, affectedRows: final.rowCount ?? 0 };
400
402
  }
401
403
  function createClientAdapter(params) {
402
- const { client, scope, budget } = params;
403
- async function applyEngineBudget() {
404
- const ms = engineStatementBudgetMs({ budget });
405
- if (ms === null)
406
- return;
407
- await runMapped(() => client.query({ text: statementTimeoutStatement({ timeout: ms }) }));
408
- }
404
+ const { dispatch, scope, budget } = params;
405
+ const client = { query: (config) => dispatch({ config }) };
409
406
  return {
410
407
  dialect: DIALECT,
411
408
  provider: PROVIDER,
@@ -416,7 +413,6 @@ function pgAdapter(options) {
416
413
  if (turn !== undefined)
417
414
  await turn;
418
415
  budget.assertUsable({ stage: "statement" });
419
- await applyEngineBudget();
420
416
  return runExecute({ queryable: client, ...execParams });
421
417
  },
422
418
  async executeUnsafe(execParams) {
@@ -424,7 +420,6 @@ function pgAdapter(options) {
424
420
  if (turn !== undefined)
425
421
  await turn;
426
422
  budget.assertUsable({ stage: "statement" });
427
- await applyEngineBudget();
428
423
  return runExecuteUnsafe({ queryable: client, ...execParams });
429
424
  },
430
425
  async transaction(fn, nestedOptions) {
@@ -432,8 +427,12 @@ function pgAdapter(options) {
432
427
  budget.assertUsable({ stage: "nested" });
433
428
  return runNestedSavepoint({
434
429
  scope,
435
- execute: (statement) => runMapped(() => client.query({ text: statement })),
436
- body: (child) => fn(createClientAdapter({ client, scope: child, budget }))
430
+ execute: (statement) => runMapped(() => dispatch({
431
+ config: { text: statement },
432
+ stage: "nested",
433
+ cleanup: statement.startsWith("ROLLBACK TO SAVEPOINT ")
434
+ })),
435
+ body: (child) => fn(createClientAdapter({ dispatch, scope: child, budget }))
437
436
  });
438
437
  },
439
438
  async connect() {},
@@ -448,6 +447,27 @@ function pgAdapter(options) {
448
447
  provider: PROVIDER,
449
448
  wire: WIRE,
450
449
  budgets: BUDGETS,
450
+ async withSession({ run }) {
451
+ const client = await runMapped(() => getPool().connect());
452
+ let backgroundFailure;
453
+ const onError = (error) => {
454
+ backgroundFailure = error;
455
+ };
456
+ client.on?.("error", onError);
457
+ return runPinnedSession({
458
+ run,
459
+ execute: async (query) => {
460
+ if (backgroundFailure !== undefined)
461
+ throw mapPgDriverError({ error: backgroundFailure });
462
+ return (await runExecuteUnsafe({ queryable: client, ...query })).rows;
463
+ },
464
+ isLost: (error) => error instanceof VibeError4 && (backgroundFailure !== undefined || typeof error.meta.sqlstate !== "string" || error.meta.sqlstate.startsWith("08") || ["57P01", "57P02", "57P03"].includes(error.meta.sqlstate)),
465
+ release: async () => {
466
+ client.removeListener?.("error", onError);
467
+ client.release(true);
468
+ }
469
+ });
470
+ },
451
471
  async execute(params) {
452
472
  return runExecute({ queryable: getPool(), ...params });
453
473
  },
@@ -494,6 +514,31 @@ function pgAdapter(options) {
494
514
  const unguard = guardBackgroundErrors({ client });
495
515
  const scope = createSavepointScope();
496
516
  const budget = startTransactionBudget({ options: transactionOptions, provider: PROVIDER });
517
+ let tail = Promise.resolve();
518
+ const dispatch = (dispatchParams) => {
519
+ if (budget.totalMs === null)
520
+ return client.query(dispatchParams.config);
521
+ const pending = tail.then(async () => {
522
+ if (!dispatchParams.cleanup) {
523
+ if (scope.state.poisoned !== null)
524
+ throw scope.state.poisoned;
525
+ const stage = dispatchParams.stage ?? "statement";
526
+ budget.assertUsable({ stage });
527
+ const ms = engineStatementBudgetMs({ budget });
528
+ if (ms !== null) {
529
+ await runMapped(() => client.query({ text: statementTimeoutStatement({ timeout: ms }) }));
530
+ budget.assertUsable({ stage });
531
+ }
532
+ }
533
+ return client.query(dispatchParams.config);
534
+ });
535
+ tail = pending.then(() => {
536
+ return;
537
+ }, () => {
538
+ return;
539
+ });
540
+ return pending;
541
+ };
497
542
  let discard = false;
498
543
  try {
499
544
  await runMapped(() => client.query({ text: beginStatement({ isolationLevel: transactionOptions?.isolationLevel }) }));
@@ -503,15 +548,19 @@ function pgAdapter(options) {
503
548
  }
504
549
  const result = await runRootScope({
505
550
  scope,
506
- body: () => fn(createClientAdapter({ client, scope, budget }))
551
+ body: () => fn(createClientAdapter({ dispatch, scope, budget }))
507
552
  });
508
553
  budget.assertUsable({ stage: "commit" });
509
- await runMapped(() => client.query({ text: "COMMIT" }));
554
+ await runMapped(() => dispatch({ config: { text: "COMMIT" }, stage: "commit" }));
510
555
  budget.close({ outcome: "committed" });
511
556
  return result;
512
557
  } catch (error) {
558
+ if (isSavepointRollbackFailedError(error) || scope.state.poisoned !== null) {
559
+ discard = true;
560
+ budget.close({ outcome: "unknown" });
561
+ }
513
562
  try {
514
- await client.query({ text: "ROLLBACK" });
563
+ await dispatch({ config: { text: "ROLLBACK" }, cleanup: true });
515
564
  budget.close({ outcome: "rolled-back" });
516
565
  } catch {
517
566
  discard = true;
@@ -586,4 +635,4 @@ export {
586
635
  DEFAULT_DEADLINE_CLOCK
587
636
  };
588
637
 
589
- //# debugId=1477648043AB662D64756E2164756E21
638
+ //# debugId=A62FE6D05ED7C89864756E2164756E21
package/dist/index.js.map CHANGED
@@ -2,14 +2,14 @@
2
2
  "version": 3,
3
3
  "sources": ["../src/index.ts", "../src/errors.ts", "../src/params.ts", "../src/pool-config.ts", "../src/statement-cache.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 {\n createSavepointScope,\n engineStatementBudgetMs,\n isTransactionDeadlineError,\n runNestedSavepoint,\n runRootScope,\n type SavepointScope,\n scopeTurn,\n startTransactionBudget,\n type TransactionBudget,\n transactionOutcomeUnknownError,\n validateTransactionDeadline,\n} from \"@vibeorm/runtime/adapter-kit\";\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 { createStatementNameCache, type StatementNameCache } from \"./statement-cache.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 \"@vibeorm/runtime/adapter-kit\";\nexport type { SavepointCounter } from \"@vibeorm/runtime/adapter-kit\";\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 (shared, @vibeorm/runtime/adapter-kit) ───\nexport {\n DEFAULT_DEADLINE_CLOCK,\n engineStatementBudgetMs,\n isTransactionDeadlineError,\n startTransactionBudget,\n transactionClosedError,\n transactionDeadlineError,\n transactionOutcomeUnknownError,\n validateTransactionDeadline,\n} from \"@vibeorm/runtime/adapter-kit\";\nexport type { BudgetStage, DeadlineClock, TransactionBudget, TransactionOutcome } from \"@vibeorm/runtime/adapter-kit\";\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 {\n type BudgetStage,\n runPinnedSession,\n createSavepointScope,\n engineStatementBudgetMs,\n isSavepointRollbackFailedError,\n isTransactionDeadlineError,\n runNestedSavepoint,\n runRootScope,\n type SavepointScope,\n scopeTurn,\n startTransactionBudget,\n type TransactionBudget,\n transactionOutcomeUnknownError,\n validateTransactionDeadline,\n} from \"@vibeorm/runtime/adapter-kit\";\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 { createStatementNameCache, type StatementNameCache } from \"./statement-cache.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/** pg returns one result per command for simple-protocol scripts. */\nexport type PgQueryResultsLike = PgQueryResultLike | PgQueryResultLike[];\n\n/** The parts of a `pg` PoolClient this adapter uses. */\nexport type PgPoolClientLike = {\n query(config: PgQueryConfig): Promise<PgQueryResultsLike>;\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<PgQueryResultsLike>;\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<PgQueryResultsLike> };\n\ntype TransactionDispatch = (params: {\n config: PgQueryConfig;\n stage?: BudgetStage;\n cleanup?: boolean;\n}) => Promise<PgQueryResultsLike>;\n\nfunction finalResult(params: { result: PgQueryResultsLike }): PgQueryResultLike {\n return Array.isArray(params.result)\n ? (params.result.at(-1) ?? { rows: [], rowCount: 0 })\n : params.result;\n}\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 * Schema changes can invalidate a retained result descriptor (SQLSTATE\n * 0A000). No statement or transaction callback is retried. Drain traffic and\n * recycle backend connections across incompatible schema deployments. For\n * an injected pool its owner must replace the pool; adapter.disconnect()\n * does not end it. force_custom_plan does not remove this requirement.\n */\n readonly preparedStatements?: boolean;\n /**\n * Budget of prepared-statement NAMES per pool (default 1000). Names never\n * evict — a name evicted here would stay prepared on every backend that saw\n * it — so this is a bound on the server's retention per connection, and a\n * text arriving after the budget is spent runs unnamed (it still binds its\n * parameters; only the server-side plan cache is skipped). A name is\n * allocated on a text's SECOND sighting, so one-off texts — offset pagination\n * emits one per page — never spend the budget.\n */\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` on a text's first sighting and once the pool's name budget is\n // spent: the statement runs unnamed rather than growing the server's\n // 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 finalResult({ 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 const final = finalResult({ result });\n return { rows: final.rows, affectedRows: final.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 dispatch: TransactionDispatch;\n scope: SavepointScope;\n budget: TransactionBudget;\n }): DatabaseAdapter {\n const { dispatch, scope, budget } = params;\n const client: Queryable = { query: (config) => dispatch({ config }) };\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 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 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(() => dispatch({\n config: { text: statement },\n stage: \"nested\",\n cleanup: statement.startsWith(\"ROLLBACK TO SAVEPOINT \"),\n })),\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({ dispatch, 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 withSession({ run }) {\n const client = await runMapped(() => getPool().connect());\n let backgroundFailure: unknown;\n const onError = (error: unknown): void => { backgroundFailure = error; };\n client.on?.(\"error\", onError);\n return runPinnedSession({\n run,\n execute: async (query) => {\n if (backgroundFailure !== undefined) throw mapPgDriverError({ error: backgroundFailure });\n return (await runExecuteUnsafe({ queryable: client, ...query })).rows;\n },\n isLost: (error) => error instanceof VibeError && (\n backgroundFailure !== undefined || typeof error.meta.sqlstate !== \"string\" ||\n error.meta.sqlstate.startsWith(\"08\") || [\"57P01\", \"57P02\", \"57P03\"].includes(error.meta.sqlstate)\n ),\n release: async () => {\n client.removeListener?.(\"error\", onError);\n // Migration SQL may change arbitrary session state. Never pool it.\n client.release(true);\n },\n });\n },\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 // One turn for SET LOCAL + dispatch + completion, shared by every nested\n // handle. Never queue merely the timeout update: pg queues queries itself,\n // which would let concurrent statements spend the same stale budget.\n let tail: Promise<unknown> = Promise.resolve();\n const dispatch: TransactionDispatch = (dispatchParams) => {\n if (budget.totalMs === null) return client.query(dispatchParams.config);\n const pending = tail.then(async () => {\n if (!dispatchParams.cleanup) {\n if (scope.state.poisoned !== null) throw scope.state.poisoned;\n const stage = dispatchParams.stage ?? \"statement\";\n budget.assertUsable({ stage });\n const ms = engineStatementBudgetMs({ budget });\n if (ms !== null) {\n await runMapped(() => client.query({ text: statementTimeoutStatement({ timeout: ms }) }));\n // SET LOCAL itself costs time. Refuse if its round trip exhausted\n // the deadline; no user query may enter pg's queue afterwards.\n budget.assertUsable({ stage });\n }\n }\n return client.query(dispatchParams.config);\n });\n // A failed statement must not strand ROLLBACK behind a rejected tail.\n tail = pending.then(() => undefined, () => undefined);\n return pending;\n };\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({ dispatch, scope, budget })),\n });\n // An expired budget must never commit, however the callback ended.\n budget.assertUsable({ stage: \"commit\" });\n await runMapped(() => dispatch({ config: { text: \"COMMIT\" }, stage: \"commit\" }));\n budget.close({ outcome: \"committed\" });\n return result;\n } catch (error) {\n if (isSavepointRollbackFailedError(error) || scope.state.poisoned !== null) {\n // A successful outer ROLLBACK cannot establish what happened when the\n // savepoint stack was lost. Keep the shared poison's unknown outcome.\n discard = true;\n budget.close({ outcome: \"unknown\" });\n }\n try {\n await dispatch({ config: { text: \"ROLLBACK\" }, cleanup: true });\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 \"@vibeorm/runtime/adapter-kit\";\nexport type { SavepointCounter } from \"@vibeorm/runtime/adapter-kit\";\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 (shared, @vibeorm/runtime/adapter-kit) ───\nexport {\n DEFAULT_DEADLINE_CLOCK,\n engineStatementBudgetMs,\n isTransactionDeadlineError,\n startTransactionBudget,\n transactionClosedError,\n transactionDeadlineError,\n transactionOutcomeUnknownError,\n validateTransactionDeadline,\n} from \"@vibeorm/runtime/adapter-kit\";\nexport type { BudgetStage, DeadlineClock, TransactionBudget, TransactionOutcome } from \"@vibeorm/runtime/adapter-kit\";\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 /** exclusion_violation */\n \"23P01\": \"VIBE_EXCLUSION_VIOLATION\",\n /** unique_violation */\n \"23505\": \"VIBE_UNIQUE_VIOLATION\",\n /** foreign_key_violation */\n \"23503\": \"VIBE_FK_VIOLATION\",\n /** restrict_violation — PostgreSQL 18+ ON DELETE/UPDATE RESTRICT */\n \"23001\": \"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 * lock_not_available — a `nowait` locking read whose rows were already\n * locked, OR a `lock_timeout` that expired while blocking. The server reports\n * both with this ONE SQLSTATE (verified on postgres 18.6: \"could not obtain\n * lock on row in relation\" and \"canceling statement due to lock timeout\"), so\n * VibeORM does not invent a distinction the server does not make. Nothing is\n * retried automatically — waiting, skipping or failing is the caller's policy.\n */\n \"55P03\": \"VIBE_LOCK_NOT_AVAILABLE\",\n});\n\n/** SQLSTATEs that additionally explain themselves through `meta.reason`. */\nexport const PG_SQLSTATE_REASONS: Readonly<Record<string, string>> = Object.freeze({\n \"23P01\": \"exclusion-violation\",\n \"57014\": \"timeout\",\n \"55P03\": \"lock-not-available\",\n /** Same reason string mysql already uses for errno 1213 — one word per dialect. */\n \"40P01\": \"deadlock\",\n \"40001\": \"serialization-failure\",\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
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
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",
9
- "/**\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",
10
- "/**\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"
9
+ "/**\n * Bounded prepared-statement NAME registry, shared per pool.\n *\n * With `preparedStatements: true` every SQL text that proves itself repeated\n * gets a stable name, so PostgreSQL keeps a cached plan for it. Three\n * properties matter, and the first version of this file had none of them:\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 * 3. **A one-off text must not spend the budget.** Because names never evict\n * (property 1), allocating on FIRST sight hands the whole budget to whatever\n * arrives first. Offset pagination emits a distinct text per page, so a\n * paginating workload used to burn all `max` names for the pool's lifetime\n * and leave every genuinely hot text unnamed thereafter. A name is therefore\n * allocated on the `promoteAfter`-th sighting of a text. Sightings live in\n * their own bounded LRU (capped at {@link SIGHTINGS_PER_NAME} × `max`);\n * evicting a sighting only delays a promotion, it can never name the wrong\n * text or exceed the budget. A hot text loses exactly one prepared\n * execution; a one-off text costs the server nothing.\n */\n\n/** Sightings of a text before it earns a name. */\nconst DEFAULT_PROMOTE_AFTER = 2;\n\n/**\n * Sighting slots per name of budget. The sighting map is a pure heuristic —\n * over-sizing it only wastes memory, under-sizing it only delays promotions —\n * so a small multiple of the name budget is enough to see a repeat through a\n * burst of one-off texts.\n */\nconst SIGHTINGS_PER_NAME = 4;\n\n/** Registry mapping SQL text to the prepared-statement name `pg` should send. */\nexport type StatementNameCache = {\n /**\n * Stable statement name for `text` — or `undefined` meaning \"send this one\n * unnamed\", which is the answer until the text has been seen `promoteAfter`\n * times and again once the name budget is spent.\n */\n getName(params: { text: string }): string | undefined;\n /** Number of names allocated — diagnostics and tests. */\n size(): number;\n /** Number of texts tracked toward promotion — diagnostics and tests. */\n sightings(): number;\n};\n\n/**\n * Create a statement-name registry that allocates at most `max` names\n * (`vibeorm_0`, `vibeorm_1`, …), each on the `promoteAfter`-th sighting of its\n * text (default 2). Texts seen fewer times, and texts arriving after the budget\n * is spent, get no name and run unprepared, so the server's prepared-statement\n * retention stays bounded and a stream of one-off texts cannot consume it.\n */\nexport function createStatementNameCache(params: { max: number; promoteAfter?: number }): StatementNameCache {\n const max = Math.max(0, params.max);\n const promoteAfter = Math.max(1, params.promoteAfter ?? DEFAULT_PROMOTE_AFTER);\n const sightingsMax = max * SIGHTINGS_PER_NAME;\n /** Never evicted, never re-used: this map is the server-side bound. */\n const names = new Map<string, string>();\n /** Insertion-ordered, so the first key is the least recently seen text. */\n const sightings = new Map<string, number>();\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 // Budget spent (and the `max: 0` case): nothing left to promote into, so\n // do not spend memory counting sightings either.\n if (names.size >= max) return undefined;\n\n const seen = (sightings.get(text) ?? 0) + 1;\n if (seen < promoteAfter) {\n // Re-insert to move the text to the most-recently-seen end.\n sightings.delete(text);\n sightings.set(text, seen);\n while (sightings.size > sightingsMax) {\n const oldest = sightings.keys().next();\n if (oldest.done === true) break;\n sightings.delete(oldest.value);\n }\n return undefined;\n }\n\n sightings.delete(text);\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 sightings(): number {\n return sightings.size;\n },\n };\n}\n",
10
+ "/**\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 } 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// ONE copy, shared by every adapter through the runtime's adapter-kit;\n// re-exported here so this adapter's public surface is unchanged.\nexport { refuseNestedTransactionOptions } from \"@vibeorm/runtime/adapter-kit\";\n"
11
11
  ],
12
- "mappings": "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",
13
- "debugId": "1477648043AB662D64756E2164756E21",
12
+ "mappings": "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",
13
+ "debugId": "A62FE6D05ED7C89864756E2164756E21",
14
14
  "names": []
15
15
  }
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * Bounded prepared-statement NAME registry, shared per pool.
3
3
  *
4
- * With `preparedStatements: true` every distinct SQL text gets a stable name, so
5
- * PostgreSQL keeps a cached plan for it. Two properties matter, and the first
6
- * version of this file had neither:
4
+ * With `preparedStatements: true` every SQL text that proves itself repeated
5
+ * gets a stable name, so PostgreSQL keeps a cached plan for it. Three
6
+ * properties matter, and the first version of this file had none of them:
7
7
  *
8
8
  * 1. **The bound has to be a bound on the SERVER** (F16). Evicting a local
9
9
  * mapping and minting a fresh name leaves the old prepared statement on
@@ -26,25 +26,42 @@
26
26
  * ("Prepared statements must be unique"). Names are therefore allocated by a
27
27
  * registry shared per pool, which also stops N adapters from preparing the
28
28
  * same text N times.
29
+ *
30
+ * 3. **A one-off text must not spend the budget.** Because names never evict
31
+ * (property 1), allocating on FIRST sight hands the whole budget to whatever
32
+ * arrives first. Offset pagination emits a distinct text per page, so a
33
+ * paginating workload used to burn all `max` names for the pool's lifetime
34
+ * and leave every genuinely hot text unnamed thereafter. A name is therefore
35
+ * allocated on the `promoteAfter`-th sighting of a text. Sightings live in
36
+ * their own bounded LRU (capped at {@link SIGHTINGS_PER_NAME} × `max`);
37
+ * evicting a sighting only delays a promotion, it can never name the wrong
38
+ * text or exceed the budget. A hot text loses exactly one prepared
39
+ * execution; a one-off text costs the server nothing.
29
40
  */
30
41
  /** Registry mapping SQL text to the prepared-statement name `pg` should send. */
31
42
  export type StatementNameCache = {
32
43
  /**
33
- * Stable statement name for `text`, allocating one on first sight — or
34
- * `undefined` once the name budget is spent, meaning "send this one unnamed".
44
+ * Stable statement name for `text` — or `undefined` meaning "send this one
45
+ * unnamed", which is the answer until the text has been seen `promoteAfter`
46
+ * times and again once the name budget is spent.
35
47
  */
36
48
  getName(params: {
37
49
  text: string;
38
50
  }): string | undefined;
39
51
  /** Number of names allocated — diagnostics and tests. */
40
52
  size(): number;
53
+ /** Number of texts tracked toward promotion — diagnostics and tests. */
54
+ sightings(): number;
41
55
  };
42
56
  /**
43
57
  * Create a statement-name registry that allocates at most `max` names
44
- * (`vibeorm_0`, `vibeorm_1`, …). Texts beyond the budget get no name and run
45
- * unprepared, so the server's prepared-statement retention stays bounded.
58
+ * (`vibeorm_0`, `vibeorm_1`, …), each on the `promoteAfter`-th sighting of its
59
+ * text (default 2). Texts seen fewer times, and texts arriving after the budget
60
+ * is spent, get no name and run unprepared, so the server's prepared-statement
61
+ * retention stays bounded and a stream of one-off texts cannot consume it.
46
62
  */
47
63
  export declare function createStatementNameCache(params: {
48
64
  max: number;
65
+ promoteAfter?: number;
49
66
  }): StatementNameCache;
50
67
  //# sourceMappingURL=statement-cache.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"statement-cache.d.ts","sourceRoot":"","sources":["../src/statement-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,iFAAiF;AACjF,MAAM,MAAM,kBAAkB,GAAG;IAC/B;;;OAGG;IACH,OAAO,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,MAAM,GAAG,SAAS,CAAC;IACtD,yDAAyD;IACzD,IAAI,IAAI,MAAM,CAAC;CAChB,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,kBAAkB,CAoBpF"}
1
+ {"version":3,"file":"statement-cache.d.ts","sourceRoot":"","sources":["../src/statement-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAaH,iFAAiF;AACjF,MAAM,MAAM,kBAAkB,GAAG;IAC/B;;;;OAIG;IACH,OAAO,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,MAAM,GAAG,SAAS,CAAC;IACtD,yDAAyD;IACzD,IAAI,IAAI,MAAM,CAAC;IACf,wEAAwE;IACxE,SAAS,IAAI,MAAM,CAAC;CACrB,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,kBAAkB,CA8C3G"}
@@ -5,7 +5,7 @@
5
5
  * is unit-testable without a server, and so the timeout value is validated
6
6
  * before it is ever interpolated into a statement.
7
7
  */
8
- import type { IsolationLevel, TransactionOptions } from "@vibeorm/runtime";
8
+ import type { IsolationLevel } from "@vibeorm/runtime";
9
9
  /** VibeORM isolation level → PostgreSQL isolation level keyword. */
10
10
  export declare const ISOLATION_LEVEL_SQL: Readonly<Record<IsolationLevel, string>>;
11
11
  /** `BEGIN`, with an `ISOLATION LEVEL` clause when the caller asked for one. */
@@ -45,18 +45,5 @@ export type RawTransactionControl = "open" | "close" | null;
45
45
  export declare function classifyRawTransactionControl(params: {
46
46
  text: string;
47
47
  }): RawTransactionControl;
48
- /**
49
- * Refuse options on a NESTED `transaction()` call — the same contract on every
50
- * adapter (see the `DatabaseAdapter.transaction` JSDoc in @vibeorm/runtime):
51
- * a nested transaction is a SAVEPOINT, and a savepoint can neither change the
52
- * isolation level of the transaction it joins nor enforce its own timeout.
53
- * Silently dropping the option (the pre-#3 behaviour) hid exactly that.
54
- *
55
- * @throws VibeError `VIBE_VALIDATION` when `isolationLevel` or `timeout` is
56
- * present (an `undefined` or empty options object is accepted).
57
- */
58
- export declare function refuseNestedTransactionOptions(params: {
59
- options?: TransactionOptions;
60
- provider: string;
61
- }): void;
48
+ export { refuseNestedTransactionOptions } from "@vibeorm/runtime/adapter-kit";
62
49
  //# sourceMappingURL=transaction-sql.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"transaction-sql.d.ts","sourceRoot":"","sources":["../src/transaction-sql.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAG3E,oEAAoE;AACpE,eAAO,MAAM,mBAAmB,EAAE,QAAQ,CAAC,MAAM,CAAC,cAAc,EAAE,MAAM,CAAC,CAIvE,CAAC;AAEH,+EAA+E;AAC/E,wBAAgB,cAAc,CAAC,MAAM,EAAE;IAAE,cAAc,CAAC,EAAE,cAAc,CAAA;CAAE,GAAG,MAAM,CAKlF;AAED;;;;;;;GAOG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAU7E;AAID,+EAA+E;AAC/E,MAAM,MAAM,qBAAqB,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC;AAK5D;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,6BAA6B,CAAC,MAAM,EAAE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,qBAAqB,CAI7F;AAID;;;;;;;;;GASG;AACH,wBAAgB,8BAA8B,CAAC,MAAM,EAAE;IACrD,OAAO,CAAC,EAAE,kBAAkB,CAAC;IAC7B,QAAQ,EAAE,MAAM,CAAC;CAClB,GAAG,IAAI,CAoBP"}
1
+ {"version":3,"file":"transaction-sql.d.ts","sourceRoot":"","sources":["../src/transaction-sql.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAGvD,oEAAoE;AACpE,eAAO,MAAM,mBAAmB,EAAE,QAAQ,CAAC,MAAM,CAAC,cAAc,EAAE,MAAM,CAAC,CAIvE,CAAC;AAEH,+EAA+E;AAC/E,wBAAgB,cAAc,CAAC,MAAM,EAAE;IAAE,cAAc,CAAC,EAAE,cAAc,CAAA;CAAE,GAAG,MAAM,CAKlF;AAED;;;;;;;GAOG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAU7E;AAID,+EAA+E;AAC/E,MAAM,MAAM,qBAAqB,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC;AAK5D;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,6BAA6B,CAAC,MAAM,EAAE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,qBAAqB,CAI7F;AAMD,OAAO,EAAE,8BAA8B,EAAE,MAAM,8BAA8B,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibeorm/adapter-pg",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "node-postgres adapter for VibeORM v2 — Bun or Node.js",
5
5
  "keywords": [
6
6
  "orm",
@@ -43,8 +43,8 @@
43
43
  "bun": ">=1.2.0"
44
44
  },
45
45
  "dependencies": {
46
- "@vibeorm/runtime": "2.1.0",
47
- "@vibeorm/schema": "2.1.0"
46
+ "@vibeorm/runtime": "2.3.0",
47
+ "@vibeorm/schema": "2.2.0"
48
48
  },
49
49
  "peerDependencies": {
50
50
  "pg": ">=8"