• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

typeorm / typeorm / 19549987525

20 Nov 2025 08:11PM UTC coverage: 80.769% (+4.3%) from 76.433%
19549987525

push

github

web-flow
ci: run tests on commits to master and next (#11783)

Co-authored-by: Oleg "OSA413" Sokolov <OSA413@users.noreply.github.com>

26500 of 32174 branches covered (82.36%)

Branch coverage included in aggregate %.

91252 of 113615 relevant lines covered (80.32%)

88980.79 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.28
/src/migration/MigrationExecutor.ts
1
import { Table } from "../schema-builder/table/Table"
26✔
2
import { DataSource } from "../data-source/DataSource"
26✔
3
import { Migration } from "./Migration"
26✔
4
import { ObjectLiteral } from "../common/ObjectLiteral"
26✔
5
import { QueryRunner } from "../query-runner/QueryRunner"
26✔
6
import { MssqlParameter } from "../driver/sqlserver/MssqlParameter"
26✔
7
import { MongoQueryRunner } from "../driver/mongodb/MongoQueryRunner"
26✔
8
import { ForbiddenTransactionModeOverrideError, TypeORMError } from "../error"
26✔
9
import { InstanceChecker } from "../util/InstanceChecker"
26✔
10

26✔
11
/**
26✔
12
 * Executes migrations: runs pending and reverts previously executed migrations.
26✔
13
 */
26✔
14
export class MigrationExecutor {
26✔
15
    // -------------------------------------------------------------------------
26✔
16
    // Public Properties
26✔
17
    // -------------------------------------------------------------------------
26✔
18

26✔
19
    /**
26✔
20
     * Indicates how migrations should be run in transactions.
26✔
21
     *   all: all migrations are run in a single transaction
26✔
22
     *   none: all migrations are run without a transaction
26✔
23
     *   each: each migration is run in a separate transaction
26✔
24
     */
26✔
25
    transaction: "all" | "none" | "each" = "all"
26✔
26

26✔
27
    /**
26✔
28
     * Option to fake-run or fake-revert a migration, adding to the
26✔
29
     * executed migrations table, but not actually running it. This feature is
26✔
30
     * useful for when migrations are added after the fact or for
26✔
31
     * interoperability between applications which are desired to each keep
26✔
32
     * a consistent migration history.
26✔
33
     */
26✔
34
    fake: boolean
26✔
35

26✔
36
    // -------------------------------------------------------------------------
26✔
37
    // Private Properties
26✔
38
    // -------------------------------------------------------------------------
26✔
39

26✔
40
    private readonly migrationsDatabase?: string
26✔
41
    private readonly migrationsSchema?: string
26✔
42
    private readonly migrationsTable: string
26✔
43
    private readonly migrationsTableName: string
26✔
44

26✔
45
    // -------------------------------------------------------------------------
26✔
46
    // Constructor
26✔
47
    // -------------------------------------------------------------------------
26✔
48

26✔
49
    constructor(
26✔
50
        protected connection: DataSource,
333✔
51
        protected queryRunner?: QueryRunner,
333✔
52
    ) {
333✔
53
        const { schema } = this.connection.driver.options as any
333✔
54
        const database = this.connection.driver.database
333✔
55
        this.migrationsDatabase = database
333✔
56
        this.migrationsSchema = schema
333✔
57
        this.migrationsTableName =
333✔
58
            connection.options.migrationsTableName || "migrations"
333✔
59
        this.migrationsTable = this.connection.driver.buildTableName(
333✔
60
            this.migrationsTableName,
333✔
61
            schema,
333✔
62
            database,
333✔
63
        )
333✔
64
    }
333✔
65

26✔
66
    // -------------------------------------------------------------------------
26✔
67
    // Public Methods
26✔
68
    // -------------------------------------------------------------------------
26✔
69

26✔
70
    /**
26✔
71
     * Tries to execute a single migration given.
26✔
72
     */
26✔
73
    public async executeMigration(migration: Migration): Promise<Migration> {
26✔
74
        return this.withQueryRunner(async (queryRunner) => {
×
75
            await this.createMigrationsTableIfNotExist(queryRunner)
×
76

×
77
            // create typeorm_metadata table if it's not created yet
×
78
            const schemaBuilder = this.connection.driver.createSchemaBuilder()
×
79
            if (InstanceChecker.isRdbmsSchemaBuilder(schemaBuilder)) {
×
80
                await schemaBuilder.createMetadataTableIfNecessary(queryRunner)
×
81
            }
×
82

×
83
            await queryRunner.beforeMigration()
×
84
            await (migration.instance as any).up(queryRunner)
×
85
            await queryRunner.afterMigration()
×
86
            await this.insertExecutedMigration(queryRunner, migration)
×
87

×
88
            return migration
×
89
        })
×
90
    }
×
91

26✔
92
    /**
26✔
93
     * Returns an array of all migrations.
26✔
94
     */
26✔
95
    public async getAllMigrations(): Promise<Migration[]> {
26✔
96
        return Promise.resolve(this.getMigrations())
×
97
    }
×
98

26✔
99
    /**
26✔
100
     * Returns an array of all executed migrations.
26✔
101
     */
26✔
102
    public async getExecutedMigrations(): Promise<Migration[]> {
26✔
103
        return this.withQueryRunner(async (queryRunner) => {
×
104
            await this.createMigrationsTableIfNotExist(queryRunner)
×
105

×
106
            return await this.loadExecutedMigrations(queryRunner)
×
107
        })
×
108
    }
×
109

26✔
110
    /**
26✔
111
     * Returns an array of all pending migrations.
26✔
112
     */
26✔
113
    public async getPendingMigrations(): Promise<Migration[]> {
26✔
114
        const allMigrations = await this.getAllMigrations()
×
115
        const executedMigrations = await this.getExecutedMigrations()
×
116

×
117
        return allMigrations.filter(
×
118
            (migration) =>
×
119
                !executedMigrations.find(
×
120
                    (executedMigration) =>
×
121
                        executedMigration.name === migration.name,
×
122
                ),
×
123
        )
×
124
    }
×
125

26✔
126
    /**
26✔
127
     * Inserts an executed migration.
26✔
128
     */
26✔
129
    public insertMigration(migration: Migration): Promise<void> {
26✔
130
        return this.withQueryRunner((q) =>
×
131
            this.insertExecutedMigration(q, migration),
×
132
        )
×
133
    }
×
134

26✔
135
    /**
26✔
136
     * Deletes an executed migration.
26✔
137
     */
26✔
138
    public deleteMigration(migration: Migration): Promise<void> {
26✔
139
        return this.withQueryRunner((q) =>
×
140
            this.deleteExecutedMigration(q, migration),
×
141
        )
×
142
    }
×
143

26✔
144
    /**
26✔
145
     * Lists all migrations and whether they have been executed or not
26✔
146
     * returns true if there are unapplied migrations
26✔
147
     */
26✔
148
    async showMigrations(): Promise<boolean> {
26✔
149
        let hasUnappliedMigrations = false
54✔
150
        const queryRunner =
54✔
151
            this.queryRunner || this.connection.createQueryRunner()
54✔
152
        // create migrations table if its not created yet
54✔
153
        await this.createMigrationsTableIfNotExist(queryRunner)
54✔
154

54✔
155
        // get all migrations that are executed and saved in the database
54✔
156
        const executedMigrations = await this.loadExecutedMigrations(
54✔
157
            queryRunner,
54✔
158
        )
54✔
159

54✔
160
        // get all user's migrations in the source code
54✔
161
        const allMigrations = this.getMigrations()
54✔
162

54✔
163
        for (const migration of allMigrations) {
54✔
164
            const executedMigration = executedMigrations.find(
68✔
165
                (executedMigration) =>
68✔
166
                    executedMigration.name === migration.name,
68✔
167
            )
68✔
168

68✔
169
            if (executedMigration) {
68✔
170
                this.connection.logger.logSchemaBuild(
20✔
171
                    `[X] ${executedMigration.id} ${migration.name}`,
20✔
172
                )
20✔
173
            } else {
68✔
174
                hasUnappliedMigrations = true
48✔
175
                this.connection.logger.logSchemaBuild(`[ ] ${migration.name}`)
48✔
176
            }
48✔
177
        }
68✔
178

54✔
179
        // if query runner was created by us then release it
54✔
180
        if (!this.queryRunner) {
54✔
181
            await queryRunner.release()
54✔
182
        }
54✔
183

54✔
184
        return hasUnappliedMigrations
54✔
185
    }
54✔
186

26✔
187
    /**
26✔
188
     * Executes all pending migrations. Pending migrations are migrations that are not yet executed,
26✔
189
     * thus not saved in the database.
26✔
190
     */
26✔
191
    async executePendingMigrations(): Promise<Migration[]> {
26✔
192
        const queryRunner =
221✔
193
            this.queryRunner || this.connection.createQueryRunner()
221✔
194
        // create migrations table if it's not created yet
221✔
195
        await this.createMigrationsTableIfNotExist(queryRunner)
221✔
196

221✔
197
        // create the typeorm_metadata table if it's not created yet
221✔
198
        const schemaBuilder = this.connection.driver.createSchemaBuilder()
221✔
199
        if (InstanceChecker.isRdbmsSchemaBuilder(schemaBuilder)) {
221!
200
            await schemaBuilder.createMetadataTableIfNecessary(queryRunner)
217✔
201
        }
217✔
202

221✔
203
        // get all migrations that are executed and saved in the database
221✔
204
        const executedMigrations = await this.loadExecutedMigrations(
221✔
205
            queryRunner,
221✔
206
        )
221✔
207

221✔
208
        // get the time when last migration was executed
221✔
209
        const lastTimeExecutedMigration =
221✔
210
            this.getLatestTimestampMigration(executedMigrations)
221✔
211

221✔
212
        // get all user's migrations in the source code
221✔
213
        const allMigrations = this.getMigrations()
221✔
214

221✔
215
        // variable to store all migrations we did successfully
221✔
216
        const successMigrations: Migration[] = []
221✔
217

221✔
218
        // find all migrations that needs to be executed
221✔
219
        const pendingMigrations = allMigrations.filter((migration) => {
221✔
220
            // check if we already have executed migration
264✔
221
            const executedMigration = executedMigrations.find(
264✔
222
                (executedMigration) =>
264✔
223
                    executedMigration.name === migration.name,
264✔
224
            )
264✔
225
            if (executedMigration) return false
264✔
226

234✔
227
            // migration is new and not executed. now check if its timestamp is correct
234✔
228
            // if (lastTimeExecutedMigration && migration.timestamp < lastTimeExecutedMigration.timestamp)
234✔
229
            //     throw new TypeORMError(`New migration found: ${migration.name}, however this migration's timestamp is not valid. Migration's timestamp should not be older then migrations already executed in the database.`);
234✔
230

234✔
231
            // every check is passed means that migration was not run yet and we need to run it
234✔
232
            return true
234✔
233
        })
221✔
234

221✔
235
        // if no migrations are pending then nothing to do here
221✔
236
        if (!pendingMigrations.length) {
221!
237
            this.connection.logger.logSchemaBuild(`No migrations are pending`)
44✔
238
            // if query runner was created by us then release it
44✔
239
            if (!this.queryRunner) await queryRunner.release()
44✔
240
            return []
44✔
241
        }
44✔
242

173✔
243
        // log information about migration execution
173✔
244
        this.connection.logger.logSchemaBuild(
173✔
245
            `${executedMigrations.length} migrations are already loaded in the database.`,
173✔
246
        )
173✔
247
        this.connection.logger.logSchemaBuild(
173✔
248
            `${allMigrations.length} migrations were found in the source code.`,
173✔
249
        )
173✔
250
        if (lastTimeExecutedMigration)
173✔
251
            this.connection.logger.logSchemaBuild(
221!
252
                `${
2✔
253
                    lastTimeExecutedMigration.name
2✔
254
                } is the last executed migration. It was executed on ${new Date(
2✔
255
                    lastTimeExecutedMigration.timestamp,
2✔
256
                ).toString()}.`,
2✔
257
            )
2✔
258
        this.connection.logger.logSchemaBuild(
173✔
259
            `${pendingMigrations.length} migrations are new migrations must be executed.`,
173✔
260
        )
173✔
261

173✔
262
        if (this.transaction === "all") {
185✔
263
            // If we desire to run all migrations in a single transaction
161✔
264
            // but there is a migration that explicitly overrides the transaction mode
161✔
265
            // then we have to fail since we cannot properly resolve that intent
161✔
266
            // In theory we could support overrides that are set to `true`,
161✔
267
            // however to keep the interface more rigid, we fail those too
161✔
268
            const migrationsOverridingTransactionMode =
161✔
269
                pendingMigrations.filter(
161✔
270
                    (migration) =>
161✔
271
                        !(migration.instance?.transaction === undefined),
161✔
272
                )
161✔
273

161✔
274
            if (migrationsOverridingTransactionMode.length > 0) {
161!
275
                const error = new ForbiddenTransactionModeOverrideError(
4✔
276
                    migrationsOverridingTransactionMode,
4✔
277
                )
4✔
278
                this.connection.logger.logMigration(
4✔
279
                    `Migrations failed, error: ${error.message}`,
4✔
280
                )
4✔
281
                throw error
4✔
282
            }
4✔
283
        }
161✔
284

169✔
285
        // Set the per-migration defaults for the transaction mode
169✔
286
        // so that we have one centralized place that controls this behavior
169✔
287

169✔
288
        // When transaction mode is `each` the default is to run in a transaction
169✔
289
        // When transaction mode is `none` the default is to not run in a transaction
169✔
290
        // When transaction mode is `all` the default is to not run in a transaction
169✔
291
        // since all the migrations are already running in one single transaction
169✔
292

169✔
293
        const txModeDefault = {
169✔
294
            each: true,
169✔
295
            none: false,
169✔
296
            all: false,
169✔
297
        }[this.transaction]
169✔
298

169✔
299
        for (const migration of pendingMigrations) {
191✔
300
            if (migration.instance) {
222✔
301
                const instanceTx = migration.instance.transaction
222✔
302

222✔
303
                if (instanceTx === undefined) {
222✔
304
                    migration.transaction = txModeDefault
206✔
305
                } else {
222!
306
                    migration.transaction = instanceTx
16✔
307
                }
16✔
308
            }
222✔
309
        }
222✔
310

169✔
311
        // start transaction if its not started yet
169✔
312
        let transactionStartedByUs = false
169✔
313
        if (this.transaction === "all" && !queryRunner.isTransactionActive) {
221✔
314
            await queryRunner.beforeMigration()
157✔
315
            await queryRunner.startTransaction()
157✔
316
            transactionStartedByUs = true
157✔
317
        }
157✔
318

169✔
319
        // run all pending migrations in a sequence
169✔
320
        try {
169✔
321
            for (const migration of pendingMigrations) {
191✔
322
                if (this.fake) {
222!
323
                    // directly insert migration record into the database if it is fake
28✔
324
                    await this.insertExecutedMigration(queryRunner, migration)
28✔
325

28✔
326
                    // nothing else needs to be done, continue to next migration
28✔
327
                    continue
28✔
328
                }
28✔
329

194✔
330
                if (migration.transaction && !queryRunner.isTransactionActive) {
222!
331
                    await queryRunner.beforeMigration()
24✔
332
                    await queryRunner.startTransaction()
24✔
333
                    transactionStartedByUs = true
24✔
334
                }
24✔
335

194✔
336
                await migration
194✔
337
                    .instance!.up(queryRunner)
194✔
338
                    .catch((error) => {
194✔
339
                        // informative log about migration failure
60✔
340
                        this.connection.logger.logMigration(
60✔
341
                            `Migration "${migration.name}" failed, error: ${error?.message}`,
60✔
342
                        )
60✔
343
                        throw error
60✔
344
                    })
194✔
345
                    .then(async () => {
194✔
346
                        // now when migration is executed we need to insert record about it into the database
134✔
347
                        await this.insertExecutedMigration(
134✔
348
                            queryRunner,
134✔
349
                            migration,
134✔
350
                        )
134✔
351
                        // commit transaction if we started it
134✔
352
                        if (migration.transaction && transactionStartedByUs) {
134!
353
                            await queryRunner.commitTransaction()
24✔
354
                            await queryRunner.afterMigration()
24✔
355
                        }
24✔
356
                    })
194✔
357
                    .then(() => {
194✔
358
                        // informative log about migration success
134✔
359
                        successMigrations.push(migration)
134✔
360
                        this.connection.logger.logSchemaBuild(
134✔
361
                            `Migration ${migration.name} has been ${
134✔
362
                                this.fake ? "(fake) " : ""
134!
363
                            }executed successfully.`,
134✔
364
                        )
134✔
365
                    })
194✔
366
            }
134✔
367

109✔
368
            // commit transaction if we started it
109✔
369
            if (this.transaction === "all" && transactionStartedByUs) {
221✔
370
                await queryRunner.commitTransaction()
97✔
371
                await queryRunner.afterMigration()
97✔
372
            }
97✔
373
        } catch (err) {
221!
374
            // rollback transaction if we started it
60✔
375
            if (transactionStartedByUs) {
60✔
376
                try {
60✔
377
                    // we throw original error even if rollback thrown an error
60✔
378
                    await queryRunner.rollbackTransaction()
60✔
379
                } catch (rollbackError) {}
60!
380
            }
60✔
381

60✔
382
            throw err
60✔
383
        } finally {
213✔
384
            // if query runner was created by us then release it
169✔
385
            if (!this.queryRunner) await queryRunner.release()
169✔
386
        }
169✔
387
        return successMigrations
109✔
388
    }
109✔
389

26✔
390
    /**
26✔
391
     * Reverts last migration that were run.
26✔
392
     */
26✔
393
    async undoLastMigration(): Promise<void> {
26✔
394
        const queryRunner =
58✔
395
            this.queryRunner || this.connection.createQueryRunner()
58✔
396

58✔
397
        // create migrations table if it's not created yet
58✔
398
        await this.createMigrationsTableIfNotExist(queryRunner)
58✔
399

58✔
400
        // create typeorm_metadata table if it's not created yet
58✔
401
        const schemaBuilder = this.connection.driver.createSchemaBuilder()
58✔
402
        if (InstanceChecker.isRdbmsSchemaBuilder(schemaBuilder)) {
58!
403
            await schemaBuilder.createMetadataTableIfNecessary(queryRunner)
56✔
404
        }
56✔
405

58✔
406
        // get all migrations that are executed and saved in the database
58✔
407
        const executedMigrations = await this.loadExecutedMigrations(
58✔
408
            queryRunner,
58✔
409
        )
58✔
410

58✔
411
        // get the time when last migration was executed
58✔
412
        const lastTimeExecutedMigration =
58✔
413
            this.getLatestExecutedMigration(executedMigrations)
58✔
414

58✔
415
        // if no migrations found in the database then nothing to revert
58✔
416
        if (!lastTimeExecutedMigration) {
58!
417
            this.connection.logger.logSchemaBuild(
×
418
                `No migrations were found in the database. Nothing to revert!`,
×
419
            )
×
420
            return
×
421
        }
×
422

58✔
423
        // get all user's migrations in the source code
58✔
424
        const allMigrations = this.getMigrations()
58✔
425

58✔
426
        // find the instance of the migration we need to remove
58✔
427
        const migrationToRevert = allMigrations.find(
58✔
428
            (migration) => migration.name === lastTimeExecutedMigration!.name,
58✔
429
        )
58✔
430

58✔
431
        // if no migrations found in the database then nothing to revert
58✔
432
        if (!migrationToRevert)
58✔
433
            throw new TypeORMError(
58!
434
                `No migration ${lastTimeExecutedMigration.name} was found in the source code. Make sure you have this migration in your codebase and its included in the connection options.`,
×
435
            )
×
436

58✔
437
        // log information about migration execution
58✔
438
        this.connection.logger.logSchemaBuild(
58✔
439
            `${executedMigrations.length} migrations are already loaded in the database.`,
58✔
440
        )
58✔
441
        this.connection.logger.logSchemaBuild(
58✔
442
            `${
58✔
443
                lastTimeExecutedMigration.name
58✔
444
            } is the last executed migration. It was executed on ${new Date(
58✔
445
                lastTimeExecutedMigration.timestamp,
58✔
446
            ).toString()}.`,
58✔
447
        )
58✔
448
        this.connection.logger.logSchemaBuild(`Now reverting it...`)
58✔
449

58✔
450
        // start transaction if its not started yet
58✔
451
        let transactionStartedByUs = false
58✔
452
        if (this.transaction !== "none" && !queryRunner.isTransactionActive) {
58✔
453
            await queryRunner.startTransaction()
58✔
454
            transactionStartedByUs = true
58✔
455
        }
58✔
456

58✔
457
        try {
58✔
458
            if (!this.fake) {
58✔
459
                await queryRunner.beforeMigration()
30✔
460
                await migrationToRevert.instance!.down(queryRunner)
30✔
461
                await queryRunner.afterMigration()
2!
462
            }
2✔
463

30✔
464
            await this.deleteExecutedMigration(queryRunner, migrationToRevert)
30✔
465
            this.connection.logger.logSchemaBuild(
30✔
466
                `Migration ${migrationToRevert.name} has been ${
30✔
467
                    this.fake ? "(fake) " : ""
58!
468
                }reverted successfully.`,
58✔
469
            )
58✔
470

58✔
471
            // commit transaction if we started it
58✔
472
            if (transactionStartedByUs) await queryRunner.commitTransaction()
58✔
473
        } catch (err) {
58!
474
            // rollback transaction if we started it
28✔
475
            if (transactionStartedByUs) {
28✔
476
                try {
28✔
477
                    // we throw original error even if rollback thrown an error
28✔
478
                    await queryRunner.rollbackTransaction()
28✔
479
                } catch (rollbackError) {}
28!
480
            }
28✔
481

28✔
482
            throw err
28✔
483
        } finally {
30✔
484
            // if query runner was created by us then release it
58✔
485
            if (!this.queryRunner) await queryRunner.release()
58✔
486
        }
58✔
487
    }
58✔
488

26✔
489
    // -------------------------------------------------------------------------
26✔
490
    // Protected Methods
26✔
491
    // -------------------------------------------------------------------------
26✔
492

26✔
493
    /**
26✔
494
     * Creates table "migrations" that will store information about executed migrations.
26✔
495
     */
26✔
496
    protected async createMigrationsTableIfNotExist(
26✔
497
        queryRunner: QueryRunner,
333✔
498
    ): Promise<void> {
333✔
499
        // If driver is mongo no need to create
333✔
500
        if (this.connection.driver.options.type === "mongodb") {
333!
501
            return
6✔
502
        }
6✔
503
        const tableExist = await queryRunner.hasTable(this.migrationsTable) // todo: table name should be configurable
327!
504
        if (!tableExist) {
327✔
505
            await queryRunner.createTable(
168✔
506
                new Table({
168✔
507
                    database: this.migrationsDatabase,
168✔
508
                    schema: this.migrationsSchema,
168✔
509
                    name: this.migrationsTable,
168✔
510
                    columns: [
168✔
511
                        {
168✔
512
                            name: "id",
168✔
513
                            type: this.connection.driver.normalizeType({
168✔
514
                                type: this.connection.driver.mappedDataTypes
168✔
515
                                    .migrationId,
168✔
516
                            }),
168✔
517
                            isGenerated: true,
168✔
518
                            generationStrategy: "increment",
168✔
519
                            isPrimary: true,
168✔
520
                            isNullable: false,
168✔
521
                        },
168✔
522
                        {
168✔
523
                            name: "timestamp",
168✔
524
                            type: this.connection.driver.normalizeType({
168✔
525
                                type: this.connection.driver.mappedDataTypes
168✔
526
                                    .migrationTimestamp,
168✔
527
                            }),
168✔
528
                            isPrimary: false,
168✔
529
                            isNullable: false,
168✔
530
                        },
168✔
531
                        {
168✔
532
                            name: "name",
168✔
533
                            type: this.connection.driver.normalizeType({
168✔
534
                                type: this.connection.driver.mappedDataTypes
168✔
535
                                    .migrationName,
168✔
536
                            }),
168✔
537
                            isNullable: false,
168✔
538
                        },
168✔
539
                    ],
168✔
540
                }),
168✔
541
            )
168✔
542
        }
168✔
543
    }
333✔
544

26✔
545
    /**
26✔
546
     * Loads all migrations that were executed and saved into the database (sorts by id).
26✔
547
     */
26✔
548
    protected async loadExecutedMigrations(
26✔
549
        queryRunner: QueryRunner,
333✔
550
    ): Promise<Migration[]> {
333✔
551
        if (this.connection.driver.options.type === "mongodb") {
333!
552
            const mongoRunner = queryRunner as MongoQueryRunner
6✔
553
            return mongoRunner
6✔
554
                .cursor(this.migrationsTableName, {})
6✔
555
                .sort({ _id: -1 })
6✔
556
                .toArray()
6✔
557
        } else {
333!
558
            const migrationsRaw: ObjectLiteral[] = await this.connection.manager
327✔
559
                .createQueryBuilder(queryRunner)
327✔
560
                .select()
327✔
561
                .orderBy(this.connection.driver.escape("id"), "DESC")
327✔
562
                .from(this.migrationsTable, this.migrationsTableName)
327✔
563
                .getRawMany()
327✔
564
            return migrationsRaw.map((migrationRaw) => {
327✔
565
                return new Migration(
104✔
566
                    parseInt(migrationRaw["id"]),
104✔
567
                    parseInt(migrationRaw["timestamp"]),
104✔
568
                    migrationRaw["name"],
104✔
569
                )
104✔
570
            })
327✔
571
        }
327✔
572
    }
333✔
573

26✔
574
    /**
26✔
575
     * Gets all migrations that setup for this connection.
26✔
576
     */
26✔
577
    protected getMigrations(): Migration[] {
26✔
578
        const migrations = this.connection.migrations.map((migration) => {
333✔
579
            const migrationClassName =
404✔
580
                migration.name || (migration.constructor as any).name
404✔
581
            const migrationTimestamp = parseInt(
404✔
582
                migrationClassName.substr(-13),
404✔
583
                10,
404✔
584
            )
404✔
585
            if (!migrationTimestamp || isNaN(migrationTimestamp)) {
404!
586
                throw new TypeORMError(
×
587
                    `${migrationClassName} migration name is wrong. Migration class name should have a JavaScript timestamp appended.`,
×
588
                )
×
589
            }
×
590

404✔
591
            return new Migration(
404✔
592
                undefined,
404✔
593
                migrationTimestamp,
404✔
594
                migrationClassName,
404✔
595
                migration,
404✔
596
            )
404✔
597
        })
333✔
598

333✔
599
        this.checkForDuplicateMigrations(migrations)
333✔
600

333✔
601
        // sort them by timestamp
333✔
602
        return migrations.sort((a, b) => a.timestamp - b.timestamp)
333✔
603
    }
333✔
604

26✔
605
    protected checkForDuplicateMigrations(migrations: Migration[]) {
26✔
606
        const migrationNames = migrations.map((migration) => migration.name)
333✔
607
        const duplicates = Array.from(
333✔
608
            new Set(
333✔
609
                migrationNames.filter(
333✔
610
                    (migrationName, index) =>
333✔
611
                        migrationNames.indexOf(migrationName) < index,
333✔
612
                ),
333✔
613
            ),
333✔
614
        )
333✔
615
        if (duplicates.length > 0) {
333!
616
            throw Error(`Duplicate migrations: ${duplicates.join(", ")}`)
4✔
617
        }
4✔
618
    }
333✔
619

26✔
620
    /**
26✔
621
     * Finds the latest migration (sorts by timestamp) in the given array of migrations.
26✔
622
     */
26✔
623
    protected getLatestTimestampMigration(
26✔
624
        migrations: Migration[],
221✔
625
    ): Migration | undefined {
221✔
626
        const sortedMigrations = migrations
221✔
627
            .map((migration) => migration)
221✔
628
            .sort((a, b) => (a.timestamp - b.timestamp) * -1)
221✔
629
        return sortedMigrations.length > 0 ? sortedMigrations[0] : undefined
221✔
630
    }
221✔
631

26✔
632
    /**
26✔
633
     * Finds the latest migration in the given array of migrations.
26✔
634
     * PRE: Migration array must be sorted by descending id.
26✔
635
     */
26✔
636
    protected getLatestExecutedMigration(
26✔
637
        sortedMigrations: Migration[],
58✔
638
    ): Migration | undefined {
58✔
639
        return sortedMigrations.length > 0 ? sortedMigrations[0] : undefined
58!
640
    }
58✔
641

26✔
642
    /**
26✔
643
     * Inserts new executed migration's data into migrations table.
26✔
644
     */
26✔
645
    protected async insertExecutedMigration(
26✔
646
        queryRunner: QueryRunner,
162✔
647
        migration: Migration,
162✔
648
    ): Promise<void> {
162✔
649
        const values: ObjectLiteral = {}
162✔
650
        if (this.connection.driver.options.type === "mssql") {
162!
651
            values["timestamp"] = new MssqlParameter(
4✔
652
                migration.timestamp,
4✔
653
                this.connection.driver.normalizeType({
4✔
654
                    type: this.connection.driver.mappedDataTypes
4✔
655
                        .migrationTimestamp,
4✔
656
                }) as any,
4✔
657
            )
4✔
658
            values["name"] = new MssqlParameter(
4✔
659
                migration.name,
4✔
660
                this.connection.driver.normalizeType({
4✔
661
                    type: this.connection.driver.mappedDataTypes.migrationName,
4✔
662
                }) as any,
4✔
663
            )
4✔
664
        } else {
162!
665
            values["timestamp"] = migration.timestamp
158✔
666
            values["name"] = migration.name
158✔
667
        }
158✔
668
        if (this.connection.driver.options.type === "mongodb") {
162!
669
            const mongoRunner = queryRunner as MongoQueryRunner
6✔
670
            await mongoRunner.databaseConnection
6✔
671
                .db(this.connection.driver.database!)
6✔
672
                .collection(this.migrationsTableName)
6✔
673
                .insertOne(values)
6✔
674
        } else {
162!
675
            const qb = queryRunner.manager.createQueryBuilder()
156✔
676
            await qb
156✔
677
                .insert()
156✔
678
                .into(this.migrationsTable)
156✔
679
                .values(values)
156✔
680
                .execute()
156✔
681
        }
156✔
682
    }
162✔
683

26✔
684
    /**
26✔
685
     * Delete previously executed migration's data from the migrations table.
26✔
686
     */
26✔
687
    protected async deleteExecutedMigration(
26✔
688
        queryRunner: QueryRunner,
30✔
689
        migration: Migration,
30✔
690
    ): Promise<void> {
30✔
691
        const conditions: ObjectLiteral = {}
30✔
692
        if (this.connection.driver.options.type === "mssql") {
30!
693
            conditions["timestamp"] = new MssqlParameter(
2✔
694
                migration.timestamp,
2✔
695
                this.connection.driver.normalizeType({
2✔
696
                    type: this.connection.driver.mappedDataTypes
2✔
697
                        .migrationTimestamp,
2✔
698
                }) as any,
2✔
699
            )
2✔
700
            conditions["name"] = new MssqlParameter(
2✔
701
                migration.name,
2✔
702
                this.connection.driver.normalizeType({
2✔
703
                    type: this.connection.driver.mappedDataTypes.migrationName,
2✔
704
                }) as any,
2✔
705
            )
2✔
706
        } else {
30!
707
            conditions["timestamp"] = migration.timestamp
28✔
708
            conditions["name"] = migration.name
28✔
709
        }
28✔
710

30✔
711
        if (this.connection.driver.options.type === "mongodb") {
30!
712
            const mongoRunner = queryRunner as MongoQueryRunner
2✔
713
            await mongoRunner.databaseConnection
2✔
714
                .db(this.connection.driver.database!)
2✔
715
                .collection(this.migrationsTableName)
2✔
716
                .deleteOne(conditions)
2✔
717
        } else {
30!
718
            const qb = queryRunner.manager.createQueryBuilder()
28✔
719
            await qb
28✔
720
                .delete()
28✔
721
                .from(this.migrationsTable)
28✔
722
                .where(`${qb.escape("timestamp")} = :timestamp`)
28✔
723
                .andWhere(`${qb.escape("name")} = :name`)
28✔
724
                .setParameters(conditions)
28✔
725
                .execute()
28✔
726
        }
28✔
727
    }
30✔
728

26✔
729
    protected async withQueryRunner<T extends any>(
26✔
730
        callback: (queryRunner: QueryRunner) => T | Promise<T>,
×
731
    ) {
×
732
        const queryRunner =
×
733
            this.queryRunner || this.connection.createQueryRunner()
×
734

×
735
        try {
×
736
            return await callback(queryRunner)
×
737
        } finally {
×
738
            if (!this.queryRunner) {
×
739
                await queryRunner.release()
×
740
            }
×
741
        }
×
742
    }
×
743
}
26✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc