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 +1 -1
- package/dist/dialect/mssql/mssql-driver.d.ts +3 -0
- package/dist/dialect/mysql/mysql-driver.d.ts +3 -0
- package/dist/dialect/pglite/pglite-driver.d.ts +3 -0
- package/dist/dialect/pglite/pglite-driver.js +18 -6
- package/dist/dialect/postgres/postgres-dialect-config.d.ts +2 -2
- package/dist/dialect/postgres/postgres-driver.d.ts +6 -1
- package/dist/dialect/postgres/postgres-driver.js +31 -3
- package/dist/dialect/sqlite/sqlite-driver.d.ts +3 -0
- package/dist/driver/aborted-transaction-error.d.ts +11 -0
- package/dist/driver/aborted-transaction-error.js +16 -0
- package/dist/driver/driver.d.ts +3 -0
- package/dist/driver/dummy-driver.d.ts +3 -0
- package/dist/driver/runtime-driver.d.ts +3 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/kysely.d.ts +57 -4
- package/dist/kysely.js +101 -28
- package/dist/migration/file-migration-provider.d.ts +66 -0
- package/dist/migration/file-migration-provider.js +10 -0
- package/dist/migration/migrator.d.ts +10 -0
- package/dist/migration/migrator.js +45 -7
- package/dist/plugin/with-schema/with-schema-transformer.js +11 -8
- package/dist/query-builder/json-path-builder.d.ts +5 -2
- package/dist/query-compiler/compiled-query.d.ts +72 -0
- package/dist/util/log-once.d.ts +5 -0
- package/dist/util/log-once.js +11 -0
- package/package.json +8 -8
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[](https://stand-with-ukraine.pp.ua)
|
|
2
2
|
|
|
3
3
|
[](https://github.com/kysely-org/kysely/releases/latest)
|
|
4
|
-
[](https://socket.dev/npm/package/kysely/overview/0.29.9)
|
|
5
5
|
[](https://github.com/kysely-org/kysely)
|
|
6
6
|
[](https://github.com/kysely-org/kysely/blob/master/LICENSE)
|
|
7
7
|
[](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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
+
}
|
package/dist/driver/driver.d.ts
CHANGED
|
@@ -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
|
|
819
|
-
|
|
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
|
|
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
|
|
684
|
-
|
|
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
|
-
|
|
743
|
+
transactionState.status = 'committed';
|
|
693
744
|
return result;
|
|
694
745
|
}
|
|
695
746
|
catch (error) {
|
|
696
|
-
if (
|
|
747
|
+
if (isAbortedTransactionError(error)) {
|
|
748
|
+
transactionState.status = 'aborted';
|
|
749
|
+
}
|
|
750
|
+
else if (transactionBegun) {
|
|
697
751
|
await this.#props.driver.rollbackTransaction(connection);
|
|
698
|
-
|
|
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
|
-
|
|
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.
|
|
802
|
+
executor: new NotCommittedOrRolledBackAssertingExecutor(props.executor, props.transactionState),
|
|
749
803
|
};
|
|
750
|
-
const { connection,
|
|
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.
|
|
811
|
+
return this.#props.transactionState.status === 'committed';
|
|
758
812
|
}
|
|
759
813
|
get isRolledBack() {
|
|
760
|
-
return this.#props.
|
|
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
|
-
|
|
794
|
-
|
|
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.
|
|
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(
|
|
1018
|
-
if (
|
|
1090
|
+
function assertNotCommittedOrRolledBack(transactionState) {
|
|
1091
|
+
if (transactionState.status === 'committed') {
|
|
1019
1092
|
throw new Error('Transaction is already committed');
|
|
1020
1093
|
}
|
|
1021
|
-
if (
|
|
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
|
-
#
|
|
1034
|
-
constructor(executor,
|
|
1106
|
+
#transactionState;
|
|
1107
|
+
constructor(executor, transactionState) {
|
|
1035
1108
|
this.#executor =
|
|
1036
1109
|
executor instanceof NotCommittedOrRolledBackAssertingExecutor
|
|
1037
1110
|
? executor.#executor
|
|
1038
1111
|
: executor;
|
|
1039
|
-
this.#
|
|
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.#
|
|
1127
|
+
assertNotCommittedOrRolledBack(this.#transactionState);
|
|
1055
1128
|
return this.#executor.provideConnection(async (connection) => {
|
|
1056
|
-
assertNotCommittedOrRolledBack(this.#
|
|
1129
|
+
assertNotCommittedOrRolledBack(this.#transactionState);
|
|
1057
1130
|
return await consumer(connection);
|
|
1058
1131
|
}, options);
|
|
1059
1132
|
}
|
|
1060
1133
|
executeQuery(compiledQuery, options) {
|
|
1061
|
-
assertNotCommittedOrRolledBack(this.#
|
|
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.#
|
|
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.#
|
|
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.#
|
|
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.#
|
|
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.#
|
|
1155
|
+
return new NotCommittedOrRolledBackAssertingExecutor(this.#executor.withPluginAtFront(plugin), this.#transactionState);
|
|
1083
1156
|
}
|
|
1084
1157
|
withoutPlugins() {
|
|
1085
|
-
return new NotCommittedOrRolledBackAssertingExecutor(this.#executor.withoutPlugins(), this.#
|
|
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
|
|
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
|
-
|
|
499
|
+
let maxExecutedAt = 0;
|
|
500
|
+
const executedMigrations = rows
|
|
487
501
|
// https://github.com/kysely-org/kysely/issues/843
|
|
488
502
|
.sort((a, b) => {
|
|
489
|
-
|
|
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
|
|
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)
|
|
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)
|
|
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;
|
package/dist/util/log-once.d.ts
CHANGED
|
@@ -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;
|
package/dist/util/log-once.js
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
90
|
-
"semver": "7.8.
|
|
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.
|
|
93
|
+
"tedious": "20.3.8",
|
|
94
94
|
"tsd": "0.33.0",
|
|
95
95
|
"tsx": "4.23.15",
|
|
96
96
|
"typescript": "7.0.2",
|