mongoose-query-toolkit
Version:
A toolkit for handling Mongoose queries with support for search, filtering, pagination, sorting, field selection, and population
112 lines (111 loc) • 4.02 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.QueryToolkit = void 0;
class QueryToolkit {
constructor(model, options = {}) {
this.model = model;
this.populatableFields = [];
this.searchFields = options.searchFields || [];
this.filterableFields = options.filterableFields || [];
this.selectableFields = options.selectableFields || [];
this.populatableFields = options.populatableFields || [];
}
buildSearchQuery(q) {
if (!q || !this.searchFields.length)
return {};
return {
$or: this.searchFields.map((field) => ({
[field]: { $regex: q, $options: 'i' },
})),
};
}
buildFilterQuery(options) {
const filterQuery = {};
for (const key of this.filterableFields) {
if (options[key] !== undefined) {
filterQuery[key] = options[key];
}
}
return filterQuery;
}
parseSortString(sort) {
const sortQuery = {};
if (!sort)
return sortQuery;
sort.split(',').forEach((field) => {
const order = field.startsWith('-') ? -1 : 1;
const fieldName = field.startsWith('-') ? field.substring(1) : field;
sortQuery[fieldName] = order;
});
return sortQuery;
}
buildSelectQuery(select) {
if (!select)
return null;
// If selectableFields is empty, allow all fields
if (this.selectableFields.length === 0) {
return select.replace(/,/g, ' ');
}
// Filter fields based on selectableFields
const fields = select.split(',');
const validFields = fields.filter(field => {
// Handle exclusion fields (fields with minus prefix)
const fieldName = field.startsWith('-') ? field.substring(1) : field;
return this.selectableFields.includes(fieldName);
});
return validFields.join(' ');
}
buildPopulateFields(populate) {
if (!populate)
return [];
// Convert comma-separated fields to array
const fields = populate.split(',').map(field => field.trim());
// If populatableFields is empty, allow all fields
if (this.populatableFields.length === 0) {
return fields;
}
// Filter fields based on populatableFields
return fields.filter(field => this.populatableFields.includes(field));
}
async findWithOptions(options = {}) {
const { q, page = 1, limit = 10, sort, select, populate, ...filterOptions } = options;
const skip = (page - 1) * limit;
const query = {
...this.buildSearchQuery(q || ''),
...this.buildFilterQuery(filterOptions),
};
const sortQuery = this.parseSortString(sort);
const selectQuery = this.buildSelectQuery(select);
const populateFields = this.buildPopulateFields(populate);
let findQuery = this.model.find(query);
if (sortQuery && Object.keys(sortQuery).length > 0) {
findQuery = findQuery.sort(sortQuery);
}
if (selectQuery) {
findQuery = findQuery.select(selectQuery);
}
// Apply populate fields
populateFields.forEach(field => {
// Using type assertion to handle the TypeScript error
findQuery = findQuery.populate(field);
});
const [docs, totalDocs] = await Promise.all([
findQuery
.skip(skip)
.limit(limit)
.exec(),
this.model.countDocuments(query),
]);
const totalPages = Math.ceil(totalDocs / limit);
return {
docs,
totalDocs,
limit,
page,
totalPages,
hasNextPage: page < totalPages,
hasPrevPage: page > 1,
};
}
}
exports.QueryToolkit = QueryToolkit;