filter-kit 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/README.md +294 -0
  2. package/dist/cjs/filters/buildDateFilter.d.ts +11 -0
  3. package/dist/cjs/filters/buildDateFilter.d.ts.map +1 -0
  4. package/dist/cjs/filters/buildDateFilter.js +39 -0
  5. package/dist/cjs/filters/buildDateFilter.js.map +1 -0
  6. package/dist/cjs/filters/buildDynamicFilters.d.ts +3 -0
  7. package/dist/cjs/filters/buildDynamicFilters.d.ts.map +1 -0
  8. package/dist/cjs/filters/buildDynamicFilters.js +110 -0
  9. package/dist/cjs/filters/buildDynamicFilters.js.map +1 -0
  10. package/dist/cjs/filters/buildPagination.d.ts +15 -0
  11. package/dist/cjs/filters/buildPagination.d.ts.map +1 -0
  12. package/dist/cjs/filters/buildPagination.js +30 -0
  13. package/dist/cjs/filters/buildPagination.js.map +1 -0
  14. package/dist/cjs/filters/buildPopulate.d.ts +3 -0
  15. package/dist/cjs/filters/buildPopulate.d.ts.map +1 -0
  16. package/dist/cjs/filters/buildPopulate.js +45 -0
  17. package/dist/cjs/filters/buildPopulate.js.map +1 -0
  18. package/dist/cjs/filters/buildSearch.d.ts +4 -0
  19. package/dist/cjs/filters/buildSearch.d.ts.map +1 -0
  20. package/dist/cjs/filters/buildSearch.js +57 -0
  21. package/dist/cjs/filters/buildSearch.js.map +1 -0
  22. package/dist/cjs/filters/buildSort.d.ts +2 -0
  23. package/dist/cjs/filters/buildSort.d.ts.map +1 -0
  24. package/dist/cjs/filters/buildSort.js +19 -0
  25. package/dist/cjs/filters/buildSort.js.map +1 -0
  26. package/dist/cjs/index.d.ts +3 -0
  27. package/dist/cjs/index.d.ts.map +1 -0
  28. package/dist/cjs/index.js +6 -0
  29. package/dist/cjs/index.js.map +1 -0
  30. package/dist/cjs/package.json +3 -0
  31. package/dist/cjs/queryBuilder.d.ts +3 -0
  32. package/dist/cjs/queryBuilder.d.ts.map +1 -0
  33. package/dist/cjs/queryBuilder.js +75 -0
  34. package/dist/cjs/queryBuilder.js.map +1 -0
  35. package/dist/cjs/types.d.ts +48 -0
  36. package/dist/cjs/types.d.ts.map +1 -0
  37. package/dist/cjs/types.js +3 -0
  38. package/dist/cjs/types.js.map +1 -0
  39. package/dist/esm/filters/buildDateFilter.d.ts +11 -0
  40. package/dist/esm/filters/buildDateFilter.d.ts.map +1 -0
  41. package/dist/esm/filters/buildDateFilter.js +36 -0
  42. package/dist/esm/filters/buildDateFilter.js.map +1 -0
  43. package/dist/esm/filters/buildDynamicFilters.d.ts +3 -0
  44. package/dist/esm/filters/buildDynamicFilters.d.ts.map +1 -0
  45. package/dist/esm/filters/buildDynamicFilters.js +107 -0
  46. package/dist/esm/filters/buildDynamicFilters.js.map +1 -0
  47. package/dist/esm/filters/buildPagination.d.ts +15 -0
  48. package/dist/esm/filters/buildPagination.d.ts.map +1 -0
  49. package/dist/esm/filters/buildPagination.js +27 -0
  50. package/dist/esm/filters/buildPagination.js.map +1 -0
  51. package/dist/esm/filters/buildPopulate.d.ts +3 -0
  52. package/dist/esm/filters/buildPopulate.d.ts.map +1 -0
  53. package/dist/esm/filters/buildPopulate.js +42 -0
  54. package/dist/esm/filters/buildPopulate.js.map +1 -0
  55. package/dist/esm/filters/buildSearch.d.ts +4 -0
  56. package/dist/esm/filters/buildSearch.d.ts.map +1 -0
  57. package/dist/esm/filters/buildSearch.js +54 -0
  58. package/dist/esm/filters/buildSearch.js.map +1 -0
  59. package/dist/esm/filters/buildSort.d.ts +2 -0
  60. package/dist/esm/filters/buildSort.d.ts.map +1 -0
  61. package/dist/esm/filters/buildSort.js +16 -0
  62. package/dist/esm/filters/buildSort.js.map +1 -0
  63. package/dist/esm/index.d.ts +3 -0
  64. package/dist/esm/index.d.ts.map +1 -0
  65. package/dist/esm/index.js +2 -0
  66. package/dist/esm/index.js.map +1 -0
  67. package/dist/esm/package.json +3 -0
  68. package/dist/esm/queryBuilder.d.ts +3 -0
  69. package/dist/esm/queryBuilder.d.ts.map +1 -0
  70. package/dist/esm/queryBuilder.js +72 -0
  71. package/dist/esm/queryBuilder.js.map +1 -0
  72. package/dist/esm/types.d.ts +48 -0
  73. package/dist/esm/types.d.ts.map +1 -0
  74. package/dist/esm/types.js +2 -0
  75. package/dist/esm/types.js.map +1 -0
  76. package/package.json +54 -0
