@typed-sql/postgres 1.0.0-rc.0 → 2.0.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/CHANGELOG.md +130 -0
  2. package/README.md +57 -65
  3. package/dist/packages/postgres/src/bulk.d.ts +30 -0
  4. package/dist/packages/postgres/src/bulk.d.ts.map +1 -0
  5. package/dist/packages/postgres/src/bulk.js +228 -0
  6. package/dist/packages/postgres/src/bulk.js.map +1 -0
  7. package/dist/packages/postgres/src/index.d.ts +5 -1
  8. package/dist/packages/postgres/src/index.d.ts.map +1 -1
  9. package/dist/packages/postgres/src/index.js +17 -4
  10. package/dist/packages/postgres/src/index.js.map +1 -1
  11. package/dist/packages/postgres/src/pg.d.ts +68 -4
  12. package/dist/packages/postgres/src/pg.d.ts.map +1 -1
  13. package/dist/packages/postgres/src/pg.js +458 -14
  14. package/dist/packages/postgres/src/pg.js.map +1 -1
  15. package/dist/packages/postgres/src/prepared.d.ts +20 -0
  16. package/dist/packages/postgres/src/prepared.d.ts.map +1 -0
  17. package/dist/packages/postgres/src/prepared.js +47 -0
  18. package/dist/packages/postgres/src/prepared.js.map +1 -0
  19. package/dist/packages/postgres/src/provider.d.ts +2 -1
  20. package/dist/packages/postgres/src/provider.d.ts.map +1 -1
  21. package/dist/packages/postgres/src/provider.js +3 -1
  22. package/dist/packages/postgres/src/provider.js.map +1 -1
  23. package/dist/packages/postgres/src/resolver.d.ts.map +1 -1
  24. package/dist/packages/postgres/src/resolver.js +11 -1
  25. package/dist/packages/postgres/src/resolver.js.map +1 -1
  26. package/dist/packages/postgres/src/routing.d.ts +22 -0
  27. package/dist/packages/postgres/src/routing.d.ts.map +1 -0
  28. package/dist/packages/postgres/src/routing.js +70 -0
  29. package/dist/packages/postgres/src/routing.js.map +1 -0
  30. package/dist/packages/postgres/src/runtime.d.ts +34 -3
  31. package/dist/packages/postgres/src/runtime.d.ts.map +1 -1
  32. package/dist/packages/postgres/src/runtime.js +923 -22
  33. package/dist/packages/postgres/src/runtime.js.map +1 -1
  34. package/dist/packages/postgres/src/semantics.d.ts +5 -0
  35. package/dist/packages/postgres/src/semantics.d.ts.map +1 -0
  36. package/dist/packages/postgres/src/semantics.js +211 -0
  37. package/dist/packages/postgres/src/semantics.js.map +1 -0
  38. package/dist/packages/postgres/src/stream.d.ts +34 -0
  39. package/dist/packages/postgres/src/stream.d.ts.map +1 -0
  40. package/dist/packages/postgres/src/stream.js +124 -0
  41. package/dist/packages/postgres/src/stream.js.map +1 -0
  42. package/dist/packages/postgres/src/version.d.ts +2 -0
  43. package/dist/packages/postgres/src/version.d.ts.map +1 -0
  44. package/dist/packages/postgres/src/version.js +2 -0
  45. package/dist/packages/postgres/src/version.js.map +1 -0
  46. package/package.json +4 -4
@@ -1,7 +1,28 @@
1
1
  var _a;
2
- import { renderQuery } from "@typed-sql/core";
2
+ import { createHash } from "node:crypto";
3
+ import { adapterCapabilities, assertExecutionCapabilities, createAdapterCapabilityResolver, databaseErrorCompletion, hasQueryResultValidator, observeQueryStream, QueryCardinalityError, queryResultValidationSource, renderQuery, runControlledExecution, startDatabaseObservation, validateQueryResultRows, validateQueryResultStream, } from "@typed-sql/core";
4
+ import { createPostgresCopyCapability, postgresCopy } from "./bulk.js";
5
+ import { createPostgresPreparedQueryState, preparePostgresQuery, } from "./prepared.js";
6
+ import { PostgresQueryStream, validatePostgresStreamBatchSize } from "./stream.js";
3
7
  import { defaultPostgresTypePolicy } from "./type-policy.js";
8
+ import { POSTGRES_DIALECT_VERSION } from "./version.js";
4
9
  const defaultPolicy = defaultPostgresTypePolicy;
