mongo-cursor-pagination
Version:
Make it easy to return cursor-paginated results from a Mongo collection
84 lines (74 loc) • 2.69 kB
text/typescript
import _ from 'underscore';
import resolveFields from './resolveFields';
import { FindParams } from '../find';
export interface QueryObject {
limit?: string;
next?: string;
previous?: string;
fields?: string | string[];
[key: string]: any;
}
/**
* Normalize the given query parameter to an array, supporting both `param=a,b` and `param[]=a¶m[]=b`.
*
* @param query - The parsed query object containing the given parameter.
* @param param - The parameter to normalize.
* @returns The normalized array from the given query parameter.
* @throws {TypeError} When the query parameter isn't a string, an empty value, or an array of strings.
*/
function normalizeQueryArray(query: QueryObject, param: string): string[] {
const value = query[param];
if (Array.isArray(value)) {
for (let i = 0; i < value.length; ++i) {
if (!_.isString(value[i])) {
throw new TypeError('expected string array or comma-separated string for ' + param);
}
}
return value;
}
// This goes before _.isString so we don't split an empty string into ['']. The array option just
// uses whatever the user provides.
if (_.isEmpty(value)) {
return [];
}
if (_.isString(value)) {
return value.split(',');
}
throw new TypeError('expected string array or comma-separated string for ' + param);
}
/**
* Sanitizes a `query` object received and merges its changes to an optional `params` object.
*
* @param query - The parsed query object with various parameters (limit, next, previous, fields).
* @param params - The parameters object to merge and sanitize the query into.
* @returns The sanitized and merged `params` object.
*/
export default function sanitizeQuery(query: QueryObject, params: FindParams = {}): FindParams {
params = params || {};
if (!_.isEmpty(query.limit)) {
const limit = parseInt(query.limit, 10);
// Don't let the user specify a higher limit than params.limit, if defined.
if (!isNaN(limit) && (!params.limit || params.limit > limit)) {
params.limit = limit;
}
}
if (!_.isEmpty(query.next)) {
params.next = query.next;
}
if (!_.isEmpty(query.previous)) {
params.previous = query.previous;
}
// Don't trust fields passed in the querystring, so whitelist them against the fields defined in
// parameters.
const fields = resolveFields(
normalizeQueryArray(query, 'fields'),
params.fields,
params.overrideFields
);
if (fields === null) {
throw new TypeError('no valid fields provided');
}
// Set fields to undefined if it's empty to avoid adding _id: 0 in find.
params.fields = _.isEmpty(fields) ? undefined : fields;
return params;
}