@vibeorm/adapter-pg 4.2.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -16,17 +16,22 @@ import {
16
16
  transactionLifecycleRefusal,
17
17
  createSavepointScope,
18
18
  engineStatementBudgetMs,
19
+ findRefusedSetting,
19
20
  isSavepointRollbackFailedError,
20
21
  readOnlyRequested,
22
+ readTransactionSettings,
23
+ renderTransactionSettings,
24
+ transactionSettingRefusedError,
21
25
  runNestedSavepoint,
22
26
  runRootScope,
23
27
  scopeTurn,
24
28
  settleFailedTransaction,
25
29
  startTransactionBudget,
26
- transactionAbortedError,
27
- validateTransactionDeadline
30
+ transactionAbortedError as transactionAbortedError2,
31
+ validateTransactionDeadline,
32
+ parseExtendedDateTimeText
28
33
  } from "@vibeorm/runtime/adapter-kit";
29
- import { VibeError as VibeError5 } from "@vibeorm/schema";
34
+ import { VibeError as VibeError7 } from "@vibeorm/schema";
30
35
  import pg3 from "pg";
31
36
 
32
37
  // packages/adapter-pg/src/batch.ts
@@ -35,6 +40,9 @@ var prepareValue = (() => {
35
40
  const utils = pg.utils;
36
41
  return typeof utils?.prepareValue === "function" ? utils.prepareValue : undefined;
37
42
  })();
43
+ function batchDriverAvailable() {
44
+ return prepareValue !== undefined;
45
+ }
38
46
  function supportsBatch(params) {
39
47
  if (prepareValue === undefined)
40
48
  return false;
@@ -93,13 +101,20 @@ class PgBatch {
93
101
  }
94
102
  }
95
103
  handleRowDescription(message) {
104
+ this.rows = [];
105
+ if (this.steps[this.outcomes.length]?.discardRows === true) {
106
+ this.fields = [];
107
+ this.parsers = [];
108
+ return;
109
+ }
96
110
  this.fields = message.fields;
97
111
  this.parsers = message.fields.map((field) => this.types.getTypeParser(field.dataTypeID, field.format ?? "text"));
98
- this.rows = [];
99
112
  }