10
+ const emptyBatchResults = Object.freeze([]);
11
+ function createPostgresObservationState(observer) {
12
+ return { observer, fingerprints: observer === undefined ? undefined : new WeakMap() };
13
+ }
14
+ function postgresQueryFingerprint(state, query) {
15
+ const key = query;
16
+ const cached = state.fingerprints?.get(key);
17
+ if (cached !== undefined)
18
+ return cached;
19
+ const text = renderQuery(query, postgresRenderer).text;
20
+ const fingerprint = `sha256:${createHash("sha256")
21
+ .update(`postgres\0${POSTGRES_DIALECT_VERSION}\0${text}`)
22
+ .digest("hex")}`;
23
+ state.fingerprints?.set(key, fingerprint);
24
+ return fingerprint;
25
+ }
5
26
  const oids = {
6
27
  int8: 20,
7
28
  date: 1082,
@@ -121,61 +142,941 @@ class PostgresDatabaseImplementation {
121
142
  #client;
122
143
  #parsers;
123
144
  #ownsPool;
145
+ #prepared;
146
+ #observation;
124
147
  #transactionDepth;
125
- constructor(pool, client, parsers, ownsPool, depth) {
148
+ #transactionScopeOpen = true;
149
+ #transactionOperationFailed = false;
150
+ #transactionOperationError;
151
+ #transactionState;
152
+ #transactionExecutes = new Set();
153
+ #transactionStreams = new Set();
154
+ executionCapabilities;
155
+ [adapterCapabilities];
156
+ constructor(pool, client, parsers, ownsPool, depth, prepared, observation, transactionState = {
157
+ activeBatch: undefined,
158
+ activeCopy: undefined,
159
+ activeExecutes: new Set(),
160
+ activePipeline: undefined,
161
+ activeStreams: new Set(),
162
+ discardLease: false,
163
+ leaseReleased: false,
164
+ firstDatabaseOperationError: undefined,
165
+ unsafe: false,
166
+ unsafeDepth: undefined,
167
+ unsafeError: undefined,
168
+ valid: true,
169
+ }) {
126
170
  this.#pool = pool;
127
171
  this.#client = client;
128
172
  this.#parsers = parsers;
129
173
  this.#ownsPool = ownsPool;
130
174
  this.#transactionDepth = depth;
175
+ this.#prepared = prepared;
176
+ this.#observation = observation;
177
+ this.#transactionState = transactionState;
178
+ this.executionCapabilities = Object.freeze({
179
+ cancellation: pool.executionCapabilities?.cancellation ?? false,
180
+ deadlines: pool.executionCapabilities?.deadlines ?? false,
181
+ });
182
+ const copyTransport = {
183
+ copyFrom: (statement, chunks, options) => this.#copyFrom(statement, chunks, options),
184
+ copyTo: (statement, options) => this.#copyTo(statement, options),
185
+ };
186
+ this[adapterCapabilities] = createAdapterCapabilityResolver(pool.ensureCopy === undefined ? [] : [[postgresCopy, createPostgresCopyCapability(copyTransport)]]);
131
187
  }
132
188
  async execute(query) {
133
- const rendered = renderQuery(query, postgresRenderer);
134
- const result = await (this.#client ?? this.#pool).query({
135
- text: rendered.text,
136
- values: rendered.values.map(encodeValue),
137
- types: this.#parsers,
189
+ if (this.#observation.observer === undefined) {
190
+ return this.#validateRows(query, await this.#executeUnobserved(query));
191
+ }
192
+ return this.#observeQuery(query, "many", async () => this.#validateRows(query, await this.#executeUnobserved(query)));
193
+ }
194
+ async #executeUnobserved(query) {
195
+ this.#assertTransactionScopeOpen();
196
+ this.#rejectOperationDuringTransactionStream("execute another query");
197
+ this.#rejectOperationDuringTransactionBatch("execute another query");
198
+ this.#rejectOperationDuringTransactionCopy("execute another query");
199
+ this.#rejectOperationDuringTransactionPipeline("execute another query");
200
+ const config = this.#queryConfig(query);
201
+ let operation;
202
+ try {
203
+ operation = (this.#client ?? this.#pool).query(config);
204
+ }
205
+ catch (error) {
206
+ this.#recordTransactionOperationFailure(error);
207
+ throw error;
208
+ }
209
+ if (this.#client !== undefined) {
210
+ this.#transactionExecutes.add(operation);
211
+ this.#transactionState.activeExecutes.add(operation);
212
+ }
213
+ try {
214
+ const result = await operation;
215
+ return result.rows;
216
+ }
217
+ catch (error) {
218
+ this.#recordTransactionOperationFailure(error);
219
+ throw error;
220
+ }
221
+ finally {
222
+ this.#transactionExecutes.delete(operation);
223
+ this.#transactionState.activeExecutes.delete(operation);
224
+ }
225
+ }
226
+ async all(query, options) {
227
+ if (this.#observation.observer === undefined) {
228
+ return this.#validateRows(query, await this.#allUnobserved(query, options));
229
+ }
230
+ return this.#observeQuery(query, "many", async () => this.#validateRows(query, await this.#allUnobserved(query, options)));
231
+ }
232
+ async #allUnobserved(query, options) {
233
+ if (options === undefined || (options.signal === undefined && options.deadline === undefined))
234
+ return this.#executeUnobserved(query);
235
+ assertExecutionCapabilities(this.executionCapabilities, options);
236
+ this.#assertTransactionScopeOpen();
237
+ this.#rejectOperationDuringTransactionStream("execute another query");
238
+ this.#rejectOperationDuringTransactionBatch("execute another query");
239
+ this.#rejectOperationDuringTransactionCopy("execute another query");
240
+ this.#rejectOperationDuringTransactionPipeline("execute another query");
241
+ if (options.signal?.aborted || (options.deadline !== undefined && Number(options.deadline) <= Date.now())) {
242
+ return runControlledExecution(options, async () => [], () => undefined);
243
+ }
244
+ if (this.#client === undefined) {
245
+ const client = await this.#pool.connect();
246
+ let discarded = false;
247
+ try {
248
+ return await this.#executeControlledOn(client, query, options, (error) => {
249
+ client.release(error);
250
+ discarded = true;
251
+ });
252
+ }
253
+ finally {
254
+ if (!discarded)
255
+ client.release();
256
+ }
257
+ }
258
+ const client = this.#client;
259
+ const operation = this.#executeControlledOn(client, query, options, (error) => {
260
+ this.#transactionState.discardLease = true;
261
+ this.#markTransactionStateUnsafe(error);
262
+ try {
263
+ client.release(error);
264
+ this.#transactionState.leaseReleased = true;
265
+ }
266
+ catch {
267
+ /* The transaction finalizer also owns lease cleanup. */
268
+ }
138
269
  });
