UNPKG

periodicjs.core.data

Version:

Core data is the ORM wrapping component of periodicjs.core.controller that provides database adapters for commonly used databases (ie. mongo, sql, postgres). Adapters provide a standard set of methods and options regardless of the type of database and so

689 lines (672 loc) 34.4 kB
'use strict'; const path = require('path'); const mongoose = require('mongoose'); const Promisie = require('promisie'); const flatten = require('flat'); const utility = require('../utility/index'); const xss_default_whitelist = require(path.join(__dirname, '../defaults/index')).xss_whitelist(); const objectIdTest = new RegExp(/[a-f0-9]{24}/i); /** * Convenience method for .find mongo method * @param {Object} options Options for the mongo query * @param {Object} [options.query={}] The query that should be used for the database search * @param {Object} [options.model=this.model] The mongoose model for query will default to the this.model value if not defined * @param {string} [options.sort=this.sort] Sorting criteria for query will default to the this.sort value if not defined * @param {number} [options.limit=this.limit] Limits the total returned documents for query will default to the this.limit value if not defined * @param {Object|string} [options.population=this.population] The mongoose population for query will default to the this.population value if not defined * @param {Object} [options.fields=this.fields] The fields that should be returned in query will default to the this.fields value if not defined * @param {number} [options.skip] The number of documents to offset in query * @param {Function} cb Callback function for query */ const _QUERY = function(options, cb) { try { let Model = options.model || this.model; //Iteratively checks if value was passed in options argument and conditionally assigns the default value if not passed in options let { sort, limit, population, fields, skip, } = ['sort', 'limit', 'population', 'fields', 'skip', ].reduce((result, key) => { if (options[key] && !isNaN(Number(options[key]))) options[key] = Number(options[key]); result[key] = options[key] || this[key]; return result; }, {}); Model.find((options.query && typeof options.query === 'object') ? options.query : {}, fields) .sort(sort) .skip(skip) .limit(limit) .populate(population || '') .exec(cb); } catch (e) { cb(e); } }; /** * Convenience method for returning a stream of mongo data * @param {Object} options Options for the mongo query * @param {Object} [options.query={}] The query that should be used for the database search * @param {Object} [options.model=this.model] The mongoose model for query will default to the this.model value if not defined * @param {string} [options.sort=this.sort] Sorting criteria for query will default to the this.sort value if not defined * @param {number} [options.limit=this.limit] Limits the total returned documents for query will default to the this.limit value if not defined * @param {Object|string} [options.population=this.population] The mongoose population for query will default to the this.population value if not defined * @param {Object} [options.fields=this.fields] The fields that should be returned in query will default to the this.fields value if not defined * @param {number} [options.skip] The number of documents to offset in query * @param {Function} cb Callback function for stream */ const _STREAM = function(options, cb) { try { let Model = options.model || this.model; //Iteratively checks if value was passed in options argument and conditionally assigns the default value if not passed in options let { sort, limit, population, fields, skip, } = ['sort', 'limit', 'population', 'fields', 'skip', ].reduce((result, key) => { if (options[key] && !isNaN(Number(options[key]))) options[key] = Number(options[key]); result[key] = options[key] || this[key]; return result; }, {}); skip = (typeof skip === 'number') ? skip : 0; let stream = Model.find((options.query && typeof options.query === 'object') ? options.query : {}, fields) .sort(sort) .skip(skip) .limit(limit) .populate(population || '') .cursor(); cb(null, stream); } catch (e) { cb(e); } }; /** * Convenience method for .find mongo method with built in pagination of data * @param {Object} options Options for the mongo query * @param {Object} [options.query={}] The query that should be used for the database search * @param {Object} [options.model=this.model] The mongoose model for query will default to the this.model value if not defined * @param {string} [options.sort=this.sort] Sorting criteria for query will default to the this.sort value if not defined * @param {number} [options.limit=this.limit] Limits the total returned documents for query will default to the this.limit value if not defined * @param {number} [options.pagelength=this.pagelength] Defines the max length of each sub-set of data * @param {Object|string} [options.population=this.population] The mongoose population for query will default to the this.population value if not defined * @param {Object} [options.fields=this.fields] The fields that should be returned in query will default to the this.fields value if not defined * @param {number} [options.skip] The number of documents to offset in query * @param {Function} cb Callback function for query */ const _QUERY_WITH_PAGINATION = function(options, cb) { try { let Model = options.model || this.model; //Iteratively checks if value was passed in options argument and conditionally assigns the default value if not passed in options let { sort, limit, population, fields, skip, pagelength, query, } = ['sort', 'limit', 'population', 'fields', 'skip', 'pagelength', 'query', ].reduce((result, key) => { if (options[key] && !isNaN(Number(options[key]))) options[key] = Number(options[key]); result[key] = options[key] || this[key]; return result; }, {}); let pages = { total: 0, total_pages: 0, }; let total = 0; let index = 0; skip = (typeof skip === 'number') ? skip : 0; Promisie.parallel({ count: () => { return new Promisie((resolve, reject) => { Model.estimatedDocumentCount(query, (err, count) => { if (err) reject(err); else resolve(count); }); }); }, pagination: () => { return Promisie.doWhilst(() => { return new Promisie((resolve, reject) => { _QUERY.call(this, { query, sort, limit: (total + pagelength <= limit) ? pagelength : (limit - total), fields, skip, population, model: Model, }, (err, data) => { if (err) reject(err); else { skip += data.length; total += data.length; pages.total += data.length; pages.total_pages++; pages[index++] = { documents: data, count: data.length, }; resolve(data.length); } }); }); }, current => (current === pagelength && total < limit)) .then(() => pages) .catch(e => Promisie.reject(e)); }, }) .then(result => { cb(null, Object.assign({}, result.pagination, { collection_count: result.count, collection_pages: Math.ceil(result.count / ((pagelength <= limit) ? pagelength : limit)), })); }) .catch(cb); } catch (e) { cb(e); } }; /** * Convenience method for .find mongo method with built in query builder functionality * @param {Object} options Options for the mongo query * @param {Object|string} [options.query] The query that should be used for the database search. If this value is a string it will be treated as a delimited list of values to use in query * @param {Object} [options.model=this.model] The mongoose model for query will default to the this.model value if not defined * @param {string} [options.sort=this.sort] Sorting criteria for query will default to the this.sort value if not defined * @param {number} [options.limit=this.limit] Limits the total returned documents for query will default to the this.limit value if not defined * @param {number} [options.pagelength=this.pagelength] Defines the max length of each sub-set of data * @param {Object|string} [options.population=this.population] The mongoose population for query will default to the this.population value if not defined * @param {Object} [options.fields=this.fields] The fields that should be returned in query will default to the this.fields value if not defined * @param {number} [options.skip] The number of documents to offset in query * @param {string[]} [options.search=this.searchfields] Used in building the query. A separate $or statement is appended into query array for each search field specified ie. ['a','b'] => { $or: [{a: ..., b ...}] } * @param {string} [options.delimeter="|||"] The value that the query values are delimeted by. If options.query is an object this value is ignored * @param {string} [options.docid=this.docid] When using options.values this specifies the name of the field that should be matched * @param {string} [options.values] A comma separated list of values to be queried against docid or "_id" if docid is not specified * @param {Boolean} options.paginate If true documents will be returned in a paginated format * @param {Function} cb Callback function for query */ const _SEARCH = function(options, cb) { try { let query; let searchfields; let docid = options.docid || this.docid; if (Array.isArray(options.search)) searchfields = options.search; else if (typeof options.search === 'string') searchfields = options.search.split(','); else searchfields = this.searchfields; let toplevel = (options.inclusive) ? '$or' : '$and'; query = { [toplevel]: [], }; //Pushes options.query if it already a composed query object if (options.query && typeof options.query === 'object') query[toplevel].push(options.query); //Handles options.query if string or number else if (typeof options.query === 'string' || typeof options.query === 'number') { let values = []; if (typeof options.query === 'number') values.push(options.query); //Tries to split on delimeter and generate query from options.query string else values = options.query.split((typeof options.delimeter === 'string' || options.delimeter instanceof RegExp) ? options.delimeter : '|||'); let statement = values.reduce((result, value) => { let block = { $or: [], }; for (let i = 0; i < searchfields.length; i++) { block.$or.push({ [searchfields[i]]: new RegExp(value, 'gi'), }); } return result.concat(block); }, []); query[toplevel].push({ $or: statement, }); } //Handles docnamelookup portion of query if (typeof options.values === 'string') { let split = options.values.split(','); let isObjectIds = (split.filter(utility.isObjectId).length === split.length); if (isObjectIds) { query[toplevel].push({ '_id': { $in: split, }, }); } else if (Array.isArray(docid)) { docid.forEach(d => { if (d === '_id') { if (utility.isObjectId(options.query)) { query[toplevel].push({ [d]: { $in: split, }, }); } } else { query[toplevel].push({ [d]: { $in: split, }, }); } }); } else { query[toplevel].push({ [(docid) ? (docid) : '_id']: { $in: split, }, }); } } if (options.fq) { query[toplevel].push(...utility.filterqueries.getFilterQueries(options.fq)); } // console.log({ query }) options.query = query; if (options.paginate) _QUERY_WITH_PAGINATION.call(this, options, cb); else _QUERY.call(this, options, cb); } catch (e) { cb(e); } }; /** * Convenience method for .findOne or .findById mongoose methods * @param {Object} options Configurable options for mongo query * @param {Object} [options.model=this.model] The mongoose model for query will default to the this.model value if not defined * @param {string} [options.sort=this.sort] Sorting criteria for query will default to the this.sort value if not defined * @param {Object|string} [options.population=this.population] The mongoose population for query will default to the this.population value if not defined * @param {Object} [options.fields=this.fields] The fields that should be returned in query will default to the this.fields value if not defined * @param {string} [options.docid="_id"] A field that should be queried will default to "_id" * @param {Object|string|number} options.query If value is an object query will be set to the value otherwise a query will be built based on options.docid and any other value provided in options.query * @param {Function} cb Callback function for load */ const _LOAD = function(options, cb) { try { let Model = options.model || this.model; let query; // console.log('_LOAD options', options); // console.log('_LOAD options.query', options.query); // if(!options.query) console.log({options}) //Iteratively checks if value was passed in options argument and conditionally assigns the default value if not passed in options if (options.docid && typeof options.docid === 'string') { options.docid = (utility.isObjectId(options.docid)) ? mongoose.Types.ObjectId( options.docid) : options.docid; } let { sort, population, fields, docid, } = ['sort', 'population', 'fields', 'docid', ].reduce((result, key) => { if (options[key] && !isNaN(Number(options[key]))) options[key] = Number(options[key]); result[key] = options[key] || this[key]; return result; }, {}); if (options.query && typeof options.query === 'object') { query = options.query; } else if ((Array.isArray(docid))) { query = { '$or': [], }; docid.forEach(d => { if (d === '_id') { if (utility.isObjectId(options.query)) { query.$or.push({ [d]: options.query, }); } } else { query.$or.push({ [d]: options.query, }); } }); } else if (!options.query && options.docid) { query = { _id: options.docid.toString(), }; } else { query = { [(utility.isObjectId(options.query)) ? '_id' : (docid || '_id')]: options.query, }; } // const util = require('util'); // console.log('query',util.inspect(query, { depth: 20 })); // console.log('typeof query', typeof query,{query}); Model.findOne(query, fields) .sort(sort) .populate(population || '') .exec(cb); } catch (e) { cb(e); } }; /** * Creates a mongoose update operation that only uses $set and $push * @param {Object} data Any fields that should be updated as part of patch * @return {Object} Returns an object with $set and $push properties */ const GENERATE_PATCH = function(data) { delete data._id; delete data.__v; let flattened = flatten(data, { safe: true, }); let $set = {}; let $push = {}; for (let key in flattened) { if (Array.isArray(flattened[key])) $push[key] = { $each: flattened[key], }; else $set[key] = flattened[key]; } let compiled = {}; if (Object.keys($set).length) compiled.$set = $set; if (Object.keys($push).length) compiled.$push = $push; return compiled; }; /** * Returns a cleaned object for a full document update * @param {Object} data A full document with updated data for put * @return {Object} Returns original object with reserved fields removed */ const GENERATE_PUT = function(data) { delete data._id; delete data.__v; return data; }; /** * Convenience method for .update mongo method * @param {Object} options Configurable options for mongo update * @param {Boolean} options.isPatch If true the update will be treated as a patch instead of a full document update * @param {Object} options.updatedoc Either specific fields to update in the case of a patch otherwise the entire updatedated document * @param {string} options.id The mongo _id of the document that should be updated * @param {Boolean} [options.skip_xss] If true xss character escaping will be skipped and xss whitelist is ignored * @param {Boolean} [options.html_xss] If true xss npm module will be used for character escaping * @param {Boolean} [options.track_changes] If false changes will not be tracked * @param {Boolean} [options.ensure_changes] If true changeset generation and saving is blocking and errors will cause entire operation to fail * @param {Object} [options.model=this.model] The mongoose model for query will default to the this.model value if not defined * @param {Function} cb Callback function for update */ const _UPDATE = function(options, cb) { try { // console.log('_UPDATE', { options }); options.track_changes = (typeof options.track_changes === 'boolean') ? options.track_changes : this.track_changes; if (!options.id) { options.id = options.updatedoc._id; options.id = (utility.isObjectId(options.id)) ? mongoose.Types.ObjectId( options.id) : options.id; } let changesetData = { update: Object.assign({}, options.updatedoc), original: Object.assign({}, options.originalrevision), }; let depopulate = (options.depopulate === false) ? false : true; let generateChanges = (callback) => { if (!options.track_changes || (options.track_changes && !options.ensure_changes)) { callback(); } if (options.track_changes) { Promise.resolve(options.originalrevision) .then(originalDoc => { if (originalDoc) { return changesetData.original; } else { // console.log({ docid: options.id, }) return this.load({ docid: options.id, }); } }) .then(originalDoc => { changesetData.original = (typeof originalDoc.toObject === 'function') ? originalDoc.toObject() : originalDoc; // console.log({ originalDoc }); let changeset = (!options.isPatch) ? utility.diff(changesetData.original, changesetData.update, depopulate) : options.updatedoc; this.changeset.create({ parent_document: { id: options.id, }, changeset: changeset, }, (err, result) => { if (options.ensure_changes) { if (err) callback(err); else callback(null, result); } }); }) .catch(callback); } }; let usePatch = options.isPatch; let xss_whitelist = (options.xss_whitelist) ? options.xss_whitelist : this.xss_whitelist; let originalId = options.updatedoc._id; options.updatedoc.updatedat = new Date(); options.updatedoc = (depopulate) ? utility.depopulate(options.updatedoc) : options.updatedoc; options.updatedoc._id = originalId; options.updatedoc = utility.enforceXSSRules(options.updatedoc, xss_whitelist, options); let updateOperation = (usePatch) ? GENERATE_PATCH(options.updatedoc) : GENERATE_PUT(options.updatedoc); let Model = options.model || this.model; Promise.resolve(options.originalrevision) .then(originalDoc => { if (originalDoc) { return changesetData.original; } else if (options.track_changes) { // console.log({ docid: options.id, }) return this.load({ docid: options.id, }); } else { return {}; } }) .then(originalDoc => { changesetData.original = (originalDoc && typeof originalDoc.toObject === 'function') ? originalDoc.toObject() : originalDoc; Promisie.parallel({ update: Promisie.promisify(Model.updateOne, Model)({ _id: options.id, }, updateOperation), changes: Promisie.promisify(generateChanges)(), }) .then(result => { if (options.ensure_changes) cb(null, result); else cb(null, result.update); }, cb); }) .catch(cb); } catch (e) { cb(e); } }; /** * Convenience method for .findAndUpdate mongoose method (returns updated document instead of normal mongo update status object) * @param {Object} options Configurable options for mongo update * @param {Boolean} options.isPatch If true the update will be treated as a patch instead of a full document update * @param {Object} options.updatedoc Either specific fields to update in the case of a patch otherwise the entire updated document * @param {string} options.id The mongo _id of the document that should be updated * @param {Boolean} [options.skip_xss] If true xss character escaping will be skipped and xss whitelist is ignored * @param {Boolean} [options.html_xss] If true xss npm module will be used for character escaping * @param {Boolean} [options.track_changes] If false changes will not be tracked * @param {Boolean} [options.ensure_changes] If true changeset generation and saving is blocking and errors will cause entire operation to fail * @param {Object} [options.model=this.model] The mongoose model for query will default to the this.model value if not defined * @param {Function} cb Callback function for update */ const _UPDATED = function(options, cb) { try { _UPDATE.call(this, options, (err) => { if (err) cb(err); else _LOAD.call(this, { model: options.model, query: options.id, }, cb); }); } catch (e) { cb(e); } }; /** * Convenience method for .update with the multi options set to true for multiple document updates * @param {Object} options Configurable options for mongo update with multi true * @param {Object} [options.model=this.model] The mongoose model for query will default to the this.model value if not defined * @param {Object} options.query Query that should be used in update * @param {Object} [options.updatequery] Alias for options.query if options.query is set this option is ignored * @param {Object} options.updateattributes A mongo update formatted object * @param {Object} [options.updatedoc] Object specifying fields to update with new values this object will be formatted as a patch update. If options.updateattributes is set this option is ignored * @param {Function} cb Callback function for update all */ const _UPDATE_ALL = function(options, cb) { try { let Model = options.model || this.model; let query = options.query || options.updatequery; let patch; if (options.updateattributes && typeof options.updateattributes === 'object') patch = options.updateattributes; else if (options.updatedoc && typeof options.updatedoc === 'object') patch = GENERATE_PATCH(options.updatedoc); else throw new Error('Either updateattributes or updatedoc option must be set in order to execute multi update'); Model.updateMany(query, patch, { multi: true, }, cb); } catch (e) { cb(e); } }; /** * Convenience method for .create mongoose method * @param {Object} options Configurable options for mongo create * @param {Object} [options.model=this.model] The mongoose model for query will default to the this.model value if not defined * @param {Object|Object[]} [options.newdoc=options] The document that should be created. If newdoc option is not passed it is assumed that the entire options object is the document * @param {Boolean} options.bulk_create If true and options.newdoc is an array each index will be treated as an individual document and be bulk inserted * @param {Boolean} [options.skip_xss] If true xss character escaping will be skipped and xss whitelist is ignored * @param {Boolean} [options.html_xss] If true xss npm module will be used for character escaping * @param {Object} [options.xss_whitelist=this.xss_whitelist] XSS white-list configuration for xss npm module * @param {Function} cb Callback function for create */ const _CREATE = function(options, cb) { try { let Model = options.model || this.model; let newdoc = options.newdoc || options; let xss_whitelist = (options.xss_whitelist) ? options.xss_whitelist : this.xss_whitelist; if (Array.isArray(newdoc) && options.bulk_create) { Promisie.map(newdoc, (doc) => { return Promisie.promisify(Model.create, Model)(utility.enforceXSSRules(doc, xss_whitelist, options)); }) .then(created => cb(null, created)) .catch(cb); } else Model.create(utility.enforceXSSRules(newdoc, xss_whitelist, (options.newdoc) ? options : undefined), cb); } catch (e) { cb(e); } }; /** * Convenience method for .remove mongoose method * @param {Object} options Configurable options for mongo delete * @param {Object} [options.model=this.model] The mongoose model for query will default to the this.model value if not defined * @param {string} options.deleteid The mongo id of the document that should be removed * @param {string} options.id If options.deleteid is provided this value is ignored - alias for options.deleteid * @param {Function} cb Callback function for delete */ const _DELETE = function(options, cb) { try { let Model = options.model || this.model; if (options.query) { Model.deleteOne(options.query, cb); } else { let deleteid = options.deleteid || options.id; if (typeof deleteid !== 'string') throw new Error('Must specify "deleteid" or "id" for delete'); Model.deleteOne({ _id: deleteid, }, cb); } } catch (e) { cb(e); } }; /** * Convenience method for .remove mongoose method but returns the deleted document * @param {Object} options Configurable options for mongo delete * @param {Object} [options.model=this.model] The mongoose model for query will default to the this.model value if not defined * @param {string} options.deleteid The mongo id of the document that should be removed * @param {string} options.id If options.deleteid is provided this value is ignored - alias for options.deleteid * @param {Function} cb Callback function for delete */ const _DELETED = function(options, cb) { try { _LOAD.call(this, { model: options.model, query: options.query || options.deleteid || options.id, }, (err1, loaded) => { if (err1) cb(err1); else { _DELETE.call(this, options, (err2) => { if (err2) cb(err2); else cb(null, loaded); }); } }); } catch (e) { cb(e); } }; /** * A mongoose specific adapter which provides CRUD methods for a given model * @type Mongo_Adapter */ const MONGO_ADAPTER = class Mongo_Adapter { /** * @constructor * @param {Object} [options={}] Configurable options for the mongo adapter * @param {string} options.docid Specifies the field which should be queried by default for .load * @param {Object} options.model Mongoose model that should be used in CRUD operations by default * @param {Object|string} [options.sort="-createdat"] Specifies default sort logic for .query and .search queries * @param {Object} [options.db_connection=mongoose] A custom mongoose db instance if connecting to a different mongoose instance. Will default to cached mongoose connection if not passed. If this option is defined the changeset scheam will be registered on this instance. * @param {number} [options.limit=500] Specifies a default limit to the total documents returned in a .query and .search queries * @param {number} [options.skip=0] Specifies a default amount of documents to skip in a .query and .search queries * @param {Object|string} [options.population] Optional population configuration for documents returned in .load and .search queries * @param {Object} [options.fields] Optional configuration for limiting fields that are returned in .load and .search queries * @param {number} [options.pagelength=15] Specifies max number of documents that should appear in each sub-set for pagination * @param {Boolean} [options.track_changes=true] Sets default track changes behavior for udpates * @param {string[]} [options.xss_whitelist=false] Configuration for XSS whitelist package. If false XSS whitelisting will be ignored */ constructor(options = { }) { this.adapter_type = 'mongo'; this.use_changes = typeof options.use_changes ==='boolean'? options.use_changes :true; this.db_connection = options.db_connection || mongoose; this.docid = options.docid; //previously docnamelookup this.model = (typeof options.model === 'string') ? this.db_connection.model(options.model) : options.model; this.sort = options.sort || '-createdat'; this.limit = options.limit || 500; this.skip = options.skip || 0; if (Array.isArray(options.search)) this.searchfields = options.search; else if (typeof options.search === 'string') this.searchfields = options.search.split(','); else this.searchfields = []; if(Array.isArray(options.plugins)) { options.plugins.forEach(plugin => { this.model.schema.plugin(plugin.func, plugin.options) ; }); } this.population = options.population; this.fields = options.fields; this.pagelength = options.pagelength || 15; this.cache = options.cache; this.track_changes = (options.track_changes === false) ? false : true; if (this.use_changes && this.track_changes) { this.changeset = (options.db_connection) ? require(path.join(__dirname, '../changeset/index')).mongo(this.db_connection) // ? require(path.join(__dirname, '../changeset/index')).mongo_default : require(path.join(__dirname, '../changeset/index')).mongo_default; } this.xss_whitelist = options.xss_whitelist || xss_default_whitelist; this._useCache = (options.useCache && options.cache) ? true : false; } /** * Query method for adapter see _QUERY and _QUERY_WITH_PAGINATION for more details * @param {Object} [options={}] Configurable options for query * @param {Boolean} options.paginate When true query will return data in a paginated form * @param {Function} [cb=false] Callback argument. When cb is not passed function returns a Promise * @return {Object} Returns a Promise when cb argument is not passed */ query(options = {}, cb = false) { let _query = (options && options.paginate) ? _QUERY_WITH_PAGINATION.bind(this) : _QUERY.bind(this); if (typeof cb === 'function') _query(options, cb); else return Promisie.promisify(_query)(options); } /** * Search method for adapter see _SEARCH for more details * @param {Object} [options={}] Configurable options for query * @param {Function} [cb=false] Callback argument. When cb is not passed function returns a Promise * @return {Object} Returns a Promise when cb argument is not passed */ search(options = {}, cb = false) { let _search = _SEARCH.bind(this); if (typeof cb === 'function') _search(options, cb); else return Promisie.promisify(_search)(options); } /** * Stream method for adapter see _STREAM for more details * @param {Object} [options={}] Configurable options for stream * @param {Function} [cb=false] Callback argument. When cb is not passed function returns a Promise * @return {Object} Returns a Promise when cb argument is not passed */ stream(options = {}, cb = false) { let _stream = _STREAM.bind(this); if (typeof cb === 'function') _stream(options, cb); else return Promisie.promisify(_stream)(options); } /** * Load method for adapter see _LOAD for more details * @param {Object} [options={}] Configurable options for load * @param {Function} [cb=false] Callback argument. When cb is not passed function returns a Promise * @return {Object} Returns a Promise when cb argument is not passed */ load(options = {}, cb = false) { let _load = _LOAD.bind(this); if (typeof cb === 'function') _load(options, cb); else return Promisie.promisify(_load)(options); } /** * Update method for adapter see _UPDATE, _UPDATED and _UPDATE_ALL for more details * @param {Object} [options={}] Configurable options for update * @param {Boolean} options.return_updated If true update method will return the updated document instead of an update status message * @param {Boolean} options.multi If true a multiple document update will be perfomed * @param {Function} [cb=false] Callback argument. When cb is not passed function returns a Promise * @return {Object} Returns a Promise when cb argument is not passed */ update(options = {}, cb = false) { let _update = (options.multi) ? _UPDATE_ALL.bind(this) : ((options.return_updated) ? _UPDATED.bind(this) : _UPDATE.bind(this)); if (typeof cb === 'function') _update(options, cb); else return Promisie.promisify(_update)(options); } /** * Create method for adapter see _CREATE for more details * @param {Object} [options={}] Configurable options for create * @param {Function} [cb=false] Callback argument. When cb is not passed function returns a Promise * @return {Object} Returns a Promise when cb argument is not passed */ create(options = {}, cb = false) { let _create = _CREATE.bind(this); if (typeof cb === 'function') _create(options, cb); else return Promisie.promisify(_create)(options); } /** * Delete method for adapter see _DELETE and _DELETED for more details * @param {Object} [options={}] Configurable options for create * @param {Boolean} options.return_deleted If true delete method will return the deleted document * @param {Function} [cb=false] Callback argument. When cb is not passed function returns a Promise * @return {Object} Returns a Promise when cb argument is not passed */ delete(options = {}, cb = false) { let _delete = (options.return_deleted) ? _DELETED.bind(this) : _DELETE.bind(this); if (typeof cb === 'function') _delete(options, cb); else return Promisie.promisify(_delete)(options); } }; module.exports = MONGO_ADAPTER;