packages/moleculer-db-adapter-sequelize/src/index.js
/*
* moleculer-db-adapter-sequelize
* Copyright (c) 2019 MoleculerJS (https://github.com/moleculerjs/moleculer-db)
* MIT Licensed
*/
"use strict";
const _ = require("lodash");
const { ServiceSchemaError } = require("moleculer").Errors;
const Promise = require("bluebird");
const Sequelize = require("sequelize");
const { Model, Op } = Sequelize;
class SequelizeDbAdapter {
/**
* Creates an instance of SequelizeDbAdapter.
* @param {any} opts
*
* @memberof SequelizeDbAdapter
*/
constructor(...opts) {
this.opts = opts;
}
/**
* Initialize adapter
*
* @param {ServiceBroker} broker
* @param {Service} service
*
* @memberof SequelizeDbAdapter
*/
init(broker, service) {
this.broker = broker;
this.service = service;
if (!this.service.schema.model) {
/* istanbul ignore next */
throw new ServiceSchemaError("Missing `model` definition in schema of service!");
}
}
/**
* Connect to database
*
* @returns {Promise}
*
* @memberof SequelizeDbAdapter
*/
connect() {
const sequelizeInstance = this.opts[0];
if (sequelizeInstance && sequelizeInstance instanceof Sequelize)
this.db = sequelizeInstance;
else
this.db = new Sequelize(...this.opts);
return this.db.authenticate().then(() => {
let modelDefinitionOrInstance = this.service.schema.model;
let noSync = false;
if (this.opts[0] && Object.prototype.hasOwnProperty.call(this.opts[0],"noSync")) {
noSync = !!this.opts[0].noSync;
} else if (this.opts[0] && Object.prototype.hasOwnProperty.call(this.opts[0],"sync")) {
noSync = !this.opts[0].sync.force;
} else if (this.opts[3] && Object.prototype.hasOwnProperty.call(this.opts[3],"sync")) {
noSync = !this.opts[3].sync.force;
} else if (this.opts[3]) {
noSync = !!this.opts[3].noSync;
}
let modelReadyPromise;
let isModelInstance = modelDefinitionOrInstance
&& (Object.prototype.hasOwnProperty.call(modelDefinitionOrInstance, "attributes")
|| modelDefinitionOrInstance.prototype instanceof Model);
if (isModelInstance) {
this.model = modelDefinitionOrInstance;
modelReadyPromise = Promise.resolve();
} else {
this.model = this.db.define(modelDefinitionOrInstance.name, modelDefinitionOrInstance.define, modelDefinitionOrInstance.options);
modelReadyPromise = noSync ? Promise.resolve(this.model) : this.model.sync();
}
this.service.model = this.model;
return modelReadyPromise.then(() => {
this.service.logger.info("Sequelize adapter has connected successfully.");
}).catch((e) => {
return this.db.close()
.finally(() => Promise.reject(e));
});
});
}
/**
* Disconnect from database
*
* @returns {Promise}
*
* @memberof SequelizeDbAdapter
*/
disconnect() {
if (this.db) {
return this.db.close();
}
/* istanbul ignore next */
return Promise.resolve();
}
/**
* Find all entities by filters.
*
* Available filter props:
* - limit
* - offset
* - sort
* - search
* - searchFields
* - query
*
* @param {any} filters
* @returns {Promise}
*
* @memberof SequelizeDbAdapter
*/
find(filters) {
return this.createCursor(filters);
}
/**
* Find an entity by query
*
* @param {Object} query
* @returns {Promise}
* @memberof MemoryDbAdapter
*/
findOne(query) {
return this.model.findOne(query);
}
/**
* Find an entities by ID
*
* @param {any} _id
* @returns {Promise}
*
* @memberof SequelizeDbAdapter
*/
findById(_id) {
return this.model.findByPk(_id);
}
/**
* Find any entities by IDs
*
* @param {Array} idList
* @returns {Promise}
*
* @memberof SequelizeDbAdapter
*/
findByIds(idList) {
return this.model.findAll({
where: {
id: {
[Op.in]: idList
}
}
});
}
/**
* Get count of filtered entites
*
* Available filter props:
* - search
* - searchFields
* - query
*
* @param {Object} [filters={}]
* @returns {Promise}
*
* @memberof SequelizeDbAdapter
*/
count(filters = {}) {
return this.createCursor(filters, true);
}
/**
* Insert an entity
*
* @param {Object} entity
* @returns {Promise}
*
* @memberof SequelizeDbAdapter
*/
insert(entity) {
return this.model.create(entity);
}
/**
* Insert many entities
*
* @param {Array} entities
* @param {Object} opts
* @returns {Promise}
*
* @memberof SequelizeDbAdapter
*/
insertMany(entities, opts = { returning: true }) {
return this.model.bulkCreate(entities, opts);
}
/**
* Update many entities by `where` and `update`
*
* @param {Object} where
* @param {Object} update
* @returns {Promise}
*
* @memberof SequelizeDbAdapter
*/
updateMany(where, update) {
return this.model.update(update, { where }).then(res => res[0]);
}
/**
* Update an entity by ID and `update`
*
* @param {any} _id
* @param {Object} update
* @returns {Promise}
*
* @memberof SequelizeDbAdapter
*/
updateById(_id, update) {
return this.findById(_id).then(entity => {
return entity && entity.update(update["$set"]);
});
}
/**
* Remove entities which are matched by `where`
*
* @param {Object} where
* @returns {Promise}
*
* @memberof SequelizeDbAdapter
*/
removeMany(where) {
return this.model.destroy({ where });
}
/**
* Remove an entity by ID
*
* @param {any} _id
* @returns {Promise}
*
* @memberof SequelizeDbAdapter
*/
removeById(_id) {
return this.findById(_id).then(entity => {
return entity && entity.destroy().then(() => entity);
});
}
/**
* Clear all entities from collection
*
* @returns {Promise}
*
* @memberof SequelizeDbAdapter
*/
clear() {
return this.model.destroy({ where: {} });
}
/**
* Convert DB entity to JSON object
*
* @param {any} entity
* @returns {Object}
* @memberof SequelizeDbAdapter
*/
entityToObject(entity) {
return entity.get({ plain: true });
}
/**
* Create a filtered query
* Available filters in `params`:
* - search
* - sort
* - limit
* - offset
* - query
*
* @param {Object} params
* @param {Boolean} isCounting
* @returns {Promise}
*/
createCursor(params, isCounting) {
if (!params) {
if (isCounting)
return this.model.count();
return this.model.findAll();
}
const q = {
where: {}
};
// Text search
if (_.isString(params.search) && params.search !== "") {
let fields = [];
if (params.searchFields) {
fields = _.isString(params.searchFields) ? params.searchFields.split(" ") : params.searchFields;
}
const searchConditions = fields.map(f => {
return {
[f]: {
[Op.like]: "%" + params.search + "%"
}
};
});
if (params.query) {
q.where[Op.and] = [
params.query,
{ [Op.or]: searchConditions }
];
} else {
q.where[Op.or] = searchConditions;
}
} else if (params.query) {
Object.assign(q.where, params.query);
}
// Sort
if (params.sort) {
let sort = this.transformSort(params.sort);
if (sort)
q.order = sort;
}
// Offset
if (_.isNumber(params.offset) && params.offset > 0)
q.offset = params.offset;
// Limit
if (_.isNumber(params.limit) && params.limit > 0)
q.limit = params.limit;
if (isCounting)
return this.model.count(q);
return this.model.findAll(q);
}
/**
* Convert the `sort` param to a `sort` object to Sequelize queries.
*
* @param {String|Array<String>|Object} paramSort
* @returns {Object} Return with a sort object like `[["votes", "ASC"], ["title", "DESC"]]`
* @memberof SequelizeDbAdapter
*/
transformSort(paramSort) {
let sort = paramSort;
if (_.isString(sort))
sort = sort.replace(/,/, " ").split(" ");
if (Array.isArray(sort)) {
let sortObj = [];
sort.forEach(s => {
if (s.startsWith("-"))
sortObj.push([s.slice(1), "DESC"]);
else
sortObj.push([s, "ASC"]);
});
return sortObj;
}
if (_.isObject(sort)) {
return Object.keys(sort).map(name => [name, sort[name] > 0 ? "ASC" : "DESC"]);
}
/* istanbul ignore next*/
return [];
}
/**
* For compatibility only.
* @param {Object} entity
* @param {String} idField
* @memberof SequelizeDbAdapter
* @returns {Object} Entity
*/
beforeSaveTransformID(entity, idField) {
return entity;
}
/**
* For compatibility only.
* @param {Object} entity
* @param {String} idField
* @memberof SequelizeDbAdapter
* @returns {Object} Entity
*/
afterRetrieveTransformID(entity, idField) {
return entity;
}
}
module.exports = SequelizeDbAdapter;