UNPKG

@conpago/mongo-cursor-pagination

Version:

Make it easy to return cursor-paginated results from a Mongo collection

130 lines 5.94 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.sanitizeMultiParamsMutate = sanitizeMultiParamsMutate; const mongodb_1 = require("mongodb"); const config_1 = __importDefault(require("../config")); const bsonUrlEncoding_1 = require("./bsonUrlEncoding"); const getPropertyViaDotNotation_1 = __importDefault(require("./getPropertyViaDotNotation")); exports.default = async (collection, params) => { // // set the params.paginatedField params.paginatedField ??= "_id"; // set the params.limit params.limit = (() => { const requestedLimit = params.limit; if (requestedLimit < 1) return 1; if (requestedLimit > config_1.default.MAX_LIMIT) return config_1.default.MAX_LIMIT; return requestedLimit || config_1.default.DEFAULT_LIMIT; })(); // set params.previous || params.next if (params.previous) params.previous = (0, bsonUrlEncoding_1.decode)(params.previous); if (params.next) params.next = (0, bsonUrlEncoding_1.decode)(params.next); // if after || before in params, overwrite an existing previous || next value if (params.after || params.before) await applyAfterOrBeforeToParams({ collection, params }); // set the params.fields, which are the requested projected fields PLUS the paginated field // (the latter required for sorting and constucting the cursor) if (params.fields) { const { fields: requestedFields, paginatedField } = params; params.fields = { _id: 0, // mongo projects _id by default, so ensure only projecting if user requests ...requestedFields, [paginatedField]: 1, }; } return params; }; async function sanitizeMultiParamsMutate(collection, params) { // add default _id paginate sort if (!params.paginatedFields?.length) { params.paginatedFields = []; params.paginatedFields.push({ paginatedField: "_id", sortAscending: false, sortCaseInsensitive: false, }); // add _id to the end of the fields } else if (!params.paginatedFields.find(f => f.paginatedField === "_id")) { const lastField = params?.paginatedFields[params.paginatedFields?.length - 1]; params.paginatedFields.push({ paginatedField: "_id", sortAscending: lastField?.sortAscending, sortCaseInsensitive: lastField?.sortCaseInsensitive, }); } // set the params.limit params.limit = (() => { const requestedLimit = params.limit; if (requestedLimit < 1) return 1; if (requestedLimit > config_1.default.MAX_LIMIT) return config_1.default.MAX_LIMIT; return requestedLimit || config_1.default.DEFAULT_LIMIT; })(); // set params.previous || params.next if (params.previous) params.previous = (0, bsonUrlEncoding_1.decode)(params.previous); if (params.next) params.next = (0, bsonUrlEncoding_1.decode)(params.next); // if after || before in params, overwrite an existing previous || next value if (params.after || params.before) await applyAfterOrBeforeToParams({ collection, params }); if (params.fields) { const { fields: requestedFields, paginatedFields } = params; params.fields = Object.assign({ _id: 0, }, requestedFields, ...paginatedFields.map(pf => ({ [pf.paginatedField]: 1 }))); } return params; } /** * @description * The 'after' param sets the start position for the next page. This is similar to the * 'next' param, with the difference that 'after' takes a plain _id instead of an encoded * string of both _id and paginatedField values. * > a valid params.after will override params.next value * * The 'before' param sets the start position for the previous page. This is similar to the * 'previous' param, with the difference that 'before' takes a plain _id instead of an encoded * string of both _id and paginatedField values. * > a valid params.before will override params.previous value * * @returns undefined (but may update the given params.next || params.previous with a decoded cursor) */ async function applyAfterOrBeforeToParams({ collection, params, }) { const { after, before, sortCaseInsensitive, paginatedField } = params; if ((after && before) || (!after && !before)) return; // if the primary sort field is the _id, then the results are assured to have a unique // value, and after || before can immediately overwrite the next || previous param if (paginatedField === "_id") { after ? (params.next = after) : (params.previous = before); return; } // otherwise an alternative primary field may have duplicates affecting $gt | $lt sorts, so // will need to be secondarily sorted by _id. As 'after' && 'before' cursors ONLY hold an _id // value, the primary sort value needs to be established to create a valid next | previous cursor const document = await collection.findOne({ _id: new mongodb_1.ObjectId(after || before) }, { [paginatedField]: true, _id: false }); if (!document) return; // retrieve paginated field value (field can be single or dot-notation; such as "user.first_name") const paginatedFieldValue = (0, getPropertyViaDotNotation_1.default)({ propertyName: paginatedField, object: document, sortCaseInsensitive, }); // decoded next | previous cursor expection is [ <value of paginated field in document >, < _id of document >] if (after) params.next = [paginatedFieldValue, after]; if (before) params.previous = [paginatedFieldValue, before]; } //# sourceMappingURL=sanitizeParams.js.map