lx-mongodb
Version:
Litixsoft backend driver for mongoDb.
646 lines (536 loc) • 19.6 kB
JavaScript
;
var lxHelpers = require('lx-helpers');
/**
* Returns a base repo.
* @param {Object} schema
* @param {!Object} collection The mongoDB collection.
* @returns {Object|Error} The repo object.
* @constructor
*/
module.exports = function (collection, schema) {
if (!collection || !lxHelpers.isObject(collection)) {
throw lxHelpers.getTypeError('collection', collection, {});
}
schema = schema || {};
var pub = {},
sort = {},
idFields = {},
key = '_id',
ObjectID = require('mongodb').ObjectID;
function checkProperty (name, prop) {
var idx = {};
if (prop.type === 'string' && prop.format === 'mongo-id') {
if (!idFields.hasOwnProperty(name)) {
idFields[name] = true;
}
}
if (prop.sort) {
sort[name] = prop.sort;
}
if (prop.key) {
key = name;
}
if (prop.index) {
idx[name] = 1;
collection.ensureIndex(idx, function (error) {
if (error) {
console.error(error);
}
});
}
if (prop.unique) {
idx[name] = 1;
collection.ensureIndex(idx, {unique: true}, function (error) {
if (error) {
console.error(error);
}
});
}
}
function analyseSchema (schema, namespace) {
namespace = namespace || '';
lxHelpers.forEach(schema, function (propertyValue, propertyName) {
propertyName = namespace === '' ? propertyName : namespace + '.' + propertyName;
if (propertyValue.properties) {
analyseSchema(propertyValue.properties, propertyName);
} else if (propertyValue.items) {
analyseSchema(propertyValue.items, propertyName);
} else {
checkProperty(propertyName, propertyValue);
}
});
}
/**
* convert id to mongoId
* @param query
* @return {*}
*/
function convertToMongoId (query) {
query = query || {};
lxHelpers.forEach(idFields, function (value, key) {
if (query.hasOwnProperty(key) && typeof query[key] === 'string') {
query[key] = ObjectID.createFromHexString(query[key]);
}
if (query.hasOwnProperty(key) && query[key] && query[key].hasOwnProperty('$in') && lxHelpers.isArray(query[key].$in)) {
query[key].$in = lxHelpers.arrayMap(query[key].$in, function (item) {
if (typeof item === 'string') {
return ObjectID.createFromHexString(item);
}
if (lxHelpers.isObject(item)) {
return item;
}
});
}
});
return query;
}
/**
* query tester
* @param query
* @returns {boolean}
*/
function isOptions (query) {
var isOption = false,
checkArr = ['fields', 'sort', 'skip', 'limit'];
lxHelpers.forEach(query, function (value, key) {
if (checkArr.indexOf(key) > -1) {
isOption = true;
return false;
}
});
return isOption;
}
/**
* Returns if the object is a mongo options object.
*
* @param query
* @returns {boolean}
*/
function isMongoOptionsObject (query) {
var isOption = false,
checkArr = ['w', 'journal', 'wtimeout', 'single', 'fsync'];
lxHelpers.forEach(query, function (value, key) {
if (checkArr.indexOf(key) > -1) {
isOption = true;
return false;
}
});
return isOption;
}
/**
* Deletes the key of the document for update operation.
*
* @param {object=} document The document.
*/
function deleteDocumentKey (document) {
document = document || {};
if (document.$set) {
delete document.$set[key];
} else {
delete document[key];
}
}
/**
* Converts a value by format. Is used in lx-valid.
*
* @param {String} format The schema format.
* @param {*} value The value to convert.
* @returns {*}
*/
function convert (format, value) {
if (typeof value !== 'string') {
return value;
}
if (format === 'mongo-id') {
return ObjectID.createFromHexString(value);
}
if (format === 'date-time' || format === 'date') {
return new Date(value);
}
return value;
}
/**
* Gets the sort option for mongoDB.
*
* @param {string|array|object} value The sort value.
* @returns {*}
*/
function getSort (value) {
var result = {};
// return default sort
if (!value) {
return sort;
}
// sort by the given string ascending, e.g. 'name'
if (typeof value === 'string') {
result[value] = 1;
return result;
}
if (lxHelpers.isArray(value) && value.length > 0) {
if (lxHelpers.isArray(value[0])) {
// sort by array, e.g. [['name': 1], ['city': -1]]
return value;
} else {
lxHelpers.forEach(value, function (item) {
if (typeof item === 'string') {
result[item] = 1;
}
});
// sort by the strings in the array ascending, e.g. ['name', 'city']
return result;
}
}
// sort by object, e.g. {name: 1, city: -1}
if (lxHelpers.isObject(value)) {
return value;
}
return null;
}
/**
* Returns the schema of the collection.
*
* @returns {Object}
*/
pub.getSchema = function () {
return lxHelpers.isFunction(schema) ? schema.apply() : schema;
};
/**
* Returns the collection.
*
* @returns {!Object}
*/
pub.getCollection = function () {
return collection;
};
/**
* Returns the validation options for lx-valid.
*
* @returns {{deleteUnknownProperties: boolean, convert: Function, trim: boolean, strictRequired: boolean}}
*/
pub.getValidationOptions = function () {
return {
// deletes all properties not defined in the json schema
deleteUnknownProperties: true,
// function to convert the values with a format to a value that mongoDb can handle (e.g dates, ObjectID)
convert: convert,
// trim all values which are in schema and of type 'string'
trim: true,
// handle empty string values as invalid when they are required in schema
strictRequired: true
};
};
/**
* Creates a new mongo ObjectID
*
* @returns {ObjectID}
*/
pub.createNewId = function () {
return new ObjectID();
};
/**
* Converts a string in a mongo ObjectID and vice versa.
*
* @param {String|ObjectID} id The id to convert.
* @returns {String|ObjectID}
*/
pub.convertId = function (id) {
if (lxHelpers.isString(id)) {
id = ObjectID.createFromHexString(id);
} else if (lxHelpers.isObject(id)) {
id = id.toHexString();
}
return id;
};
/**
* Gets the count of the collection.
*
* @param {Object|function(err, res)=} query The query/options object or callback.
* @param {Object|function(err, res)=} options The options object or the callback.
* @param {!function(err, res)} callback The callback.
*/
pub.count = function (query, options, callback) {
if (arguments.length === 1) {
callback = query;
options = {};
query = {};
}
if (arguments.length === 2) {
callback = options;
if (isOptions(query)) {
options = query;
query = {};
} else {
options = {};
}
}
if (!lxHelpers.isFunction(callback)) {
throw new TypeError('Param "callback" is of type ' + lxHelpers.getType(callback) + '! Type ' + lxHelpers.getType(lxHelpers.getType) + ' expected');
}
if (!lxHelpers.isObject(query)) {
callback(new TypeError('Param "query" is of type ' + lxHelpers.getType(query) + '! Type ' + lxHelpers.getType({}) + ' expected'), null);
return;
}
query = convertToMongoId(query);
collection.count(query, options, callback);
};
/**
* Creates a new document in the db.
*
* @param {!Object|!Array} doc The document/s.
* @param {Object|function(err, res)=} options The options object or the callback.
* @param {function(err, res)=} callback The callback.
*/
pub.insert = function (doc, options, callback) {
var error;
if (arguments.length === 2) {
if (lxHelpers.isFunction(options)) {
callback = options;
}
}
options = options || {};
if (!(lxHelpers.isObject(doc) || lxHelpers.isArray(doc))) {
error = new TypeError('Param "doc" is of type ' + lxHelpers.getType(doc) + '! Type ' + lxHelpers.getType({}) + ' or ' + lxHelpers.getType([]) + ' expected');
if (callback && lxHelpers.isFunction(callback)) {
callback(error, null);
return;
} else {
throw error;
}
}
if (arguments.length === 2 && !(lxHelpers.isObject(options) || lxHelpers.isFunction(options))) {
throw new TypeError('Param "options" is of type ' + lxHelpers.getType(options) + '! Type ' + lxHelpers.getType({}) + ' or ' + lxHelpers.getType(function () {}) + ' expected');
}
if (callback && !lxHelpers.isFunction(callback)) {
throw new TypeError('Param "callback" is of type ' + lxHelpers.getType(callback) + '! Type ' + lxHelpers.getType(lxHelpers.getType) + ' expected');
}
if (callback) {
collection.insert(doc, options, callback);
} else {
collection.insert(doc, options);
}
};
/**
* Gets all documents of the collection.
*
* @param {Object|function(err, res)=} query The query/options object or the callback.
* @param {Object|function(err, res)=} options The options object or the callback.
* @param {Number=} options.skip The skip param for mongoDB.
* @param {Number=} options.limit The limit param for mongoDB.
* @param {array|object=} options.fields The fields which returns from the query.
* @param {string|array|object=} options.sort The sort param for mongoDB.
* @param {!function(err, res)} callback The callback.
*/
pub.find = function (query, options, callback) {
if (arguments.length === 1) {
callback = query;
query = {};
options = {};
}
if (arguments.length === 2) {
callback = options;
if (isOptions(query)) {
options = query;
query = {};
} else {
options = {};
}
}
if (!lxHelpers.isFunction(callback)) {
throw new TypeError('Param "callback" is of type ' + lxHelpers.getType(callback) + '! Type ' + lxHelpers.getType(lxHelpers.getType) + ' expected');
}
if (!lxHelpers.isObject(query)) {
callback(new TypeError('Param "query" is of type ' + lxHelpers.getType(query) + '! Type ' + lxHelpers.getType({}) + ' expected'), null);
return;
}
var mongoOptions = {
skip: options.skip || 0,
limit: options.limit || 0,
fields: options.fields,
sort: getSort(options.sort)
};
query = convertToMongoId(query);
collection.find(query, mongoOptions).toArray(callback);
};
/**
* Gets one document by query
*
* @param {!Object} query The query object.
* @param {Object|function(err, res)=} options The options object or the callback.
* @param {Number=} options.skip The skip param for mongoDB.
* @param {Number=} options.limit The limit param for mongoDB.
* @param {Array|Object=} options.fields The fields which returns from the query.
* @param {string|Array|Object=} options.sort The sort param for mongoDB.
* @param {!function(err, res)} callback The callback.
*/
pub.findOne = function (query, options, callback) {
if (arguments.length < 2) {
throw new TypeError('Param "callback" is undefined! Type ' + lxHelpers.getType(lxHelpers.getType) + ' expected');
}
if (arguments.length === 2) {
callback = options;
if (isOptions(query)) {
options = query;
query = {};
} else {
options = {};
}
}
if (!lxHelpers.isFunction(callback)) {
throw new TypeError('Param "callback" is of type ' + lxHelpers.getType(callback) + '! Type ' + lxHelpers.getType(lxHelpers.getType) + ' expected');
}
if (!lxHelpers.isObject(query)) {
callback(new TypeError('Param "query" is of type ' + lxHelpers.getType(query) + '! Type ' + lxHelpers.getType({}) + ' expected'), null);
return;
}
var mongoOptions = {
fields: options.fields,
sort: getSort(options.sort)
};
query = convertToMongoId(query);
collection.findOne(query, mongoOptions, callback);
};
/**
* Gets one document by id
*
* @param {ObjectID|String} id The id.
* @param {Object|function(err, res)=} options The options object or the callback.
* @param {array|object} options.fields The fields which returns from the query.
* @param {function(err, res)=} callback The callback.
*/
pub.findOneById = function (id, options, callback) {
if (arguments.length < 2) {
throw new TypeError('Param "callback" is undefined! Type ' + lxHelpers.getType(lxHelpers.getType) + ' expected');
}
if (arguments.length === 2) {
callback = options;
options = {};
}
if (!lxHelpers.isFunction(callback)) {
throw new TypeError('Param "callback" is of type ' + lxHelpers.getType(callback) + '! Type ' + lxHelpers.getType(lxHelpers.getType) + ' expected');
}
if (!id || (!lxHelpers.isObject(id) && !lxHelpers.isString(id))) {
callback(new TypeError('Param "id" is of type ' + lxHelpers.getType(id) + '! Type ' + lxHelpers.getType({}) + ' or ' + lxHelpers.getType('') + ' expected'), null);
return;
}
var mongoOptions = {
fields: options.fields
};
var query = {};
query[key] = lxHelpers.isString(id) ? pub.convertId(id) : id;
collection.findOne(query, mongoOptions, callback);
};
/**
* Updates the documents of the query.
*
* @param {!Object} query The query object.
* @param {!Object} update The new data.
* @param {Object=} options The options for multi update.
* @param {!function(err, res)} callback The callback.
*/
pub.update = function (query, update, options, callback) {
if (arguments.length < 3) {
throw new Error('missing parameters.');
}
if (arguments.length === 3) {
callback = options;
options = {};
}
query = convertToMongoId(query);
// delete key property
deleteDocumentKey(update);
options = options || {};
collection.update(query, update, options, callback);
};
/**
* Deletes the documents of the query.
*
* @param {Object=} query The query object.
* @param {Object=} options The options object.
* @param {function(object, object)=} callback The callback.
*/
pub.remove = function (query, options, callback) {
var error;
if (arguments.length === 1) {
if (lxHelpers.isFunction(query)) {
callback = query;
query = {};
options = {};
}
if (isMongoOptionsObject(query)) {
options = query;
query = {};
}
}
if (arguments.length === 2) {
if (lxHelpers.isFunction(options)) {
callback = options;
options = {};
}
if (isMongoOptionsObject(query)) {
options = query;
query = {};
}
}
options = options || {};
if (lxHelpers.getType(query) !== 'undefined' && !lxHelpers.isObject(query)) {
error = new TypeError('Param "query" is of type ' + lxHelpers.getType(query) + '! Type ' + lxHelpers.getType({}) + ' expected');
if (callback && lxHelpers.isFunction(callback)) {
callback(error, null);
return;
} else {
throw error;
}
}
if (options && !lxHelpers.isObject(options)) {
error = new TypeError('Param "options" is of type ' + lxHelpers.getType(options) + '! Type ' + lxHelpers.getType({}) + ' expected');
if (callback && lxHelpers.isFunction(callback)) {
callback(error, null);
return;
} else {
throw error;
}
}
query = convertToMongoId(query);
if (callback && !lxHelpers.isFunction(callback)) {
throw new TypeError('Param "callback" is of type ' + lxHelpers.getType(callback) + '! Type ' + lxHelpers.getType(lxHelpers.getType) + ' expected');
}
if (callback) {
collection.remove(query, options, callback);
} else {
collection.remove(query, options);
}
};
/**
* Execute an aggregation framework pipeline against the collection.
*
* @param {Array} pipeline The aggregation framework pipeline.
* @param {Object=} options The additional options.
* @param {function(err, res)} callback The callback.
*/
pub.aggregate = function (pipeline, options, callback) {
if (arguments.length < 2) {
throw new Error('missing callback parameter');
}
if (arguments.length === 2) {
callback = options;
options = {};
}
if (!lxHelpers.isArray(pipeline)) {
callback(new TypeError('Param "pipeline" is of type ' + lxHelpers.getType(pipeline) + '! Type ' + lxHelpers.getType([]) + ' expected'), null);
return;
}
options = options || {};
collection.aggregate(pipeline, options, callback);
};
var tmpSchema = pub.getSchema();
if (tmpSchema.hasOwnProperty('properties')) {
analyseSchema(tmpSchema.properties);
} else {
analyseSchema(tmpSchema);
}
// set default sorting
if (Object.keys(sort).length === 0) {
sort[key] = 1;
}
return pub;
};