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.
- package/README.md +294 -0
- package/dist/cjs/filters/buildDateFilter.d.ts +11 -0
- package/dist/cjs/filters/buildDateFilter.d.ts.map +1 -0
- package/dist/cjs/filters/buildDateFilter.js +39 -0
- package/dist/cjs/filters/buildDateFilter.js.map +1 -0
- package/dist/cjs/filters/buildDynamicFilters.d.ts +3 -0
- package/dist/cjs/filters/buildDynamicFilters.d.ts.map +1 -0
- package/dist/cjs/filters/buildDynamicFilters.js +110 -0
- package/dist/cjs/filters/buildDynamicFilters.js.map +1 -0
- package/dist/cjs/filters/buildPagination.d.ts +15 -0
- package/dist/cjs/filters/buildPagination.d.ts.map +1 -0
- package/dist/cjs/filters/buildPagination.js +30 -0
- package/dist/cjs/filters/buildPagination.js.map +1 -0
- package/dist/cjs/filters/buildPopulate.d.ts +3 -0
- package/dist/cjs/filters/buildPopulate.d.ts.map +1 -0
- package/dist/cjs/filters/buildPopulate.js +45 -0
- package/dist/cjs/filters/buildPopulate.js.map +1 -0
- package/dist/cjs/filters/buildSearch.d.ts +4 -0
- package/dist/cjs/filters/buildSearch.d.ts.map +1 -0
- package/dist/cjs/filters/buildSearch.js +57 -0
- package/dist/cjs/filters/buildSearch.js.map +1 -0
- package/dist/cjs/filters/buildSort.d.ts +2 -0
- package/dist/cjs/filters/buildSort.d.ts.map +1 -0
- package/dist/cjs/filters/buildSort.js +19 -0
- package/dist/cjs/filters/buildSort.js.map +1 -0
- package/dist/cjs/index.d.ts +3 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +6 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/queryBuilder.d.ts +3 -0
- package/dist/cjs/queryBuilder.d.ts.map +1 -0
- package/dist/cjs/queryBuilder.js +75 -0
- package/dist/cjs/queryBuilder.js.map +1 -0
- package/dist/cjs/types.d.ts +48 -0
- package/dist/cjs/types.d.ts.map +1 -0
- package/dist/cjs/types.js +3 -0
- package/dist/cjs/types.js.map +1 -0
- package/dist/esm/filters/buildDateFilter.d.ts +11 -0
- package/dist/esm/filters/buildDateFilter.d.ts.map +1 -0
- package/dist/esm/filters/buildDateFilter.js +36 -0
- package/dist/esm/filters/buildDateFilter.js.map +1 -0
- package/dist/esm/filters/buildDynamicFilters.d.ts +3 -0
- package/dist/esm/filters/buildDynamicFilters.d.ts.map +1 -0
- package/dist/esm/filters/buildDynamicFilters.js +107 -0
- package/dist/esm/filters/buildDynamicFilters.js.map +1 -0
- package/dist/esm/filters/buildPagination.d.ts +15 -0
- package/dist/esm/filters/buildPagination.d.ts.map +1 -0
- package/dist/esm/filters/buildPagination.js +27 -0
- package/dist/esm/filters/buildPagination.js.map +1 -0
- package/dist/esm/filters/buildPopulate.d.ts +3 -0
- package/dist/esm/filters/buildPopulate.d.ts.map +1 -0
- package/dist/esm/filters/buildPopulate.js +42 -0
- package/dist/esm/filters/buildPopulate.js.map +1 -0
- package/dist/esm/filters/buildSearch.d.ts +4 -0
- package/dist/esm/filters/buildSearch.d.ts.map +1 -0
- package/dist/esm/filters/buildSearch.js +54 -0
- package/dist/esm/filters/buildSearch.js.map +1 -0
- package/dist/esm/filters/buildSort.d.ts +2 -0
- package/dist/esm/filters/buildSort.d.ts.map +1 -0
- package/dist/esm/filters/buildSort.js +16 -0
- package/dist/esm/filters/buildSort.js.map +1 -0
- package/dist/esm/index.d.ts +3 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/queryBuilder.d.ts +3 -0
- package/dist/esm/queryBuilder.d.ts.map +1 -0
- package/dist/esm/queryBuilder.js +72 -0
- package/dist/esm/queryBuilder.js.map +1 -0
- package/dist/esm/types.d.ts +48 -0
- package/dist/esm/types.d.ts.map +1 -0
- package/dist/esm/types.js +2 -0
- package/dist/esm/types.js.map +1 -0
- package/package.json +54 -0
package/README.md
ADDED
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
# mongoose-filter-kit
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/mongoose-filter-kit)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](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 @@
|
|
|
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 @@
|
|
|
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
|