UNPKG

mongoose-paginate-v2

Version:

A custom pagination library for Mongoose with customizable labels.

351 lines (337 loc) 14 kB
"use strict"; var _excluded = ["limit", "skip"]; function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } function _objectWithoutProperties(e, t) { if (null == e) return {}; var o, r, i = _objectWithoutPropertiesLoose(e, t); if (Object.getOwnPropertySymbols) { var n = Object.getOwnPropertySymbols(e); for (r = 0; r < n.length; r++) o = n[r], -1 === t.indexOf(o) && {}.propertyIsEnumerable.call(e, o) && (i[o] = e[o]); } return i; } function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; } function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; } function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } /** * @param {Object} [query={}] * @param {Object} [options={}] * @param {Object|String} [options.select=''] * @param {Object|String} [options.projection={}] * @param {Object} [options.options={}] * @param {Object|String} [options.sort] * @param {Object|String} [options.customLabels] * @param {Object} [options.collation] * @param {Array|Object|String} [options.populate] * @param {Boolean} [options.lean=false] * @param {Boolean} [options.leanWithId=true] * @param {Boolean} [options.leanWithVirtuals=false] * @param {Number} [options.offset=0] - Use offset or page to set skip position * @param {Number} [options.page=1] * @param {Number} [options.limit=10] * @param {Boolean} [options.useEstimatedCount=true] - Enable estimatedDocumentCount for larger datasets. As the name says, the count may not abe accurate. * @param {(function(query: Object=): Promise<number>)} [options.useCustomCountFn=false] - Use custom function for count datasets. Receives `query` as an optional argument. * @param {Object} [options.read={}] - Determines the MongoDB nodes from which to read. * @param {Function} [callback] * * @returns {Promise} */ var PaginationParametersHelper = require('./pagination-parameters'); var paginateSubDocsHelper = require('./pagination-subdocs'); var paginateQueryHelper = require('./pagination-queryHelper'); var defaultOptions = { customLabels: { totalDocs: 'totalDocs', limit: 'limit', page: 'page', totalPages: 'totalPages', docs: 'docs', nextPage: 'nextPage', prevPage: 'prevPage', pagingCounter: 'pagingCounter', hasPrevPage: 'hasPrevPage', hasNextPage: 'hasNextPage', meta: null }, collation: {}, lean: false, leanWithId: true, leanWithVirtuals: false, limit: 10, projection: {}, select: '', options: {}, pagination: true, useEstimatedCount: false, useCustomCountFn: false, forceCountFn: false, allowDiskUse: false, customFind: 'find' }; function hasOwn(obj, key) { return Object.prototype.hasOwnProperty.call(obj || {}, key); } function isSessionInTransaction(session) { if (session == null) { return false; } if (typeof session.inTransaction === 'function') { return session.inTransaction(); } // Older driver sessions may expose the driver session via `session.session`. var driverSession = session.session; if (driverSession && typeof driverSession.inTransaction === 'function') { return driverSession.inTransaction(); } // Pre-inTransaction drivers: conservatively serialize because we cannot // determine whether the session is transactional. These drivers are too old // to support causal sessions reliably anyway. return true; } function hasActiveSession(model, queryOptions) { if (hasOwn(queryOptions, 'session')) { return isSessionInTransaction(queryOptions.session); } var base = model && model.db && model.db.base; var als = base && base.transactionAsyncLocalStorage; if (!als || typeof als.getStore !== 'function') { return false; } var store = als.getStore(); return isSessionInTransaction(store && store.session); } function paginate(query, options, callback) { options = _objectSpread(_objectSpread(_objectSpread({}, defaultOptions), paginate.options), options); query = query || {}; var _options = options, collation = _options.collation, lean = _options.lean, leanWithId = _options.leanWithId, leanWithVirtuals = _options.leanWithVirtuals, populate = _options.populate, projection = _options.projection, read = _options.read, select = _options.select, sort = _options.sort, pagination = _options.pagination, useEstimatedCount = _options.useEstimatedCount, useCustomCountFn = _options.useCustomCountFn, forceCountFn = _options.forceCountFn, allowDiskUse = _options.allowDiskUse, customFind = _options.customFind; var customLabels = _objectSpread(_objectSpread({}, defaultOptions.customLabels), options.customLabels); var limit = defaultOptions.limit; if (pagination && !isNaN(Number(options.limit))) { limit = parseInt(options.limit, 10) > 0 ? parseInt(options.limit, 10) : 0; } var isCallbackSpecified = typeof callback === 'function'; var findOptions = options.options; // Create queryOptions with collation included to preserve session context in transactions var queryOptions = Object.keys(collation).length > 0 ? _objectSpread(_objectSpread({}, findOptions), {}, { collation }) : findOptions; // For countDocuments, exclude limit/skip which MongoDB applies to the count result // eslint-disable-next-line no-unused-vars var _ref = queryOptions || {}, _l = _ref.limit, _s = _ref.skip, countOptions = _objectWithoutProperties(_ref, _excluded); var offset; var page; var skip; var docsPromise = []; // Labels var labelDocs = customLabels.docs; var labelLimit = customLabels.limit; var labelNextPage = customLabels.nextPage; var labelPage = customLabels.page; var labelPagingCounter = customLabels.pagingCounter; var labelPrevPage = customLabels.prevPage; var labelTotal = customLabels.totalDocs; var labelTotalPages = customLabels.totalPages; var labelHasPrevPage = customLabels.hasPrevPage; var labelHasNextPage = customLabels.hasNextPage; var labelMeta = customLabels.meta; if (Object.prototype.hasOwnProperty.call(options, 'offset')) { offset = parseInt(options.offset, 10); skip = offset; } else if (Object.prototype.hasOwnProperty.call(options, 'page')) { page = parseInt(options.page, 10) > 0 ? parseInt(options.page, 10) : 1; skip = (page - 1) * limit; } else { offset = 0; page = 1; skip = offset; } if (!pagination) { page = 1; } var countPromise; // Only run count when pagination is enabled if (pagination) { if (forceCountFn === true) { // Deprecated since starting from MongoDB Node.JS driver v3.1 // Используем estimatedDocumentCount, если query пустой, иначе countDocuments if (!query || Object.keys(query).length === 0) { countPromise = this.estimatedDocumentCount().exec(); } else { countPromise = this.countDocuments(query, countOptions).exec(); } } else { if (useEstimatedCount === true) { countPromise = this.estimatedDocumentCount().exec(); } else if (typeof useCustomCountFn === 'function') { countPromise = useCustomCountFn(query); } else { // Используем estimatedDocumentCount, если query пустой, иначе countDocuments if (!query || Object.keys(query).length === 0) { countPromise = this.estimatedDocumentCount().exec(); } else { countPromise = this.countDocuments(query, countOptions).exec(); } } } } var model = this; function buildDocsPromise() { if (!limit) { return Promise.resolve([]); } // Use queryOptions (which includes collation) to preserve session context in transactions var mQuery = model[customFind](query, projection, queryOptions); if (populate) { mQuery.populate(populate); } mQuery.select(select); mQuery.sort(sort); if (lean) { // use whit mongoose-lean-virtuals if (leanWithVirtuals) { mQuery.lean({ virtuals: leanWithVirtuals }); } else { mQuery.lean(lean); } } if (read && read.pref) { /** * Determines the MongoDB nodes from which to read. * @param read.pref one of the listed preference options or aliases * @param read.tags optional tags for this query */ mQuery.read(read.pref, read.tags); } if (pagination) { mQuery.skip(skip); mQuery.limit(limit); } try { if (allowDiskUse === true) { mQuery.allowDiskUse(); } } catch (ex) { console.error('Your MongoDB version does not support `allowDiskUse`.'); } var execPromise = mQuery.exec(); if (lean && leanWithId) { execPromise = execPromise.then(function (docs) { docs.forEach(function (doc) { if (doc._id) { doc.id = String(doc._id); } }); return docs; }); } return execPromise; } function buildResult(count, docs) { if (pagination !== true) { count = docs.length; } var meta = { [labelTotal]: count }; var result; if (typeof offset !== 'undefined') { meta.offset = offset; page = Math.ceil((offset + 1) / limit); } var pages = limit > 0 ? Math.ceil(count / limit) || 1 : null; // Setting default values if (labelLimit) meta[labelLimit] = count; if (labelTotalPages) meta[labelTotalPages] = 1; if (labelPage) meta[labelPage] = page; if (labelPagingCounter) meta[labelPagingCounter] = (page - 1) * limit + 1; if (labelHasPrevPage) meta[labelHasPrevPage] = false; if (labelHasNextPage) meta[labelHasNextPage] = false; if (labelPrevPage) meta[labelPrevPage] = null; if (labelNextPage) meta[labelNextPage] = null; if (pagination) { if (labelLimit) meta[labelLimit] = limit; if (labelTotalPages) meta[labelTotalPages] = pages; // Set prev page if (page > 1) { if (labelHasPrevPage) meta[labelHasPrevPage] = true; if (labelPrevPage) meta[labelPrevPage] = page - 1; } else if (page == 1 && typeof offset !== 'undefined' && offset !== 0) { if (labelHasPrevPage) meta[labelHasPrevPage] = true; if (labelPrevPage) meta[labelPrevPage] = 1; } // Set next page if (page < pages) { if (labelHasNextPage) meta[labelHasNextPage] = true; if (labelNextPage) meta[labelNextPage] = page + 1; } } // Remove customLabels set to false delete meta['false']; if (limit == 0) { if (labelLimit) meta[labelLimit] = 0; if (labelTotalPages) meta[labelTotalPages] = 1; if (labelPage) meta[labelPage] = 1; if (labelPagingCounter) meta[labelPagingCounter] = 1; if (labelPrevPage) meta[labelPrevPage] = null; if (labelNextPage) meta[labelNextPage] = null; if (labelHasPrevPage) meta[labelHasPrevPage] = false; if (labelHasNextPage) meta[labelHasNextPage] = false; } if (labelMeta) { result = { [labelDocs]: docs, [labelMeta]: meta }; } else { result = _objectSpread({ [labelDocs]: docs }, meta); } return result; } var hasSession = hasActiveSession(model, queryOptions); var resultPromise; if (hasSession) { resultPromise = Promise.resolve(countPromise).then(function (count) { return buildDocsPromise().then(function (docs) { return buildResult(count, docs); }); }); } else { docsPromise = buildDocsPromise(); resultPromise = Promise.all([countPromise, docsPromise]).then(function (values) { return buildResult(values[0], values[1]); }); } return resultPromise.then(function (result) { return isCallbackSpecified ? callback(null, result) : Promise.resolve(result); }).catch(function (error) { return isCallbackSpecified ? callback(error) : Promise.reject(error); }); } /** * @param {Schema} schema */ module.exports = function (schema) { schema.statics.paginate = paginate; schema.statics.paginateSubDocs = paginateSubDocsHelper; schema.query.paginate = paginateQueryHelper; }; module.exports.PaginationParameters = PaginationParametersHelper; module.exports.paginateSubDocs = paginateSubDocsHelper; module.exports.paginate = paginate;