@mikro-orm/migrations 7.1.0-dev.3 → 7.1.0-dev.30

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.
@@ -8,8 +8,11 @@ export declare class MigrationRunner {
8
8
  protected readonly options: MigrationsOptions;
9
9
  protected readonly config: Configuration;
10
10
  constructor(driver: AbstractSqlDriver, options: MigrationsOptions, config: Configuration);
11
- run(migration: Migration, method: 'up' | 'down'): Promise<void>;
11
+ run(migration: Migration, method: 'up' | 'down', afterRun?: (tx?: Transaction) => Promise<void>): Promise<void>;
12
+ private resetSessionSchema;
12
13
  setMasterMigration(trx: Transaction): void;
13
14
  unsetMasterMigration(): void;
15
+ setRunSchema(schema?: string): void;
16
+ unsetRunSchema(): void;
14
17
  private getQueries;
15
18
  }
@@ -7,6 +7,7 @@ export class MigrationRunner {
7
7
  #connection;
8
8
  #helper;
9
9
  #masterTransaction;
10
+ #runSchema;
10
11
  constructor(driver, options, config) {
11
12
  this.driver = driver;
12
13
  this.options = options;
@@ -14,31 +15,64 @@ export class MigrationRunner {
14
15
  this.#connection = this.driver.getConnection();
15
16
  this.#helper = this.driver.getPlatform().getSchemaHelper();
16
17
  }
17
- async run(migration, method) {
18
+ async run(migration, method, afterRun) {
18
19
  migration.reset();
19
20
  if (!this.options.transactional || !migration.isTransactional()) {
21
+ // Without a pinned transaction the set/reset statements may land on different pooled
22
+ // connections than the DDL, so refuse rather than silently running in the default schema.
23
+ if (this.#runSchema) {
24
+ throw new Error('Runtime schema (migrations.schema / migrator.up({ schema })) is only supported with transactional migrations');
25
+ }
20
26
  const queries = await this.getQueries(migration, method);
21
27
  await Utils.runSerial(queries, sql => this.driver.execute(sql));
28
+ await afterRun?.();
22
29
  }
23
30
  else {
24
31
  await this.#connection.transactional(async (tx) => {
25
32
  migration.setTransactionContext(tx);
26
- const queries = await this.getQueries(migration, method);
27
- await Utils.runSerial(queries, sql => this.driver.execute(sql, undefined, 'all', tx));
33
+ try {
34
+ const queries = await this.getQueries(migration, method);
35
+ await Utils.runSerial(queries, sql => this.driver.execute(sql, undefined, 'all', tx));
36
+ await afterRun?.(tx);
37
+ }
38
+ finally {
39
+ await this.resetSessionSchema(tx);
40
+ }
28
41
  }, { ctx: this.#masterTransaction });
29
42
  }
30
43
  }
44
+ async resetSessionSchema(ctx) {
45
+ if (!this.#runSchema) {
46
+ return;
47
+ }
48
+ const sql = this.#helper.getResetSchemaSQL(this.config.get('dbName'));
49
+ /* v8 ignore next 3 */
50
+ if (!sql) {
51
+ return;
52
+ }
53
+ // best-effort — surfacing a reset failure would mask the real migration error
54
+ await this.driver.execute(sql, undefined, 'all', ctx).catch(() => void 0);
55
+ }
31
56
  setMasterMigration(trx) {
32
57
  this.#masterTransaction = trx;
33
58
  }
34
59
  unsetMasterMigration() {
35
60
  this.#masterTransaction = undefined;
36
61
  }
62
+ setRunSchema(schema) {
63
+ this.#runSchema = this.#helper.resolveMigrationSchema(schema);
64
+ }
65
+ unsetRunSchema() {
66
+ this.#runSchema = undefined;
67
+ }
37
68
  async getQueries(migration, method) {
38
69
  await migration[method]();
39
70
  const charset = this.config.get('charset');
40
71
  let queries = migration.getQueries();
41
72
  queries.unshift(...this.#helper.getSchemaBeginning(charset, this.options.disableForeignKeys).split('\n'));
73
+ if (this.#runSchema) {
74
+ queries.unshift(this.#helper.getSetSchemaSQL(this.#runSchema));
75
+ }
42
76
  queries.push(...this.#helper.getSchemaEnd(this.options.disableForeignKeys).split('\n'));
43
77
  queries = queries.filter(sql => typeof sql !== 'string' || sql.trim().length > 0);
44
78
  return queries;
@@ -10,14 +10,16 @@ export declare class MigrationStorage {
10
10
  executed(): Promise<string[]>;
11
11
  logMigration(params: {
12
12
  name: string;
13
- }): Promise<void>;
13
+ }, tx?: Transaction): Promise<void>;
14
14
  unlogMigration(params: {
15
15
  name: string;
16
- }): Promise<void>;
16
+ }, tx?: Transaction): Promise<void>;
17
17
  getExecutedMigrations(): Promise<MigrationRow[]>;
18
18
  ensureTable(): Promise<void>;
19
19
  setMasterMigration(trx: Transaction): void;
20
20
  unsetMasterMigration(): void;
21
+ setRunSchema(schema?: string): void;
22
+ unsetRunSchema(): void;
21
23
  /**
22
24
  * @internal
23
25
  */
@@ -30,4 +32,5 @@ export declare class MigrationStorage {
30
32
  schemaName: string;
31
33
  entity: EntitySchema;
32
34
  };
35
+ private resolveTableName;
33
36
  }
@@ -7,7 +7,9 @@ export class MigrationStorage {
7
7
  #connection;
8
8
  #helper;
9
9
  #masterTransaction;
10
+ #runSchema;
10
11
  #platform;
12
+ #ensuredSchemas = new Set();
11
13
  constructor(driver, options) {
12
14
  this.driver = driver;
13
15
  this.options = options;
@@ -19,18 +21,21 @@ export class MigrationStorage {
19
21
  const migrations = await this.getExecutedMigrations();
20
22
  return migrations.map(({ name }) => this.getMigrationName(name));
21
23
  }
22
- async logMigration(params) {
24
+ async logMigration(params, tx) {
25
+ await this.ensureTable();
23
26
  const { entity } = this.getTableName();
24
27
  const name = this.getMigrationName(params.name);
25
- await this.driver.nativeInsert(entity, { name }, { ctx: this.#masterTransaction });
28
+ await this.driver.nativeInsert(entity, { name }, { ctx: tx ?? this.#masterTransaction });
26
29
  }
27
- async unlogMigration(params) {
30
+ async unlogMigration(params, tx) {
31
+ await this.ensureTable();
28
32
  const { entity } = this.getTableName();
29
33
  const withoutExt = this.getMigrationName(params.name);
30
34
  const names = [withoutExt, withoutExt + '.js', withoutExt + '.ts'];
31
- await this.driver.nativeDelete(entity, { name: { $in: [params.name, ...names] } }, { ctx: this.#masterTransaction });
35
+ await this.driver.nativeDelete(entity, { name: { $in: [params.name, ...names] } }, { ctx: tx ?? this.#masterTransaction });
32
36
  }
33
37
  async getExecutedMigrations() {
38
+ await this.ensureTable();
34
39
  const { entity, schemaName } = this.getTableName();
35
40
  const res = await this.driver
36
41
  .createQueryBuilder(entity, this.#masterTransaction)
@@ -45,15 +50,20 @@ export class MigrationStorage {
45
50
  });
46
51
  }
47
52
  async ensureTable() {
48
- const tables = await this.#connection.execute(this.#helper.getListTablesSQL(), [], 'all', this.#masterTransaction);
49
- const { tableName, schemaName } = this.getTableName();
50
- if (tables.find(t => t.table_name === tableName && (!t.schema_name || t.schema_name === schemaName))) {
53
+ const { tableName, schemaName } = this.resolveTableName();
54
+ // `\x00` can't appear in SQL identifiers — unambiguous pair encoding
55
+ const cacheKey = `${schemaName ?? ''}\x00${tableName}`;
56
+ if (this.#ensuredSchemas.has(cacheKey)) {
51
57
  return;
52
58
  }
53
- const schemas = await this.#helper.getNamespaces(this.#connection);
59
+ if (await this.#helper.tableExists(this.#connection, tableName, schemaName, this.#masterTransaction)) {
60
+ this.#ensuredSchemas.add(cacheKey);
61
+ return;
62
+ }
63
+ const schemas = await this.#helper.getNamespaces(this.#connection, this.#masterTransaction);
54
64
  if (schemaName && !schemas.includes(schemaName)) {
55
65
  const sql = this.#helper.getCreateNamespaceSQL(schemaName);
56
- await this.#connection.execute(sql);
66
+ await this.#connection.execute(sql, [], 'run', this.#masterTransaction);
57
67
  }
58
68
  const table = new DatabaseTable(this.#platform, tableName, schemaName);
59
69
  table.addColumn({
@@ -78,6 +88,7 @@ export class MigrationStorage {
78
88
  });
79
89
  const sql = this.#helper.createTable(table);
80
90
  await this.#connection.execute(sql.join(';\n'), [], 'run', this.#masterTransaction);
91
+ this.#ensuredSchemas.add(cacheKey);
81
92
  }
82
93
  setMasterMigration(trx) {
83
94
  this.#masterTransaction = trx;
@@ -85,6 +96,12 @@ export class MigrationStorage {
85
96
  unsetMasterMigration() {
86
97
  this.#masterTransaction = undefined;
87
98
  }
99
+ setRunSchema(schema) {
100
+ this.#runSchema = this.#helper.resolveMigrationSchema(schema);
101
+ }
102
+ unsetRunSchema() {
103
+ this.#runSchema = undefined;
104
+ }
88
105
  /**
89
106
  * @internal
90
107
  */
@@ -95,9 +112,7 @@ export class MigrationStorage {
95
112
  * @internal
96
113
  */
97
114
  getTableName() {
98
- const parts = this.options.tableName.split('.');
99
- const tableName = parts.length > 1 ? parts[1] : parts[0];
100
- const schemaName = parts.length > 1 ? parts[0] : this.driver.config.get('schema', this.driver.getPlatform().getDefaultSchemaName());
115
+ const { tableName, schemaName } = this.resolveTableName();
101
116
  const entity = defineEntity({
102
117
  name: 'Migration',
103
118
  tableName,
@@ -111,4 +126,14 @@ export class MigrationStorage {
111
126
  entity.meta.sync();
112
127
  return { tableName, schemaName, entity };
113
128
  }
129
+ resolveTableName() {
130
+ const parts = this.options.tableName.split('.');
131
+ const tableName = parts.length > 1 ? parts[1] : parts[0];
132
+ const schemaName = this.#runSchema ??
133
+ this.options.schema ??
134
+ (parts.length > 1
135
+ ? parts[0]
136
+ : this.driver.config.get('schema', this.driver.getPlatform().getDefaultSchemaName()));
137
+ return { tableName, schemaName };
138
+ }
114
139
  }
package/Migrator.d.ts CHANGED
@@ -17,7 +17,9 @@ export declare class Migrator extends AbstractMigrator<AbstractSqlDriver> {
17
17
  * @inheritDoc
18
18
  */
19
19
  create(path?: string, blank?: boolean, initial?: boolean, name?: string): Promise<MigrationResult>;
20
- getPending(): Promise<MigrationInfo[]>;
20
+ getPending(options?: {
21
+ schema?: string;
22
+ }): Promise<MigrationInfo[]>;
21
23
  private hasSnapshot;
22
24
  checkSchema(): Promise<boolean>;
23
25
  /**
package/Migrator.js CHANGED
@@ -51,7 +51,14 @@ export class Migrator extends AbstractMigrator {
51
51
  if (created) {
52
52
  this.initServices();
53
53
  }
54
- await this.storage.ensureTable();
54
+ // Defer tracking-table creation when wildcard-schema fan-out is enabled — pre-creating
55
+ // here would land in the default schema and leave a stray `mikro_orm_migrations` after
56
+ // `migrator.up({ schema })` against a virgin DB. For the standard flow, eager creation
57
+ // keeps the per-call query log clean (no `tableExists`/`getNamespaces`/`create` probe
58
+ // on first storage access).
59
+ if (!this.options.includeWildcardSchema) {
60
+ await this.storage.ensureTable();
61
+ }
55
62
  }
56
63
  /**
57
64
  * @inheritDoc
@@ -74,23 +81,30 @@ export class Migrator extends AbstractMigrator {
74
81
  diff,
75
82
  };
76
83
  }
77
- async getPending() {
84
+ async getPending(options) {
78
85
  if (!(await this.hasSnapshot())) {
79
- return super.getPending();
86
+ return super.getPending(options);
80
87
  }
81
88
  await this.initPaths();
82
89
  const all = await this.discoverMigrations();
83
- // probe the DB via `ensureTable` — if it fails, the DB is unreachable and
84
- // we treat every discovered migration as pending; otherwise let errors
85
- // from `storage.executed()` propagate so real bugs are not swallowed
90
+ const schema = options?.schema ?? this.options.schema;
91
+ this.storage.setRunSchema?.(schema);
86
92
  try {
87
- await this.storage.ensureTable();
93
+ // probe the DB via `ensureTable` — if it fails, the DB is unreachable and
94
+ // we treat every discovered migration as pending; otherwise let errors
95
+ // from `storage.executed()` propagate so real bugs are not swallowed
96
+ try {
97
+ await this.storage.ensureTable();
98
+ }
99
+ catch {
100
+ return all.map(m => ({ name: m.name, path: m.path }));
101
+ }
102
+ const executed = new Set(await this.storage.executed());
103
+ return all.filter(m => !executed.has(m.name)).map(m => ({ name: m.name, path: m.path }));
88
104
  }
89
- catch {
90
- return all.map(m => ({ name: m.name, path: m.path }));
105
+ finally {
106
+ this.storage.unsetRunSchema?.();
91
107
  }
92
- const executed = new Set(await this.storage.executed());
93
- return all.filter(m => !executed.has(m.name)).map(m => ({ name: m.name, path: m.path }));
94
108
  }
95
109
  async hasSnapshot() {
96
110
  if (!this.options.snapshot) {
@@ -197,9 +211,12 @@ export class Migrator extends AbstractMigrator {
197
211
  const data = fs.readJSONSync(snapshotPath);
198
212
  const schema = new DatabaseSchema(this.driver.getPlatform(), this.config.get('schema'));
199
213
  const { tables, namespaces, ...rest } = data;
214
+ // share schema-level native enums by reference; fall back to per-table copy on older snapshots
215
+ const sharedNativeEnums = rest.nativeEnums ?? {};
216
+ const hasSharedNativeEnums = Object.keys(sharedNativeEnums).length > 0;
200
217
  const tableInstances = tables.map((tbl) => {
201
218
  const table = new DatabaseTable(this.driver.getPlatform(), tbl.name, tbl.schema);
202
- table.nativeEnums = tbl.nativeEnums ?? {};
219
+ table.nativeEnums = hasSharedNativeEnums ? sharedNativeEnums : (tbl.nativeEnums ?? {});
203
220
  table.comment = tbl.comment;
204
221
  if (tbl.indexes) {
205
222
  table.setIndexes(tbl.indexes);
@@ -271,7 +288,10 @@ export class Migrator extends AbstractMigrator {
271
288
  down.push('select 1');
272
289
  }
273
290
  else if (initial) {
274
- const dump = await this.#schemaGenerator.getCreateSchemaSQL({ wrap: false });
291
+ const dump = await this.#schemaGenerator.getCreateSchemaSQL({
292
+ wrap: false,
293
+ includeWildcardSchema: this.options.includeWildcardSchema,
294
+ });
275
295
  up.push(...splitStatements(dump));
276
296
  }
277
297
  else {
@@ -280,6 +300,7 @@ export class Migrator extends AbstractMigrator {
280
300
  safe: this.options.safe,
281
301
  dropTables: this.options.dropTables,
282
302
  fromSchema: await this.getSchemaFromSnapshot(),
303
+ includeWildcardSchema: this.options.includeWildcardSchema,
283
304
  });
284
305
  up.push(...splitStatements(diff.up));
285
306
  down.push(...splitStatements(diff.down));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mikro-orm/migrations",
3
- "version": "7.1.0-dev.3",
3
+ "version": "7.1.0-dev.30",
4
4
  "description": "TypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, PostgreSQL and SQLite databases as well as usage with vanilla JavaScript.",
5
5
  "keywords": [
6
6
  "data-mapper",
@@ -47,13 +47,13 @@
47
47
  "copy": "node ../../scripts/copy.mjs"
48
48
  },
49
49
  "dependencies": {
50
- "@mikro-orm/sql": "7.1.0-dev.3"
50
+ "@mikro-orm/sql": "7.1.0-dev.30"
51
51
  },
52
52
  "devDependencies": {
53
- "@mikro-orm/core": "^7.0.11"
53
+ "@mikro-orm/core": "^7.0.15"
54
54
  },
55
55
  "peerDependencies": {
56
- "@mikro-orm/core": "7.1.0-dev.3"
56
+ "@mikro-orm/core": "7.1.0-dev.30"
57
57
  },
58
58
  "engines": {
59
59
  "node": ">= 22.17.0"