UNPKG

mgdb-migrator

Version:
308 lines 12.5 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Migrator = void 0; const node_assert_1 = __importDefault(require("node:assert")); const _ = __importStar(require("lodash")); const mongodb_1 = require("mongodb"); class Migrator { constructor(opts) { this.migratorKey = 'control'; this.defaultMigration = { down: (_db) => Promise.reject(`Can't go down from default`), name: 'default', up: (_db) => Promise.resolve(), version: 0, }; this.list = [this.defaultMigration]; this.options = opts ? opts : { collectionName: 'migrations', db: null, log: true, logIfLatest: true, logger: null, }; } config(opts) { return __awaiter(this, void 0, void 0, function* () { this.options = Object.assign({}, this.options, opts); if (!this.options.logger && this.options.log) { this.options.logger = (level, ...args) => console.log(level, ...args); } if (this.options.log === false) { this.options.logger = (_level, ..._args) => { return; }; } let db = this.options.db || this.db; if (!db) { throw new ReferenceError('db option must be defined'); } if (db.connectionUrl) { const dbProps = db; const options = Object.assign({}, dbProps.options); const client = yield mongodb_1.MongoClient.connect(dbProps.connectionUrl, options); db = client.db(dbProps.name); } this.collection = db.collection(this.options.collectionName); this.db = db; }); } add(migration) { if (typeof migration.up !== 'function') { throw new Error('Migration must supply an up function.'); } if (typeof migration.down !== 'function') { throw new Error('Migration must supply a down function.'); } if (typeof migration.version !== 'number') { throw new Error('Migration must supply a version number.'); } if (migration.version <= 0) { throw new Error('Migration version must be greater than 0'); } Object.freeze(migration); this.list.push(migration); this.list = _.sortBy(this.list, (m) => m.version); } migrateTo(command) { return __awaiter(this, void 0, void 0, function* () { if (!this.db) { throw new Error('Migration instance has not be configured/initialized.' + ' Call <instance>.config(..) to initialize this instance'); } if (_.isUndefined(command) || command === '' || this.list.length === 0) { throw new Error('Cannot migrate using invalid command: ' + command); } let version; let subcommand; if (typeof command === 'number') { version = command; } else { version = command.split(',')[0]; subcommand = command.split(',')[1]; } try { if (version === 'latest') { yield this.execute(_.last(this.list).version); } else { yield this.execute(parseInt(version, null), subcommand === 'rerun'); } } catch (e) { this.options.logger('info', `Encountered an error while migrating. Migration failed.`); throw e; } }); } getNumberOfMigrations() { return this.list.length - 1; } getVersion() { return __awaiter(this, void 0, void 0, function* () { const control = yield this.getControl(); return control.version; }); } unlock() { this.collection.updateOne({ _id: this.migratorKey }, { $set: { locked: false } }); } reset() { return __awaiter(this, void 0, void 0, function* () { this.list = [this.defaultMigration]; yield this.collection.deleteMany({}); }); } execute(version, rerun) { return __awaiter(this, void 0, void 0, function* () { const self = this; const control = yield this.getControl(); let currentVersion = control.version; const lock = () => __awaiter(this, void 0, void 0, function* () { const updateResult = yield self.collection.findOneAndUpdate({ _id: this.migratorKey, locked: false, }, { $set: { locked: true, lockedAt: new Date(), }, }, { includeResultMetadata: true }); return null != updateResult.value && 1 === updateResult.ok; }); const unlock = () => self.setControl({ locked: false, version: currentVersion, }); const updateVersion = () => __awaiter(this, void 0, void 0, function* () { return yield self.setControl({ locked: true, version: currentVersion, }); }); const migrate = (direction, idx) => __awaiter(this, void 0, void 0, function* () { const migration = self.list[idx]; if (typeof migration[direction] !== 'function') { unlock(); throw new Error('Cannot migrate ' + direction + ' on version ' + migration.version); } function maybeName() { return migration.name ? ' (' + migration.name + ')' : ''; } this.options.logger('info', 'Running ' + direction + '() on version ' + migration.version + maybeName()); yield migration[direction](self.db, this.options.logger); }); if ((yield lock()) === false) { this.options.logger('info', 'Not migrating, control is locked.'); return; } if (rerun) { this.options.logger('info', 'Rerunning version ' + version); migrate('up', version); this.options.logger('info', 'Finished migrating.'); yield unlock(); return; } if (currentVersion === version) { if (this.options.logIfLatest) { this.options.logger('info', 'Not migrating, already at version ' + version); } yield unlock(); return; } const startIdx = this.findIndexByVersion(currentVersion); const endIdx = this.findIndexByVersion(version); this.options.logger('info', 'Migrating from version ' + this.list[startIdx].version + ' -> ' + this.list[endIdx].version); if (currentVersion < version) { for (let i = startIdx; i < endIdx; i++) { try { yield migrate('up', i + 1); currentVersion = self.list[i + 1].version; yield updateVersion(); } catch (e) { const prevVersion = self.list[i].version; const destVersion = self.list[i + 1].version; this.options.logger('error', `Encountered an error while migrating from ${prevVersion} to ${destVersion}`); throw e; } } } else { for (let i = startIdx; i > endIdx; i--) { try { yield migrate('down', i); currentVersion = self.list[i - 1].version; yield updateVersion(); } catch (e) { const prevVersion = self.list[i].version; const destVersion = self.list[i - 1].version; this.options.logger('error', `Encountered an error while migrating from ${prevVersion} to ${destVersion}`); throw e; } } } yield unlock(); this.options.logger('info', 'Finished migrating.'); }); } getControl() { return __awaiter(this, void 0, void 0, function* () { const con = yield this.collection.findOne({ _id: this.migratorKey }); return (con || (yield this.setControl({ locked: false, version: 0, }))); }); } setControl(control) { return __awaiter(this, void 0, void 0, function* () { (0, node_assert_1.default)(control && typeof control === 'object'); (0, node_assert_1.default)(typeof control.version === 'number'); (0, node_assert_1.default)(typeof control.locked === 'boolean'); const updateResult = yield this.collection.updateOne({ _id: this.migratorKey, }, { $set: { locked: control.locked, version: control.version, }, }, { upsert: true, }); if (updateResult && updateResult.acknowledged) { return control; } else { return null; } }); } findIndexByVersion(version) { for (let i = 0; i < this.list.length; i++) { if (this.list[i].version === version) { return i; } } throw new Error("Can't find migration version " + version); } } exports.Migrator = Migrator; //# sourceMappingURL=migrator.js.map