@mikro-orm/migrations
Advanced tools
| import { MigrationGenerator } from './MigrationGenerator.js'; | ||
| /** Generates migration files in CommonJS JavaScript format. */ | ||
| export declare class JSMigrationGenerator extends MigrationGenerator { | ||
| /** | ||
| * @inheritDoc | ||
| */ | ||
| generateMigrationFile(className: string, diff: { | ||
| up: string[]; | ||
| down: string[]; | ||
| }): string; | ||
| /** | ||
| * @inheritDoc | ||
| */ | ||
| generateMigrationFile( | ||
| className: string, | ||
| diff: { | ||
| up: string[]; | ||
| down: string[]; | ||
| }, | ||
| ): string; | ||
| } |
+20
-20
| import { MigrationGenerator } from './MigrationGenerator.js'; | ||
| /** Generates migration files in CommonJS JavaScript format. */ | ||
| export class JSMigrationGenerator extends MigrationGenerator { | ||
| /** | ||
| * @inheritDoc | ||
| */ | ||
| generateMigrationFile(className, diff) { | ||
| let ret = `'use strict';\n`; | ||
| ret += `Object.defineProperty(exports, '__esModule', { value: true });\n`; | ||
| ret += `const { Migration } = require('@mikro-orm/migrations');\n\n`; | ||
| ret += `class ${className} extends Migration {\n\n`; | ||
| ret += ` async up() {\n`; | ||
| diff.up.forEach(sql => (ret += this.createStatement(sql, 4))); | ||
| ret += ` }\n\n`; | ||
| /* v8 ignore next */ | ||
| if (diff.down.length > 0) { | ||
| ret += ` async down() {\n`; | ||
| diff.down.forEach(sql => (ret += this.createStatement(sql, 4))); | ||
| ret += ` }\n\n`; | ||
| } | ||
| ret += `}\n`; | ||
| ret += `exports.${className} = ${className};\n`; | ||
| return ret; | ||
| /** | ||
| * @inheritDoc | ||
| */ | ||
| generateMigrationFile(className, diff) { | ||
| let ret = `'use strict';\n`; | ||
| ret += `Object.defineProperty(exports, '__esModule', { value: true });\n`; | ||
| ret += `const { Migration } = require('@mikro-orm/migrations');\n\n`; | ||
| ret += `class ${className} extends Migration {\n\n`; | ||
| ret += ` async up() {\n`; | ||
| diff.up.forEach(sql => (ret += this.createStatement(sql, 4))); | ||
| ret += ` }\n\n`; | ||
| /* v8 ignore next */ | ||
| if (diff.down.length > 0) { | ||
| ret += ` async down() {\n`; | ||
| diff.down.forEach(sql => (ret += this.createStatement(sql, 4))); | ||
| ret += ` }\n\n`; | ||
| } | ||
| ret += `}\n`; | ||
| ret += `exports.${className} = ${className};\n`; | ||
| return ret; | ||
| } | ||
| } |
+29
-23
@@ -1,2 +0,8 @@ | ||
| import { type AnyEntity, type Configuration, type EntityData, type RawQueryFragment, type Transaction } from '@mikro-orm/core'; | ||
| import { | ||
| type AnyEntity, | ||
| type Configuration, | ||
| type EntityData, | ||
| type RawQueryFragment, | ||
| type Transaction, | ||
| } from '@mikro-orm/core'; | ||
| import type { AbstractSqlDriver, EntityManager, NativeQueryBuilder } from '@mikro-orm/sql'; | ||
@@ -7,24 +13,24 @@ /** A migration query: raw SQL string, a native query builder instance, or a `raw()` SQL fragment. */ | ||
| export declare abstract class Migration { | ||
| #private; | ||
| protected readonly driver: AbstractSqlDriver; | ||
| protected readonly config: Configuration; | ||
| protected ctx?: Transaction; | ||
| constructor(driver: AbstractSqlDriver, config: Configuration); | ||
| abstract up(): Promise<void> | void; | ||
| down(): Promise<void> | void; | ||
| isTransactional(): boolean; | ||
| addSql(sql: Query): void; | ||
| reset(): void; | ||
| setTransactionContext(ctx: Transaction): void; | ||
| /** | ||
| * Executes a raw SQL query. Accepts a string SQL, `raw()` SQL fragment, or a native query builder instance. | ||
| * The `params` parameter is respected only if you use string SQL in the first parameter. | ||
| */ | ||
| execute(sql: Query, params?: unknown[]): Promise<EntityData<AnyEntity>[]>; | ||
| /** | ||
| * Creates a cached `EntityManager` instance for this migration, which will respect | ||
| * the current transaction context. | ||
| */ | ||
| getEntityManager(): EntityManager; | ||
| getQueries(): Query[]; | ||
| #private; | ||
| protected readonly driver: AbstractSqlDriver; | ||
| protected readonly config: Configuration; | ||
| protected ctx?: Transaction; | ||
| constructor(driver: AbstractSqlDriver, config: Configuration); | ||
| abstract up(): Promise<void> | void; | ||
| down(): Promise<void> | void; | ||
| isTransactional(): boolean; | ||
| addSql(sql: Query): void; | ||
| reset(): void; | ||
| setTransactionContext(ctx: Transaction): void; | ||
| /** | ||
| * Executes a raw SQL query. Accepts a string SQL, `raw()` SQL fragment, or a native query builder instance. | ||
| * The `params` parameter is respected only if you use string SQL in the first parameter. | ||
| */ | ||
| execute(sql: Query, params?: unknown[]): Promise<EntityData<AnyEntity>[]>; | ||
| /** | ||
| * Creates a cached `EntityManager` instance for this migration, which will respect | ||
| * the current transaction context. | ||
| */ | ||
| getEntityManager(): EntityManager; | ||
| getQueries(): Query[]; | ||
| } |
+45
-45
| /** Base class for SQL database migrations. Extend this class and implement `up()` (and optionally `down()`). */ | ||
| export class Migration { | ||
| driver; | ||
| config; | ||
| #queries = []; | ||
| ctx; | ||
| #em; | ||
| constructor(driver, config) { | ||
| this.driver = driver; | ||
| this.config = config; | ||
| driver; | ||
| config; | ||
| #queries = []; | ||
| ctx; | ||
| #em; | ||
| constructor(driver, config) { | ||
| this.driver = driver; | ||
| this.config = config; | ||
| } | ||
| down() { | ||
| throw new Error('This migration cannot be reverted'); | ||
| } | ||
| isTransactional() { | ||
| return true; | ||
| } | ||
| addSql(sql) { | ||
| this.#queries.push(sql); | ||
| } | ||
| reset() { | ||
| this.#queries.length = 0; | ||
| this.ctx = undefined; | ||
| } | ||
| setTransactionContext(ctx) { | ||
| this.ctx = ctx; | ||
| } | ||
| /** | ||
| * Executes a raw SQL query. Accepts a string SQL, `raw()` SQL fragment, or a native query builder instance. | ||
| * The `params` parameter is respected only if you use string SQL in the first parameter. | ||
| */ | ||
| async execute(sql, params) { | ||
| return this.driver.execute(sql, params, 'all', this.ctx); | ||
| } | ||
| /** | ||
| * Creates a cached `EntityManager` instance for this migration, which will respect | ||
| * the current transaction context. | ||
| */ | ||
| getEntityManager() { | ||
| if (!this.#em) { | ||
| this.#em = this.driver.createEntityManager(); | ||
| this.#em.setTransactionContext(this.ctx); | ||
| } | ||
| down() { | ||
| throw new Error('This migration cannot be reverted'); | ||
| } | ||
| isTransactional() { | ||
| return true; | ||
| } | ||
| addSql(sql) { | ||
| this.#queries.push(sql); | ||
| } | ||
| reset() { | ||
| this.#queries.length = 0; | ||
| this.ctx = undefined; | ||
| } | ||
| setTransactionContext(ctx) { | ||
| this.ctx = ctx; | ||
| } | ||
| /** | ||
| * Executes a raw SQL query. Accepts a string SQL, `raw()` SQL fragment, or a native query builder instance. | ||
| * The `params` parameter is respected only if you use string SQL in the first parameter. | ||
| */ | ||
| async execute(sql, params) { | ||
| return this.driver.execute(sql, params, 'all', this.ctx); | ||
| } | ||
| /** | ||
| * Creates a cached `EntityManager` instance for this migration, which will respect | ||
| * the current transaction context. | ||
| */ | ||
| getEntityManager() { | ||
| if (!this.#em) { | ||
| this.#em = this.driver.createEntityManager(); | ||
| this.#em.setTransactionContext(this.ctx); | ||
| } | ||
| return this.#em; | ||
| } | ||
| getQueries() { | ||
| return this.#queries; | ||
| } | ||
| return this.#em; | ||
| } | ||
| getQueries() { | ||
| return this.#queries; | ||
| } | ||
| } |
+35
-23
@@ -1,27 +0,39 @@ | ||
| import { type IMigrationGenerator, type MaybePromise, type MigrationsOptions, type NamingStrategy } from '@mikro-orm/core'; | ||
| import { | ||
| type IMigrationGenerator, | ||
| type MaybePromise, | ||
| type MigrationsOptions, | ||
| type NamingStrategy, | ||
| } from '@mikro-orm/core'; | ||
| import type { AbstractSqlDriver } from '@mikro-orm/sql'; | ||
| /** Base class for generating migration source files from schema diffs. */ | ||
| export declare abstract class MigrationGenerator implements IMigrationGenerator { | ||
| protected readonly driver: AbstractSqlDriver; | ||
| protected readonly namingStrategy: NamingStrategy; | ||
| protected readonly options: MigrationsOptions; | ||
| constructor(driver: AbstractSqlDriver, namingStrategy: NamingStrategy, options: MigrationsOptions); | ||
| /** | ||
| * @inheritDoc | ||
| */ | ||
| generate(diff: { | ||
| up: string[]; | ||
| down: string[]; | ||
| }, path?: string, name?: string): Promise<[string, string]>; | ||
| /** | ||
| * @inheritDoc | ||
| */ | ||
| createStatement(sql: string, padLeft: number): string; | ||
| /** | ||
| * @inheritDoc | ||
| */ | ||
| abstract generateMigrationFile(className: string, diff: { | ||
| up: string[]; | ||
| down: string[]; | ||
| }): MaybePromise<string>; | ||
| protected readonly driver: AbstractSqlDriver; | ||
| protected readonly namingStrategy: NamingStrategy; | ||
| protected readonly options: MigrationsOptions; | ||
| constructor(driver: AbstractSqlDriver, namingStrategy: NamingStrategy, options: MigrationsOptions); | ||
| /** | ||
| * @inheritDoc | ||
| */ | ||
| generate( | ||
| diff: { | ||
| up: string[]; | ||
| down: string[]; | ||
| }, | ||
| path?: string, | ||
| name?: string, | ||
| ): Promise<[string, string]>; | ||
| /** | ||
| * @inheritDoc | ||
| */ | ||
| createStatement(sql: string, padLeft: number): string; | ||
| /** | ||
| * @inheritDoc | ||
| */ | ||
| abstract generateMigrationFile( | ||
| className: string, | ||
| diff: { | ||
| up: string[]; | ||
| down: string[]; | ||
| }, | ||
| ): MaybePromise<string>; | ||
| } |
+33
-33
| /** Base class for generating migration source files from schema diffs. */ | ||
| export class MigrationGenerator { | ||
| driver; | ||
| namingStrategy; | ||
| options; | ||
| constructor(driver, namingStrategy, options) { | ||
| this.driver = driver; | ||
| this.namingStrategy = namingStrategy; | ||
| this.options = options; | ||
| driver; | ||
| namingStrategy; | ||
| options; | ||
| constructor(driver, namingStrategy, options) { | ||
| this.driver = driver; | ||
| this.namingStrategy = namingStrategy; | ||
| this.options = options; | ||
| } | ||
| /** | ||
| * @inheritDoc | ||
| */ | ||
| async generate(diff, path, name) { | ||
| const { fs } = await import('@mikro-orm/core/fs-utils'); | ||
| /* v8 ignore next */ | ||
| const defaultPath = this.options.emit === 'ts' && this.options.pathTs ? this.options.pathTs : this.options.path; | ||
| path = fs.normalizePath(this.driver.config.get('baseDir'), path ?? defaultPath); | ||
| fs.ensureDir(path); | ||
| const timestamp = new Date().toISOString().replace(/[-T:]|\.\d{3}z$/gi, ''); | ||
| const className = this.namingStrategy.classToMigrationName(timestamp, name); | ||
| const fileName = `${this.options.fileName(timestamp, name)}.${this.options.emit}`; | ||
| const ret = await this.generateMigrationFile(className, diff); | ||
| await fs.writeFile(path + '/' + fileName, ret, { flush: true }); | ||
| return [ret, fileName]; | ||
| } | ||
| /** | ||
| * @inheritDoc | ||
| */ | ||
| createStatement(sql, padLeft) { | ||
| if (sql) { | ||
| const padding = ' '.repeat(padLeft); | ||
| return `${padding}this.addSql(\`${sql.replace(/[`$\\]/g, '\\$&')}\`);\n`; | ||
| } | ||
| /** | ||
| * @inheritDoc | ||
| */ | ||
| async generate(diff, path, name) { | ||
| const { fs } = await import('@mikro-orm/core/fs-utils'); | ||
| /* v8 ignore next */ | ||
| const defaultPath = this.options.emit === 'ts' && this.options.pathTs ? this.options.pathTs : this.options.path; | ||
| path = fs.normalizePath(this.driver.config.get('baseDir'), path ?? defaultPath); | ||
| fs.ensureDir(path); | ||
| const timestamp = new Date().toISOString().replace(/[-T:]|\.\d{3}z$/gi, ''); | ||
| const className = this.namingStrategy.classToMigrationName(timestamp, name); | ||
| const fileName = `${this.options.fileName(timestamp, name)}.${this.options.emit}`; | ||
| const ret = await this.generateMigrationFile(className, diff); | ||
| await fs.writeFile(path + '/' + fileName, ret, { flush: true }); | ||
| return [ret, fileName]; | ||
| } | ||
| /** | ||
| * @inheritDoc | ||
| */ | ||
| createStatement(sql, padLeft) { | ||
| if (sql) { | ||
| const padding = ' '.repeat(padLeft); | ||
| return `${padding}this.addSql(\`${sql.replace(/[`$\\]/g, '\\$&')}\`);\n`; | ||
| } | ||
| return '\n'; | ||
| } | ||
| return '\n'; | ||
| } | ||
| } |
@@ -6,11 +6,11 @@ import { type Configuration, type MigrationsOptions, type Transaction } from '@mikro-orm/core'; | ||
| export declare class MigrationRunner { | ||
| #private; | ||
| protected readonly driver: AbstractSqlDriver; | ||
| protected readonly options: MigrationsOptions; | ||
| protected readonly config: Configuration; | ||
| constructor(driver: AbstractSqlDriver, options: MigrationsOptions, config: Configuration); | ||
| run(migration: Migration, method: 'up' | 'down'): Promise<void>; | ||
| setMasterMigration(trx: Transaction): void; | ||
| unsetMasterMigration(): void; | ||
| private getQueries; | ||
| #private; | ||
| protected readonly driver: AbstractSqlDriver; | ||
| protected readonly options: MigrationsOptions; | ||
| protected readonly config: Configuration; | ||
| constructor(driver: AbstractSqlDriver, options: MigrationsOptions, config: Configuration); | ||
| run(migration: Migration, method: 'up' | 'down'): Promise<void>; | ||
| setMasterMigration(trx: Transaction): void; | ||
| unsetMasterMigration(): void; | ||
| private getQueries; | ||
| } |
+43
-41
| import { Utils } from '@mikro-orm/core'; | ||
| /** Executes individual migration files within optional transaction contexts. */ | ||
| export class MigrationRunner { | ||
| driver; | ||
| options; | ||
| config; | ||
| #connection; | ||
| #helper; | ||
| #masterTransaction; | ||
| constructor(driver, options, config) { | ||
| this.driver = driver; | ||
| this.options = options; | ||
| this.config = config; | ||
| this.#connection = this.driver.getConnection(); | ||
| this.#helper = this.driver.getPlatform().getSchemaHelper(); | ||
| driver; | ||
| options; | ||
| config; | ||
| #connection; | ||
| #helper; | ||
| #masterTransaction; | ||
| constructor(driver, options, config) { | ||
| this.driver = driver; | ||
| this.options = options; | ||
| this.config = config; | ||
| this.#connection = this.driver.getConnection(); | ||
| this.#helper = this.driver.getPlatform().getSchemaHelper(); | ||
| } | ||
| async run(migration, method) { | ||
| migration.reset(); | ||
| if (!this.options.transactional || !migration.isTransactional()) { | ||
| const queries = await this.getQueries(migration, method); | ||
| await Utils.runSerial(queries, sql => this.driver.execute(sql)); | ||
| } else { | ||
| await this.#connection.transactional( | ||
| async tx => { | ||
| migration.setTransactionContext(tx); | ||
| const queries = await this.getQueries(migration, method); | ||
| await Utils.runSerial(queries, sql => this.driver.execute(sql, undefined, 'all', tx)); | ||
| }, | ||
| { ctx: this.#masterTransaction }, | ||
| ); | ||
| } | ||
| async run(migration, method) { | ||
| migration.reset(); | ||
| if (!this.options.transactional || !migration.isTransactional()) { | ||
| const queries = await this.getQueries(migration, method); | ||
| await Utils.runSerial(queries, sql => this.driver.execute(sql)); | ||
| } | ||
| else { | ||
| await this.#connection.transactional(async (tx) => { | ||
| migration.setTransactionContext(tx); | ||
| const queries = await this.getQueries(migration, method); | ||
| await Utils.runSerial(queries, sql => this.driver.execute(sql, undefined, 'all', tx)); | ||
| }, { ctx: this.#masterTransaction }); | ||
| } | ||
| } | ||
| setMasterMigration(trx) { | ||
| this.#masterTransaction = trx; | ||
| } | ||
| unsetMasterMigration() { | ||
| this.#masterTransaction = undefined; | ||
| } | ||
| async getQueries(migration, method) { | ||
| await migration[method](); | ||
| const charset = this.config.get('charset'); | ||
| let queries = migration.getQueries(); | ||
| queries.unshift(...this.#helper.getSchemaBeginning(charset, this.options.disableForeignKeys).split('\n')); | ||
| queries.push(...this.#helper.getSchemaEnd(this.options.disableForeignKeys).split('\n')); | ||
| queries = queries.filter(sql => typeof sql !== 'string' || sql.trim().length > 0); | ||
| return queries; | ||
| } | ||
| } | ||
| setMasterMigration(trx) { | ||
| this.#masterTransaction = trx; | ||
| } | ||
| unsetMasterMigration() { | ||
| this.#masterTransaction = undefined; | ||
| } | ||
| async getQueries(migration, method) { | ||
| await migration[method](); | ||
| const charset = this.config.get('charset'); | ||
| let queries = migration.getQueries(); | ||
| queries.unshift(...this.#helper.getSchemaBeginning(charset, this.options.disableForeignKeys).split('\n')); | ||
| queries.push(...this.#helper.getSchemaEnd(this.options.disableForeignKeys).split('\n')); | ||
| queries = queries.filter(sql => typeof sql !== 'string' || sql.trim().length > 0); | ||
| return queries; | ||
| } | ||
| } |
+23
-27
@@ -6,29 +6,25 @@ import { type MigrationsOptions, type Transaction, type EntitySchema } from '@mikro-orm/core'; | ||
| export declare class MigrationStorage { | ||
| #private; | ||
| protected readonly driver: AbstractSqlDriver; | ||
| protected readonly options: MigrationsOptions; | ||
| constructor(driver: AbstractSqlDriver, options: MigrationsOptions); | ||
| executed(): Promise<string[]>; | ||
| logMigration(params: { | ||
| name: string; | ||
| }): Promise<void>; | ||
| unlogMigration(params: { | ||
| name: string; | ||
| }): Promise<void>; | ||
| getExecutedMigrations(): Promise<MigrationRow[]>; | ||
| ensureTable(): Promise<void>; | ||
| setMasterMigration(trx: Transaction): void; | ||
| unsetMasterMigration(): void; | ||
| /** | ||
| * @internal | ||
| */ | ||
| getMigrationName(name: string): string; | ||
| /** | ||
| * @internal | ||
| */ | ||
| getTableName(): { | ||
| tableName: string; | ||
| schemaName: string; | ||
| entity: EntitySchema; | ||
| }; | ||
| #private; | ||
| protected readonly driver: AbstractSqlDriver; | ||
| protected readonly options: MigrationsOptions; | ||
| constructor(driver: AbstractSqlDriver, options: MigrationsOptions); | ||
| executed(): Promise<string[]>; | ||
| logMigration(params: { name: string }): Promise<void>; | ||
| unlogMigration(params: { name: string }): Promise<void>; | ||
| getExecutedMigrations(): Promise<MigrationRow[]>; | ||
| ensureTable(): Promise<void>; | ||
| setMasterMigration(trx: Transaction): void; | ||
| unsetMasterMigration(): void; | ||
| /** | ||
| * @internal | ||
| */ | ||
| getMigrationName(name: string): string; | ||
| /** | ||
| * @internal | ||
| */ | ||
| getTableName(): { | ||
| tableName: string; | ||
| schemaName: string; | ||
| entity: EntitySchema; | ||
| }; | ||
| } |
+113
-108
| import { defineEntity, p } from '@mikro-orm/core'; | ||
| import { DatabaseTable, } from '@mikro-orm/sql'; | ||
| import { DatabaseTable } from '@mikro-orm/sql'; | ||
| /** Tracks executed migrations in a database table. */ | ||
| export class MigrationStorage { | ||
| driver; | ||
| options; | ||
| #connection; | ||
| #helper; | ||
| #masterTransaction; | ||
| #platform; | ||
| constructor(driver, options) { | ||
| this.driver = driver; | ||
| this.options = options; | ||
| this.#connection = this.driver.getConnection(); | ||
| this.#platform = this.driver.getPlatform(); | ||
| this.#helper = this.#platform.getSchemaHelper(); | ||
| driver; | ||
| options; | ||
| #connection; | ||
| #helper; | ||
| #masterTransaction; | ||
| #platform; | ||
| constructor(driver, options) { | ||
| this.driver = driver; | ||
| this.options = options; | ||
| this.#connection = this.driver.getConnection(); | ||
| this.#platform = this.driver.getPlatform(); | ||
| this.#helper = this.#platform.getSchemaHelper(); | ||
| } | ||
| async executed() { | ||
| const migrations = await this.getExecutedMigrations(); | ||
| return migrations.map(({ name }) => this.getMigrationName(name)); | ||
| } | ||
| async logMigration(params) { | ||
| const { entity } = this.getTableName(); | ||
| const name = this.getMigrationName(params.name); | ||
| await this.driver.nativeInsert(entity, { name }, { ctx: this.#masterTransaction }); | ||
| } | ||
| async unlogMigration(params) { | ||
| const { entity } = this.getTableName(); | ||
| const withoutExt = this.getMigrationName(params.name); | ||
| const names = [withoutExt, withoutExt + '.js', withoutExt + '.ts']; | ||
| await this.driver.nativeDelete( | ||
| entity, | ||
| { name: { $in: [params.name, ...names] } }, | ||
| { ctx: this.#masterTransaction }, | ||
| ); | ||
| } | ||
| async getExecutedMigrations() { | ||
| const { entity, schemaName } = this.getTableName(); | ||
| const res = await this.driver | ||
| .createQueryBuilder(entity, this.#masterTransaction) | ||
| .withSchema(schemaName) | ||
| .orderBy({ id: 'asc' }) | ||
| .execute('all', false); | ||
| return res.map(row => { | ||
| if (typeof row.executed_at === 'string' || typeof row.executed_at === 'number') { | ||
| row.executed_at = new Date(row.executed_at); | ||
| } | ||
| return row; | ||
| }); | ||
| } | ||
| async ensureTable() { | ||
| const tables = await this.#connection.execute(this.#helper.getListTablesSQL(), [], 'all', this.#masterTransaction); | ||
| const { tableName, schemaName } = this.getTableName(); | ||
| if (tables.find(t => t.table_name === tableName && (!t.schema_name || t.schema_name === schemaName))) { | ||
| return; | ||
| } | ||
| async executed() { | ||
| const migrations = await this.getExecutedMigrations(); | ||
| return migrations.map(({ name }) => this.getMigrationName(name)); | ||
| const schemas = await this.#helper.getNamespaces(this.#connection); | ||
| if (schemaName && !schemas.includes(schemaName)) { | ||
| const sql = this.#helper.getCreateNamespaceSQL(schemaName); | ||
| await this.#connection.execute(sql); | ||
| } | ||
| async logMigration(params) { | ||
| const { entity } = this.getTableName(); | ||
| const name = this.getMigrationName(params.name); | ||
| await this.driver.nativeInsert(entity, { name }, { ctx: this.#masterTransaction }); | ||
| } | ||
| async unlogMigration(params) { | ||
| const { entity } = this.getTableName(); | ||
| const withoutExt = this.getMigrationName(params.name); | ||
| const names = [withoutExt, withoutExt + '.js', withoutExt + '.ts']; | ||
| await this.driver.nativeDelete(entity, { name: { $in: [params.name, ...names] } }, { ctx: this.#masterTransaction }); | ||
| } | ||
| async getExecutedMigrations() { | ||
| const { entity, schemaName } = this.getTableName(); | ||
| const res = await this.driver | ||
| .createQueryBuilder(entity, this.#masterTransaction) | ||
| .withSchema(schemaName) | ||
| .orderBy({ id: 'asc' }) | ||
| .execute('all', false); | ||
| return res.map(row => { | ||
| if (typeof row.executed_at === 'string' || typeof row.executed_at === 'number') { | ||
| row.executed_at = new Date(row.executed_at); | ||
| } | ||
| return row; | ||
| }); | ||
| } | ||
| async ensureTable() { | ||
| const tables = await this.#connection.execute(this.#helper.getListTablesSQL(), [], 'all', this.#masterTransaction); | ||
| const { tableName, schemaName } = this.getTableName(); | ||
| if (tables.find(t => t.table_name === tableName && (!t.schema_name || t.schema_name === schemaName))) { | ||
| return; | ||
| } | ||
| const schemas = await this.#helper.getNamespaces(this.#connection); | ||
| if (schemaName && !schemas.includes(schemaName)) { | ||
| const sql = this.#helper.getCreateNamespaceSQL(schemaName); | ||
| await this.#connection.execute(sql); | ||
| } | ||
| const table = new DatabaseTable(this.#platform, tableName, schemaName); | ||
| table.addColumn({ | ||
| name: 'id', | ||
| type: this.#platform.getIntegerTypeDeclarationSQL({ autoincrement: true, unsigned: true }), | ||
| mappedType: this.#platform.getMappedType('number'), | ||
| primary: true, | ||
| autoincrement: true, | ||
| }); | ||
| table.addColumn({ | ||
| name: 'name', | ||
| type: this.#platform.getVarcharTypeDeclarationSQL({}), | ||
| mappedType: this.#platform.getMappedType('string'), | ||
| }); | ||
| const length = this.#platform.getDefaultDateTimeLength(); | ||
| table.addColumn({ | ||
| name: 'executed_at', | ||
| type: this.#platform.getDateTimeTypeDeclarationSQL({ length }), | ||
| mappedType: this.#platform.getMappedType('datetime'), | ||
| default: this.#platform.getCurrentTimestampSQL(length), | ||
| length, | ||
| }); | ||
| const sql = this.#helper.createTable(table); | ||
| await this.#connection.execute(sql.join(';\n'), [], 'run', this.#masterTransaction); | ||
| } | ||
| setMasterMigration(trx) { | ||
| this.#masterTransaction = trx; | ||
| } | ||
| unsetMasterMigration() { | ||
| this.#masterTransaction = undefined; | ||
| } | ||
| /** | ||
| * @internal | ||
| */ | ||
| getMigrationName(name) { | ||
| return name.replace(/\.[jt]s$/, ''); | ||
| } | ||
| /** | ||
| * @internal | ||
| */ | ||
| getTableName() { | ||
| const parts = this.options.tableName.split('.'); | ||
| const tableName = parts.length > 1 ? parts[1] : parts[0]; | ||
| const schemaName = parts.length > 1 ? parts[0] : this.driver.config.get('schema', this.driver.getPlatform().getDefaultSchemaName()); | ||
| const entity = defineEntity({ | ||
| name: 'Migration', | ||
| tableName, | ||
| schema: schemaName, | ||
| properties: { | ||
| id: p.integer().primary().fieldNames('id'), | ||
| name: p.string().fieldNames('name'), | ||
| executedAt: p.datetime().defaultRaw('current_timestamp').fieldNames('executed_at'), | ||
| }, | ||
| }).init(); | ||
| entity.meta.sync(); | ||
| return { tableName, schemaName, entity }; | ||
| } | ||
| const table = new DatabaseTable(this.#platform, tableName, schemaName); | ||
| table.addColumn({ | ||
| name: 'id', | ||
| type: this.#platform.getIntegerTypeDeclarationSQL({ autoincrement: true, unsigned: true }), | ||
| mappedType: this.#platform.getMappedType('number'), | ||
| primary: true, | ||
| autoincrement: true, | ||
| }); | ||
| table.addColumn({ | ||
| name: 'name', | ||
| type: this.#platform.getVarcharTypeDeclarationSQL({}), | ||
| mappedType: this.#platform.getMappedType('string'), | ||
| }); | ||
| const length = this.#platform.getDefaultDateTimeLength(); | ||
| table.addColumn({ | ||
| name: 'executed_at', | ||
| type: this.#platform.getDateTimeTypeDeclarationSQL({ length }), | ||
| mappedType: this.#platform.getMappedType('datetime'), | ||
| default: this.#platform.getCurrentTimestampSQL(length), | ||
| length, | ||
| }); | ||
| const sql = this.#helper.createTable(table); | ||
| await this.#connection.execute(sql.join(';\n'), [], 'run', this.#masterTransaction); | ||
| } | ||
| setMasterMigration(trx) { | ||
| this.#masterTransaction = trx; | ||
| } | ||
| unsetMasterMigration() { | ||
| this.#masterTransaction = undefined; | ||
| } | ||
| /** | ||
| * @internal | ||
| */ | ||
| getMigrationName(name) { | ||
| return name.replace(/\.[jt]s$/, ''); | ||
| } | ||
| /** | ||
| * @internal | ||
| */ | ||
| getTableName() { | ||
| const parts = this.options.tableName.split('.'); | ||
| const tableName = parts.length > 1 ? parts[1] : parts[0]; | ||
| const schemaName = | ||
| parts.length > 1 ? parts[0] : this.driver.config.get('schema', this.driver.getPlatform().getDefaultSchemaName()); | ||
| const entity = defineEntity({ | ||
| name: 'Migration', | ||
| tableName, | ||
| schema: schemaName, | ||
| properties: { | ||
| id: p.integer().primary().fieldNames('id'), | ||
| name: p.string().fieldNames('name'), | ||
| executedAt: p.datetime().defaultRaw('current_timestamp').fieldNames('executed_at'), | ||
| }, | ||
| }).init(); | ||
| entity.meta.sync(); | ||
| return { tableName, schemaName, entity }; | ||
| } | ||
| } |
+42
-32
@@ -1,2 +0,9 @@ | ||
| import { type IMigrationGenerator, type IMigrationRunner, type IMigratorStorage, type MigrateOptions, type MigrationInfo, type MikroORM } from '@mikro-orm/core'; | ||
| import { | ||
| type IMigrationGenerator, | ||
| type IMigrationRunner, | ||
| type IMigratorStorage, | ||
| type MigrateOptions, | ||
| type MigrationInfo, | ||
| type MikroORM, | ||
| } from '@mikro-orm/core'; | ||
| import { AbstractMigrator } from '@mikro-orm/core/migrations'; | ||
@@ -8,33 +15,36 @@ import { type AbstractSqlDriver, DatabaseSchema, type EntityManager } from '@mikro-orm/sql'; | ||
| export declare class Migrator extends AbstractMigrator<AbstractSqlDriver> { | ||
| #private; | ||
| constructor(em: EntityManager); | ||
| static register(orm: MikroORM): void; | ||
| protected createRunner(): IMigrationRunner; | ||
| protected createStorage(): IMigratorStorage; | ||
| protected getDefaultGenerator(): IMigrationGenerator; | ||
| private getSnapshotPath; | ||
| protected init(): Promise<void>; | ||
| /** | ||
| * @inheritDoc | ||
| */ | ||
| create(path?: string, blank?: boolean, initial?: boolean, name?: string): Promise<MigrationResult>; | ||
| checkSchema(): Promise<boolean>; | ||
| /** | ||
| * @inheritDoc | ||
| */ | ||
| createInitial(path?: string, name?: string, blank?: boolean): Promise<MigrationResult>; | ||
| protected runMigrations(method: 'up' | 'down', options?: string | string[] | MigrateOptions): Promise<MigrationInfo[]>; | ||
| getStorage(): MigrationStorage; | ||
| /** | ||
| * Initial migration can be created only if: | ||
| * 1. no previous migrations were generated or executed | ||
| * 2. existing schema do not contain any of the tables defined by metadata | ||
| * | ||
| * If existing schema contains all of the tables already, we return true, based on that we mark the migration as already executed. | ||
| * If only some of the tables are present, exception is thrown. | ||
| */ | ||
| private validateInitialMigration; | ||
| protected getSchemaFromSnapshot(): Promise<DatabaseSchema | undefined>; | ||
| protected storeCurrentSchema(schema?: DatabaseSchema): Promise<void>; | ||
| private getSchemaDiff; | ||
| #private; | ||
| constructor(em: EntityManager); | ||
| static register(orm: MikroORM): void; | ||
| protected createRunner(): IMigrationRunner; | ||
| protected createStorage(): IMigratorStorage; | ||
| protected getDefaultGenerator(): IMigrationGenerator; | ||
| private getSnapshotPath; | ||
| protected init(): Promise<void>; | ||
| /** | ||
| * @inheritDoc | ||
| */ | ||
| create(path?: string, blank?: boolean, initial?: boolean, name?: string): Promise<MigrationResult>; | ||
| checkSchema(): Promise<boolean>; | ||
| /** | ||
| * @inheritDoc | ||
| */ | ||
| createInitial(path?: string, name?: string, blank?: boolean): Promise<MigrationResult>; | ||
| protected runMigrations( | ||
| method: 'up' | 'down', | ||
| options?: string | string[] | MigrateOptions, | ||
| ): Promise<MigrationInfo[]>; | ||
| getStorage(): MigrationStorage; | ||
| /** | ||
| * Initial migration can be created only if: | ||
| * 1. no previous migrations were generated or executed | ||
| * 2. existing schema do not contain any of the tables defined by metadata | ||
| * | ||
| * If existing schema contains all of the tables already, we return true, based on that we mark the migration as already executed. | ||
| * If only some of the tables are present, exception is thrown. | ||
| */ | ||
| private validateInitialMigration; | ||
| protected getSchemaFromSnapshot(): Promise<DatabaseSchema | undefined>; | ||
| protected storeCurrentSchema(schema?: DatabaseSchema): Promise<void>; | ||
| private getSchemaDiff; | ||
| } |
+237
-238
@@ -1,4 +0,4 @@ | ||
| import { t, Type, UnknownType, } from '@mikro-orm/core'; | ||
| import { t, Type, UnknownType } from '@mikro-orm/core'; | ||
| import { AbstractMigrator } from '@mikro-orm/core/migrations'; | ||
| import { DatabaseSchema, DatabaseTable, } from '@mikro-orm/sql'; | ||
| import { DatabaseSchema, DatabaseTable } from '@mikro-orm/sql'; | ||
| import { MigrationRunner } from './MigrationRunner.js'; | ||
@@ -10,257 +10,256 @@ import { MigrationStorage } from './MigrationStorage.js'; | ||
| export class Migrator extends AbstractMigrator { | ||
| #schemaGenerator; | ||
| #snapshotPath; | ||
| constructor(em) { | ||
| super(em); | ||
| this.#schemaGenerator = this.config.getExtension('@mikro-orm/schema-generator'); | ||
| #schemaGenerator; | ||
| #snapshotPath; | ||
| constructor(em) { | ||
| super(em); | ||
| this.#schemaGenerator = this.config.getExtension('@mikro-orm/schema-generator'); | ||
| } | ||
| 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); | ||
| } | ||
| static register(orm) { | ||
| orm.config.registerExtension('@mikro-orm/migrator', () => new Migrator(orm.em)); | ||
| 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().replace(/:/g, ''); | ||
| const snapshotName = this.options.snapshotName ?? `.snapshot-${dbName}`; | ||
| this.#snapshotPath = fs.normalizePath(absoluteSnapshotPath, `${snapshotName}.json`); | ||
| } | ||
| createRunner() { | ||
| return new MigrationRunner(this.driver, this.options, this.config); | ||
| return this.#snapshotPath; | ||
| } | ||
| async init() { | ||
| if (this.initialized) { | ||
| return; | ||
| } | ||
| createStorage() { | ||
| return new MigrationStorage(this.driver, this.options); | ||
| await super.init(); | ||
| const created = await this.#schemaGenerator.ensureDatabase(); | ||
| /* v8 ignore next */ | ||
| if (created) { | ||
| this.initServices(); | ||
| } | ||
| 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); | ||
| await this.storage.ensureTable(); | ||
| } | ||
| /** | ||
| * @inheritDoc | ||
| */ | ||
| async create(path, blank = false, initial = false, name) { | ||
| await this.init(); | ||
| if (initial) { | ||
| return this.createInitial(path, name, blank); | ||
| } | ||
| 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().replace(/:/g, ''); | ||
| const snapshotName = this.options.snapshotName ?? `.snapshot-${dbName}`; | ||
| this.#snapshotPath = fs.normalizePath(absoluteSnapshotPath, `${snapshotName}.json`); | ||
| } | ||
| return this.#snapshotPath; | ||
| const diff = await this.getSchemaDiff(blank, initial); | ||
| if (diff.up.length === 0) { | ||
| return { fileName: '', code: '', diff }; | ||
| } | ||
| async init() { | ||
| if (this.initialized) { | ||
| return; | ||
| } | ||
| await super.init(); | ||
| const created = await this.#schemaGenerator.ensureDatabase(); | ||
| /* v8 ignore next */ | ||
| if (created) { | ||
| this.initServices(); | ||
| } | ||
| await this.storage.ensureTable(); | ||
| const migration = await this.generator.generate(diff, path, name); | ||
| await this.storeCurrentSchema(); | ||
| return { | ||
| fileName: migration[1], | ||
| code: migration[0], | ||
| diff, | ||
| }; | ||
| } | ||
| async checkSchema() { | ||
| await this.init(); | ||
| const diff = await this.getSchemaDiff(false, false); | ||
| return diff.up.length > 0; | ||
| } | ||
| /** | ||
| * @inheritDoc | ||
| */ | ||
| async createInitial(path, name, blank = false) { | ||
| await this.init(); | ||
| const schemaExists = await this.validateInitialMigration(blank); | ||
| const diff = await this.getSchemaDiff(blank, true); | ||
| const migration = await this.generator.generate(diff, path, name); | ||
| await this.storeCurrentSchema(); | ||
| if (schemaExists && !blank) { | ||
| await this.storage.logMigration({ name: migration[1] }); | ||
| } | ||
| /** | ||
| * @inheritDoc | ||
| */ | ||
| async create(path, blank = false, initial = false, name) { | ||
| await this.init(); | ||
| if (initial) { | ||
| return this.createInitial(path, name, blank); | ||
| } | ||
| const diff = await this.getSchemaDiff(blank, initial); | ||
| if (diff.up.length === 0) { | ||
| return { fileName: '', code: '', diff }; | ||
| } | ||
| const migration = await this.generator.generate(diff, path, name); | ||
| await this.storeCurrentSchema(); | ||
| return { | ||
| fileName: migration[1], | ||
| code: migration[0], | ||
| diff, | ||
| }; | ||
| return { | ||
| fileName: migration[1], | ||
| code: migration[0], | ||
| diff, | ||
| }; | ||
| } | ||
| async runMigrations(method, options) { | ||
| const result = await super.runMigrations(method, options); | ||
| if (result.length > 0 && this.options.snapshot) { | ||
| const schema = await DatabaseSchema.create(this.em.getConnection(), this.em.getPlatform(), this.config); | ||
| try { | ||
| await this.storeCurrentSchema(schema); | ||
| } catch { | ||
| // Silently ignore for read-only filesystems (production). | ||
| } | ||
| } | ||
| async checkSchema() { | ||
| await this.init(); | ||
| const diff = await this.getSchemaDiff(false, false); | ||
| return diff.up.length > 0; | ||
| return result; | ||
| } | ||
| getStorage() { | ||
| return this.storage; | ||
| } | ||
| /** | ||
| * Initial migration can be created only if: | ||
| * 1. no previous migrations were generated or executed | ||
| * 2. existing schema do not contain any of the tables defined by metadata | ||
| * | ||
| * If existing schema contains all of the tables already, we return true, based on that we mark the migration as already executed. | ||
| * If only some of the tables are present, exception is thrown. | ||
| */ | ||
| async validateInitialMigration(blank) { | ||
| const executed = await this.getExecuted(); | ||
| const pending = await this.getPending(); | ||
| if (executed.length > 0 || pending.length > 0) { | ||
| throw new Error('Initial migration cannot be created, as some migrations already exist'); | ||
| } | ||
| /** | ||
| * @inheritDoc | ||
| */ | ||
| async createInitial(path, name, blank = false) { | ||
| await this.init(); | ||
| const schemaExists = await this.validateInitialMigration(blank); | ||
| const diff = await this.getSchemaDiff(blank, true); | ||
| const migration = await this.generator.generate(diff, path, name); | ||
| await this.storeCurrentSchema(); | ||
| if (schemaExists && !blank) { | ||
| await this.storage.logMigration({ name: migration[1] }); | ||
| } | ||
| return { | ||
| fileName: migration[1], | ||
| code: migration[0], | ||
| diff, | ||
| }; | ||
| const schema = await DatabaseSchema.create(this.em.getConnection(), this.em.getPlatform(), this.config); | ||
| const exists = new Set(); | ||
| const expected = new Set(); | ||
| [...this.em.getMetadata().getAll().values()] | ||
| .filter(meta => meta.tableName && !meta.embeddable && !meta.virtual) | ||
| .forEach(meta => { | ||
| const schema = meta.schema ?? this.config.get('schema', this.em.getPlatform().getDefaultSchemaName()); | ||
| expected.add(schema ? `${schema}.${meta.collection}` : meta.collection); | ||
| }); | ||
| schema.getTables().forEach(table => { | ||
| const schema = table.schema ?? this.em.getPlatform().getDefaultSchemaName(); | ||
| const tableName = schema ? `${schema}.${table.name}` : table.name; | ||
| if (expected.has(tableName)) { | ||
| exists.add(table.schema ? `${table.schema}.${table.name}` : table.name); | ||
| } | ||
| }); | ||
| if (expected.size === 0 && !blank) { | ||
| throw new Error('No entities found'); | ||
| } | ||
| async runMigrations(method, options) { | ||
| const result = await super.runMigrations(method, options); | ||
| if (result.length > 0 && this.options.snapshot) { | ||
| const schema = await DatabaseSchema.create(this.em.getConnection(), this.em.getPlatform(), this.config); | ||
| try { | ||
| await this.storeCurrentSchema(schema); | ||
| } | ||
| catch { | ||
| // Silently ignore for read-only filesystems (production). | ||
| } | ||
| } | ||
| return result; | ||
| if (exists.size > 0 && expected.size !== exists.size) { | ||
| throw new Error( | ||
| `Some tables already exist in your schema, remove them first to create the initial migration: ${[...exists].join(', ')}`, | ||
| ); | ||
| } | ||
| getStorage() { | ||
| return this.storage; | ||
| return expected.size === exists.size; | ||
| } | ||
| async getSchemaFromSnapshot() { | ||
| if (!this.options.snapshot) { | ||
| return undefined; | ||
| } | ||
| /** | ||
| * Initial migration can be created only if: | ||
| * 1. no previous migrations were generated or executed | ||
| * 2. existing schema do not contain any of the tables defined by metadata | ||
| * | ||
| * If existing schema contains all of the tables already, we return true, based on that we mark the migration as already executed. | ||
| * If only some of the tables are present, exception is thrown. | ||
| */ | ||
| async validateInitialMigration(blank) { | ||
| const executed = await this.getExecuted(); | ||
| const pending = await this.getPending(); | ||
| if (executed.length > 0 || pending.length > 0) { | ||
| throw new Error('Initial migration cannot be created, as some migrations already exist'); | ||
| } | ||
| const schema = await DatabaseSchema.create(this.em.getConnection(), this.em.getPlatform(), this.config); | ||
| const exists = new Set(); | ||
| const expected = new Set(); | ||
| [...this.em.getMetadata().getAll().values()] | ||
| .filter(meta => meta.tableName && !meta.embeddable && !meta.virtual) | ||
| .forEach(meta => { | ||
| const schema = meta.schema ?? this.config.get('schema', this.em.getPlatform().getDefaultSchemaName()); | ||
| expected.add(schema ? `${schema}.${meta.collection}` : meta.collection); | ||
| }); | ||
| schema.getTables().forEach(table => { | ||
| const schema = table.schema ?? this.em.getPlatform().getDefaultSchemaName(); | ||
| const tableName = schema ? `${schema}.${table.name}` : table.name; | ||
| if (expected.has(tableName)) { | ||
| exists.add(table.schema ? `${table.schema}.${table.name}` : table.name); | ||
| } | ||
| }); | ||
| if (expected.size === 0 && !blank) { | ||
| throw new Error('No entities found'); | ||
| } | ||
| if (exists.size > 0 && expected.size !== exists.size) { | ||
| throw new Error(`Some tables already exist in your schema, remove them first to create the initial migration: ${[...exists].join(', ')}`); | ||
| } | ||
| return expected.size === exists.size; | ||
| const snapshotPath = await this.getSnapshotPath(); | ||
| const { fs } = await import('@mikro-orm/core/fs-utils'); | ||
| if (!fs.pathExists(snapshotPath)) { | ||
| return undefined; | ||
| } | ||
| async getSchemaFromSnapshot() { | ||
| if (!this.options.snapshot) { | ||
| return undefined; | ||
| } | ||
| const snapshotPath = await this.getSnapshotPath(); | ||
| const { fs } = await import('@mikro-orm/core/fs-utils'); | ||
| if (!fs.pathExists(snapshotPath)) { | ||
| return undefined; | ||
| } | ||
| const data = fs.readJSONSync(snapshotPath); | ||
| const schema = new DatabaseSchema(this.driver.getPlatform(), this.config.get('schema')); | ||
| const { tables, namespaces, ...rest } = data; | ||
| const tableInstances = tables.map((tbl) => { | ||
| const table = new DatabaseTable(this.driver.getPlatform(), tbl.name, tbl.schema); | ||
| table.nativeEnums = tbl.nativeEnums ?? {}; | ||
| table.comment = tbl.comment; | ||
| if (tbl.indexes) { | ||
| table.setIndexes(tbl.indexes); | ||
| } | ||
| if (tbl.checks) { | ||
| table.setChecks(tbl.checks); | ||
| } | ||
| if (tbl.foreignKeys) { | ||
| table.setForeignKeys(tbl.foreignKeys); | ||
| } | ||
| const cols = tbl.columns; | ||
| Object.keys(cols).forEach(col => { | ||
| const column = { ...cols[col] }; | ||
| /* v8 ignore next */ | ||
| column.mappedType = Type.getType(t[cols[col].mappedType] ?? UnknownType); | ||
| table.addColumn(column); | ||
| }); | ||
| return table; | ||
| }); | ||
| schema.setTables(tableInstances); | ||
| schema.setNamespaces(new Set(namespaces)); | ||
| if (rest.nativeEnums) { | ||
| schema.setNativeEnums(rest.nativeEnums); | ||
| } | ||
| if (rest.views) { | ||
| schema.setViews(rest.views); | ||
| } | ||
| return schema; | ||
| const data = fs.readJSONSync(snapshotPath); | ||
| const schema = new DatabaseSchema(this.driver.getPlatform(), this.config.get('schema')); | ||
| const { tables, namespaces, ...rest } = data; | ||
| const tableInstances = tables.map(tbl => { | ||
| const table = new DatabaseTable(this.driver.getPlatform(), tbl.name, tbl.schema); | ||
| table.nativeEnums = tbl.nativeEnums ?? {}; | ||
| table.comment = tbl.comment; | ||
| if (tbl.indexes) { | ||
| table.setIndexes(tbl.indexes); | ||
| } | ||
| if (tbl.checks) { | ||
| table.setChecks(tbl.checks); | ||
| } | ||
| if (tbl.foreignKeys) { | ||
| table.setForeignKeys(tbl.foreignKeys); | ||
| } | ||
| const cols = tbl.columns; | ||
| Object.keys(cols).forEach(col => { | ||
| const column = { ...cols[col] }; | ||
| /* v8 ignore next */ | ||
| column.mappedType = Type.getType(t[cols[col].mappedType] ?? UnknownType); | ||
| table.addColumn(column); | ||
| }); | ||
| return table; | ||
| }); | ||
| schema.setTables(tableInstances); | ||
| schema.setNamespaces(new Set(namespaces)); | ||
| if (rest.nativeEnums) { | ||
| schema.setNativeEnums(rest.nativeEnums); | ||
| } | ||
| async storeCurrentSchema(schema) { | ||
| if (!this.options.snapshot) { | ||
| return; | ||
| } | ||
| const snapshotPath = await this.getSnapshotPath(); | ||
| schema ??= this.#schemaGenerator.getTargetSchema(); | ||
| const { fs } = await import('@mikro-orm/core/fs-utils'); | ||
| await fs.writeFile(snapshotPath, JSON.stringify(schema, null, 2)); | ||
| if (rest.views) { | ||
| schema.setViews(rest.views); | ||
| } | ||
| async getSchemaDiff(blank, initial) { | ||
| const up = []; | ||
| const down = []; | ||
| // Split SQL by statement boundaries (semicolons followed by newline) rather than | ||
| // just newlines, to preserve multiline statements like view definitions. | ||
| // Blank lines (from double newlines) are preserved as empty strings for grouping. | ||
| // Splits inside single-quoted string literals are re-merged (GH #7185). | ||
| const splitStatements = (sql) => { | ||
| const result = []; | ||
| let buf = ''; | ||
| for (const chunk of sql.split(/;\n/)) { | ||
| buf += (buf ? ';\n' : '') + chunk; | ||
| // odd number of single quotes means we're inside a string literal | ||
| if (buf.split(`'`).length % 2 === 0) { | ||
| continue; | ||
| } | ||
| // A chunk starting with \n indicates there was a blank line (grouping separator) | ||
| if (buf.startsWith('\n')) { | ||
| result.push(''); | ||
| } | ||
| const trimmed = buf.trim(); | ||
| if (trimmed) { | ||
| result.push(trimmed.endsWith(';') ? trimmed : trimmed + ';'); | ||
| } | ||
| buf = ''; | ||
| } | ||
| return result; | ||
| }; | ||
| if (blank) { | ||
| up.push('select 1'); | ||
| down.push('select 1'); | ||
| return schema; | ||
| } | ||
| async storeCurrentSchema(schema) { | ||
| if (!this.options.snapshot) { | ||
| return; | ||
| } | ||
| const snapshotPath = await this.getSnapshotPath(); | ||
| schema ??= this.#schemaGenerator.getTargetSchema(); | ||
| const { fs } = await import('@mikro-orm/core/fs-utils'); | ||
| await fs.writeFile(snapshotPath, JSON.stringify(schema, null, 2)); | ||
| } | ||
| async getSchemaDiff(blank, initial) { | ||
| const up = []; | ||
| const down = []; | ||
| // Split SQL by statement boundaries (semicolons followed by newline) rather than | ||
| // just newlines, to preserve multiline statements like view definitions. | ||
| // Blank lines (from double newlines) are preserved as empty strings for grouping. | ||
| // Splits inside single-quoted string literals are re-merged (GH #7185). | ||
| const splitStatements = sql => { | ||
| const result = []; | ||
| let buf = ''; | ||
| for (const chunk of sql.split(/;\n/)) { | ||
| buf += (buf ? ';\n' : '') + chunk; | ||
| // odd number of single quotes means we're inside a string literal | ||
| if (buf.split(`'`).length % 2 === 0) { | ||
| continue; | ||
| } | ||
| else if (initial) { | ||
| const dump = await this.#schemaGenerator.getCreateSchemaSQL({ wrap: false }); | ||
| up.push(...splitStatements(dump)); | ||
| // A chunk starting with \n indicates there was a blank line (grouping separator) | ||
| if (buf.startsWith('\n')) { | ||
| result.push(''); | ||
| } | ||
| else { | ||
| const diff = await this.#schemaGenerator.getUpdateSchemaMigrationSQL({ | ||
| wrap: false, | ||
| safe: this.options.safe, | ||
| dropTables: this.options.dropTables, | ||
| fromSchema: await this.getSchemaFromSnapshot(), | ||
| }); | ||
| up.push(...splitStatements(diff.up)); | ||
| down.push(...splitStatements(diff.down)); | ||
| const trimmed = buf.trim(); | ||
| if (trimmed) { | ||
| result.push(trimmed.endsWith(';') ? trimmed : trimmed + ';'); | ||
| } | ||
| const cleanUp = (diff) => { | ||
| for (let i = diff.length - 1; i >= 0; i--) { | ||
| if (diff[i]) { | ||
| break; | ||
| } | ||
| /* v8 ignore next */ | ||
| diff.splice(i, 1); | ||
| } | ||
| }; | ||
| cleanUp(up); | ||
| cleanUp(down); | ||
| return { up, down }; | ||
| buf = ''; | ||
| } | ||
| return result; | ||
| }; | ||
| if (blank) { | ||
| up.push('select 1'); | ||
| down.push('select 1'); | ||
| } else if (initial) { | ||
| const dump = await this.#schemaGenerator.getCreateSchemaSQL({ wrap: false }); | ||
| up.push(...splitStatements(dump)); | ||
| } else { | ||
| const diff = await this.#schemaGenerator.getUpdateSchemaMigrationSQL({ | ||
| wrap: false, | ||
| safe: this.options.safe, | ||
| dropTables: this.options.dropTables, | ||
| fromSchema: await this.getSchemaFromSnapshot(), | ||
| }); | ||
| up.push(...splitStatements(diff.up)); | ||
| down.push(...splitStatements(diff.down)); | ||
| } | ||
| const cleanUp = diff => { | ||
| for (let i = diff.length - 1; i >= 0; i--) { | ||
| if (diff[i]) { | ||
| break; | ||
| } | ||
| /* v8 ignore next */ | ||
| diff.splice(i, 1); | ||
| } | ||
| }; | ||
| cleanUp(up); | ||
| cleanUp(down); | ||
| return { up, down }; | ||
| } | ||
| } |
+4
-4
| { | ||
| "name": "@mikro-orm/migrations", | ||
| "version": "7.0.4-dev.14", | ||
| "version": "7.0.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.", | ||
@@ -50,9 +50,9 @@ "keywords": [ | ||
| "dependencies": { | ||
| "@mikro-orm/sql": "7.0.4-dev.14" | ||
| "@mikro-orm/sql": "7.0.4" | ||
| }, | ||
| "devDependencies": { | ||
| "@mikro-orm/core": "^7.0.3" | ||
| "@mikro-orm/core": "^7.0.4" | ||
| }, | ||
| "peerDependencies": { | ||
| "@mikro-orm/core": "7.0.4-dev.14" | ||
| "@mikro-orm/core": "7.0.4" | ||
| }, | ||
@@ -59,0 +59,0 @@ "engines": { |
+1
-1
@@ -136,3 +136,3 @@ <h1 align="center"> | ||
| author.name = 'Jon Snow II'; | ||
| author.books.getItems().forEach(book => book.title += ' (2nd ed.)'); | ||
| author.books.getItems().forEach(book => (book.title += ' (2nd ed.)')); | ||
| author.books.add(orm.em.create(Book, { title: 'New Book', author })); | ||
@@ -139,0 +139,0 @@ |
| import { MigrationGenerator } from './MigrationGenerator.js'; | ||
| /** Generates migration files in TypeScript format. */ | ||
| export declare class TSMigrationGenerator extends MigrationGenerator { | ||
| /** | ||
| * @inheritDoc | ||
| */ | ||
| generateMigrationFile(className: string, diff: { | ||
| up: string[]; | ||
| down: string[]; | ||
| }): string; | ||
| /** | ||
| * @inheritDoc | ||
| */ | ||
| generateMigrationFile( | ||
| className: string, | ||
| diff: { | ||
| up: string[]; | ||
| down: string[]; | ||
| }, | ||
| ): string; | ||
| } |
+16
-16
| import { MigrationGenerator } from './MigrationGenerator.js'; | ||
| /** Generates migration files in TypeScript format. */ | ||
| export class TSMigrationGenerator extends MigrationGenerator { | ||
| /** | ||
| * @inheritDoc | ||
| */ | ||
| generateMigrationFile(className, diff) { | ||
| let ret = `import { Migration } from '@mikro-orm/migrations';\n\n`; | ||
| ret += `export class ${className} extends Migration {\n\n`; | ||
| ret += ` override up(): void | Promise<void> {\n`; | ||
| diff.up.forEach(sql => (ret += this.createStatement(sql, 4))); | ||
| ret += ` }\n\n`; | ||
| if (diff.down.length > 0) { | ||
| ret += ` override down(): void | Promise<void> {\n`; | ||
| diff.down.forEach(sql => (ret += this.createStatement(sql, 4))); | ||
| ret += ` }\n\n`; | ||
| } | ||
| ret += `}\n`; | ||
| return ret; | ||
| /** | ||
| * @inheritDoc | ||
| */ | ||
| generateMigrationFile(className, diff) { | ||
| let ret = `import { Migration } from '@mikro-orm/migrations';\n\n`; | ||
| ret += `export class ${className} extends Migration {\n\n`; | ||
| ret += ` override up(): void | Promise<void> {\n`; | ||
| diff.up.forEach(sql => (ret += this.createStatement(sql, 4))); | ||
| ret += ` }\n\n`; | ||
| if (diff.down.length > 0) { | ||
| ret += ` override down(): void | Promise<void> {\n`; | ||
| diff.down.forEach(sql => (ret += this.createStatement(sql, 4))); | ||
| ret += ` }\n\n`; | ||
| } | ||
| ret += `}\n`; | ||
| return ret; | ||
| } | ||
| } |
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
783
4.82%0
-100%39576
-5.51%+ Added
+ Added
- Removed
- Removed
Updated