@vibeorm/adapter-pg 5.0.1 → 5.1.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/dist/index.js CHANGED
@@ -29,13 +29,37 @@ import {
29
29
  startTransactionBudget,
30
30
  transactionAbortedError as transactionAbortedError2,
31
31
  validateTransactionDeadline,
32
- parseExtendedDateTimeText
32
+ parseExtendedDateTimeText,
33
+ assertStatementName
33
34
  } from "@vibeorm/runtime/adapter-kit";
34
- import { VibeError as VibeError7 } from "@vibeorm/schema";
35
+ import { VibeError as VibeError9 } from "@vibeorm/schema";
35
36
  import pg3 from "pg";
36
37
 
37
38
  // packages/adapter-pg/src/batch.ts
39
+ import { VibeError } from "@vibeorm/schema";
38
40
  import pg from "pg";
41
+ function statementBookOf(params) {
42
+ const connection = params.connection;
43
+ if (connection === null || typeof connection !== "object")
44
+ return;
45
+ const { parsedStatements, submittedNamedStatements, on, removeListener } = connection;
46
+ if (typeof parsedStatements !== "object" || parsedStatements === null)
47
+ return;
48
+ if (typeof submittedNamedStatements !== "object" || submittedNamedStatements === null)
49
+ return;
50
+ if (typeof on !== "function" || typeof removeListener !== "function")
51
+ return;
52
+ return {
53
+ parsed: parsedStatements,
54
+ submitted: submittedNamedStatements,
55
+ onParseComplete: (listener) => {
56
+ on.call(connection, "parseComplete", listener);
57
+ return () => {
58
+ removeListener.call(connection, "parseComplete", listener);
59
+ };
60
+ }
61
+ };
62
+ }
39
63
  var prepareValue = (() => {
40
64
  const utils = pg.utils;
41
65
  return typeof utils?.prepareValue === "function" ? utils.prepareValue : undefined;
@@ -78,6 +102,11 @@ class PgBatch {
78
102
  resolve = () => {
79
103
  return;
80
104
  };
105
+ book = undefined;
106
+ parses = [];
107
+ parsesAnswered = 0;
108
+ stopParseTracking = undefined;
109
+ written = false;
81
110
  constructor(params) {
82
111
  this.steps = params.steps;
83
112
  this.types = params.types;
@@ -87,18 +116,85 @@ class PgBatch {
87
116
  }
88
117
  submit(connection) {
89
118
  const writer = connection;
119
+ if (writer.stream?.writable === false) {
120
+ return new VibeError({
121
+ code: "VIBE_ADAPTER",
122
+ message: "the connection's socket is no longer writable: the batch was not sent",
123
+ meta: { adapter: "pg", reason: "connection-not-writable" }
124
+ });
125
+ }
126
+ const book = this.steps.some((step) => step.name !== undefined) ? statementBookOf({ connection }) : undefined;
127
+ const pending = new Map;
128
+ const names = [];
129
+ for (const step of this.steps) {
130
+ const name = step.name;
131
+ if (name === undefined || book === undefined) {
132
+ names.push({ parse: true });
133
+ continue;
134
+ }
135
+ const known = book.parsed[name] ?? book.submitted[name] ?? pending.get(name);
136
+ if (known !== undefined && known !== step.text) {
137
+ return new VibeError({
138
+ code: "VIBE_VALIDATION",
139
+ message: `statement name "${name}" already names another SQL text on this connection: one name means one text`,
140
+ meta: { adapter: "pg", reason: "statement-name-reused", name }
141
+ });
142
+ }
143
+ if (known === undefined)
144
+ pending.set(name, step.text);
145
+ names.push({ parse: known === undefined, name });
146
+ }
147
+ if (book !== undefined)
148
+ for (const [name, text] of pending)
149
+ book.submitted[name] = text;
150
+ this.written = true;
151
+ if (names.some((entry) => entry.parse && entry.name !== undefined) && book !== undefined) {
152
+ this.book = book;
153
+ this.stopParseTracking = book.onParseComplete(() => {
154
+ this.answerParse();
155
+ });
156
+ }
90
157
  writer.stream?.cork?.();
91
158
  try {
92
- for (const step of this.steps) {
93
- writer.parse({ text: step.text, types: [] });
94
- writer.bind({ values: step.values ?? [] });
159
+ this.steps.forEach((step, index) => {
160
+ const entry = names[index];
161
+ if (entry.parse) {
162
+ writer.parse(entry.name === undefined ? { text: step.text, types: [] } : { text: step.text, name: entry.name, types: [] });
163
+ this.parses.push(entry.name === undefined ? null : { name: entry.name, text: step.text });
164
+ }
165
+ writer.bind(entry.name === undefined ? { values: step.values ?? [] } : { statement: entry.name, values: step.values ?? [] });
95
166
  writer.describe({ type: "P", name: "" });
96
167
  writer.execute({ portal: "", rows: 0 });
97
- }
168
+ });
98
169
  writer.sync();
99
170
  } finally {
100
171
  writer.stream?.uncork?.();
101
172
  }
173
+ return null;
174
+ }
175
+ answerParse() {
176
+ const book = this.book;
177
+ if (book === undefined || this.parsesAnswered >= this.parses.length)
178
+ return;
179
+ const parse = this.parses[this.parsesAnswered];
180
+ this.parsesAnswered += 1;
181
+ if (parse === null || parse === undefined)
182
+ return;
183
+ book.parsed[parse.name] = parse.text;
184
+ delete book.submitted[parse.name];
185
+ }
186
+ endParseTracking() {
187
+ const book = this.book;
188
+ if (book === undefined)
189
+ return;
190
+ for (const parse of this.parses.slice(this.parsesAnswered)) {
191
+ if (parse !== null && book.submitted[parse.name] === parse.text)
192
+ delete book.submitted[parse.name];
193
+ }
194
+ this.parsesAnswered = this.parses.length;
195
+ this.book = undefined;
196
+ this.stopParseTracking?.();
197
+ this.stopParseTracking = undefined;
102
198
  }
103
199
  handleRowDescription(message) {
104
200
  this.rows = [];
@@ -152,6 +248,8 @@ class PgBatch {
152
248
  this.outcomes.push({ status: "ok", result: { rows: [], rowCount: null } });
153
249
  }
154
250
  handleError(error) {
251
+ if (isServerError(error))
252
+ this.endParseTracking();
155
253
  if (this.settled)
156
254
  return;
157
255
  if (!isServerError(error)) {
@@ -164,6 +262,7 @@ class PgBatch {
164
262
  this.finish({});
165
263
  }
166
264
  handleReadyForQuery() {
265
+ this.endParseTracking();
167
266
  this.finish({});
168
267
  }
169
268
  handlePortalSuspended() {}
@@ -178,7 +277,7 @@ class PgBatch {
178
277
  this.callback?.(params.transportError ?? null);
179
278
  this.resolve({
180
279
  steps: this.outcomes,
181
- ...params.transportError === undefined ? {} : { transportError: params.transportError }
280
+ ...params.transportError === undefined ? {} : { transportError: params.transportError, ...this.written ? {} : { unsent: true } }
182
281
  });
183
282
  }
184
283
  }
@@ -198,10 +297,10 @@ function firstBatchFailure(params) {
198
297
  }
199
298
 
200
299
  // packages/adapter-pg/src/connection-group.ts
201
- import { VibeError } from "@vibeorm/schema";
300
+ import { VibeError as VibeError2 } from "@vibeorm/schema";
202
301
  import { transactionAbortedError } from "@vibeorm/runtime/adapter-kit";
203
302
  function connectionGroupMisuse(params) {
204
- return new VibeError({ code: "VIBE_INTERNAL", message: `pipeline lane group: ${params.message}`, meta: { reason: "pipeline-lane-group" } });
303
+ return new VibeError2({ code: "VIBE_INTERNAL", message: `pipeline lane group: ${params.message}`, meta: { reason: "pipeline-lane-group" } });
205
304
  }
206
305
  function openConnectionGroup(params) {
207
306
  const { driver } = params;
@@ -309,7 +408,7 @@ function openConnectionGroup(params) {
309
408
  }
310
409
 
311
410
  // packages/adapter-pg/src/connection-string.ts
312
- import { VibeError as VibeError2 } from "@vibeorm/schema";
411
+ import { VibeError as VibeError3 } from "@vibeorm/schema";
313
412
  function lastValue(params) {
314
413
  const values = params.search.getAll(params.key);
315
414
  return values.length === 0 ? undefined : values[values.length - 1];
@@ -328,7 +427,7 @@ function honourSystemRootCert(params) {
328
427
  const ssl = lastValue({ search, key: "ssl" });
329
428
  const weaker = sslmode !== undefined && sslmode !== "verify-full" ? { parameter: "sslmode", value: sslmode } : ssl !== undefined && ssl !== "true" && ssl !== "1" ? { parameter: "ssl", value: ssl } : undefined;
330
429
  if (weaker !== undefined) {
331
- throw new VibeError2({
430
+ throw new VibeError3({
332
431
  code: "VIBE_CONFIG",
333
432
  message: `pgAdapter \`${option}\` combines sslrootcert=system with ${weaker.parameter}=${JSON.stringify(weaker.value)}. ` + "libpq allows sslrootcert=system only with sslmode=verify-full (its default when sslmode is omitted): any certificate " + "the system trusts would otherwise vouch for a server that is not yours. Use sslmode=verify-full, or remove the setting.",
334
433
  meta: { adapter: "pg", reason: "sslrootcert-system-weak-sslmode", option, parameter: weaker.parameter, value: weaker.value }
@@ -370,7 +469,7 @@ function createClientTurns() {
370
469
 
371
470
  // packages/adapter-pg/src/read-cancellation.ts
372
471
  import pg2 from "pg";
373
- import { VibeError as VibeError4 } from "@vibeorm/schema";
472
+ import { VibeError as VibeError5 } from "@vibeorm/schema";
374
473
  import { assertReadExecution, readCancelledError } from "@vibeorm/runtime/adapter-kit";
375
474
 
376
475
  // packages/adapter-pg/src/config.ts
@@ -379,7 +478,7 @@ var PG_READ_CANCELLATION = Object.freeze({
379
478
  });
380
479
 
381
480
  // packages/adapter-pg/src/errors.ts
382
- import { VibeError as VibeError3 } from "@vibeorm/schema";
481
+ import { VibeError as VibeError4 } from "@vibeorm/schema";
383
482
  var PG_SQLSTATE_ERROR_CODES = Object.freeze({
384
483
  "23P01": "VIBE_EXCLUSION_VIOLATION",
385
484
  "23505": "VIBE_UNIQUE_VIOLATION",
@@ -407,7 +506,7 @@ var PG_SQLSTATE_REASONS = Object.freeze({
407
506
  "40001": "serialization-failure",
408
507
  "25006": "read-only-transaction"
409
508
  });
410
- var NAMED_STATEMENT_POOLER_HINT = "Hint: with preparedStatements: true every query must reach the backend that prepared its name. " + "Use a direct connection, session pooling, or PgBouncer 1.21+ with max_prepared_statements > 0; " + "behind a transaction-mode pooler without prepared-statement support, turn preparedStatements off.";
509
+ var NAMED_STATEMENT_POOLER_HINT = "Hint: with preparedStatements: true or a named raw statement, every query must reach the backend that prepared its name. " + "Use a direct connection, session pooling, or PgBouncer 1.21+ with max_prepared_statements > 0; " + "behind a transaction-mode pooler without prepared-statement support, turn preparedStatements off and send raw statements without a name.";
411
510
  var PG_SQLSTATE_HINTS = Object.freeze({
412
511
  "26000": NAMED_STATEMENT_POOLER_HINT,
413
512
  "42P05": NAMED_STATEMENT_POOLER_HINT
@@ -426,7 +525,7 @@ function pgErrorSqlstate(params) {
426
525
  }
427
526
  function mapPgDriverError(params) {
428
527
  const { error } = params;
429
- if (error instanceof VibeError3)
528
+ if (error instanceof VibeError4)
430
529
  return error;
431
530
  const source = error !== null && typeof error === "object" ? error : {};
432
531
  const sqlstate = pgErrorSqlstate({ error });
@@ -449,7 +548,7 @@ function mapPgDriverError(params) {
449
548
  const driverMessage = readStringField({ source, key: "message" }) ?? (typeof error === "string" ? error : "unknown postgres driver error");
450
549
  const hint = sqlstate === undefined ? undefined : PG_SQLSTATE_HINTS[sqlstate];
451
550
  const message = hint === undefined ? driverMessage : `${driverMessage}. ${hint}`;
452
- return new VibeError3({ code, message, meta, cause: error });
551
+ return new VibeError4({ code, message, meta, cause: error });
453
552
  }
454
553
  async function runMapped(fn) {
455
554
  try {
@@ -472,14 +571,14 @@ function createPgReadCancellation(params) {
472
571
  if (query.execution?.cancellation !== "running")
473
572
  return query.send();
474
573
  if (params.target.pipeline === true || (params.target.connectionParameters?.query_timeout ?? 0) > 0) {
475
- throw new VibeError4({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Running cancellation requires a non-pipelined target without local query_timeout", meta: { capability: "readCancellation", reason: "target-lifetime" } });
574
+ throw new VibeError5({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Running cancellation requires a non-pipelined target without local query_timeout", meta: { capability: "readCancellation", reason: "target-lifetime" } });
476
575
  }
477
576
  const address = params.connectionString;
478
577
  if (address === undefined)
479
- throw new VibeError4({ code: "VIBE_INTERNAL", message: "Running cancellation requires a control address" });
578
+ throw new VibeError5({ code: "VIBE_INTERNAL", message: "Running cancellation requires a control address" });
480
579
  const token = `vibe_cancel_${crypto.randomUUID()}`;
481
580
  if (!URL.canParse(address))
482
- throw new VibeError4({ code: "VIBE_CONFIG", message: "The PostgreSQL cancellation control address must be a URL" });
581
+ throw new VibeError5({ code: "VIBE_CONFIG", message: "The PostgreSQL cancellation control address must be a URL" });
483
582
  const url = new URL(address);
484
583
  url.searchParams.delete("query_timeout");
485
584
  url.searchParams.set("connect_timeout", String(PG_READ_CANCELLATION.controlTimeoutMs / 1000));
@@ -526,7 +625,7 @@ function createPgReadCancellation(params) {
526
625
  if (preflightAborted)
527
626
  throw readCancelledError({ stage: "waiting-for-connection" });
528
627
  if (witness === undefined || typeof witness.pid !== "number" || typeof witness.backend_start !== "string" || typeof witness.datname !== "string" || typeof witness.usename !== "string" || witness.tag !== token) {
529
- throw new VibeError4({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Cannot prove the exact PostgreSQL cancellation target", meta: { capability: "readCancellation", reason: "target-witness" } });
628
+ throw new VibeError5({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Cannot prove the exact PostgreSQL cancellation target", meta: { capability: "readCancellation", reason: "target-witness" } });
530
629
  }
531
630
  assertReadExecution({ execution: query.execution, effect: "read", support, provider: "postgresql", stage: "waiting-for-connection" });
532
631
  listener = () => {
@@ -543,7 +642,7 @@ function createPgReadCancellation(params) {
543
642
  throw error;
544
643
  };
545
644
  settlementTimer = setTimeout(() => {
546
- controlFailure = new VibeError4({ code: "VIBE_ADAPTER", message: "PostgreSQL read did not settle after cancellation; its exact session was discarded", meta: { reason: "read-cancellation-unsettled", databaseCancelled: false } });
645
+ controlFailure = new VibeError5({ code: "VIBE_ADAPTER", message: "PostgreSQL read did not settle after cancellation; its exact session was discarded", meta: { reason: "read-cancellation-unsettled", databaseCancelled: false } });
547
646
  if (!terminated) {
548
647
  terminated = true;
549
648
  params.terminate();
@@ -555,7 +654,7 @@ function createPgReadCancellation(params) {
555
654
  })).then((result) => {
556
655
  signalSent = rowsOf(result)[0]?.signalled === true;
557
656
  if (!signalSent)
558
- failControl(new VibeError4({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "The control endpoint did not signal the exact PostgreSQL target", meta: { capability: "readCancellation", reason: "target-not-signalled", databaseCancelled: false } }));
657
+ failControl(new VibeError5({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "The control endpoint did not signal the exact PostgreSQL target", meta: { capability: "readCancellation", reason: "target-not-signalled", databaseCancelled: false } }));
559
658
  }, failControl);
560
659
  cancel.then(() => {
561
660
  return;
@@ -586,12 +685,12 @@ function createPgReadCancellation(params) {
586
685
  }, () => {
587
686
  return;
588
687
  });
589
- const sqlstate = error instanceof VibeError4 ? error.meta.sqlstate : pgErrorSqlstate({ error });
688
+ const sqlstate = error instanceof VibeError5 ? error.meta.sqlstate : pgErrorSqlstate({ error });
590
689
  if (terminated && controlFailure !== undefined && sqlstate === undefined)
591
690
  throw controlFailure;
592
691
  if (attempted && signalSent && sqlstate === "57014") {
593
692
  const cancelled = readCancelledError({ stage: "running", databaseCancelled: true, cause: error });
594
- throw new VibeError4({ code: cancelled.code, message: cancelled.message, meta: { ...cancelled.meta, sqlstate: "57014" }, cause: error });
693
+ throw new VibeError5({ code: cancelled.code, message: cancelled.message, meta: { ...cancelled.meta, sqlstate: "57014" }, cause: error });
595
694
  }
596
695
  throw error;
597
696
  });
@@ -673,7 +772,7 @@ function serializeParamsRaw(params) {
673
772
  }
674
773
 
675
774
  // packages/adapter-pg/src/pool-config.ts
676
- import { VibeError as VibeError5 } from "@vibeorm/schema";
775
+ import { VibeError as VibeError6 } from "@vibeorm/schema";
677
776
  var PLAN_CACHE_MODES = ["auto", "force_custom_plan", "force_generic_plan"];
678
777
  function isPlanCacheMode(value) {
679
778
  return typeof value === "string" && PLAN_CACHE_MODES.includes(value);
@@ -682,7 +781,7 @@ function assertPlanCacheMode(params) {
682
781
  const { value } = params;
683
782
  if (isPlanCacheMode(value))
684
783
  return value;
685
- throw new VibeError5({
784
+ throw new VibeError6({
686
785
  code: "VIBE_CONFIG",
687
786
  message: `planCacheMode must be one of ${PLAN_CACHE_MODES.map((mode) => `"${mode}"`).join(", ")}, received ${JSON.stringify(value)}`,
688
787
  meta: { planCacheMode: value, allowed: PLAN_CACHE_MODES }
@@ -712,7 +811,7 @@ function assertIdleTimeout(params) {
712
811
  const { value } = params;
713
812
  if (typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 2147483647)
714
813
  return value;
715
- throw new VibeError5({
814
+ throw new VibeError6({
716
815
  code: "VIBE_CONFIG",
717
816
  message: "pgAdapter `idleTimeout` must be integer milliseconds from 0 through 2,147,483,647 (0 keeps idle connections open).",
718
817
  meta: { adapter: "pg", reason: "invalid-idle-timeout", option: "idleTimeout" }
@@ -722,7 +821,7 @@ function assertPoolMin(params) {
722
821
  const { value, max } = params;
723
822
  if (typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= max)
724
823
  return value;
725
- throw new VibeError5({
824
+ throw new VibeError6({
726
825
  code: "VIBE_CONFIG",
727
826
  message: `pgAdapter \`min\` must be an integer from 0 through \`max\` (${max}).`,
728
827
  meta: { adapter: "pg", reason: "invalid-pool-min", option: "min", max }
@@ -750,6 +849,7 @@ function buildPoolConfig(params) {
750
849
 
751
850
  // packages/adapter-pg/src/statement-cache.ts
752
851
  import { createHash } from "crypto";
852
+ import { VibeError as VibeError7 } from "@vibeorm/schema";
753
853
  var DEFAULT_PROMOTE_AFTER = 2;
754
854
  var SIGHTINGS_PER_NAME = 4;
755
855
  var NAME_PREFIX = "vibeorm_";
@@ -811,6 +911,25 @@ function createStatementNameCache(params) {
811
911
  }
812
912
  };
813
913
  }
914
+ function createRawStatementNames() {
915
+ const texts = new Map;
916
+ return {
917
+ admit(params) {
918
+ const known = texts.get(params.name);
919
+ if (known === undefined) {
920
+ texts.set(params.name, params.text);
921
+ return;
922
+ }
923
+ if (known === params.text)
924
+ return;
925
+ throw new VibeError7({
926
+ code: "VIBE_VALIDATION",
927
+ message: `statement name "${params.name}" already names another SQL text on this pool: one name means one text (give the new text its own name)`,
928
+ meta: { adapter: "pg", reason: "statement-name-reused", name: params.name }
929
+ });
930
+ }
931
+ };
932
+ }
814
933
 
815
934
  // packages/adapter-pg/src/timestamp-text.ts
816
935
  var DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
@@ -893,7 +1012,7 @@ function parsePlainTimestampText(params) {
893
1012
  }
894
1013
 
895
1014
  // packages/adapter-pg/src/transaction-sql.ts
896
- import { VibeError as VibeError6 } from "@vibeorm/schema";
1015
+ import { VibeError as VibeError8 } from "@vibeorm/schema";
897
1016
  import { refuseNestedTransactionOptions } from "@vibeorm/runtime/adapter-kit";
898
1017
  var ISOLATION_LEVEL_SQL = Object.freeze({
899
1018
  ReadCommitted: "READ COMMITTED",
@@ -916,7 +1035,7 @@ function openTransactionStatement(params) {
916
1035
  function statementTimeoutStatement(params) {
917
1036
  const { timeout } = params;
918
1037
  if (!Number.isInteger(timeout) || timeout < 0) {
919
- throw new VibeError6({
1038
+ throw new VibeError8({
920
1039
  code: "VIBE_VALIDATION",
921
1040
  message: `transaction timeout must be a non-negative integer of milliseconds, received ${String(timeout)}`,
922
1041
  meta: { timeout }
@@ -953,32 +1072,32 @@ import {
953
1072
  validateTransactionDeadline as validateTransactionDeadline2
954
1073
  } from "@vibeorm/runtime/adapter-kit";
955
1074
  function laneGroupMisuse(params) {
956
- return new VibeError7({ code: "VIBE_INTERNAL", message: `pipeline lane group: ${params.message}`, meta: { reason: "pipeline-lane-group" } });
1075
+ return new VibeError9({ code: "VIBE_INTERNAL", message: `pipeline lane group: ${params.message}`, meta: { reason: "pipeline-lane-group" } });
957
1076
  }
958
1077
  var MAX_STATEMENT_TIMEOUT_MS = 2147483647;
959
1078
  var ignore = () => {
960
1079
  return;
961
1080
  };
962
1081
  function predecessorClosedError() {
963
- return new VibeError7({
1082
+ return new VibeError9({
964
1083
  code: "VIBE_TRANSACTION",
965
1084
  message: "pipeline(): an earlier lane of this ordered scope closed without issuing its statement, so this statement was not sent " + "(the operation before it failed before it reached the database)",
966
1085
  meta: { reason: "pipeline-predecessor-closed" }
967
1086
  });
968
1087
  }
969
1088
  function orderedLaneMisuse(params) {
970
- return new VibeError7({ code: "VIBE_INTERNAL", message: `pipeline(): ${params.message}`, meta: { reason: "pipeline-ordered-lane" } });
1089
+ return new VibeError9({ code: "VIBE_INTERNAL", message: `pipeline(): ${params.message}`, meta: { reason: "pipeline-ordered-lane" } });
971
1090
  }
972
1091
  var COMMIT_STEP = Object.freeze({ text: "COMMIT" });
973
1092
  function commitSentRefusal() {
974
- return new VibeError7({
1093
+ return new VibeError9({
975
1094
  code: "VIBE_TRANSACTION",
976
1095
  message: "this transaction already sent its COMMIT together with its last statement, so it accepts no further statement",
977
1096
  meta: { provider: PROVIDER, reason: "commit-sent" }
978
1097
  });
979
1098
  }
980
1099
  function finalStatementMisuse() {
981
- return new VibeError7({
1100
+ return new VibeError9({
982
1101
  code: "VIBE_INTERNAL",
983
1102
  message: "withCommit(): a view carries ONE statement, the transaction's last",
984
1103
  meta: { reason: "with-commit-second-statement" }
@@ -993,15 +1112,16 @@ function finalResult(params) {
993
1112
  return Array.isArray(params.result) ? params.result.at(-1) ?? { rows: [], rowCount: 0 } : params.result;
994
1113
  }
995
1114
  var POOL_STATEMENT_NAMES = new WeakMap;
1115
+ var POOL_RAW_STATEMENT_NAMES = new WeakMap;
996
1116
  function isConnectionLoss(params) {
997
1117
  const { error } = params;
998
- if (!(error instanceof VibeError7))
1118
+ if (!(error instanceof VibeError9))
999
1119
  return true;
1000
1120
  const sqlstate = error.meta.sqlstate;
1001
1121
  return typeof sqlstate !== "string" || sqlstate.startsWith("08") || ["57P01", "57P02", "57P03"].includes(sqlstate);
1002
1122
  }
1003
1123
  function commitOutcomeUnknownError(params) {
1004
- return new VibeError7({
1124
+ return new VibeError9({
1005
1125
  code: "VIBE_TRANSACTION",
1006
1126
  message: "the connection was lost while COMMIT was in flight, so the transaction's outcome is unknown: the write may have committed. The connection was discarded; inspect the data before retrying.",
1007
1127
  meta: { provider: PROVIDER, reason: "transaction-outcome-unknown", outcome: "unknown", stage: "commit" },
@@ -1010,6 +1130,10 @@ function commitOutcomeUnknownError(params) {
1010
1130
  }
1011
1131
  var NEVER_REUSE_SQLSTATE_CLASSES = new Set(["08", "53", "57", "58", "XX"]);
1012
1132
  var STALE_STATEMENT_SQLSTATES = new Set(["0A000", "26000", "42P05"]);
1133
+ function isStaleStatementFailure(params) {
1134
+ const sqlstate = mapPgDriverError({ error: params.error }).meta.sqlstate;
1135
+ return typeof sqlstate === "string" && STALE_STATEMENT_SQLSTATES.has(sqlstate);
1136
+ }
1013
1137
  function observeReadyForQuery(params) {
1014
1138
  const client = params.client;
1015
1139
  let backgroundFailure = false;
@@ -1030,7 +1154,7 @@ function observeReadyForQuery(params) {
1030
1154
  client.on("end", onEnd);
1031
1155
  return {
1032
1156
  reusableAfter: async ({ error, named }) => {
1033
- if (!(error instanceof VibeError7))
1157
+ if (!(error instanceof VibeError9))
1034
1158
  return false;
1035
1159
  const sqlstate = error.meta.sqlstate;
1036
1160
  if (typeof sqlstate !== "string" || NEVER_REUSE_SQLSTATE_CLASSES.has(sqlstate.slice(0, 2)))
@@ -1160,7 +1284,7 @@ function takeAddress(params) {
1160
1284
  if (typeof connectionString === "string" && connectionString.trim() !== "") {
1161
1285
  return { connectionString: honourSystemRootCert({ connectionString, option: "connectionString" }) };
1162
1286
  }
1163
- throw new VibeError7({
1287
+ throw new VibeError9({
1164
1288
  code: "VIBE_CONFIG",
1165
1289
  message: "pgAdapter needs an explicit address: pass `connectionString` (a PostgreSQL URL) or an existing `pool`. " + "The adapter does not read DATABASE_URL or the PG* environment variables.",
1166
1290
  meta: { adapter: "pg", reason: "missing-address" }
@@ -1194,7 +1318,7 @@ function pgAdapter(options) {
1194
1318
  const planCacheMode = options?.planCacheMode === undefined ? "auto" : assertPlanCacheMode({ value: options.planCacheMode });
1195
1319
  const idleTimeout = "pool" in address || options?.idleTimeout === undefined ? undefined : assertIdleTimeout({ value: options.idleTimeout });
1196
1320
  if (options?.min !== undefined && "pool" in address) {
1197
- throw new VibeError7({
1321
+ throw new VibeError9({
1198
1322
  code: "VIBE_CONFIG",
1199
1323
  message: "pgAdapter `min` configures the pool the adapter builds \u2014 it cannot be applied to a caller-supplied `pool`; set `min` (and `idleTimeoutMillis`) on that pool instead",
1200
1324
  meta: { adapter: "pg", reason: "pool-min-with-injected-pool", option: "min" }
@@ -1202,7 +1326,7 @@ function pgAdapter(options) {
1202
1326
  }
1203
1327
  const poolMin = options?.min === undefined ? undefined : assertPoolMin({ value: options.min, max: options.max ?? 10 });
1204
1328
  if (planCacheMode !== "auto" && "pool" in address) {
1205
- throw new VibeError7({
1329
+ throw new VibeError9({
1206
1330
  code: "VIBE_CONFIG",
1207
1331
  message: "planCacheMode configures the pool the adapter builds \u2014 it cannot be applied to a caller-supplied `pool`; " + 'set `options: "-c plan_cache_mode=\u2026"` on that pool instead',
1208
1332
  meta: { planCacheMode }
@@ -1244,6 +1368,14 @@ function pgAdapter(options) {
1244
1368
  POOL_STATEMENT_NAMES.set(params.pool, created);
1245
1369
  return created;
1246
1370
  }
1371
+ function rawStatementNamesFor(params) {
1372
+ const existing = POOL_RAW_STATEMENT_NAMES.get(params.pool);
1373
+ if (existing !== undefined)
1374
+ return existing;
1375
+ const created = createRawStatementNames();
1376
+ POOL_RAW_STATEMENT_NAMES.set(params.pool, created);
1377
+ return created;
1378
+ }
1247
1379
  function buildQueryConfig(params) {
1248
1380
  const values = serializeParams({ values: params.values });
1249
1381
  if (!usePrepared)
@@ -1267,8 +1399,12 @@ function pgAdapter(options) {
1267
1399
  return finalResult({ result }).rows;
1268
1400
  }
1269
1401
  async function runExecuteUnsafe(params) {
1402
+ const name = params.name === undefined ? undefined : assertStatementName({ name: params.name, method: "executeUnsafe" });
1403
+ if (name !== undefined)
1404
+ rawStatementNamesFor({ pool: getPool() }).admit({ name, text: params.text });
1270
1405
  const values = params.values === undefined ? undefined : serializeParamsRaw({ values: params.values });
1271
- const result = await runMapped(() => params.queryable.query({ text: params.text, values, types: EXACT_TYPES }));
1406
+ const config = name === undefined ? { text: params.text, values, types: EXACT_TYPES } : { name, text: params.text, values, types: EXACT_TYPES };
1407
+ const result = await runMapped(() => params.queryable.query(config));
1272
1408
  observeResult({ scope: params.scope, effect: params.effect, result });
1273
1409
  const final = finalResult({ result });
1274
1410
  return { rows: final.rows, affectedRows: final.rowCount ?? 0 };
@@ -1286,7 +1422,13 @@ function pgAdapter(options) {
1286
1422
  };
1287
1423
  const waitAsLane = (wait) => lane === undefined || pipelines === null ? wait : pipelines.waitAs({ lane, wait });
1288
1424
  const rawClient = {
1289
- query: (config) => dispatch({ config, batchable: (config.values?.length ?? 0) > 0 || isSingleStatementText({ text: config.text }), ...laneOf, ...finalOf() })
1425
+ query: (config) => dispatch({
1426
+ config,
1427
+ batchable: (config.values?.length ?? 0) > 0 || config.name !== undefined || isSingleStatementText({ text: config.text }),
1428
+ ...config.name === undefined ? {} : { name: config.name },
1429
+ ...laneOf,
1430
+ ...finalOf()
1431
+ })
1290
1432
  };
1291
1433
  const savepointControl = (statement, cleanup) => {
1292
1434
  if (deferred !== null && deferred.routeSavepoint({ text: statement, cleanup }) !== "send")
@@ -1337,10 +1479,10 @@ function pgAdapter(options) {
1337
1479
  if (lane?.group !== undefined)
1338
1480
  throw laneGroupMisuse({ message: "a lane opens no pipeline scope while its group is open" });
1339
1481
  if (!Number.isSafeInteger(count) || count < 1) {
1340
- throw new VibeError7({ code: "VIBE_VALIDATION", message: "pipeline() needs a positive whole number of lanes", meta: { lanes: count } });
1482
+ throw new VibeError9({ code: "VIBE_VALIDATION", message: "pipeline() needs a positive whole number of lanes", meta: { lanes: count } });
1341
1483
  }
1342
1484
  if (ordered === true && params.ordered !== true) {
1343
- throw new VibeError7({
1485
+ throw new VibeError9({
1344
1486
  code: "VIBE_VALIDATION",
1345
1487
  message: "pipeline({ ordered: true }) is offered only on a handle that declares orderedPipeline (the top-level or a savepoint handle of a transaction without a deadline, outside a pipeline lane)",
1346
1488
  meta: { ordered: true, reason: "ordered-pipeline-undeclared" }
@@ -1610,7 +1752,10 @@ function pgAdapter(options) {
1610
1752
  const settleRidden = (entry, outcome, commitIndex) => {
1611
1753
  const step = outcome.steps[commitIndex];
1612
1754
  const answeredError = step?.status === "failed" ? mapPgDriverError({ error: step.error }) : undefined;
1613
- if (step === undefined || answeredError !== undefined && isConnectionLoss({ error: answeredError })) {
1755
+ if (step === undefined && outcome.unsent === true) {
1756
+ commitProgress = "not-sent";
1757
+ entry.state = "skipped";
1758
+ } else if (step === undefined || answeredError !== undefined && isConnectionLoss({ error: answeredError })) {
1614
1759
  commitProgress = "unanswered";
1615
1760
  discard = true;
1616
1761
  budget.close({ outcome: "unknown" });
@@ -1675,7 +1820,12 @@ function pgAdapter(options) {
1675
1820
  return gatherInTurn(dispatchParams);
1676
1821
  return handOver();
1677
1822
  }
1678
- const statement = dispatchParams.batchable === true ? [{ text: config.text, values: prepareBatchValues({ values: config.values ?? [] }), ...rides && dispatchParams.discardRows === true ? { discardRows: true } : {} }] : [];
1823
+ const statement = dispatchParams.batchable === true ? [{
1824
+ text: config.text,
1825
+ values: prepareBatchValues({ values: config.values ?? [] }),
1826
+ ...dispatchParams.name === undefined ? {} : { name: dispatchParams.name },
1827
+ ...rides && dispatchParams.discardRows === true ? { discardRows: true } : {}
1828
+ }] : [];
1679
1829
  assertReadExecution2({ execution: dispatchParams.execution, effect: "read", support: readCancellation, provider: PROVIDER, stage: "waiting-for-connection" });
1680
1830
  const carriesOpening = !deferred.opened();
1681
1831
  const control = pendingControl ? [...deferred.takeSteps()] : [];
@@ -1826,7 +1976,11 @@ function pgAdapter(options) {
1826
1976
  const control = inTurn ? [] : deferred.takeSteps();
1827
1977
  const openingSteps = carriesOpening ? openingLength : 0;
1828
1978
  const commitIndex = control.length + live.length;
1829
- const sendBatch = () => runBatch({ client, steps: [...control, ...live.map((entry) => ({ text: entry.text, values: entry.values })), ...rides ? [COMMIT_STEP] : []], types: EXACT_TYPES });
1979
+ const sendBatch = () => runBatch({
1980
+ client,
1981
+ steps: [...control, ...live.map((entry) => entry.name === undefined ? { text: entry.text, values: entry.values } : { text: entry.text, values: entry.values, name: entry.name }), ...rides ? [COMMIT_STEP] : []],
1982
+ types: EXACT_TYPES
1983
+ });
1830
1984
  const batch = inTurn ? new Promise((resolve) => {
1831
1985
  resolve(sendBatch());
1832
1986
  }) : runTurn(sendBatch);
@@ -1862,6 +2016,7 @@ function pgAdapter(options) {
1862
2016
  return {
1863
2017
  text: member.params.config.text,
1864
2018
  values: member.values,
2019
+ ...member.params.name === undefined ? {} : { name: member.params.name },
1865
2020
  ...member.params.execution === undefined || options?.inTurn === true ? {} : { execution: member.params.execution },
1866
2021
  lane: gatheredLane,
1867
2022
  resolve: member.resolve,
@@ -2184,6 +2339,7 @@ function pgAdapter(options) {
2184
2339
  queued.push({
2185
2340
  text: dispatchParams.config.text,
2186
2341
  values,
2342
+ ...dispatchParams.name === undefined ? {} : { name: dispatchParams.name },
2187
2343
  ...dispatchParams.execution === undefined ? {} : { execution: dispatchParams.execution },
2188
2344
  ...alone === true ? { alone: dispatchParams } : {},
2189
2345
  ...dispatchParams.commit === true ? { commit: true } : {},
@@ -2201,7 +2357,18 @@ function pgAdapter(options) {
2201
2357
  return pending;
2202
2358
  };
2203
2359
  const coalescable = (dispatchParams) => capable && budget.totalMs === null && !cancellationQueue && !((client.connectionParameters?.query_timeout ?? 0) > 0) && dispatchParams.batchable === true && dispatchParams.cleanup !== true && dispatchParams.stage === undefined && dispatchParams.sent === undefined && dispatchParams.execution?.cancellation !== "running";
2360
+ let staleNamedStatement = false;
2204
2361
  const dispatch = (dispatchParams) => {
2362
+ const dispatched = route(dispatchParams);
2363
+ if (dispatchParams.config.name === undefined)
2364
+ return dispatched;
2365
+ return dispatched.then(undefined, (error) => {
2366
+ if (isStaleStatementFailure({ error }))
2367
+ staleNamedStatement = true;
2368
+ throw error;
2369
+ });
2370
+ };
2371
+ const route = (dispatchParams) => {
2205
2372
  const lane = dispatchParams.lane?.open === true ? dispatchParams.lane : undefined;
2206
2373
  if (lane !== undefined && dispatchParams.cleanup !== true) {
2207
2374
  if (lane.group !== undefined)
@@ -2416,12 +2583,12 @@ function pgAdapter(options) {
2416
2583
  scope,
2417
2584
  commit: commitProgress,
2418
2585
  rollback,
2419
- connection: discard ? "discarded" : "returned"
2586
+ connection: discard || staleNamedStatement ? "discarded" : "returned"
2420
2587
  });
2421
2588
  } finally {
2422
2589
  unguard();
2423
2590
  if (!released)
2424
- client.release(discard);
2591
+ client.release(discard || staleNamedStatement);
2425
2592
  }
2426
2593
  },
2427
2594
  async connect() {
@@ -2548,4 +2715,4 @@ export {
2548
2715
  validateTransactionDeadline2 as validateTransactionDeadline
2549
2716
  };
2550
2717
 
2551
- //# debugId=96B1BE63991D190764756E2164756E21
2718
+ //# debugId=7B0EC2D2FB69EE3264756E2164756E21