kysely 0.27.1 → 0.27.2

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.
@@ -1,6 +1,7 @@
1
1
  import { Kysely } from '../kysely.js';
2
2
  export declare const DEFAULT_MIGRATION_TABLE = "kysely_migration";
3
3
  export declare const DEFAULT_MIGRATION_LOCK_TABLE = "kysely_migration_lock";
4
+ export declare const DEFAULT_ALLOW_UNORDERED_MIGRATIONS = false;
4
5
  export declare const MIGRATION_LOCK_ID = "migration_lock";
5
6
  export declare const NO_MIGRATIONS: NoMigrations;
6
7
  export interface Migration {
@@ -58,7 +59,7 @@ export declare class Migrator {
58
59
  * This method goes through all possible migrations provided by the provider and runs the
59
60
  * ones whose names come alphabetically after the last migration that has been run. If the
60
61
  * list of executed migrations doesn't match the beginning of the list of possible migrations
61
- * an error is thrown.
62
+ * an error is returned.
62
63
  *
63
64
  * ### Examples
64
65
  *
@@ -197,6 +198,20 @@ export interface MigratorProps {
197
198
  * This only works on postgres.
198
199
  */
199
200
  readonly migrationTableSchema?: string;
201
+ /**
202
+ * Enforces whether or not migrations must be run in alpha-numeric order.
203
+ *
204
+ * When false, migrations must be run in their exact alpha-numeric order.
205
+ * This is checked against the migrations already run in the database
206
+ * (`migrationTableName'). This ensures your migrations are always run in
207
+ * the same order and is the safest option.
208
+ *
209
+ * When true, migrations are still run in alpha-numeric order, but
210
+ * the order is not checked against already-run migrations in the database.
211
+ * Kysely will simply run all migrations that haven't run yet, in alpha-numeric
212
+ * order.
213
+ */
214
+ readonly allowUnorderedMigrations?: boolean;
200
215
  }
201
216
  /**
202
217
  * All migration methods ({@link Migrator.migrateTo | migrateTo},
@@ -231,12 +246,13 @@ export interface MigrationResultSet {
231
246
  */
232
247
  readonly results?: MigrationResult[];
233
248
  }
249
+ type MigrationDirection = 'Up' | 'Down';
234
250
  export interface MigrationResult {
235
251
  readonly migrationName: string;
236
252
  /**
237
253
  * The direction in which this migration was executed.
238
254
  */
239
- readonly direction: 'Up' | 'Down';
255
+ readonly direction: MigrationDirection;
240
256
  /**
241
257
  * The execution status.
242
258
  *
@@ -293,3 +309,4 @@ export interface MigrationInfo {
293
309
  */
294
310
  executedAt?: Date;
295
311
  }
312
+ export {};
@@ -1,11 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Migrator = exports.NO_MIGRATIONS = exports.MIGRATION_LOCK_ID = exports.DEFAULT_MIGRATION_LOCK_TABLE = exports.DEFAULT_MIGRATION_TABLE = void 0;
3
+ exports.Migrator = exports.NO_MIGRATIONS = exports.MIGRATION_LOCK_ID = exports.DEFAULT_ALLOW_UNORDERED_MIGRATIONS = exports.DEFAULT_MIGRATION_LOCK_TABLE = exports.DEFAULT_MIGRATION_TABLE = void 0;
4
4
  const noop_plugin_js_1 = require("../plugin/noop-plugin.js");
5
5
  const with_schema_plugin_js_1 = require("../plugin/with-schema/with-schema-plugin.js");
6
6
  const object_utils_js_1 = require("../util/object-utils.js");
7
7
  exports.DEFAULT_MIGRATION_TABLE = 'kysely_migration';
8
8
  exports.DEFAULT_MIGRATION_LOCK_TABLE = 'kysely_migration_lock';
9
+ exports.DEFAULT_ALLOW_UNORDERED_MIGRATIONS = false;
9
10
  exports.MIGRATION_LOCK_ID = 'migration_lock';
10
11
  exports.NO_MIGRATIONS = (0, object_utils_js_1.freeze)({ __noMigrations__: true });
11
12
  /**
@@ -72,7 +73,7 @@ class Migrator {
72
73
  * This method goes through all possible migrations provided by the provider and runs the
73
74
  * ones whose names come alphabetically after the last migration that has been run. If the
74
75
  * list of executed migrations doesn't match the beginning of the list of possible migrations
75
- * an error is thrown.
76
+ * an error is returned.
76
77
  *
77
78
  * ### Examples
78
79
  *
@@ -109,7 +110,7 @@ class Migrator {
109
110
  * ```
110
111
  */
111
112
  async migrateToLatest() {
112
- return this.#migrate(({ migrations }) => migrations.length - 1);
113
+ return this.#migrate(() => ({ direction: 'Up', step: Infinity }));
113
114
  }
114
115
  /**
115
116
  * Migrate up/down to a specific migration.
@@ -135,15 +136,27 @@ class Migrator {
135
136
  * ```
136
137
  */
137
138
  async migrateTo(targetMigrationName) {
138
- return this.#migrate(({ migrations }) => {
139
+ return this.#migrate(({ migrations, executedMigrations, pendingMigrations, }) => {
139
140
  if (targetMigrationName === exports.NO_MIGRATIONS) {
140
- return -1;
141
+ return { direction: 'Down', step: Infinity };
141
142
  }
142
- const index = migrations.findIndex((it) => it.name === targetMigrationName);
143
- if (index === -1) {
143
+ if (!migrations.find((m) => m.name === targetMigrationName)) {
144
144
  throw new Error(`migration "${targetMigrationName}" doesn't exist`);
145
145
  }
146
- return index;
146
+ const executedIndex = executedMigrations.indexOf(targetMigrationName);
147
+ const pendingIndex = pendingMigrations.findIndex((m) => m.name === targetMigrationName);
148
+ if (executedIndex !== -1) {
149
+ return {
150
+ direction: 'Down',
151
+ step: executedMigrations.length - executedIndex - 1,
152
+ };
153
+ }
154
+ else if (pendingIndex !== -1) {
155
+ return { direction: 'Up', step: pendingIndex + 1 };
156
+ }
157
+ else {
158
+ throw new Error(`migration "${targetMigrationName}" isn't executed or pending`);
159
+ }
147
160
  });
148
161
  }
