UNPKG

think-mongo

Version:
634 lines (614 loc) 15.6 kB
function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; } const Query = require('./query.js'); const path = require('path'); const helper = require('think-helper'); const { ObjectID } = require('mongodb'); const MODELS = Symbol('think-models'); const DB = Symbol('think-model-db'); class Mongo { /** * constructor * @param {String} modelName * @param {Object} config */ constructor(modelName, config) { if (helper.isObject(modelName)) { [modelName, config] = [config, {}]; } this.modelName = modelName; this.config = config; this.options = {}; } /** * get all store models */ get models() { return this[MODELS] || {}; } /** * set models */ set models(value) { this[MODELS] = value; } /** * get primary key */ get pk() { return this._pk || '_id'; } /** * get table prefix */ get tablePrefix() { return this.config.prefix || ''; } /** * get table name, with table prefix */ get tableName() { return this.tablePrefix + this.modelName; } /** * get model instance * @param {String} name */ mongo(name) { const ModelClass = this.models[name]; const modelName = path.basename(name); let instance; if (ModelClass) { instance = new ModelClass(modelName, this.config); } else { instance = new Mongo(modelName, this.config); } instance.models = this.models; return instance; } /** * this.mongo alias */ model(...args) { return this.mongo(...args); } /** * get or set adapter * @param {Object} connection */ db(db) { if (db) { this[DB] = db; return this; } if (this[DB]) return this[DB]; const instance = new Query(this.config); this[DB] = instance; return instance; } /** * set limit options * @param {Number} offset [] * @param {Number} length [] * @return {} [] */ limit(offset, length) { if (offset === undefined) return this; if (helper.isArray(offset)) { length = offset[1] || length; offset = offset[0]; } offset = Math.max(parseInt(offset) || 0, 0); if (length) { length = Math.max(parseInt(length) || 0, 0); } this.options.limit = [offset, length]; return this; } /** * set page options * @param {Number} page [] * @param {} listRows [] * @return {} [] */ page(page, listRows = this.config.pagesize) { if (helper.isArray(page)) { listRows = page[1] || listRows; page = page[0]; } page = Math.max(parseInt(page) || 1, 1); listRows = Math.max(parseInt(listRows) || 10, 1); this.options.limit = [listRows * (page - 1), listRows]; return this; } /** * set where options * @return {} [] */ where(where) { if (!where) return this; const options = this.options; options.where = helper.extend({}, options.where, where); return this; } /** * set field options * @param {String} field [] * @return {} [] */ field(field) { if (!field) return this; this.options.field = field; return this; } /** * set table name * @param {String} table [] * @return {} [] */ table(table, hasPrefix = false) { if (!table) return this; table = table.trim(); if (!hasPrefix) { table = this.tablePrefix + table; } this.options.table = table; return this; } /** * set order options * @param {String} value [] * @return {} [] */ order(value) { this.options.order = value; return this; } /** * set group options * @param {String} value [] * @return {} [] */ group(value) { this.options.group = value; return this; } /** * set distinct options * @param {String} data [] * @return {} [] */ distinct(data) { this.options.distinct = data; if (helper.isString(data)) { this.options.field = data; } return this; } /** * before add * @param {Object} data [] * @return {} [] */ beforeAdd(data) { return data; } /** * after add * @param {} data [] * @return {} [] */ afterAdd(data) { return data; } /** * before delete */ beforeDelete(options) { return options; } /** * after delete * @param {Mixed} data [] * @return {} [] */ afterDelete(data) { return data; } /** * before update * @param {Mixed} data [] * @return {} [] */ beforeUpdate(data) { return data; } /** * after update * @param {} data [] * @return {} [] */ afterUpdate(data) { return data; } /** * before find */ beforeFind(options) { return options; } /** * after find * @return {} [] */ afterFind(data) { return data; } /** * before select */ beforeSelect(options) { return options; } /** * after select * @param {Mixed} result [] * @return {} [] */ afterSelect(data) { return data; } /** * parse options * @param {Object} options */ parseOptions(options) { options = Object.assign({}, this.options, options); this.options = {}; if (!options.table) { options.table = this.tableName; } options.tablePrefix = this.tablePrefix; options.model = this.modelName; return options; } /** * parse data * @param {Object} data [] * @return {Object} [] */ parseData(data) { return data; } /** * add data * @param {Object} data [] * @param {Object} options [] */ add(data, options) { var _this = this; return _asyncToGenerator(function* () { // copy data data = Object.assign({}, data); if (helper.isEmpty(data)) { return Promise.reject(new Error('add data is empty')); } options = _this.parseOptions(options); data = yield _this.beforeAdd(data, options); data = _this.parseData(data); const insertId = yield _this.db().add(data, options); const copyData = Object.assign({}, data, { [_this.pk]: insertId }); yield _this.afterAdd(copyData, options); return insertId; })(); } /** * then add * @param {Object} data [] * @param {Object} where [] * @return {} [] */ thenAdd(data, where) { var _this2 = this; return _asyncToGenerator(function* () { const findData = yield _this2.where(where).find(); if (!helper.isEmpty(findData)) { return { [_this2.pk]: findData[_this2.pk], type: 'exist' }; } const insertId = yield _this2.add(data); return { [_this2.pk]: insertId, type: 'add' }; })(); } /** * update data when exist, otherwise add data * @return {id} */ thenUpdate(data, where) { var _this3 = this; return _asyncToGenerator(function* () { const findData = yield _this3.where(where).find(); if (helper.isEmpty(findData)) { return _this3.add(data); } yield _this3.where(where).update(data); return findData[_this3.pk]; })(); } /** * add multi data * @param {Object} data [] * @param {} options [] * @param {} replace [] */ addMany(data, options) { var _this4 = this; return _asyncToGenerator(function* () { if (!helper.isArray(data) || !helper.isObject(data[0])) { return Promise.reject(new Error('addMany must be an array')); } options = _this4.parseOptions(options); data = yield _this4.beforeAdd(data, options); const insertIds = yield _this4.db().addMany(data, options); yield _this4.afterAdd(data, options); return insertIds; })(); } /** * delete data * @return {} [] */ delete(options) { var _this5 = this; return _asyncToGenerator(function* () { options = _this5.parseOptions(options); options = yield _this5.beforeDelete(options); const data = yield _this5.db().delete(options); yield _this5.afterDelete(options); return data.result.n || 0; })(); } /** * update data * @return {Promise} [] */ update(data, options, ignoreDefault) { var _this6 = this; return _asyncToGenerator(function* () { if (helper.isBoolean(options)) { [ignoreDefault, options] = [options, {}]; } const pk = _this6.pk; if (data[pk]) { _this6.where({ [pk]: data[pk] }); delete data[pk]; } options = _this6.parseOptions(options); if (ignoreDefault !== true) { data = yield _this6.beforeUpdate(data, options); } const result = yield _this6.db().update(data, options); yield _this6.afterUpdate(data, options); return result.result.nModified || 0; })(); } /** * update many data * @param {Promise} dataList [] * @return {Promise} [] */ updateMany(dataList, options) { var _this7 = this; return _asyncToGenerator(function* () { if (!helper.isArray(dataList)) { return Promise.reject(new Error('dataList must be an array')); } const promises = dataList.map(function (data) { return _this7.update(data, options); }); return Promise.all(promises).then(function (data) { return data.reduce(function (a, b) { return a + b; }); }); })(); } /** * select data * @return {Promise} [] */ select(options) { var _this8 = this; return _asyncToGenerator(function* () { options = _this8.parseOptions(options); options = yield _this8.beforeSelect(options); const data = yield _this8.db().select(options); return _this8.afterSelect(data, options); })(); } /** * count select * @param {Object} options [] * @param {Boolean} pageFlag [] * @return {Promise} [] */ countSelect(options, pageFlag) { var _this9 = this; return _asyncToGenerator(function* () { let count; if (helper.isBoolean(options)) { [pageFlag, options] = [options, {}]; } else if (helper.isNumber(options)) { [count, options] = [options, {}]; } options = _this9.parseOptions(options); if (!count) { // get count _this9.options = options; count = yield _this9.count(); } options.limit = options.limit || [0, _this9.config.pagesize]; const pagesize = options.limit[1]; // get page options const data = { pageSize: pagesize }; data.currentPage = parseInt(options.limit[0] / options.limit[1] + 1); const totalPage = Math.ceil(count / data.pageSize); if (helper.isBoolean(pageFlag) && data.currentPage > totalPage) { if (pageFlag) { data.currentPage = 1; options.limit = [0, pagesize]; } else { data.currentPage = totalPage; options.limit = [(totalPage - 1) * pagesize, pagesize]; } } const result = Object.assign({ count: count, totalPages: totalPage }, data); result.data = count ? yield _this9.select(options) : []; return result; })(); } /** * select one row data * @param {Object} options [] * @return {Promise} [] */ find(options) { var _this10 = this; return _asyncToGenerator(function* () { _this10.limit(1); options = _this10.parseOptions(options); options = yield _this10.beforeFind(options); const data = yield _this10.db().select(options); return _this10.afterFind(data[0] || {}, options); })(); } /** * increment field data * @param {String} field [] * @param {Number} step [] * @return {Promise} [] */ increment(field, step = 1) { const options = this.parseOptions(); return this.db().update({ $inc: { [field]: step } }, options).then(data => { return data.result.n; }); } /** * decrement field data * @param {String} field [] * @param {Number} step [] * @return {Promise} [] */ decrement(field, step = 1) { const options = this.parseOptions(); return this.db().update({ $inc: { [field]: 0 - step } }, options).then(data => { return data.result.n; }); } /** * get count * @param {String} field [] * @param {Object} options * @return {Promise} [] */ count(field, options) { if (helper.isObject(field) && !options) { options = field; field = undefined; } this.field(field); options = this.parseOptions(options); return this.db().count(options); } /** * get sum * @param {String} field [] * @param {Object} options * @return {Promise} [] */ sum(field, options) { if (helper.isObject(field) && !options) { options = field; field = undefined; } this.field(field); options = this.parseOptions(options); return this.db().sum(options); } /** * aggregate * http://docs.mongodb.org/manual/reference/sql-aggregation-comparison/ * @param {} options [] * @return {} [] */ aggregate(pipeline, options) { return this.db().aggregate(this.tableName, pipeline, options); } /** * map reduce * Examples: http://docs.mongodb.org/manual/tutorial/map-reduce-examples/ * @param {Function} map [] * @param {Function} reduce [] * @param {Object} out [] * @return {Promise} [] */ mapReduce(map, reduce, out) { const table = this.tableName; return this.db().socket.autoRelease(connection => { const collection = connection.collection(table); return collection.mapReduce(map, reduce, out); }); } /** * create indexes * @param {Object} indexes [] * @return {Promise} [] */ createIndex(indexes, options) { return this.db().ensureIndex(this.tableName, indexes, options); } /** * get collection indexes * @return {Promise} [] */ getIndexes() { const table = this.tableName; return this.db().socket.autoRelease(connection => { const collection = connection.collection(table); return collection.indexes(); }); } transaction(fn, transactionOptions = {}) { var _this11 = this; return _asyncToGenerator(function* () { const connect = _this11.db().socket; const client = yield connect.getConnection(); const session = _this11.options.session || client.startSession(); try { session.startTransaction(transactionOptions); _this11.options.session = session; // fn 中的其他表操作需要手动增加 options.session 赋值 // const UserModel = this.mongo('user'); // UserModel.options.session = session; yield fn(session, client); yield session.commitTransaction(); } catch (err) { yield session.abortTransaction(); throw err; } finally { yield session.endSession(); _this11.options.session = null; yield connect.release(client); } })(); } }; Mongo.ObjectID = ObjectID; module.exports = Mongo;