filter-kit 2.0.0 → 2.0.1

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 (2) hide show
  1. package/README.md +291 -139
  2. package/package.json +46 -9
package/README.md CHANGED
@@ -1,30 +1,90 @@
1
- # mongoose-filter-kit
1
+ <div align="center">
2
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/)
3
+ # filter-kit
6
4
 
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.
5
+ **The last Mongoose query builder you'll ever need.**
6
+ One function. Full REST API filtering — search, paginate, sort, populate, date ranges, operator filters, soft-delete — all from URL query params.
7
+
8
+ [![npm version](https://img.shields.io/npm/v/filter-kit.svg?style=flat-square&color=cb3837&logo=npm)](https://www.npmjs.com/package/filter-kit)
9
+ [![npm downloads](https://img.shields.io/npm/dw/filter-kit.svg?style=flat-square&color=cb3837)](https://www.npmjs.com/package/filter-kit)
10
+ [![Bundle Size](https://img.shields.io/bundlephobia/minzip/filter-kit?style=flat-square&label=minzipped)](https://bundlephobia.com/package/filter-kit)
11
+ [![License: MIT](https://img.shields.io/npm/l/filter-kit?style=flat-square&color=green)](https://opensource.org/licenses/MIT)
12
+ [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-3178c6?style=flat-square&logo=typescript)](https://www.typescriptlang.org/)
13
+ [![Mongoose](https://img.shields.io/badge/Mongoose-6%2B-880000?style=flat-square&logo=mongoose)](https://mongoosejs.com/)
14
+
15
+ </div>
16
+
17
+ ---
18
+
19
+ ## Why filter-kit?
20
+
21
+ - **Zero boilerplate** — one call replaces 50+ lines of hand-written filter logic
22
+ - **URL-driven** — every option is controllable via `req.query`
23
+ - **Smart search** — regex for strings, `$eq` for numbers, exact match mode
24
+ - **Deep populate** — dot-notation nesting up to 4 levels (`user.role.department.company`)
25
+ - **Operator filters** — `_gte`, `_lte`, `_gt`, `_lt`, `_ne`, `_in`, `_nin`, `_exists`, `_regex` via URL
26
+ - **Multi-sort** — `?sort=createdAt,name&order=desc,asc`
27
+ - **Soft-delete guard** — auto-exclude deleted documents with one option
28
+ - **Security gate** — unknown filter keys are silently dropped
29
+ - **Dual CJS + ESM** — works in Node.js `require` and bundler `import`
30
+ - **Full TypeScript** — generic types, zero `@types/` install needed
31
+
32
+ ---
33
+
34
+ ## Demo
35
+
36
+ > **Record a quick GIF** of your terminal or API response and drop it here.
37
+ > Upload to `assets/demo.gif` in your GitHub repo, then replace this line:
38
+
39
+ ![filter-kit demo](https://raw.githubusercontent.com/YOUR_GITHUB_USERNAME/filter-kit/main/assets/demo.gif)
40
+
41
+ **Example — what one URL does:**
42
+
43
+ ```
44
+ GET /users?search=john&role=admin&age_gte=18&sort=createdAt,name&order=desc,asc&page=2&limit=10&populate=user.role&from=2024-01-01
45
+ ```
46
+
47
+ ```json
48
+ {
49
+ "data": [
50
+ { "_id": "...", "name": "John Admin", "role": { "name": "Administrator" }, "age": 28 }
51
+ ],
52
+ "pagination": {
53
+ "total": 47,
54
+ "page": 2,
55
+ "limit": 10,
56
+ "pages": 5,
57
+ "hasNext": true,
58
+ "hasPrev": true
59
+ }
60
+ }
61
+ ```
62
+
63
+ ---
8
64
 
9
65
  ## Install
10
66
 
11
67
  ```bash
12
- npm install mongoose-filter-kit
68
+ npm install filter-kit
69
+ ```
70
+
71
+ ```bash
72
+ yarn add filter-kit
13
73
  ```
14
74
 
15
- Mongoose 6.x or higher is required as a peer dependency:
75
+ Mongoose `>=6.0.0` required as a peer dependency:
16
76
 
17
77
  ```bash
18
78
  npm install mongoose
19
79
  ```
20
80
 
21
- ## JavaScript Usage
81
+ ---
22
82
 
23
- **CommonJS (Node.js `require`)**
83
+ ## Quick Start
24
84
 
85
+ **CommonJS**
25
86
  ```javascript
26
- const { queryBuilder } = require('mongoose-filter-kit');
27
- const User = require('./models/User');
87
+ const { queryBuilder } = require('filter-kit');
28
88
 
29
89
  app.get('/users', async (req, res) => {
30
90
  const result = await queryBuilder({
@@ -38,11 +98,9 @@ app.get('/users', async (req, res) => {
38
98
  });
39
99
  ```
40
100
 
41
- **ESM (`import`)**
42
-
43
- ```javascript
44
- import { queryBuilder } from 'mongoose-filter-kit';
45
- import User from './models/User.js';
101
+ **ESM / TypeScript**
102
+ ```typescript
103
+ import { queryBuilder } from 'filter-kit';
46
104
 
47
105
  app.get('/users', async (req, res) => {
48
106
  const result = await queryBuilder({
@@ -50,97 +108,200 @@ app.get('/users', async (req, res) => {
50
108
  query: req.query,
51
109
  searchFields: ['name', 'email'],
52
110
  allowedFilters: ['role', 'status'],
53
- customFilter: { isDeleted: false },
111
+ defaultSort: 'createdAt',
112
+ defaultOrder: 'desc',
113
+ populate: 'role',
114
+ dateField: 'createdAt',
115
+ softDelete: { field: 'isDeleted' },
54
116
  });
55
117
  res.json(result);
56
118
  });
57
119
  ```
58
120
 
59
- ## Quick Start (TypeScript)
121
+ ---
122
+
123
+ ## Features
124
+
125
+ ### Smart Search — strings, numbers, exact
60
126
 
61
127
  ```typescript
62
- import { queryBuilder } from 'mongoose-filter-kit';
63
- import User from './models/User';
128
+ searchFields: [
129
+ 'name', // string → case-insensitive regex
130
+ 'email', // string → case-insensitive regex
131
+ { field: 'age', type: 'number' }, // number → $eq (skips non-numeric search)
132
+ { field: 'code', type: 'exact' }, // string → exact match, no regex
133
+ ]
134
+ ```
64
135
 
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
- });
136
+ ```
137
+ ?search=john → { $or: [ { name: /john/i }, { email: /john/i } ] }
138
+ ?search=42 → { $or: [ { name: /42/i }, { age: { $eq: 42 } } ] }
139
+ ```
80
140
 
81
- res.json(result);
82
- });
141
+ **AND mode** — every space-separated word must match at least one field:
142
+
143
+ ```typescript
144
+ searchMode: 'and'
145
+ // ?search=john admin
146
+ // → { $and: [
147
+ // { $or: [{ name: /john/i }, { email: /john/i }] },
148
+ // { $or: [{ name: /admin/i }, { email: /admin/i }] }
149
+ // ]}
83
150
  ```
84
151
 
85
- Sample response:
152
+ ---
86
153
 
87
- ```json
154
+ ### Deep Populate — up to 4 levels
155
+
156
+ Use dot-notation in the `?populate=` query param or the `populate` option:
157
+
158
+ ```
159
+ ?populate=user.role.department.company
160
+ ```
161
+
162
+ ```javascript
163
+ // Produces:
88
164
  {
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
165
+ path: 'user',
166
+ populate: {
167
+ path: 'role',
168
+ populate: {
169
+ path: 'department',
170
+ populate: { path: 'company' }
171
+ }
99
172
  }
100
173
  }
101
174
  ```
102
175
 
103
- ## Options Reference
176
+ Mix flat and deep in one request:
177
+ ```
178
+ ?populate=company,user.role
179
+ ```
180
+
181
+ ---
182
+
183
+ ### Operator Filters — via URL
184
+
185
+ Append a suffix to any allowed field name in the URL:
186
+
187
+ | Suffix | MongoDB Op | Example URL | Result |
188
+ |--------|-----------|-------------|--------|
189
+ | `_gte` | `$gte` | `?age_gte=18` | `{ age: { $gte: 18 } }` |
190
+ | `_lte` | `$lte` | `?age_lte=65` | `{ age: { $lte: 65 } }` |
191
+ | `_gt` | `$gt` | `?price_gt=100` | `{ price: { $gt: 100 } }` |
192
+ | `_lt` | `$lt` | `?price_lt=500` | `{ price: { $lt: 500 } }` |
193
+ | `_ne` | `$ne` | `?status_ne=deleted` | `{ status: { $ne: 'deleted' } }` |
194
+ | `_in` | `$in` | `?role_in=admin,editor` | `{ role: { $in: ['admin','editor'] } }` |
195
+ | `_nin` | `$nin` | `?role_nin=banned,guest` | `{ role: { $nin: ['banned','guest'] } }` |
196
+ | `_exists` | `$exists` | `?avatar_exists=false` | `{ avatar: { $exists: false } }` |
197
+ | `_regex` | `$regex` | `?name_regex=^john` | `{ name: { $regex: /^john/i } }` |
198
+
199
+ Multiple operators on the same field are **merged automatically**:
200
+ ```
201
+ ?age_gte=18&age_lte=65 → { age: { $gte: 18, $lte: 65 } }
202
+ ```
203
+
204
+ ---
205
+
206
+ ### Multi-Sort
207
+
208
+ ```
209
+ ?sort=createdAt,name&order=desc,asc
210
+ → { createdAt: -1, name: 1 }
211
+ ```
212
+
213
+ ---
214
+
215
+ ### Multiple Date Ranges
216
+
217
+ Filter different date fields with custom param names:
218
+
219
+ ```typescript
220
+ dateFields: [
221
+ { field: 'createdAt', fromParam: 'createdFrom', toParam: 'createdTo' },
222
+ { field: 'updatedAt', fromParam: 'updatedFrom', toParam: 'updatedTo' },
223
+ ]
224
+ // ?createdFrom=2024-01-01&updatedFrom=2024-06-01
225
+ ```
226
+
227
+ ---
228
+
229
+ ### Soft Delete Guard
230
+
231
+ ```typescript
232
+ softDelete: { field: 'isDeleted' } // excludes { isDeleted: true }
233
+ softDelete: { field: 'deletedAt', value: null } // excludes { deletedAt: null }
234
+ ```
235
+
236
+ Auto-merges into every query via `$and`. Cannot be bypassed by URL params.
237
+
238
+ ---
239
+
240
+ ### Skip Count for Performance
241
+
242
+ ```typescript
243
+ withCount: false // skips countDocuments — faster for infinite scroll / cursor UI
244
+ ```
245
+
246
+ ---
247
+
248
+ ## Full Options Reference
104
249
 
105
250
  | 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`. |
251
+ |--------|------|---------|-------------|
252
+ | `model` | `Model<T>` | **required** | Mongoose model to query. |
253
+ | `query` | `Record<string, any>` | **required** | Parsed query string (`req.query`). |
254
+ | `searchFields` | `SearchField[]` | `undefined` | Fields for `?search=`. Strings = regex, `{ type: 'number' }` = `$eq`, `{ type: 'exact' }` = literal. |
255
+ | `searchMode` | `'or' \| 'and'` | `'or'` | `'or'` any field matches. `'and'` every space-split term must match. |
256
+ | `allowedFilters` | `string[]` | `undefined` | Extra keys allowed as dynamic filters beyond schema paths. |
257
+ | `defaultSort` | `string` | `'createdAt'` | Sort field when `?sort=` absent. |
258
+ | `defaultOrder` | `'asc' \| 'desc'` | `'desc'` | Sort direction when `?order=` absent. |
259
+ | `defaultLimit` | `number` | `10` | Page size when `?limit=` absent. |
260
+ | `maxLimit` | `number` | `100` | Hard cap on page size. |
261
+ | `populate` | `string \| string[] \| PopulateOptions \| PopulateOptions[]` | `undefined` | Populate when `?populate=` absent. |
262
+ | `select` | `ProjectionType<T> \| string` | `undefined` | Field projection when `?select=` absent. |
263
+ | `dateField` | `string` | `undefined` | Single date field for `?from=` / `?to=`. |
264
+ | `dateFields` | `DateFieldConfig[]` | `undefined` | Multiple date fields with custom param names. |
265
+ | `lean` | `boolean` | `false` | Return plain JS objects (faster reads). |
266
+ | `customFilter` | `Record<string, any>` | `undefined` | Always-on filter, merged via `$and`. Cannot be bypassed by URL. |
267
+ | `softDelete` | `SoftDeleteConfig` | `undefined` | Auto-exclude soft-deleted docs (`{ field, value? }`). |
268
+ | `withCount` | `boolean` | `true` | Set `false` to skip `countDocuments` for perf. |
269
+
270
+ ---
120
271
 
121
272
  ## Query Parameters Reference
122
273
 
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. |
274
+ | Param | Example | Description |
275
+ |-------|---------|-------------|
276
+ | `search` | `?search=john` | Case-insensitive search across `searchFields`. |
277
+ | `page` | `?page=2` | Page number (1-based). |
278
+ | `limit` | `?limit=25` | Docs per page (clamped to `maxLimit`). |
279
+ | `sort` | `?sort=name,age` | Comma-separated sort fields. |
280
+ | `order` | `?order=asc,desc` | Per-field order, matches `sort` positions. |
281
+ | `from` | `?from=2024-01-01` | Date range start (`$gte`) on `dateField`. |
282
+ | `to` | `?to=2024-12-31` | Date range end (`$lte`, end of day). |
283
+ | `populate` | `?populate=user.role,company` | Comma-separated paths. Dot = nested populate. |
284
+ | `select` | `?select=name,email` | Comma-separated fields to include. |
285
+ | `<field>` | `?status=active` | Dynamic filter (schema path or `allowedFilters`). |
286
+ | `<field>_gte` | `?age_gte=18` | Greater-than-or-equal operator filter. |
287
+ | `<field>_lte` | `?age_lte=65` | Less-than-or-equal operator filter. |
288
+ | `<field>_gt` | `?price_gt=99` | Greater-than operator filter. |
289
+ | `<field>_lt` | `?price_lt=500` | Less-than operator filter. |
290
+ | `<field>_ne` | `?status_ne=deleted` | Not-equal operator filter. |
291
+ | `<field>_in` | `?role_in=admin,mod` | `$in` array filter. |
292
+ | `<field>_nin` | `?role_nin=banned` | `$nin` array filter. |
293
+ | `<field>_exists` | `?photo_exists=false` | Field existence filter. |
294
+ | `<field>_regex` | `?name_regex=^A` | Regex filter (case-insensitive). |
295
+
296
+ ---
135
297
 
136
298
  ## Examples
137
299
 
138
- ### With Express
300
+ ### Express
139
301
 
140
302
  ```typescript
141
303
  import express from 'express';
142
- import mongoose from 'mongoose';
143
- import { queryBuilder } from 'mongoose-filter-kit';
304
+ import { queryBuilder } from 'filter-kit';
144
305
  import Product from './models/Product';
145
306
 
146
307
  const app = express();
@@ -150,7 +311,11 @@ app.get('/products', async (req, res, next) => {
150
311
  const result = await queryBuilder({
151
312
  model: Product,
152
313
  query: req.query,
153
- searchFields: ['name', 'description', 'sku'],
314
+ searchFields: [
315
+ 'name',
316
+ 'description',
317
+ { field: 'price', type: 'number' },
318
+ ],
154
319
  allowedFilters: ['category', 'brand', 'inStock'],
155
320
  defaultSort: 'createdAt',
156
321
  defaultOrder: 'desc',
@@ -159,6 +324,7 @@ app.get('/products', async (req, res, next) => {
159
324
  dateField: 'createdAt',
160
325
  populate: [{ path: 'category', select: 'name slug' }],
161
326
  customFilter: { isActive: true },
327
+ softDelete: { field: 'isDeleted' },
162
328
  lean: true,
163
329
  });
164
330
 
@@ -169,13 +335,22 @@ app.get('/products', async (req, res, next) => {
169
335
  });
170
336
  ```
171
337
 
172
- ### With NestJS
338
+ Client calls:
339
+ ```
340
+ GET /products?search=shoe&price_gte=50&price_lte=200&brand=nike,adidas&sort=price,name&order=asc,asc&page=1&limit=20
341
+ GET /products?category=electronics&inStock=true&createdAt_exists=true
342
+ GET /products?name_regex=^air&sort=createdAt&order=desc
343
+ ```
344
+
345
+ ---
346
+
347
+ ### NestJS
173
348
 
174
349
  ```typescript
175
350
  import { Controller, Get, Query } from '@nestjs/common';
176
351
  import { InjectModel } from '@nestjs/mongoose';
177
352
  import { Model } from 'mongoose';
178
- import { queryBuilder, QueryBuilderResult } from 'mongoose-filter-kit';
353
+ import { queryBuilder, QueryBuilderResult } from 'filter-kit';
179
354
  import { User, UserDocument } from './user.schema';
180
355
 
181
356
  @Controller('users')
@@ -190,20 +365,19 @@ export class UsersController {
190
365
  model: this.userModel,
191
366
  query,
192
367
  searchFields: ['name', 'email'],
368
+ searchMode: 'and',
193
369
  allowedFilters: ['role', 'status', 'department'],
194
- defaultSort: 'createdAt',
195
- defaultOrder: 'desc',
196
- defaultLimit: 10,
197
- maxLimit: 100,
198
- populate: 'role',
370
+ populate: 'role.permissions', // level 2 deep
199
371
  dateField: 'createdAt',
200
- customFilter: { isDeleted: false },
372
+ softDelete: { field: 'isDeleted' },
201
373
  });
202
374
  }
203
375
  }
204
376
  ```
205
377
 
206
- ### With Custom Filters and Populate Options
378
+ ---
379
+
380
+ ### Advanced — Orders with multiple date ranges
207
381
 
208
382
  ```typescript
209
383
  const result = await queryBuilder({
@@ -211,64 +385,39 @@ const result = await queryBuilder({
211
385
  query: req.query,
212
386
  searchFields: ['orderNumber', 'customerName'],
213
387
  allowedFilters: ['status', 'paymentMethod'],
214
- defaultSort: 'placedAt',
215
- defaultOrder: 'desc',
216
- defaultLimit: 15,
217
- maxLimit: 50,
218
- dateField: 'placedAt',
388
+ dateFields: [
389
+ { field: 'placedAt', fromParam: 'placedFrom', toParam: 'placedTo' },
390
+ { field: 'shippedAt', fromParam: 'shippedFrom', toParam: 'shippedTo' },
391
+ ],
219
392
  populate: [
220
393
  { path: 'customer', select: 'name email phone' },
221
394
  { path: 'items.product', select: 'name sku price' },
222
395
  ],
223
- select: '-__v -internalNotes',
224
- customFilter: { archived: false },
396
+ softDelete: { field: 'isArchived', value: true },
397
+ withCount: false, // infinite scroll — skip the count query
225
398
  lean: true,
226
399
  });
227
400
  ```
228
401
 
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
402
+ Client call:
233
403
  ```
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
- // ]}
404
+ GET /orders?placedFrom=2024-01-01&placedTo=2024-03-31&status=pending,processing&paymentMethod_ne=cash&sort=placedAt&order=desc
247
405
  ```
248
406
 
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:
407
+ ---
252
408
 
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.
409
+ ## Security
254
410
 
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`.
411
+ - **Reserved params** (`page`, `limit`, `sort`, `order`, `search`, `from`, `to`, `populate`, `select`) are never treated as field filters.
412
+ - **Unknown keys** are silently dropped — not in `allowedFilters` and not in `model.schema.paths` = ignored.
413
+ - **`customFilter` and `softDelete`** are always applied server-side — URL params cannot override them.
414
+ - Operator suffixes (`_gte`, etc.) are only applied when the **base field** passes the security gate.
258
415
 
259
- Unknown keys with no schema definition are silently dropped.
416
+ ---
260
417
 
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.
418
+ ## TypeScript
266
419
 
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:
420
+ Full types ship with the package no `@types/` install needed.
272
421
 
273
422
  ```typescript
274
423
  import {
@@ -276,19 +425,22 @@ import {
276
425
  QueryBuilderOptions,
277
426
  QueryBuilderResult,
278
427
  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
428
+ SearchField,
429
+ SearchFieldConfig,
430
+ SearchFieldType,
431
+ DateFieldConfig,
432
+ SoftDeleteConfig,
433
+ } from 'filter-kit';
434
+
435
+ // Generic — result.data is UserDocument[]
285
436
  const result = await queryBuilder<UserDocument>({
286
437
  model: User,
287
438
  query: req.query,
288
439
  });
289
- // result.data is UserDocument[]
290
440
  ```
291
441
 
442
+ ---
443
+
292
444
  ## License
293
445
 
294
- MIT
446
+ [MIT](https://opensource.org/licenses/MIT) © [parthpatel2597](https://www.npmjs.com/~parthpatel2597)
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "filter-kit",
3
- "version": "2.0.0",
4
- "description": "Powerful Mongoose query builder — smart search (string + number), deep populate (4 levels), multi-sort, operator filters, date ranges, soft-delete, pagination",
3
+ "version": "2.0.1",
4
+ "description": "Powerful Mongoose query builder — smart search (string + number), deep populate (4 levels), multi-sort, operator filters (_gte _lte _ne _in _regex), date ranges, soft-delete, pagination. One function for full REST API filtering from URL query params.",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
7
7
  "types": "dist/cjs/index.d.ts",
@@ -17,7 +17,7 @@
17
17
  }
18
18
  }
19
19
  },
20
- "files": ["dist"],
20
+ "files": ["dist", "README.md"],
21
21
  "scripts": {
22
22
  "clean": "node -e \"require('fs').rmSync('dist', { recursive: true, force: true })\"",
23
23
  "build": "npm run clean && npm run build:cjs && npm run build:esm && node scripts/postbuild.js",
@@ -31,14 +31,51 @@
31
31
  "prepublishOnly": "npm run build"
32
32
  },
33
33
  "keywords": [
34
- "mongoose", "mongodb", "query-builder", "filter", "pagination",
35
- "search", "sorting", "date-filter", "populate", "dynamic-filter",
36
- "admin-panel", "rest-api", "express", "nestjs", "mongoose-query",
37
- "mongodb-filter", "mongoose-pagination", "mongoose-search",
38
- "api-query", "mongoose-utils", "javascript", "typescript"
34
+ "filter-kit",
35
+ "mongoose",
36
+ "mongodb",
37
+ "query-builder",
38
+ "mongoose-query-builder",
39
+ "mongodb-query-builder",
40
+ "filter",
41
+ "pagination",
42
+ "mongoose-pagination",
43
+ "mongodb-pagination",
44
+ "search",
45
+ "mongoose-search",
46
+ "sorting",
47
+ "multi-sort",
48
+ "populate",
49
+ "deep-populate",
50
+ "date-filter",
51
+ "dynamic-filter",
52
+ "operator-filter",
53
+ "soft-delete",
54
+ "rest-api",
55
+ "express",
56
+ "nestjs",
57
+ "typescript",
58
+ "mongoose-utils",
59
+ "api-query",
60
+ "url-filter",
61
+ "mongodb-filter"
39
62
  ],
40
- "author": "parthpatel2597",
63
+ "author": {
64
+ "name": "parthpatel2597",
65
+ "url": "https://www.npmjs.com/~parthpatel2597"
66
+ },
41
67
  "license": "MIT",
68
+ "homepage": "https://www.npmjs.com/package/filter-kit",
69
+ "repository": {
70
+ "type": "git",
71
+ "url": "https://github.com/parthpatel2597/filter-kit"
72
+ },
73
+ "bugs": {
74
+ "url": "https://github.com/parthpatel2597/filter-kit/issues"
75
+ },
76
+ "engines": {
77
+ "node": ">=16.0.0"
78
+ },
42
79
  "peerDependencies": { "mongoose": ">=6.0.0" },
43
80
  "devDependencies": {
44
81
  "@types/jest": "^29.5.0",