kysely 0.29.6 → 0.29.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  [![Stand With Ukraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner2-direct.svg)](https://stand-with-ukraine.pp.ua)
2
2
 
3
3
  [![NPM Version](https://img.shields.io/npm/v/kysely?style=flat&label=latest)](https://github.com/kysely-org/kysely/releases/latest)
4
- [![Socket Badge](https://badge.socket.dev/npm/package/kysely/0.29.6)](https://socket.dev/npm/package/kysely/overview/0.29.6)
4
+ [![Socket Badge](https://badge.socket.dev/npm/package/kysely/0.29.8)](https://socket.dev/npm/package/kysely/overview/0.29.8)
5
5
  [![Tests](https://github.com/kysely-org/kysely/actions/workflows/test.yml/badge.svg)](https://github.com/kysely-org/kysely)
6
6
  [![License](https://img.shields.io/github/license/kysely-org/kysely?style=flat)](https://github.com/kysely-org/kysely/blob/master/LICENSE)
7
7
  [![Issues](https://img.shields.io/github/issues-closed/kysely-org/kysely?logo=github)](https://github.com/kysely-org/kysely/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc)
@@ -25,6 +25,9 @@ export declare class MssqlDriver implements Driver {
25
25
  beginTransaction(connection: MssqlConnection, settings: TransactionSettings): Promise<void>;
26
26
  /**
27
27
  * Commits a transaction.
28
+ *
29
+ * Throws {@link AbortedTransactionError} if the database confirms that
30
+ * the transaction was rolled back instead of committed.
28
31
  */
29
32
  commitTransaction(connection: MssqlConnection): Promise<void>;
30
33
  /**
@@ -19,6 +19,16 @@ export interface MysqlDialectConfig {
19
19
  *
20
20
  * If a function is provided, it's called once when the first query is executed.
21
21
  *
22
+ * Once the driver has initialized, {@link Kysely.destroy} calls
23
+ * `pool.end(callback)`, whether the pool is supplied directly or returned by a
24
+ * function. If initialization hasn't started, `db.destroy()` does nothing,
25
+ * even if the supplied pool has been used outside Kysely.
26
+ *
27
+ * If Kysely is not the sole consumer of the pool, or the pool's lifecycle is
28
+ * managed elsewhere, skip `db.destroy()`. Let the application call
29
+ * `pool.end(callback)` once all consumers, including Kysely, have finished. No
30
+ * separate cleanup of the `Kysely` instance is needed.
31
+ *
22
32
  * https://github.com/sidorares/node-mysql2#using-connection-pools
23
33
  */
24
34
  pool: MysqlPool | ((options?: AbortableOperationOptions) => Promise<MysqlPool>);
@@ -25,6 +25,9 @@ export declare class MysqlDriver implements Driver {
25
25
  beginTransaction(connection: DatabaseConnection, settings: TransactionSettings): Promise<void>;
26
26
  /**
27
27
  * Commits a transaction.
28
+ *
29
+ * Throws {@link AbortedTransactionError} if the database confirms that
30
+ * the transaction was rolled back instead of committed.
28
31
  */
29
32
  commitTransaction(connection: DatabaseConnection): Promise<void>;
30
33
  /**
@@ -16,6 +16,16 @@ export interface PGliteDialectConfig {
16
16
  *
17
17
  * If a function is provided, it's called once when the first query is executed.
18
18
  *
19
+ * Once the driver has initialized, {@link Kysely.destroy} calls `pglite.close()`
20
+ * if the instance is still open, whether it is supplied directly or returned
21
+ * by a function. If initialization hasn't started, `db.destroy()` does nothing,
22
+ * even if the supplied instance has been used outside Kysely.
23
+ *
24
+ * If Kysely is not the sole consumer of the instance, or its lifecycle is
25
+ * managed elsewhere, skip `db.destroy()`. Let the application call
26
+ * `pglite.close()` once all consumers, including Kysely, have finished. No
27
+ * separate cleanup of the `Kysely` instance is needed.
28
+ *
19
29
  * https://pglite.dev/docs/api#main-constructor
20
30
  */
21
31
  pglite: PGlite | ((options?: AbortableOperationOptions) => PGlite | Promise<PGlite>);
@@ -20,6 +20,9 @@ export declare class PGliteDriver implements Driver {
20
20
  beginTransaction(connection: PGliteConnection): Promise<void>;
21
21
  /**
22
22
  * Commits a transaction.
23
+ *
24
+ * Throws {@link AbortedTransactionError} if the database confirms that
25
+ * the transaction was rolled back instead of committed.
23
26
  */
24
27
  commitTransaction(connection: PGliteConnection): Promise<void>;
25
28
  /**
@@ -42,6 +42,16 @@ export interface PostgresDialectConfig {
42
42
  *
43
43
  * If a function is provided, it's called once when the first query is executed.
44
44
  *
45
+ * Once the driver has initialized, {@link Kysely.destroy} calls `pool.end()`,
46
+ * whether the pool is supplied directly or returned by a function. If
47
+ * initialization hasn't started, `db.destroy()` does nothing, even if the
48
+ * supplied pool has been used outside Kysely.
49
+ *
50
+ * If Kysely is not the sole consumer of the pool, or the pool's lifecycle is
51
+ * managed elsewhere, skip `db.destroy()`. Let the application call `pool.end()`
52
+ * once all consumers, including Kysely, have finished. No separate cleanup of
53
+ * the `Kysely` instance is needed.
54
+ *
45
55
  * https://node-postgres.com/apis/pool
46
56
  */
47
57
  pool: PostgresPool | ((options?: AbortableOperationOptions) => Promise<PostgresPool>);
@@ -85,7 +95,7 @@ export type PostgresClientConstructor = new (options: any) => PostgresClient;
85
95
  * https://node-postgres.com/apis/pool#releasing-clients
86
96
  */
87
97
  export interface PostgresPoolClient extends Omit<PostgresClient, 'connect' | 'end'> {
88
- release(): void;
98
+ release(destroy?: boolean): void;
89
99
  }
90
100
  /**
91
101
  * This interface is pg driver's `Cursor` class that kysely needs.
@@ -114,7 +124,7 @@ export type PostgresCursorConstructor = new <T>(sql: string, parameters: unknown
114
124
  * https://node-postgres.com/apis/result
115
125
  */
116
126
  export interface PostgresQueryResult<R> {
117
- command: 'UPDATE' | 'DELETE' | 'INSERT' | 'SELECT' | 'MERGE';
127
+ command: 'UPDATE' | 'DELETE' | 'INSERT' | 'SELECT' | 'MERGE' | 'COMMIT' | 'ROLLBACK';
118
128
  rowCount: number;
119
129
  rows: R[];
120
130
  }
@@ -5,6 +5,7 @@ import type { QueryCompiler } from '../../query-compiler/query-compiler.js';
5
5
  import type { AbortableOperationOptions } from '../../util/abort.js';
6
6
  import type { PostgresClientConstructor, PostgresCursorConstructor, PostgresDialectConfig, PostgresPoolClient } from './postgres-dialect-config.js';
7
7
  declare const PRIVATE_RELEASE_METHOD: unique symbol;
8
+ declare const PRIVATE_MARK_FOR_DISCARD_METHOD: unique symbol;
8
9
  export declare class PostgresDriver implements Driver {
9
10
  #private;
10
11
  constructor(config: PostgresDialectConfig);
@@ -25,12 +26,15 @@ export declare class PostgresDriver implements Driver {
25
26
  beginTransaction(connection: DatabaseConnection, settings: TransactionSettings): Promise<void>;
26
27
  /**
27
28
  * Commits a transaction.
29
+ *
30
+ * Throws {@link AbortedTransactionError} if the database confirms that
31
+ * the transaction was rolled back instead of committed.
28
32
  */
29
33
  commitTransaction(connection: DatabaseConnection): Promise<void>;
30
34
  /**
31
35
  * Rolls back a transaction.
32
36
  */
33
- rollbackTransaction(connection: DatabaseConnection): Promise<void>;
37
+ rollbackTransaction(connection: PostgresConnection): Promise<void>;
34
38
  savepoint(connection: DatabaseConnection, savepointName: string, compileQuery: QueryCompiler['compileQuery']): Promise<void>;
35
39
  rollbackToSavepoint(connection: DatabaseConnection, savepointName: string, compileQuery: QueryCompiler['compileQuery']): Promise<void>;
36
40
  releaseSavepoint(connection: DatabaseConnection, savepointName: string, compileQuery: QueryCompiler['compileQuery']): Promise<void>;
@@ -57,5 +61,6 @@ declare class PostgresConnection implements DatabaseConnection {
57
61
  killSession(controlConnectionProvider: ControlConnectionProvider): Promise<void>;
58
62
  streamQuery<O>(compiledQuery: CompiledQuery, chunkSize: number): AsyncIterableIterator<QueryResult<O>>;
59
63
  [PRIVATE_RELEASE_METHOD](): void;
64
+ [PRIVATE_MARK_FOR_DISCARD_METHOD](): void;
60
65
  }
61
66
  export {};
@@ -1,10 +1,12 @@
1
1
  /// <reference types="./postgres-driver.d.ts" />
2
+ import { AbortedTransactionError } from '../../driver/aborted-transaction-error.js';
2
3
  import { parseSavepointCommand } from '../../parser/savepoint-parser.js';
3
4
  import { CompiledQuery } from '../../query-compiler/compiled-query.js';
4
5
  import { isFunction, freeze } from '../../util/object-utils.js';
5
6
  import { createQueryId } from '../../util/query-id.js';
6
7
  import { extendStackTrace } from '../../util/stack-trace-utils.js';
7
8
  const PRIVATE_RELEASE_METHOD = Symbol();
9
+ const PRIVATE_MARK_FOR_DISCARD_METHOD = Symbol();
8
10
  export class PostgresDriver {
9
11
  #config;
10
12
  #connections = new WeakMap();
@@ -19,6 +21,11 @@ export class PostgresDriver {
19
21
  }
20
22
  async acquireConnection(options) {
21
23
  const client = await this.#pool.connect();
24
+ // `destroy` was called while the connection was being acquired.
25
+ if (!this.#pool) {
26
+ client.release();
27
+ throw new Error('driver has already been destroyed');
28
+ }
22
29
  let connection = this.#connections.get(client);
23
30
  if (!connection) {
24
31
  connection = new PostgresConnection(client, {
@@ -53,10 +60,21 @@ export class PostgresDriver {
53
60
  await connection.executeQuery(CompiledQuery.raw(sql));
54
61
  }
55
62
  async commitTransaction(connection) {
56
- await connection.executeQuery(CompiledQuery.raw('commit'));
63
+ const { rows } = await connection.executeQuery(CompiledQuery.raw('commit'));
64
+ // PostgreSQL completes COMMIT with a ROLLBACK tag if an earlier
65
+ // statement error left the transaction aborted.
66
+ if (rows[0]?.command === 'ROLLBACK') {
67
+ throw new AbortedTransactionError();
68
+ }
57
69
  }
58
70
  async rollbackTransaction(connection) {
59
- await connection.executeQuery(CompiledQuery.raw('rollback'));
71
+ try {
72
+ await connection.executeQuery(CompiledQuery.raw('rollback'));
73
+ }
74
+ catch (err) {
75
+ connection[PRIVATE_MARK_FOR_DISCARD_METHOD]();
76
+ throw err;
77
+ }
60
78
  }
61
79
  async savepoint(connection, savepointName, compileQuery) {
62
80
  await connection.executeQuery(compileQuery(parseSavepointCommand('savepoint', savepointName), createQueryId()));
@@ -83,6 +101,7 @@ class PostgresConnection {
83
101
  #options;
84
102
  #queryId;
85
103
  #pid;
104
+ #discarded;
86
105
  constructor(client, options) {
87
106
  this.#client = client;
88
107
  this.#options = options;
@@ -102,16 +121,21 @@ class PostgresConnection {
102
121
  this.#pid = processID;
103
122
  }
104
123
  else {
124
+ this.#assertNotDiscarded();
105
125
  const { rows: [{ pid }], } = await this.#client.query('select pg_backend_pid() as pid', []);
106
126
  this.#pid = Number(pid);
107
127
  }
108
128
  }
109
129
  async executeQuery(compiledQuery) {
130
+ this.#assertNotDiscarded();
110
131
  try {
111
132
  // this helps ensure we don't cancel the wrong query when aborted.
112
133
  this.#queryId = compiledQuery.queryId;
113
134
  const result = await this.#client.query(compiledQuery.sql, compiledQuery.parameters);
114
135
  const { command, rowCount, rows } = result;
136
+ if (command === 'ROLLBACK' || command === 'COMMIT') {
137
+ return { rows: [{ command }] };
138
+ }
115
139
  return {
116
140
  numAffectedRows: command === 'INSERT' ||
117
141
  command === 'UPDATE' ||
@@ -137,6 +161,7 @@ class PostgresConnection {
137
161
  if (!this.#options.cursor) {
138
162
  throw new Error("`cursor` is not present in your postgres dialect config. It's required to make streaming work in postgres.");
139
163
  }
164
+ this.#assertNotDiscarded();
140
165
  // this helps ensure we don't cancel the wrong query when aborted.
141
166
  this.#queryId = compiledQuery.queryId;
142
167
  const cursor = this.#client.query(new this.#options.cursor(compiledQuery.sql, compiledQuery.parameters.slice()));
@@ -158,7 +183,15 @@ class PostgresConnection {
158
183
  }
159
184
  }
160
185
  [PRIVATE_RELEASE_METHOD]() {
161
- this.#client.release();
186
+ this.#client.release(Boolean(this.#discarded));
187
+ }
188
+ [PRIVATE_MARK_FOR_DISCARD_METHOD]() {
189
+ this.#discarded = true;
190
+ }
191
+ #assertNotDiscarded() {
192
+ if (this.#discarded) {
193
+ throw new Error('Postgres connection has been discarded');
194
+ }
162
195
  }
163
196
  async #executeControlQuery(query, controlConnectionProvider) {
164
197
  if (!this.#queryId) {
@@ -9,6 +9,16 @@ export interface SqliteDialectConfig {
9
9
  *
10
10
  * If a function is provided, it's called once when the first query is executed.
11
11
  *
12
+ * Once the driver has initialized, {@link Kysely.destroy} calls
13
+ * `database.close()`, whether the database is supplied directly or returned by
14
+ * a function. If initialization hasn't started, `db.destroy()` does nothing,
15
+ * even if the supplied database has been used outside Kysely.
16
+ *
17
+ * If Kysely is not the sole consumer of the database, or the database's
18
+ * lifecycle is managed elsewhere, skip `db.destroy()`. Let the application
19
+ * call `database.close()` once all consumers, including Kysely, have finished.
20
+ * No separate cleanup of the `Kysely` instance is needed.
21
+ *
12
22
  * https://github.com/JoshuaWise/better-sqlite3/blob/master/docs/api.md#new-databasepath-options
13
23
  */
14
24
  database: SqliteDatabase | ((options?: AbortableOperationOptions) => Promise<SqliteDatabase>);
@@ -23,6 +23,9 @@ export declare class SqliteDriver implements Driver {
23
23
  beginTransaction(connection: DatabaseConnection): Promise<void>;
24
24
  /**
25
25
  * Commits a transaction.
26
+ *
27
+ * Throws {@link AbortedTransactionError} if the database confirms that
28
+ * the transaction was rolled back instead of committed.
26
29
  */
27
30
  commitTransaction(connection: DatabaseConnection): Promise<void>;
28
31
  /**
@@ -0,0 +1,11 @@
1
+ declare const ABORTED_TRANSACTION_ERROR: unique symbol;
2
+ /**
3
+ * Thrown when the database rolls back a transaction instead of committing it.
4
+ */
5
+ export declare class AbortedTransactionError extends Error {
6
+ name: string;
7
+ readonly [ABORTED_TRANSACTION_ERROR] = true;
8
+ constructor();
9
+ }
10
+ export declare function isAbortedTransactionError(error: unknown): error is AbortedTransactionError;
11
+ export {};
@@ -0,0 +1,16 @@
1
+ /// <reference types="./aborted-transaction-error.d.ts" />
2
+ import { isObject } from '../util/object-utils.js';
3
+ const ABORTED_TRANSACTION_ERROR = Symbol.for('kysely.AbortedTransactionError');
4
+ /**
5
+ * Thrown when the database rolls back a transaction instead of committing it.
6
+ */
7
+ export class AbortedTransactionError extends Error {
8
+ name = 'AbortedTransactionError';
9
+ [ABORTED_TRANSACTION_ERROR] = true;
10
+ constructor() {
11
+ super('Transaction was rolled back instead of committed');
12
+ }
13
+ }
14
+ export function isAbortedTransactionError(error) {
15
+ return isObject(error) && ABORTED_TRANSACTION_ERROR in error;
16
+ }
@@ -24,6 +24,9 @@ export interface Driver {
24
24
  beginTransaction(connection: DatabaseConnection, settings: TransactionSettings): Promise<void>;
25
25
  /**
26
26
  * Commits a transaction.
27
+ *
28
+ * Throws {@link AbortedTransactionError} if the database confirms that
29
+ * the transaction was rolled back instead of committed.
27
30
  */
28
31
  commitTransaction(connection: DatabaseConnection): Promise<void>;
29
32
  /**
@@ -55,6 +55,9 @@ export declare class DummyDriver implements Driver {
55
55
  beginTransaction(): Promise<void>;
56
56
  /**
57
57
  * Commits a transaction.
58
+ *
59
+ * Throws {@link AbortedTransactionError} if the database confirms that
60
+ * the transaction was rolled back instead of committed.
58
61
  */
59
62
  commitTransaction(): Promise<void>;
60
63
  /**
@@ -33,6 +33,9 @@ export declare class RuntimeDriver implements Driver {
33
33
  beginTransaction(connection: DatabaseConnection, settings: TransactionSettings): Promise<void>;
34
34
  /**
35
35
  * Commits a transaction.
36
+ *
37
+ * Throws {@link AbortedTransactionError} if the database confirms that
38
+ * the transaction was rolled back instead of committed.
36
39
  */
37
40
  commitTransaction(connection: DatabaseConnection): Promise<void>;
38
41
  /**
@@ -202,7 +202,9 @@ export declare function jsonObjectFrom<O>(expr: Expression<O>): RawBuilder<Simpl
202
202
  *
203
203
  * ```sql
204
204
  * select "id", json_query(
205
- * '{"first":"'+"first_name"+',"last":"'+"last_name"+',"full":"'+concat("first_name", ' ', "last_name")+'"}'
205
+ * '{"'+string_escape(N'first', 'json')+'":"'+string_escape("first_name", 'json')+
206
+ * '","'+string_escape(N'last', 'json')+'":"'+string_escape("last_name", 'json')+
207
+ * '","'+string_escape(N'full', 'json')+'":"'+string_escape(concat("first_name", ' ', "last_name"), 'json')+'"}'
206
208
  * ) as "name"
207
209
  * from "person"
208
210
  * ```
@@ -205,11 +205,13 @@ export function jsonObjectFrom(expr) {
205
205
  *
206
206
  * ```sql
207
207
  * select "id", json_query(
208
- * '{"first":"'+"first_name"+',"last":"'+"last_name"+',"full":"'+concat("first_name", ' ', "last_name")+'"}'
208
+ * '{"'+string_escape(N'first', 'json')+'":"'+string_escape("first_name", 'json')+
209
+ * '","'+string_escape(N'last', 'json')+'":"'+string_escape("last_name", 'json')+
210
+ * '","'+string_escape(N'full', 'json')+'":"'+string_escape(concat("first_name", ' ', "last_name"), 'json')+'"}'
209
211
  * ) as "name"
210
212
  * from "person"
211
213
  * ```
212
214
  */
213
215
  export function jsonBuildObject(obj) {
214
- return sql `json_query('{${sql.join(Object.keys(obj).map((k) => sql `"${sql.raw(k)}":"'+${obj[k]}+'"`), sql `,`)}}')`;
216
+ return sql `json_query('{${sql.join(Object.keys(obj).map((k) => sql `"'+string_escape(N${sql.lit(k)}, 'json')+'":"'+string_escape(${obj[k]}, 'json')+'"`), sql `,`)}}')`;
215
217
  }
package/dist/index.d.ts CHANGED
@@ -60,6 +60,7 @@ export * from './dynamic/dynamic.js';
60
60
  export * from './dynamic/dynamic-reference-builder.js';
61
61
  export * from './dynamic/dynamic-table-builder.js';
62
62
  export * from './driver/driver.js';
63
+ export * from './driver/aborted-transaction-error.js';
63
64
  export type * from './driver/database-connection.js';
64
65
  export type * from './driver/connection-provider.js';
65
66
  export * from './driver/default-connection-provider.js';
package/dist/index.js CHANGED
@@ -53,6 +53,7 @@ export * from './dynamic/dynamic.js';
53
53
  export * from './dynamic/dynamic-reference-builder.js';
54
54
  export * from './dynamic/dynamic-table-builder.js';
55
55
  export * from './driver/driver.js';
56
+ export * from './driver/aborted-transaction-error.js';
56
57
  export * from './driver/default-connection-provider.js';
57
58
  export * from './driver/single-connection-provider.js';
58
59
  export * from './driver/dummy-driver.js';
package/dist/kysely.d.ts CHANGED
@@ -29,7 +29,8 @@ declare global {
29
29
  * The main Kysely class.
30
30
  *
31
31
  * You should create one instance of `Kysely` per database using the {@link Kysely}
32
- * constructor. Each `Kysely` instance maintains its own connection pool.
32
+ * constructor. The dialect manages database connections and may use a shared
33
+ * pool or database instance.
33
34
  *
34
35
  * ### Examples
35
36
  *
@@ -450,7 +451,16 @@ export declare class Kysely<DB> extends QueryCreator<DB> implements QueryExecuto
450
451
  /**
451
452
  * Releases all resources and disconnects from the database.
452
453
  *
453
- * You need to call this when you are done using the `Kysely` instance.
454
+ * Call this when you are done using the `Kysely` instance and Kysely is
455
+ * responsible for closing its connections, pool, or database instance.
456
+ *
457
+ * If the pool or database instance is shared with other consumers or its
458
+ * lifecycle is managed elsewhere, skip this call. Let the application close it
459
+ * once all consumers, including Kysely, have finished. No separate cleanup of
460
+ * the `Kysely` instance is needed.
461
+ *
462
+ * The driver is initialized lazily. If initialization hasn't started, this
463
+ * method does nothing.
454
464
  */
455
465
  destroy(): Promise<void>;
456
466
  /**
@@ -616,6 +626,13 @@ export declare class ControlledTransaction<DB, S extends string[] = []> extends
616
626
  /**
617
627
  * Commits the transaction.
618
628
  *
629
+ * If the database rolls back instead of committing, marks the transaction
630
+ * as rolled back, releases the connection and throws {@link AbortedTransactionError}.
631
+ *
632
+ * Waits for ongoing queries to finish. Streams must be exhausted or closed
633
+ * before awaiting this command. When consuming a stream with `for await`,
634
+ * exit the loop before committing.
635
+ *
619
636
  * See {@link rollback}.
620
637
  *
621
638
  * ### Examples
@@ -641,6 +658,16 @@ export declare class ControlledTransaction<DB, S extends string[] = []> extends
641
658
  /**
642
659
  * Rolls back the transaction.
643
660
  *
661
+ * If a commit already reported that the database rolled back the transaction,
662
+ * succeeds without sending another rollback command.
663
+ *
664
+ * The connection is released even if the rollback fails, so the transaction
665
+ * cannot be used afterwards in either case.
666
+ *
667
+ * Waits for ongoing queries to finish. Streams must be exhausted or closed
668
+ * before awaiting this command. When consuming a stream with `for await`,
669
+ * exit the loop before rolling back.
670
+ *
644
671
  * See {@link commit} and {@link rollbackToSavepoint}.
645
672
  *
646
673
  * ### Examples
@@ -666,6 +693,10 @@ export declare class ControlledTransaction<DB, S extends string[] = []> extends
666
693
  /**
667
694
  * Creates a savepoint with a given name.
668
695
  *
696
+ * Waits for ongoing queries to finish. Streams must be exhausted or closed
697
+ * before awaiting this command. When consuming a stream with `for await`,
698
+ * exit the loop before creating a savepoint.
699
+ *
669
700
  * See {@link rollbackToSavepoint} and {@link releaseSavepoint}.
670
701
  *
671
702
  * For a type-safe experience, you should use the returned instance from now on.
@@ -696,6 +727,10 @@ export declare class ControlledTransaction<DB, S extends string[] = []> extends
696
727
  /**
697
728
  * Rolls back to a savepoint with a given name.
698
729
  *
730
+ * Waits for ongoing queries to finish. Streams must be exhausted or closed
731
+ * before awaiting this command. When consuming a stream with `for await`,
732
+ * exit the loop before rolling back to a savepoint.
733
+ *
699
734
  * See {@link savepoint} and {@link releaseSavepoint}.
700
735
  *
701
736
  * You must use the same instance returned by {@link savepoint}, or
@@ -727,6 +762,10 @@ export declare class ControlledTransaction<DB, S extends string[] = []> extends
727
762
  /**
728
763
  * Releases a savepoint with a given name.
729
764
  *
765
+ * Waits for ongoing queries to finish. Streams must be exhausted or closed
766
+ * before awaiting this command. When consuming a stream with `for await`,
767
+ * exit the loop before releasing a savepoint.
768
+ *
730
769
  * See {@link savepoint} and {@link rollbackToSavepoint}.
731
770
  *
732
771
  * You must use the same instance returned by {@link savepoint}, or
@@ -782,8 +821,12 @@ export declare class ControlledTransaction<DB, S extends string[] = []> extends
782
821
  $omitTables<T extends keyof DB>(): ControlledTransaction<DB extends object ? Omit<DB, T> : DB, S>;
783
822
  $pickTables<T extends keyof DB>(): ControlledTransaction<DB extends object ? Pick<DB, T> : DB, S>;
784
823
  }
824
+ interface ControlledTransactionState {
825
+ status: 'active' | 'committed' | 'rolled back' | 'aborted';
826
+ }
785
827
  interface ControlledTransactionProps extends KyselyProps {
786
828
  readonly connection: ControlledConnection;
829
+ readonly transactionState: ControlledTransactionState;
787
830
  }
788
831
  export declare class Command<T> {
789
832
  #private;
package/dist/kysely.js CHANGED
@@ -6,6 +6,7 @@ import { QueryCreator } from './query-creator.js';
6
6
  import { DefaultQueryExecutor } from './query-executor/default-query-executor.js';
7
7
  import { freeze, isObject, isUndefined } from './util/object-utils.js';
8
8
  import { RuntimeDriver } from './driver/runtime-driver.js';
9
+ import { isAbortedTransactionError, } from './driver/aborted-transaction-error.js';
9
10
  import { SingleConnectionProvider } from './driver/single-connection-provider.js';
10
11
  import { validateTransactionSettings, } from './driver/driver.js';
11
12
  import { createFunctionModule, } from './query-builder/function-module.js';
@@ -23,7 +24,8 @@ Symbol.asyncDispose ??= Symbol('Symbol.asyncDispose');
23
24
  * The main Kysely class.
24
25
  *
25
26
  * You should create one instance of `Kysely` per database using the {@link Kysely}
26
- * constructor. Each `Kysely` instance maintains its own connection pool.
27
+ * constructor. The dialect manages database connections and may use a shared
28
+ * pool or database instance.
27
29
  *
28
30
  * ### Examples
29
31
  *
@@ -505,7 +507,16 @@ export class Kysely extends QueryCreator {
505
507
  /**
506
508
  * Releases all resources and disconnects from the database.
507
509
  *
508
- * You need to call this when you are done using the `Kysely` instance.
510
+ * Call this when you are done using the `Kysely` instance and Kysely is
511
+ * responsible for closing its connections, pool, or database instance.
512
+ *
513
+ * If the pool or database instance is shared with other consumers or its
514
+ * lifecycle is managed elsewhere, skip this call. Let the application close it
515
+ * once all consumers, including Kysely, have finished. No separate cleanup of
516
+ * the `Kysely` instance is needed.
517
+ *
518
+ * The driver is initialized lazily. If initialization hasn't started, this
519
+ * method does nothing.
509
520
  */
510
521
  async destroy() {
511
522
  await this.#props.driver.destroy();
@@ -670,8 +681,10 @@ export class TransactionBuilder {
670
681
  const settings = { isolationLevel, accessMode };
671
682
  validateTransactionSettings(settings);
672
683
  return this.#props.executor.provideConnection(async (connection) => {
673
- const state = { isCommitted: false, isRolledBack: false };
674
- const executor = new NotCommittedOrRolledBackAssertingExecutor(this.#props.executor.withConnectionProvider(new SingleConnectionProvider(connection)), state);
684
+ const transactionState = {
685
+ status: 'active',
686
+ };
687
+ const executor = new NotCommittedOrRolledBackAssertingExecutor(this.#props.executor.withConnectionProvider(new SingleConnectionProvider(connection)), transactionState);
675
688
  const transaction = new Transaction({ ...kyselyProps, executor });
676
689
  let transactionBegun = false;
677
690
  try {
@@ -679,13 +692,16 @@ export class TransactionBuilder {
679
692
  transactionBegun = true;
680
693
  const result = await callback(transaction);
681
694
  await this.#props.driver.commitTransaction(connection);
682
- state.isCommitted = true;
695
+ transactionState.status = 'committed';
683
696
  return result;
684
697
  }
685
698
  catch (error) {
686
- if (transactionBegun) {
699
+ if (isAbortedTransactionError(error)) {
700
+ transactionState.status = 'aborted';
701
+ }
702
+ else if (transactionBegun) {
687
703
  await this.#props.driver.rollbackTransaction(connection);
688
- state.isRolledBack = true;
704
+ transactionState.status = 'rolled back';
689
705
  }
690
706
  throw error;
691
707
  }
@@ -714,10 +730,17 @@ export class ControlledTransactionBuilder {
714
730
  const settings = { isolationLevel, accessMode };
715
731
  validateTransactionSettings(settings);
716
732
  const connection = await provideControlledConnection(this.#props.executor);
717
- await this.#props.driver.beginTransaction(connection.connection, settings);
733
+ try {
734
+ await this.#props.driver.beginTransaction(connection.connection, settings);
735
+ }
736
+ catch (error) {
737
+ connection.release();
738
+ throw error;
739
+ }
718
740
  return new ControlledTransaction({
719
741
  ...props,
720
742
  connection,
743
+ transactionState: { status: 'active' },
721
744
  executor: this.#props.executor.withConnectionProvider(new SingleConnectionProvider(connection.connection)),
722
745
  });
723
746
  }
@@ -725,29 +748,34 @@ export class ControlledTransactionBuilder {
725
748
  export class ControlledTransaction extends Transaction {
726
749
  #props;
727
750
  #compileQuery;
728
- #state;
729
751
  constructor(props) {
730
- const state = { isCommitted: false, isRolledBack: false };
731
752
  props = {
732
753
  ...props,
733
- executor: new NotCommittedOrRolledBackAssertingExecutor(props.executor, state),
754
+ executor: new NotCommittedOrRolledBackAssertingExecutor(props.executor, props.transactionState),
734
755
  };
735
- const { connection, ...transactionProps } = props;
756
+ const { connection, transactionState, ...transactionProps } = props;
736
757
  super(transactionProps);
737
758
  this.#props = freeze(props);
738
- this.#state = state;
739
759
  const queryId = createQueryId();
740
760
  this.#compileQuery = (node) => props.executor.compileQuery(node, queryId);
741
761
  }
742
762
  get isCommitted() {
743
- return this.#state.isCommitted;
763
+ return this.#props.transactionState.status === 'committed';
744
764
  }
745
765
  get isRolledBack() {
746
- return this.#state.isRolledBack;
766
+ return (this.#props.transactionState.status === 'rolled back' ||
767
+ this.#props.transactionState.status === 'aborted');
747
768
  }
748
769
  /**
749
770
  * Commits the transaction.
750
771
  *
772
+ * If the database rolls back instead of committing, marks the transaction
773
+ * as rolled back, releases the connection and throws {@link AbortedTransactionError}.
774
+ *
775
+ * Waits for ongoing queries to finish. Streams must be exhausted or closed
776
+ * before awaiting this command. When consuming a stream with `for await`,
777
+ * exit the loop before committing.
778
+ *
751
779
  * See {@link rollback}.
752
780
  *
753
781
  * ### Examples
@@ -770,16 +798,36 @@ export class ControlledTransaction extends Transaction {
770
798
  * ```
771
799
  */
772
800
  commit() {
773
- assertNotCommittedOrRolledBack(this.#state);
774
801
  return new Command(async () => {
775
- await this.#props.driver.commitTransaction(this.#props.connection.connection);
776
- this.#state.isCommitted = true;
777
- this.#props.connection.release();
802
+ await this.#props.executor.provideConnection(async (connection) => {
803
+ try {
804
+ await this.#props.driver.commitTransaction(connection);
805
+ }
806
+ catch (error) {
807
+ if (isAbortedTransactionError(error)) {
808
+ this.#props.transactionState.status = 'aborted';
809
+ this.#props.connection.release();
810
+ }
811
+ throw error;
812
+ }
813
+ this.#props.transactionState.status = 'committed';
814
+ this.#props.connection.release();
815
+ });
778
816
  });
779
817
  }
780
818
  /**
781
819
  * Rolls back the transaction.
782
820
  *
821
+ * If a commit already reported that the database rolled back the transaction,
822
+ * succeeds without sending another rollback command.
823
+ *
824
+ * The connection is released even if the rollback fails, so the transaction
825
+ * cannot be used afterwards in either case.
826
+ *
827
+ * Waits for ongoing queries to finish. Streams must be exhausted or closed
828
+ * before awaiting this command. When consuming a stream with `for await`,
829
+ * exit the loop before rolling back.
830
+ *
783
831
  * See {@link commit} and {@link rollbackToSavepoint}.
784
832
  *
785
833
  * ### Examples
@@ -802,16 +850,29 @@ export class ControlledTransaction extends Transaction {
802
850
  * ```
803
851
  */
804
852
  rollback() {
805
- assertNotCommittedOrRolledBack(this.#state);
806
853
  return new Command(async () => {
807
- await this.#props.driver.rollbackTransaction(this.#props.connection.connection);
808
- this.#state.isRolledBack = true;
809
- this.#props.connection.release();
854
+ if (this.#props.transactionState.status === 'aborted') {
855
+ return;
856
+ }
857
+ await this.#props.executor.provideConnection(async (connection) => {
858
+ // A failed rollback can't be meaningfully retried (e.g. dead connection).
859
+ try {
860
+ await this.#props.driver.rollbackTransaction(connection);
861
+ }
862
+ finally {
863
+ this.#props.transactionState.status = 'rolled back';
864
+ this.#props.connection.release();
865
+ }
866
+ });
810
867
  });
811
868
  }
812
869
  /**
813
870
  * Creates a savepoint with a given name.
814
871
  *
872
+ * Waits for ongoing queries to finish. Streams must be exhausted or closed
873
+ * before awaiting this command. When consuming a stream with `for await`,
874
+ * exit the loop before creating a savepoint.
875
+ *
815
876
  * See {@link rollbackToSavepoint} and {@link releaseSavepoint}.
816
877
  *
817
878
  * For a type-safe experience, you should use the returned instance from now on.
@@ -839,15 +900,20 @@ export class ControlledTransaction extends Transaction {
839
900
  * ```
840
901
  */
841
902
  savepoint(savepointName) {
842
- assertNotCommittedOrRolledBack(this.#state);
843
903
  return new Command(async () => {
844
- await this.#props.driver.savepoint?.(this.#props.connection.connection, savepointName, this.#compileQuery);
904
+ await this.#props.executor.provideConnection(async (connection) => {
905
+ await this.#props.driver.savepoint?.(connection, savepointName, this.#compileQuery);
906
+ });
845
907
  return new ControlledTransaction({ ...this.#props });
846
908
  });
847
909
  }
848
910
  /**
849
911
  * Rolls back to a savepoint with a given name.
850
912
  *
913
+ * Waits for ongoing queries to finish. Streams must be exhausted or closed
914
+ * before awaiting this command. When consuming a stream with `for await`,
915
+ * exit the loop before rolling back to a savepoint.
916
+ *
851
917
  * See {@link savepoint} and {@link releaseSavepoint}.
852
918
  *
853
919
  * You must use the same instance returned by {@link savepoint}, or
@@ -876,15 +942,20 @@ export class ControlledTransaction extends Transaction {
876
942
  * ```
877
943
  */
878
944
  rollbackToSavepoint(savepointName) {
879
- assertNotCommittedOrRolledBack(this.#state);
880
945
  return new Command(async () => {
881
- await this.#props.driver.rollbackToSavepoint?.(this.#props.connection.connection, savepointName, this.#compileQuery);
946
+ await this.#props.executor.provideConnection(async (connection) => {
947
+ await this.#props.driver.rollbackToSavepoint?.(connection, savepointName, this.#compileQuery);
948
+ });
882
949
  return new ControlledTransaction({ ...this.#props });
883
950
  });
884
951
  }
885
952
  /**
886
953
  * Releases a savepoint with a given name.
887
954
  *
955
+ * Waits for ongoing queries to finish. Streams must be exhausted or closed
956
+ * before awaiting this command. When consuming a stream with `for await`,
957
+ * exit the loop before releasing a savepoint.
958
+ *
888
959
  * See {@link savepoint} and {@link rollbackToSavepoint}.
889
960
  *
890
961
  * You must use the same instance returned by {@link savepoint}, or
@@ -918,9 +989,10 @@ export class ControlledTransaction extends Transaction {
918
989
  * ```
919
990
  */
920
991
  releaseSavepoint(savepointName) {
921
- assertNotCommittedOrRolledBack(this.#state);
922
992
  return new Command(async () => {
923
- await this.#props.driver.releaseSavepoint?.(this.#props.connection.connection, savepointName, this.#compileQuery);
993
+ await this.#props.executor.provideConnection(async (connection) => {
994
+ await this.#props.driver.releaseSavepoint?.(connection, savepointName, this.#compileQuery);
995
+ });
924
996
  return new ControlledTransaction({ ...this.#props });
925
997
  });
926
998
  }
@@ -967,11 +1039,11 @@ export class Command {
967
1039
  return await this.#cb();
968
1040
  }
969
1041
  }
970
- function assertNotCommittedOrRolledBack(state) {
971
- if (state.isCommitted) {
1042
+ function assertNotCommittedOrRolledBack(transactionState) {
1043
+ if (transactionState.status === 'committed') {
972
1044
  throw new Error('Transaction is already committed');
973
1045
  }
974
- if (state.isRolledBack) {
1046
+ if (transactionState.status !== 'active') {
975
1047
  throw new Error('Transaction is already rolled back');
976
1048
  }
977
1049
  }
@@ -983,13 +1055,13 @@ function assertNotCommittedOrRolledBack(state) {
983
1055
  */
984
1056
  class NotCommittedOrRolledBackAssertingExecutor {
985
1057
  #executor;
986
- #state;
987
- constructor(executor, state) {
1058
+ #transactionState;
1059
+ constructor(executor, transactionState) {
988
1060
  this.#executor =
989
1061
  executor instanceof NotCommittedOrRolledBackAssertingExecutor
990
1062
  ? executor.#executor
991
1063
  : executor;
992
- this.#state = state;
1064
+ this.#transactionState = transactionState;
993
1065
  }
994
1066
  get adapter() {
995
1067
  return this.#executor.adapter;
@@ -1004,29 +1076,37 @@ class NotCommittedOrRolledBackAssertingExecutor {
1004
1076
  return this.#executor.compileQuery(node, queryId);
1005
1077
  }
1006
1078
  provideConnection(consumer, options) {
1007
- return this.#executor.provideConnection(consumer, options);
1079
+ assertNotCommittedOrRolledBack(this.#transactionState);
1080
+ return this.#executor.provideConnection(async (connection) => {
1081
+ assertNotCommittedOrRolledBack(this.#transactionState);
1082
+ return await consumer(connection);
1083
+ }, options);
1008
1084
  }
1009
1085
  executeQuery(compiledQuery, options) {
1010
- assertNotCommittedOrRolledBack(this.#state);
1011
- return this.#executor.executeQuery(compiledQuery, options);
1086
+ assertNotCommittedOrRolledBack(this.#transactionState);
1087
+ return this.#executor
1088
+ .withConnectionProvider(this)
1089
+ .executeQuery(compiledQuery, options);
1012
1090
  }
1013
1091
  stream(compiledQuery, chunkSize, options) {
1014
- assertNotCommittedOrRolledBack(this.#state);
1015
- return this.#executor.stream(compiledQuery, chunkSize, options);
1092
+ assertNotCommittedOrRolledBack(this.#transactionState);
1093
+ return this.#executor
1094
+ .withConnectionProvider(this)
1095
+ .stream(compiledQuery, chunkSize, options);
1016
1096
  }
1017
1097
  withConnectionProvider(connectionProvider) {
1018
- return new NotCommittedOrRolledBackAssertingExecutor(this.#executor.withConnectionProvider(connectionProvider), this.#state);
1098
+ return new NotCommittedOrRolledBackAssertingExecutor(this.#executor.withConnectionProvider(connectionProvider), this.#transactionState);
1019
1099
  }
1020
1100
  withPlugin(plugin) {
1021
- return new NotCommittedOrRolledBackAssertingExecutor(this.#executor.withPlugin(plugin), this.#state);
1101
+ return new NotCommittedOrRolledBackAssertingExecutor(this.#executor.withPlugin(plugin), this.#transactionState);
1022
1102
  }
1023
1103
  withPlugins(plugins) {
1024
- return new NotCommittedOrRolledBackAssertingExecutor(this.#executor.withPlugins(plugins), this.#state);
1104
+ return new NotCommittedOrRolledBackAssertingExecutor(this.#executor.withPlugins(plugins), this.#transactionState);
1025
1105
  }
1026
1106
  withPluginAtFront(plugin) {
1027
- return new NotCommittedOrRolledBackAssertingExecutor(this.#executor.withPluginAtFront(plugin), this.#state);
1107
+ return new NotCommittedOrRolledBackAssertingExecutor(this.#executor.withPluginAtFront(plugin), this.#transactionState);
1028
1108
  }
1029
1109
  withoutPlugins() {
1030
- return new NotCommittedOrRolledBackAssertingExecutor(this.#executor.withoutPlugins(), this.#state);
1110
+ return new NotCommittedOrRolledBackAssertingExecutor(this.#executor.withoutPlugins(), this.#transactionState);
1031
1111
  }
1032
1112
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kysely",
3
- "version": "0.29.6",
3
+ "version": "0.29.8",
4
4
  "description": "Type safe SQL query builder",
5
5
  "repository": {
6
6
  "type": "git",
@@ -62,67 +62,67 @@
62
62
  ],
63
63
  "devDependencies": {
64
64
  "@arethetypeswrong/cli": "0.18.5",
65
- "@electric-sql/pglite": "0.5.8",
65
+ "@electric-sql/pglite": "0.5.13",
66
66
  "@types/better-sqlite3": "9.6.0",
67
67
  "@types/chai": "5.2.3",
68
68
  "@types/chai-as-promised": "8.0.2",
69
69
  "@types/mocha": "10.0.10",
70
- "@types/node": "26.2.0",
70
+ "@types/node": "26.6.6",
71
71
  "@types/pg": "8.23.1",
72
72
  "@types/pg-cursor": "2.7.2",
73
73
  "@types/prototype-pollution-vulnerable-lodash.merge-dont-upgrade": "npm:@types/lodash.merge@4.6.1",
74
74
  "@types/semver": "7.8.0",
75
- "@types/sinon": "22.0.0",
75
+ "@types/sinon": "22.0.2",
76
76
  "better-sqlite3": "13.0.3",
77
- "chai": "6.2.2",
77
+ "chai": "6.3.0",
78
78
  "chai-as-promised": "8.0.2",
79
79
  "esbuild": "0.28.2",
80
80
  "jsr": "0.14.3",
81
- "mocha": "12.0.1",
82
- "mysql2": "3.24.4",
81
+ "mocha": "12.0.3",
82
+ "mysql2": "3.24.6",
83
83
  "pathe": "2.0.3",
84
- "pg": "8.23.0",
85
- "pg-cursor": "2.22.0",
86
- "playwright": "1.63.0",
87
- "prettier": "3.9.6",
84
+ "pg": "8.23.1",
85
+ "pg-cursor": "2.22.1",
86
+ "playwright": "1.64.1",
87
+ "prettier": "3.9.10",
88
88
  "prototype-pollution-vulnerable-lodash.merge-dont-upgrade": "npm:lodash.merge@4.6.1",
89
- "remeda": "2.50.0",
89
+ "remeda": "2.57.0",
90
90
  "semver": "7.8.5",
91
91
  "sinon": "22.1.0",
92
92
  "tarn": "3.1.2",
93
- "tedious": "20.0.0",
93
+ "tedious": "20.3.8",
94
94
  "tsd": "0.33.0",
95
- "tsx": "4.23.13",
95
+ "tsx": "4.23.15",
96
96
  "typescript": "7.0.2",
97
97
  "typescript-6": "npm:typescript@6.0.3"
98
98
  },
99
99
  "scripts": {
100
100
  "clean": "rm -rf dist & rm -rf test/node/dist & rm -rf test/browser/bundle.js",
101
- "bench:ts": "pnpm build && node --experimental-strip-types ./test/ts-benchmarks/index.ts",
102
- "test": "pnpm build && pnpm test:node:build && pnpm test:node:run && pnpm test:typings && pnpm test:esmimports && pnpm test:exports",
101
+ "bench:ts": "pnpm run build && node --experimental-strip-types ./test/ts-benchmarks/index.ts",
102
+ "test": "pnpm run build && pnpm run test:node:build && pnpm run test:node:run && pnpm run test:typings && pnpm run test:esmimports && pnpm run test:exports",
103
103
  "test:node:build": "tsc -p test/node",
104
- "test:node": "pnpm build && pnpm test:node:build && pnpm test:node:run",
104
+ "test:node": "pnpm run build && pnpm run test:node:build && pnpm run test:node:run",
105
105
  "test:node:run": "mocha --timeout 15000 test/node/dist/**/*.test.js",
106
106
  "test:browser:build": "rm -rf test/browser/bundle.js && esbuild test/browser/main.ts --bundle --outfile=test/browser/bundle.js",
107
- "test:browser": "pnpm build && pnpm test:browser:build && node test/browser/test.js",
108
- "test:bun": "pnpm build && bun link && cd test/bun && bun install && bun run test",
109
- "test:cloudflare-workers": "pnpm build && pnpm -r test --filter kysely-cloudflare-workers-test",
107
+ "test:browser": "pnpm run build && pnpm run test:browser:build && node test/browser/test.js",
108
+ "test:bun": "pnpm run build && bun link && cd test/bun && bun install && bun run test",
109
+ "test:cloudflare-workers": "pnpm run build && pnpm -r --filter kysely-cloudflare-workers-test run test",
110
110
  "test:deno": "deno run --allow-env --allow-read --allow-net --no-lock test/deno/test.ts",
111
111
  "test:typings": "tsd test/typings",
112
112
  "test:esmimports": "node scripts/check-esm-imports.cjs",
113
113
  "test:esbuild": "esbuild --bundle --platform=node --external:pg-native dist/index.js --outfile=/dev/null",
114
114
  "test:exports": "attw --profile esm-only --pack . && node scripts/check-exports.cjs",
115
115
  "test:jsdocs": "deno check --doc-only --no-lock --unstable-sloppy-imports --config=\"deno.check.json\" ./src",
116
- "test:outdatedts": "pnpm build && cd test/outdated-ts && pnpm i && pnpm test",
116
+ "test:outdatedts": "pnpm run build && cd test/outdated-ts && pnpm i && pnpm run test",
117
117
  "lint:deno": "deno lint --config=\"deno.lint.json\" ./src",
118
118
  "prettier": "prettier --write 'src/**/*.ts' 'test/**/*.ts'",
119
- "build": "pnpm clean && tsc && pnpm script:add-deno-type-references && pnpm script:copy-interface-doc",
119
+ "build": "pnpm run clean && tsc && pnpm run script:add-deno-type-references && pnpm run script:copy-interface-doc",
120
120
  "script:copy-interface-doc": "node scripts/copy-interface-documentation.cjs",
121
121
  "script:add-deno-type-references": "node scripts/add-deno-type-references.cjs",
122
122
  "script:align-versions": "node --experimental-strip-types scripts/align-versions.mts",
123
123
  "script:generate-site-examples": "node scripts/generate-site-examples.mts",
124
124
  "script:override-tsconfig-target": "node --experimental-strip-types scripts/override-tsconfig-target.mts",
125
125
  "script:remove-global-augmentations": "node --experimental-strip-types scripts/remove-global-augmentations.mts",
126
- "version": "pnpm script:align-versions && git add ."
126
+ "version": "pnpm run script:align-versions && git add ."
127
127
  }
128
128
  }