think-mongo
Version:
634 lines (614 loc) • 15.6 kB
JavaScript
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;