@conpago/mongo-cursor-pagination
Version:
Make it easy to return cursor-paginated results from a Mongo collection
110 lines • 6.22 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const underscore_1 = require("underscore");
const aggregateMulti_1 = __importDefault(require("./aggregateMulti"));
const config_1 = __importDefault(require("./config"));
const query_1 = require("./utils/query");
const sanitizeParams_1 = require("./utils/sanitizeParams");
/**
* Performs a find() query on a passed-in Mongo collection, using criteria you specify. The results
* are ordered by the paginatedField.
*
* @param {Collection} collection A collection object returned from the MongoDB library's.
* @param {QueryParamsMulti} params
* @param {object} params.query The find query.
* @param {Number} params.limit The page size. Must be between 1 and `config.MAX_LIMIT`.
* @param {object} params.fields Fields to query in the Mongo object format, e.g. {_id: 1, timestamp :1}.
* The default is to query all fields.
* @param {String} params.paginatedField The field name to query the range for. The field must be:
* 1. Orderable. We must sort by this value. If duplicate values for paginatedField field
* exist, the results will be secondarily ordered by the _id.
* 2. Indexed. For large collections, this should be indexed for query performance.
* 3. Immutable. If the value changes between paged queries, it could appear twice.
4. Consistent. All values (except undefined and null values) must be of the same type.
* The default is to use the Mongo built-in '_id' field, which satisfies the above criteria.
* The only reason to NOT use the Mongo _id field is if you chose to implement your own ids.
* @param {boolean} params.sortAscending Whether to sort in ascending order by the `paginatedField`.
* @param {boolean} params.sortCaseInsensitive Whether to ignore case when sorting, in which case `paginatedField`
* @param {boolean} params.getTotal Whether to fetch the total count for the query results
* must be a string property.
* @param {String} params.next The value to start querying the page.
* @param {String} params.previous The value to start querying previous page.
* @param {String} params.after The _id to start querying the page.
* @param {String} params.before The _id to start querying previous page.
* @param {String} params.hint An optional index hint to provide to the mongo query
* @param {object} params.collation An optional collation to provide to the mongo query. E.g. { locale: 'en', strength: 2 }. When null, disables the global collation.
*/
exports.default = async (collection, params) => {
const projectedFields = params.fields;
let response = {};
const isCaseInsensitive = params.paginatedFields?.some(pf => pf.sortCaseInsensitive);
// For case-insensitive sorting, we need to work with an aggregation:
if (isCaseInsensitive || params.aggregationSearch) {
const query = (() => {
if (params.aggregationSearch) {
return Array.isArray(params.query) ? params.query : [];
}
return params.query ? [{ $match: params.query }] : [];
})();
response = await (0, aggregateMulti_1.default)(collection, Object.assign({}, params, {
aggregation: query,
}));
if (params.getTotal) {
if (params.aggregationSearch) {
// NOTE: this mimics the behavior of countDocuments, but we can pass our aggregation first
// https://www.mongodb.com/docs/manual/reference/method/db.collection.countDocuments/
const result = await collection
.aggregate([...query, { $group: { _id: null, n: { $sum: 1 } } }])
.toArray();
response.totalCount = result?.[0]?.n ?? 0;
}
else {
response.totalCount = await collection.countDocuments(params.query);
}
}
}
else {
// Need to repeat `params.paginatedField` default value ('_id') since it's set in 'sanitizeParams()'
params = (0, underscore_1.defaults)(await (0, sanitizeParams_1.sanitizeMultiParamsMutate)(collection, params), {
query: {},
});
const cursorQuerys = (0, query_1.generateCursorQueryMulti)(params);
const $sort = (0, query_1.generateSorts)(params);
// Support both the native 'mongodb' driver and 'mongoist'. See:
// https://www.npmjs.com/package/mongoist#cursor-operations
const findMethod = collection.findAsCursor ? "findAsCursor" : "find";
const query = collection[findMethod]()?.project
? collection
.find({ $and: [cursorQuerys, params.query] })
.project(params.fields)
: collection[findMethod]({ $and: [cursorQuerys, params.query] }, params.fields);
/**
* IMPORTANT
*
* If using collation, check the README:
* https://github.com/mixmaxhq/mongo-cursor-pagination#important-note-regarding-collation
*/
const isCollationNull = params.collation === null;
const collation = params.collation || config_1.default.COLLATION;
const collatedQuery = collation && !isCollationNull ? query.collation(collation) : query;
// Query one more element to see if there's another page.
const cursor = collatedQuery.sort($sort).limit(params.limit + 1);
if (params.hint)
cursor.hint(params.hint);
const results = await cursor.toArray();
response = (0, query_1.prepareResponse)(results, params, true);
if (params.getTotal)
response.totalCount = await collection.countDocuments(params.query);
}
// Remove fields that we added to the query (such as paginatedField and _id) that the user didn't ask for.
const projectedResults = (0, query_1.filterProjectedFields)({
projectedFields,
results: response.results,
sortCaseInsensitive: isCaseInsensitive,
});
return { ...response, results: projectedResults };
};
//# sourceMappingURL=findMulti.js.map