packages/moleculer-db-adapter-couchdb-nano/src/index.js
/*
* moleculer-db-adapter-couchdb-nano
* Copyright (c) 2018 Mr. Kutin (https://github.com/moleculerjs/moleculer-db/tree/master/packages/moleculer-db-adapter-couchdb-nano#readme)
* MIT Licensed
*/
"use strict";
const _ = require("lodash");
const Promise = require("bluebird");
const { ServiceSchemaError } = require("moleculer").Errors;
const Nano = require("nano");
class CouchDbNanoAdapter {
/**
* Creates an instance of CouchDbNano.
* @param {String} url
* @param {Object?} opts
*
* @memberof CouchDbNano
*/
constructor(url, opts) {
this.url = url;
this.opts = opts;
this.db = null;
}
/**
* Initialize adapter
*
* @param {ServiceBroker} broker
* @param {Service} service
*
* @memberof CouchDbNano
*/
init(broker, service) {
this.broker = broker;
this.service = service;
this.schema = service.schema.schema;
this.modelName = (service.schema && service.schema.model && service.schema.model.name) || (service.schema && service.schema.modelName) || service.schema.name;
if (!this.modelName) {
throw new ServiceSchemaError("Missing `modelName` or `name` definition in schema of service!");
}
}
/**
* Connect to database
*
* @returns {Promise}
*
* @memberof CouchDbNano
*/
connect() {
const opts = Object.assign({}, this.opts, {url: this.url ? this.url.replace("couchdb://", "http://") : "" || "http://localhost:5984"});
const nano = Nano(opts);
this.db = nano.db;
return this.db.get(this.modelName)
.catch(e => {
if (e.statusCode === 404) {
return this.db.create(this.modelName);
}
throw(e);
})
.then(() => {
this.db = this.db.use(this.modelName);
this.service.logger.info("CouchDB adapter has connected successfully.");
});
}
/**
* Disconnect from database
*
* @returns {Promise}
*
* @memberof CouchDbNano
*/
disconnect() {
this.db = null;
return Promise.resolve();
}
/**
* Insert an entity.
*
* @param {Object} entity
* @returns {Promise<Object>} Return with the inserted document.
*
* @memberof CouchDbNano
*/
insert(entity) {
return Promise.resolve(
this.db.insert(entity).then(result => this.findById(result.id))
);
}
/**
* Find an entity by ID.
*
* @param {String} _id
* @returns {Promise<Object>} Return with the found document.
*
* @memberof CouchDbNano
*/
findById(_id) {
return Promise.resolve(this.db.get(_id));
}
/**
* Remove an entity by ID
*
* @param {String} _id - ObjectID as hexadecimal string.
* @returns {Promise<Object>} Return with the removed document.
*
* @memberof CouchDbNano
*/
removeById(_id) {
return Promise.resolve(
this.findById(_id)
.then(doc => {
return this.db.destroy(doc._id, doc._rev);
})
.then(res => {
res._id = res.id;
delete res.id;
return res;
})
);
}
/**
* Find all entities by filters.
*
* Available filter props:
* - limit
* - offset
* - sort
* - search
* - searchFields
* - selector
*
* @param {Object} filters
* @returns {Promise<Array>}
*
* @memberof CouchDbNano
*/
find(filters) {
const limit = filters.limit;
const skip = filters.skip;
const sort = filters.sort;
const fields = filters.fields;
const selector = filters.selector ? filters.selector : filters;
if (selector.limit) {
delete selector.limit;
}
if (selector.skip) {
delete selector.skip;
}
if (selector.sort) {
delete selector.sort;
}
if (selector.fields) {
delete selector.fields;
}
Object.keys(selector).forEach(key => {
if (Object.prototype.hasOwnProperty.call(selector, key) && typeof selector[key] !== "object") {
selector[key] = {$eq: selector[key]};
}
});
return Promise.resolve(this.db.find({selector, limit, skip, sort, fields}).then(result => result.docs));
}
/**
* Find an entity by selector
*
* @param {Object} params
* @returns {Promise}
* @memberof MemoryDbAdapter
*/
findOne(params) {
const findParams = Object.assign({}, params.selector || params, {limit: 1});
return this.find(findParams).then(docs => docs.length ? docs[0] : null);
}
/**
* Find any entities by IDs.
*
* @param {Array} idList
* @returns {Promise<Array>} Return with the found documents in an Array.
*
* @memberof CouchDbNano
*/
findByIds(idList) {
return Promise.resolve(
this.db.fetch({keys: idList})
.then(res => res.rows.map(el => el.doc))
);
}
/**
* Insert many entities
*
* @param {Array} entities
* @returns {Promise<Array<Object>>} Return with the inserted documents in an Array.
*
* @memberof CouchDbNano
*/
insertMany(entities) {
return Promise.resolve(
this.db.bulk({docs: entities})
.then(result => result.map(el => el.id))
.then(ids => this.findByIds(ids))
);
}
/**
* Update many entities by `selector` and `update`
*
* @param {Object} selector
* @param {Object} update
* @returns {Promise<Number>} Count of modified documents.
*
* @memberof CouchDbNano
*/
updateMany(selector, update) {
return this.find({selector})
.then(docs => docs.map(doc => Object.assign({}, doc, update.$set ? update.$set : update)))
.then(docs => this.db.bulk({docs}))
.then(result => result.length);
}
/**
* Remove entities which are matched by `selector`
*
* @param {Object} selector
* @returns {Promise<Number>} Return with the count of deleted documents.
*
* @memberof CouchDbNano
*/
removeMany(selector) {
return this.find({selector})
.then(docs => docs.map(doc => Object.assign({}, doc, {_deleted: true})))
.then(docs => this.db.bulk({docs}))
.then(result => result.length);
}
/**
* Get count of filtered entites.
*
* Available filter props:
* - selector (json) – JSON object describing criteria used to select documents. More information provided in the section on selector syntax. Required
* - limit (number) – Maximum number of results returned. Default is 25. Optional
* - skip (number) – Skip the first ‘n’ results, where ‘n’ is the value specified. Optional
* - sort (json) – JSON array following sort syntax. Optional
*
* @param {Object} [filters={}]
* @returns {Promise<Number>} Return with the count of documents.
*
* @memberof CouchDbNano
*/
count(filters = {}) {
return this.find(filters).then(docs => docs.length);
}
/**
* Update an entity by ID and `update`
*
* @param {String} _id - ObjectID as hexadecimal string.
* @param {Object} update
* @returns {Promise<Object>} Return with the updated document.
*
* @memberof CouchDbNano
*/
updateById(_id, update) {
return this.findById(_id)
.then(doc => Object.assign({}, doc, update.$set ? update.$set : update))
.then(mergedDoc => this.db.insert(mergedDoc))
.then(result => this.findById(result.id));
}
/**
* Clear all entities from collection
*
* @returns {Promise}
*
* @memberof CouchDbNano
*/
clear() {
return this.removeMany({});
}
/**
* Convert DB entity to JSON object. It converts the `_id` to hexadecimal `String`.
*
* @param {Object} entity
* @returns {Object}
* @memberof CouchDbNano
*/
entityToObject(entity) {
return _.cloneDeep(entity);
}
/**
* Transforms 'idField' into CouchDB's '_id'
* @param {Object} entity
* @param {String} idField
* @memberof CouchDbNano
* @returns {Object} Modified entity
*/
beforeSaveTransformID(entity, idField) {
let newEntity = _.cloneDeep(entity);
if (idField !== "_id" && newEntity[idField] !== undefined) {
newEntity._id = newEntity[idField];
delete newEntity[idField];
}
return newEntity;
}
/**
* Transforms '_id' into user defined 'idField'
* @param {Object} entity
* @param {String} idField
* @memberof CouchDbNano
* @returns {Object} Modified entity
*/
afterRetrieveTransformID(entity, idField) {
if (idField !== "_id") {
entity[idField] = entity["_id"];
delete entity._id;
}
return entity;
}
}
module.exports = CouchDbNanoAdapter;