package/README.md ADDED
@@ -0,0 +1,294 @@
1
+ # mongoose-filter-kit
2
+
3
+ [![npm version](https://img.shields.io/npm/v/mongoose-filter-kit.svg)](https://www.npmjs.com/package/mongoose-filter-kit)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.x-blue.svg)](https://www.typescriptlang.org/)
6
+
7
+ A production-ready Mongoose query builder for REST APIs and admin panels. One function call builds a full MongoDB query with regex search, pagination, sorting, date-range filters, populate, field selection, and dynamic filters — all driven by URL query parameters.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ npm install mongoose-filter-kit
13
+ ```
14
+
15
+ Mongoose 6.x or higher is required as a peer dependency:
16
+
17
+ ```bash
18
+ npm install mongoose
19
+ ```
20
+
21
+ ## JavaScript Usage
22
+
23
+ **CommonJS (Node.js `require`)**
24
+
25
+ ```javascript
26
+ const { queryBuilder } = require('mongoose-filter-kit');
27
+ const User = require('./models/User');
28
+
29
+ app.get('/users', async (req, res) => {
30
+ const result = await queryBuilder({
31
+ model: User,
32
+ query: req.query,
33
+ searchFields: ['name', 'email'],
34
+ allowedFilters: ['role', 'status'],
35
+ customFilter: { isDeleted: false },
36
+ });
37
+ res.json(result);
38
+ });
39
+ ```
40
+
41
+ **ESM (`import`)**
42
+
43
+ ```javascript
44
+ import { queryBuilder } from 'mongoose-filter-kit';
45
+ import User from './models/User.js';
46
+
47
+ app.get('/users', async (req, res) => {
48
+ const result = await queryBuilder({
49
+ model: User,
50
+ query: req.query,
51
+ searchFields: ['name', 'email'],
52
+ allowedFilters: ['role', 'status'],
53
+ customFilter: { isDeleted: false },
54
+ });
55
+ res.json(result);
56
+ });
57
+ ```
58
+
59
+ ## Quick Start (TypeScript)
60
+
61
+ ```typescript
62
+ import { queryBuilder } from 'mongoose-filter-kit';
63
+ import User from './models/User';
64
+
65
+ // Express handler
66
+ app.get('/users', async (req, res) => {
67
+ const result = await queryBuilder({
68
+ model: User,
69
+ query: req.query,
70
+ searchFields: ['name', 'email'],
71
+ allowedFilters: ['role', 'status'],
72
+ defaultSort: 'createdAt',
73
+ defaultOrder: 'desc',
74
+ defaultLimit: 10,
75
+ maxLimit: 100,
76
+ populate: 'role',
77
+ dateField: 'createdAt',
78
+ customFilter: { isDeleted: false },
79
+ });
80
+
81
+ res.json(result);
82
+ });
83
+ ```
84
+
85
+ Sample response:
86
+
87
+ ```json
88
+ {
89
+ "data": [
90
+ { "_id": "...", "name": "John Doe", "email": "john@example.com", "role": "admin" }
91
+ ],
92
+ "pagination": {
93
+ "total": 42,
94
+ "page": 1,
95
+ "limit": 10,
96
+ "pages": 5,
97
+ "hasNext": true,
98
+ "hasPrev": false
99
+ }
100
+ }
101
+ ```
102
+
103
+ ## Options Reference
104
+
105
+ | Option | Type | Default | Description |
106
+ |---|---|---|---|
107
+ | `model` | `Model<T>` | **required** | The Mongoose model to query against. |
108
+ | `query` | `Record<string, any>` | **required** | Parsed query string object (e.g. `req.query`). |
109
+ | `searchFields` | `string[]` | `undefined` | Fields to regex-search when `?search=` is provided. |
110
+ | `allowedFilters` | `string[]` | `undefined` | Extra query keys allowed as dynamic filters (in addition to schema paths). |
111
+ | `defaultSort` | `string` | `'createdAt'` | Default sort field when `?sort=` is absent. |
112
+ | `defaultOrder` | `'asc' \| 'desc'` | `'desc'` | Default sort direction when `?order=` is absent. |
113
+ | `defaultLimit` | `number` | `10` | Default page size when `?limit=` is absent. |
114
+ | `maxLimit` | `number` | `100` | Hard ceiling on page size — prevents abuse. |
115
+ | `populate` | `string \| string[] \| PopulateOptions \| PopulateOptions[]` | `undefined` | Populate option used when `?populate=` is absent. |
116
+ | `select` | `ProjectionType<T> \| string` | `undefined` | Field projection used when `?select=` is absent. |
117
+ | `dateField` | `string` | `undefined` | Date field targeted by `?from=` / `?to=` filters. |
118
+ | `lean` | `boolean` | `false` | Return plain JS objects instead of Mongoose documents. |
119
+ | `customFilter` | `Record<string, any>` | `undefined` | Extra conditions always merged into the query via `$and`. |
120
+
121
+ ## Query Parameters Reference
122
+
123
+ | Param | Type | Example | Description |
124
+ |---|---|---|---|
125
+ | `search` | `string` | `?search=john` | Case-insensitive regex search across all `searchFields`. Special regex chars are escaped automatically. |
126
+ | `page` | `number` | `?page=2` | Page number (1-based, min 1). |
127
+ | `limit` | `number` | `?limit=25` | Documents per page (clamped to `maxLimit`). |
128
+ | `sort` | `string` | `?sort=name` | Field to sort by. |
129
+ | `order` | `'asc' \| 'desc'` | `?order=asc` | Sort direction. |
130
+ | `from` | `ISO 8601 date` | `?from=2024-01-01` | Start of date range on `dateField` (`$gte`). Invalid dates are silently ignored. |
131
+ | `to` | `ISO 8601 date` | `?to=2024-12-31` | End of date range on `dateField` (`$lte`, adjusted to 23:59:59.999). Invalid dates are silently ignored. |
132
+ | `populate` | `string` | `?populate=role,department` | Comma-separated paths to populate. Overrides the `populate` option. |
133
+ | `select` | `string` | `?select=name,email` | Comma-separated fields to include. Overrides the `select` option. |
134
+ | `<field>` | `string` | `?status=active` | Dynamic filter — passed through only if the key is in `allowedFilters` or exists as a schema path. Comma-separated values become `{ $in: [...] }`. The strings `'true'` / `'false'` are coerced to booleans. |
135
+
136
+ ## Examples
137
+
138
+ ### With Express
139
+
140
+ ```typescript
141
+ import express from 'express';
142
+ import mongoose from 'mongoose';
143
+ import { queryBuilder } from 'mongoose-filter-kit';
144
+ import Product from './models/Product';
145
+
146
+ const app = express();
147
+
148
+ app.get('/products', async (req, res, next) => {
149
+ try {
150
+ const result = await queryBuilder({
151
+ model: Product,
152
+ query: req.query,
153
+ searchFields: ['name', 'description', 'sku'],
154
+ allowedFilters: ['category', 'brand', 'inStock'],
155
+ defaultSort: 'createdAt',
156
+ defaultOrder: 'desc',
157
+ defaultLimit: 20,
158
+ maxLimit: 200,
159
+ dateField: 'createdAt',
160
+ populate: [{ path: 'category', select: 'name slug' }],
161
+ customFilter: { isActive: true },
162
+ lean: true,
163
+ });
164
+
165
+ res.json(result);
166
+ } catch (err) {
167
+ next(err);
168
+ }
169
+ });
170
+ ```
171
+
172
+ ### With NestJS
173
+
174
+ ```typescript
175
+ import { Controller, Get, Query } from '@nestjs/common';
176
+ import { InjectModel } from '@nestjs/mongoose';
177
+ import { Model } from 'mongoose';
178
+ import { queryBuilder, QueryBuilderResult } from 'mongoose-filter-kit';
179
+ import { User, UserDocument } from './user.schema';
180
+
181
+ @Controller('users')
182
+ export class UsersController {
183
+ constructor(
184
+ @InjectModel(User.name) private readonly userModel: Model<UserDocument>,
185
+ ) {}
186
+
187
+ @Get()
188
+ async findAll(@Query() query: Record<string, any>): Promise<QueryBuilderResult<UserDocument>> {
189
+ return queryBuilder({
190
+ model: this.userModel,
191
+ query,
192
+ searchFields: ['name', 'email'],
193
+ allowedFilters: ['role', 'status', 'department'],
194
+ defaultSort: 'createdAt',
195
+ defaultOrder: 'desc',
196
+ defaultLimit: 10,
197
+ maxLimit: 100,
198
+ populate: 'role',
199
+ dateField: 'createdAt',
200
+ customFilter: { isDeleted: false },
201
+ });
202
+ }
203
+ }
204
+ ```
205
+
206
+ ### With Custom Filters and Populate Options
207
+
208
+ ```typescript
209
+ const result = await queryBuilder({
210
+ model: Order,
211
+ query: req.query,
212
+ searchFields: ['orderNumber', 'customerName'],
213
+ allowedFilters: ['status', 'paymentMethod'],
214
+ defaultSort: 'placedAt',
215
+ defaultOrder: 'desc',
216
+ defaultLimit: 15,
217
+ maxLimit: 50,
218
+ dateField: 'placedAt',
219
+ populate: [
220
+ { path: 'customer', select: 'name email phone' },
221
+ { path: 'items.product', select: 'name sku price' },
222
+ ],
223
+ select: '-__v -internalNotes',
224
+ customFilter: { archived: false },
225
+ lean: true,
226
+ });
227
+ ```
228
+
229
+ ### Combining Date Ranges and Boolean Filters
230
+
231
+ ```bash
232
+ GET /orders?from=2024-01-01&to=2024-03-31&status=pending,processing&isPaid=false&page=1&limit=25
233
+ ```
234
+
235
+ ```typescript
236
+ const result = await queryBuilder({
237
+ model: Order,
238
+ query: req.query, // { from, to, status, isPaid, page, limit }
239
+ allowedFilters: ['isPaid'],
240
+ dateField: 'placedAt',
241
+ // status is also allowed because it exists on the schema
242
+ });
243
+ // Produces: { $and: [
244
+ // { placedAt: { $gte: ..., $lte: ... } },
245
+ // { status: { $in: ['pending', 'processing'] }, isPaid: false }
246
+ // ]}
247
+ ```
248
+
249
+ ## How Dynamic Filters Work
250
+
251
+ Dynamic filters let clients filter by any model field directly via the URL — but with a security gate to prevent arbitrary injection:
252
+
253
+ 1. **Reserved params are never treated as field filters** — `page`, `limit`, `sort`, `order`, `search`, `from`, `to`, `populate`, and `select` are always consumed by their dedicated handlers.
254
+
255
+ 2. **A field key is accepted only when:**
256
+ - It appears in your `allowedFilters` array, **or**
257
+ - It exists as a path in `model.schema.paths`.
258
+
259
+ Unknown keys with no schema definition are silently dropped.
260
+
261
+ 3. **Value coercions applied automatically:**
262
+ - `?status=active,inactive` → `{ status: { $in: ['active', 'inactive'] } }`
263
+ - `?isVerified=true` → `{ isVerified: true }` (boolean)
264
+ - `?isVerified=false` → `{ isVerified: false }` (boolean)
265
+ - Everything else is passed through as-is.
266
+
267
+ 4. **`customFilter` is always applied** — it cannot be overridden by query parameters. Use it for tenant isolation, soft-delete guards, or any condition that must always be present.
268
+
269
+ ## TypeScript Support
270
+
271
+ Full types ship with the package — no `@types/` install needed. All types are exported:
272
+
273
+ ```typescript
274
+ import {
275
+ queryBuilder,
276
+ QueryBuilderOptions,
277
+ QueryBuilderResult,
278
+ PaginationMeta,
279
+ } from 'mongoose-filter-kit';
280
+ ```
281
+
282
+ The main function is generic — pass your document type for full type inference:
283
+
284
+ ```typescript
285
+ const result = await queryBuilder<UserDocument>({
286
+ model: User,
287
+ query: req.query,
288
+ });
289
+ // result.data is UserDocument[]
290
+ ```
291
+
292
+ ## License
293
+
294
+ MIT
@@ -0,0 +1,11 @@
1
+ import { FilterQuery } from 'mongoose';
2
+ /**
3
+ * Builds a MongoDB date-range filter for the specified field.
4
+ *
5
+ * - `from` maps to `$gte` (start of that day, as-is from the ISO string).
6
+ * - `to` maps to `$lte` (end of that day — time set to 23:59:59.999).
7
+ * - Invalid date strings are silently ignored.
8
+ * - Returns `{}` when no dateField is provided or both dates are invalid.
9
+ */
10
+ export declare function buildDateFilter(from: string | undefined, to: string | undefined, dateField: string | undefined): FilterQuery<any>;
11
+ //# sourceMappingURL=buildDateFilter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buildDateFilter.d.ts","sourceRoot":"","sources":["../../../src/filters/buildDateFilter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAWvC;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,EAAE,EAAE,MAAM,GAAG,SAAS,EACtB,SAAS,EAAE,MAAM,GAAG,SAAS,GAC5B,WAAW,CAAC,GAAG,CAAC,CAqBlB"}
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildDateFilter = buildDateFilter;
4
+ /**
5
+ * Determines whether a given value produces a valid Date object.
6
+ */
7
+ function isValidDate(value) {
8
+ if (!value || value.trim() === '')
9
+ return false;
10
+ const d = new Date(value);
11
+ return !isNaN(d.getTime());
12
+ }
13
+ /**
14
+ * Builds a MongoDB date-range filter for the specified field.
15
+ *
16
+ * - `from` maps to `$gte` (start of that day, as-is from the ISO string).
17
+ * - `to` maps to `$lte` (end of that day — time set to 23:59:59.999).
18
+ * - Invalid date strings are silently ignored.
19
+ * - Returns `{}` when no dateField is provided or both dates are invalid.
20
+ */
21
+ function buildDateFilter(from, to, dateField) {
22
+ if (!dateField)
23
+ return {};
24
+ const conditions = {};
25
+ if (from && isValidDate(from)) {
26
+ conditions['$gte'] = new Date(from);
27
+ }
28
+ if (to && isValidDate(to)) {
29
+ const toDate = new Date(to);
30
+ // Set to end of day so the entire `to` day is included
31
+ toDate.setHours(23, 59, 59, 999);
32
+ conditions['$lte'] = toDate;
33
+ }
34
+ if (Object.keys(conditions).length === 0) {
35
+ return {};
36
+ }
37
+ return { [dateField]: conditions };
38
+ }
39
+ //# sourceMappingURL=buildDateFilter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buildDateFilter.js","sourceRoot":"","sources":["../../../src/filters/buildDateFilter.ts"],"names":[],"mappings":";;AAmBA,0CAyBC;AA1CD;;GAEG;AACH,SAAS,WAAW,CAAC,KAAa;IAChC,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE;QAAE,OAAO,KAAK,CAAC;IAChD,MAAM,CAAC,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC;IAC1B,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;AAC7B,CAAC;AAED;;;;;;;GAOG;AACH,SAAgB,eAAe,CAC7B,IAAwB,EACxB,EAAsB,EACtB,SAA6B;IAE7B,IAAI,CAAC,SAAS;QAAE,OAAO,EAAE,CAAC;IAE1B,MAAM,UAAU,GAAyB,EAAE,CAAC;IAE5C,IAAI,IAAI,IAAI,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC;QAC9B,UAAU,CAAC,MAAM,CAAC,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC;IACtC,CAAC;IAED,IAAI,EAAE,IAAI,WAAW,CAAC,EAAE,CAAC,EAAE,CAAC;QAC1B,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,EAAE,CAAC,CAAC;QAC5B,uDAAuD;QACvD,MAAM,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,CAAC,CAAC;QACjC,UAAU,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC;IAC9B,CAAC;IAED,IAAI,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzC,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,OAAO,EAAE,CAAC,SAAS,CAAC,EAAE,UAAU,EAAE,CAAC;AACrC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { FilterQuery, Model } from 'mongoose';
2
+ export declare function buildDynamicFilters(query: Record<string, any>, allowedFilters: string[] | undefined, model: Model<any>): FilterQuery<any>;
3
+ //# sourceMappingURL=buildDynamicFilters.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buildDynamicFilters.d.ts","sourceRoot":"","sources":["../../../src/filters/buildDynamicFilters.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AAiE9C,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC1B,cAAc,EAAE,MAAM,EAAE,GAAG,SAAS,EACpC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,GAChB,WAAW,CAAC,GAAG,CAAC,CAyClB"}
@@ -0,0 +1,110 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildDynamicFilters = buildDynamicFilters;
4
+ const RESERVED_PARAMS = new Set([
5
+ 'page', 'limit', 'sort', 'order', 'search', 'from', 'to', 'populate', 'select',
6
+ ]);
7
+ const OPERATOR_SUFFIXES = [
8
+ ['_exists', '$exists'],
9
+ ['_regex', '$regex'],
10
+ ['_nin', '$nin'],
11
+ ['_gte', '$gte'],
12
+ ['_lte', '$lte'],
13
+ ['_ne', '$ne'],
14
+ ['_in', '$in'],
15
+ ['_gt', '$gt'],
16
+ ['_lt', '$lt'],
17
+ ];
18
+ function parseOperatorKey(key) {
19
+ for (const [suffix, op] of OPERATOR_SUFFIXES) {
20
+ if (key.endsWith(suffix) && key.length > suffix.length) {
21
+ return { field: key.slice(0, -suffix.length), op };
22
+ }
23
+ }
24
+ return null;
25
+ }
26
+ function coerceScalar(raw) {
27
+ const str = String(raw);
28
+ if (str === 'true')
29
+ return true;
30
+ if (str === 'false')
31
+ return false;
32
+ const n = Number(str);
33
+ return isNaN(n) ? str : n;
34
+ }
35
+ function coerceForOperator(raw, op) {
36
+ const str = String(raw);
37
+ switch (op) {
38
+ case '$gte':
39
+ case '$lte':
40
+ case '$gt':
41
+ case '$lt': {
42
+ const n = Number(str);
43
+ return isNaN(n) ? str : n;
44
+ }
45
+ case '$ne':
46
+ return coerceScalar(raw);
47
+ case '$in':
48
+ case '$nin':
49
+ return str.split(',').map((v) => coerceScalar(v.trim()));
50
+ case '$exists':
51
+ return str !== 'false';
52
+ case '$regex': {
53
+ try {
54
+ return new RegExp(str, 'i');
55
+ }
56
+ catch (_a) {
57
+ return null;
58
+ }
59
+ }
60
+ default:
61
+ return coerceScalar(raw);
62
+ }
63
+ }
64
+ function buildDynamicFilters(query, allowedFilters, model) {
65
+ const filter = {};
66
+ const allowed = new Set(allowedFilters !== null && allowedFilters !== void 0 ? allowedFilters : []);
67
+ const schemaPaths = model.schema.paths;
68
+ for (const key of Object.keys(query)) {
69
+ if (RESERVED_PARAMS.has(key))
70
+ continue;
71
+ const raw = query[key];
72
+ if (raw === undefined || raw === null || raw === '')
73
+ continue;
74
+ const parsed = parseOperatorKey(key);
75
+ if (parsed) {
76
+ const { field, op } = parsed;
77
+ const fieldAllowed = allowed.has(field) || field in schemaPaths;
78
+ if (!fieldAllowed)
79
+ continue;
80
+ const coerced = coerceForOperator(raw, op);
81
+ if (coerced === null)
82
+ continue;
83
+ if (typeof filter[field] !== 'object' || Array.isArray(filter[field])) {
84
+ filter[field] = {};
85
+ }
86
+ filter[field][op] = coerced;
87
+ }
88
+ else {
89
+ const keyAllowed = allowed.has(key) || key in schemaPaths;
90
+ if (!keyAllowed)
91
+ continue;
92
+ const value = String(raw);
93
+ if (value === 'true') {
94
+ filter[key] = true;
95
+ continue;
96
+ }
97
+ if (value === 'false') {
98
+ filter[key] = false;
99
+ continue;
100
+ }
101
+ if (value.includes(',')) {
102
+ filter[key] = { $in: value.split(',').map((v) => v.trim()) };
103
+ continue;
104
+ }
105
+ filter[key] = raw;
106
+ }
107
+ }
108
+ return filter;
109
+ }
110
+ //# sourceMappingURL=buildDynamicFilters.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buildDynamicFilters.js","sourceRoot":"","sources":["../../../src/filters/buildDynamicFilters.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ export interface PaginationParams {
2
+ page: number;
3
+ limit: number;
4
+ skip: number;
5
+ }
6
+ /**
7
+ * Parses and validates pagination query parameters.
8
+ *
9
+ * Rules:
10
+ * - `page` is clamped to a minimum of 1.
11
+ * - `limit` is clamped between 1 and `maxLimit` (inclusive).
12
+ * - Non-numeric or missing values fall back to the provided defaults.
13
+ */
14
+ export declare function buildPagination(queryPage: string | undefined, queryLimit: string | undefined, defaultLimit: number, maxLimit: number): PaginationParams;
15
+ //# sourceMappingURL=buildPagination.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buildPagination.d.ts","sourceRoot":"","sources":["../../../src/filters/buildPagination.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,GACf,gBAAgB,CAgBlB"}
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildPagination = buildPagination;
4
+ /**
5
+ * Parses and validates pagination query parameters.
6
+ *
7
+ * Rules:
8
+ * - `page` is clamped to a minimum of 1.
9
+ * - `limit` is clamped between 1 and `maxLimit` (inclusive).
10
+ * - Non-numeric or missing values fall back to the provided defaults.
11
+ */
12
+ function buildPagination(queryPage, queryLimit, defaultLimit, maxLimit) {
13
+ let page = parseInt(queryPage, 10);
14
+ let limit = parseInt(queryLimit, 10);
15
+ // Fall back to defaults for non-numeric inputs
16
+ if (isNaN(page))
17
+ page = 1;
18
+ if (isNaN(limit))
19
+ limit = defaultLimit;
20
+ // Enforce bounds
21
+ if (page < 1)
22
+ page = 1;
23
+ if (limit < 1)
24
+ limit = 1;
25
+ if (limit > maxLimit)
26
+ limit = maxLimit;
27
+ const skip = (page - 1) * limit;
28
+ return { page, limit, skip };
29
+ }
30
+ //# sourceMappingURL=buildPagination.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buildPagination.js","sourceRoot":"","sources":["../../../src/filters/buildPagination.ts"],"names":[],"mappings":";;AAcA,0CAqBC;AA7BD;;;;;;;GAOG;AACH,SAAgB,eAAe,CAC7B,SAA6B,EAC7B,UAA8B,EAC9B,YAAoB,EACpB,QAAgB;IAEhB,IAAI,IAAI,GAAG,QAAQ,CAAC,SAAmB,EAAE,EAAE,CAAC,CAAC;IAC7C,IAAI,KAAK,GAAG,QAAQ,CAAC,UAAoB,EAAE,EAAE,CAAC,CAAC;IAE/C,+CAA+C;IAC/C,IAAI,KAAK,CAAC,IAAI,CAAC;QAAE,IAAI,GAAG,CAAC,CAAC;IAC1B,IAAI,KAAK,CAAC,KAAK,CAAC;QAAE,KAAK,GAAG,YAAY,CAAC;IAEvC,iBAAiB;IACjB,IAAI,IAAI,GAAG,CAAC;QAAE,IAAI,GAAG,CAAC,CAAC;IACvB,IAAI,KAAK,GAAG,CAAC;QAAE,KAAK,GAAG,CAAC,CAAC;IACzB,IAAI,KAAK,GAAG,QAAQ;QAAE,KAAK,GAAG,QAAQ,CAAC;IAEvC,MAAM,IAAI,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC;IAEhC,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;AAC/B,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { PopulateOptions } from 'mongoose';
2
+ export declare function buildPopulate(queryPopulate: string | undefined, optionPopulate: string | string[] | PopulateOptions | PopulateOptions[] | undefined): PopulateOptions[];
3
+ //# sourceMappingURL=buildPopulate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buildPopulate.d.ts","sourceRoot":"","sources":["../../../src/filters/buildPopulate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAmB3C,wBAAgB,aAAa,CAC3B,aAAa,EAAE,MAAM,GAAG,SAAS,EACjC,cAAc,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,eAAe,GAAG,eAAe,EAAE,GAAG,SAAS,GAClF,eAAe,EAAE,CA2BnB"}
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildPopulate = buildPopulate;
4
+ function dotToPopulate(dotPath) {
5
+ const parts = dotPath
6
+ .trim()
7
+ .split('.')
8
+ .map((p) => p.trim())
9
+ .filter((p) => p.length > 0)
10
+ .slice(0, 4);
11
+ if (parts.length === 0)
12
+ throw new Error(`Invalid populate path: "${dotPath}"`);
13
+ let result = { path: parts[parts.length - 1] };
14
+ for (let i = parts.length - 2; i >= 0; i--) {
15
+ result = { path: parts[i], populate: result };
16
+ }
17
+ return result;
18
+ }
19
+ function buildPopulate(queryPopulate, optionPopulate) {
20
+ if (queryPopulate && queryPopulate.trim() !== '') {
21
+ return queryPopulate
22
+ .split(',')
23
+ .map((p) => p.trim())
24
+ .filter((p) => p.length > 0)
25
+ .map(dotToPopulate);
26
+ }
27
+ if (optionPopulate === undefined || optionPopulate === null)
28
+ return [];
29
+ if (typeof optionPopulate === 'string') {
30
+ return optionPopulate.trim() === '' ? [] : [dotToPopulate(optionPopulate)];
31
+ }
32
+ if (Array.isArray(optionPopulate)) {
33
+ if (optionPopulate.length === 0)
34
+ return [];
35
+ if (typeof optionPopulate[0] === 'string') {
36
+ return optionPopulate
37
+ .map((p) => p.trim())
38
+ .filter((p) => p.length > 0)
39
+ .map(dotToPopulate);
40
+ }
41
+ return optionPopulate;
42
+ }
43
+ return [optionPopulate];
44
+ }
45
+ //# sourceMappingURL=buildPopulate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buildPopulate.js","sourceRoot":"","sources":["../../../src/filters/buildPopulate.ts"],"names":[],"mappings":";;AAmBA,sCA8BC;AA/CD,SAAS,aAAa,CAAC,OAAe;IACpC,MAAM,KAAK,GAAG,OAAO;SAClB,IAAI,EAAE;SACN,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;SAC3B,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAEf,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,2BAA2B,OAAO,GAAG,CAAC,CAAC;IAE/E,IAAI,MAAM,GAAoB,EAAE,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,EAAE,CAAC;IAChE,KAAK,IAAI,CAAC,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAC3C,MAAM,GAAG,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;IAChD,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAgB,aAAa,CAC3B,aAAiC,EACjC,cAAmF;IAEnF,IAAI,aAAa,IAAI,aAAa,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QACjD,OAAO,aAAa;aACjB,KAAK,CAAC,GAAG,CAAC;aACV,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;aACpB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;aAC3B,GAAG,CAAC,aAAa,CAAC,CAAC;IACxB,CAAC;IAED,IAAI,cAAc,KAAK,SAAS,IAAI,cAAc,KAAK,IAAI;QAAE,OAAO,EAAE,CAAC;IAEvE,IAAI,OAAO,cAAc,KAAK,QAAQ,EAAE,CAAC;QACvC,OAAO,cAAc,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,cAAc,CAAC,CAAC,CAAC;IAC7E,CAAC;IAED,IAAI,KAAK,CAAC,OAAO,CAAC,cAAc,CAAC,EAAE,CAAC;QAClC,IAAI,cAAc,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QAC3C,IAAI,OAAO,cAAc,CAAC,CAAC,CAAC,KAAK,QAAQ,EAAE,CAAC;YAC1C,OAAQ,cAA2B;iBAChC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;iBACpB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;iBAC3B,GAAG,CAAC,aAAa,CAAC,CAAC;QACxB,CAAC;QACD,OAAO,cAAmC,CAAC;IAC7C,CAAC;IAED,OAAO,CAAC,cAAiC,CAAC,CAAC;AAC7C,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { FilterQuery } from 'mongoose';
2
+ import { SearchField } from '../types';
3
+ export declare function buildSearch(search: string | undefined, searchFields: SearchField[] | undefined, searchMode?: 'or' | 'and'): FilterQuery<any>;
4
+ //# sourceMappingURL=buildSearch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buildSearch.d.ts","sourceRoot":"","sources":["../../../src/filters/buildSearch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAgCvC,wBAAgB,WAAW,CACzB,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,YAAY,EAAE,WAAW,EAAE,GAAG,SAAS,EACvC,UAAU,GAAE,IAAI,GAAG,KAAY,GAC9B,WAAW,CAAC,GAAG,CAAC,CA8BlB"}
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildSearch = buildSearch;
4
+ function escapeRegex(value) {
5
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
6
+ }
7
+ function buildFieldCondition(field, type, term) {
8
+ if (type === 'number') {
9
+ const num = Number(term);
10
+ if (isNaN(num))
11
+ return null;
12
+ return { [field]: { $eq: num } };
13
+ }
14
+ if (type === 'exact') {
15
+ return { [field]: term };
16
+ }
17
+ return { [field]: { $regex: new RegExp(escapeRegex(term), 'i') } };
18
+ }
19
+ function normaliseFields(searchFields) {
20
+ return searchFields.map((f) => {
21
+ var _a;
22
+ return typeof f === 'string'
23
+ ? { field: f, type: 'string' }
24
+ : { field: f.field, type: (_a = f.type) !== null && _a !== void 0 ? _a : 'string' };
25
+ });
26
+ }
27
+ function buildSearch(search, searchFields, searchMode = 'or') {
28
+ if (!search || !searchFields || searchFields.length === 0)
29
+ return {};
30
+ const trimmed = search.trim();
31
+ if (trimmed.length === 0)
32
+ return {};
33
+ const fields = normaliseFields(searchFields);
34
+ if (searchMode === 'and') {
35
+ const terms = trimmed.split(/\s+/).filter((t) => t.length > 0);
36
+ const andClauses = [];
37
+ for (const term of terms) {
38
+ const orConds = fields
39
+ .map((f) => buildFieldCondition(f.field, f.type, term))
40
+ .filter((c) => c !== null);
41
+ if (orConds.length > 0)
42
+ andClauses.push({ $or: orConds });
43
+ }
44
+ if (andClauses.length === 0)
45
+ return {};
46
+ if (andClauses.length === 1)
47
+ return andClauses[0];
48
+ return { $and: andClauses };
49
+ }
50
+ const orConds = fields
51
+ .map((f) => buildFieldCondition(f.field, f.type, trimmed))
52
+ .filter((c) => c !== null);
53
+ if (orConds.length === 0)
54
+ return {};
55
+ return { $or: orConds };
56
+ }
57
+ //# sourceMappingURL=buildSearch.js.map