claire-framework
Version:
- được viết bằng TypeScript - hỗ trợ websocket và HTTP request - hỗ trợ CLI để generate base project (claire-cli)
820 lines • 86.3 kB
JavaScript
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
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 });
//-- force postgres to parse int
require('pg').defaults.parseInt8 = true;
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const child_process_1 = require("child_process");
const sequelize_1 = require("sequelize");
const Utils_1 = require("../system/Utils");
const ModelMetadata_1 = require("../model/ModelMetadata");
const AbstractQuery_1 = require("../model/AbstractQuery");
const ClaireError_1 = require("../system/ClaireError");
const errors_1 = require("../system/errors");
const QueryOperator_1 = require("../model/QueryOperator");
const __1 = require("..");
const __2 = require("..");
const AbstractLogger_1 = require("../logger/AbstractLogger");
const FieldValidationMetadata_1 = require("../validator/FieldValidationMetadata");
const DataType_1 = require("../enum/DataType");
class ModelAdapter extends AbstractQuery_1.AbstractQuery {
constructor(connection, model, metadata, schema, transaction) {
super(model, metadata);
this.currentSchema = schema;
this.connection = connection;
this.transaction = transaction;
}
updateConditionWithOptions(conditions, options) {
if (options) {
if (options.order) {
//-- convert order to data object
// @ts-ignore
const dataOrder = Utils_1.Utils.getCleanObject(this.convertToDataObjects([options.order])[0]);
Object.assign(conditions, { order: Object.keys(dataOrder).map((key) => ([key, dataOrder[key]])) });
}
if (options.limit) {
conditions = Object.assign(conditions, { limit: options.limit });
if (options.page) {
conditions = Object.assign(conditions, { offset: options.limit * (options.page - 1) });
}
}
if (options.projection) {
Object.assign(conditions, {
attributes: this.getProjectionFields(options.projection),
});
}
}
}
saveOne(modelInstance) {
return __awaiter(this, void 0, void 0, function* () {
let result = yield this.saveMany([modelInstance]);
return result[0];
});
}
saveMany(modelInstances) {
return __awaiter(this, void 0, void 0, function* () {
let primaryKey = this.currentMetadata.getPrimaryKey();
let dataObjects = this.convertToDataObjects(modelInstances);
const defaultDataValues = this.convertToDataObjects([this.currentMetadata.getDefaultLogicValues()])[0];
dataObjects = dataObjects.map((obj) => (Object.assign(Object.assign({}, defaultDataValues), Utils_1.Utils.getCleanObject(obj))));
//-- use sequelize bulkCreate function
let instances = yield this.currentSchema.modelConnection.bulkCreate(dataObjects, { transaction: this.transaction });
if (instances.length !== modelInstances.length) {
throw new ClaireError_1.ClaireError(errors_1.BULK_OPERATION_ERROR);
}
let result = [];
if (primaryKey.isAutoIncrement) {
for (let i = 0; i < instances.length; i++) {
result.push(Object.assign(this.convertToLogicObjects([instances[i]])[0], { [primaryKey.fieldLogicName]: instances[i][primaryKey.fieldDataName] }));
}
}
else {
for (let i = 0; i < instances.length; i++) {
result.push(Object.assign(this.convertToLogicObjects([instances[i]])[0]));
}
}
// @ts-ignore
return result;
});
}
getOne(queries, options) {
return __awaiter(this, void 0, void 0, function* () {
let conditions = {
where: this.getQueryObjectFromQueryConditions(queries || []),
transaction: this.transaction,
};
this.updateConditionWithOptions(conditions, options);
let instance = yield this.currentSchema.modelConnection.findOne(conditions);
if (!instance) {
return undefined;
}
return this.convertToLogicObjects([instance], options && options.projection)[0];
});
}
getMany(queries, options) {
return __awaiter(this, void 0, void 0, function* () {
let conditions = {
where: this.getQueryObjectFromQueryConditions(queries),
transaction: this.transaction
};
this.updateConditionWithOptions(conditions, options);
let instances = yield this.currentSchema.modelConnection.findAll(conditions);
return this.convertToLogicObjects(instances, options && options.projection);
});
}
deleteOne(modelInstance) {
return __awaiter(this, void 0, void 0, function* () {
let primaryKey = this.currentMetadata.getPrimaryKey();
try {
// @ts-ignore
yield this.deleteMany([{ [primaryKey.fieldDataName]: QueryOperator_1.eq(modelInstance[primaryKey.fieldDataName]) }]);
return modelInstance;
}
catch (err) {
throw new ClaireError_1.ClaireError(errors_1.QUERY_ERROR, err.stack || String(err));
}
});
}
deleteMany(queries) {
return __awaiter(this, void 0, void 0, function* () {
try {
return yield this.currentSchema.modelConnection.destroy({
where: this.getQueryObjectFromQueryConditions(queries),
transaction: this.transaction
});
}
catch (err) {
throw new ClaireError_1.ClaireError(errors_1.QUERY_ERROR, err.stack || String(err));
}
});
}
updateOne(modelInstance) {
return __awaiter(this, void 0, void 0, function* () {
try {
yield this.currentSchema.modelConnection.update(this.convertToDataObjects([modelInstance])[0], {
// @ts-ignore
where: this.getQueryObjectFromQueryConditions([{ [this.currentMetadata.getPrimaryKey().fieldDataName]: QueryOperator_1.eq(modelInstance[this.currentMetadata.getPrimaryKey().fieldLogicName]) }]),
transaction: this.transaction
});
return modelInstance;
}
catch (err) {
throw new ClaireError_1.ClaireError(errors_1.QUERY_ERROR, err.stack || String(err));
}
});
}
updateMany(queries, update) {
return __awaiter(this, void 0, void 0, function* () {
//-- remove undefined fields from update because sequelize treat undefined as null
try {
let queryObject = this.getQueryObjectFromQueryConditions(queries);
let dataUpdate = this.convertToDataObjects([update])[0];
let result = yield this.currentSchema.modelConnection.update(dataUpdate, {
where: queryObject,
transaction: this.transaction
});
if (result[0] === 0) {
//-- may the updated value does not update anything
let result = yield this.currentSchema.modelConnection.findAndCountAll({
where: Object.assign({}, dataUpdate, queryObject),
transaction: this.transaction
});
return result.count;
}
return result[0];
}
catch (err) {
throw new ClaireError_1.ClaireError(errors_1.QUERY_ERROR, err.stack || String(err));
}
});
}
rawQuery(query) {
return new Promise((resolve) => {
this.connection.query(query, { transaction: this.transaction }).then(([results, metadata]) => {
return resolve([results, metadata]);
});
});
}
getQueryObjectFromQueryConditions(queries) {
if (!queries || !queries.length) {
return {};
}
const orQueries = [];
for (const query of queries) {
let result = {};
Object.keys(query).forEach((key) => {
// @ts-ignore
let operator = query[key];
let dataKey = this.currentMetadata.getFieldByLogicName(key).fieldDataName;
switch (operator.operator) {
case QueryOperator_1.Operator.COMMON_EQUALITY:
result[dataKey] = { [sequelize_1.Op.eq]: operator.value };
break;
case QueryOperator_1.Operator.COMMON_INEQUALITY:
result[dataKey] = { [sequelize_1.Op.ne]: operator.value };
break;
case QueryOperator_1.Operator.COMMON_BELONG:
if (operator.value.length === 1 && operator.value[0] === null) {
result[dataKey] = { [sequelize_1.Op.is]: null };
}
else {
result[dataKey] = { [sequelize_1.Op.in]: operator.value };
}
break;
case QueryOperator_1.Operator.STRING_CONTAIN:
result[dataKey] = { [sequelize_1.Op.contains]: operator.value };
break;
case QueryOperator_1.Operator.STRING_REGEX:
result[dataKey] = { [sequelize_1.Op.regexp]: operator.value };
break;
case QueryOperator_1.Operator.NUMBER_GT:
result[dataKey] = { [sequelize_1.Op.gt]: operator.value };
break;
case QueryOperator_1.Operator.NUMBER_GTE:
result[dataKey] = { [sequelize_1.Op.gte]: operator.value };
break;
case QueryOperator_1.Operator.NUMBER_LT:
result[dataKey] = { [sequelize_1.Op.lt]: operator.value };
break;
case QueryOperator_1.Operator.NUMBER_LTE:
result[dataKey] = { [sequelize_1.Op.lte]: operator.value };
break;
case QueryOperator_1.Operator.NUMBER_BETWEEN:
result[dataKey] = { [sequelize_1.Op.and]: [{ [sequelize_1.Op.gte]: operator.value.start }, { [sequelize_1.Op.lte]: operator.value.end }] };
break;
default:
throw new ClaireError_1.ClaireError(errors_1.OPERATOR_NOT_FOUND);
}
});
orQueries.push(result);
}
return { [sequelize_1.Op.or]: orQueries };
}
}
class TransactionAdapter {
constructor(transaction, connection, modelMetadata, schemas) {
this.modelAdapters = new Map();
this.transaction = transaction;
this.connection = connection;
this.modelMetadata = modelMetadata;
this.schemas = schemas;
}
use(model) {
let modelAdapter = this.modelAdapters.get(model.name);
if (!modelAdapter) {
modelAdapter = new ModelAdapter(this.connection, model, this.modelMetadata.find((meta) => meta.modelName === model.name), this.schemas[model.name], this.transaction);
this.modelAdapters.set(model.name, modelAdapter);
}
// @ts-ignore
return modelAdapter;
}
commit() {
return __awaiter(this, void 0, void 0, function* () {
return this.transaction.commit();
});
}
rollback() {
return __awaiter(this, void 0, void 0, function* () {
return this.transaction.rollback();
});
}
}
class DefaultSqlAdapter extends __1.AbstractDatabaseAdapter {
constructor(sqlProvider, connectionString, migrationDirectory) {
super();
this.connectionString = connectionString;
this.migrationDirectory = migrationDirectory;
switch (sqlProvider) {
case __1.SqlProvider.MY_SQL:
this.provider = "mysql";
break;
case __1.SqlProvider.POSTGRES:
this.provider = "postgres";
break;
default:
throw new ClaireError_1.ClaireError(errors_1.NOT_SUPPORTED);
}
this.databaseURL = `${this.provider}://${this.connectionString}`;
this.connection = new sequelize_1.Sequelize(this.databaseURL, {
logging: false,
define: {
timestamps: false
}
});
this.modelAdapters = new Map();
}
use(model) {
let modelAdapter = this.modelAdapters.get(model.name);
if (!modelAdapter) {
let modelMetaData = this.modelMetadata.find((meta) => meta.modelName === model.name);
if (!modelMetaData) {
throw new ClaireError_1.ClaireError(errors_1.MODEL_METADATA_NOT_FOUND, model.name);
}
modelAdapter = new ModelAdapter(this.connection, model, modelMetaData, this.schemas[model.name]);
this.modelAdapters.set(model.name, modelAdapter);
}
// @ts-ignore
return modelAdapter;
}
init() {
const _super = Object.create(null, {
init: { get: () => super.init }
});
return __awaiter(this, void 0, void 0, function* () {
yield _super.init.call(this);
this.logger.debug("Init database adapter");
//-- create db if not existed
this.logger.debug("Create database if not exist...");
yield new Promise((resolve, reject) => {
child_process_1.exec(`${path_1.default.join(__dirname, DefaultSqlAdapter.SEQUELIZE_CLI_PATH, ".bin/sequelize")} --url=${this.databaseURL} db:create`, { env: process.env }, (err) => {
if (err) {
if (String(err).indexOf("already exists") >= 0) {
return resolve();
}
else {
return reject(new ClaireError_1.ClaireError("CANNOT_CREATE_DB", String(err)));
}
}
return resolve();
});
});
//-- connect and authenticate
yield new Promise((resolve, reject) => {
this.connection.authenticate()
.then(() => {
return resolve();
})
.catch((err) => {
return reject(new ClaireError_1.ClaireError("DATABASE_CONNECTION_FAILED", err.stack || String(err)));
});
});
//-- doing migration
if (!!this.migrationDirectory) {
this.logger.debug("Running migration...");
yield this.runMigration();
}
this.logger.debug("Generating in-memory schema");
//-- inject modelConnection
this.modelMetadata.forEach((metadata) => {
this.schemas[metadata.modelName].modelConnection = DefaultSqlAdapter.generateSchemaObject(this.connection, metadata);
});
this.logger.debug("Database adapter init succeeded");
});
}
stop() {
const _super = Object.create(null, {
stop: { get: () => super.stop }
});
return __awaiter(this, void 0, void 0, function* () {
this.logger.debug("Disconnecting from database...");
yield this.connection.close();
yield _super.stop.call(this);
});
}
runMigration() {
return __awaiter(this, void 0, void 0, function* () {
let prototypeFilePath = path_1.default.join(this.migrationDirectory, "metadata.json");
let migrationDir = path_1.default.join(this.migrationDirectory, "scripts");
if (!fs_1.default.existsSync(migrationDir)) {
this.logger.debug("Migration directory not exists, creating empty folder");
fs_1.default.mkdirSync(migrationDir, { recursive: true });
}
let fileExists = fs_1.default.existsSync(prototypeFilePath);
if (!fileExists) {
//-- write default empty array of metadata
this.logger.debug("Migration metadata does not exist, creating one");
fs_1.default.writeFileSync(prototypeFilePath, JSON.stringify([]));
}
//-- parse model metadata from json file
this.logger.debug("Parsing migration metadata file");
let oldMetadataJSON = JSON.parse(String(fs_1.default.readFileSync(prototypeFilePath)));
let oldMetadata = [];
oldMetadataJSON.forEach((jsonData) => {
let modelMetaData = new ModelMetadata_1.ModelMetadata();
modelMetaData.modelName = jsonData["modelName"];
modelMetaData.tableName = jsonData["tableName"];
jsonData["fields"].forEach((field) => {
let fieldMetaData = new FieldValidationMetadata_1.FieldValidationMetadata();
fieldMetaData.fieldDataName = field["fieldDataName"];
fieldMetaData.isPrimaryKey = field["isPrimaryKey"];
fieldMetaData.isForeignKey = field["isForeignKey"];
fieldMetaData.isAutoIncrement = field["isAutoIncrement"];
fieldMetaData.isUnique = field["isUnique"];
fieldMetaData.referModel = field["referModel"];
fieldMetaData.innerDataType = field["innerDataType"];
fieldMetaData.nullable = field["nullable"];
fieldMetaData.defaultValue = field["defaultValue"];
modelMetaData.addField(fieldMetaData);
});
oldMetadata.push(modelMetaData);
});
//-- calculate differences between two array of prototypes
this.logger.debug("Calculating differences");
let differences = [];
this.modelMetadata.forEach((metadata) => {
let matchedMetadata = oldMetadata.find((m) => m.modelName === metadata.modelName);
differences.push(DefaultSqlAdapter.getDifferences(matchedMetadata, metadata));
});
oldMetadata.forEach((metadata) => {
let matchedMetadata = this.modelMetadata.find((m) => m.modelName === metadata.modelName);
if (!matchedMetadata) {
differences.push(DefaultSqlAdapter.getDifferences(metadata, undefined));
}
});
//-- generating scripts from difference
let scripts = [];
for (let i = 0; i < differences.length; i++) {
let upTableQueries = [];
let downTableQueries = [];
let upConstraintQueries = [];
let downConstraintQueries = [];
let diff = differences[i];
if (diff.removed && diff.removed.modelName) {
let currentModelName = diff.removed.modelName;
let currentModel = this.modelMetadata.find((meta) => meta.modelName === currentModelName);
//-- check whether to remove entire table or just the column
if (!diff.added) {
//-- remove entire table !! no more support auto removing table, please remove it manually
// upTableQueries.push(`queryInterface.dropTable('${currentModel && currentModel.tableName}',{}),\n`);
}
else {
//-- check to remove or update column
let oldFields = diff.removed.fields;
let newFields = diff.added.fields;
for (let j = 0; j < oldFields.length; j++) {
let oldField = oldFields[j];
let matchedField = newFields.find((f) => f.fieldDataName === oldField.fieldDataName);
if (!matchedField) {
//-- completely matched, remove field
upTableQueries.push(`queryInterface.removeColumn('${currentModel && currentModel.tableName}','${oldField.fieldDataName}',),\n`);
}
else {
if (oldField.isUnique === true) {
//-- remove unique constraint
upConstraintQueries.push(`queryInterface.removeConstraint('${currentModel && currentModel.tableName}','${diff.removed}_${oldField.fieldDataName}_un'),\n`);
downConstraintQueries.push(`queryInterface.addConstraint('${currentModel && currentModel.tableName}',['${oldField.fieldDataName}'],{type:'unique',name:'${currentModel && currentModel.tableName}_${oldField.fieldDataName}_un'}),\n`);
}
if (oldField.isForeignKey === true) {
let referModel = this.modelMetadata.find((meta) => meta.modelName === oldField.referModel);
//-- remove foreign key constraint
upConstraintQueries.push(`queryInterface.removeConstraint('${currentModel && currentModel.tableName}','${currentModel && currentModel.tableName}_${oldField.fieldDataName}_fk'),\n`);
downConstraintQueries.push(`queryInterface.addConstraint('${currentModel && currentModel.tableName}',['${oldField.fieldDataName}'],{type:'foreign key',name:'${currentModel && currentModel.tableName}_${oldField.fieldDataName}_fk',references:{table:'${referModel && referModel.tableName}',field:'${referModel && referModel.getPrimaryKey().fieldDataName}'}}),\n`);
}
}
}
}
}
if (diff.added && diff.added.modelName) {
let currentModelName = diff.added.modelName;
let currentModel = this.modelMetadata.find((meta) => meta.modelName === currentModelName);
let fieldsQuery = "";
let optionsQuery = "";
if (!diff.removed) {
//-- create new table
fieldsQuery += "{\n";
let tableFields = diff.added.fields;
for (let j = 0; j < tableFields.length; j++) {
let propertiesQuery = this.generatePropertiesQuery(tableFields[j]);
fieldsQuery += `${tableFields[j].fieldDataName}:${propertiesQuery},\n`;
}
fieldsQuery += "}\n";
//-- check options
optionsQuery += "{\n";
// let charset = diff.added.charset;
// switch (charset) {
// case Charset.UTF8:
optionsQuery += `charset:'utf8',`;
// break;
// case Charset.ASCII:
// optionsQuery += `charset:'ascii',`;
// break;
// default:
// break;
// }
optionsQuery += "\n}";
upTableQueries.push(`queryInterface.createTable('${currentModel && currentModel.tableName}',${fieldsQuery},${optionsQuery}),\n`);
downTableQueries.push(`queryInterface.dropTable('${currentModel && currentModel.tableName}',{}),\n`);
//-- check to add constraint
for (let j = 0; j < tableFields.length; j++) {
//-- check for unique constraint
if (tableFields[j].isUnique) {
upConstraintQueries.push(`queryInterface.addConstraint('${currentModel && currentModel.tableName}',['${tableFields[j].fieldDataName}'],{type:'unique',name:'${currentModel && currentModel.tableName}_${tableFields[j].fieldDataName}_un'}),\n`);
downConstraintQueries.push(`queryInterface.removeConstraint('${currentModel && currentModel.tableName}','${currentModel && currentModel.tableName}_${tableFields[j].fieldDataName}_un'),\n`);
}
if (tableFields[j].isForeignKey) {
let referModel = this.modelMetadata.find((m) => m.modelName === tableFields[j].referModel);
upConstraintQueries.push(`queryInterface.addConstraint('${currentModel && currentModel.tableName}',['${tableFields[j].fieldDataName}'],{type:'foreign key',name:'${currentModel && currentModel.tableName}_${tableFields[j].fieldDataName}_fk',references:{table:'${referModel && referModel.tableName}',field:'${referModel && referModel.getPrimaryKey().fieldDataName}'}}),\n`);
downConstraintQueries.push(`queryInterface.removeConstraint('${currentModel && currentModel.tableName}','${currentModel && currentModel.tableName}_${tableFields[j].fieldDataName}_fk'),\n`);
}
}
}
else {
//-- check to add or update column
let tableFields = diff.added.fields;
fieldsQuery += "{\n";
for (let j = 0; j < tableFields.length; j++) {
let field = tableFields[j];
let propertiesQuery = this.generatePropertiesQuery(field);
let oldMatchedField = diff.removed.getFieldByDataName(field.fieldDataName || "");
if (!oldMatchedField) {
//-- add field
upTableQueries.push(`queryInterface.addColumn('${currentModel && currentModel.tableName}','${field.fieldDataName}',${propertiesQuery}),\n`);
downTableQueries.push(`queryInterface.removeColumn('${currentModel && currentModel.tableName}','${field.fieldDataName}',),\n`);
}
else {
if (field.isUnique === true) {
//-- add unique constraint
upConstraintQueries.push(`queryInterface.addConstraint('${currentModel && currentModel.tableName}',['${field.fieldDataName}'],{type:'unique',name:'${currentModel && currentModel.tableName}_${field.fieldDataName}_un'}),\n`);
downConstraintQueries.push(`queryInterface.removeConstraint('${currentModel && currentModel.tableName}','${currentModel && currentModel.tableName}_${field.fieldDataName}_un'),\n`);
}
if (field.isForeignKey === true) {
//-- add foreign key constraint
let referModel = this.modelMetadata.find((meta) => meta.modelName === field.referModel);
upConstraintQueries.push(`queryInterface.addConstraint('${currentModel && currentModel.tableName}',['${field.fieldDataName}'],{type:'foreign key',name:'${currentModel && currentModel.tableName}_${field.fieldDataName}_fk',references:{table:'${referModel && referModel.tableName}',field:'${referModel && referModel.getPrimaryKey().fieldDataName}'}}),\n`);
downConstraintQueries.push(`queryInterface.removeConstraint('${currentModel && currentModel.tableName}','${currentModel && currentModel.tableName}_${field.fieldDataName}_fk'),\n`);
}
if (field.innerDataType !== undefined || field.nullable !== undefined) {
let currentField = currentModel.getFieldByDataName(field.fieldDataName);
let oldField = oldMetadata.find((m) => m.modelName === currentModel.modelName).getFieldByDataName(field.fieldDataName);
upTableQueries.push(`queryInterface.changeColumn('${currentModel && currentModel.tableName}','${field.fieldDataName}',${this.generatePropertiesQuery(Object.assign({}, currentField, field))}),\n`);
downTableQueries.push(`queryInterface.changeColumn('${currentModel && currentModel.tableName}','${field.fieldDataName}',${this.generatePropertiesQuery(oldField)}),\n`);
}
}
}
fieldsQuery += "\n}\n";
}
}
if (upTableQueries.length || downTableQueries.length || upConstraintQueries.length || downConstraintQueries.length) {
scripts.push({
upTable: upTableQueries,
downTable: downTableQueries,
upConstraint: upConstraintQueries,
downConstraint: downConstraintQueries
});
}
}
if (!scripts.length) {
//-- exec the migration
this.logger.debug("No difference, running migration");
yield new Promise((resolve, reject) => {
child_process_1.exec(`${path_1.default.join(__dirname, DefaultSqlAdapter.SEQUELIZE_CLI_PATH, ".bin/sequelize")} --url=${this.databaseURL} --migrations-path=${migrationDir} db:migrate`, { env: process.env }, (err) => {
if (err) {
return reject(new ClaireError_1.ClaireError("MIGRATION_FAILED", err.stack || String(err)));
}
return resolve();
});
});
return;
}
else {
//-- generate migration file
let migrationFileNames = [];
let migrationFunction = (scripts, index, migrationType) => __awaiter(this, void 0, void 0, function* () {
if (!scripts.up.length && !scripts.down.length)
return;
let generationResult = yield new Promise((resolve) => {
child_process_1.exec(`${path_1.default.join(__dirname, DefaultSqlAdapter.SEQUELIZE_CLI_PATH, ".bin/sequelize")} --migrations-path=${migrationDir} migration:generate --name auto-migration`, {}, (err, stdout) => {
if (err) {
return resolve(new ClaireError_1.ClaireError("INTERNAL_SYSTEM_ERROR", err.stack || String(err)));
}
return resolve(stdout);
});
});
if (generationResult instanceof ClaireError_1.ClaireError) {
return generationResult;
}
//-- use regex to capture generated file name
let regex = new RegExp(`[0-9]*-auto-migration.js`);
let fileName = regex.exec(generationResult);
if (fileName === null) {
return new ClaireError_1.ClaireError("MIGRATION_GENERATION_FAILED", generationResult);
}
//-- replace content of generated migration file
let migrationFilePath = path_1.default.join(migrationDir, fileName[0]);
//-- wait until the system populate the new file
this.logger.debug("Waiting for system to create migration file: ", migrationFilePath);
while (!fs_1.default.existsSync(migrationFilePath)) {
yield new Promise((resolve) => {
setTimeout(resolve, 1000);
});
}
let fileContent = String(fs_1.default.readFileSync(migrationFilePath));
let commentRegex = new RegExp(DefaultSqlAdapter.COMMENT_BLOCK_REGEX_PATTERN);
let upScript = "return [\n" + scripts.up.reduce((accumulator, e) => accumulator + e, "") + "]" + DefaultSqlAdapter.SERIAL_PROMISE;
let downScript = "return [\n" + scripts.down.reverse().reduce((accumulator, e) => accumulator + e, "") + "]" + DefaultSqlAdapter.SERIAL_PROMISE;
fileContent = fileContent.replace(commentRegex, upScript);
fileContent = fileContent.replace(commentRegex, downScript);
this.logger.debug("writing to file", migrationFilePath);
fs_1.default.writeFileSync(migrationFilePath, fileContent);
//-- rename, use i to avoid file overlapping
let newName = fileName[0].substr(0, fileName[0].length - 3) + `-${index}-${migrationType}.js`;
this.logger.debug("renaming file", migrationFilePath);
fs_1.default.renameSync(migrationFilePath, path_1.default.join(migrationDir, newName));
migrationFileNames.push(newName);
return;
});
for (let i = 0; i < scripts.length; i++) {
yield migrationFunction({
up: scripts[i].upTable,
down: scripts[i].downTable
}, i, "0");
}
for (let i = 0; i < scripts.length; i++) {
yield migrationFunction({
up: scripts[i].upConstraint,
down: scripts[i].downConstraint
}, i, "1");
}
//-- update metadata file
this.logger.debug("updating metadata file");
fs_1.default.writeFileSync(prototypeFilePath, JSON.stringify(this.modelMetadata));
throw new ClaireError_1.ClaireError("MIGRATION_REVIEW_NEEDED", JSON.stringify(migrationFileNames));
}
});
}
generatePropertiesQuery(fieldProperties) {
let propertiesQuery = "{";
//-- check field properties
if (fieldProperties.isPrimaryKey === true) {
propertiesQuery += `primaryKey:true,`;
}
if (fieldProperties.isForeignKey === true) {
//-- get table name of the refer model
let referModel = this.modelMetadata.find((meta) => meta.modelName === fieldProperties.referModel);
propertiesQuery += `references:{model:'${referModel && referModel.tableName}',key:'${referModel && referModel.getPrimaryKey().fieldDataName}'},`;
}
if (fieldProperties.isAutoIncrement === true) {
propertiesQuery += `autoIncrement:true,`;
}
propertiesQuery += `allowNull:${fieldProperties.nullable ? "true" : "false"},`;
if (fieldProperties.defaultValue !== undefined && typeof fieldProperties.defaultValue !== "function") {
propertiesQuery += `defaultValue:${fieldProperties.defaultValue},`;
}
if (fieldProperties.innerDataType !== undefined) {
switch (fieldProperties.innerDataType) {
case DataType_1.DataType.STRING:
propertiesQuery += `type:Sequelize.STRING,`;
break;
case DataType_1.DataType.TEXT:
propertiesQuery += `type:Sequelize.TEXT,`;
break;
case DataType_1.DataType.FLOAT:
propertiesQuery += `type:Sequelize.FLOAT,`;
break;
case DataType_1.DataType.INTEGER:
propertiesQuery += `type:Sequelize.INTEGER,`;
break;
case DataType_1.DataType.BIGINT:
propertiesQuery += `type:Sequelize.BIGINT,`;
break;
case DataType_1.DataType.BOOL:
propertiesQuery += `type:Sequelize.BOOLEAN,`;
break;
}
}
propertiesQuery += "}";
return propertiesQuery;
}
static getDifferences(oldMeta, newMeta) {
let result = {
removed: undefined,
added: undefined,
};
if (oldMeta === undefined) {
result.added = newMeta;
}
else if (newMeta === undefined) {
result.removed = oldMeta;
}
else {
let removed = new ModelMetadata_1.ModelMetadata();
let added = new ModelMetadata_1.ModelMetadata();
//-- this value must be present
removed.modelName = oldMeta.modelName;
added.modelName = newMeta.modelName;
//-- check tableName
if (oldMeta.tableName !== newMeta.tableName) {
removed.tableName = oldMeta.tableName;
added.tableName = newMeta.tableName;
}
//-- check charset
// if (oldMeta.charset !== newMeta.charset) {
// removed.charset = oldMeta.charset;
// added.charset = newMeta.charset;
// }
//-- check fields
let oldMetaFields = oldMeta.fields;
for (let i = 0; i < oldMetaFields.length; i++) {
let oldField = oldMetaFields[i];
if (oldField.fieldDataName) {
let matchedField = newMeta.getFieldByDataName(oldField.fieldDataName);
if (!matchedField) {
removed.addField(oldField);
}
else {
let removedField = new FieldValidationMetadata_1.FieldValidationMetadata();
let addedField = new FieldValidationMetadata_1.FieldValidationMetadata();
removedField.fieldDataName = oldField.fieldDataName;
addedField.fieldDataName = matchedField.fieldDataName;
//-- comparing properties
const properties = [
"isPrimaryKey", "isForeignKey", "isAutoIncrement",
"isUnique", "referModel", "innerDataType", "nullable", "defaultValue"
];
for (const props of properties) {
if (oldField[props] !== matchedField[props]) {
// @ts-ignore
removedField[props] = oldField[props];
// @ts-ignore
addedField[props] = matchedField[props];
}
}
removed.addField(removedField);
added.addField(addedField);
}
}
}
let newMetaFields = newMeta.fields;
for (let i = 0; i < newMetaFields.length; i++) {
let newField = newMetaFields[i];
if (newField.fieldDataName) {
let matchedField = oldMeta.getFieldByDataName(newField.fieldDataName);
if (!matchedField) {
added.addField(newField);
}
}
}
result.removed = ModelMetadata_1.ModelMetadata.isNotEmptyMetadata(removed) ? removed : undefined;
result.added = ModelMetadata_1.ModelMetadata.isNotEmptyMetadata(added) ? added : undefined;
}
return result;
}
static generateSchemaObject(builder, tablePrototype) {
let schema;
let schemaDefinition = {};
if (tablePrototype.tableName) {
tablePrototype.fields.forEach((field) => {
if (field.fieldDataName) {
schemaDefinition[field.fieldDataName] = {};
//-- check type, prefer innerDataType if existed
const dataType = field.innerDataType;
switch (dataType) {
case DataType_1.DataType.FLOAT:
schemaDefinition[field.fieldDataName]["type"] = sequelize_1.FLOAT;
break;
case DataType_1.DataType.INTEGER:
schemaDefinition[field.fieldDataName]["type"] = sequelize_1.INTEGER;
break;
case DataType_1.DataType.BIGINT:
schemaDefinition[field.fieldDataName]["type"] = sequelize_1.BIGINT;
break;
case DataType_1.DataType.TEXT:
schemaDefinition[field.fieldDataName]["type"] = sequelize_1.TEXT;
break;
case DataType_1.DataType.STRING:
schemaDefinition[field.fieldDataName]["type"] = sequelize_1.STRING;
break;
case DataType_1.DataType.BOOL:
schemaDefinition[field.fieldDataName]["type"] = sequelize_1.BOOLEAN;
break;
}
//-- check default value
if (!Utils_1.Utils.isNullOrUndefined(field.defaultValue) && typeof field.defaultValue !== "function") {
schemaDefinition[field.fieldDataName]["defaultValue"] = field.defaultValue;
}
//-- check primaryKey
if (field.isPrimaryKey === true) {
schemaDefinition[field.fieldDataName]["primaryKey"] = true;
}
//-- check auto increment
if (field.isAutoIncrement === true) {
schemaDefinition[field.fieldDataName]["autoIncrement"] = true;
}
//-- check nullable
schemaDefinition[field.fieldDataName]["allowNull"] = field.nullable;
}
});
schema = builder.define(tablePrototype.tableName, schemaDefinition, {
tableName: tablePrototype.tableName
});
}
return schema;
}
createTransaction() {
return __awaiter(this, void 0, void 0, function* () {
let transaction = yield this.connection.transaction();
return new TransactionAdapter(transaction, this.connection, this.modelMetadata, this.schemas);
});
}
}
DefaultSqlAdapter.SERIAL_PROMISE = ".reduce((promise, func) => promise.then((result) => func.then(Array.prototype.concat.bind(result))), Promise.resolve([]));";
DefaultSqlAdapter.COMMENT_BLOCK_REGEX_PATTERN = "/\\*(\\*(?!/)|[^*])*\\*\\/";
// private static readonly SEQUELIZE_CLI_PATH = "../../node_modules/"; /*dev-only*/
DefaultSqlAdapter.SEQUELIZE_CLI_PATH = "../../../";
__decorate([
__2.Inject(),
__metadata("design:type", AbstractLogger_1.AbstractLogger)
], DefaultSqlAdapter.prototype, "logger", void 0);
exports.DefaultSqlAdapter = DefaultSqlAdapter;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiRGVmYXVsdFNxbEFkYXB0ZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9saWIvZGVmYXVsdC1pbXBsZW1lbnRhdGlvbnMvRGVmYXVsdFNxbEFkYXB0ZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFBQSxnQ0FBZ0M7QUFDaEMsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDLFFBQVEsQ0FBQyxTQUFTLEdBQUcsSUFBSSxDQUFDO0FBRXhDLDRDQUFvQjtBQUNwQixnREFBd0I7QUFDeEIsaURBQW1DO0FBQ25DLHlDQUF1RjtBQUN2RiwyQ0FBc0M7QUFDdEMsMERBQXFEO0FBQ3JELDBEQUFxRDtBQUVyRCx1REFBa0Q7QUFDbEQsNkNBTTBCO0FBRTFCLDBEQUFvRDtBQUVwRCwwQkFBd0Q7QUFDeEQsMEJBQTBCO0FBQzFCLDZEQUF3RDtBQUN4RCxrRkFBNkU7QUFDN0UsK0NBQTBDO0FBSTFDLE1BQU0sWUFBZ0IsU0FBUSw2QkFBZ0I7SUFNMUMsWUFBWSxVQUFxQixFQUFFLEtBQTRDLEVBQUUsUUFBdUIsRUFBRSxNQUFnQyxFQUFFLFdBQWlCO1FBQ3pKLEtBQUssQ0FBQyxLQUFLLEVBQUUsUUFBUSxDQUFDLENBQUM7UUFDdkIsSUFBSSxDQUFDLGFBQWEsR0FBRyxNQUFNLENBQUM7UUFDNUIsSUFBSSxDQUFDLFVBQVUsR0FBRyxVQUFVLENBQUM7UUFDN0IsSUFBSSxDQUFDLFdBQVcsR0FBRyxXQUFXLENBQUM7SUFDbkMsQ0FBQztJQUVTLDBCQUEwQixDQUFDLFVBQWUsRUFBRSxPQUtyRDtRQUNHLElBQUksT0FBTyxFQUFFO1lBQ1QsSUFBSSxPQUFPLENBQUMsS0FBSyxFQUFFO2dCQUNmLGlDQUFpQztnQkFDakMsYUFBYTtnQkFDYixNQUFNLFNBQVMsR0FBRyxhQUFLLENBQUMsY0FBYyxDQUFDLElBQUksQ0FBQyxvQkFBb0IsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENB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