@conpago/mongo-cursor-pagination
Version:
Make it easy to return cursor-paginated results from a Mongo collection
130 lines • 5.94 kB
JavaScript
;
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