UNPKG

@resin/pinejs

Version:

Pine.js is a sophisticated rules-driven API engine that enables you to define rules in a structured subset of English. Those rules are used in order for Pine.js to generate a database schema and the associated [OData](http://www.odata.org/) API. This make

175 lines • 6.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.config = exports.run = exports.postRun = exports.MigrationError = void 0; const Bluebird = require("bluebird"); const _ = require("lodash"); const typed_error_1 = require("typed-error"); const env_1 = require("../config-loader/env"); const sbvrUtils = require("../sbvr-api/sbvr-utils"); const modelText = require('./migrations.sbvr'); class MigrationError extends typed_error_1.TypedError { } exports.MigrationError = MigrationError; const binds = (strings, ...bindNums) => strings .map((str, i) => { if (i === bindNums.length) { return str; } if (i + 1 !== bindNums[i]) { throw new SyntaxError('Migration sql binds must be sequential'); } if (sbvrUtils.db.engine === "postgres") { return str + `$${bindNums[i]}`; } return str + `?`; }) .join(''); exports.postRun = Bluebird.method(async (tx, model) => { var _a, _b; const { initSql } = model; if (initSql == null) { return; } const modelName = model.apiRoot; const exists = await checkModelAlreadyExists(tx, modelName); if (!exists) { ((_b = (_a = sbvrUtils.api.migrations) === null || _a === void 0 ? void 0 : _a.logger.info) !== null && _b !== void 0 ? _b : console.info)('First time executing, running init script'); await Bluebird.using(lockMigrations(tx, modelName), async () => { await tx.executeSql(initSql); }); } }); exports.run = Bluebird.method(async (tx, model) => { var _a, _b; const { migrations } = model; if (migrations == null || _.isEmpty(migrations)) { return; } const modelName = model.apiRoot; const exists = await checkModelAlreadyExists(tx, modelName); if (!exists) { ((_b = (_a = sbvrUtils.api.migrations) === null || _a === void 0 ? void 0 : _a.logger.info) !== null && _b !== void 0 ? _b : console.info)('First time model has executed, skipping migrations'); return setExecutedMigrations(tx, modelName, Object.keys(migrations)); } await Bluebird.using(lockMigrations(tx, modelName), async () => { const executedMigrations = await getExecutedMigrations(tx, modelName); const pendingMigrations = filterAndSortPendingMigrations(migrations, executedMigrations); if (pendingMigrations.length === 0) { return; } const newlyExecutedMigrations = await executeMigrations(tx, pendingMigrations); return setExecutedMigrations(tx, modelName, [ ...executedMigrations, ...newlyExecutedMigrations, ]); }); }); const checkModelAlreadyExists = Bluebird.method(async (tx, modelName) => { const result = await tx.tableList("name = 'migration'"); if (result.rows.length === 0) { return false; } const { rows } = await tx.executeSql(binds ` SELECT 1 FROM "model" WHERE "model"."is of-vocabulary" = ${1} LIMIT 1`, [modelName]); return rows.length > 0; }); const getExecutedMigrations = async (tx, modelName) => { const { rows } = await tx.executeSql(binds ` SELECT "migration"."executed migrations" AS "executed_migrations" FROM "migration" WHERE "migration"."model name" = ${1}`, [modelName]); const data = rows[0]; if (data == null) { return []; } return JSON.parse(data.executed_migrations); }; const setExecutedMigrations = async (tx, modelName, executedMigrations) => { const stringifiedMigrations = JSON.stringify(executedMigrations); const result = await tx.tableList("name = 'migration'"); if (result.rows.length === 0) { return; } const { rowsAffected } = await tx.executeSql(binds ` UPDATE "migration" SET "model name" = ${1}, "executed migrations" = ${2} WHERE "migration"."model name" = ${3}`, [modelName, stringifiedMigrations, modelName]); if (rowsAffected === 0) { tx.executeSql(binds ` INSERT INTO "migration" ("model name", "executed migrations") VALUES (${1}, ${2})`, [modelName, stringifiedMigrations]); } }; const filterAndSortPendingMigrations = (migrations, executedMigrations) => _(migrations).omit(executedMigrations) .toPairs() .sortBy(([migrationKey]) => migrationKey) .value(); const lockMigrations = (tx, modelName) => Bluebird.try(async () => { try { await tx.executeSql(binds ` DELETE FROM "migration lock" WHERE "model name" = ${1} AND "created at" < ${2}`, [modelName, new Date(Date.now() - env_1.migrator.lockTimeout)]); await tx.executeSql(binds ` INSERT INTO "migration lock" ("model name") VALUES (${1})`, [modelName]); } catch (err) { await Bluebird.delay(env_1.migrator.lockFailDelay); throw err; } }).disposer(async () => { await tx.executeSql(binds ` DELETE FROM "migration lock" WHERE "model name" = ${1}`, [modelName]); }); const executeMigrations = async (tx, migrations = []) => { var _a, _b; try { for (const migration of migrations) { await executeMigration(tx, migration); } } catch (err) { ((_b = (_a = sbvrUtils.api.migrations) === null || _a === void 0 ? void 0 : _a.logger.error) !== null && _b !== void 0 ? _b : console.error)('Error while executing migrations, rolled back'); throw new MigrationError(err); } return migrations.map(([migrationKey]) => migrationKey); }; const executeMigration = async (tx, [key, migration]) => { var _a, _b; ((_b = (_a = sbvrUtils.api.migrations) === null || _a === void 0 ? void 0 : _a.logger.info) !== null && _b !== void 0 ? _b : console.info)(`Running migration ${JSON.stringify(key)}`); if (typeof migration === 'function') { await migration(tx, sbvrUtils); } else if (typeof migration === 'string') { await tx.executeSql(migration); } else { throw new MigrationError(`Invalid migration type: ${typeof migration}`); } }; exports.config = { models: [ { modelName: 'migrations', apiRoot: 'migrations', modelText, migrations: { '11.0.0-modified-at': ` ALTER TABLE "migration" ADD COLUMN IF NOT EXISTS "modified at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL; `, '11.0.1-modified-at': ` ALTER TABLE "migration lock" ADD COLUMN IF NOT EXISTS "modified at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL; `, }, }, ], }; //# sourceMappingURL=migrator.js.map