UNPKG

@sotatech/query3

Version:

A flexible query handler for MongoDB using Mongoose

168 lines 6.89 kB
"use strict"; 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