You're Invited:Meet the Socket Team at RSAC and BSidesSF 2026, March 23–26.RSVP
Socket
Book a DemoSign in
Socket

@mikro-orm/migrations

Package Overview
Dependencies
Maintainers
1
Versions
4197
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@mikro-orm/migrations - npm Package Compare versions

Comparing version
7.0.4-dev.14
to
7.0.4
+10
-7
JSMigrationGenerator.d.ts
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;
}
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;
}
}

@@ -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[];
}
/** 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;
}
}

@@ -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>;
}
/** 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;
}
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;
}
}

@@ -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;
};
}
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 };
}
}

@@ -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 };
}
}
{
"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": {

@@ -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;
}
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;
}
}