-
-
Notifications
You must be signed in to change notification settings - Fork 495
/
Migrator.ts
154 lines (122 loc) · 5.04 KB
/
Migrator.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
// @ts-ignore
import umzug, { Umzug, migrationsList } from 'umzug';
import { Utils, Constructor, MigrationObject } from '@mikro-orm/core';
import { SchemaGenerator, EntityManager } from '@mikro-orm/knex';
import { Migration } from './Migration';
import { MigrationRunner } from './MigrationRunner';
import { MigrationGenerator } from './MigrationGenerator';
import { MigrationStorage } from './MigrationStorage';
export class Migrator {
private readonly umzug: Umzug;
private readonly driver = this.em.getDriver();
private readonly schemaGenerator = new SchemaGenerator(this.em);
private readonly config = this.em.config;
private readonly options = this.config.get('migrations');
private readonly runner = new MigrationRunner(this.driver, this.options, this.config);
private readonly generator = new MigrationGenerator(this.driver, this.config.getNamingStrategy(), this.options);
private readonly storage = new MigrationStorage(this.driver, this.options);
constructor(private readonly em: EntityManager) {
let migrations = {
path: Utils.absolutePath(this.options.path!, this.config.get('baseDir')),
pattern: this.options.pattern,
customResolver: (file: string) => this.resolve(file),
};
if (this.options.migrationsList?.length) {
migrations = migrationsList(
this.options.migrationsList.map((migration: MigrationObject) =>
this.initialize(
migration.class as unknown as Constructor<Migration>,
migration.name
)
)
);
}
this.umzug = new umzug({
storage: this.storage,
logging: this.config.get('logger'),
migrations,
});
}
async createMigration(path?: string, blank = false): Promise<MigrationResult> {
const diff = blank ? ['select 1'] : await this.getSchemaDiff();
if (diff.length === 0) {
return { fileName: '', code: '', diff };
}
const migration = await this.generator.generate(diff, path);
return {
fileName: migration[1],
code: migration[0],
diff,
};
}
async getExecutedMigrations(): Promise<MigrationRow[]> {
await this.storage.ensureTable();
return this.storage.getExecutedMigrations();
}
async getPendingMigrations(): Promise<UmzugMigration[]> {
await this.storage.ensureTable();
return this.umzug.pending();
}
async up(options?: string | string[] | MigrateOptions): Promise<UmzugMigration[]> {
return this.runMigrations('up', options);
}
async down(options?: string | string[] | MigrateOptions): Promise<UmzugMigration[]> {
return this.runMigrations('down', options);
}
protected resolve(file: string) {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const migration = require(file);
const MigrationClass = Object.values(migration)[0] as Constructor<Migration>;
return this.initialize(MigrationClass);
}
protected initialize(MigrationClass: Constructor<Migration>, name?: string) {
const instance = new MigrationClass(this.driver.getConnection(), this.config);
return {
name,
up: () => this.runner.run(instance, 'up'),
down: () => this.runner.run(instance, 'down'),
};
}
private async getSchemaDiff(): Promise<string[]> {
const dump = await this.schemaGenerator.getUpdateSchemaSQL(false, this.options.safe, this.options.dropTables);
const lines = dump.split('\n');
for (let i = lines.length - 1; i > 0; i--) {
if (lines[i]) {
break;
}
delete lines[i];
}
return lines;
}
private prefix<T extends string | string[] | { from?: string; to?: string; migrations?: string[] }>(options?: T): T {
if (Utils.isString(options) || Array.isArray(options)) {
return Utils.asArray(options).map(m => m.startsWith('Migration') ? m : 'Migration' + m) as T;
}
if (!Utils.isObject<{ from?: string; to?: string; migrations?: string[] }>(options)) {
return options as T;
}
if (options.migrations) {
options.migrations = options.migrations.map(m => this.prefix(m));
}
['from', 'to'].filter(k => options[k]).forEach(k => options[k] = this.prefix(options[k]));
return options as T;
}
private async runMigrations(method: 'up' | 'down', options?: string | string[] | MigrateOptions) {
await this.storage.ensureTable();
if (!this.options.transactional || !this.options.allOrNothing) {
return this.umzug[method](this.prefix(options as string[]));
}
return this.driver.getConnection().transactional(async trx => {
this.runner.setMasterMigration(trx);
this.storage.setMasterMigration(trx);
const ret = await this.umzug[method](this.prefix(options as string[]));
this.runner.unsetMasterMigration();
this.storage.unsetMasterMigration();
return ret;
});
}
}
export type UmzugMigration = { path?: string; file: string };
export type MigrateOptions = { from?: string | number; to?: string | number; migrations?: string[] };
export type MigrationResult = { fileName: string; code: string; diff: string[] };
export type MigrationRow = { name: string; executed_at: Date };