@mikro-orm/migrations
Advanced tools
@@ -1,3 +0,1 @@ | ||
| import { fs } from '@mikro-orm/core/fs-utils'; | ||
| import { writeFile } from 'node:fs/promises'; | ||
| export class MigrationGenerator { | ||
@@ -16,2 +14,3 @@ driver; | ||
| async generate(diff, path, name) { | ||
| const { fs } = await import('@mikro-orm/core/fs-utils'); | ||
| /* v8 ignore next */ | ||
@@ -25,3 +24,3 @@ const defaultPath = this.options.emit === 'ts' && this.options.pathTs ? this.options.pathTs : this.options.path; | ||
| const ret = await this.generateMigrationFile(className, diff); | ||
| await writeFile(path + '/' + fileName, ret, { flush: true }); | ||
| await fs.writeFile(path + '/' + fileName, ret, { flush: true }); | ||
| return [ret, fileName]; | ||
@@ -28,0 +27,0 @@ } |
| import { type MigrationsOptions, type Transaction, type EntitySchema } from '@mikro-orm/core'; | ||
| import { type AbstractSqlDriver } from '@mikro-orm/sql'; | ||
| import type { MigrationParams, UmzugStorage } from 'umzug'; | ||
| import type { MigrationRow } from './typings.js'; | ||
| export declare class MigrationStorage implements UmzugStorage { | ||
| export declare class MigrationStorage { | ||
| protected readonly driver: AbstractSqlDriver; | ||
@@ -14,4 +13,8 @@ protected readonly options: MigrationsOptions; | ||
| executed(): Promise<string[]>; | ||
| logMigration(params: MigrationParams<any>): Promise<void>; | ||
| unlogMigration(params: MigrationParams<any>): Promise<void>; | ||
| logMigration(params: { | ||
| name: string; | ||
| }): Promise<void>; | ||
| unlogMigration(params: { | ||
| name: string; | ||
| }): Promise<void>; | ||
| getExecutedMigrations(): Promise<MigrationRow[]>; | ||
@@ -18,0 +21,0 @@ ensureTable(): Promise<void>; |
| import { defineEntity, p } from '@mikro-orm/core'; | ||
| import { DatabaseTable, } from '@mikro-orm/sql'; | ||
| import { parse } from 'node:path'; | ||
| export class MigrationStorage { | ||
@@ -91,8 +90,3 @@ driver; | ||
| getMigrationName(name) { | ||
| const parsedName = parse(name); | ||
| if (['.js', '.ts'].includes(parsedName.ext)) { | ||
| // strip extension | ||
| return parsedName.name; | ||
| } | ||
| return name; | ||
| return name.replace(/\.[jt]s$/, ''); | ||
| } | ||
@@ -99,0 +93,0 @@ /** |
+13
-60
@@ -1,30 +0,16 @@ | ||
| import { type MigrationParams, type RunnableMigration } from 'umzug'; | ||
| import { type Constructor, type IMigrator, type MaybePromise, type MigratorEvent, type MikroORM } from '@mikro-orm/core'; | ||
| import { DatabaseSchema, type EntityManager } from '@mikro-orm/sql'; | ||
| import type { Migration } from './Migration.js'; | ||
| import { type IMigrationGenerator, type IMigrationRunner, type IMigratorStorage, type MikroORM } from '@mikro-orm/core'; | ||
| import { AbstractMigrator } from '@mikro-orm/core/migrations'; | ||
| import { type AbstractSqlDriver, DatabaseSchema, type EntityManager } from '@mikro-orm/sql'; | ||
| import { MigrationStorage } from './MigrationStorage.js'; | ||
| import type { MigrateOptions, MigrationResult, MigrationRow, UmzugMigration } from './typings.js'; | ||
| export declare class Migrator implements IMigrator { | ||
| private readonly em; | ||
| private umzug; | ||
| private runner; | ||
| private storage; | ||
| private generator; | ||
| private readonly driver; | ||
| import type { MigrationResult } from './typings.js'; | ||
| export declare class Migrator extends AbstractMigrator<AbstractSqlDriver> { | ||
| private readonly schemaGenerator; | ||
| private readonly config; | ||
| private readonly options; | ||
| private readonly absolutePath; | ||
| private readonly snapshotPath; | ||
| private snapshotPath?; | ||
| constructor(em: EntityManager); | ||
| /** | ||
| * Checks if `src` folder exists, it so, tries to adjust the migrations and seeders paths automatically to use it. | ||
| * If there is a `dist` or `build` folder, it will be used for the JS variant (`path` option), while the `src` folder will be | ||
| * used for the TS variant (`pathTs` option). | ||
| * | ||
| * If the default folder exists (e.g. `/migrations`), the config will respect that, so this auto-detection should not | ||
| * break existing projects, only help with the new ones. | ||
| */ | ||
| private detectSourceFolder; | ||
| static register(orm: MikroORM): void; | ||
| protected createRunner(): IMigrationRunner; | ||
| protected createStorage(): IMigratorStorage; | ||
| protected getDefaultGenerator(): IMigrationGenerator; | ||
| private getSnapshotPath; | ||
| protected init(): Promise<void>; | ||
| /** | ||
@@ -39,12 +25,4 @@ * @inheritDoc | ||
| createInitial(path?: string, name?: string, blank?: boolean): Promise<MigrationResult>; | ||
| getStorage(): MigrationStorage; | ||
| /** | ||
| * @inheritDoc | ||
| */ | ||
| on(eventName: MigratorEvent, listener: (event: UmzugMigration) => MaybePromise<void>): this; | ||
| /** | ||
| * @inheritDoc | ||
| */ | ||
| off(eventName: MigratorEvent, listener: (event: UmzugMigration) => MaybePromise<void>): this; | ||
| private createUmzug; | ||
| /** | ||
| * Initial migration can be created only if: | ||
@@ -58,30 +36,5 @@ * 1. no previous migrations were generated or executed | ||
| private validateInitialMigration; | ||
| /** | ||
| * @inheritDoc | ||
| */ | ||
| getExecuted(): Promise<MigrationRow[]>; | ||
| private ensureDatabase; | ||
| /** | ||
| * @inheritDoc | ||
| */ | ||
| getPending(): Promise<UmzugMigration[]>; | ||
| /** | ||
| * @inheritDoc | ||
| */ | ||
| up(options?: string | string[] | MigrateOptions): Promise<UmzugMigration[]>; | ||
| /** | ||
| * @inheritDoc | ||
| */ | ||
| down(options?: string | string[] | MigrateOptions): Promise<UmzugMigration[]>; | ||
| getStorage(): MigrationStorage; | ||
| protected resolve(params: MigrationParams<any>): RunnableMigration<any>; | ||
| protected getSchemaFromSnapshot(): DatabaseSchema | undefined; | ||
| protected getSchemaFromSnapshot(): Promise<DatabaseSchema | undefined>; | ||
| protected storeCurrentSchema(): Promise<void>; | ||
| protected initialize(MigrationClass: Constructor<Migration>, name: string): RunnableMigration<any>; | ||
| private getSchemaDiff; | ||
| private getMigrationFilename; | ||
| private prefix; | ||
| private runMigrations; | ||
| private runInTransaction; | ||
| private ensureMigrationsDirExists; | ||
| } |
+57
-217
@@ -1,6 +0,3 @@ | ||
| import { Umzug, } from 'umzug'; | ||
| import { basename, join } from 'node:path'; | ||
| import { existsSync, writeFileSync } from 'node:fs'; | ||
| import { t, Type, UnknownType, Utils, } from '@mikro-orm/core'; | ||
| import { fs } from '@mikro-orm/core/fs-utils'; | ||
| import { t, Type, UnknownType, } from '@mikro-orm/core'; | ||
| import { AbstractMigrator } from '@mikro-orm/core/migrations'; | ||
| import { DatabaseSchema, DatabaseTable, } from '@mikro-orm/sql'; | ||
@@ -11,63 +8,49 @@ import { MigrationRunner } from './MigrationRunner.js'; | ||
| import { JSMigrationGenerator } from './JSMigrationGenerator.js'; | ||
| export class Migrator { | ||
| em; | ||
| umzug; | ||
| runner; | ||
| storage; | ||
| generator; | ||
| driver; | ||
| export class Migrator extends AbstractMigrator { | ||
| schemaGenerator; | ||
| config; | ||
| options; | ||
| absolutePath; | ||
| snapshotPath; | ||
| constructor(em) { | ||
| this.em = em; | ||
| this.driver = this.em.getDriver(); | ||
| this.config = this.em.config; | ||
| this.options = this.config.get('migrations'); | ||
| super(em); | ||
| this.schemaGenerator = this.config.getExtension('@mikro-orm/schema-generator'); | ||
| this.detectSourceFolder(); | ||
| /* v8 ignore next */ | ||
| const key = (this.config.get('preferTs', Utils.detectTypeScriptSupport()) && this.options.pathTs) ? 'pathTs' : 'path'; | ||
| this.absolutePath = fs.absolutePath(this.options[key], this.config.get('baseDir')); | ||
| // for snapshots, we always want to use the path based on `emit` option, regardless of whether we run in TS context | ||
| /* v8 ignore next */ | ||
| const snapshotPath = this.options.emit === 'ts' && this.options.pathTs ? this.options.pathTs : this.options.path; | ||
| const absoluteSnapshotPath = fs.absolutePath(snapshotPath, this.config.get('baseDir')); | ||
| const dbName = basename(this.config.get('dbName')); | ||
| const snapshotName = this.options.snapshotName ?? `.snapshot-${dbName}`; | ||
| this.snapshotPath = fs.normalizePath(absoluteSnapshotPath, `${snapshotName}.json`); | ||
| this.createUmzug(); | ||
| } | ||
| /** | ||
| * Checks if `src` folder exists, it so, tries to adjust the migrations and seeders paths automatically to use it. | ||
| * If there is a `dist` or `build` folder, it will be used for the JS variant (`path` option), while the `src` folder will be | ||
| * used for the TS variant (`pathTs` option). | ||
| * | ||
| * If the default folder exists (e.g. `/migrations`), the config will respect that, so this auto-detection should not | ||
| * break existing projects, only help with the new ones. | ||
| */ | ||
| detectSourceFolder() { | ||
| const baseDir = this.config.get('baseDir'); | ||
| const defaultPath = './migrations'; | ||
| if (!fs.pathExists(baseDir + '/src')) { | ||
| this.options.path ??= defaultPath; | ||
| static register(orm) { | ||
| orm.config.registerExtension('@mikro-orm/migrator', () => new Migrator(orm.em)); | ||
| } | ||
| createRunner() { | ||
| return new MigrationRunner(this.driver, this.options, this.config); | ||
| } | ||
| createStorage() { | ||
| return new MigrationStorage(this.driver, this.options); | ||
| } | ||
| getDefaultGenerator() { | ||
| if (this.options.emit === 'js' || this.options.emit === 'cjs') { | ||
| return new JSMigrationGenerator(this.driver, this.config.getNamingStrategy(), this.options); | ||
| } | ||
| return new TSMigrationGenerator(this.driver, this.config.getNamingStrategy(), this.options); | ||
| } | ||
| async getSnapshotPath() { | ||
| if (!this.snapshotPath) { | ||
| const { fs } = await import('@mikro-orm/core/fs-utils'); | ||
| // for snapshots, we always want to use the path based on `emit` option, regardless of whether we run in TS context | ||
| /* v8 ignore next */ | ||
| const snapshotPath = this.options.emit === 'ts' && this.options.pathTs ? this.options.pathTs : this.options.path; | ||
| const absoluteSnapshotPath = fs.absolutePath(snapshotPath, this.config.get('baseDir')); | ||
| const dbName = this.config.get('dbName').replace(/\\/g, '/').split('/').pop(); | ||
| const snapshotName = this.options.snapshotName ?? `.snapshot-${dbName}`; | ||
| this.snapshotPath = fs.normalizePath(absoluteSnapshotPath, `${snapshotName}.json`); | ||
| } | ||
| return this.snapshotPath; | ||
| } | ||
| async init() { | ||
| if (this.initialized) { | ||
| return; | ||
| } | ||
| const exists = fs.pathExists(`${baseDir}/${defaultPath}`); | ||
| const distDir = fs.pathExists(baseDir + '/dist'); | ||
| const buildDir = fs.pathExists(baseDir + '/build'); | ||
| // if neither `dist` nor `build` exist, we use the `src` folder as it might be a JS project without building, but with `src` folder | ||
| await super.init(); | ||
| const created = await this.schemaGenerator.ensureDatabase(); | ||
| /* v8 ignore next */ | ||
| const path = distDir ? './dist' : (buildDir ? './build' : './src'); | ||
| // only if the user did not provide any values and if the default path does not exist | ||
| if (!this.options.path && !this.options.pathTs && !exists) { | ||
| this.options.path = `${path}/migrations`; | ||
| this.options.pathTs = './src/migrations'; | ||
| if (created) { | ||
| this.initServices(); | ||
| } | ||
| await this.storage.ensureTable(); | ||
| } | ||
| static register(orm) { | ||
| orm.config.registerExtension('@mikro-orm/migrator', () => new Migrator(orm.em)); | ||
| } | ||
| /** | ||
@@ -77,6 +60,6 @@ * @inheritDoc | ||
| async create(path, blank = false, initial = false, name) { | ||
| await this.init(); | ||
| if (initial) { | ||
| return this.createInitial(path, name, blank); | ||
| } | ||
| this.ensureMigrationsDirExists(); | ||
| const diff = await this.getSchemaDiff(blank, initial); | ||
@@ -95,3 +78,3 @@ if (diff.up.length === 0) { | ||
| async checkSchema() { | ||
| this.ensureMigrationsDirExists(); | ||
| await this.init(); | ||
| const diff = await this.getSchemaDiff(false, false); | ||
@@ -104,3 +87,3 @@ return diff.up.length > 0; | ||
| async createInitial(path, name, blank = false) { | ||
| this.ensureMigrationsDirExists(); | ||
| await this.init(); | ||
| const schemaExists = await this.validateInitialMigration(blank); | ||
@@ -111,3 +94,3 @@ const diff = await this.getSchemaDiff(blank, true); | ||
| if (schemaExists && !blank) { | ||
| await this.storage.logMigration({ name: migration[1], context: null }); | ||
| await this.storage.logMigration({ name: migration[1] }); | ||
| } | ||
@@ -120,55 +103,6 @@ return { | ||
| } | ||
| /** | ||
| * @inheritDoc | ||
| */ | ||
| on(eventName, listener) { | ||
| this.umzug.on(eventName, listener); | ||
| return this; | ||
| getStorage() { | ||
| return this.storage; | ||
| } | ||
| /** | ||
| * @inheritDoc | ||
| */ | ||
| off(eventName, listener) { | ||
| this.umzug.off(eventName, listener); | ||
| return this; | ||
| } | ||
| createUmzug() { | ||
| this.runner = new MigrationRunner(this.driver, this.options, this.config); | ||
| this.storage = new MigrationStorage(this.driver, this.options); | ||
| let migrations = { | ||
| glob: join(this.absolutePath, this.options.glob).replace(/\\/g, '/'), | ||
| resolve: (params) => this.resolve(params), | ||
| }; | ||
| if (this.options.migrationsList) { | ||
| migrations = this.options.migrationsList.map(migration => { | ||
| if (typeof migration === 'function') { | ||
| return this.initialize(migration, migration.name); | ||
| } | ||
| return this.initialize(migration.class, migration.name); | ||
| }); | ||
| } | ||
| this.umzug = new Umzug({ | ||
| storage: this.storage, | ||
| logger: undefined, | ||
| migrations, | ||
| }); | ||
| /* v8 ignore else */ | ||
| if (!this.options.silent) { | ||
| const logger = this.config.getLogger(); | ||
| this.umzug.on('migrating', event => logger.log('migrator', `Processing '${event.name}'`, { enabled: true })); | ||
| this.umzug.on('migrated', event => logger.log('migrator', `Applied '${event.name}'`, { enabled: true })); | ||
| this.umzug.on('reverting', event => logger.log('migrator', `Processing '${event.name}'`, { enabled: true })); | ||
| this.umzug.on('reverted', event => logger.log('migrator', `Reverted '${event.name}'`, { enabled: true })); | ||
| } | ||
| if (this.options.generator) { | ||
| this.generator = new this.options.generator(this.driver, this.config.getNamingStrategy(), this.options); | ||
| } | ||
| else if (this.options.emit === 'js' || this.options.emit === 'cjs') { | ||
| this.generator = new JSMigrationGenerator(this.driver, this.config.getNamingStrategy(), this.options); | ||
| } | ||
| else { | ||
| this.generator = new TSMigrationGenerator(this.driver, this.config.getNamingStrategy(), this.options); | ||
| } | ||
| } | ||
| /** | ||
| * Initial migration can be created only if: | ||
@@ -211,58 +145,12 @@ * 1. no previous migrations were generated or executed | ||
| } | ||
| /** | ||
| * @inheritDoc | ||
| */ | ||
| async getExecuted() { | ||
| await this.ensureDatabase(); | ||
| return this.storage.getExecutedMigrations(); | ||
| } | ||
| async ensureDatabase() { | ||
| this.ensureMigrationsDirExists(); | ||
| const created = await this.schemaGenerator.ensureDatabase(); | ||
| /* v8 ignore next */ | ||
| if (created) { | ||
| this.createUmzug(); | ||
| async getSchemaFromSnapshot() { | ||
| if (!this.options.snapshot) { | ||
| return undefined; | ||
| } | ||
| await this.storage.ensureTable(); | ||
| } | ||
| /** | ||
| * @inheritDoc | ||
| */ | ||
| async getPending() { | ||
| await this.ensureDatabase(); | ||
| return this.umzug.pending(); | ||
| } | ||
| /** | ||
| * @inheritDoc | ||
| */ | ||
| async up(options) { | ||
| return this.runMigrations('up', options); | ||
| } | ||
| /** | ||
| * @inheritDoc | ||
| */ | ||
| async down(options) { | ||
| return this.runMigrations('down', options); | ||
| } | ||
| getStorage() { | ||
| return this.storage; | ||
| } | ||
| resolve(params) { | ||
| const createMigrationHandler = async (method) => { | ||
| const migration = await fs.dynamicImport(params.path); | ||
| const MigrationClass = Object.values(migration).find(cls => typeof cls === 'function' && typeof cls.constructor === 'function'); | ||
| const instance = new MigrationClass(this.driver, this.config); | ||
| await this.runner.run(instance, method); | ||
| }; | ||
| return { | ||
| name: this.storage.getMigrationName(params.name), | ||
| up: () => createMigrationHandler('up'), | ||
| down: () => createMigrationHandler('down'), | ||
| }; | ||
| } | ||
| getSchemaFromSnapshot() { | ||
| if (!this.options.snapshot || !existsSync(this.snapshotPath)) { | ||
| const snapshotPath = await this.getSnapshotPath(); | ||
| const { fs } = await import('@mikro-orm/core/fs-utils'); | ||
| if (!fs.pathExists(snapshotPath)) { | ||
| return undefined; | ||
| } | ||
| const data = fs.readJSONSync(this.snapshotPath); | ||
| const data = fs.readJSONSync(snapshotPath); | ||
| const schema = new DatabaseSchema(this.driver.getPlatform(), this.config.get('schema')); | ||
@@ -289,13 +177,7 @@ const { tables, namespaces, ...rest } = data; | ||
| } | ||
| const snapshotPath = await this.getSnapshotPath(); | ||
| const schema = this.schemaGenerator.getTargetSchema(); | ||
| writeFileSync(this.snapshotPath, JSON.stringify(schema, null, 2)); | ||
| const { fs } = await import('@mikro-orm/core/fs-utils'); | ||
| await fs.writeFile(snapshotPath, JSON.stringify(schema, null, 2)); | ||
| } | ||
| initialize(MigrationClass, name) { | ||
| const instance = new MigrationClass(this.driver, this.config); | ||
| return { | ||
| name: this.storage.getMigrationName(name), | ||
| up: () => this.runner.run(instance, 'up'), | ||
| down: () => this.runner.run(instance, 'down'), | ||
| }; | ||
| } | ||
| async getSchemaDiff(blank, initial) { | ||
@@ -342,3 +224,3 @@ const up = []; | ||
| dropTables: this.options.dropTables, | ||
| fromSchema: this.getSchemaFromSnapshot(), | ||
| fromSchema: await this.getSchemaFromSnapshot(), | ||
| }); | ||
@@ -353,2 +235,3 @@ up.push(...splitStatements(diff.up)); | ||
| } | ||
| /* v8 ignore next */ | ||
| diff.splice(i, 1); | ||
@@ -361,45 +244,2 @@ } | ||
| } | ||
| getMigrationFilename(name) { | ||
| name = name.replace(/\.[jt]s$/, ''); | ||
| return name.match(/^\d{14}$/) ? this.options.fileName(name) : name; | ||
| } | ||
| prefix(options) { | ||
| if (typeof options === 'string' || Array.isArray(options)) { | ||
| return { migrations: Utils.asArray(options).map(name => this.getMigrationFilename(name)) }; | ||
| } | ||
| if (!options) { | ||
| return {}; | ||
| } | ||
| if (options.migrations) { | ||
| options.migrations = options.migrations.map(name => this.getMigrationFilename(name)); | ||
| } | ||
| if (options.transaction) { | ||
| delete options.transaction; | ||
| } | ||
| ['from', 'to'].filter(k => options[k]).forEach(k => options[k] = this.getMigrationFilename(options[k])); | ||
| return options; | ||
| } | ||
| async runMigrations(method, options) { | ||
| await this.ensureDatabase(); | ||
| if (!this.options.transactional || !this.options.allOrNothing) { | ||
| return this.umzug[method](this.prefix(options)); | ||
| } | ||
| if (Utils.isObject(options) && options.transaction) { | ||
| return this.runInTransaction(options.transaction, method, options); | ||
| } | ||
| return this.driver.getConnection().transactional(trx => this.runInTransaction(trx, method, options)); | ||
| } | ||
| async runInTransaction(trx, method, options) { | ||
| this.runner.setMasterMigration(trx); | ||
| this.storage.setMasterMigration(trx); | ||
| const ret = await this.umzug[method](this.prefix(options)); | ||
| this.runner.unsetMasterMigration(); | ||
| this.storage.unsetMasterMigration(); | ||
| return ret; | ||
| } | ||
| ensureMigrationsDirExists() { | ||
| if (!this.options.migrationsList) { | ||
| fs.ensureDir(this.absolutePath); | ||
| } | ||
| } | ||
| } |
+3
-4
| { | ||
| "name": "@mikro-orm/migrations", | ||
| "type": "module", | ||
| "version": "7.0.0-dev.260", | ||
| "version": "7.0.0-dev.261", | ||
| "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.", | ||
@@ -53,4 +53,3 @@ "exports": { | ||
| "dependencies": { | ||
| "@mikro-orm/sql": "7.0.0-dev.260", | ||
| "umzug": "3.8.2" | ||
| "@mikro-orm/sql": "7.0.0-dev.261" | ||
| }, | ||
@@ -61,4 +60,4 @@ "devDependencies": { | ||
| "peerDependencies": { | ||
| "@mikro-orm/core": "7.0.0-dev.260" | ||
| "@mikro-orm/core": "7.0.0-dev.261" | ||
| } | ||
| } |
+1
-1
@@ -1,1 +0,1 @@ | ||
| export type { UmzugMigration, MigrateOptions, MigrationResult, MigrationRow } from '@mikro-orm/core'; | ||
| export type { MigrationInfo, MigrateOptions, MigrationResult, MigrationRow } from '@mikro-orm/core'; |
Sorry, the diff of this file is too big to display
2
-33.33%180795
-20.09%708
-22.96%+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
Updated