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