149
162
  /**
@@ -161,7 +174,7 @@ class Migrator {
161
174
  * ```
162
175
  */
163
176
  async migrateUp() {
164
- return this.#migrate(({ currentIndex, migrations }) => Math.min(currentIndex + 1, migrations.length - 1));
177
+ return this.#migrate(() => ({ direction: 'Up', step: 1 }));
165
178
  }
166
179
  /**
167
180
  * Migrate one step down.
@@ -178,12 +191,12 @@ class Migrator {
178
191
  * ```
179
192
  */
180
193
  async migrateDown() {
181
- return this.#migrate(({ currentIndex }) => Math.max(currentIndex - 1, -1));
194
+ return this.#migrate(() => ({ direction: 'Down', step: 1 }));
182
195
  }
183
- async #migrate(getTargetMigrationIndex) {
196
+ async #migrate(getMigrationDirectionAndStep) {
184
197
  try {
185
198
  await this.#ensureMigrationTablesExists();
186
- return await this.#runMigrations(getTargetMigrationIndex);
199
+ return await this.#runMigrations(getMigrationDirectionAndStep);
187
200
  }
188
201
  catch (error) {
189
202
  if (error instanceof MigrationResultSetError) {
@@ -201,6 +214,9 @@ class Migrator {
201
214
  get #migrationLockTable() {
202
215
  return this.#props.migrationLockTableName ?? exports.DEFAULT_MIGRATION_LOCK_TABLE;
203
216
  }
217
+ get #allowUnorderedMigrations() {
218
+ return (this.#props.allowUnorderedMigrations ?? exports.DEFAULT_ALLOW_UNORDERED_MIGRATIONS);
219
+ }
204
220
  get #schemaPlugin() {
205
221
  if (this.#migrationTableSchema) {
206
222
  return new with_schema_plugin_js_1.WithSchemaPlugin(this.#migrationTableSchema);
@@ -311,7 +327,7 @@ class Migrator {
311
327
  .executeTakeFirst();
312
328
  return !!lockRow;
313
329
  }
314
- async #runMigrations(getTargetMigrationIndex) {
330
+ async #runMigrations(getMigrationDirectionAndStep) {
315
331
  const adapter = this.#props.db.getExecutor().adapter;
316
332
  const lockOptions = (0, object_utils_js_1.freeze)({
317
333
  lockTable: this.#props.migrationLockTableName ?? exports.DEFAULT_MIGRATION_LOCK_TABLE,
@@ -325,15 +341,15 @@ class Migrator {
325
341
  if (state.migrations.length === 0) {
326
342
  return { results: [] };
327
343
  }
328
- const targetIndex = getTargetMigrationIndex(state);
329
- if (targetIndex === undefined) {
344
+ const { direction, step } = getMigrationDirectionAndStep(state);
345
+ if (step <= 0) {
330
346
  return { results: [] };
331
347
  }
332
- if (targetIndex < state.currentIndex) {
333
- return await this.#migrateDown(db, state, targetIndex);
348
+ if (direction === 'Down') {
349
+ return await this.#migrateDown(db, state, step);
334
350
  }
335
- else if (targetIndex > state.currentIndex) {
336
- return await this.#migrateUp(db, state, targetIndex);
351
+ else if (direction === 'Up') {
352
+ return await this.#migrateUp(db, state, step);
337
353
  }
338
354
  return { results: [] };
339
355
  }
@@ -351,10 +367,21 @@ class Migrator {
351
367
  async #getState(db) {
352
368
  const migrations = await this.#resolveMigrations();
353
369
  const executedMigrations = await this.#getExecutedMigrations(db);
354
- this.#ensureMigrationsNotCorrupted(migrations, executedMigrations);
370
+ this.#ensureNoMissingMigrations(migrations, executedMigrations);
371
+ if (!this.#allowUnorderedMigrations) {
372
+ this.#ensureMigrationsInOrder(migrations, executedMigrations);
373
+ }
374
+ const pendingMigrations = this.#getPendingMigrations(migrations, executedMigrations);
355
375
  return (0, object_utils_js_1.freeze)({
356
376
  migrations,
357
- currentIndex: migrations.findIndex((it) => it.name === (0, object_utils_js_1.getLast)(executedMigrations)),
377
+ executedMigrations,
378
+ lastMigration: (0, object_utils_js_1.getLast)(executedMigrations),
379
+ pendingMigrations,
380
+ });
381
+ }
382
+ #getPendingMigrations(migrations, executedMigrations) {
383
+ return migrations.filter((migration) => {
384
+ return !executedMigrations.includes(migration.name);
358
385
  });
359
386
  }
360
387
  async #resolveMigrations() {
@@ -371,36 +398,43 @@ class Migrator {
371
398
  .withPlugin(this.#schemaPlugin)
372
399
  .selectFrom(this.#migrationTable)
373
400
  .select('name')
374
- .orderBy('name')
401
+ .orderBy(['timestamp', 'name'])
375
402
  .execute();
376
403
  return executedMigrations.map((it) => it.name);
377
404
  }
378
- #ensureMigrationsNotCorrupted(migrations, executedMigrations) {
405
+ #ensureNoMissingMigrations(migrations, executedMigrations) {
406
+ // Ensure all executed migrations exist in the `migrations` list.
379
407
  for (const executed of executedMigrations) {
380
408
  if (!migrations.some((it) => it.name === executed)) {
381
409
  throw new Error(`corrupted migrations: previously executed migration ${executed} is missing`);
382
410
  }
383
411
  }
384
- // Now we know all executed migrations exist in the `migrations` list.
385
- // Next we need to make sure that the executed migratiosns are the first
386
- // ones in the migration list.
412
+ }
413
+ #ensureMigrationsInOrder(migrations, executedMigrations) {
414
+ // Ensure the executed migrations are the first ones in the migration list.
387
415
  for (let i = 0; i < executedMigrations.length; ++i) {
388
416
  if (migrations[i].name !== executedMigrations[i]) {
389
417
  throw new Error(`corrupted migrations: expected previously executed migration ${executedMigrations[i]} to be at index ${i} but ${migrations[i].name} was found in its place. New migrations must always have a name that comes alphabetically after the last executed migration.`);
390
418
  }
391
419
  }
392
420
  }
393
- async #migrateDown(db, state, targetIndex) {
394
- const results = [];
395
- for (let i = state.currentIndex; i > targetIndex; --i) {
396
- results.push({
397
- migrationName: state.migrations[i].name,
421
+ async #migrateDown(db, state, step) {
422
+ const migrationsToRollback = state.executedMigrations
423
+ .slice()
424
+ .reverse()
425
+ .slice(0, step)
426
+ .map((name) => {
427
+ return state.migrations.find((it) => it.name === name);
428
+ });
429
+ const results = migrationsToRollback.map((migration) => {
430
+ return {
431
+ migrationName: migration.name,
398
432
  direction: 'Down',
399
433
  status: 'NotExecuted',
400
- });
401
- }
434
+ };
435
+ });
402
436
  for (let i = 0; i < results.length; ++i) {
403
- const migration = state.migrations.find((it) => it.name === results[i].migrationName);
437
+ const migration = migrationsToRollback[i];
404
438
  try {
405
439
  if (migration.down) {
406
440
  await migration.down(db);
@@ -430,17 +464,17 @@ class Migrator {
430
464
  }
431
465
  return { results };
432
466
  }
433
- async #migrateUp(db, state, targetIndex) {
434
- const results = [];
435
- for (let i = state.currentIndex + 1; i <= targetIndex; ++i) {
436
- results.push({
437
- migrationName: state.migrations[i].name,
467
+ async #migrateUp(db, state, step) {
468
+ const migrationsToRun = state.pendingMigrations.slice(0, step);
469
+ const results = migrationsToRun.map((migration) => {
470
+ return {
471
+ migrationName: migration.name,
438
472
  direction: 'Up',
439
473
  status: 'NotExecuted',
440
- });
441
- }
442
- for (let i = 0; i < results.length; ++i) {
443
- const migration = state.migrations.find((it) => it.name === results[i].migrationName);
474
+ };
475
+ });
476
+ for (let i = 0; i < results.length; i++) {
477
+ const migration = state.pendingMigrations[i];
444
478
  try {
445
479
  await migration.up(db);
446
480
  await db
@@ -1,7 +1,7 @@
1
1
  import { OperationNodeSource } from '../operation-node/operation-node-source.js';
2
2
  import { CompiledQuery } from '../query-compiler/compiled-query.js';
3
3
  import { SelectExpression, SelectCallback } from '../parser/select-parser.js';
4
- import { InsertObjectOrList, InsertObjectOrListFactory } from '../parser/insert-values-parser.js';
4
+ import { InsertExpression } from '../parser/insert-values-parser.js';
5
5
  import { InsertQueryNode } from '../operation-node/insert-query-node.js';
6
6
  import { QueryNode } from '../operation-node/query-node.js';
7
7
  import { NarrowPartial, SimplifyResult, SimplifySingleResult } from '../util/type-utils.js';
@@ -184,8 +184,7 @@ export declare class InsertQueryBuilder<DB, TB extends keyof DB, O> implements R
184
184
  * }))
185
185
  * ```
186
186
  */
187
- values(insert: InsertObjectOrList<DB, TB>): InsertQueryBuilder<DB, TB, O>;
188
- values(insert: InsertObjectOrListFactory<DB, TB>): InsertQueryBuilder<DB, TB, O>;
187
+ values(insert: InsertExpression<DB, TB>): InsertQueryBuilder<DB, TB, O>;
189
188
  /**
190
189
  * Sets the columns to insert.
191
190
  *
@@ -20,6 +20,166 @@ class InsertQueryBuilder {
20
20
  constructor(props) {
21
21
  this.#props = (0, object_utils_js_1.freeze)(props);
22
22
  }
23
+ /**
24
+ * Sets the values to insert for an {@link Kysely.insertInto | insert} query.
25
+ *
26
+ * This method takes an object whose keys are column names and values are
27
+ * values to insert. In addition to the column's type, the values can be
28
+ * raw {@link sql} snippets or select queries.
29
+ *
30
+ * You must provide all fields you haven't explicitly marked as nullable
31
+ * or optional using {@link Generated} or {@link ColumnType}.
32
+ *
33
+ * The return value of an `insert` query is an instance of {@link InsertResult}. The
34
+ * {@link InsertResult.insertId | insertId} field holds the auto incremented primary
35
+ * key if the database returned one.
36
+ *
37
+ * On PostgreSQL and some other dialects, you need to call `returning` to get
38
+ * something out of the query.
39
+ *
40
+ * Also see the {@link expression} method for inserting the result of a select
41
+ * query or any other expression.
42
+ *
43
+ * ### Examples
44
+ *
45
+ * <!-- siteExample("insert", "Single row", 10) -->
46
+ *
47
+ * Insert a single row:
48
+ *
49
+ * ```ts
50
+ * const result = await db
51
+ * .insertInto('person')
52
+ * .values({
53
+ * first_name: 'Jennifer',
54
+ * last_name: 'Aniston',
55
+ * age: 40
56
+ * })
57
+ * .executeTakeFirst()
58
+ *
59
+ * // `insertId` is only available on dialects that
60
+ * // automatically return the id of the inserted row
61
+ * // such as MySQL and SQLite. On PostgreSQL, for example,
62
+ * // you need to add a `returning` clause to the query to
63
+ * // get anything out. See the "returning data" example.
64
+ * console.log(result.insertId)
65
+ * ```
66
+ *
67
+ * The generated SQL (MySQL):
68
+ *
69
+ * ```sql
70
+ * insert into `person` (`first_name`, `last_name`, `age`) values (?, ?, ?)
71
+ * ```
72
+ *
73
+ * <!-- siteExample("insert", "Multiple rows", 20) -->
74
+ *
75
+ * On dialects that support it (for example PostgreSQL) you can insert multiple
76
+ * rows by providing an array. Note that the return value is once again very
77
+ * dialect-specific. Some databases may only return the id of the *last* inserted
78
+ * row and some return nothing at all unless you call `returning`.
79
+ *
80
+ * ```ts
81
+ * await db
82
+ * .insertInto('person')
83
+ * .values([{
84
+ * first_name: 'Jennifer',
85
+ * last_name: 'Aniston',
86
+ * age: 40,
87
+ * }, {
88
+ * first_name: 'Arnold',
89
+ * last_name: 'Schwarzenegger',
90
+ * age: 70,
91
+ * }])
92
+ * .execute()
93
+ * ```
94
+ *
95
+ * The generated SQL (PostgreSQL):
96
+ *
97
+ * ```sql
98
+ * insert into "person" ("first_name", "last_name", "age") values (($1, $2, $3), ($4, $5, $6))
99
+ * ```
100
+ *
101
+ * <!-- siteExample("insert", "Returning data", 30) -->
102
+ *
103
+ * On supported dialects like PostgreSQL you need to chain `returning` to the query to get
104
+ * the inserted row's columns (or any other expression) as the return value. `returning`
105
+ * works just like `select`. Refer to `select` method's examples and documentation for
106
+ * more info.
107
+ *
108
+ * ```ts
109
+ * const result = await db
110
+ * .insertInto('person')
111
+ * .values({
112
+ * first_name: 'Jennifer',
113
+ * last_name: 'Aniston',
114
+ * age: 40,
115
+ * })
116
+ * .returning(['id', 'first_name as name'])
117
+ * .executeTakeFirstOrThrow()
118
+ * ```
119
+ *
120
+ * The generated SQL (PostgreSQL):
121
+ *
122
+ * ```sql
123
+ * insert into "person" ("first_name", "last_name", "age") values ($1, $2, $3) returning "id", "first_name" as "name"
124
+ * ```
125
+ *
126
+ * <!-- siteExample("insert", "Complex values", 40) -->
127
+ *
128
+ * In addition to primitives, the values can also be arbitrary expressions.
129
+ * You can build the expressions by using a callback and calling the methods
130
+ * on the expression builder passed to it:
131
+ *
132
+ * ```ts
133
+ * import { sql } from 'kysely'
134
+ *
135
+ * const ani = "Ani"
136
+ * const ston = "ston"
137
+ *
138
+ * const result = await db
139
+ * .insertInto('person')
140
+ * .values(({ ref, selectFrom, fn }) => ({
141
+ * first_name: 'Jennifer',
142
+ * last_name: sql`concat(${ani}, ${ston})`,
143
+ * middle_name: ref('first_name'),
144
+ * age: selectFrom('person')
145
+ * .select(fn.avg<number>('age')
146
+ * .as('avg_age')),
147
+ * }))
148
+ * .executeTakeFirst()
149
+ * ```
150
+ *
151
+ * The generated SQL (PostgreSQL):
152
+ *
153
+ * ```sql
154
+ * insert into "person" (
155
+ * "first_name",
156
+ * "last_name",
157
+ * "middle_name",
158
+ * "age"
159
+ * )
160
+ * values (
161
+ * $1,
162
+ * concat($2, $3),
163
+ * "first_name",
164
+ * (select avg("age") as "avg_age" from "person")
165
+ * )
166
+ * ```
167
+ *
168
+ * You can also use the callback version of subqueries or raw expressions:
169
+ *
170
+ * ```ts
171
+ * db.with('jennifer', (db) => db
172
+ * .selectFrom('person')
173
+ * .where('first_name', '=', 'Jennifer')
174
+ * .select(['id', 'first_name', 'gender'])
175
+ * .limit(1)
176
+ * ).insertInto('pet').values((eb) => ({
177
+ * owner_id: eb.selectFrom('jennifer').select('id'),
178
+ * name: eb.selectFrom('jennifer').select('first_name'),
179
+ * species: 'cat',
180
+ * }))
181
+ * ```
182
+ */
23
183
  values(insert) {
24
184
  const [columns, values] = (0, insert_values_parser_js_1.parseInsertExpression)(insert);
25
185
  return new InsertQueryBuilder({
@@ -8,7 +8,7 @@ import { ReferenceExpression } from '../parser/reference-parser.js';
8
8
  import { QueryNode } from '../operation-node/query-node.js';
9
9
  import { DrainOuterGeneric, NarrowPartial, Nullable, ShallowRecord, SimplifyResult, SimplifySingleResult, SqlBool } from '../util/type-utils.js';
10
10
  import { UpdateQueryNode } from '../operation-node/update-query-node.js';
11
- import { UpdateObject, UpdateObjectFactory, ExtractUpdateTypeFromReferenceExpression } from '../parser/update-set-parser.js';
11
+ import { UpdateObjectExpression, ExtractUpdateTypeFromReferenceExpression } from '../parser/update-set-parser.js';
12
12
  import { Compilable } from '../util/compilable.js';
13
13
  import { QueryExecutor } from '../query-executor/query-executor.js';
14
14
  import { QueryId } from '../util/query-id.js';
@@ -695,8 +695,7 @@ export declare class UpdateQueryBuilder<DB, UT extends keyof DB, TB extends keyo
695
695
  * where "id" = $5
696
696
  * ```
697
697
  */
698
- set(update: UpdateObject<DB, TB, UT>): UpdateQueryBuilder<DB, UT, TB, O>;
699
- set(update: UpdateObjectFactory<DB, TB, UT>): UpdateQueryBuilder<DB, UT, TB, O>;
698
+ set(update: UpdateObjectExpression<DB, TB, UT>): UpdateQueryBuilder<DB, UT, TB, O>;
700
699
  set<RE extends ReferenceExpression<DB, UT>>(key: RE, value: ValueExpression<DB, TB, ExtractUpdateTypeFromReferenceExpression<DB, UT, RE>>): UpdateQueryBuilder<DB, UT, TB, O>;
701
700
  /**
702
701
  * Allows you to return data from modified rows.
@@ -1,6 +1,7 @@
1
1
  import { Kysely } from '../kysely.js';
2
2
  export declare const DEFAULT_MIGRATION_TABLE = "kysely_migration";
3
3
  export declare const DEFAULT_MIGRATION_LOCK_TABLE = "kysely_migration_lock";
4
+ export declare const DEFAULT_ALLOW_UNORDERED_MIGRATIONS = false;
4
5
  export declare const MIGRATION_LOCK_ID = "migration_lock";
5
6
  export declare const NO_MIGRATIONS: NoMigrations;
6
7
  export interface Migration {
@@ -58,7 +59,7 @@ export declare class Migrator {
58
59
  * This method goes through all possible migrations provided by the provider and runs the
59
60
  * ones whose names come alphabetically after the last migration that has been run. If the
60
61
  * list of executed migrations doesn't match the beginning of the list of possible migrations
61
- * an error is thrown.
62
+ * an error is returned.
62
63
  *
63
64
  * ### Examples
64
65
  *
@@ -197,6 +198,20 @@ export interface MigratorProps {
197
198
  * This only works on postgres.
198
199
  */
199
200
  readonly migrationTableSchema?: string;
201
+ /**
202
+ * Enforces whether or not migrations must be run in alpha-numeric order.
203
+ *
204
+ * When false, migrations must be run in their exact alpha-numeric order.
205
+ * This is checked against the migrations already run in the database
206
+ * (`migrationTableName'). This ensures your migrations are always run in
207
+ * the same order and is the safest option.
208
+ *
209
+ * When true, migrations are still run in alpha-numeric order, but
210
+ * the order is not checked against already-run migrations in the database.
211
+ * Kysely will simply run all migrations that haven't run yet, in alpha-numeric
212
+ * order.
213
+ */
214
+ readonly allowUnorderedMigrations?: boolean;
200
215
  }
201
216
  /**
202
217
  * All migration methods ({@link Migrator.migrateTo | migrateTo},
@@ -231,12 +246,13 @@ export interface MigrationResultSet {
231
246
  */
232
247
  readonly results?: MigrationResult[];
233
248
  }
249
+ type MigrationDirection = 'Up' | 'Down';
234
250
  export interface MigrationResult {
235
251
  readonly migrationName: string;
236
252
  /**
237
253
  * The direction in which this migration was executed.
238
254
  */
239
- readonly direction: 'Up' | 'Down';
255
+ readonly direction: MigrationDirection;
240
256
  /**
241
257
  * The execution status.
242
258
  *
@@ -293,3 +309,4 @@ export interface MigrationInfo {
293
309
  */
294
310
  executedAt?: Date;
295
311
  }
312
+ export {};
@@ -4,6 +4,7 @@ import { WithSchemaPlugin } from '../plugin/with-schema/with-schema-plugin.js';
4
4
  import { freeze, getLast } from '../util/object-utils.js';
5
5
  export const DEFAULT_MIGRATION_TABLE = 'kysely_migration';
6
6
  export const DEFAULT_MIGRATION_LOCK_TABLE = 'kysely_migration_lock';
7
+ export const DEFAULT_ALLOW_UNORDERED_MIGRATIONS = false;
7
8
  export const MIGRATION_LOCK_ID = 'migration_lock';
8
9
  export const NO_MIGRATIONS = freeze({ __noMigrations__: true });
9
10
  /**
@@ -70,7 +71,7 @@ export class Migrator {
70
71
  * This method goes through all possible migrations provided by the provider and runs the
71
72
  * ones whose names come alphabetically after the last migration that has been run. If the
72
73
  * list of executed migrations doesn't match the beginning of the list of possible migrations
73
- * an error is thrown.
74
+ * an error is returned.
74
75
  *
75
76
  * ### Examples
76
77
  *
@@ -107,7 +108,7 @@ export class Migrator {
107
108
  * ```
108
109
  */
109
110
  async migrateToLatest() {
110
- return this.#migrate(({ migrations }) => migrations.length - 1);
111
+ return this.#migrate(() => ({ direction: 'Up', step: Infinity }));
111
112
  }
112
113
  /**
113
114
  * Migrate up/down to a specific migration.
@@ -133,15 +134,27 @@ export class Migrator {
133
134
  * ```
134
135
  */
135
136
  async migrateTo(targetMigrationName) {
136
- return this.#migrate(({ migrations }) => {
137
+ return this.#migrate(({ migrations, executedMigrations, pendingMigrations, }) => {
137
138
  if (targetMigrationName === NO_MIGRATIONS) {
138
- return -1;
139
+ return { direction: 'Down', step: Infinity };
139
140
  }
140
- const index = migrations.findIndex((it) => it.name === targetMigrationName);
141
- if (index === -1) {
141
+ if (!migrations.find((m) => m.name === targetMigrationName)) {
142
142
  throw new Error(`migration "${targetMigrationName}" doesn't exist`);
143
143
  }
144
- return index;
144
+ const executedIndex = executedMigrations.indexOf(targetMigrationName);
145
+ const pendingIndex = pendingMigrations.findIndex((m) => m.name === targetMigrationName);
146
+ if (executedIndex !== -1) {
147
+ return {
148
+ direction: 'Down',
149
+ step: executedMigrations.length - executedIndex - 1,
150
+ };
151
+ }
152
+ else if (pendingIndex !== -1) {
153
+ return { direction: 'Up', step: pendingIndex + 1 };
154
+ }
155
+ else {
156
+ throw new Error(`migration "${targetMigrationName}" isn't executed or pending`);
157
+ }
145
158
  });
146
159
  }
147
160
  /**
@@ -159,7 +172,7 @@ export class Migrator {
159
172
  * ```
160
173
  */
161
174
  async migrateUp() {
162
- return this.#migrate(({ currentIndex, migrations }) => Math.min(currentIndex + 1, migrations.length - 1));
175
+ return this.#migrate(() => ({ direction: 'Up', step: 1 }));
163
176
  }
164
177
  /**
165
178
  * Migrate one step down.
@@ -176,12 +189,12 @@ export class Migrator {
176
189
  * ```
177
190
  */
178
191
  async migrateDown() {
179
- return this.#migrate(({ currentIndex }) => Math.max(currentIndex - 1, -1));
192
+ return this.#migrate(() => ({ direction: 'Down', step: 1 }));
180
193
  }
181
- async #migrate(getTargetMigrationIndex) {
194
+ async #migrate(getMigrationDirectionAndStep) {
182
195
  try {
183
196
  await this.#ensureMigrationTablesExists();
184
- return await this.#runMigrations(getTargetMigrationIndex);
197
+ return await this.#runMigrations(getMigrationDirectionAndStep);
185
198
  }
186
199
  catch (error) {
187
200
  if (error instanceof MigrationResultSetError) {
@@ -199,6 +212,9 @@ export class Migrator {
199
212
  get #migrationLockTable() {
200
213
  return this.#props.migrationLockTableName ?? DEFAULT_MIGRATION_LOCK_TABLE;
201
214
  }
215
+ get #allowUnorderedMigrations() {
216
+ return (this.#props.allowUnorderedMigrations ?? DEFAULT_ALLOW_UNORDERED_MIGRATIONS);
217
+ }
202
218
  get #schemaPlugin() {
203
219
  if (this.#migrationTableSchema) {
204
220
  return new WithSchemaPlugin(this.#migrationTableSchema);
@@ -309,7 +325,7 @@ export class Migrator {
309
325
  .executeTakeFirst();
310
326
  return !!lockRow;
311
327
  }
312
- async #runMigrations(getTargetMigrationIndex) {
328
+ async #runMigrations(getMigrationDirectionAndStep) {
313
329
  const adapter = this.#props.db.getExecutor().adapter;
314
330
  const lockOptions = freeze({
315
331
  lockTable: this.#props.migrationLockTableName ?? DEFAULT_MIGRATION_LOCK_TABLE,
@@ -323,15 +339,15 @@ export class Migrator {
323
339
  if (state.migrations.length === 0) {
324
340
  return { results: [] };
325
341
  }
326
- const targetIndex = getTargetMigrationIndex(state);
327
- if (targetIndex === undefined) {
342
+ const { direction, step } = getMigrationDirectionAndStep(state);
343
+ if (step <= 0) {
328
344
  return { results: [] };
329
345
  }
330
- if (targetIndex < state.currentIndex) {
331
- return await this.#migrateDown(db, state, targetIndex);
346
+ if (direction === 'Down') {
347
+ return await this.#migrateDown(db, state, step);
332
348
  }
333
- else if (targetIndex > state.currentIndex) {
334
- return await this.#migrateUp(db, state, targetIndex);
349
+ else if (direction === 'Up') {
350
+ return await this.#migrateUp(db, state, step);
335
351
  }
336
352
  return { results: [] };
337
353
  }
@@ -349,10 +365,21 @@ export class Migrator {
349
365
  async #getState(db) {
350
366
  const migrations = await this.#resolveMigrations();
351
367
  const executedMigrations = await this.#getExecutedMigrations(db);
352
- this.#ensureMigrationsNotCorrupted(migrations, executedMigrations);
368
+ this.#ensureNoMissingMigrations(migrations, executedMigrations);
369
+ if (!this.#allowUnorderedMigrations) {
370
+ this.#ensureMigrationsInOrder(migrations, executedMigrations);
371
+ }
372
+ const pendingMigrations = this.#getPendingMigrations(migrations, executedMigrations);
353
373
  return freeze({
354
374
  migrations,
355
- currentIndex: migrations.findIndex((it) => it.name === getLast(executedMigrations)),
375
+ executedMigrations,
376
+ lastMigration: getLast(executedMigrations),
377
+ pendingMigrations,
378
+ });
379
+ }
380
+ #getPendingMigrations(migrations, executedMigrations) {
381
+ return migrations.filter((migration) => {
382
+ return !executedMigrations.includes(migration.name);
356
383
  });
357
384
  }
358
385
  async #resolveMigrations() {
@@ -369,36 +396,43 @@ export class Migrator {
369
396
  .withPlugin(this.#schemaPlugin)
370
397
  .selectFrom(this.#migrationTable)
371
398
  .select('name')
372
- .orderBy('name')
399
+ .orderBy(['timestamp', 'name'])
373
400
  .execute();
374
401
  return executedMigrations.map((it) => it.name);
375
402
  }
376
- #ensureMigrationsNotCorrupted(migrations, executedMigrations) {
403
+ #ensureNoMissingMigrations(migrations, executedMigrations) {
404
+ // Ensure all executed migrations exist in the `migrations` list.
377
405
  for (const executed of executedMigrations) {
378
406
  if (!migrations.some((it) => it.name === executed)) {
379
407
  throw new Error(`corrupted migrations: previously executed migration ${executed} is missing`);
380
408
  }
381
409
  }
382
- // Now we know all executed migrations exist in the `migrations` list.
383
- // Next we need to make sure that the executed migratiosns are the first
384
- // ones in the migration list.
410
+ }
411
+ #ensureMigrationsInOrder(migrations, executedMigrations) {
412
+ // Ensure the executed migrations are the first ones in the migration list.
385
413
  for (let i = 0; i < executedMigrations.length; ++i) {
386
414
  if (migrations[i].name !== executedMigrations[i]) {
387
415
  throw new Error(`corrupted migrations: expected previously executed migration ${executedMigrations[i]} to be at index ${i} but ${migrations[i].name} was found in its place. New migrations must always have a name that comes alphabetically after the last executed migration.`);
388
416
  }
389
417
  }
390
418
  }
391
- async #migrateDown(db, state, targetIndex) {
392
- const results = [];
393
- for (let i = state.currentIndex; i > targetIndex; --i) {
394
- results.push({
395
- migrationName: state.migrations[i].name,
419
+ async #migrateDown(db, state, step) {
420
+ const migrationsToRollback = state.executedMigrations
421
+ .slice()
422
+ .reverse()
423
+ .slice(0, step)
424
+ .map((name) => {
425
+ return state.migrations.find((it) => it.name === name);
426
+ });
427
+ const results = migrationsToRollback.map((migration) => {
428
+ return {
429
+ migrationName: migration.name,
396
430
  direction: 'Down',
397
431
  status: 'NotExecuted',
398
- });
399
- }
432
+ };
433
+ });
400
434
  for (let i = 0; i < results.length; ++i) {
401
- const migration = state.migrations.find((it) => it.name === results[i].migrationName);
435
+ const migration = migrationsToRollback[i];
402
436
  try {
403
437
  if (migration.down) {
404
438
  await migration.down(db);
@@ -428,17 +462,17 @@ export class Migrator {
428
462
  }
429
463
  return { results };
430
464
  }
431
- async #migrateUp(db, state, targetIndex) {
432
- const results = [];
433
- for (let i = state.currentIndex + 1; i <= targetIndex; ++i) {
434
- results.push({
435
- migrationName: state.migrations[i].name,
465
+ async #migrateUp(db, state, step) {
466
+ const migrationsToRun = state.pendingMigrations.slice(0, step);
467
+ const results = migrationsToRun.map((migration) => {
468
+ return {
469
+ migrationName: migration.name,
436
470
  direction: 'Up',
437
471
  status: 'NotExecuted',
438
- });
439
- }
440
- for (let i = 0; i < results.length; ++i) {
441
- const migration = state.migrations.find((it) => it.name === results[i].migrationName);
472
+ };
473
+ });
474
+ for (let i = 0; i < results.length; i++) {
475
+ const migration = state.pendingMigrations[i];
442
476
  try {
443
477
  await migration.up(db);
444
478
  await db
@@ -1,7 +1,7 @@
1
1
  import { OperationNodeSource } from '../operation-node/operation-node-source.js';
2
2
  import { CompiledQuery } from '../query-compiler/compiled-query.js';
3
3
  import { SelectExpression, SelectCallback } from '../parser/select-parser.js';
4
- import { InsertObjectOrList, InsertObjectOrListFactory } from '../parser/insert-values-parser.js';
4
+ import { InsertExpression } from '../parser/insert-values-parser.js';
5
5
  import { InsertQueryNode } from '../operation-node/insert-query-node.js';
6
6
  import { QueryNode } from '../operation-node/query-node.js';
7
7
  import { NarrowPartial, SimplifyResult, SimplifySingleResult } from '../util/type-utils.js';
@@ -184,8 +184,7 @@ export declare class InsertQueryBuilder<DB, TB extends keyof DB, O> implements R
184
184
  * }))
185
185
  * ```
186
186
  */
187
- values(insert: InsertObjectOrList<DB, TB>): InsertQueryBuilder<DB, TB, O>;
188
- values(insert: InsertObjectOrListFactory<DB, TB>): InsertQueryBuilder<DB, TB, O>;
187
+ values(insert: InsertExpression<DB, TB>): InsertQueryBuilder<DB, TB, O>;
189
188
  /**
190
189
  * Sets the columns to insert.
191
190
  *
@@ -18,6 +18,166 @@ export class InsertQueryBuilder {
18
18
  constructor(props) {
19
19
  this.#props = freeze(props);
20
20
  }
21
+ /**
22
+ * Sets the values to insert for an {@link Kysely.insertInto | insert} query.
23
+ *
24
+ * This method takes an object whose keys are column names and values are
25
+ * values to insert. In addition to the column's type, the values can be
26
+ * raw {@link sql} snippets or select queries.
27
+ *
28
+ * You must provide all fields you haven't explicitly marked as nullable
29
+ * or optional using {@link Generated} or {@link ColumnType}.
30
+ *
31
+ * The return value of an `insert` query is an instance of {@link InsertResult}. The
32
+ * {@link InsertResult.insertId | insertId} field holds the auto incremented primary
33
+ * key if the database returned one.
34
+ *
35
+ * On PostgreSQL and some other dialects, you need to call `returning` to get
36
+ * something out of the query.
37
+ *
38
+ * Also see the {@link expression} method for inserting the result of a select
39
+ * query or any other expression.
40
+ *
41
+ * ### Examples
42
+ *
43
+ * <!-- siteExample("insert", "Single row", 10) -->
44
+ *
45
+ * Insert a single row:
46
+ *
47
+ * ```ts
48
+ * const result = await db
49
+ * .insertInto('person')
50
+ * .values({
51
+ * first_name: 'Jennifer',
52
+ * last_name: 'Aniston',
53
+ * age: 40
54
+ * })
55
+ * .executeTakeFirst()
56
+ *
57
+ * // `insertId` is only available on dialects that
58
+ * // automatically return the id of the inserted row
59
+ * // such as MySQL and SQLite. On PostgreSQL, for example,
60
+ * // you need to add a `returning` clause to the query to
61
+ * // get anything out. See the "returning data" example.
62
+ * console.log(result.insertId)
63
+ * ```
64
+ *
65
+ * The generated SQL (MySQL):
66
+ *
67
+ * ```sql
68
+ * insert into `person` (`first_name`, `last_name`, `age`) values (?, ?, ?)
69
+ * ```
70
+ *
71
+ * <!-- siteExample("insert", "Multiple rows", 20) -->
72
+ *
73
+ * On dialects that support it (for example PostgreSQL) you can insert multiple
74
+ * rows by providing an array. Note that the return value is once again very
75
+ * dialect-specific. Some databases may only return the id of the *last* inserted
76
+ * row and some return nothing at all unless you call `returning`.
77
+ *
78
+ * ```ts
79
+ * await db
80
+ * .insertInto('person')
81
+ * .values([{
82
+ * first_name: 'Jennifer',
83
+ * last_name: 'Aniston',
84
+ * age: 40,
85
+ * }, {
86
+ * first_name: 'Arnold',
87
+ * last_name: 'Schwarzenegger',
88
+ * age: 70,
89
+ * }])
90
+ * .execute()
91
+ * ```
92
+ *
93
+ * The generated SQL (PostgreSQL):
94
+ *
95
+ * ```sql
96
+ * insert into "person" ("first_name", "last_name", "age") values (($1, $2, $3), ($4, $5, $6))
97
+ * ```
98
+ *
99
+ * <!-- siteExample("insert", "Returning data", 30) -->
100
+ *
101
+ * On supported dialects like PostgreSQL you need to chain `returning` to the query to get
102
+ * the inserted row's columns (or any other expression) as the return value. `returning`
103
+ * works just like `select`. Refer to `select` method's examples and documentation for
104
+ * more info.
105
+ *
106
+ * ```ts
107
+ * const result = await db
108
+ * .insertInto('person')
109
+ * .values({
110
+ * first_name: 'Jennifer',
111
+ * last_name: 'Aniston',
112
+ * age: 40,
113
+ * })
114
+ * .returning(['id', 'first_name as name'])
115
+ * .executeTakeFirstOrThrow()
116
+ * ```
117
+ *
118
+ * The generated SQL (PostgreSQL):
119
+ *
120
+ * ```sql
121
+ * insert into "person" ("first_name", "last_name", "age") values ($1, $2, $3) returning "id", "first_name" as "name"
122
+ * ```
123
+ *
124
+ * <!-- siteExample("insert", "Complex values", 40) -->
125
+ *
126
+ * In addition to primitives, the values can also be arbitrary expressions.
127
+ * You can build the expressions by using a callback and calling the methods
128
+ * on the expression builder passed to it:
129
+ *
130
+ * ```ts
131
+ * import { sql } from 'kysely'
132
+ *
133
+ * const ani = "Ani"
134
+ * const ston = "ston"
135
+ *
136
+ * const result = await db
137
+ * .insertInto('person')
138
+ * .values(({ ref, selectFrom, fn }) => ({
139
+ * first_name: 'Jennifer',
140
+ * last_name: sql`concat(${ani}, ${ston})`,
141
+ * middle_name: ref('first_name'),
142
+ * age: selectFrom('person')
143
+ * .select(fn.avg<number>('age')
144
+ * .as('avg_age')),
145
+ * }))
146
+ * .executeTakeFirst()
147
+ * ```
148
+ *
149
+ * The generated SQL (PostgreSQL):
150
+ *
151
+ * ```sql
152
+ * insert into "person" (
153
+ * "first_name",
154
+ * "last_name",
155
+ * "middle_name",
156
+ * "age"
157
+ * )
158
+ * values (
159
+ * $1,
160
+ * concat($2, $3),
161
+ * "first_name",
162
+ * (select avg("age") as "avg_age" from "person")
163
+ * )
164
+ * ```
165
+ *
166
+ * You can also use the callback version of subqueries or raw expressions:
167
+ *
168
+ * ```ts
169
+ * db.with('jennifer', (db) => db
170
+ * .selectFrom('person')
171
+ * .where('first_name', '=', 'Jennifer')
172
+ * .select(['id', 'first_name', 'gender'])
173
+ * .limit(1)
174
+ * ).insertInto('pet').values((eb) => ({
175
+ * owner_id: eb.selectFrom('jennifer').select('id'),
176
+ * name: eb.selectFrom('jennifer').select('first_name'),
177
+ * species: 'cat',
178
+ * }))
179
+ * ```
180
+ */
21
181
  values(insert) {
22
182
  const [columns, values] = parseInsertExpression(insert);
23
183
  return new InsertQueryBuilder({
@@ -8,7 +8,7 @@ import { ReferenceExpression } from '../parser/reference-parser.js';
8
8
  import { QueryNode } from '../operation-node/query-node.js';
9
9
  import { DrainOuterGeneric, NarrowPartial, Nullable, ShallowRecord, SimplifyResult, SimplifySingleResult, SqlBool } from '../util/type-utils.js';
10
10
  import { UpdateQueryNode } from '../operation-node/update-query-node.js';
11
- import { UpdateObject, UpdateObjectFactory, ExtractUpdateTypeFromReferenceExpression } from '../parser/update-set-parser.js';
11
+ import { UpdateObjectExpression, ExtractUpdateTypeFromReferenceExpression } from '../parser/update-set-parser.js';
12
12
  import { Compilable } from '../util/compilable.js';
13
13
  import { QueryExecutor } from '../query-executor/query-executor.js';
14
14
  import { QueryId } from '../util/query-id.js';
@@ -695,8 +695,7 @@ export declare class UpdateQueryBuilder<DB, UT extends keyof DB, TB extends keyo
695
695
  * where "id" = $5
696
696
  * ```
697
697
  */
698
- set(update: UpdateObject<DB, TB, UT>): UpdateQueryBuilder<DB, UT, TB, O>;
699
- set(update: UpdateObjectFactory<DB, TB, UT>): UpdateQueryBuilder<DB, UT, TB, O>;
698
+ set(update: UpdateObjectExpression<DB, TB, UT>): UpdateQueryBuilder<DB, UT, TB, O>;
700
699
  set<RE extends ReferenceExpression<DB, UT>>(key: RE, value: ValueExpression<DB, TB, ExtractUpdateTypeFromReferenceExpression<DB, UT, RE>>): UpdateQueryBuilder<DB, UT, TB, O>;
701
700
  /**
702
701
  * Allows you to return data from modified rows.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kysely",
3
- "version": "0.27.1",
3
+ "version": "0.27.2",
4
4
  "description": "Type safe SQL query builder",
5
5
  "repository": {
6
6
  "type": "git",