100
113
  handleDataRow(message) {
101
114
  if (this.settled || this.rowError !== undefined)
102
115
  return;
116
+ if (this.steps[this.outcomes.length]?.discardRows === true)
117
+ return;
103
118
  const row = {};
104
119
  try {
105
120
  for (let index = 0;index < this.fields.length; index++) {
@@ -182,9 +197,180 @@ function firstBatchFailure(params) {
182
197
  return;
183
198
  }
184
199
 
200
+ // packages/adapter-pg/src/connection-group.ts
201
+ import { VibeError } from "@vibeorm/schema";
202
+ import { transactionAbortedError } from "@vibeorm/runtime/adapter-kit";
203
+ function connectionGroupMisuse(params) {
204
+ return new VibeError({ code: "VIBE_INTERNAL", message: `pipeline lane group: ${params.message}`, meta: { reason: "pipeline-lane-group" } });
205
+ }
206
+ function openConnectionGroup(params) {
207
+ const { driver } = params;
208
+ const seats = [];
209
+ let sealed = false;
210
+ let sent = false;
211
+ const settle = (entries, outcome) => {
212
+ let failure;
213
+ entries.forEach((seat, index) => {
214
+ const issued = seat.issued;
215
+ if (issued === undefined)
216
+ return;
217
+ const step = outcome.steps[index];
218
+ if (step === undefined) {
219
+ const error = driver.mapError(outcome.transportError);
220
+ failure ??= error;
221
+ issued.reject(error);
222
+ } else if (step.status === "ok") {
223
+ issued.resolve(step.result.rows);
224
+ } else if (step.status === "failed") {
225
+ const error = driver.mapError(step.error);
226
+ failure ??= error;
227
+ issued.reject(error);
228
+ } else {
229
+ issued.reject(transactionAbortedError({ cause: failure }));
230
+ }
231
+ });
232
+ };
233
+ const flushIfReady = () => {
234
+ if (!sealed || sent)
235
+ return;
236
+ for (const seat of seats)
237
+ if (seat.open && seat.issued === undefined && !seat.done)
238
+ return;
239
+ sent = true;
240
+ const entries = seats.filter((seat) => seat.issued !== undefined);
241
+ if (entries.length === 0)
242
+ return;
243
+ const sending = new Promise((resolve) => {
244
+ resolve(driver.send(entries.map((seat) => seat.issued.entry)));
245
+ });
246
+ sending.then((outcome) => {
247
+ settle(entries, outcome);
248
+ }, (error) => {
249
+ const mapped = driver.mapError(error);
250
+ for (const seat of entries)
251
+ seat.issued?.reject(mapped);
252
+ });
253
+ };
254
+ const refused = (message) => () => Promise.reject(connectionGroupMisuse({ message }));
255
+ const subAdapter = (seat) => ({
256
+ ...driver.base,
257
+ execute: (call) => {
258
+ if (seat.issued !== undefined || seat.done)
259
+ return Promise.reject(connectionGroupMisuse({ message: "a sub-lane issues ONE statement" }));
260
+ if (!seat.open || sent)
261
+ return Promise.reject(connectionGroupMisuse({ message: "a closed sub-lane issues nothing" }));
262
+ if (call.execution !== undefined) {
263
+ seat.done = true;
264
+ flushIfReady();
265
+ return Promise.reject(connectionGroupMisuse({ message: "a sub-lane statement takes no read options" }));
266
+ }
267
+ const pending = new Promise((resolve, reject) => {
268
+ const step = driver.prepare(call);
269
+ seat.issued = { entry: { step, call }, resolve, reject };
270
+ });
271
+ if (seat.issued === undefined) {
272
+ seat.done = true;
273
+ flushIfReady();
274
+ return pending.then(undefined, (error) => {
275
+ throw driver.mapError(error);
276
+ });
277
+ }
278
+ flushIfReady();
279
+ return pending;
280
+ },
281
+ executeUnsafe: refused("a sub-lane runs no raw statement"),
282
+ transaction: refused("a sub-lane opens no transaction"),
283
+ connect: refused("a sub-lane does not connect"),
284
+ disconnect: refused("a sub-lane does not disconnect")
285
+ });
286
+ return {
287
+ lane: () => {
288
+ if (sealed)
289
+ throw connectionGroupMisuse({ message: "a sub-lane is opened only before the group is sealed" });
290
+ const seat = { index: seats.length, open: true, done: false };
291
+ seats.push(seat);
292
+ return {
293
+ adapter: subAdapter(seat),
294
+ close: () => {
295
+ if (!seat.open)
296
+ return;
297
+ seat.open = false;
298
+ flushIfReady();
299
+ }
300
+ };
301
+ },
302
+ seal: () => {
303
+ if (sealed)
304
+ return;
305
+ sealed = true;
306
+ flushIfReady();
307
+ }
308
+ };
309
+ }
310
+
311
+ // packages/adapter-pg/src/connection-string.ts
312
+ import { VibeError as VibeError2 } from "@vibeorm/schema";
313
+ function lastValue(params) {
314
+ const values = params.search.getAll(params.key);
315
+ return values.length === 0 ? undefined : values[values.length - 1];
316
+ }
317
+ function honourSystemRootCert(params) {
318
+ const { connectionString, option } = params;
319
+ const queryStart = connectionString.indexOf("?");
320
+ if (queryStart === -1)
321
+ return connectionString;
322
+ const hashStart = connectionString.indexOf("#", queryStart);
323
+ const queryEnd = hashStart === -1 ? connectionString.length : hashStart;
324
+ const search = new URLSearchParams(connectionString.slice(queryStart + 1, queryEnd));
325
+ if (lastValue({ search, key: "sslrootcert" }) !== "system")
326
+ return connectionString;
327
+ const sslmode = lastValue({ search, key: "sslmode" });
328
+ const ssl = lastValue({ search, key: "ssl" });
329
+ const weaker = sslmode !== undefined && sslmode !== "verify-full" ? { parameter: "sslmode", value: sslmode } : ssl !== undefined && ssl !== "true" && ssl !== "1" ? { parameter: "ssl", value: ssl } : undefined;
330
+ if (weaker !== undefined) {
331
+ throw new VibeError2({
332
+ code: "VIBE_CONFIG",
333
+ 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
+ meta: { adapter: "pg", reason: "sslrootcert-system-weak-sslmode", option, parameter: weaker.parameter, value: weaker.value }
335
+ });
336
+ }
337
+ search.delete("sslrootcert");
338
+ search.delete("ssl");
339
+ search.set("sslmode", "verify-full");
340
+ const query = search.toString().replace(/\+/g, "%20");
341
+ return `${connectionString.slice(0, queryStart)}?${query}${connectionString.slice(queryEnd)}`;
342
+ }
343
+
344
+ // packages/adapter-pg/src/client-turns.ts
345
+ function createClientTurns() {
346
+ let outstanding = 0;
347
+ let failure;
348
+ let previous = Promise.resolve();
349
+ const release = () => {
350
+ outstanding -= 1;
351
+ failure = undefined;
352
+ };
353
+ const fail = (error) => {
354
+ outstanding -= 1;
355
+ failure = { error };
356
+ };
357
+ return {
358
+ run: (send) => {
359
+ const running = outstanding === 0 ? new Promise((resolve) => {
360
+ resolve(send());
361
+ }) : previous.then(send);
362
+ outstanding += 1;
363
+ previous = running.then(release, fail);
364
+ return running;
365
+ },
366
+ outstanding: () => outstanding,
367
+ lastFailure: () => failure
368
+ };
369
+ }
370
+
185
371
  // packages/adapter-pg/src/read-cancellation.ts
186
372
  import pg2 from "pg";
187
- import { VibeError as VibeError2 } from "@vibeorm/schema";
373
+ import { VibeError as VibeError4 } from "@vibeorm/schema";
188
374
  import { assertReadExecution, readCancelledError } from "@vibeorm/runtime/adapter-kit";
189
375
 
190
376
  // packages/adapter-pg/src/config.ts
@@ -193,7 +379,7 @@ var PG_READ_CANCELLATION = Object.freeze({
193
379
  });
194
380
 
195
381
  // packages/adapter-pg/src/errors.ts
196
- import { VibeError } from "@vibeorm/schema";
382
+ import { VibeError as VibeError3 } from "@vibeorm/schema";
197
383
  var PG_SQLSTATE_ERROR_CODES = Object.freeze({
198
384
  "23P01": "VIBE_EXCLUSION_VIOLATION",
199
385
  "23505": "VIBE_UNIQUE_VIOLATION",
@@ -221,6 +407,11 @@ var PG_SQLSTATE_REASONS = Object.freeze({
221
407
  "40001": "serialization-failure",
222
408
  "25006": "read-only-transaction"
223
409
  });
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.";
411
+ var PG_SQLSTATE_HINTS = Object.freeze({
412
+ "26000": NAMED_STATEMENT_POOLER_HINT,
413
+ "42P05": NAMED_STATEMENT_POOLER_HINT
414
+ });
224
415
  var SQLSTATE_PATTERN = /^(?=[A-Z]*\d)[0-9A-Z]{5}$/;
225
416
  function readStringField(params) {
226
417
  const value = params.source[params.key];
@@ -235,7 +426,7 @@ function pgErrorSqlstate(params) {
235
426
  }
236
427
  function mapPgDriverError(params) {
237
428
  const { error } = params;
238
- if (error instanceof VibeError)
429
+ if (error instanceof VibeError3)
239
430
  return error;
240
431
  const source = error !== null && typeof error === "object" ? error : {};
241
432
  const sqlstate = pgErrorSqlstate({ error });
@@ -255,8 +446,10 @@ function mapPgDriverError(params) {
255
446
  const reason = sqlstate === undefined ? undefined : PG_SQLSTATE_REASONS[sqlstate];
256
447
  if (reason !== undefined)
257
448
  meta.reason = reason;
258
- const message = readStringField({ source, key: "message" }) ?? (typeof error === "string" ? error : "unknown postgres driver error");
259
- return new VibeError({ code, message, meta, cause: error });
449
+ const driverMessage = readStringField({ source, key: "message" }) ?? (typeof error === "string" ? error : "unknown postgres driver error");
450
+ const hint = sqlstate === undefined ? undefined : PG_SQLSTATE_HINTS[sqlstate];
451
+ const message = hint === undefined ? driverMessage : `${driverMessage}. ${hint}`;
452
+ return new VibeError3({ code, message, meta, cause: error });
260
453
  }
261
454
  async function runMapped(fn) {
262
455
  try {
@@ -279,14 +472,14 @@ function createPgReadCancellation(params) {
279
472
  if (query.execution?.cancellation !== "running")
280
473
  return query.send();
281
474
  if (params.target.pipeline === true || (params.target.connectionParameters?.query_timeout ?? 0) > 0) {
282
- throw new VibeError2({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Running cancellation requires a non-pipelined target without local query_timeout", meta: { capability: "readCancellation", reason: "target-lifetime" } });
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" } });
283
476
  }
284
477
  const address = params.connectionString;
285
478
  if (address === undefined)
286
- throw new VibeError2({ code: "VIBE_INTERNAL", message: "Running cancellation requires a control address" });
479
+ throw new VibeError4({ code: "VIBE_INTERNAL", message: "Running cancellation requires a control address" });
287
480
  const token = `vibe_cancel_${crypto.randomUUID()}`;
288
481
  if (!URL.canParse(address))
289
- throw new VibeError2({ code: "VIBE_CONFIG", message: "The PostgreSQL cancellation control address must be a URL" });
482
+ throw new VibeError4({ code: "VIBE_CONFIG", message: "The PostgreSQL cancellation control address must be a URL" });
290
483
  const url = new URL(address);
291
484
  url.searchParams.delete("query_timeout");
292
485
  url.searchParams.set("connect_timeout", String(PG_READ_CANCELLATION.controlTimeoutMs / 1000));
@@ -333,7 +526,7 @@ function createPgReadCancellation(params) {
333
526
  if (preflightAborted)
334
527
  throw readCancelledError({ stage: "waiting-for-connection" });
335
528
  if (witness === undefined || typeof witness.pid !== "number" || typeof witness.backend_start !== "string" || typeof witness.datname !== "string" || typeof witness.usename !== "string" || witness.tag !== token) {
336
- throw new VibeError2({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Cannot prove the exact PostgreSQL cancellation target", meta: { capability: "readCancellation", reason: "target-witness" } });
529
+ throw new VibeError4({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Cannot prove the exact PostgreSQL cancellation target", meta: { capability: "readCancellation", reason: "target-witness" } });
337
530
  }
338
531
  assertReadExecution({ execution: query.execution, effect: "read", support, provider: "postgresql", stage: "waiting-for-connection" });
339
532
  listener = () => {
@@ -350,7 +543,7 @@ function createPgReadCancellation(params) {
350
543
  throw error;
351
544
  };
352
545
  settlementTimer = setTimeout(() => {
353
- controlFailure = new VibeError2({ code: "VIBE_ADAPTER", message: "PostgreSQL read did not settle after cancellation; its exact session was discarded", meta: { reason: "read-cancellation-unsettled", databaseCancelled: false } });
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 } });
354
547
  if (!terminated) {
355
548
  terminated = true;
356
549
  params.terminate();
@@ -362,7 +555,7 @@ function createPgReadCancellation(params) {
362
555
  })).then((result) => {
363
556
  signalSent = rowsOf(result)[0]?.signalled === true;
364
557
  if (!signalSent)
365
- failControl(new VibeError2({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "The control endpoint did not signal the exact PostgreSQL target", meta: { capability: "readCancellation", reason: "target-not-signalled", databaseCancelled: false } }));
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 } }));
366
559
  }, failControl);
367
560
  cancel.then(() => {
368
561
  return;
@@ -393,12 +586,12 @@ function createPgReadCancellation(params) {
393
586
  }, () => {
394
587
  return;
395
588
  });
396
- const sqlstate = error instanceof VibeError2 ? error.meta.sqlstate : pgErrorSqlstate({ error });
589
+ const sqlstate = error instanceof VibeError4 ? error.meta.sqlstate : pgErrorSqlstate({ error });
397
590
  if (terminated && controlFailure !== undefined && sqlstate === undefined)
398
591
  throw controlFailure;
399
592
  if (attempted && signalSent && sqlstate === "57014") {
400
593
  const cancelled = readCancelledError({ stage: "running", databaseCancelled: true, cause: error });
401
- throw new VibeError2({ code: cancelled.code, message: cancelled.message, meta: { ...cancelled.meta, sqlstate: "57014" }, cause: error });
594
+ throw new VibeError4({ code: cancelled.code, message: cancelled.message, meta: { ...cancelled.meta, sqlstate: "57014" }, cause: error });
402
595
  }
403
596
  throw error;
404
597
  });
@@ -480,7 +673,7 @@ function serializeParamsRaw(params) {
480
673
  }
481
674
 
482
675
  // packages/adapter-pg/src/pool-config.ts
483
- import { VibeError as VibeError3 } from "@vibeorm/schema";
676
+ import { VibeError as VibeError5 } from "@vibeorm/schema";
484
677
  var PLAN_CACHE_MODES = ["auto", "force_custom_plan", "force_generic_plan"];
485
678
  function isPlanCacheMode(value) {
486
679
  return typeof value === "string" && PLAN_CACHE_MODES.includes(value);
@@ -489,7 +682,7 @@ function assertPlanCacheMode(params) {
489
682
  const { value } = params;
490
683
  if (isPlanCacheMode(value))
491
684
  return value;
492
- throw new VibeError3({
685
+ throw new VibeError5({
493
686
  code: "VIBE_CONFIG",
494
687
  message: `planCacheMode must be one of ${PLAN_CACHE_MODES.map((mode) => `"${mode}"`).join(", ")}, received ${JSON.stringify(value)}`,
495
688
  meta: { planCacheMode: value, allowed: PLAN_CACHE_MODES }
@@ -515,32 +708,76 @@ function placePlanCacheMode(params) {
515
708
  url.searchParams.set("options", merged);
516
709
  return { connectionString: url.toString(), options: merged };
517
710
  }
711
+ function assertIdleTimeout(params) {
712
+ const { value } = params;
713
+ if (typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 2147483647)
714
+ return value;
715
+ throw new VibeError5({
716
+ code: "VIBE_CONFIG",
717
+ message: "pgAdapter `idleTimeout` must be integer milliseconds from 0 through 2,147,483,647 (0 keeps idle connections open).",
718
+ meta: { adapter: "pg", reason: "invalid-idle-timeout", option: "idleTimeout" }
719
+ });
720
+ }
721
+ function assertPoolMin(params) {
722
+ const { value, max } = params;
723
+ if (typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= max)
724
+ return value;
725
+ throw new VibeError5({
726
+ code: "VIBE_CONFIG",
727
+ message: `pgAdapter \`min\` must be an integer from 0 through \`max\` (${max}).`,
728
+ meta: { adapter: "pg", reason: "invalid-pool-min", option: "min", max }
729
+ });
730
+ }
518
731
  function buildPoolConfig(params) {
519
- const { max, statementTimeout, connectionTimeout } = params;
732
+ const { max, min, statementTimeout, connectionTimeout, idleTimeout } = params;
520
733
  const placement = placePlanCacheMode({
521
734
  connectionString: params.connectionString,
522
735
  planCacheMode: params.planCacheMode ?? "auto"
523
736
  });
524
737
  const config = { connectionString: placement.connectionString, max };
738
+ if (min !== undefined)
739
+ config.min = min;
525
740
  if (statementTimeout !== undefined)
526
741
  config.statement_timeout = statementTimeout;
527
742
  if (connectionTimeout !== undefined)
528
743
  config.connectionTimeoutMillis = connectionTimeout;
744
+ if (idleTimeout !== undefined)
745
+ config.idleTimeoutMillis = idleTimeout;
529
746
  if (placement.options !== undefined)
530
747
  config.options = placement.options;
531
748
  return config;
532
749
  }
533
750
 
534
751
  // packages/adapter-pg/src/statement-cache.ts
752
+ import { createHash } from "crypto";
535
753
  var DEFAULT_PROMOTE_AFTER = 2;
536
754
  var SIGHTINGS_PER_NAME = 4;
755
+ var NAME_PREFIX = "vibeorm_";
756
+ var BASE32_ALPHABET = "abcdefghijklmnopqrstuvwxyz234567";
757
+ function statementNameFor(params) {
758
+ const digest = createHash("sha256").update(params.text, "utf8").digest();
759
+ let name = NAME_PREFIX;
760
+ let buffer = 0;
761
+ let bits = 0;
762
+ for (const byte of digest) {
763
+ buffer = buffer << 8 | byte;
764
+ bits += 8;
765
+ while (bits >= 5) {
766
+ bits -= 5;
767
+ name += BASE32_ALPHABET[buffer >>> bits & 31];
768
+ }
769
+ buffer &= (1 << bits) - 1;
770
+ }
771
+ if (bits > 0)
772
+ name += BASE32_ALPHABET[buffer << 5 - bits & 31];
773
+ return name;
774
+ }
537
775
  function createStatementNameCache(params) {
538
776
  const max = Math.max(0, params.max);
539
777
  const promoteAfter = Math.max(1, params.promoteAfter ?? DEFAULT_PROMOTE_AFTER);
540
778
  const sightingsMax = max * SIGHTINGS_PER_NAME;
541
779
  const names = new Map;
542
780
  const sightings = new Map;
543
- let counter = 0;
544
781
  return {
545
782
  getName(nameParams) {
546
783
  const { text } = nameParams;
@@ -562,7 +799,7 @@ function createStatementNameCache(params) {
562
799
  return;
563
800
  }
564
801
  sightings.delete(text);
565
- const name = `vibeorm_${counter++}`;
802
+ const name = statementNameFor({ text });
566
803
  names.set(text, name);
567
804
  return name;
568
805
  },
@@ -575,8 +812,88 @@ function createStatementNameCache(params) {
575
812
  };
576
813
  }
577
814
 
815
+ // packages/adapter-pg/src/timestamp-text.ts
816
+ var DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
817
+ var DIGIT_POSITIONS = [0, 1, 2, 3, 5, 6, 8, 9, 11, 12, 14, 15, 17, 18];
818
+ function parsePlainTimestampText(params) {
819
+ const { text, zoned } = params;
820
+ const length = text.length;
821
+ if (length < 19 || text.charCodeAt(4) !== 45 || text.charCodeAt(7) !== 45 || text.charCodeAt(10) !== 32 || text.charCodeAt(13) !== 58 || text.charCodeAt(16) !== 58) {
822
+ return;
823
+ }
824
+ for (let position = 0;position < DIGIT_POSITIONS.length; position++) {
825
+ const code = text.charCodeAt(DIGIT_POSITIONS[position]);
826
+ if (code < 48 || code > 57)
827
+ return;
828
+ }
829
+ const year = (text.charCodeAt(0) - 48) * 1000 + (text.charCodeAt(1) - 48) * 100 + (text.charCodeAt(2) - 48) * 10 + (text.charCodeAt(3) - 48);
830
+ const month = (text.charCodeAt(5) - 48) * 10 + (text.charCodeAt(6) - 48);
831
+ const day = (text.charCodeAt(8) - 48) * 10 + (text.charCodeAt(9) - 48);
832
+ const hour = (text.charCodeAt(11) - 48) * 10 + (text.charCodeAt(12) - 48);
833
+ const minute = (text.charCodeAt(14) - 48) * 10 + (text.charCodeAt(15) - 48);
834
+ const second = (text.charCodeAt(17) - 48) * 10 + (text.charCodeAt(18) - 48);
835
+ if (year < 1000 || month < 1 || month > 12 || day < 1 || hour > 23 || minute > 59 || second > 59)
836
+ return;
837
+ if (day > 28) {
838
+ const leap = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
839
+ const days = month === 2 ? leap ? 29 : 28 : DAYS_IN_MONTH[month - 1];
840
+ if (day > days)
841
+ return;
842
+ }
843
+ let index = 19;
844
+ let milliseconds = 0;
845
+ if (index < length && text.charCodeAt(index) === 46) {
846
+ index++;
847
+ const start = index;
848
+ while (index < length) {
849
+ const code = text.charCodeAt(index);
850
+ if (code < 48 || code > 57)
851
+ break;
852
+ if (index - start < 3)
853
+ milliseconds = milliseconds * 10 + (code - 48);
854
+ index++;
855
+ }
856
+ const digits = index - start;
857
+ if (digits === 0 || digits > 6)
858
+ return;
859
+ if (digits === 1)
860
+ milliseconds *= 100;
861
+ else if (digits === 2)
862
+ milliseconds *= 10;
863
+ }
864
+ let offsetMinutes = 0;
865
+ if (zoned) {
866
+ if (index + 3 > length)
867
+ return;
868
+ const sign = text.charCodeAt(index);
869
+ if (sign !== 43 && sign !== 45)
870
+ return;
871
+ const hours1 = text.charCodeAt(index + 1) - 48;
872
+ const hours2 = text.charCodeAt(index + 2) - 48;
873
+ if (hours1 < 0 || hours1 > 9 || hours2 < 0 || hours2 > 9)
874
+ return;
875
+ offsetMinutes = (hours1 * 10 + hours2) * 60;
876
+ index += 3;
877
+ if (index < length) {
878
+ if (index + 3 !== length || text.charCodeAt(index) !== 58)
879
+ return;
880
+ const minutes1 = text.charCodeAt(index + 1) - 48;
881
+ const minutes2 = text.charCodeAt(index + 2) - 48;
882
+ if (minutes1 < 0 || minutes1 > 9 || minutes2 < 0 || minutes2 > 9)
883
+ return;
884
+ offsetMinutes += minutes1 * 10 + minutes2;
885
+ index = length;
886
+ }
887
+ if (sign === 45)
888
+ offsetMinutes = -offsetMinutes;
889
+ }
890
+ if (index !== length)
891
+ return;
892
+ return new Date(Date.UTC(year, month - 1, day, hour, minute, second, milliseconds) - offsetMinutes * 60000);
893
+ }
894
+
578
895
  // packages/adapter-pg/src/transaction-sql.ts
579
- import { VibeError as VibeError4 } from "@vibeorm/schema";
896
+ import { VibeError as VibeError6 } from "@vibeorm/schema";
580
897
  import { refuseNestedTransactionOptions } from "@vibeorm/runtime/adapter-kit";
581
898
  var ISOLATION_LEVEL_SQL = Object.freeze({
582
899
  ReadCommitted: "READ COMMITTED",
@@ -599,7 +916,7 @@ function openTransactionStatement(params) {
599
916
  function statementTimeoutStatement(params) {
600
917
  const { timeout } = params;
601
918
  if (!Number.isInteger(timeout) || timeout < 0) {
602
- throw new VibeError4({
919
+ throw new VibeError6({
603
920
  code: "VIBE_VALIDATION",
604
921
  message: `transaction timeout must be a non-negative integer of milliseconds, received ${String(timeout)}`,
605
922
  meta: { timeout }
@@ -616,6 +933,12 @@ function classifyRawTransactionControl(params) {
616
933
  return "close";
617
934
  return null;
618
935
  }
936
+ var NOT_PROVABLY_ONE_STATEMENT = /[;$\u0000]|--|\/\*/;
937
+ var OWN_TURN_VERBS = /^\s*(?:begin|start|commit|end|rollback|abort|savepoint|release|prepare|copy)\b/i;
938
+ function isSingleStatementText(params) {
939
+ const { text } = params;
940
+ return text.trim() !== "" && !NOT_PROVABLY_ONE_STATEMENT.test(text) && !OWN_TURN_VERBS.test(text);
941
+ }
619
942
 
620
943
  // packages/adapter-pg/src/index.ts
621
944
  import { createSavepointCounter, nextSavepointName } from "@vibeorm/runtime/adapter-kit";
@@ -629,23 +952,56 @@ import {
629
952
  transactionOutcomeUnknownError,
630
953
  validateTransactionDeadline as validateTransactionDeadline2
631
954
  } from "@vibeorm/runtime/adapter-kit";
955
+ function laneGroupMisuse(params) {
956
+ return new VibeError7({ code: "VIBE_INTERNAL", message: `pipeline lane group: ${params.message}`, meta: { reason: "pipeline-lane-group" } });
957
+ }
632
958
  var MAX_STATEMENT_TIMEOUT_MS = 2147483647;
633
959
  var ignore = () => {
634
960
  return;
635
961
  };
962
+ function predecessorClosedError() {
963
+ return new VibeError7({
964
+ code: "VIBE_TRANSACTION",
965
+ 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
+ meta: { reason: "pipeline-predecessor-closed" }
967
+ });
968
+ }
969
+ function orderedLaneMisuse(params) {
970
+ return new VibeError7({ code: "VIBE_INTERNAL", message: `pipeline(): ${params.message}`, meta: { reason: "pipeline-ordered-lane" } });
971
+ }
972
+ var COMMIT_STEP = Object.freeze({ text: "COMMIT" });
973
+ function commitSentRefusal() {
974
+ return new VibeError7({
975
+ code: "VIBE_TRANSACTION",
976
+ message: "this transaction already sent its COMMIT together with its last statement, so it accepts no further statement",
977
+ meta: { provider: PROVIDER, reason: "commit-sent" }
978
+ });
979
+ }
980
+ function finalStatementMisuse() {
981
+ return new VibeError7({
982
+ code: "VIBE_INTERNAL",
983
+ message: "withCommit(): a view carries ONE statement, the transaction's last",
984
+ meta: { reason: "with-commit-second-statement" }
985
+ });
986
+ }
987
+ function beforeCommit(params) {
988
+ const { outcome, commitIndex } = params;
989
+ const steps = outcome.steps.slice(0, commitIndex);
990
+ return steps.length < commitIndex && outcome.transportError !== undefined ? { steps, transportError: outcome.transportError } : { steps };
991
+ }
636
992
  function finalResult(params) {
637
993
  return Array.isArray(params.result) ? params.result.at(-1) ?? { rows: [], rowCount: 0 } : params.result;
638
994
  }
639
995
  var POOL_STATEMENT_NAMES = new WeakMap;
640
996
  function isConnectionLoss(params) {
641
997
  const { error } = params;
642
- if (!(error instanceof VibeError5))
998
+ if (!(error instanceof VibeError7))
643
999
  return true;
644
1000
  const sqlstate = error.meta.sqlstate;
645
1001
  return typeof sqlstate !== "string" || sqlstate.startsWith("08") || ["57P01", "57P02", "57P03"].includes(sqlstate);
646
1002
  }
647
1003
  function commitOutcomeUnknownError(params) {
648
- return new VibeError5({
1004
+ return new VibeError7({
649
1005
  code: "VIBE_TRANSACTION",
650
1006
  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.",
651
1007
  meta: { provider: PROVIDER, reason: "transaction-outcome-unknown", outcome: "unknown", stage: "commit" },
@@ -674,7 +1030,7 @@ function observeReadyForQuery(params) {
674
1030
  client.on("end", onEnd);
675
1031
  return {
676
1032
  reusableAfter: async ({ error, named }) => {
677
- if (!(error instanceof VibeError5))
1033
+ if (!(error instanceof VibeError7))
678
1034
  return false;
679
1035
  const sqlstate = error.meta.sqlstate;
680
1036
  if (typeof sqlstate !== "string" || NEVER_REUSE_SQLSTATE_CLASSES.has(sqlstate.slice(0, 2)))
@@ -711,8 +1067,10 @@ function guardBackgroundErrors(params) {
711
1067
  }
712
1068
  var DIALECT = "postgres";
713
1069
  var PROVIDER = "postgresql";
714
- var WIRE = Object.freeze({ dateTime: "native", json: "parsed", numbers: "native" });
1070
+ var WIRE = Object.freeze({ dateTime: "native", json: "parsed", numbers: "native", bytes: "buffer" });
715
1071
  var ROUND_TRIPS = "network";
1072
+ var READ_SNAPSHOT = Object.freeze({ isolationLevel: "RepeatableRead", accessMode: "readOnly" });
1073
+ var PIPELINING = "batched";
716
1074
  var BUDGETS = Object.freeze({
717
1075
  acquireTimeout: true,
718
1076
  statementTimeout: "all-statements",
@@ -725,16 +1083,21 @@ var TIMESTAMP_OID = 1114;
725
1083
  var TIMESTAMP_ARRAY_OID = 1115;
726
1084
  var DATE_OID = 1082;
727
1085
  var DATE_ARRAY_OID = 1182;
1086
+ var TIMESTAMPTZ_OID = 1184;
1087
+ var TIMESTAMPTZ_ARRAY_OID = 1185;
728
1088
  var driverTypeParser = pg3.types.getTypeParser;
729
1089
  var ZONE_LESS_TEXT = /^\d{4}-\d{2}-\d{2}(?: \d{2}:\d{2}:\d{2}(?:\.\d{1,6})?)?$/;
730
1090
  function parseZoneLessAsUtc(value) {
1091
+ const plain = parsePlainTimestampText({ text: value, zoned: false });
1092
+ if (plain !== undefined)
1093
+ return plain;
731
1094
  if (ZONE_LESS_TEXT.test(value)) {
732
1095
  const iso = value.includes(" ") ? `${value.replace(" ", "T").replace(/(\.\d{3})\d+$/, "$1")}Z` : value;
733
1096
  const parsed = new Date(iso);
734
1097
  if (!Number.isNaN(parsed.getTime()))
735
1098
  return parsed;
736
1099
  }
737
- return driverTypeParser(TIMESTAMP_OID, "text")(value);
1100
+ return parseExtendedDateTimeText({ text: value }) ?? driverTypeParser(TIMESTAMP_OID, "text")(value);
738
1101
  }
739
1102
  function parseZoneLessArrayAsUtc(value) {
740
1103
  const mapElements = (element) => {
@@ -746,6 +1109,28 @@ function parseZoneLessArrayAsUtc(value) {
746
1109
  };
747
1110
  return mapElements(driverTypeParser(TEXT_ARRAY_OID, "text")(value));
748
1111
  }
1112
+ var pgTypesArrayParser = (() => {
1113
+ const candidate = pg3.types.arrayParser;
1114
+ return typeof candidate?.create === "function" ? candidate : undefined;
1115
+ })();
1116
+ function recogniseBuiltinParser(params) {
1117
+ const parser = driverTypeParser(params.oid, "text");
1118
+ return parser.name === params.name && params.peers.every((peer) => driverTypeParser(peer, "text") === parser) ? parser : undefined;
1119
+ }
1120
+ var BUILTIN_TIMESTAMPTZ_PARSER = recogniseBuiltinParser({
1121
+ oid: TIMESTAMPTZ_OID,
1122
+ peers: [TIMESTAMP_OID, DATE_OID],
1123
+ name: "parseDate"
1124
+ });
1125
+ var BUILTIN_TIMESTAMPTZ_ARRAY_PARSER = pgTypesArrayParser === undefined || BUILTIN_TIMESTAMPTZ_PARSER === undefined ? undefined : recogniseBuiltinParser({ oid: TIMESTAMPTZ_ARRAY_OID, peers: [TIMESTAMP_ARRAY_OID, DATE_ARRAY_OID], name: "parseDateArray" });
1126
+ function parseTimestamptzText(value) {
1127
+ return parsePlainTimestampText({ text: value, zoned: true }) ?? BUILTIN_TIMESTAMPTZ_PARSER(value);
1128
+ }
1129
+ function parseTimestamptzArrayText(value) {
1130
+ if (!value)
1131
+ return null;
1132
+ return pgTypesArrayParser.create(value, (entry) => entry !== null ? parseTimestamptzText(entry) : entry).parse();
1133
+ }
749
1134
  var EXACT_TYPES = {
750
1135
  getTypeParser(oid, format) {
751
1136
  if (format === undefined || format === "text") {
@@ -755,6 +1140,14 @@ var EXACT_TYPES = {
755
1140
  return parseZoneLessAsUtc;
756
1141
  if (oid === TIMESTAMP_ARRAY_OID || oid === DATE_ARRAY_OID)
757
1142
  return parseZoneLessArrayAsUtc;
1143
+ if (oid === TIMESTAMPTZ_OID) {
1144
+ const driver = driverTypeParser(oid, "text");
1145
+ return driver === BUILTIN_TIMESTAMPTZ_PARSER ? parseTimestamptzText : driver;
1146
+ }
1147
+ if (oid === TIMESTAMPTZ_ARRAY_OID) {
1148
+ const driver = driverTypeParser(oid, "text");
1149
+ return driver === BUILTIN_TIMESTAMPTZ_ARRAY_PARSER ? parseTimestamptzArrayText : driver;
1150
+ }
758
1151
  }
759
1152
  return driverTypeParser(oid, format);
760
1153
  }
@@ -764,23 +1157,52 @@ function takeAddress(params) {
764
1157
  if (options?.pool !== undefined && options.pool !== null)
765
1158
  return { pool: options.pool };
766
1159
  const connectionString = options?.connectionString;
767
- if (typeof connectionString === "string" && connectionString.trim() !== "")
768
- return { connectionString };
769
- throw new VibeError5({
1160
+ if (typeof connectionString === "string" && connectionString.trim() !== "") {
1161
+ return { connectionString: honourSystemRootCert({ connectionString, option: "connectionString" }) };
1162
+ }
1163
+ throw new VibeError7({
770
1164
  code: "VIBE_CONFIG",
771
1165
  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.",
772
1166
  meta: { adapter: "pg", reason: "missing-address" }
773
1167
  });
774
1168
  }
1169
+ function transactionsTakeOneRoundTrip(params) {
1170
+ const { address } = params;
1171
+ if (!batchDriverAvailable())
1172
+ return false;
1173
+ const config = "pool" in address ? address.pool.options ?? {} : { connectionString: address.connectionString };
1174
+ if (config.pipeline === true)
1175
+ return false;
1176
+ let timeout = config.query_timeout;
1177
+ const connectionString = config.connectionString;
1178
+ if (typeof connectionString === "string") {
1179
+ if (!URL.canParse(connectionString))
1180
+ return false;
1181
+ const fromUrl = new URL(connectionString).searchParams.get("query_timeout");
1182
+ if (fromUrl !== null)
1183
+ timeout = fromUrl;
1184
+ }
1185
+ timeout ||= pg3.defaults.query_timeout;
1186
+ return !(Number(timeout ?? 0) > 0);
1187
+ }
775
1188
  function pgAdapter(options) {
776
1189
  const address = takeAddress({ options });
777
- const cancellationAddress = options.cancellationConnectionString ?? ("connectionString" in address ? address.connectionString : undefined);
1190
+ const cancellationAddress = options.cancellationConnectionString !== undefined ? honourSystemRootCert({ connectionString: options.cancellationConnectionString, option: "cancellationConnectionString" }) : ("connectionString" in address) ? address.connectionString : undefined;
778
1191
  const readCancellation = cancellationAddress === undefined ? "before-dispatch" : "running";
779
1192
  const usePrepared = options?.preparedStatements ?? false;
780
1193
  const stmtCacheMax = options?.stmtCacheMax ?? 1000;
781
1194
  const planCacheMode = options?.planCacheMode === undefined ? "auto" : assertPlanCacheMode({ value: options.planCacheMode });
1195
+ const idleTimeout = "pool" in address || options?.idleTimeout === undefined ? undefined : assertIdleTimeout({ value: options.idleTimeout });
1196
+ if (options?.min !== undefined && "pool" in address) {
1197
+ throw new VibeError7({
1198
+ code: "VIBE_CONFIG",
1199
+ 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
+ meta: { adapter: "pg", reason: "pool-min-with-injected-pool", option: "min" }
1201
+ });
1202
+ }
1203
+ const poolMin = options?.min === undefined ? undefined : assertPoolMin({ value: options.min, max: options.max ?? 10 });
782
1204
  if (planCacheMode !== "auto" && "pool" in address) {
783
- throw new VibeError5({
1205
+ throw new VibeError7({
784
1206
  code: "VIBE_CONFIG",
785
1207
  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',
786
1208
  meta: { planCacheMode }
@@ -802,8 +1224,10 @@ function pgAdapter(options) {
802
1224
  const config = buildPoolConfig({
803
1225
  connectionString: address.connectionString,
804
1226
  max: options?.max ?? 10,
1227
+ ...poolMin === undefined ? {} : { min: poolMin },
805
1228
  ...options?.statementTimeout === undefined ? {} : { statementTimeout: options.statementTimeout },
806
1229
  ...options?.connectionTimeout === undefined ? {} : { connectionTimeout: options.connectionTimeout },
1230
+ ...idleTimeout === undefined ? {} : { idleTimeout },
807
1231
  planCacheMode
808
1232
  });
809
1233
  const PoolCtor = pg3.Pool;
@@ -850,16 +1274,29 @@ function pgAdapter(options) {
850
1274
  return { rows: final.rows, affectedRows: final.rowCount ?? 0 };
851
1275
  }
852
1276
  function createClientAdapter(params) {
853
- const { dispatch, scope, budget, deferred, cancellation } = params;
854
- const rawClient = { query: (config) => dispatch({ config, batchable: (config.values?.length ?? 0) > 0 }) };
1277
+ const { dispatch, scope, budget, deferred, cancellation, pipelines, lane, connection, final } = params;
1278
+ const laneOf = lane === undefined ? {} : { lane };
1279
+ const finalOf = () => {
1280
+ if (final === undefined)
1281
+ return {};
1282
+ if (final.used)
1283
+ throw finalStatementMisuse();
1284
+ final.used = true;
1285
+ return { commit: true, ...final.discardRows === true ? { discardRows: true } : {} };
1286
+ };
1287
+ const waitAsLane = (wait) => lane === undefined || pipelines === null ? wait : pipelines.waitAs({ lane, wait });
1288
+ const rawClient = {
1289
+ query: (config) => dispatch({ config, batchable: (config.values?.length ?? 0) > 0 || isSingleStatementText({ text: config.text }), ...laneOf, ...finalOf() })
1290
+ };
855
1291
  const savepointControl = (statement, cleanup) => {
856
- if (deferred !== null && deferred.routeSavepoint({ text: statement }) !== "send")
1292
+ if (deferred !== null && deferred.routeSavepoint({ text: statement, cleanup }) !== "send")
857
1293
  return;
858
1294
  return runMapped(() => dispatch({
859
1295
  config: { text: statement },
860
1296
  stage: "nested",
861
1297
  batchable: true,
862
- ...cleanup ? { cleanup: true } : {}
1298
+ ...cleanup ? { cleanup: true } : {},
1299
+ ...laneOf
863
1300
  }));
864
1301
  };
865
1302
  return {
@@ -869,6 +1306,74 @@ function pgAdapter(options) {
869
1306
  budgets: BUDGETS,
870
1307
  readCancellation: cancellation.support,
871
1308
  roundTrips: ROUND_TRIPS,
1309
+ readSnapshot: READ_SNAPSHOT,
1310
+ pipelining: pipelines === null ? "ordered" : PIPELINING,
1311
+ connectionIdentity: connection,
1312
+ ...params.topLevel !== true || deferred === null ? {} : {
1313
+ deferStatement: (statement) => {
1314
+ budget.assertUsable({ stage: "statement" });
1315
+ deferred.defer({ text: statement.text, values: prepareBatchValues({ values: statement.values }) });
1316
+ }
1317
+ },
1318
+ ...params.commits !== true || deferred === null || scope.depth !== 0 || final !== undefined ? {} : {
1319
+ withCommit: (options) => createClientAdapter({
1320
+ dispatch,
1321
+ scope,
1322
+ budget,
1323
+ deferred,
1324
+ cancellation,
1325
+ pipelines,
1326
+ ...laneOf,
1327
+ connection,
1328
+ final: { used: false, ...options?.rows === "discard" ? { discardRows: true } : {} }
1329
+ })
1330
+ },
1331
+ ...pipelines === null ? {} : {
1332
+ ...params.ordered === true ? { orderedPipeline: true } : {},
1333
+ pipeline: ({ lanes: count, ordered }) => {
1334
+ budget.assertUsable({ stage: "statement" });
1335
+ if (lane?.seat !== undefined)
1336
+ throw laneGroupMisuse({ message: "a sub-lane opens no pipeline scope" });
1337
+ if (lane?.group !== undefined)
1338
+ throw laneGroupMisuse({ message: "a lane opens no pipeline scope while its group is open" });
1339
+ 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 } });
1341
+ }
1342
+ if (ordered === true && params.ordered !== true) {
1343
+ throw new VibeError7({
1344
+ code: "VIBE_VALIDATION",
1345
+ 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
+ meta: { ordered: true, reason: "ordered-pipeline-undeclared" }
1347
+ });
1348
+ }
1349
+ const laneHandle = (opened) => ({
1350
+ adapter: createClientAdapter({ dispatch, scope, budget, deferred, cancellation, pipelines, lane: opened, connection, ...params.commits === true && opened.seat === undefined ? { commits: true } : {} }),
1351
+ close: () => {
1352
+ pipelines.close(opened);
1353
+ },
1354
+ ...opened.order === undefined ? {} : {
1355
+ follow: () => {
1356
+ budget.assertUsable({ stage: "statement" });
1357
+ return laneHandle(pipelines.follow(opened));
1358
+ }
1359
+ },
1360
+ ...opened.order !== undefined || opened.seat !== undefined || lane !== undefined ? {} : {
1361
+ group: (options) => {
1362
+ const opening = pipelines.group(opened, options?.final === true && params.commits === true);
1363
+ if (opening === undefined)
1364
+ return;
1365
+ return {
1366
+ lane: () => laneHandle(pipelines.seat(opening)),
1367
+ seal: () => {
1368
+ pipelines.seal(opening);
1369
+ }
1370
+ };
1371
+ }
1372
+ }
1373
+ });
1374
+ return (ordered === true ? pipelines.openOrdered(count) : pipelines.open(count)).map(laneHandle);
1375
+ }
1376
+ },
872
1377
  rowChanges: () => {
873
1378
  budget.assertUsable({ stage: "statement" });
874
1379
  return readRowChanges({ scope });
@@ -877,29 +1382,39 @@ function pgAdapter(options) {
877
1382
  return runReadExecution({ ...execParams, support: cancellation.support, provider: PROVIDER, run: async () => {
878
1383
  const turn = scopeTurn({ scope });
879
1384
  if (turn !== undefined)
880
- await turn;
1385
+ await waitAsLane(turn);
881
1386
  budget.assertUsable({ stage: "statement" });
882
1387
  assertReadExecution2({ ...execParams, support: cancellation.support, provider: PROVIDER, stage: "waiting-for-connection" });
883
- const queryable = { query: (config) => dispatch({ config, batchable: execParams.execution?.cancellation !== "running", execution: execParams.execution, onReadControl: execParams.onReadControl }) };
1388
+ const queryable = { query: (config) => dispatch({ config, batchable: execParams.execution?.cancellation !== "running", execution: execParams.execution, onReadControl: execParams.onReadControl, ...laneOf, ...finalOf() }) };
884
1389
  return runExecute({ queryable, scope, ...execParams }).catch((error) => abortTransactionOnServerFailure({ scope, error }));
885
1390
  } });
886
1391
  },
887
1392
  async executeUnsafe(execParams) {
888
1393
  const turn = scopeTurn({ scope });
889
1394
  if (turn !== undefined)
890
- await turn;
1395
+ await waitAsLane(turn);
891
1396
  budget.assertUsable({ stage: "statement" });
892
1397
  return runExecuteUnsafe({ queryable: rawClient, scope, ...execParams }).catch((error) => abortTransactionOnServerFailure({ scope, error }));
893
1398
  },
894
1399
  async transaction(fn, nestedOptions) {
895
1400
  refuseNestedTransactionOptions({ options: nestedOptions, provider: PROVIDER });
896
1401
  budget.assertUsable({ stage: "nested" });
1402
+ if (lane?.group !== undefined)
1403
+ throw laneGroupMisuse({ message: "a lane opens no savepoint while its group is open" });
1404
+ if (lane?.seat !== undefined)
1405
+ throw laneGroupMisuse({ message: "a sub-lane opens no savepoint" });
1406
+ pipelines?.flush();
1407
+ const leave = lane === undefined || pipelines === null ? ignore : pipelines.enter(lane);
897
1408
  return runNestedSavepoint({
898
1409
  scope,
899
1410
  execute: (statement) => savepointControl(statement, false),
900
1411
  cleanup: (statement) => savepointControl(statement, true),
901
- body: (child) => fn(createClientAdapter({ dispatch, scope: child, budget, deferred, cancellation }))
902
- });
1412
+ body: (child) => {
1413
+ leave();
1414
+ const ordered = lane === undefined && params.ordered === true ? { ordered: true } : {};
1415
+ return fn(createClientAdapter({ dispatch, scope: child, budget, deferred, cancellation, pipelines, connection, ...laneOf, ...ordered }));
1416
+ }
1417
+ }).finally(leave);
903
1418
  },
904
1419
  async connect() {
905
1420
  budget.assertUsable({ stage: "statement" });
@@ -1034,7 +1549,8 @@ function pgAdapter(options) {
1034
1549
  validateTransactionDeadline({
1035
1550
  options: transactionOptions,
1036
1551
  support: BUDGETS.transactionDeadline,
1037
- provider: PROVIDER
1552
+ provider: PROVIDER,
1553
+ settings: "transaction-local"
1038
1554
  });
1039
1555
  const openingOptions = {
1040
1556
  isolationLevel: transactionOptions?.isolationLevel,
@@ -1042,7 +1558,25 @@ function pgAdapter(options) {
1042
1558
  timeout: transactionOptions?.timeout
1043
1559
  };
1044
1560
  const openText = openTransactionStatement(openingOptions);
1561
+ const settings = readTransactionSettings({ settings: transactionOptions?.settings, provider: PROVIDER });
1562
+ const settingsStatement = settings.length === 0 ? undefined : renderTransactionSettings({ settings });
1045
1563
  const client = await runMapped(() => getPool().connect());
1564
+ const turns = createClientTurns();
1565
+ let gathering = null;
1566
+ let gatheringsPending = 0;
1567
+ const runTurn = (send) => {
1568
+ gathering = null;
1569
+ return turns.run(send);
1570
+ };
1571
+ const onClient = {
1572
+ query: (config) => runTurn(() => client.query(config)),
1573
+ get pipeline() {
1574
+ return client.pipeline;
1575
+ },
1576
+ get connectionParameters() {
1577
+ return client.connectionParameters;
1578
+ }
1579
+ };
1046
1580
  let connectionFailure;
1047
1581
  const unguard = guardBackgroundErrors({
1048
1582
  client,
@@ -1054,22 +1588,68 @@ function pgAdapter(options) {
1054
1588
  const scope = createSavepointScope();
1055
1589
  const budget = startTransactionBudget({ options: transactionOptions, provider: PROVIDER });
1056
1590
  const capable = supportsBatch({ client });
1057
- const deferOpening = capable && (openingOptions.timeout === undefined || openingOptions.timeout <= MAX_STATEMENT_TIMEOUT_MS);
1591
+ const deferOpening = (settingsStatement === undefined || transactionOptions?.deferSettings === true) && capable && (openingOptions.timeout === undefined || openingOptions.timeout <= MAX_STATEMENT_TIMEOUT_MS);
1058
1592
  const opening = deferOpening ? [beginStatement(openingOptions), ...openingOptions.timeout === undefined ? [] : [statementTimeoutStatement({ timeout: openingOptions.timeout })]] : [];
1059
- const deferred = createDeferredControl({ opening });
1593
+ const deferred = createDeferredControl({ opening, ...budget.totalMs === null ? { deferReleases: true } : {} });
1594
+ const settingsDeferred = deferOpening && settingsStatement !== undefined;
1595
+ if (settingsDeferred)
1596
+ deferred.defer({ text: settingsStatement.text, values: prepareBatchValues({ values: settingsStatement.values }) });
1597
+ const openingLength = opening.length + (settingsDeferred ? 1 : 0);
1598
+ const openingStepError = (index, error) => {
1599
+ const mapped = mapPgDriverError({ error });
1600
+ if (!settingsDeferred || index !== opening.length || typeof mapped.meta.sqlstate !== "string")
1601
+ return mapped;
1602
+ return transactionSettingRefusedError({ provider: PROVIDER, settings, setting: findRefusedSetting({ settings, cause: mapped }), cause: mapped, deferred: true });
1603
+ };
1060
1604
  let openingFailure;
1061
1605
  let inFlight = null;
1606
+ const commits = capable && budget.totalMs === null && !((client.connectionParameters?.query_timeout ?? 0) > 0);
1607
+ let ridden;
1608
+ let ordinaryOpen = 0;
1609
+ const ridesNow = () => commits && ridden === undefined && !cancellationQueue && !cancellationAttempted && connectionFailure === undefined && openingFailure === undefined && scope.state.open === null && scope.state.aborted === null && scope.state.poisoned === null;
1610
+ const settleRidden = (entry, outcome, commitIndex) => {
1611
+ const step = outcome.steps[commitIndex];
1612
+ const answeredError = step?.status === "failed" ? mapPgDriverError({ error: step.error }) : undefined;
1613
+ if (step === undefined || answeredError !== undefined && isConnectionLoss({ error: answeredError })) {
1614
+ commitProgress = "unanswered";
1615
+ discard = true;
1616
+ budget.close({ outcome: "unknown" });
1617
+ entry.state = "lost";
1618
+ entry.error = commitOutcomeUnknownError({ cause: answeredError ?? outcome.transportError ?? firstBatchFailure({ outcome })?.error });
1619
+ } else if (step.status === "skipped") {
1620
+ commitProgress = "not-sent";
1621
+ entry.state = "skipped";
1622
+ } else if (step.status === "failed" || step.result.command === "ROLLBACK") {
1623
+ commitProgress = "answered";
1624
+ entry.state = "failed";
1625
+ entry.error = answeredError ?? transactionAbortedError2({ cause: scope.state.aborted?.cause });
1626
+ } else {
1627
+ entry.state = "committed";
1628
+ }
1629
+ };
1630
+ const trackRidden = (batch, commitIndex) => {
1631
+ commitProgress = "sent";
1632
+ const entry = { settled: Promise.resolve(), state: "in-flight" };
1633
+ entry.settled = batch.then((outcome) => {
1634
+ settleRidden(entry, outcome, commitIndex);
1635
+ }, (error) => {
1636
+ settleRidden(entry, { steps: [], transportError: error }, commitIndex);
1637
+ });
1638
+ ridden = entry;
1639
+ };
1062
1640
  const sendWithControl = (dispatchParams) => {
1063
1641
  const { config, cleanup } = dispatchParams;
1064
1642
  const handOver = () => {
1065
1643
  assertReadExecution2({ execution: dispatchParams.execution, effect: "read", support: readCancellation, provider: PROVIDER, stage: "waiting-for-connection" });
1066
1644
  dispatchParams.sent?.();
1067
- return cancellation.run({ config, execution: dispatchParams.execution, onReadControl: dispatchParams.onReadControl, send: () => client.query(config) });
1645
+ return cancellation.run({ config, execution: dispatchParams.execution, onReadControl: dispatchParams.onReadControl, send: () => onClient.query(config) });
1068
1646
  };
1069
1647
  if (cleanup !== true && cancellationAttempted)
1070
- throw transactionAbortedError({ cause: readCancelledError2({ stage: "running" }) });
1648
+ throw transactionAbortedError2({ cause: readCancelledError2({ stage: "running" }) });
1071
1649
  if (cleanup !== true && openingFailure !== undefined)
1072
- throw transactionAbortedError({ cause: openingFailure });
1650
+ throw transactionAbortedError2({ cause: openingFailure });
1651
+ if (cleanup !== true && ridden !== undefined)
1652
+ throw commitSentRefusal();
1073
1653
  let bound;
1074
1654
  if (cleanup !== true && budget.totalMs !== null) {
1075
1655
  if (scope.state.poisoned !== null)
@@ -1083,30 +1663,45 @@ function pgAdapter(options) {
1083
1663
  if (bound === undefined)
1084
1664
  return handOver();
1085
1665
  const stage = dispatchParams.stage ?? "statement";
1086
- return runMapped(() => client.query({ text: bound })).then(() => {
1666
+ return runMapped(() => onClient.query({ text: bound })).then(() => {
1087
1667
  budget.assertUsable({ stage });
1088
1668
  return handOver();
1089
1669
  });
1090
1670
  }
1091
1671
  const pendingControl = cleanup !== true && deferred.hasPending();
1092
- if (!pendingControl && bound === undefined)
1672
+ const rides = dispatchParams.commit === true && cleanup !== true && dispatchParams.batchable === true && bound === undefined && ordinaryOpen === 1 && ridesNow();
1673
+ if (!pendingControl && bound === undefined && !rides) {
1674
+ if ((gathering?.phase === "turn" || turns.outstanding() > 0) && gathers(dispatchParams) && deferred.opened())
1675
+ return gatherInTurn(dispatchParams);
1093
1676
  return handOver();
1094
- const statement = dispatchParams.batchable === true ? [{ text: config.text, values: prepareBatchValues({ values: config.values ?? [] }) }] : [];
1677
+ }
1678
+ const statement = dispatchParams.batchable === true ? [{ text: config.text, values: prepareBatchValues({ values: config.values ?? [] }), ...rides && dispatchParams.discardRows === true ? { discardRows: true } : {} }] : [];
1679
+ assertReadExecution2({ execution: dispatchParams.execution, effect: "read", support: readCancellation, provider: PROVIDER, stage: "waiting-for-connection" });
1095
1680
  const carriesOpening = !deferred.opened();
1096
- const control = pendingControl ? [...deferred.take()] : [];
1681
+ const control = pendingControl ? [...deferred.takeSteps()] : [];
1097
1682
  if (bound !== undefined)
1098
- control.push(bound);
1099
- const openingSteps = carriesOpening ? opening.length : 0;
1100
- assertReadExecution2({ execution: dispatchParams.execution, effect: "read", support: readCancellation, provider: PROVIDER, stage: "waiting-for-connection" });
1683
+ control.push({ text: bound });
1684
+ const openingSteps = carriesOpening ? openingLength : 0;
1101
1685
  if (statement.length > 0)
1102
1686
  dispatchParams.sent?.();
1103
- const burst = runBatch({ client, steps: [...control.map((text) => ({ text })), ...statement], types: EXACT_TYPES }).then((outcome) => {
1687
+ const commitIndex = control.length + statement.length;
1688
+ const batch = runTurn(() => runBatch({ client, steps: [...control, ...statement, ...rides ? [COMMIT_STEP] : []], types: EXACT_TYPES }));
1689
+ if (rides)
1690
+ trackRidden(batch, commitIndex);
1691
+ const burst = batch.then((received) => {
1692
+ const outcome = rides ? beforeCommit({ outcome: received, commitIndex }) : received;
1104
1693
  const failure = firstBatchFailure({ outcome });
1105
1694
  if (failure !== undefined) {
1106
- if (failure.index < openingSteps || outcome.transportError !== undefined && openingSteps > 0) {
1107
- openingFailure = mapPgDriverError({ error: failure.error });
1695
+ const openingFailed = failure.index < openingSteps || outcome.transportError !== undefined && openingSteps > 0;
1696
+ if (openingFailed) {
1697
+ openingFailure = openingStepError(failure.index, failure.error);
1108
1698
  }
1109
- throw failure.error;
1699
+ if (gatheringsPending > 0) {
1700
+ const mapped = openingFailed && openingFailure !== undefined ? openingFailure : mapPgDriverError({ error: failure.error });
1701
+ if (typeof mapped.meta.sqlstate === "string")
1702
+ markTransactionAborted({ scope, cause: mapped });
1703
+ }
1704
+ throw openingFailed && settingsDeferred && failure.index === opening.length && openingFailure !== undefined ? openingFailure : failure.error;
1110
1705
  }
1111
1706
  if (statement.length === 0)
1112
1707
  return handOver();
@@ -1122,35 +1717,564 @@ function pgAdapter(options) {
1122
1717
  }
1123
1718
  return burst;
1124
1719
  };
1125
- const send = (dispatchParams) => {
1720
+ const guarded = (run) => {
1126
1721
  try {
1127
- return sendWithControl(dispatchParams);
1722
+ return run();
1128
1723
  } catch (error) {
1129
1724
  return Promise.reject(error);
1130
1725
  }
1131
1726
  };
1727
+ const send = (dispatchParams) => guarded(() => sendWithControl(dispatchParams));
1132
1728
  let tail = Promise.resolve();
1133
1729
  const activeDispatches = new Set;
1134
1730
  let cancellationQueue = false;
1731
+ const lanes = new Set;
1732
+ let queued = [];
1733
+ const settleQueued = (entry, outcome) => {
1734
+ if (entry.settled)
1735
+ return;
1736
+ entry.settled = true;
1737
+ entry.lane.waiting -= 1;
1738
+ if ("result" in outcome) {
1739
+ entry.resolve(outcome.result);
1740
+ } else {
1741
+ entry.failure = outcome.error;
1742
+ entry.reject(outcome.error);
1743
+ }
1744
+ };
1745
+ const distributeBurst = (burst) => {
1746
+ const { outcome, live, controlSteps, openingSteps } = burst;
1747
+ const failure = firstBatchFailure({ outcome });
1748
+ if (failure === undefined) {
1749
+ live.forEach((entry, index) => {
1750
+ const step = outcome.steps[controlSteps + index];
1751
+ settleQueued(entry, { result: step?.status === "ok" ? step.result : { rows: [], rowCount: 0 } });
1752
+ });
1753
+ return;
1754
+ }
1755
+ const mapped = failure.index < openingSteps ? openingStepError(failure.index, failure.error) : mapPgDriverError({ error: failure.error });
1756
+ if (failure.index < openingSteps || outcome.transportError !== undefined && openingSteps > 0)
1757
+ openingFailure = mapped;
1758
+ if (typeof mapped.meta.sqlstate === "string")
1759
+ markTransactionAborted({ scope, cause: mapped });
1760
+ const answered = outcome.transportError === undefined;
1761
+ if (failure.index < controlSteps) {
1762
+ const later = answered || openingFailure !== undefined ? transactionAbortedError2({ cause: openingFailure ?? mapped }) : mapped;
1763
+ live.forEach((entry, index) => {
1764
+ settleQueued(entry, { error: index === 0 ? mapped : later });
1765
+ });
1766
+ return;
1767
+ }
1768
+ let abortCause = mapped;
1769
+ live.forEach((entry, index) => {
1770
+ const position = controlSteps + index;
1771
+ const step = outcome.steps[position];
1772
+ if (step === undefined) {
1773
+ settleQueued(entry, { error: position === failure.index || openingFailure === undefined ? mapped : transactionAbortedError2({ cause: openingFailure }) });
1774
+ } else if (step.status === "ok") {
1775
+ settleQueued(entry, { result: step.result });
1776
+ } else if (step.status === "failed") {
1777
+ const error = position === failure.index ? mapped : mapPgDriverError({ error: step.error });
1778
+ if (typeof error.meta.sqlstate === "string")
1779
+ abortCause = error;
1780
+ settleQueued(entry, { error });
1781
+ } else {
1782
+ settleQueued(entry, { error: transactionAbortedError2({ cause: abortCause }) });
1783
+ }
1784
+ });
1785
+ };
1786
+ const sendRefusal = () => ridden !== undefined ? commitSentRefusal() : cancellationAttempted ? transactionAbortedError2({ cause: readCancelledError2({ stage: "running" }) }) : openingFailure !== undefined ? transactionAbortedError2({ cause: openingFailure }) : scope.state.poisoned ?? (scope.state.aborted === null ? undefined : transactionAbortedError2({ cause: scope.state.aborted.cause }));
1787
+ const ridesQueued = (live) => {
1788
+ const last = live.at(-1);
1789
+ if (last === undefined || last.commit !== true && last.lane.seat?.group.final !== true || queued.length > 0 || inFlight !== null || ordinaryOpen > 0 || !ridesNow())
1790
+ return false;
1791
+ const counts = new Map;
1792
+ for (const entry of live) {
1793
+ if (entry.commit !== true && entry.lane.order?.followed !== true && entry.lane.seat?.group.final !== true)
1794
+ return false;
1795
+ counts.set(entry.lane, (counts.get(entry.lane) ?? 0) + 1);
1796
+ }
1797
+ for (const open of lanes) {
1798
+ const count = counts.get(open) ?? 0;
1799
+ if (count > 0 && open.waiting === count)
1800
+ continue;
1801
+ const group = open.group;
1802
+ if (count === 0 && group?.final === true && group.sealed && open.waiting === 1 && live.some((entry) => entry.lane.seat?.group === group))
1803
+ continue;
1804
+ if (count === 0 && open.final === true && open.waiting === 0)
1805
+ continue;
1806
+ return false;
1807
+ }
1808
+ return true;
1809
+ };
1810
+ const sendQueued = (entries, options) => {
1811
+ const inTurn = options?.inTurn === true;
1812
+ const refusal = sendRefusal();
1813
+ const live = [];
1814
+ for (const entry of entries) {
1815
+ if (refusal !== undefined)
1816
+ settleQueued(entry, { error: refusal });
1817
+ else if (entry.execution?.signal?.aborted === true)
1818
+ settleQueued(entry, { error: readCancelledError2({ stage: "waiting-for-connection" }) });
1819
+ else
1820
+ live.push(entry);
1821
+ }
1822
+ if (live.length === 0)
1823
+ return Promise.resolve();
1824
+ const carriesOpening = !inTurn && !deferred.opened();
1825
+ const rides = !inTurn && live.length === entries.length && ridesQueued(live);
1826
+ const control = inTurn ? [] : deferred.takeSteps();
1827
+ const openingSteps = carriesOpening ? openingLength : 0;
1828
+ 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 });
1830
+ const batch = inTurn ? new Promise((resolve) => {
1831
+ resolve(sendBatch());
1832
+ }) : runTurn(sendBatch);
1833
+ if (rides)
1834
+ trackRidden(batch, commitIndex);
1835
+ const burst = batch.then((received) => {
1836
+ distributeBurst({ outcome: rides ? beforeCommit({ outcome: received, commitIndex }) : received, live, controlSteps: control.length, openingSteps });
1837
+ }).then(undefined, (error) => {
1838
+ if (openingSteps > 0)
1839
+ openingFailure ??= mapPgDriverError({ error });
1840
+ for (const entry of live)
1841
+ settleQueued(entry, { error });
1842
+ });
1843
+ if (carriesOpening) {
1844
+ const settled = burst.then(() => {
1845
+ if (inFlight === settled)
1846
+ inFlight = null;
1847
+ });
1848
+ inFlight = settled;
1849
+ }
1850
+ return burst;
1851
+ };
1852
+ const gathers = (dispatchParams) => dispatchParams.commit !== true && coalescable(dispatchParams);
1853
+ const closeGathering = () => {
1854
+ gathering = null;
1855
+ };
1856
+ const gatheredLane = { open: false, waiting: 0 };
1857
+ const joinGathering = (group, dispatchParams, values) => new Promise((resolve, reject) => {
1858
+ group.members.push({ params: dispatchParams, values, resolve, reject });
1859
+ });
1860
+ const sendGathered = (members, options) => sendQueued(members.map((member) => {
1861
+ gatheredLane.waiting += 1;
1862
+ return {
1863
+ text: member.params.config.text,
1864
+ values: member.values,
1865
+ ...member.params.execution === undefined || options?.inTurn === true ? {} : { execution: member.params.execution },
1866
+ lane: gatheredLane,
1867
+ resolve: member.resolve,
1868
+ reject: member.reject,
1869
+ settled: false
1870
+ };
1871
+ }), options);
1872
+ const gatherBehind = (dispatchParams, behind) => {
1873
+ const values = prepareBatchValues({ values: dispatchParams.config.values ?? [] });
1874
+ const open = gathering;
1875
+ if (open !== null && open.phase === "opening" && open.behind === behind)
1876
+ return joinGathering(open, dispatchParams, values);
1877
+ const group = { phase: "opening", behind, members: [] };
1878
+ gathering = group;
1879
+ gatheringsPending += 1;
1880
+ const joined = joinGathering(group, dispatchParams, values);
1881
+ behind.then(() => {
1882
+ if (gathering === group)
1883
+ gathering = null;
1884
+ gatheringsPending -= 1;
1885
+ const [only, ...more] = group.members;
1886
+ if (only !== undefined && more.length === 0) {
1887
+ send(only.params).then(only.resolve, only.reject);
1888
+ return;
1889
+ }
1890
+ sendGathered(group.members);
1891
+ });
1892
+ return joined;
1893
+ };
1894
+ const gatherInTurn = (dispatchParams) => {
1895
+ assertReadExecution2({ execution: dispatchParams.execution, effect: "read", support: readCancellation, provider: PROVIDER, stage: "waiting-for-connection" });
1896
+ const values = prepareBatchValues({ values: dispatchParams.config.values ?? [] });
1897
+ const open = gathering;
1898
+ if (open !== null && open.phase === "turn")
1899
+ return joinGathering(open, dispatchParams, values);
1900
+ const group = { phase: "turn", members: [] };
1901
+ gatheringsPending += 1;
1902
+ const joined = joinGathering(group, dispatchParams, values);
1903
+ turns.run(() => sendTurnGroup(group));
1904
+ gathering = group;
1905
+ return joined;
1906
+ };
1907
+ const sendTurnGroup = (group) => {
1908
+ if (gathering === group)
1909
+ gathering = null;
1910
+ gatheringsPending -= 1;
1911
+ const [only, ...more] = group.members;
1912
+ if (only !== undefined && more.length === 0) {
1913
+ const query = new Promise((resolve) => {
1914
+ resolve(client.query(only.params.config));
1915
+ });
1916
+ query.then(only.resolve, only.reject);
1917
+ return query;
1918
+ }
1919
+ const prior = turns.lastFailure();
1920
+ if (prior !== undefined) {
1921
+ const mapped = mapPgDriverError({ error: prior.error });
1922
+ if (typeof mapped.meta.sqlstate === "string")
1923
+ markTransactionAborted({ scope, cause: mapped });
1924
+ }
1925
+ return sendGathered(group.members, { inTurn: true });
1926
+ };
1927
+ const sendAlone = (entry, alone) => {
1928
+ const refusal = sendRefusal();
1929
+ if (refusal !== undefined) {
1930
+ settleQueued(entry, { error: refusal });
1931
+ return Promise.resolve();
1932
+ }
1933
+ return dispatchOrdinary(alone).then((result) => {
1934
+ settleQueued(entry, { result });
1935
+ }, (error) => {
1936
+ const mapped = mapPgDriverError({ error });
1937
+ if (typeof mapped.meta.sqlstate === "string")
1938
+ markTransactionAborted({ scope, cause: mapped });
1939
+ settleQueued(entry, { error });
1940
+ });
1941
+ };
1942
+ const prefixOf = (lane) => {
1943
+ const order = lane.order;
1944
+ if (order === undefined)
1945
+ return "issued";
1946
+ let state = "issued";
1947
+ for (let index = 0;index < order.index; index++) {
1948
+ const earlier = order.scope.lanes[index];
1949
+ if (earlier.order.issued > 0)
1950
+ continue;
1951
+ if (!earlier.open)
1952
+ return "closed";
1953
+ state = "open";
1954
+ }
1955
+ return state;
1956
+ };
1957
+ const refuseOrphaned = () => {
1958
+ if (!queued.some((entry) => prefixOf(entry.lane) === "closed"))
1959
+ return;
1960
+ const kept = [];
1961
+ for (const entry of queued) {
1962
+ if (prefixOf(entry.lane) === "closed")
1963
+ settleQueued(entry, { error: predecessorClosedError() });
1964
+ else
1965
+ kept.push(entry);
1966
+ }
1967
+ queued = kept;
1968
+ };
1969
+ const orderedSend = (entries, send) => () => {
1970
+ const live = [];
1971
+ for (const entry of entries) {
1972
+ const failed = entry.lane.order?.scope.failed;
1973
+ if (failed === undefined)
1974
+ live.push(entry);
1975
+ else
1976
+ settleQueued(entry, { error: sendRefusal() ?? transactionAbortedError2({ cause: failed.cause }) });
1977
+ }
1978
+ if (live.length === 0)
1979
+ return Promise.resolve();
1980
+ return send(live).then(() => {
1981
+ for (const entry of live) {
1982
+ const order = entry.lane.order;
1983
+ if (order !== undefined && entry.failure !== undefined)
1984
+ order.scope.failed ??= { cause: mapPgDriverError({ error: entry.failure }) };
1985
+ }
1986
+ });
1987
+ };
1988
+ let orderedTail = null;
1989
+ const flushOrdered = () => {
1990
+ refuseOrphaned();
1991
+ const ready = [];
1992
+ const held = [];
1993
+ for (const entry of queued)
1994
+ (prefixOf(entry.lane) === "issued" ? ready : held).push(entry);
1995
+ queued = held;
1996
+ if (ready.length === 0)
1997
+ return;
1998
+ ready.sort((first, second) => (first.lane.order?.index ?? 0) - (second.lane.order?.index ?? 0));
1999
+ const sends = [];
2000
+ let run = [];
2001
+ for (const entry of ready) {
2002
+ const alone = entry.alone;
2003
+ if (alone === undefined) {
2004
+ run.push(entry);
2005
+ continue;
2006
+ }
2007
+ if (run.length > 0) {
2008
+ sends.push(orderedSend(run, sendQueued));
2009
+ run = [];
2010
+ }
2011
+ sends.push(orderedSend([entry], () => sendAlone(entry, alone)));
2012
+ }
2013
+ if (run.length > 0)
2014
+ sends.push(orderedSend(run, sendQueued));
2015
+ const before = [orderedTail, inFlight].filter((wait) => wait !== null);
2016
+ let chain = before.length === 0 ? sends.shift()() : Promise.all(before).then(ignore);
2017
+ for (const next of sends)
2018
+ chain = chain.then(next);
2019
+ const tail = chain;
2020
+ orderedTail = tail;
2021
+ tail.then(() => {
2022
+ if (orderedTail === tail)
2023
+ orderedTail = null;
2024
+ });
2025
+ };
2026
+ const flushQueue = () => {
2027
+ if (queued.length === 0)
2028
+ return;
2029
+ closeGathering();
2030
+ if (queued.some((entry) => entry.lane.order !== undefined)) {
2031
+ flushOrdered();
2032
+ return;
2033
+ }
2034
+ const entries = queued.some((entry) => entry.lane.seat !== undefined) ? seatOrder(queued) : queued;
2035
+ queued = [];
2036
+ if (inFlight === null)
2037
+ sendQueued(entries);
2038
+ else
2039
+ inFlight.then(() => {
2040
+ sendQueued(entries);
2041
+ });
2042
+ };
2043
+ const flushIfReady = () => {
2044
+ if (queued.length === 0)
2045
+ return;
2046
+ for (const open of lanes)
2047
+ if (open.waiting === 0)
2048
+ return;
2049
+ flushQueue();
2050
+ };
2051
+ const seatOrder = (entries) => [...entries].sort((first, second) => (first.lane.seat?.index ?? -1) - (second.lane.seat?.index ?? -1));
2052
+ const endGroupIfOver = (opening) => {
2053
+ if (!opening.sealed || opening.live > 0 || opening.parent.group !== opening)
2054
+ return;
2055
+ opening.parent.group = undefined;
2056
+ opening.parent.waiting -= 1;
2057
+ };
2058
+ const closeLane = (lane) => {
2059
+ if (!lane.open)
2060
+ return;
2061
+ lane.open = false;
2062
+ lanes.delete(lane);
2063
+ if (lane.order !== undefined)
2064
+ refuseOrphaned();
2065
+ if (lane.seat !== undefined) {
2066
+ lane.seat.group.live -= 1;
2067
+ endGroupIfOver(lane.seat.group);
2068
+ }
2069
+ flushIfReady();
2070
+ };
2071
+ const pipelines = {
2072
+ open: (count) => Array.from({ length: count }, () => {
2073
+ const opened = { open: true, waiting: 0 };
2074
+ lanes.add(opened);
2075
+ return opened;
2076
+ }),
2077
+ openOrdered: (count) => {
2078
+ const ordered = { lanes: [] };
2079
+ for (let index = 0;index < count; index++) {
2080
+ const opened = { open: true, waiting: 0, order: { scope: ordered, index, issued: 0, followed: false } };
2081
+ ordered.lanes.push(opened);
2082
+ lanes.add(opened);
2083
+ }
2084
+ return [...ordered.lanes];
2085
+ },
2086
+ follow: (lane) => {
2087
+ const order = lane.order;
2088
+ if (order === undefined)
2089
+ throw orderedLaneMisuse({ message: "follow() exists only on the lanes of an ordered scope" });
2090
+ if (!lane.open)
2091
+ throw orderedLaneMisuse({ message: "follow() on a closed lane" });
2092
+ if (order.followed || order.scope.lanes.at(-1) !== lane)
2093
+ throw orderedLaneMisuse({ message: "follow() opens one lane after the LAST lane of its scope" });
2094
+ if (order.issued > 0)
2095
+ throw orderedLaneMisuse({ message: "follow() must come before the lane issues its statement" });
2096
+ order.followed = true;
2097
+ const next = { open: true, waiting: 0, order: { scope: order.scope, index: order.scope.lanes.length, issued: 0, followed: false } };
2098
+ order.scope.lanes.push(next);
2099
+ lanes.add(next);
2100
+ return next;
2101
+ },
2102
+ close: closeLane,
2103
+ waitAs: ({ lane, wait }) => {
2104
+ if (!lane.open)
2105
+ return wait;
2106
+ lane.waiting += 1;
2107
+ flushIfReady();
2108
+ return wait.then((value) => {
2109
+ lane.waiting -= 1;
2110
+ return value;
2111
+ }, (error) => {
2112
+ lane.waiting -= 1;
2113
+ throw error;
2114
+ });
2115
+ },
2116
+ enter: (lane) => {
2117
+ if (!lane.open)
2118
+ return ignore;
2119
+ lane.waiting += 1;
2120
+ flushIfReady();
2121
+ let left = false;
2122
+ return () => {
2123
+ if (left)
2124
+ return;
2125
+ left = true;
2126
+ lane.waiting -= 1;
2127
+ };
2128
+ },
2129
+ flush: flushQueue,
2130
+ group: (lane, final) => {
2131
+ if (!lane.open || lane.order !== undefined || lane.seat !== undefined || lane.gate === true || lane.group !== undefined || lane.final === true)
2132
+ return;
2133
+ if (!capable || budget.totalMs !== null || cancellationQueue || sendRefusal() !== undefined)
2134
+ return;
2135
+ if ((client.connectionParameters?.query_timeout ?? 0) > 0)
2136
+ return;
2137
+ for (const other of lanes) {
2138
+ if (other !== lane && (other.final !== true || other.waiting > 0))
2139
+ return;
2140
+ }
2141
+ const gate = { open: true, waiting: 0, gate: true };
2142
+ const opening = { parent: lane, gate, opened: 0, live: 0, sealed: false, final };
2143
+ lanes.add(gate);
2144
+ lane.group = opening;
2145
+ lane.waiting += 1;
2146
+ return opening;
2147
+ },
2148
+ seat: (opening) => {
2149
+ if (opening.sealed)
2150
+ throw laneGroupMisuse({ message: "a sub-lane is opened only before the group is sealed" });
2151
+ if (!opening.parent.open)
2152
+ throw laneGroupMisuse({ message: "a sub-lane of a closed lane" });
2153
+ const seat = { open: true, waiting: 0, seat: { group: opening, index: opening.opened, issued: false } };
2154
+ opening.opened += 1;
2155
+ opening.live += 1;
2156
+ lanes.add(seat);
2157
+ return seat;
2158
+ },
2159
+ seal: (opening) => {
2160
+ if (opening.sealed)
2161
+ return;
2162
+ opening.sealed = true;
2163
+ endGroupIfOver(opening);
2164
+ closeLane(opening.gate);
2165
+ }
2166
+ };
2167
+ const endPipeline = () => {
2168
+ for (const open of lanes)
2169
+ open.open = false;
2170
+ lanes.clear();
2171
+ flushQueue();
2172
+ };
2173
+ const enqueue = (dispatchParams, lane, alone) => {
2174
+ if (cancellationAttempted)
2175
+ throw transactionAbortedError2({ cause: readCancelledError2({ stage: "running" }) });
2176
+ if (openingFailure !== undefined)
2177
+ throw transactionAbortedError2({ cause: openingFailure });
2178
+ if (ridden !== undefined)
2179
+ throw commitSentRefusal();
2180
+ assertReadExecution2({ execution: dispatchParams.execution, effect: "read", support: readCancellation, provider: PROVIDER, stage: "waiting-for-connection" });
2181
+ const values = alone === true ? [] : prepareBatchValues({ values: dispatchParams.config.values ?? [] });
2182
+ const pending = new Promise((resolve, reject) => {
2183
+ lane.waiting += 1;
2184
+ queued.push({
2185
+ text: dispatchParams.config.text,
2186
+ values,
2187
+ ...dispatchParams.execution === undefined ? {} : { execution: dispatchParams.execution },
2188
+ ...alone === true ? { alone: dispatchParams } : {},
2189
+ ...dispatchParams.commit === true ? { commit: true } : {},
2190
+ lane,
2191
+ resolve,
2192
+ reject,
2193
+ settled: false
2194
+ });
2195
+ });
2196
+ if (lane.order !== undefined)
2197
+ lane.order.issued += 1;
2198
+ activeDispatches.add(pending);
2199
+ pending.then(() => activeDispatches.delete(pending), () => activeDispatches.delete(pending));
2200
+ flushIfReady();
2201
+ return pending;
2202
+ };
2203
+ 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";
1135
2204
  const dispatch = (dispatchParams) => {
2205
+ const lane = dispatchParams.lane?.open === true ? dispatchParams.lane : undefined;
2206
+ if (lane !== undefined && dispatchParams.cleanup !== true) {
2207
+ if (lane.group !== undefined)
2208
+ return Promise.reject(laneGroupMisuse({ message: "a lane issues nothing while its group is open" }));
2209
+ const seat = lane.seat;
2210
+ if (seat !== undefined) {
2211
+ if (seat.issued)
2212
+ return Promise.reject(laneGroupMisuse({ message: "a sub-lane issues ONE statement" }));
2213
+ if (!coalescable(dispatchParams))
2214
+ return Promise.reject(laneGroupMisuse({ message: "a sub-lane statement must be able to join a batch" }));
2215
+ seat.issued = true;
2216
+ }
2217
+ if (dispatchParams.commit === true)
2218
+ lane.final = true;
2219
+ }
2220
+ const order = lane?.order;
2221
+ if (lane !== undefined && order !== undefined && dispatchParams.cleanup !== true) {
2222
+ return guarded(() => {
2223
+ if (order.followed && order.issued > 0)
2224
+ throw orderedLaneMisuse({ message: "a lane that has a follower may issue ONE statement" });
2225
+ return coalescable(dispatchParams) ? enqueue(dispatchParams, lane) : enqueue(dispatchParams, lane, true);
2226
+ });
2227
+ }
2228
+ if (lane !== undefined && coalescable(dispatchParams))
2229
+ return guarded(() => enqueue(dispatchParams, lane));
2230
+ if (lane === undefined)
2231
+ return dispatchOrdinary(dispatchParams);
2232
+ const leave = pipelines.enter(lane);
2233
+ flushQueue();
2234
+ return dispatchOrdinary(dispatchParams).then((result) => {
2235
+ leave();
2236
+ return result;
2237
+ }, (error) => {
2238
+ leave();
2239
+ throw error;
2240
+ });
2241
+ };
2242
+ const dispatchOrdinary = (dispatchParams) => {
1136
2243
  if (dispatchParams.execution?.cancellation === "running" && !cancellationQueue) {
2244
+ flushQueue();
1137
2245
  cancellationQueue = true;
2246
+ closeGathering();
1138
2247
  tail = Promise.allSettled([...activeDispatches]);
1139
2248
  }
1140
2249
  if (budget.totalMs === null && !cancellationQueue) {
1141
- const pending = inFlight === null ? send(dispatchParams) : inFlight.then(() => send(dispatchParams));
2250
+ ordinaryOpen += 1;
2251
+ const behind = gathering?.phase === "opening" ? gathering.behind ?? null : inFlight;
2252
+ let pending;
2253
+ if (behind === null)
2254
+ pending = send(dispatchParams);
2255
+ else if (gathers(dispatchParams))
2256
+ pending = guarded(() => gatherBehind(dispatchParams, behind));
2257
+ else {
2258
+ closeGathering();
2259
+ pending = behind.then(() => send(dispatchParams));
2260
+ }
1142
2261
  activeDispatches.add(pending);
1143
- pending.then(() => activeDispatches.delete(pending), () => activeDispatches.delete(pending));
2262
+ const settle = () => {
2263
+ activeDispatches.delete(pending);
2264
+ ordinaryOpen -= 1;
2265
+ };
2266
+ pending.then(settle, settle);
1144
2267
  return pending;
1145
2268
  }
1146
2269
  const pending = tail.then(() => send(dispatchParams));
1147
2270
  tail = pending.then(ignore, ignore);
2271
+ closeGathering();
1148
2272
  return pending;
1149
2273
  };
1150
2274
  let discard = false;
1151
2275
  let released = false;
1152
2276
  let cancellationAttempted = false;
1153
- const cancellation = createPgReadCancellation({ connectionString: cancellationAddress, target: client, terminate: () => {
2277
+ const cancellation = createPgReadCancellation({ connectionString: cancellationAddress, target: onClient, terminate: () => {
1154
2278
  if (!released) {
1155
2279
  released = true;
1156
2280
  client.release(true);
@@ -1161,20 +2285,77 @@ function pgAdapter(options) {
1161
2285
  markTransactionAborted({ scope, cause: readCancelledError2({ stage: "running" }) });
1162
2286
  } });
1163
2287
  let commitProgress = "not-sent";
2288
+ let openingRollback;
2289
+ const openWithSettings = async (statement) => {
2290
+ let failed;
2291
+ if (capable) {
2292
+ const control = [beginStatement(openingOptions), ...openingOptions.timeout === undefined ? [] : [statementTimeoutStatement({ timeout: openingOptions.timeout })]];
2293
+ const outcome = await turns.run(() => runBatch({
2294
+ client,
2295
+ steps: [...control.map((text) => ({ text })), { text: statement.text, values: prepareBatchValues({ values: statement.values }) }],
2296
+ types: EXACT_TYPES
2297
+ }));
2298
+ const failure = firstBatchFailure({ outcome });
2299
+ if (failure === undefined)
2300
+ return;
2301
+ if (failure.index < control.length || outcome.transportError !== undefined)
2302
+ throw mapPgDriverError({ error: failure.error });
2303
+ failed = { error: failure.error };
2304
+ } else {
2305
+ await runMapped(() => onClient.query({ text: openText }));
2306
+ failed = await onClient.query({ text: statement.text, values: [...statement.values] }).then(() => {
2307
+ return;
2308
+ }, (error) => ({ error }));
2309
+ if (failed === undefined)
2310
+ return;
2311
+ }
2312
+ const cause = mapPgDriverError({ error: failed.error });
2313
+ if (typeof cause.meta.sqlstate !== "string")
2314
+ throw cause;
2315
+ openingRollback = await onClient.query({ text: "ROLLBACK" }).then(() => "acknowledged", () => "failed");
2316
+ if (openingRollback === "failed")
2317
+ discard = true;
2318
+ const setting = findRefusedSetting({ settings, cause });
2319
+ throw transactionSettingRefusedError({ provider: PROVIDER, settings, setting, cause });
2320
+ };
1164
2321
  try {
1165
- if (!deferOpening)
1166
- await runMapped(() => client.query({ text: openText }));
2322
+ if (settingsStatement !== undefined && !settingsDeferred)
2323
+ await openWithSettings(settingsStatement);
2324
+ else if (!deferOpening)
2325
+ await runMapped(() => onClient.query({ text: openText }));
1167
2326
  const result = await runRootScope({
1168
2327
  scope,
1169
- body: () => fn(createClientAdapter({ dispatch, scope, budget, deferred: capable ? deferred : null, cancellation }))
2328
+ body: () => fn(createClientAdapter({
2329
+ dispatch,
2330
+ scope,
2331
+ budget,
2332
+ deferred: capable ? deferred : null,
2333
+ cancellation,
2334
+ pipelines: capable ? pipelines : null,
2335
+ connection: client,
2336
+ topLevel: true,
2337
+ ordered: capable && budget.totalMs === null && !((client.connectionParameters?.query_timeout ?? 0) > 0),
2338
+ commits
2339
+ }))
1170
2340
  });
2341
+ endPipeline();
2342
+ const riding = ridden;
2343
+ if (riding !== undefined) {
2344
+ await riding.settled;
2345
+ if (riding.state === "committed") {
2346
+ budget.close({ outcome: "committed" });
2347
+ return result;
2348
+ }
2349
+ throw riding.state === "skipped" ? transactionAbortedError2({ cause: scope.state.aborted?.cause }) : riding.error;
2350
+ }
1171
2351
  budget.assertUsable({ stage: "commit" });
1172
2352
  if (!deferred.opened()) {
1173
2353
  budget.close({ outcome: "committed" });
1174
2354
  return result;
1175
2355
  }
1176
2356
  if (openingFailure !== undefined)
1177
- throw transactionAbortedError({ cause: openingFailure });
2357
+ throw transactionAbortedError2({ cause: openingFailure });
2358
+ deferred.dropReleases();
1178
2359
  const commit = await runMapped(() => dispatch({
1179
2360
  config: { text: "COMMIT" },
1180
2361
  stage: "commit",
@@ -1198,18 +2379,29 @@ function pgAdapter(options) {
1198
2379
  });
1199
2380
  if (finalResult({ result: commit }).command === "ROLLBACK") {
1200
2381
  commitProgress = "answered";
1201
- throw transactionAbortedError({ cause: scope.state.aborted?.cause });
2382
+ throw transactionAbortedError2({ cause: scope.state.aborted?.cause });
1202
2383
  }
1203
2384
  budget.close({ outcome: "committed" });
1204
2385
  return result;
1205
- } catch (error) {
2386
+ } catch (caught) {
2387
+ endPipeline();
2388
+ const riding = ridden;
2389
+ if (riding !== undefined)
2390
+ await riding.settled;
2391
+ if (riding?.state === "committed") {
2392
+ budget.close({ outcome: "committed" });
2393
+ throw caught;
2394
+ }
2395
+ const error = riding?.state === "lost" ? riding.error : caught;
1206
2396
  if (isSavepointRollbackFailedError(error) || scope.state.poisoned !== null) {
1207
2397
  discard = true;
1208
2398
  budget.close({ outcome: "unknown" });
1209
2399
  }
1210
2400
  let rollback = "not-needed";
1211
2401
  try {
1212
- if (deferred.opened()) {
2402
+ if (openingRollback !== undefined)
2403
+ rollback = openingRollback;
2404
+ else if (deferred.opened()) {
1213
2405
  await dispatch({ config: { text: "ROLLBACK" }, cleanup: true });
1214
2406
  rollback = "acknowledged";
1215
2407
  }
@@ -1233,9 +2425,56 @@ function pgAdapter(options) {
1233
2425
  }
1234
2426
  },
1235
2427
  async connect() {
1236
- const client = await runMapped(() => getPool().connect());
1237
- client.release();
2428
+ const opened = await Promise.allSettled(Array.from({ length: Math.max(1, poolMin ?? 0) }, () => runMapped(() => getPool().connect())));
2429
+ for (const outcome of opened) {
2430
+ if (outcome.status === "fulfilled")
2431
+ outcome.value.release();
2432
+ }
2433
+ const failed = opened.find((outcome) => outcome.status === "rejected");
2434
+ if (failed !== undefined)
2435
+ throw failed.reason;
2436
+ }
2437
+ };
2438
+ const sendConnectionGroup = async (entries) => {
2439
+ const rootPool = getPool();
2440
+ const client = rootViaPoolQuery ? undefined : await runMapped(() => rootPool.connect());
2441
+ if (client === undefined || !hasReadyForQuerySeam({ client }) || !supportsBatch({ client }) || (client.connectionParameters?.query_timeout ?? 0) > 0) {
2442
+ client?.release();
2443
+ const steps = [];
2444
+ for (const entry of entries) {
2445
+ if (steps.some((step) => step.status === "failed")) {
2446
+ steps.push({ status: "skipped" });
2447
+ continue;
2448
+ }
2449
+ steps.push(await root.execute(entry.call).then((rows) => ({ status: "ok", result: { rows, rowCount: rows.length } }), (error) => ({ status: "failed", error })));
2450
+ }
2451
+ return { steps };
1238
2452
  }
2453
+ const seam = observeReadyForQuery({ client });
2454
+ let reuse = false;
2455
+ const outcome = await runBatch({ client, steps: entries.map((entry) => entry.step), types: EXACT_TYPES }).then((settled) => settled, (error) => ({ steps: [], transportError: error }));
2456
+ const failure = firstBatchFailure({ outcome });
2457
+ if (failure === undefined)
2458
+ reuse = true;
2459
+ else if (outcome.transportError === undefined)
2460
+ reuse = await seam.reusableAfter({ error: mapPgDriverError({ error: failure.error }), named: false });
2461
+ seam.detach();
2462
+ client.release(!reuse);
2463
+ return outcome;
2464
+ };
2465
+ const connectionGroupBase = {
2466
+ dialect: DIALECT,
2467
+ provider: PROVIDER,
2468
+ wire: WIRE,
2469
+ budgets: BUDGETS,
2470
+ roundTrips: ROUND_TRIPS,
2471
+ formatArrayParam: (values) => new PgArrayParam(values)
2472
+ };
2473
+ const connectionGroupDriver = {
2474
+ base: connectionGroupBase,
2475
+ prepare: (call) => ({ text: call.text, values: prepareBatchValues({ values: serializeParams({ values: call.values }) }) }),
2476
+ send: (entries) => lifecycle.admit({ run: () => sendConnectionGroup(entries) }),
2477
+ mapError: (error) => mapPgDriverError({ error })
1239
2478
  };
1240
2479
  return {
1241
2480
  dialect: DIALECT,
@@ -1244,7 +2483,19 @@ function pgAdapter(options) {
1244
2483
  budgets: BUDGETS,
1245
2484
  readCancellation,
1246
2485
  roundTrips: ROUND_TRIPS,
2486
+ readSnapshot: READ_SNAPSHOT,
2487
+ pipelining: PIPELINING,
1247
2488
  transactionOutcome: "reported",
2489
+ transactionSettings: "transaction-local",
2490
+ ...transactionsTakeOneRoundTrip({ address }) ? {
2491
+ oneRoundTripTransactions: true,
2492
+ connectionGroup: () => {
2493
+ const closed = lifecycle.refusal();
2494
+ if (closed !== undefined)
2495
+ throw closed;
2496
+ return rootViaPoolQuery ? undefined : openConnectionGroup({ driver: connectionGroupDriver });
2497
+ }
2498
+ } : {},
1248
2499
  withSession({ run }) {
1249
2500
  return lifecycle.admit({ run: () => root.withSession({ run: (executor) => lifecycle.inCallback({ run: () => run(executor) }) }) });
1250
2501
  },
@@ -1297,4 +2548,4 @@ export {
1297
2548
  validateTransactionDeadline2 as validateTransactionDeadline
1298
2549
  };
1299
2550
 
1300
- //# debugId=3DBA8F0584DC025664756E2164756E21
2551
+ //# debugId=96B1BE63991D190764756E2164756E21