@sotatech/query3
Version:
A flexible query handler for MongoDB using Mongoose
168 lines • 6.89 kB
JavaScript
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Query3 = void 0;
const qs = __importStar(require("qs"));
const query3_constant_1 = require("./query3.constant");
/**
* Query3 - A flexible query handler for MongoDB models.
* Supports advanced filtering, pagination, operator validation, and aggregation pipelines.
*/
class Query3 {
/**
* @param model - The MongoDB model to be queried.
*/
constructor(model) {
this.model = model;
}
/**
* Validates if the operators in the query are allowed.
* Throws an error if an operator is not part of the allowed list.
*
* @param query - The parsed query object.
* @param allowedOperators - The list of operators allowed for this query.
*/
validateOperators(query, allowedOperators) {
Object.entries(query).forEach(([key, value]) => {
if (typeof value === 'object' && value !== null) {
Object.keys(value).forEach((operator) => {
if (!allowedOperators.includes(operator)) {
throw new Error(`Operator '${operator}' is not allowed.`);
}
});
}
});
}
/**
* Parses the query string into a structured MongoDB query object.
*
* @param queryString - The query string from the client (e.g., "?limit=10&offset=0").
* @param options - Additional options for the query.
* @returns A structured MongoDB query object with filters, limits, offsets, etc.
*/
parseQueryString(queryString, options) {
const parsedQuery = qs.parse(queryString, {
ignoreQueryPrefix: true,
depth: 5, // Allow parsing nested objects
});
const limit = Number(parsedQuery.limit || query3_constant_1.DefaultLimit);
const offset = Number(parsedQuery.offset || query3_constant_1.DefaultOffset);
const sort = parsedQuery.sort || {};
const count = parsedQuery.count === 'true';
const justOne = parsedQuery.justOne === 'true';
// Convert filters to their appropriate types
const filters = Object.entries(parsedQuery)
.filter(([key]) => !['limit', 'offset', 'sort', 'count', 'justOne'].includes(key))
.reduce((acc, [key, value]) => {
if (typeof value === 'string' && value.startsWith('{')) {
acc[key] = JSON.parse(value); // Parse JSON-like strings
}
else if (typeof value === 'object' && value !== null) {
// Recursively parse objects
acc[key] = Object.entries(value).reduce((subAcc, [subKey, subValue]) => {
subAcc[subKey] = isNaN(Number(subValue))
? subValue
: Number(subValue); // Convert numeric strings to numbers
return subAcc;
}, {});
}
else {
acc[key] = isNaN(Number(value)) ? value : Number(value);
}
return acc;
}, {});
const allowedOperators = options.allowedOperators || query3_constant_1.DefaultAllowedOperators;
this.validateOperators(filters, allowedOperators); // Validate operators
return {
filter: filters,
limit,
offset,
sort,
count,
justOne,
cacheTimeMs: 0,
};
}
/**
* Executes a query based on the provided query string and options.
* Supports filtering, pagination, field omission, and population.
*
* @param queryString - The query string from the client (e.g., "?limit=10&offset=0").
* @param options - Additional query options, such as populate, omitFields, and custom filters.
* @returns The query results and pagination metadata.
*/
async query(queryString, options = {}) {
const { filter, limit, offset, sort } = this.parseQueryString(queryString, options);
// Combine the parsed filters with any additional filters from the options
const combinedFilter = Object.assign(Object.assign({}, filter), options.queryMongoose);
// Count the total number of documents matching the filter
const totalRows = await this.model.countDocuments(combinedFilter);
const totalPages = Math.ceil(totalRows / limit);
// Fetch the records with pagination, sorting, and population
const records = (await this.model
.find(combinedFilter)
.skip(offset)
.limit(limit)
.sort(sort)
.populate(options.populate)
.lean()
.exec());
// Return the result set along with pagination details
return {
records: options.omitFields
? this.omitFields(records, options.omitFields)
: records,
pagination: {
totalRows,
totalPages,
},
};
}
/**
* Removes specified fields from the result set.
*
* @param data - The array of records to process.
* @param fields - The list of fields to remove from each record.
* @returns The processed array of records with specified fields removed.
*/
omitFields(data, fields) {
return data.map((item) => {
const result = Object.assign({}, item);
fields.forEach((field) => delete result[field]);
return result;
});
}
/**
* Executes an aggregation pipeline on the model.
*
* @param pipeline - The aggregation pipeline stages.
* @returns The result of the aggregation pipeline.
*/
async aggregate(pipeline) {
return this.model.aggregate(pipeline).exec();
}
}
exports.Query3 = Query3;
//# sourceMappingURL=query3.js.map
;