kysely 0.30.0-beta.0 → 0.30.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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.30.0-beta.0)](https://socket.dev/npm/package/kysely/overview/0.30.0-beta.0)
4
+ [![Socket Badge](https://badge.socket.dev/npm/package/kysely/0.30.0-beta.1)](https://socket.dev/npm/package/kysely/overview/0.30.0-beta.1)
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)
@@ -5,23 +5,25 @@ import type { AbortableOperationOptions } from '../../util/abort.js';
5
5
  */
6
6
  export interface MysqlDialectConfig {
7
7
  /**
8
- * A `mysql2` `Client` constructor, to be used for connecting to the database
9
- * outside of the `pool` to avoid waiting for an idle connection.
8
+ * A `createConnection` function (from either `mysql2` or `mysql2/promise`),
9
+ * to be used for connecting to the database outside of the `pool` to avoid
10
+ * waiting for an idle connection.
10
11
  *
11
12
  * This is useful for cancelling queries on the database side.
12
13
  */
13
14
  controlConnection?: {
14
- (connectionUri: string): MysqlConnection;
15
- (config: object): MysqlConnection;
15
+ (connectionUri: string): MysqlConnection | Promise<MysqlPromiseConnection>;
16
+ (config: object): MysqlConnection | Promise<MysqlPromiseConnection>;
16
17
  };
17
18
  /**
18
- * A mysql2 Pool instance or a function that returns one.
19
+ * A mysql2 Pool instance (from either `mysql2` or `mysql2/promise`) or a
20
+ * function that returns one.
19
21
  *
20
22
  * If a function is provided, it's called once when the first query is executed.
21
23
  *
22
24
  * https://github.com/sidorares/node-mysql2#using-connection-pools
23
25
  */
24
- pool: MysqlPool | ((options?: AbortableOperationOptions) => Promise<MysqlPool>);
26
+ pool: MysqlPool | MysqlPromisePool | ((options?: AbortableOperationOptions) => Promise<MysqlPool | MysqlPromisePool>);
25
27
  /**
26
28
  * Called once for each created connection.
27
29
  */
@@ -43,6 +45,17 @@ export interface MysqlPool {
43
45
  getConnection(callback: (error: unknown, connection: MysqlPoolConnection) => void): void;
44
46
  end(callback: (error: unknown) => void): void;
45
47
  }
48
+ /**
49
+ * This interface is the subset of `mysql2/promise`'s `Pool` class that
50
+ * kysely needs.
51
+ *
52
+ * It's a thin promise wrapper around the callback pool, which it exposes
53
+ * as its `pool` property. The dialect unwraps it and uses the callback
54
+ * pool directly.
55
+ */
56
+ export interface MysqlPromisePool {
57
+ pool: MysqlPool;
58
+ }
46
59
  export interface MysqlConnection {
47
60
  config: object;
48
61
  connect(callback?: (error: unknown) => void): void;
@@ -52,6 +65,18 @@ export interface MysqlConnection {
52
65
  };
53
66
  threadId: number;
54
67
  }
68
+ /**
69
+ * This interface is the subset of `mysql2/promise`'s `Connection` class that
70
+ * kysely needs.
71
+ *
72
+ * It's a thin promise wrapper around the callback connection. It's already
73
+ * connected when the `createConnection` promise resolves, so the dialect
74
+ * uses its promise-based API directly.
75
+ */
76
+ export interface MysqlPromiseConnection {
77
+ destroy(): void;
78
+ query(sql: string, values?: unknown[]): Promise<unknown>;
79
+ }
55
80
  export type MysqlConectionConstructor = new (opts?: object) => MysqlConnection;
56
81
  export interface MysqlPoolConnection extends MysqlConnection {
57
82
  release(): void;
@@ -11,7 +11,7 @@ import type { MysqlDialectConfig } from './mysql-dialect-config.js';
11
11
  * The constructor takes an instance of {@link MysqlDialectConfig}.
12
12
  *
13
13
  * ```ts
14
- * import { createPool } from 'mysql2'
14
+ * import { createPool } from 'mysql2/promise' // or just 'mysql2' — both work
15
15
  *
16
16
  * new MysqlDialect({
17
17
  * pool: createPool({
@@ -25,7 +25,7 @@ import type { MysqlDialectConfig } from './mysql-dialect-config.js';
25
25
  * can be a function:
26
26
  *
27
27
  * ```ts
28
- * import { createPool } from 'mysql2'
28
+ * import { createPool } from 'mysql2/promise' // or just 'mysql2' — both work
29
29
  *
30
30
  * new MysqlDialect({
31
31
  * pool: async () => createPool({
@@ -9,7 +9,7 @@ import { MysqlAdapter } from './mysql-adapter.js';
9
9
  * The constructor takes an instance of {@link MysqlDialectConfig}.
10
10
  *
11
11
  * ```ts
12
- * import { createPool } from 'mysql2'
12
+ * import { createPool } from 'mysql2/promise' // or just 'mysql2' — both work
13
13
  *
14
14
  * new MysqlDialect({
15
15
  * pool: createPool({
@@ -23,7 +23,7 @@ import { MysqlAdapter } from './mysql-adapter.js';
23
23
  * can be a function:
24
24
  *
25
25
  * ```ts
26
- * import { createPool } from 'mysql2'
26
+ * import { createPool } from 'mysql2/promise' // or just 'mysql2' — both work
27
27
  *
28
28
  * new MysqlDialect({
29
29
  * pool: async () => createPool({
@@ -13,9 +13,12 @@ export class MysqlDriver {
13
13
  this.#config = freeze({ ...config });
14
14
  }
15
15
  async init(options) {
16
- this.#pool = isFunction(this.#config.pool)
16
+ const pool = isFunction(this.#config.pool)
17
17
  ? await this.#config.pool(options)
18
18
  : this.#config.pool;
19
+ // A `mysql2/promise` pool's `getConnection` ignores the callback we pass
20
+ // it, hanging any query forever. Unwrap the callback pool it decorates.
21
+ this.#pool = isPromisePool(pool) ? pool.pool : pool;
19
22
  }
20
23
  async acquireConnection(options) {
21
24
  const rawConnection = await this.#acquireConnection();
@@ -93,6 +96,12 @@ export class MysqlDriver {
93
96
  });
94
97
  }
95
98
  }
99
+ function isPromisePool(pool) {
100
+ return ('pool' in pool && isObject(pool.pool) && isFunction(pool.pool.getConnection));
101
+ }
102
+ function isPromiseConnection(connection) {
103
+ return isFunction(connection.then);
104
+ }
96
105
  function isOkPacket(obj) {
97
106
  return isObject(obj) && 'insertId' in obj && 'affectedRows' in obj;
98
107
  }
@@ -224,7 +233,24 @@ class MysqlConnection {
224
233
  const {
225
234
  // omitting these as they cause a warning and perhaps a future error when passed.
226
235
  clientFlags: _, maxPacketSize: __, ...cfg } = config;
227
- const controlConnection = this.#controlConnection(cfg);
236
+ const controlConnectionOrPromise = this.#controlConnection(cfg);
237
+ // `mysql2/promise`'s `createConnection` returns a promise of a connection
238
+ // that's already connected, with a promise-based API. Use it directly.
239
+ if (isPromiseConnection(controlConnectionOrPromise)) {
240
+ const controlConnection = await controlConnectionOrPromise;
241
+ try {
242
+ // by the time we get the connection, another query might have been executed.
243
+ // we need to ensure we're not canceling the wrong query.
244
+ if (queryIdToCancel.queryId === this.#queryId?.queryId) {
245
+ await controlConnection.query(query, []);
246
+ }
247
+ }
248
+ finally {
249
+ controlConnection.destroy();
250
+ }
251
+ return;
252
+ }
253
+ const controlConnection = controlConnectionOrPromise;
228
254
  try {
229
255
  await new Promise((resolve, reject) => {
230
256
  controlConnection.connect((connectError) => {
@@ -6,7 +6,7 @@ import type { MigrationProvider } from './migrator.js';
6
6
  * ### Examples
7
7
  *
8
8
  * ```ts
9
- * import { promises as fs } from 'node:fs'
9
+ * import fs from 'node:fs/promises'
10
10
  * import path from 'node:path'
11
11
  *
12
12
  * new FileMigrationProvider({
@@ -6,7 +6,7 @@ import { isFunction, isObject } from '../util/object-utils.js';
6
6
  * ### Examples
7
7
  *
8
8
  * ```ts
9
- * import { promises as fs } from 'node:fs'
9
+ * import fs from 'node:fs/promises'
10
10
  * import path from 'node:path'
11
11
  *
12
12
  * new FileMigrationProvider({
@@ -16,7 +16,7 @@ export declare const NO_MIGRATIONS: NoMigrations;
16
16
  * other way.
17
17
  *
18
18
  * ```ts
19
- * import { promises as fs } from 'node:fs'
19
+ * import fs from 'node:fs/promises'
20
20
  * import path from 'node:path'
21
21
  * import * as Sqlite from 'better-sqlite3'
22
22
  * import { Kysely, SqliteDialect } from 'kysely'
@@ -64,7 +64,7 @@ export declare class Migrator {
64
64
  * ### Examples
65
65
  *
66
66
  * ```ts
67
- * import { promises as fs } from 'node:fs'
67
+ * import fs from 'node:fs/promises'
68
68
  * import path from 'node:path'
69
69
  * import * as Sqlite from 'better-sqlite3'
70
70
  * import { FileMigrationProvider, Migrator } from 'kysely/migration'
@@ -106,7 +106,7 @@ export declare class Migrator {
106
106
  * ### Examples
107
107
  *
108
108
  * ```ts
109
- * import { promises as fs } from 'node:fs'
109
+ * import fs from 'node:fs/promises'
110
110
  * import path from 'node:path'
111
111
  * import { FileMigrationProvider, Migrator } from 'kysely/migration'
112
112
  *
@@ -129,7 +129,7 @@ export declare class Migrator {
129
129
  * you can use a special constant `NO_MIGRATIONS`:
130
130
  *
131
131
  * ```ts
132
- * import { promises as fs } from 'node:fs'
132
+ * import fs from 'node:fs/promises'
133
133
  * import path from 'node:path'
134
134
  * import { FileMigrationProvider, Migrator, NO_MIGRATIONS } from 'kysely/migration'
135
135
  *
@@ -145,8 +145,33 @@ export declare class Migrator {
145
145
  *
146
146
  * await migrator.migrateTo(NO_MIGRATIONS)
147
147
  * ```
148
+ *
149
+ * By default, this method migrates up or down depending on which side of the
150
+ * current migration state the target migration is on. Pass
151
+ * {@link MigrateToOptions.direction} to enforce a direction — if reaching the
152
+ * target would require migrating the other way, nothing is executed and
153
+ * {@link MigrationResultSet.error} holds an error:
154
+ *
155
+ * ```ts
156
+ * import fs from 'node:fs/promises'
157
+ * import path from 'node:path'
158
+ * import { FileMigrationProvider, Migrator } from 'kysely/migration'
159
+ *
160
+ * const migrator = new Migrator({
161
+ * db,
162
+ * provider: new FileMigrationProvider({
163
+ * fs,
164
+ * // Path to the folder that contains all your migrations.
165
+ * migrationFolder: 'some/path/to/migrations',
166
+ * path,
167
+ * })
168
+ * })
169
+ *
170
+ * // Errors if `some_migration` is already executed.
171
+ * await migrator.migrateTo('some_migration', { direction: 'Up' })
172
+ * ```
148
173
  */
149
- migrateTo(targetMigrationName: string | NoMigrations, options?: MigrateOptions): Promise<MigrationResultSet>;
174
+ migrateTo(targetMigrationName: string | NoMigrations, options?: MigrateToOptions): Promise<MigrationResultSet>;
150
175
  /**
151
176
  * Migrate one step up.
152
177
  *
@@ -158,7 +183,7 @@ export declare class Migrator {
158
183
  * ### Examples
159
184
  *
160
185
  * ```ts
161
- * import { promises as fs } from 'node:fs'
186
+ * import fs from 'node:fs/promises'
162
187
  * import path from 'node:path'
163
188
  * import { FileMigrationProvider, Migrator } from 'kysely/migration'
164
189
  *
@@ -187,7 +212,7 @@ export declare class Migrator {
187
212
  * ### Examples
188
213
  *
189
214
  * ```ts
190
- * import { promises as fs } from 'node:fs'
215
+ * import fs from 'node:fs/promises'
191
216
  * import path from 'node:path'
192
217
  * import { FileMigrationProvider, Migrator } from 'kysely/migration'
193
218
  *
@@ -257,6 +282,21 @@ export interface MigrateOptions {
257
282
  readonly transactionMode?: MigratorTransactionMode;
258
283
  }
259
284
  export type MigratorTransactionMode = 'per-run' | 'per-migration' | 'none';
285
+ export interface MigrateToOptions extends MigrateOptions {
286
+ /**
287
+ * Enforces a migration direction.
288
+ *
289
+ * When provided and reaching the target migration would require migrating
290
+ * the other way, nothing is executed and {@link MigrationResultSet.error}
291
+ * holds an error. For example, `direction: 'Up'` errors when the target
292
+ * migration is already executed.
293
+ *
294
+ * By default, {@link Migrator.migrateTo | migrateTo} migrates up or down
295
+ * depending on which side of the current migration state the target
296
+ * migration is on.
297
+ */
298
+ readonly direction?: MigrationDirection;
299
+ }
260
300
  export interface MigratorProps extends MigrateOptions {
261
301
  readonly db: Kysely<any>;
262
302
  readonly provider: MigrationProvider;
@@ -359,7 +399,7 @@ export interface MigrationResultSet {
359
399
  */
360
400
  readonly results?: MigrationResult[];
361
401
  }
362
- type MigrationDirection = 'Up' | 'Down';
402
+ export type MigrationDirection = 'Up' | 'Down';
363
403
  export interface MigrationResult {
364
404
  readonly migrationName: string;
365
405
  /**
@@ -422,4 +462,3 @@ export interface MigrationInfo {
422
462
  */
423
463
  executedAt?: Date;
424
464
  }
425
- export {};
@@ -19,7 +19,7 @@ export const NO_MIGRATIONS = freeze({ __noMigrations__: true });
19
19
  * other way.
20
20
  *
21
21
  * ```ts
22
- * import { promises as fs } from 'node:fs'
22
+ * import fs from 'node:fs/promises'
23
23
  * import path from 'node:path'
24
24
  * import * as Sqlite from 'better-sqlite3'
25
25
  * import { Kysely, SqliteDialect } from 'kysely'
@@ -88,7 +88,7 @@ export class Migrator {
88
88
  * ### Examples
89
89
  *
90
90
  * ```ts
91
- * import { promises as fs } from 'node:fs'
91
+ * import fs from 'node:fs/promises'
92
92
  * import path from 'node:path'
93
93
  * import * as Sqlite from 'better-sqlite3'
94
94
  * import { FileMigrationProvider, Migrator } from 'kysely/migration'
@@ -132,7 +132,7 @@ export class Migrator {
132
132
  * ### Examples
133
133
  *
134
134
  * ```ts
135
- * import { promises as fs } from 'node:fs'
135
+ * import fs from 'node:fs/promises'
136
136
  * import path from 'node:path'
137
137
  * import { FileMigrationProvider, Migrator } from 'kysely/migration'
138
138
  *
@@ -155,7 +155,7 @@ export class Migrator {
155
155
  * you can use a special constant `NO_MIGRATIONS`:
156
156
  *
157
157
  * ```ts
158
- * import { promises as fs } from 'node:fs'
158
+ * import fs from 'node:fs/promises'
159
159
  * import path from 'node:path'
160
160
  * import { FileMigrationProvider, Migrator, NO_MIGRATIONS } from 'kysely/migration'
161
161
  *
@@ -171,11 +171,39 @@ export class Migrator {
171
171
  *
172
172
  * await migrator.migrateTo(NO_MIGRATIONS)
173
173
  * ```
174
+ *
175
+ * By default, this method migrates up or down depending on which side of the
176
+ * current migration state the target migration is on. Pass
177
+ * {@link MigrateToOptions.direction} to enforce a direction — if reaching the
178
+ * target would require migrating the other way, nothing is executed and
179
+ * {@link MigrationResultSet.error} holds an error:
180
+ *
181
+ * ```ts
182
+ * import fs from 'node:fs/promises'
183
+ * import path from 'node:path'
184
+ * import { FileMigrationProvider, Migrator } from 'kysely/migration'
185
+ *
186
+ * const migrator = new Migrator({
187
+ * db,
188
+ * provider: new FileMigrationProvider({
189
+ * fs,
190
+ * // Path to the folder that contains all your migrations.
191
+ * migrationFolder: 'some/path/to/migrations',
192
+ * path,
193
+ * })
194
+ * })
195
+ *
196
+ * // Errors if `some_migration` is already executed.
197
+ * await migrator.migrateTo('some_migration', { direction: 'Up' })
198
+ * ```
174
199
  */
175
200
  async migrateTo(targetMigrationName, options) {
176
201
  return this.#migrate(({ migrations, executedMigrations, pendingMigrations, }) => {
177
202
  if (isObject(targetMigrationName) &&
178
203
  targetMigrationName.__noMigrations__ === true) {
204
+ if (options?.direction === 'Up') {
205
+ throw new Error(`can't migrate up to NO_MIGRATIONS`);
206
+ }
179
207
  return { direction: 'Down', step: Infinity };
180
208
  }
181
209
  if (!migrations.find((m) => m.name === targetMigrationName)) {
@@ -184,12 +212,18 @@ export class Migrator {
184
212
  const executedIndex = executedMigrations.indexOf(targetMigrationName);
185
213
  const pendingIndex = pendingMigrations.findIndex((m) => m.name === targetMigrationName);
186
214
  if (executedIndex !== -1) {
215
+ if (options?.direction === 'Up') {
216
+ throw new Error(`migration "${targetMigrationName}" is already executed; can't migrate up to it`);
217
+ }
187
218
  return {
188
219
  direction: 'Down',
189
220
  step: executedMigrations.length - executedIndex - 1,
190
221
  };
191
222
  }
192
223
  if (pendingIndex !== -1) {
224
+ if (options?.direction === 'Down') {
225
+ throw new Error(`migration "${targetMigrationName}" isn't executed; can't migrate down to it`);
226
+ }
193
227
  return { direction: 'Up', step: pendingIndex + 1 };
194
228
  }
195
229
  throw new Error(`migration "${targetMigrationName}" isn't executed or pending`);
@@ -206,7 +240,7 @@ export class Migrator {
206
240
  * ### Examples
207
241
  *
208
242
  * ```ts
209
- * import { promises as fs } from 'node:fs'
243
+ * import fs from 'node:fs/promises'
210
244
  * import path from 'node:path'
211
245
  * import { FileMigrationProvider, Migrator } from 'kysely/migration'
212
246
  *
@@ -237,7 +271,7 @@ export class Migrator {
237
271
  * ### Examples
238
272
  *
239
273
  * ```ts
240
- * import { promises as fs } from 'node:fs'
274
+ * import fs from 'node:fs/promises'
241
275
  * import path from 'node:path'
242
276
  * import { FileMigrationProvider, Migrator } from 'kysely/migration'
243
277
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kysely",
3
- "version": "0.30.0-beta.0",
3
+ "version": "0.30.0-beta.1",
4
4
  "description": "Type safe SQL query builder",
5
5
  "repository": {
6
6
  "type": "git",