@coko/server
Version:
Reusable server for use by Coko's projects
191 lines • 8.27 kB
JavaScript
;
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 __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const fs_extra_1 = __importDefault(require("fs-extra"));
const path_1 = __importDefault(require("path"));
const glob_1 = require("glob");
const umzug_1 = require("umzug");
const sortBy_1 = __importDefault(require("lodash/sortBy"));
const isFunction_1 = __importDefault(require("lodash/isFunction"));
const internals_1 = __importDefault(require("../logger/internals"));
const db_1 = __importDefault(require("./db"));
const migrateDbHelpers_1 = require("./migrateDbHelpers");
const MigrationResolverRulesError_1 = __importDefault(require("./errors/MigrationResolverRulesError"));
class MigrationRunner {
pattern;
threshold;
umzug;
constructor(components = []) {
const migrationPaths = components
.map(c => {
if (c.startsWith('.'))
return path_1.default.join(process.cwd(), c);
return path_1.default.dirname(require.resolve(c));
})
.map(modulePath => path_1.default.join(modulePath, 'migrations'))
.concat(path_1.default.join(__dirname, 'coreMigrations'))
.filter(fs_extra_1.default.pathExistsSync);
const globPatterns = migrationPaths.map(migrationPath => `${migrationPath}/*.{js,ts,sql}`);
if (globPatterns.length === 1) {
this.pattern = globPatterns[0];
}
else {
this.pattern = `{${globPatterns.join(',')}}`;
}
}
static stripMigrationExtensionName(filename) {
const filenameData = path_1.default.parse(filename);
if (['.js', '.ts', '.sql'].includes(filenameData.ext)) {
return filenameData.name;
}
return filename;
}
/**
* The threshold represents from which point in time forward the rules will
* apply (the creation of the meta table, ie. from the moment they upgraded to
* coko server v4).
*/
static async findThreshold() {
const metaExists = await migrateDbHelpers_1.migrationsMeta.exists();
if (!metaExists)
return null;
const data = await migrateDbHelpers_1.migrationsMeta.getData();
const createdDateAsUnixTimestamp = Math.floor(new Date(data.created).getTime() / 1000);
return createdDateAsUnixTimestamp;
}
/**
* After installing v4, some rules will apply for migrations, but only for new
* migrations, so that developers don't have to rewrite all existing migrations.
*/
async parseMigration(filePath) {
const name = path_1.default.parse(filePath).name;
const isSql = path_1.default.extname(filePath) === '.sql';
const timestamp = parseInt(name.split('-')[0], 10);
const isPastThreshold = this.threshold && timestamp > this.threshold;
/**
* Any positive integer is a valid unix timestamp, might wanna switch to umzug's filename convention further down the line for robustness.
* The current setup will work as long as the date is not less 1000000000 (some time in 2001).
*/
const isUnixTimestamp = Number.isInteger(timestamp) &&
timestamp >= 1000000000 &&
timestamp <= 9999999999;
if (!isUnixTimestamp) {
throw new MigrationResolverRulesError_1.default(`Migration files must start with a unix timestamp larger than 1000000000, followed by a dash (-)`, name);
}
if (isSql) {
if (isPastThreshold) {
throw new MigrationResolverRulesError_1.default(`Migration files must be js or ts files. Use knex.raw if you need to write sql code`, name);
}
return {
name,
path: filePath,
up: async () => {
const fileContents = fs_extra_1.default.readFileSync(filePath).toString();
return db_1.default.raw(fileContents);
},
};
}
const { up, down } = await Promise.resolve(`${filePath}`).then(s => __importStar(require(s)));
if (isPastThreshold) {
if (!down || !(0, isFunction_1.default)(down)) {
throw new MigrationResolverRulesError_1.default(`All migrations need to define a down function so that the migration can be rolled back`, name);
}
}
return {
name,
path: filePath,
up: async () => up(db_1.default),
down: async () => down(db_1.default),
};
}
async init() {
this.threshold = await MigrationRunner.findThreshold();
const allFiles = await (0, glob_1.glob)(this.pattern);
const files = allFiles.filter(file => !file.endsWith('.d.ts'));
const migrations = await Promise.all(files.map(async (filePath) => this.parseMigration(filePath)));
const sortedMigrations = (0, sortBy_1.default)(migrations, 'name');
const customStorage = {
logMigration: async (migration) => {
await migrateDbHelpers_1.migrations.logMigration(migration.name);
},
unlogMigration: async (migration) => {
await migrateDbHelpers_1.migrations.unlogMigration(migration.name);
},
executed: async () => {
await migrateDbHelpers_1.migrations.createTable();
const rows = await migrateDbHelpers_1.migrations.getRows();
return rows.map(row => MigrationRunner.stripMigrationExtensionName(row.id));
},
};
this.umzug = new umzug_1.Umzug({
storage: customStorage,
logger: undefined,
migrations: sortedMigrations,
});
// this.umzug.on('migrating', e => {
// internalLogger.wait(`Migrating ${e.name}`)
// })
this.umzug.on('migrated', e => {
internals_1.default.success(`Successfully migrated ${e.name}`);
});
// this.umzug.on('reverting', e => internalLogger.wait(`Reverting ${e.name}`))
this.umzug.on('reverted', e => {
internals_1.default.success(`Successfully reverted ${e.name}`);
});
}
async up(options) {
if (options.to) {
options.to = MigrationRunner.stripMigrationExtensionName(options.to);
}
await this.umzug.up(options);
}
async down(options) {
if (options.to) {
options.to = MigrationRunner.stripMigrationExtensionName(options.to);
}
await this.umzug.down(options);
}
async pending() {
return await this.umzug.pending();
}
async executed() {
return await this.umzug.executed();
}
}
exports.default = MigrationRunner;
//# sourceMappingURL=migrationRunner.js.map