morpheus4j
Version:
Morpheus is a migration tool for Neo4j. It aims to be a simple and intuitive way to migrate your database.
107 lines • 4.86 kB
JavaScript
;
/* eslint-disable no-await-in-loop */
Object.defineProperty(exports, "__esModule", { value: true });
exports.MigrationService = void 0;
const constants_1 = require("../constants");
const errors_1 = require("../errors");
const types_1 = require("../types");
const logger_1 = require("./logger");
const utils_1 = require("./utils");
class MigrationService {
repository;
fileService;
latestVersion;
constructor(repository, fileService) {
this.repository = repository;
this.fileService = fileService;
}
async migrate(options) {
try {
let state = await this.repository.getMigrationState();
if (!state.baselineExists) {
await this.initializeDatabase();
// Re-fetch state
state = await this.repository.getMigrationState();
}
this.latestVersion = state.latestMigration?.version;
await this.validateMigrations(state.appliedMigrations);
const pendingMigrations = await this.getPendingMigrations();
if (pendingMigrations.length === 0) {
logger_1.Logger.info('Database is up to date');
return;
}
await (options?.dryRun
? this.previewMigrations(pendingMigrations)
: this.applyMigrations(pendingMigrations, options?.transactionMode));
}
catch (error) {
throw new errors_1.MigrationError(`Migration failed: ${error instanceof Error ? error.message : String(error)}`);
}
}
async applyMigrations(fileNames, transactionMode = types_1.TransactionMode.PER_MIGRATION) {
for (const fileName of fileNames) {
const migration = await this.fileService.prepareMigration(fileName);
logger_1.Logger.info(`Executing migration: ${fileName}`);
const startTime = Date.now();
// Use the new executeQueries method
if (transactionMode === types_1.TransactionMode.PER_STATEMENT) {
for (const statement of migration.statements) {
await this.repository.executeQuery({ statement });
}
}
else {
await this.repository.executeQueries(migration.statements.map((statement) => ({ statement })));
}
const duration = Date.now() - startTime;
const migrationNode = {
checksum: (0, utils_1.generateChecksum)(migration.statements),
description: migration.description,
source: fileName,
type: 'CYPHER',
version: migration.version,
};
await this.repository.applyMigration(migrationNode, this.latestVersion, duration);
this.latestVersion = migration.version;
}
}
async getPendingMigrations() {
const files = await this.fileService.getFileNamesFromMigrationsFolder();
return files
.filter((fileName) => {
const version = this.fileService.getMigrationVersionFromFileName(fileName);
return this.latestVersion === constants_1.BASELINE || this.fileService.compareVersions(version, this.latestVersion) > 0;
})
.sort((a, b) => this.fileService.compareVersions(a, b));
}
async initializeDatabase() {
// Initialize schema first
await this.repository.initializeSchema();
// Then create baseline node
await this.repository.initializeBaseline();
logger_1.Logger.info('Initialized database with schema and baseline');
}
async previewMigrations(fileNames) {
logger_1.Logger.info('Dry run - no changes will be made to the database');
for (const fileName of fileNames) {
const migration = await this.fileService.prepareMigration(fileName);
logger_1.Logger.info(`Would execute migration: ${fileName}`);
logger_1.Logger.info('Statements:');
for (const stmt of migration.statements) {
logger_1.Logger.info(stmt);
}
}
logger_1.Logger.info(`Dry run complete - ${fileNames.length} migration(s) pending`);
}
async validateMigrations(migrations) {
for (const migration of migrations) {
const { statements } = await this.fileService.prepareMigration(migration.node.source);
const currentChecksum = (0, utils_1.generateChecksum)(statements);
if (currentChecksum !== migration.node.checksum) {
throw new errors_1.MigrationError(`Checksum mismatch for ${migration.node.source}. ` +
'The migration file has been modified after it was applied.');
}
}
}
}
exports.MigrationService = MigrationService;
//# sourceMappingURL=migrate.service.js.map