270
+ this.#transactionExecutes.add(operation);
271
+ this.#transactionState.activeExecutes.add(operation);
272
+ try {
273
+ return await operation;
274
+ }
275
+ catch (error) {
276
+ this.#recordTransactionOperationFailure(error);
277
+ throw error;
278
+ }
279
+ finally {
280
+ this.#transactionExecutes.delete(operation);
281
+ this.#transactionState.activeExecutes.delete(operation);
282
+ }
283
+ }
284
+ async #executeControlledOn(client, query, options, cancel) {
285
+ const result = await runControlledExecution(options, () => client.query(this.#queryConfig(query)), cancel);
139
286
  return result.rows;
140
287
  }
141
- async transaction(fn) {
142
- if (this.#client !== undefined)
143
- return this.#nestedTransaction(fn);
288
+ async one(query, options) {
289
+ if (this.#observation.observer === undefined) {
290
+ const rows = await this.#validateRows(query, await this.#allUnobserved(query, options));
291
+ if (rows.length !== 1)
292
+ throw new QueryCardinalityError("one", rows.length);
293
+ return rows[0];
294
+ }
295
+ const rows = await this.#observeQuery(query, "one", async () => {
296
+ const result = await this.#validateRows(query, await this.#allUnobserved(query, options));
297
+ if (result.length !== 1)
298
+ throw new QueryCardinalityError("one", result.length);
299
+ return result;
300
+ });
301
+ return rows[0];
302
+ }
303
+ async maybeOne(query, options) {
304
+ if (this.#observation.observer === undefined) {
305
+ const rows = await this.#validateRows(query, await this.#allUnobserved(query, options));
306
+ if (rows.length > 1)
307
+ throw new QueryCardinalityError("maybeOne", rows.length);
308
+ return rows[0];
309
+ }
310
+ const rows = await this.#observeQuery(query, "maybeOne", async () => {
311
+ const result = await this.#validateRows(query, await this.#allUnobserved(query, options));
312
+ if (result.length > 1)
313
+ throw new QueryCardinalityError("maybeOne", result.length);
314
+ return result;
315
+ });
316
+ return rows[0];
317
+ }
318
+ async #observeQuery(query, cardinality, operation) {
319
+ if (this.#observation.observer === undefined)
320
+ return operation();
321
+ const observation = startDatabaseObservation(this.#observation.observer, {
322
+ kind: "query",
323
+ dialect: "postgres",
324
+ grammarVersion: POSTGRES_DIALECT_VERSION,
325
+ transactionDepth: this.#transactionDepth,
326
+ fingerprint: postgresQueryFingerprint(this.#observation, query),
327
+ cardinality,
328
+ prepared: this.#prepared.queries.has(queryResultValidationSource(query)),
329
+ });
330
+ if (observation === undefined)
331
+ return operation();
332
+ try {
333
+ const rows = await observation.run(operation);
334
+ observation.end({ status: "success", rowCount: rows.length });
335
+ return rows;
336
+ }
337
+ catch (error) {
338
+ observation.end(databaseErrorCompletion(error), error);
339
+ throw error;
340
+ }
341
+ }
342
+ async batch(queries) {
343
+ if (queries.length === 0)
344
+ return emptyBatchResults;
345
+ if (this.#observation.observer === undefined) {
346
+ return this.#validateBatch(queries, await this.#batchUnobserved(queries));
347
+ }
348
+ return this.#observeGroup("batch", queries, async () => this.#validateBatch(queries, await this.#batchUnobserved(queries)));
349
+ }
350
+ async #batchUnobserved(queries) {
351
+ this.#assertTransactionScopeOpen();
352
+ this.#rejectOperationDuringTransactionStream("execute a query batch");
353
+ this.#rejectOperationDuringTransactionBatch("execute another query batch");
354
+ this.#rejectOperationDuringTransactionCopy("execute a query batch");
355
+ this.#rejectOperationDuringTransactionPipeline("execute a query batch");
356
+ if (queries.length === 0)
357
+ return emptyBatchResults;
358
+ const querySnapshot = [...queries];
359
+ if (this.#client !== undefined) {
360
+ const operation = this.#executeBatch(this.#client, querySnapshot);
361
+ this.#transactionState.activeBatch = operation;
362
+ try {
363
+ return await operation;
364
+ }
365
+ catch (error) {
366
+ this.#recordTransactionOperationFailure(error);
367
+ throw error;
368
+ }
369
+ finally {
370
+ if (this.#transactionState.activeBatch === operation)
371
+ this.#transactionState.activeBatch = undefined;
372
+ }
373
+ }
144
374
  const client = await this.#pool.connect();
375
+ let results;
145
376
  try {
146
- await client.query("BEGIN");
147
- const result = await fn(new _a(this.#pool, client, this.#parsers, false, 1));
148
- await client.query("COMMIT");
149
- return result;
377
+ results = await this.#executeBatch(client, querySnapshot);
150
378
  }
151
379
  catch (error) {
152
380
  try {
153
- await client.query("ROLLBACK");
381
+ client.release(error instanceof Error ? error : true);
154
382
  }
155
383
  catch {
156
- /* Preserve the original error. */
384
+ /* Preserve the first query failure. */
157
385
  }
158
386
  throw error;
159
387
  }
160
- finally {
388
+ client.release();
389
+ return results;
390
+ }
391
+ async pipeline(queries) {
392
+ if (queries.length === 0)
393
+ return emptyBatchResults;
394
+ if (this.#observation.observer === undefined) {
395
+ return this.#validateBatch(queries, await this.#pipelineUnobserved(queries));
396
+ }
397
+ return this.#observeGroup("pipeline", queries, async () => this.#validateBatch(queries, await this.#pipelineUnobserved(queries)));
398
+ }
399
+ async #pipelineUnobserved(queries) {
400
+ this.#assertTransactionScopeOpen();
401
+ this.#rejectOperationDuringTransactionStream("execute a query pipeline");
402
+ this.#rejectOperationDuringTransactionBatch("execute a query pipeline");
403
+ this.#rejectOperationDuringTransactionCopy("execute a query pipeline");
404
+ this.#rejectOperationDuringTransactionExecute("execute a query pipeline");
405
+ this.#rejectOperationDuringTransactionPipeline("execute another query pipeline");
406
+ if (queries.length === 0)
407
+ return emptyBatchResults;
408
+ const configs = [...queries].map((value) => this.#queryConfig(value));
409
+ if (this.#client !== undefined) {
410
+ this.#assertPipelineClient(this.#client);
411
+ const operation = this.#executePipeline(this.#client, configs);
412
+ this.#transactionState.activePipeline = operation;
413
+ try {
414
+ return await operation;
415
+ }
416
+ catch (error) {
417
+ this.#recordTransactionOperationFailure(error);
418
+ throw error;
419
+ }
420
+ finally {
421
+ if (this.#transactionState.activePipeline === operation)
422
+ this.#transactionState.activePipeline = undefined;
423
+ }
424
+ }
425
+ const client = await this.#pool.connect();
426
+ try {
427
+ this.#assertPipelineClient(client);
428
+ }
429
+ catch (error) {
430
+ try {
431
+ client.release();
432
+ }
433
+ catch {
434
+ /* Preserve the missing pipeline-capability error. */
435
+ }
436
+ throw error;
437
+ }
438
+ try {
439
+ const results = await this.#executePipeline(client, configs);
161
440
  client.release();
441
+ return results;
442
+ }
443
+ catch (error) {
444
+ try {
445
+ client.release(error instanceof Error ? error : true);
446
+ }
447
+ catch {
448
+ /* Preserve the first pipeline failure. */
449
+ }
450
+ throw error;
162
451
  }
163
452
  }
453
+ async #observeGroup(kind, queries, operation) {
454
+ if (this.#observation.observer === undefined)
455
+ return operation();
456
+ const fingerprints = Object.freeze([...queries].map((query) => postgresQueryFingerprint(this.#observation, query)));
457
+ const observation = startDatabaseObservation(this.#observation.observer, {
458
+ kind,
459
+ dialect: "postgres",
460
+ grammarVersion: POSTGRES_DIALECT_VERSION,
461
+ transactionDepth: this.#transactionDepth,
462
+ fingerprints,
463
+ size: queries.length,
464
+ });
465
+ if (observation === undefined)
466
+ return operation();
467
+ try {
468
+ const results = await observation.run(operation);
469
+ observation.end({ status: "success" });
470
+ return results;
471
+ }
472
+ catch (error) {
473
+ observation.end(databaseErrorCompletion(error), error);
474
+ throw error;
475
+ }
476
+ }
477
+ stream(query, options = {}) {
478
+ this.#assertTransactionScopeOpen();
479
+ this.#rejectOperationDuringTransactionStream("start another query stream");
480
+ this.#rejectOperationDuringTransactionBatch("start a query stream");
481
+ this.#rejectOperationDuringTransactionCopy("start a query stream");
482
+ this.#rejectOperationDuringTransactionPipeline("start a query stream");
483
+ const batchSize = validatePostgresStreamBatchSize(options.batchSize);
484
+ const config = this.#queryConfig(query);
485
+ let exposedStream;
486
+ const stream = new PostgresQueryStream({
487
+ batchSize,
488
+ start: async () => {
489
+ await this.#pool.ensureCursor?.();
490
+ const client = this.#client ?? (await this.#pool.connect());
491
+ const release = this.#client === undefined
492
+ ? (cleanupError) => client.release(cleanupError === undefined ? undefined : cleanupError instanceof Error ? cleanupError : true)
493
+ : undefined;
494
+ if (client.openCursor === undefined) {
495
+ try {
496
+ release?.();
497
+ }
498
+ catch {
499
+ /* The missing-capability failure remains primary. */
500
+ }
501
+ throw new Error("PostgreSQL streaming requires the application-owned pg-cursor package. Install it with: pnpm add pg-cursor");
502
+ }
503
+ try {
504
+ const cursor = await client.openCursor(config);
505
+ return { cursor: cursor, ...(release === undefined ? {} : { release }) };
506
+ }
507
+ catch (error) {
508
+ this.#recordTransactionOperationFailure(error);
509
+ try {
510
+ release?.(error);
511
+ }
512
+ catch {
513
+ /* Preserve cursor creation or optional-dependency failures. */
514
+ }
515
+ throw error;
516
+ }
517
+ },
518
+ ...(this.#client === undefined
519
+ ? {}
520
+ : {
521
+ onStart: () => {
522
+ this.#assertTransactionScopeOpen();
523
+ this.#rejectOperationDuringTransactionStream("start another query stream");
524
+ this.#rejectOperationDuringTransactionBatch("start a query stream");
525
+ this.#rejectOperationDuringTransactionCopy("start a query stream");
526
+ this.#rejectOperationDuringTransactionPipeline("start a query stream");
527
+ this.#transactionState.activeStreams.add(exposedStream);
528
+ },
529
+ onClose: () => {
530
+ this.#transactionState.activeStreams.delete(exposedStream);
531
+ this.#transactionStreams.delete(exposedStream);
532
+ },
533
+ onError: (error) => this.#recordTransactionOperationFailure(error),
534
+ }),
535
+ });
536
+ const validatedStream = hasQueryResultValidator(query)
537
+ ? validateQueryResultStream(query, stream, postgresQueryFingerprint(this.#observation, query))
538
+ : stream;
539
+ exposedStream =
540
+ this.#observation.observer === undefined
541
+ ? validatedStream
542
+ : observeQueryStream(validatedStream, this.#observation.observer, {
543
+ kind: "stream",
544
+ dialect: "postgres",
545
+ grammarVersion: POSTGRES_DIALECT_VERSION,
546
+ transactionDepth: this.#transactionDepth,
547
+ fingerprint: postgresQueryFingerprint(this.#observation, query),
548
+ prepared: this.#prepared.queries.has(queryResultValidationSource(query)),
549
+ });
550
+ if (this.#client !== undefined)
551
+ this.#transactionStreams.add(exposedStream);
552
+ return exposedStream;
553
+ }
554
+ async #validateRows(query, rows) {
555
+ if (!hasQueryResultValidator(query))
556
+ return rows;
557
+ return validateQueryResultRows(query, rows, postgresQueryFingerprint(this.#observation, query));
558
+ }
559
+ async #validateBatch(queries, results) {
560
+ let validated;
561
+ const queryList = queries;
562
+ const resultList = results;
563
+ for (let index = 0; index < queryList.length; index += 1) {
564
+ const query = queryList[index];
565
+ if (!hasQueryResultValidator(query))
566
+ continue;
567
+ validated ??= [...resultList];
568
+ validated[index] = await validateQueryResultRows(query, resultList[index], postgresQueryFingerprint(this.#observation, query));
569
+ }
570
+ return (validated ?? results);
571
+ }
572
+ async #copyFrom(statement, chunks, options) {
573
+ assertExecutionCapabilities(this.executionCapabilities, options);
574
+ this.#assertTransactionScopeOpen();
575
+ this.#rejectOperationDuringTransactionStream("start COPY FROM");
576
+ this.#rejectOperationDuringTransactionBatch("start COPY FROM");
577
+ this.#rejectOperationDuringTransactionCopy("start another COPY operation");
578
+ this.#rejectOperationDuringTransactionExecute("start COPY FROM");
579
+ this.#rejectOperationDuringTransactionPipeline("start COPY FROM");
580
+ await this.#pool.ensureCopy?.();
581
+ const client = this.#client ?? (await this.#pool.connect());
582
+ let sink;
583
+ const operation = runControlledExecution(options, async () => {
584
+ if (client.openCopyFrom === undefined) {
585
+ throw new Error("PostgreSQL COPY requires the application-owned pg-copy-streams package. Install it with: pnpm add pg-copy-streams");
586
+ }
587
+ sink = await client.openCopyFrom(statement);
588
+ try {
589
+ for await (const chunk of chunks)
590
+ await sink.write(chunk);
591
+ await sink.finish();
592
+ }
593
+ catch (error) {
594
+ try {
595
+ await sink.abort(error);
596
+ }
597
+ catch {
598
+ /* Preserve the producer or database failure. */
599
+ }
600
+ throw error;
601
+ }
602
+ }, (error) => {
603
+ void sink?.abort(error).catch(() => undefined);
604
+ });
605
+ if (this.#client !== undefined)
606
+ this.#transactionState.activeCopy = operation;
607
+ try {
608
+ await operation;
609
+ if (this.#client === undefined)
610
+ client.release();
611
+ }
612
+ catch (error) {
613
+ this.#recordTransactionOperationFailure(error);
614
+ if (this.#client === undefined) {
615
+ try {
616
+ client.release(error instanceof Error ? error : true);
617
+ }
618
+ catch {
619
+ /* Preserve the COPY failure. */
620
+ }
621
+ }
622
+ throw error;
623
+ }
624
+ finally {
625
+ if (this.#transactionState.activeCopy === operation)
626
+ this.#transactionState.activeCopy = undefined;
627
+ }
628
+ }
629
+ #copyTo(statement, options) {
630
+ assertExecutionCapabilities(this.executionCapabilities, options);
631
+ this.#assertTransactionScopeOpen();
632
+ this.#rejectOperationDuringTransactionStream("start COPY TO");
633
+ this.#rejectOperationDuringTransactionBatch("start COPY TO");
634
+ this.#rejectOperationDuringTransactionCopy("start COPY TO");
635
+ this.#rejectOperationDuringTransactionPipeline("start COPY TO");
636
+ let exposedStream;
637
+ const stream = new PostgresQueryStream({
638
+ batchSize: 1,
639
+ start: async () => {
640
+ await this.#pool.ensureCopy?.();
641
+ const client = this.#client ?? (await this.#pool.connect());
642
+ const release = this.#client === undefined
643
+ ? (cleanupError) => client.release(cleanupError === undefined ? undefined : cleanupError instanceof Error ? cleanupError : true)
644
+ : undefined;
645
+ if (client.openCopyTo === undefined) {
646
+ try {
647
+ release?.();
648
+ }
649
+ catch {
650
+ /* The missing optional dependency remains primary. */
651
+ }
652
+ throw new Error("PostgreSQL COPY requires the application-owned pg-copy-streams package. Install it with: pnpm add pg-copy-streams");
653
+ }
654
+ try {
655
+ const source = await client.openCopyTo(statement);
656
+ return {
657
+ cursor: {
658
+ async read() {
659
+ const result = await runControlledExecution(options, () => source.next(), (error) => {
660
+ void source.abort(error).catch(() => undefined);
661
+ });
662
+ return result.done === true ? [] : [result.value];
663
+ },
664
+ close: () => source.close(),
665
+ },
666
+ ...(release === undefined ? {} : { release }),
667
+ };
668
+ }
669
+ catch (error) {
670
+ this.#recordTransactionOperationFailure(error);
671
+ try {
672
+ release?.(error);
673
+ }
674
+ catch {
675
+ /* Preserve source creation or optional-dependency failures. */
676
+ }
677
+ throw error;
678
+ }
679
+ },
680
+ ...(this.#client === undefined
681
+ ? {}
682
+ : {
683
+ onStart: () => {
684
+ this.#assertTransactionScopeOpen();
685
+ this.#rejectOperationDuringTransactionStream("start COPY TO");
686
+ this.#rejectOperationDuringTransactionBatch("start COPY TO");
687
+ this.#rejectOperationDuringTransactionCopy("start COPY TO");
688
+ this.#rejectOperationDuringTransactionPipeline("start COPY TO");
689
+ this.#transactionState.activeStreams.add(exposedStream);
690
+ },
691
+ onClose: () => {
692
+ this.#transactionState.activeStreams.delete(exposedStream);
693
+ this.#transactionStreams.delete(exposedStream);
694
+ },
695
+ onError: (error) => this.#recordTransactionOperationFailure(error),
696
+ }),
697
+ });
698
+ exposedStream = stream;
699
+ if (this.#client !== undefined)
700
+ this.#transactionStreams.add(exposedStream);
701
+ return exposedStream;
702
+ }
703
+ prepare(statementName, factory) {
704
+ this.#assertTransactionScopeOpen();
705
+ return preparePostgresQuery(this.#prepared, postgresRenderer, statementName, factory);
706
+ }
707
+ async transaction(fn) {
708
+ if (this.#observation.observer === undefined)
709
+ return this.#transactionUnobserved(fn);
710
+ const observation = startDatabaseObservation(this.#observation.observer, {
711
+ kind: "transaction",
712
+ dialect: "postgres",
713
+ grammarVersion: POSTGRES_DIALECT_VERSION,
714
+ transactionDepth: this.#transactionDepth + 1,
715
+ });
716
+ if (observation === undefined)
717
+ return this.#transactionUnobserved(fn);
718
+ try {
719
+ const result = await observation.run(() => this.#transactionUnobserved(fn));
720
+ observation.end({ status: "success" });
721
+ return result;
722
+ }
723
+ catch (error) {
724
+ observation.end(databaseErrorCompletion(error), error);
725
+ throw error;
726
+ }
727
+ }
728
+ async #transactionUnobserved(fn) {
729
+ this.#assertTransactionScopeOpen();
730
+ this.#rejectOperationDuringTransactionStream("start a nested transaction");
731
+ this.#rejectOperationDuringTransactionBatch("start a nested transaction");
732
+ this.#rejectOperationDuringTransactionCopy("start a nested transaction");
733
+ this.#rejectOperationDuringTransactionPipeline("start a nested transaction");
734
+ if (this.#client !== undefined)
735
+ return this.#nestedTransaction(fn);
736
+ const client = await this.#pool.connect();
737
+ const scope = new _a(this.#pool, client, this.#parsers, false, 1, this.#prepared, this.#observation, {
738
+ activeBatch: undefined,
739
+ activeCopy: undefined,
740
+ activeExecutes: new Set(),
741
+ activePipeline: undefined,
742
+ activeStreams: new Set(),
743
+ discardLease: false,
744
+ leaseReleased: false,
745
+ firstDatabaseOperationError: undefined,
746
+ unsafe: false,
747
+ unsafeDepth: undefined,
748
+ unsafeError: undefined,
749
+ valid: true,
750
+ });
751
+ let result;
752
+ try {
753
+ try {
754
+ await client.query("BEGIN");
755
+ }
756
+ catch (error) {
757
+ scope.#recordTransactionOperationFailure(error);
758
+ throw error;
759
+ }
760
+ try {
761
+ result = await fn(scope);
762
+ }
763
+ finally {
764
+ scope.#transactionScopeOpen = false;
765
+ scope.#transactionState.valid = false;
766
+ }
767
+ scope.#throwIfTransactionCannotCommit();
768
+ await scope.#rejectLeakedTransactionWork();
769
+ try {
770
+ await client.query("COMMIT");
771
+ }
772
+ catch (error) {
773
+ scope.#recordTransactionOperationFailure(error);
774
+ throw error;
775
+ }
776
+ }
777
+ catch (error) {
778
+ scope.#transactionScopeOpen = false;
779
+ scope.#transactionState.valid = false;
780
+ await scope.#settleTransactionExecutesPreservingError();
781
+ await scope.#closeTransactionStreamsPreservingError();
782
+ await scope.#settleTransactionPipelinePreservingError();
783
+ await scope.#settleTransactionBatchPreservingError();
784
+ await scope.#settleTransactionCopyPreservingError();
785
+ if (!scope.#transactionState.leaseReleased) {
786
+ try {
787
+ await client.query("ROLLBACK");
788
+ }
789
+ catch (cleanupError) {
790
+ scope.#markTransactionStateUnsafe(cleanupError);
791
+ /* Preserve the original error. */
792
+ }
793
+ try {
794
+ client.release(scope.#transactionLeaseReleaseError());
795
+ }
796
+ catch {
797
+ /* Preserve the original error. */
798
+ }
799
+ }
800
+ throw error;
801
+ }
802
+ client.release(scope.#transactionLeaseReleaseError());
803
+ return result;
804
+ }
164
805
  async #nestedTransaction(fn) {
165
806
  const client = this.#client;
166
807
  const depth = this.#transactionDepth + 1;
167
808
  const savepoint = `typed_sql_${depth}`;
168
- await client.query(`SAVEPOINT ${savepoint}`);
169
809
  try {
170
- const result = await fn(new _a(this.#pool, client, this.#parsers, false, depth));
171
- await client.query(`RELEASE SAVEPOINT ${savepoint}`);
810
+ await client.query(`SAVEPOINT ${savepoint}`);
811
+ }
812
+ catch (error) {
813
+ this.#recordTransactionOperationFailure(error);
814
+ throw error;
815
+ }
816
+ const scope = new _a(this.#pool, client, this.#parsers, false, depth, this.#prepared, this.#observation, this.#transactionState);
817
+ try {
818
+ let result;
819
+ try {
820
+ result = await fn(scope);
821
+ }
822
+ finally {
823
+ scope.#transactionScopeOpen = false;
824
+ }
825
+ if (!scope.#transactionState.valid) {
826
+ throw new Error("The parent PostgreSQL transaction scope ended before its nested transaction completed");
827
+ }
828
+ scope.#throwIfTransactionCannotCommit();
829
+ await scope.#rejectLeakedTransactionWork();
830
+ try {
831
+ await client.query(`RELEASE SAVEPOINT ${savepoint}`);
832
+ }
833
+ catch (error) {
834
+ scope.#recordTransactionOperationFailure(error);
835
+ throw error;
836
+ }
172
837
  return result;
173
838
  }
174
839
  catch (error) {
175
- await client.query(`ROLLBACK TO SAVEPOINT ${savepoint}`);
840
+ await scope.#settleTransactionExecutesPreservingError();
841
+ await scope.#closeTransactionStreamsPreservingError();
842
+ await scope.#settleTransactionPipelinePreservingError();
843
+ await scope.#settleTransactionBatchPreservingError();
844
+ await scope.#settleTransactionCopyPreservingError();
845
+ if (scope.#transactionState.valid && !scope.#transactionState.leaseReleased) {
846
+ try {
847
+ await client.query(`ROLLBACK TO SAVEPOINT ${savepoint}`);
848
+ scope.#recoverTransactionStateAfterSavepoint(depth);
849
+ }
850
+ catch (cleanupError) {
851
+ scope.#markTransactionStateUnsafe(cleanupError);
852
+ /* Preserve the original callback, query, or release error. */
853
+ }
854
+ }
176
855
  throw error;
177
856
  }
178
857
  }
858
+ #queryConfig(query) {
859
+ const prepared = this.#prepared.queries.get(queryResultValidationSource(query));
860
+ const rendered = prepared?.rendered ?? renderQuery(query, postgresRenderer);
861
+ return {
862
+ ...(prepared === undefined ? {} : { name: prepared.statementName }),
863
+ text: rendered.text,
864
+ values: rendered.values.map(encodeValue),
865
+ types: this.#parsers,
866
+ };
867
+ }
868
+ async #executeBatch(client, queries) {
869
+ const results = [];
870
+ for (const value of queries) {
871
+ this.#assertTransactionScopeOpen();
872
+ const query = value;
873
+ const result = await client.query(this.#queryConfig(query));
874
+ results.push(result.rows);
875
+ }
876
+ return results;
877
+ }
878
+ async #executePipeline(client, configs) {
879
+ const operations = [];
880
+ let dispatchFailed = false;
881
+ let dispatchError;
882
+ for (const config of configs) {
883
+ try {
884
+ operations.push(client.query(config));
885
+ }
886
+ catch (error) {
887
+ dispatchFailed = true;
888
+ dispatchError = error;
889
+ break;
890
+ }
891
+ }
892
+ const settlements = await Promise.allSettled(operations);
893
+ if (dispatchFailed)
894
+ throw dispatchError;
895
+ const results = [];
896
+ for (const settlement of settlements) {
897
+ if (settlement.status === "rejected")
898
+ throw settlement.reason;
899
+ results.push(settlement.value.rows);
900
+ }
901
+ return results;
902
+ }
903
+ #assertPipelineClient(client) {
904
+ if (client.pipeline !== true) {
905
+ throw new Error("PostgreSQL query pipelining requires pg 8.23.0 or newer and pipeline mode. Create the application-owned Pool with { pipeline: true }");
906
+ }
907
+ }
908
+ async #rejectLeakedTransactionWork() {
909
+ const leakedStreams = this.#transactionStreams.size > 0 || this.#transactionState.activeStreams.size > 0;
910
+ const leakedExecutes = this.#transactionExecutes.size > 0 || this.#transactionState.activeExecutes.size > 0;
911
+ const leakedPipeline = this.#transactionState.activePipeline !== undefined;
912
+ const leakedBatch = this.#transactionState.activeBatch !== undefined;
913
+ const leakedCopy = this.#transactionState.activeCopy !== undefined;
914
+ if (!leakedStreams && !leakedExecutes && !leakedPipeline && !leakedBatch && !leakedCopy)
915
+ return;
916
+ await this.#settleTransactionExecutesPreservingError();
917
+ await this.#closeTransactionStreamsPreservingError();
918
+ await this.#settleTransactionPipelinePreservingError();
919
+ await this.#settleTransactionBatchPreservingError();
920
+ await this.#settleTransactionCopyPreservingError();
921
+ if (leakedExecutes) {
922
+ throw new Error("A PostgreSQL transaction callback returned before an execute operation completed; await execute before returning");
923
+ }
924
+ if (leakedBatch) {
925
+ throw new Error("A PostgreSQL transaction callback returned before its query batch completed; await the batch before returning");
926
+ }
927
+ if (leakedPipeline) {
928
+ throw new Error("A PostgreSQL transaction callback returned before its query pipeline completed; await the pipeline before returning");
929
+ }
930
+ if (leakedCopy) {
931
+ throw new Error("A PostgreSQL transaction callback returned before its COPY operation completed; await COPY before returning");
932
+ }
933
+ throw new Error("A PostgreSQL transaction callback returned before all query streams were completed or closed");
934
+ }
935
+ async #settleTransactionExecutesPreservingError() {
936
+ const operations = new Set([...this.#transactionExecutes, ...this.#transactionState.activeExecutes]);
937
+ for (const operation of operations) {
938
+ try {
939
+ await operation;
940
+ }
941
+ catch {
942
+ /* The callback or transaction misuse error remains primary. */
943
+ }
944
+ }
945
+ }
946
+ async #closeTransactionStreamsPreservingError() {
947
+ const streams = new Set([...this.#transactionStreams, ...this.#transactionState.activeStreams]);
948
+ for (const stream of streams) {
949
+ try {
950
+ await stream.close();
951
+ }
952
+ catch {
953
+ /* The callback or transaction misuse error remains primary. */
954
+ }
955
+ }
956
+ }
957
+ async #settleTransactionBatchPreservingError() {
958
+ const operation = this.#transactionState.activeBatch;
959
+ if (operation === undefined)
960
+ return;
961
+ try {
962
+ await operation;
963
+ }
964
+ catch {
965
+ /* The callback or transaction misuse error remains primary. */
966
+ }
967
+ }
968
+ async #settleTransactionPipelinePreservingError() {
969
+ const operation = this.#transactionState.activePipeline;
970
+ if (operation === undefined)
971
+ return;
972
+ try {
973
+ await operation;
974
+ }
975
+ catch {
976
+ /* The callback or transaction misuse error remains primary. */
977
+ }
978
+ }
979
+ async #settleTransactionCopyPreservingError() {
980
+ const operation = this.#transactionState.activeCopy;
981
+ if (operation === undefined)
982
+ return;
983
+ try {
984
+ await operation;
985
+ }
986
+ catch {
987
+ /* The callback or transaction misuse error remains primary. */
988
+ }
989
+ }
990
+ #rejectOperationDuringTransactionStream(operation) {
991
+ if (this.#client !== undefined && this.#transactionState.activeStreams.size > 0) {
992
+ throw new Error(`Cannot ${operation} while a PostgreSQL transaction query stream is still open; complete or close the stream first`);
993
+ }
994
+ }
995
+ #rejectOperationDuringTransactionBatch(operation) {
996
+ if (this.#client !== undefined && this.#transactionState.activeBatch !== undefined) {
997
+ throw new Error(`Cannot ${operation} while a PostgreSQL transaction query batch is still running; await the batch first`);
998
+ }
999
+ }
1000
+ #rejectOperationDuringTransactionCopy(operation) {
1001
+ if (this.#client !== undefined && this.#transactionState.activeCopy !== undefined) {
1002
+ throw new Error(`Cannot ${operation} while a PostgreSQL transaction COPY operation is still running; await COPY first`);
1003
+ }
1004
+ }
1005
+ #rejectOperationDuringTransactionExecute(operation) {
1006
+ if (this.#client !== undefined && this.#transactionState.activeExecutes.size > 0) {
1007
+ throw new Error(`Cannot ${operation} while a PostgreSQL transaction execute operation is still running; await execute first`);
1008
+ }
1009
+ }
1010
+ #rejectOperationDuringTransactionPipeline(operation) {
1011
+ if (this.#client !== undefined && this.#transactionState.activePipeline !== undefined) {
1012
+ throw new Error(`Cannot ${operation} while a PostgreSQL transaction query pipeline is still running; await the pipeline first`);
1013
+ }
1014
+ }
1015
+ #recordTransactionOperationFailure(error) {
1016
+ if (this.#client === undefined)
1017
+ return;
1018
+ if (!this.#transactionOperationFailed) {
1019
+ this.#transactionOperationFailed = true;
1020
+ this.#transactionOperationError = error;
1021
+ }
1022
+ this.#markTransactionStateUnsafe(error);
1023
+ }
1024
+ #markTransactionStateUnsafe(error) {
1025
+ if (!this.#transactionState.discardLease) {
1026
+ this.#transactionState.discardLease = true;
1027
+ this.#transactionState.firstDatabaseOperationError = error;
1028
+ }
1029
+ if (!this.#transactionState.unsafe ||
1030
+ this.#transactionState.unsafeDepth === undefined ||
1031
+ this.#transactionDepth < this.#transactionState.unsafeDepth) {
1032
+ this.#transactionState.unsafe = true;
1033
+ this.#transactionState.unsafeDepth = this.#transactionDepth;
1034
+ this.#transactionState.unsafeError = error;
1035
+ }
1036
+ }
1037
+ #recoverTransactionStateAfterSavepoint(depth) {
1038
+ if (this.#transactionState.unsafeDepth === undefined || this.#transactionState.unsafeDepth < depth)
1039
+ return;
1040
+ this.#transactionState.unsafe = false;
1041
+ this.#transactionState.unsafeDepth = undefined;
1042
+ this.#transactionState.unsafeError = undefined;
1043
+ }
1044
+ #transactionLeaseReleaseError() {
1045
+ if (!this.#transactionState.discardLease)
1046
+ return undefined;
1047
+ return this.#transactionState.firstDatabaseOperationError instanceof Error
1048
+ ? this.#transactionState.firstDatabaseOperationError
1049
+ : true;
1050
+ }
1051
+ #throwIfTransactionCannotCommit() {
1052
+ if (this.#transactionOperationFailed) {
1053
+ if (this.#transactionOperationError !== undefined)
1054
+ throw this.#transactionOperationError;
1055
+ throw new Error("A PostgreSQL transaction operation failed and the transaction cannot be committed");
1056
+ }
1057
+ if (this.#transactionState.unsafe) {
1058
+ throw new Error("The PostgreSQL transaction connection did not complete recovery and cannot be committed", {
1059
+ cause: this.#transactionState.unsafeError,
1060
+ });
1061
+ }
1062
+ }
1063
+ #assertTransactionScopeOpen() {
1064
+ if (this.#client === undefined)
1065
+ return;
1066
+ if (!this.#transactionScopeOpen || !this.#transactionState.valid) {
1067
+ throw new Error("This PostgreSQL transaction scope has ended and can no longer be used");
1068
+ }
1069
+ if (this.#transactionOperationFailed) {
1070
+ throw new Error("This PostgreSQL transaction scope cannot continue after a database operation failed", {
1071
+ cause: this.#transactionOperationError,
1072
+ });
1073
+ }
1074
+ if (this.#transactionState.unsafe) {
1075
+ throw new Error("The PostgreSQL transaction cannot continue until its failed operation is rolled back", {
1076
+ cause: this.#transactionState.unsafeError,
1077
+ });
1078
+ }
1079
+ }
179
1080
  async close() {
180
1081
  if (this.#client !== undefined)
181
1082
  throw new Error("Cannot close a database from inside a transaction");
@@ -185,6 +1086,6 @@ class PostgresDatabaseImplementation {
185
1086
  }
186
1087
  _a = PostgresDatabaseImplementation;
187
1088
  export function createPostgresDatabase(options) {
188
- return new PostgresDatabaseImplementation(options.pool, undefined, createPostgresTypeParsers(options.typePolicy ?? defaultPolicy, options.decimal, options.fallbackTypeParsers), options.ownsPool ?? false, 0);
1089
+ return new PostgresDatabaseImplementation(options.pool, undefined, createPostgresTypeParsers(options.typePolicy ?? defaultPolicy, options.decimal, options.fallbackTypeParsers), options.ownsPool ?? false, 0, createPostgresPreparedQueryState(), createPostgresObservationState(options.observer));
189
1090
  }
190
1091
  //# sourceMappingURL=runtime.js.map