kysely 0.29.7 → 0.29.9

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.7)](https://socket.dev/npm/package/kysely/overview/0.29.7)
4
+ [![Socket Badge](https://badge.socket.dev/npm/package/kysely/0.29.9)](https://socket.dev/npm/package/kysely/overview/0.29.9)
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
  /**
@@ -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
  /**
@@ -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
  /**
@@ -1,8 +1,9 @@
1
1
  /// <reference types="./pglite-driver.d.ts" />
2
+ import { AbortedTransactionError } from '../../driver/aborted-transaction-error.js';
2
3
  import { parseSavepointCommand } from '../../parser/savepoint-parser.js';
3
4
  import { waitOrAbort, } from '../../util/abort.js';
4
5
  import { Deferred } from '../../util/deferred.js';
5
- import { freeze, isFunction } from '../../util/object-utils.js';
6
+ import { freeze, isFunction, isObject } from '../../util/object-utils.js';
6
7
  import { createQueryId } from '../../util/query-id.js';
7
8
  import { extendStackTrace } from '../../util/stack-trace-utils.js';
8
9
  const PRIVATE_BEGIN_TRANSACTION_METHOD = Symbol();
@@ -102,11 +103,22 @@ class PGliteConnection {
102
103
  }
103
104
  async [PRIVATE_COMMIT_TRANSACTION_METHOD]() {
104
105
  this.#commitTransaction?.();
105
- await this.#transactionClosedPromise;
106
- this.#commitTransaction = undefined;
107
- this.#rollbackTransaction = undefined;
108
- this.#transaction = undefined;
109
- this.#transactionClosedPromise = undefined;
106
+ try {
107
+ await this.#transactionClosedPromise;
108
+ }
109
+ catch (error) {
110
+ // https://github.com/electric-sql/pglite/blob/bac163ecc92e968adda68864b74b7e3d65a68e97/packages/pglite/src/errors.ts#L11-L17
111
+ if (isObject(error) && 'code' in error && error.code === '25P02') {
112
+ throw new AbortedTransactionError({ cause: error });
113
+ }
114
+ throw error;
115
+ }
116
+ finally {
117
+ this.#commitTransaction = undefined;
118
+ this.#rollbackTransaction = undefined;
119
+ this.#transaction = undefined;
120
+ this.#transactionClosedPromise = undefined;
121
+ }
110
122
  }
111
123
  async [PRIVATE_ROLLBACK_TRANSACTION_METHOD]() {
112
124
  this.#rollbackTransaction?.();
@@ -95,7 +95,7 @@ export type PostgresClientConstructor = new (options: any) => PostgresClient;
95
95
  * https://node-postgres.com/apis/pool#releasing-clients
96
96
  */
97
97
  export interface PostgresPoolClient extends Omit<PostgresClient, 'connect' | 'end'> {
98
- release(): void;
98
+ release(destroy?: boolean): void;
99
99
  }
100
100
  /**
101
101
  * This interface is pg driver's `Cursor` class that kysely needs.
@@ -124,7 +124,7 @@ export type PostgresCursorConstructor = new <T>(sql: string, parameters: unknown
124
124
  * https://node-postgres.com/apis/result
125
125
  */
126
126
  export interface PostgresQueryResult<R> {
127
- command: 'UPDATE' | 'DELETE' | 'INSERT' | 'SELECT' | 'MERGE';
127
+ command: 'UPDATE' | 'DELETE' | 'INSERT' | 'SELECT' | 'MERGE' | 'COMMIT' | 'ROLLBACK';
128
128
  rowCount: number;
129
129
  rows: R[];
130
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();
@@ -58,10 +60,21 @@ export class PostgresDriver {
58
60
  await connection.executeQuery(CompiledQuery.raw(sql));
59
61
  }
60
62
  async commitTransaction(connection) {
61
- 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
+ }
62
69
  }
63
70
  async rollbackTransaction(connection) {
64
- 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
+ }
65
78
  }
66
79
  async savepoint(connection, savepointName, compileQuery) {
67
80
  await connection.executeQuery(compileQuery(parseSavepointCommand('savepoint', savepointName), createQueryId()));
@@ -88,6 +101,7 @@ class PostgresConnection {
88
101
  #options;
89
102
  #queryId;
90
103
  #pid;
104
+ #discarded;
91
105
  constructor(client, options) {
92
106
  this.#client = client;
93
107
  this.#options = options;
@@ -107,16 +121,21 @@ class PostgresConnection {
107
121
  this.#pid = processID;
108
122
  }
109
123
  else {
124
+ this.#assertNotDiscarded();
110
125
  const { rows: [{ pid }], } = await this.#client.query('select pg_backend_pid() as pid', []);
111
126
  this.#pid = Number(pid);
112
127
  }
113
128
  }
114
129
  async executeQuery(compiledQuery) {
130
+ this.#assertNotDiscarded();
115
131
  try {
116
132
  // this helps ensure we don't cancel the wrong query when aborted.
117
133
  this.#queryId = compiledQuery.queryId;
118
134
  const result = await this.#client.query(compiledQuery.sql, compiledQuery.parameters);
119
135
  const { command, rowCount, rows } = result;
136
+ if (command === 'ROLLBACK' || command === 'COMMIT') {
137
+ return { rows: [{ command }] };
138
+ }
120
139
  return {
121
140
  numAffectedRows: command === 'INSERT' ||
122
141
  command === 'UPDATE' ||
@@ -142,6 +161,7 @@ class PostgresConnection {
142
161
  if (!this.#options.cursor) {
143
162
  throw new Error("`cursor` is not present in your postgres dialect config. It's required to make streaming work in postgres.");
144
163
  }
164
+ this.#assertNotDiscarded();
145
165
  // this helps ensure we don't cancel the wrong query when aborted.
146
166
  this.#queryId = compiledQuery.queryId;
147
167
  const cursor = this.#client.query(new this.#options.cursor(compiledQuery.sql, compiledQuery.parameters.slice()));
@@ -163,7 +183,15 @@ class PostgresConnection {
163
183
  }
164
184
  }
165
185
  [PRIVATE_RELEASE_METHOD]() {
166
- 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
+ }
167
195
  }
168
196
  async #executeControlQuery(query, controlConnectionProvider) {
169
197
  if (!this.#queryId) {
@@ -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(options?: ErrorOptions);
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(options) {
11
+ super('Transaction was rolled back instead of committed', options);
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
  /**
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
@@ -477,6 +477,54 @@ export declare class Kysely<DB> extends QueryCreator<DB> implements QueryExecuto
477
477
  /**
478
478
  * Executes a given compiled query or query builder.
479
479
  *
480
+ * Returns a {@link QueryResult} containing `rows` and any metadata provided
481
+ * by the dialect, such as `numAffectedRows` or `insertId`.
482
+ * Unlike a query builder's `execute`, this method doesn't wrap write metadata
483
+ * in an {@link InsertResult}, {@link UpdateResult} or {@link DeleteResult}.
484
+ * Without `returning` or `output`, a write normally has an empty `rows` array.
485
+ *
486
+ * ### Examples
487
+ *
488
+ * ```ts
489
+ * const query = db
490
+ * .selectFrom('person')
491
+ * .select(['id', 'first_name'])
492
+ * .where('id', '=', 123)
493
+ * .compile()
494
+ *
495
+ * const { rows } = await db.executeQuery(query)
496
+ * console.log(rows[0]?.first_name)
497
+ * ```
498
+ *
499
+ * The generated SQL (PostgreSQL):
500
+ *
501
+ * ```sql
502
+ * select "id", "first_name"
503
+ * from "person"
504
+ * where "id" = $1
505
+ * ```
506
+ *
507
+ * Read metadata from a write query:
508
+ *
509
+ * ```ts
510
+ * const query = db
511
+ * .updateTable('person')
512
+ * .set({ first_name: 'Jennifer' })
513
+ * .where('id', '=', 123)
514
+ * .compile()
515
+ *
516
+ * const { numAffectedRows } = await db.executeQuery(query)
517
+ * console.log(numAffectedRows)
518
+ * ```
519
+ *
520
+ * The generated SQL (PostgreSQL):
521
+ *
522
+ * ```sql
523
+ * update "person"
524
+ * set "first_name" = $1
525
+ * where "id" = $2
526
+ * ```
527
+ *
480
528
  * See {@link https://github.com/kysely-org/kysely/blob/master/site/docs/recipes/0004-splitting-query-building-and-execution.md#execute-compiled-queries splitting build, compile and execute code recipe} for more information.
481
529
  */
482
530
  executeQuery<R>(query: CompiledQuery<R> | Compilable<R>, options?: AbortableQueryOptions): Promise<QueryResult<R>>;
@@ -626,6 +674,9 @@ export declare class ControlledTransaction<DB, S extends string[] = []> extends
626
674
  /**
627
675
  * Commits the transaction.
628
676
  *
677
+ * If the database rolls back instead of committing, marks the transaction
678
+ * as rolled back, releases the connection and throws {@link AbortedTransactionError}.
679
+ *
629
680
  * Waits for ongoing queries to finish. Streams must be exhausted or closed
630
681
  * before awaiting this command. When consuming a stream with `for await`,
631
682
  * exit the loop before committing.
@@ -655,6 +706,9 @@ export declare class ControlledTransaction<DB, S extends string[] = []> extends
655
706
  /**
656
707
  * Rolls back the transaction.
657
708
  *
709
+ * If a commit already reported that the database rolled back the transaction,
710
+ * succeeds without sending another rollback command.
711
+ *
658
712
  * The connection is released even if the rollback fails, so the transaction
659
713
  * cannot be used afterwards in either case.
660
714
  *
@@ -815,13 +869,12 @@ export declare class ControlledTransaction<DB, S extends string[] = []> extends
815
869
  $omitTables<T extends keyof DB>(): ControlledTransaction<DB extends object ? Omit<DB, T> : DB, S>;
816
870
  $pickTables<T extends keyof DB>(): ControlledTransaction<DB extends object ? Pick<DB, T> : DB, S>;
817
871
  }
818
- interface ControlledTransctionState {
819
- isCommitted: boolean;
820
- isRolledBack: boolean;
872
+ interface ControlledTransactionState {
873
+ status: 'active' | 'committed' | 'rolled back' | 'aborted';
821
874
  }
822
875
  interface ControlledTransactionProps extends KyselyProps {
823
876
  readonly connection: ControlledConnection;
824
- readonly state: ControlledTransctionState;
877
+ readonly transactionState: ControlledTransactionState;
825
878
  }
826
879
  export declare class Command<T> {
827
880
  #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';
@@ -538,6 +539,54 @@ export class Kysely extends QueryCreator {
538
539
  /**
539
540
  * Executes a given compiled query or query builder.
540
541
  *
542
+ * Returns a {@link QueryResult} containing `rows` and any metadata provided
543
+ * by the dialect, such as `numAffectedRows` or `insertId`.
544
+ * Unlike a query builder's `execute`, this method doesn't wrap write metadata
545
+ * in an {@link InsertResult}, {@link UpdateResult} or {@link DeleteResult}.
546
+ * Without `returning` or `output`, a write normally has an empty `rows` array.
547
+ *
548
+ * ### Examples
549
+ *
550
+ * ```ts
551
+ * const query = db
552
+ * .selectFrom('person')
553
+ * .select(['id', 'first_name'])
554
+ * .where('id', '=', 123)
555
+ * .compile()
556
+ *
557
+ * const { rows } = await db.executeQuery(query)
558
+ * console.log(rows[0]?.first_name)
559
+ * ```
560
+ *
561
+ * The generated SQL (PostgreSQL):
562
+ *
563
+ * ```sql
564
+ * select "id", "first_name"
565
+ * from "person"
566
+ * where "id" = $1
567
+ * ```
568
+ *
569
+ * Read metadata from a write query:
570
+ *
571
+ * ```ts
572
+ * const query = db
573
+ * .updateTable('person')
574
+ * .set({ first_name: 'Jennifer' })
575
+ * .where('id', '=', 123)
576
+ * .compile()
577
+ *
578
+ * const { numAffectedRows } = await db.executeQuery(query)
579
+ * console.log(numAffectedRows)
580
+ * ```
581
+ *
582
+ * The generated SQL (PostgreSQL):
583
+ *
584
+ * ```sql
585
+ * update "person"
586
+ * set "first_name" = $1
587
+ * where "id" = $2
588
+ * ```
589
+ *
541
590
  * See {@link https://github.com/kysely-org/kysely/blob/master/site/docs/recipes/0004-splitting-query-building-and-execution.md#execute-compiled-queries splitting build, compile and execute code recipe} for more information.
542
591
  */
543
592
  async executeQuery(query, options) {
@@ -680,8 +729,10 @@ export class TransactionBuilder {
680
729
  const settings = { isolationLevel, accessMode };
681
730
  validateTransactionSettings(settings);
682
731
  return this.#props.executor.provideConnection(async (connection) => {
683
- const state = { isCommitted: false, isRolledBack: false };
684
- const executor = new NotCommittedOrRolledBackAssertingExecutor(this.#props.executor.withConnectionProvider(new SingleConnectionProvider(connection)), state);
732
+ const transactionState = {
733
+ status: 'active',
734
+ };
735
+ const executor = new NotCommittedOrRolledBackAssertingExecutor(this.#props.executor.withConnectionProvider(new SingleConnectionProvider(connection)), transactionState);
685
736
  const transaction = new Transaction({ ...kyselyProps, executor });
686
737
  let transactionBegun = false;
687
738
  try {
@@ -689,13 +740,16 @@ export class TransactionBuilder {
689
740
  transactionBegun = true;
690
741
  const result = await callback(transaction);
691
742
  await this.#props.driver.commitTransaction(connection);
692
- state.isCommitted = true;
743
+ transactionState.status = 'committed';
693
744
  return result;
694
745
  }
695
746
  catch (error) {
696
- if (transactionBegun) {
747
+ if (isAbortedTransactionError(error)) {
748
+ transactionState.status = 'aborted';
749
+ }
750
+ else if (transactionBegun) {
697
751
  await this.#props.driver.rollbackTransaction(connection);
698
- state.isRolledBack = true;
752
+ transactionState.status = 'rolled back';
699
753
  }
700
754
  throw error;
701
755
  }
@@ -734,7 +788,7 @@ export class ControlledTransactionBuilder {
734
788
  return new ControlledTransaction({
735
789
  ...props,
736
790
  connection,
737
- state: { isCommitted: false, isRolledBack: false },
791
+ transactionState: { status: 'active' },
738
792
  executor: this.#props.executor.withConnectionProvider(new SingleConnectionProvider(connection.connection)),
739
793
  });
740
794
  }
@@ -745,23 +799,27 @@ export class ControlledTransaction extends Transaction {
745
799
  constructor(props) {
746
800
  props = {
747
801
  ...props,
748
- executor: new NotCommittedOrRolledBackAssertingExecutor(props.executor, props.state),
802
+ executor: new NotCommittedOrRolledBackAssertingExecutor(props.executor, props.transactionState),
749
803
  };
750
- const { connection, state, ...transactionProps } = props;
804
+ const { connection, transactionState, ...transactionProps } = props;
751
805
  super(transactionProps);
752
806
  this.#props = freeze(props);
753
807
  const queryId = createQueryId();
754
808
  this.#compileQuery = (node) => props.executor.compileQuery(node, queryId);
755
809
  }
756
810
  get isCommitted() {
757
- return this.#props.state.isCommitted;
811
+ return this.#props.transactionState.status === 'committed';
758
812
  }
759
813
  get isRolledBack() {
760
- return this.#props.state.isRolledBack;
814
+ return (this.#props.transactionState.status === 'rolled back' ||
815
+ this.#props.transactionState.status === 'aborted');
761
816
  }
762
817
  /**
763
818
  * Commits the transaction.
764
819
  *
820
+ * If the database rolls back instead of committing, marks the transaction
821
+ * as rolled back, releases the connection and throws {@link AbortedTransactionError}.
822
+ *
765
823
  * Waits for ongoing queries to finish. Streams must be exhausted or closed
766
824
  * before awaiting this command. When consuming a stream with `for await`,
767
825
  * exit the loop before committing.
@@ -790,8 +848,17 @@ export class ControlledTransaction extends Transaction {
790
848
  commit() {
791
849
  return new Command(async () => {
792
850
  await this.#props.executor.provideConnection(async (connection) => {
793
- await this.#props.driver.commitTransaction(connection);
794
- this.#props.state.isCommitted = true;
851
+ try {
852
+ await this.#props.driver.commitTransaction(connection);
853
+ }
854
+ catch (error) {
855
+ if (isAbortedTransactionError(error)) {
856
+ this.#props.transactionState.status = 'aborted';
857
+ this.#props.connection.release();
858
+ }
859
+ throw error;
860
+ }
861
+ this.#props.transactionState.status = 'committed';
795
862
  this.#props.connection.release();
796
863
  });
797
864
  });
@@ -799,6 +866,9 @@ export class ControlledTransaction extends Transaction {
799
866
  /**
800
867
  * Rolls back the transaction.
801
868
  *
869
+ * If a commit already reported that the database rolled back the transaction,
870
+ * succeeds without sending another rollback command.
871
+ *
802
872
  * The connection is released even if the rollback fails, so the transaction
803
873
  * cannot be used afterwards in either case.
804
874
  *
@@ -829,13 +899,16 @@ export class ControlledTransaction extends Transaction {
829
899
  */
830
900
  rollback() {
831
901
  return new Command(async () => {
902
+ if (this.#props.transactionState.status === 'aborted') {
903
+ return;
904
+ }
832
905
  await this.#props.executor.provideConnection(async (connection) => {
833
906
  // A failed rollback can't be meaningfully retried (e.g. dead connection).
834
907
  try {
835
908
  await this.#props.driver.rollbackTransaction(connection);
836
909
  }
837
910
  finally {
838
- this.#props.state.isRolledBack = true;
911
+ this.#props.transactionState.status = 'rolled back';
839
912
  this.#props.connection.release();
840
913
  }
841
914
  });
@@ -1014,11 +1087,11 @@ export class Command {
1014
1087
  return await this.#cb();
1015
1088
  }
1016
1089
  }
1017
- function assertNotCommittedOrRolledBack(state) {
1018
- if (state.isCommitted) {
1090
+ function assertNotCommittedOrRolledBack(transactionState) {
1091
+ if (transactionState.status === 'committed') {
1019
1092
  throw new Error('Transaction is already committed');
1020
1093
  }
1021
- if (state.isRolledBack) {
1094
+ if (transactionState.status !== 'active') {
1022
1095
  throw new Error('Transaction is already rolled back');
1023
1096
  }
1024
1097
  }
@@ -1030,13 +1103,13 @@ function assertNotCommittedOrRolledBack(state) {
1030
1103
  */
1031
1104
  class NotCommittedOrRolledBackAssertingExecutor {
1032
1105
  #executor;
1033
- #state;
1034
- constructor(executor, state) {
1106
+ #transactionState;
1107
+ constructor(executor, transactionState) {
1035
1108
  this.#executor =
1036
1109
  executor instanceof NotCommittedOrRolledBackAssertingExecutor
1037
1110
  ? executor.#executor
1038
1111
  : executor;
1039
- this.#state = state;
1112
+ this.#transactionState = transactionState;
1040
1113
  }
1041
1114
  get adapter() {
1042
1115
  return this.#executor.adapter;
@@ -1051,37 +1124,37 @@ class NotCommittedOrRolledBackAssertingExecutor {
1051
1124
  return this.#executor.compileQuery(node, queryId);
1052
1125
  }
1053
1126
  provideConnection(consumer, options) {
1054
- assertNotCommittedOrRolledBack(this.#state);
1127
+ assertNotCommittedOrRolledBack(this.#transactionState);
1055
1128
  return this.#executor.provideConnection(async (connection) => {
1056
- assertNotCommittedOrRolledBack(this.#state);
1129
+ assertNotCommittedOrRolledBack(this.#transactionState);
1057
1130
  return await consumer(connection);
1058
1131
  }, options);
1059
1132
  }
1060
1133
  executeQuery(compiledQuery, options) {
1061
- assertNotCommittedOrRolledBack(this.#state);
1134
+ assertNotCommittedOrRolledBack(this.#transactionState);
1062
1135
  return this.#executor
1063
1136
  .withConnectionProvider(this)
1064
1137
  .executeQuery(compiledQuery, options);
1065
1138
  }
1066
1139
  stream(compiledQuery, chunkSize, options) {
1067
- assertNotCommittedOrRolledBack(this.#state);
1140
+ assertNotCommittedOrRolledBack(this.#transactionState);
1068
1141
  return this.#executor
1069
1142
  .withConnectionProvider(this)
1070
1143
  .stream(compiledQuery, chunkSize, options);
1071
1144
  }
1072
1145
  withConnectionProvider(connectionProvider) {
1073
- return new NotCommittedOrRolledBackAssertingExecutor(this.#executor.withConnectionProvider(connectionProvider), this.#state);
1146
+ return new NotCommittedOrRolledBackAssertingExecutor(this.#executor.withConnectionProvider(connectionProvider), this.#transactionState);
1074
1147
  }
1075
1148
  withPlugin(plugin) {
1076
- return new NotCommittedOrRolledBackAssertingExecutor(this.#executor.withPlugin(plugin), this.#state);
1149
+ return new NotCommittedOrRolledBackAssertingExecutor(this.#executor.withPlugin(plugin), this.#transactionState);
1077
1150
  }
1078
1151
  withPlugins(plugins) {
1079
- return new NotCommittedOrRolledBackAssertingExecutor(this.#executor.withPlugins(plugins), this.#state);
1152
+ return new NotCommittedOrRolledBackAssertingExecutor(this.#executor.withPlugins(plugins), this.#transactionState);
1080
1153
  }
1081
1154
  withPluginAtFront(plugin) {
1082
- return new NotCommittedOrRolledBackAssertingExecutor(this.#executor.withPluginAtFront(plugin), this.#state);
1155
+ return new NotCommittedOrRolledBackAssertingExecutor(this.#executor.withPluginAtFront(plugin), this.#transactionState);
1083
1156
  }
1084
1157
  withoutPlugins() {
1085
- return new NotCommittedOrRolledBackAssertingExecutor(this.#executor.withoutPlugins(), this.#state);
1158
+ return new NotCommittedOrRolledBackAssertingExecutor(this.#executor.withoutPlugins(), this.#transactionState);
1086
1159
  }
1087
1160
  }
@@ -2,11 +2,21 @@ import type { Migration, MigrationProvider } from './migrator.js';
2
2
  /**
3
3
  * Reads all migrations from a folder.
4
4
  *
5
+ * Loads `.js`, `.ts`, `.mjs`, `.mts`, `.cjs` and `.cts` files, excluding
6
+ * TypeScript declaration files. Loading TypeScript requires a runtime or
7
+ * loader that supports it. Subdirectories are not scanned.
8
+ *
9
+ * A module must export an `up` function, either as a named export or as part
10
+ * of a default export object. The filename without its extension is used as
11
+ * the migration name. See {@link FileMigrationProviderProps.onFileIgnored}
12
+ * to report files that aren't loaded.
13
+ *
5
14
  * ### Examples
6
15
  *
7
16
  * ```ts
8
17
  * import { promises as fs } from 'node:fs'
9
18
  * import path from 'node:path'
19
+ * import { FileMigrationProvider } from 'kysely/migration'
10
20
  *
11
21
  * new FileMigrationProvider({
12
22
  * fs,
@@ -43,9 +53,65 @@ export interface FileMigrationProviderPath {
43
53
  join(...path: string[]): string;
44
54
  }
45
55
  export interface FileMigrationProviderProps {
56
+ /**
57
+ * Provides the directory listing. For Node.js, use `promises` from `node:fs`.
58
+ */
46
59
  fs: FileMigrationProviderFS;
60
+ /**
61
+ * Overrides how migration modules are imported. Receives the joined file path
62
+ * and returns the module exports. Defaults to a dynamic `import`.
63
+ *
64
+ * ### Examples
65
+ *
66
+ * Use a file URL when importing absolute paths in Node.js:
67
+ *
68
+ * ```ts
69
+ * import { promises as fs } from 'node:fs'
70
+ * import path from 'node:path'
71
+ * import { pathToFileURL } from 'node:url'
72
+ * import { FileMigrationProvider } from 'kysely/migration'
73
+ *
74
+ * const provider = new FileMigrationProvider({
75
+ * fs,
76
+ * path,
77
+ * migrationFolder: path.resolve('migrations'),
78
+ * import: (filePath) => import(pathToFileURL(filePath).href),
79
+ * })
80
+ * ```
81
+ */
47
82
  import?(module: string): Promise<any>;
83
+ /**
84
+ * The path to the migration directory. Use an absolute path to avoid
85
+ * depending on the current working directory.
86
+ */
48
87
  migrationFolder: string;
88
+ /**
89
+ * Called when a directory entry is ignored.
90
+ *
91
+ * `Extension` means the filename doesn't have a supported extension.
92
+ * `NotMigration` means the imported module has no supported `up` export.
93
+ * Import errors are thrown instead of being reported through this callback.
94
+ *
95
+ * ### Examples
96
+ *
97
+ * ```ts
98
+ * import { promises as fs } from 'node:fs'
99
+ * import path from 'node:path'
100
+ * import { FileMigrationProvider } from 'kysely/migration'
101
+ *
102
+ * const provider = new FileMigrationProvider({
103
+ * fs,
104
+ * path,
105
+ * migrationFolder: path.resolve('migrations'),
106
+ * onFileIgnored(fileName, reason) {
107
+ * console.log(`Ignored ${fileName}: ${reason}`)
108
+ * },
109
+ * })
110
+ * ```
111
+ */
49
112
  onFileIgnored?(fileName: string, reason: 'Extension' | 'NotMigration'): void;
113
+ /**
114
+ * Provides path joining. For Node.js, use `node:path`.
115
+ */
50
116
  path: FileMigrationProviderPath;
51
117
  }
@@ -3,11 +3,21 @@ import { isFunction, isObject } from '../util/object-utils.js';
3
3
  /**
4
4
  * Reads all migrations from a folder.
5
5
  *
6
+ * Loads `.js`, `.ts`, `.mjs`, `.mts`, `.cjs` and `.cts` files, excluding
7
+ * TypeScript declaration files. Loading TypeScript requires a runtime or
8
+ * loader that supports it. Subdirectories are not scanned.
9
+ *
10
+ * A module must export an `up` function, either as a named export or as part
11
+ * of a default export object. The filename without its extension is used as
12
+ * the migration name. See {@link FileMigrationProviderProps.onFileIgnored}
13
+ * to report files that aren't loaded.
14
+ *
6
15
  * ### Examples
7
16
  *
8
17
  * ```ts
9
18
  * import { promises as fs } from 'node:fs'
10
19
  * import path from 'node:path'
20
+ * import { FileMigrationProvider } from 'kysely/migration'
11
21
  *
12
22
  * new FileMigrationProvider({
13
23
  * fs,
@@ -386,6 +386,16 @@ export interface MigrationInfo {
386
386
  /**
387
387
  * When was the migration executed.
388
388
  *
389
+ * In most cases, this is the time on the machine running the migrator when
390
+ * the migration was applied (as reported by `Date.now()`), not the database
391
+ * server's time.
392
+ * Execution timestamps are used to determine the order in which migrations
393
+ * were executed, so they never go backwards. If the machine clock is behind
394
+ * the latest recorded execution timestamp (e.g. it was stepped back by an NTP
395
+ * sync, it drifted from the clock of another machine running migrations, or
396
+ * it was ahead when a previous migration was applied), the migration is
397
+ * recorded 1 millisecond after the latest recorded execution timestamp instead.
398
+ *
389
399
  * If this is undefined, the migration hasn't been executed yet.
390
400
  */
391
401
  executedAt?: Date;
@@ -1,12 +1,14 @@
1
1
  /// <reference types="./migrator.d.ts" />
2
2
  import { NoopPlugin } from '../plugin/noop-plugin.js';
3
3
  import { WithSchemaPlugin } from '../plugin/with-schema/with-schema-plugin.js';
4
+ import { warnOnce } from '../util/log-once.js';
4
5
  import { freeze, getLast, isObject } from '../util/object-utils.js';
5
6
  export const DEFAULT_MIGRATION_TABLE = 'kysely_migration';
6
7
  export const DEFAULT_MIGRATION_LOCK_TABLE = 'kysely_migration_lock';
7
8
  export const DEFAULT_ALLOW_UNORDERED_MIGRATIONS = false;
8
9
  export const MIGRATION_LOCK_ID = 'migration_lock';
9
10
  export const NO_MIGRATIONS = freeze({ __noMigrations__: true });
11
+ const CLOCK_BEHIND_WARNING_THRESHOLD_MILLISECONDS = 1_000;
10
12
  /**
11
13
  * A class for running migrations.
12
14
  *
@@ -448,7 +450,7 @@ export class Migrator {
448
450
  }
449
451
  async #getState(db) {
450
452
  const migrations = await this.#resolveMigrations();
451
- const executedMigrations = await this.#getExecutedMigrations(db);
453
+ const { executedMigrations, maxExecutedAt } = await this.#getExecutedMigrations(db);
452
454
  this.#ensureNoMissingMigrations(migrations, executedMigrations);
453
455
  if (!this.#allowUnorderedMigrations) {
454
456
  this.#ensureMigrationsInOrder(migrations, executedMigrations);
@@ -458,6 +460,7 @@ export class Migrator {
458
460
  migrations,
459
461
  executedMigrations,
460
462
  lastMigration: getLast(executedMigrations),
463
+ maxExecutedAt,
461
464
  pendingMigrations,
462
465
  });
463
466
  }
@@ -476,22 +479,44 @@ export class Migrator {
476
479
  }));
477
480
  }
478
481
  async #getExecutedMigrations(db) {
479
- const executedMigrations = await db
482
+ const rows = await db
480
483
  .withPlugin(this.#schemaPlugin)
481
484
  .selectFrom(this.#migrationTable)
482
485
  .select(['name', 'timestamp'])
483
486
  .$narrowType()
484
487
  .execute();
488
+ if (rows.length === 0) {
489
+ return { executedMigrations: [], maxExecutedAt: 0 };
490
+ }
491
+ if (rows.length === 1) {
492
+ const [{ name, timestamp }] = rows;
493
+ return {
494
+ executedMigrations: [name],
495
+ maxExecutedAt: this.#getExecutedAt(name, timestamp),
496
+ };
497
+ }
485
498
  const nameComparator = this.#props.nameComparator || ((a, b) => a.localeCompare(b));
486
- return (executedMigrations
499
+ let maxExecutedAt = 0;
500
+ const executedMigrations = rows
487
501
  // https://github.com/kysely-org/kysely/issues/843
488
502
  .sort((a, b) => {
489
- if (a.timestamp === b.timestamp) {
503
+ const aExecutedAt = this.#getExecutedAt(a.name, a.timestamp);
504
+ const bExecutedAt = this.#getExecutedAt(b.name, b.timestamp);
505
+ maxExecutedAt = Math.max(maxExecutedAt, aExecutedAt, bExecutedAt);
506
+ if (aExecutedAt === bExecutedAt) {
490
507
  return nameComparator(a.name, b.name);
491
508
  }
492
- return (new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
509
+ return aExecutedAt - bExecutedAt;
493
510
  })
494
- .map((it) => it.name));
511
+ .map((it) => it.name);
512
+ return { executedMigrations, maxExecutedAt };
513
+ }
514
+ #getExecutedAt(name, timestamp) {
515
+ const executedAt = new Date(timestamp).getTime();
516
+ if (Number.isNaN(executedAt)) {
517
+ throw new Error(`corrupted migrations: previously executed migration ${name} has an invalid timestamp ${timestamp}`);
518
+ }
519
+ return executedAt;
495
520
  }
496
521
  #ensureNoMissingMigrations(migrations, executedMigrations) {
497
522
  // Ensure all executed migrations exist in the `migrations` list.
@@ -563,16 +588,18 @@ export class Migrator {
563
588
  status: 'NotExecuted',
564
589
  };
565
590
  });
591
+ let { maxExecutedAt } = state;
566
592
  for (let i = 0; i < results.length; i++) {
567
593
  const migration = state.pendingMigrations[i];
568
594
  try {
569
595
  await migration.up(db);
596
+ maxExecutedAt = this.#getNextExecutedAt(maxExecutedAt);
570
597
  await db
571
598
  .withPlugin(this.#schemaPlugin)
572
599
  .insertInto(this.#migrationTable)
573
600
  .values({
574
601
  name: migration.name,
575
- timestamp: new Date().toISOString(),
602
+ timestamp: new Date(maxExecutedAt).toISOString(),
576
603
  })
577
604
  .execute();
578
605
  results[i] = {
@@ -601,6 +628,17 @@ export class Migrator {
601
628
  }
602
629
  await qb.execute();
603
630
  }
631
+ // https://github.com/kysely-org/kysely/issues/2244
632
+ #getNextExecutedAt(maxExecutedAt) {
633
+ const now = Date.now();
634
+ if (now > maxExecutedAt) {
635
+ return now;
636
+ }
637
+ if (maxExecutedAt - now >= CLOCK_BEHIND_WARNING_THRESHOLD_MILLISECONDS) {
638
+ warnOnce('The machine clock is behind the latest migration `executedAt`. Recording migrations 1 millisecond after it instead.');
639
+ }
640
+ return maxExecutedAt + 1;
641
+ }
604
642
  }
605
643
  class MigrationResultSetError extends Error {
606
644
  #resultSet;
@@ -78,10 +78,7 @@ export class WithSchemaTransformer extends OperationNodeTransformer {
78
78
  return {
79
79
  ...super.transformSelectModifier({ ...node, of: undefined }, queryId),
80
80
  of: node.of?.map((item) => TableNode.is(item) && !item.table.schema
81
- ? {
82
- ...item,
83
- table: this.transformIdentifier(item.table.identifier, queryId),
84
- }
81
+ ? this.#transformTableWithoutSchema(item, queryId)
85
82
  : this.transformNode(item, queryId)),
86
83
  };
87
84
  }
@@ -89,12 +86,18 @@ export class WithSchemaTransformer extends OperationNodeTransformer {
89
86
  return SCHEMALESS_FUNCTIONS[node.func]
90
87
  ? node[argsKey].map((arg) => !TableNode.is(arg) || arg.table.schema
91
88
  ? this.transformNode(arg, queryId)
92
- : {
93
- ...arg,
94
- table: this.transformIdentifier(arg.table.identifier, queryId),
95
- })
89
+ : this.#transformTableWithoutSchema(arg, queryId))
96
90
  : this.transformNodeList(node[argsKey], queryId);
97
91
  }
92
+ #transformTableWithoutSchema(node, queryId) {
93
+ return {
94
+ ...node,
95
+ table: {
96
+ ...node.table,
97
+ identifier: this.transformIdentifier(node.table.identifier, queryId),
98
+ },
99
+ };
100
+ }
98
101
  #collectSchemableIds(node) {
99
102
  const schemableIds = new Set();
100
103
  if ('name' in node && node.name && SchemableIdentifierNode.is(node.name)) {
@@ -73,7 +73,7 @@ export declare class JSONPathBuilder<S, O = S> {
73
73
  * select "nicknames"->>'$[#-1]' as `last_nickname` from `person`
74
74
  * ```
75
75
  */
76
- at<I extends (any[] extends O ? number | 'last' | `#-${number}` : never), O2 = null | NonNullable<NonNullable<O>[keyof NonNullable<O> & number]>>(index: `${I}` extends `${any}.${any}` | `#--${any}` ? never : I): TraversedJSONPathBuilder<S, O2>;
76
+ at<I extends (any[] extends O ? number | 'last' | `#-${number}` : never)>(index: `${I}` extends `${any}.${any}` | `#--${any}` ? never : I): TraversedJSONPathBuilder<S, JSONArrayElementType<O>>;
77
77
  /**
78
78
  * Access a property of a JSON object.
79
79
  *
@@ -123,7 +123,7 @@ export declare class JSONPathBuilder<S, O = S> {
123
123
  * select "profile"->'addresses'->0->'city' as "city" from "person"
124
124
  * ```
125
125
  */
126
- key<K extends (any[] extends O ? never : O extends object ? keyof NonNullable<O> & string : never), O2 = undefined extends O ? null | NonNullable<NonNullable<O>[K]> : null extends O ? null | NonNullable<NonNullable<O>[K]> : string extends keyof NonNullable<O> ? null | NonNullable<NonNullable<O>[K]> : NonNullable<O>[K]>(key: K): TraversedJSONPathBuilder<S, O2>;
126
+ key<K extends (any[] extends O ? never : O extends object ? keyof NonNullable<O> & string : never)>(key: K): TraversedJSONPathBuilder<S, JSONObjectValueType<O, K>>;
127
127
  }
128
128
  export declare class TraversedJSONPathBuilder<S, O> extends JSONPathBuilder<S, O> implements AliasableExpression<O> {
129
129
  #private;
@@ -292,3 +292,6 @@ export declare class AliasedJSONPathBuilder<O, A extends string> implements Alia
292
292
  */
293
293
  toOperationNode(): AliasNode;
294
294
  }
295
+ type JSONArrayElementType<O> = null | NonNullable<NonNullable<O>[keyof NonNullable<O> & number]>;
296
+ type JSONObjectValueType<O, K extends keyof NonNullable<O>> = undefined extends O ? null | NonNullable<NonNullable<O>[K]> : null extends O ? null | NonNullable<NonNullable<O>[K]> : string extends keyof NonNullable<O> ? null | NonNullable<NonNullable<O>[K]> : NonNullable<O>[K];
297
+ export {};
@@ -1,13 +1,85 @@
1
1
  import { type RootOperationNode } from '../operation-node/root-operation-node.js';
2
2
  import { type QueryId } from '../util/query-id.js';
3
+ /**
4
+ * A SQL query compiled for a specific dialect, together with its parameters
5
+ * and operation node tree.
6
+ *
7
+ * The output type `O` describes a result row. It doesn't validate or convert
8
+ * values returned by the database driver.
9
+ *
10
+ * Query builders create compiled queries with {@link Compilable.compile | compile()},
11
+ * and {@link RawBuilder}s with `compile(db)`. Compiling doesn't execute the query.
12
+ *
13
+ * See {@link Kysely.executeQuery} for execution and {@link CompiledQuery.raw}
14
+ * for constructing a query from an existing SQL string.
15
+ *
16
+ * ### Examples
17
+ *
18
+ * ```ts
19
+ * const compiledQuery = db
20
+ * .selectFrom('person')
21
+ * .select('first_name')
22
+ * .where('id', '=', 123)
23
+ * .compile()
24
+ *
25
+ * console.log(compiledQuery.sql) // select "first_name" from "person" where "id" = $1
26
+ * console.log(compiledQuery.parameters) // [123]
27
+ * ```
28
+ */
3
29
  export interface CompiledQuery<O = unknown> {
30
+ /**
31
+ * The operation node tree used to compile the query, after query plugins have run.
32
+ */
4
33
  readonly query: RootOperationNode;
34
+ /**
35
+ * Identifies the query when passing data between plugin hooks.
36
+ */
5
37
  readonly queryId: QueryId;
38
+ /**
39
+ * The SQL string, with placeholders in the target dialect's syntax.
40
+ */
6
41
  readonly sql: string;
42
+ /**
43
+ * The parameter values in the order expected by the placeholders in {@link CompiledQuery.sql | sql}.
44
+ */
7
45
  readonly parameters: ReadonlyArray<unknown>;
8
46
  }
9
47
  export declare function isCompiledQuery<R>(thing: unknown): thing is CompiledQuery<R>;
10
48
  type CompiledQueryFactory = Readonly<{
49
+ /**
50
+ * Creates a compiled query from an existing SQL string and parameter values.
51
+ *
52
+ * The SQL is used as provided. This method doesn't escape SQL, generate
53
+ * placeholders, or check that the parameters match the placeholders.
54
+ * Use the target dialect's placeholder syntax, such as `$1` for PostgreSQL
55
+ * or `?` for MySQL and SQLite. For SQL built from interpolated values,
56
+ * prefer the {@link sql} template tag.
57
+ *
58
+ * ### Examples
59
+ *
60
+ * Execute a parameterized PostgreSQL query:
61
+ *
62
+ * ```ts
63
+ * import { CompiledQuery } from 'kysely'
64
+ *
65
+ * const result = await db.executeQuery<{ first_name: string }>(
66
+ * CompiledQuery.raw(
67
+ * 'select "first_name" from "person" where "id" = $1',
68
+ * [123],
69
+ * ),
70
+ * )
71
+ *
72
+ * const firstName: string | undefined = result.rows[0]?.first_name
73
+ * ```
74
+ *
75
+ * The generated SQL (PostgreSQL):
76
+ *
77
+ * ```sql
78
+ * select "first_name"
79
+ * from "person"
80
+ * where "id" = $1
81
+ * ```
82
+ */
11
83
  raw(sql: string, parameters?: unknown[]): Readonly<CompiledQuery>;
12
84
  }>;
13
85
  export declare const CompiledQuery: CompiledQueryFactory;
@@ -3,3 +3,8 @@
3
3
  * Logs a message and ensures it won't be logged again.
4
4
  */
5
5
  export declare function logOnce(message: string): void;
6
+ /**
7
+ * Use for system-level warnings.
8
+ * Logs a warning and ensures it won't be logged again.
9
+ */
10
+ export declare function warnOnce(message: string): void;
@@ -11,3 +11,14 @@ export function logOnce(message) {
11
11
  LOGGED_MESSAGES.add(message);
12
12
  console.log(message);
13
13
  }
14
+ /**
15
+ * Use for system-level warnings.
16
+ * Logs a warning and ensures it won't be logged again.
17
+ */
18
+ export function warnOnce(message) {
19
+ if (LOGGED_MESSAGES.has(message)) {
20
+ return;
21
+ }
22
+ LOGGED_MESSAGES.add(message);
23
+ console.warn(message);
24
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kysely",
3
- "version": "0.29.7",
3
+ "version": "0.29.9",
4
4
  "description": "Type safe SQL query builder",
5
5
  "repository": {
6
6
  "type": "git",
@@ -62,17 +62,17 @@
62
62
  ],
63
63
  "devDependencies": {
64
64
  "@arethetypeswrong/cli": "0.18.5",
65
- "@electric-sql/pglite": "0.5.10",
65
+ "@electric-sql/pglite": "0.5.16",
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.6.5",
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
77
  "chai": "6.3.0",
78
78
  "chai-as-promised": "8.0.2",
@@ -83,14 +83,14 @@
83
83
  "pathe": "2.0.3",
84
84
  "pg": "8.23.1",
85
85
  "pg-cursor": "2.22.1",
86
- "playwright": "1.64.1",
86
+ "playwright": "1.65.0",
87
87
  "prettier": "3.9.10",
88
88
  "prototype-pollution-vulnerable-lodash.merge-dont-upgrade": "npm:lodash.merge@4.6.1",
89
- "remeda": "2.51.0",
90
- "semver": "7.8.5",
89
+ "remeda": "2.59.0",
90
+ "semver": "7.8.6",
91
91
  "sinon": "22.1.0",
92
92
  "tarn": "3.1.2",
93
- "tedious": "20.3.7",
93
+ "tedious": "20.3.8",
94
94
  "tsd": "0.33.0",
95
95
  "tsx": "4.23.15",
96
96
  "typescript": "7.0.2",