Created
September 18, 2019 02:43
-
-
Save imekachi/c8903519449084dc9f170fac60564f92 to your computer and use it in GitHub Desktop.
based on mongoose-paginate npm package, fixed deprecaion warning, add aggregateMode support
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import _ from 'lodash' | |
// This is based on a PR which fixed collection.count deprecation warning | |
// https://github.com/edwardhotchkiss/mongoose-paginate/blob/e807d87b02aca2198f4fb2e5e896e6aad4b77f55/index.js | |
// but only god knows when the PR will get merged, so here we are, create our own fork. | |
// Now with aggregateMode support!!. | |
/** | |
* mongoose-paginate | |
* @param {Object|Array} [query={}] - this must be array when using aggregate mode (options.aggregateMode = true) | |
* @param {Object} [options={}] | |
* @param {Object|String} [options.select] | |
* @param {Object|String} [options.sort] | |
* @param {Array|Object|String} [options.populate] | |
* @param {Boolean} [options.lean=true] | |
* @param {Number} [options.offset=0] - Use offset or page to set skip position | |
* @param {Number} [options.page=1] | |
* @param {Number} [options.limit=10] | |
* @returns {Promise} | |
*/ | |
async function paginate(query = {}, options = {}) { | |
const HARD_LIMIT = 100 | |
const defaultOptions = { | |
aggregateMode: false, | |
lean: true, | |
limit: 10, | |
} | |
// eslint-disable-next-line no-param-reassign | |
options = _.merge({}, defaultOptions, paginate.options, options) | |
// destructuring some variables that can be used directly | |
const { select, sort, populate, lean, aggregateMode } = options | |
// limit won't go pass HARD_LIMIT | |
const limit = Math.min(options.limit, HARD_LIMIT) | |
let page | |
let offset | |
let skip | |
// if using option offset | |
if (options.offset) { | |
skip = Math.max(options.offset, 0) // skip won't go below 0 | |
offset = skip | |
} | |
// if using options page | |
else if (options.page) { | |
page = Math.max(options.page, 1) // page won't go below 1 | |
skip = (page - 1) * limit | |
} | |
// default case | |
else { | |
page = 1 | |
skip = 0 | |
offset = skip | |
} | |
/** | |
* Create query | |
*/ | |
let docsQuery | |
if (aggregateMode) { | |
// The documents returned are plain javascript objects, | |
// not mongoose documents (since any shape of document can be returned). | |
// source: https://mongoosejs.com/docs/api.html#model_Model.aggregate | |
// So, there is no need. | |
docsQuery = this.aggregate(query) | |
// Sort has to be done like this. | |
// - if sort is undefined, mongoose will break internally with error "constructor of undefined" | |
// - if sort is an empty object, mongoose will throw that "$sort stage must have at least one sort key" | |
if (sort && !_.isEmpty(sort)) { | |
docsQuery = docsQuery.sort(sort) | |
} | |
docsQuery = docsQuery.skip(skip).limit(limit) | |
} else { | |
docsQuery = this.find(query) | |
.select(select) | |
.sort(sort) | |
.skip(skip) | |
.limit(limit) | |
.lean(lean) | |
} | |
if (populate && !aggregateMode) { | |
if (_.isArray(populate)) { | |
populate.forEach(item => docsQuery.populate(item)) | |
} else { | |
docsQuery.populate(populate) | |
} | |
} | |
/** | |
* Count documents | |
*/ | |
let countDocumentsQuery | |
if (aggregateMode) { | |
// using group to count documents from aggregate result | |
// source1: https://stackoverflow.com/a/20348364/7789516 | |
// source2: https://github.com/Maheshkumar-Kakade/mongoose-aggregate-paginate/blob/master/lib/mongoose-aggregate-paginate.js#L28 | |
countDocumentsQuery = this.aggregate(query).group({ _id: null, count: { $sum: 1 } }) | |
} else { | |
countDocumentsQuery = this.countDocuments(query) | |
} | |
/** | |
* Execute the query and count documents simultaneously | |
*/ | |
const [docs, itemCount] = await Promise.all([ | |
docsQuery.exec(), | |
// you have to use "countDocuments" here to count the correct number of documents that matches filters | |
// if you use "estimatedDocumentCount", it will count all documents in the collection and doesn't respect the filter | |
countDocumentsQuery.exec(), | |
]) | |
/** | |
* Prepare result object | |
*/ | |
const result = { docs, total: itemCount, limit } | |
// In aggregate mode, itemCount is not a number but an array [{ _id: null, count: number }] | |
if (aggregateMode) { | |
result.total = _.get(itemCount, '0.count', 0) | |
} | |
// adding some properties | |
if (offset !== undefined) { | |
result.offset = offset | |
} | |
if (page !== undefined) { | |
result.page = page | |
result.pages = Math.ceil(result.total / limit) || 1 | |
} | |
return result | |
} | |
/** | |
* @param {Schema} schema | |
*/ | |
function pluginPaginate(schema) { | |
// eslint-disable-next-line no-param-reassign | |
schema.statics.paginate = paginate | |
} | |
export default pluginPaginate |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment