{"_id":"@elchinabilov/nestjs-restapi-filters","_rev":"4-b26a621db0447f29b8ecfc030ac0e0fb","name":"@elchinabilov/nestjs-restapi-filters","dist-tags":{"latest":"1.1.2"},"versions":{"1.0.0":{"name":"@elchinabilov/nestjs-restapi-filters","version":"1.0.0","keywords":["nestjs","filters","restapi","api","strapi","query","filter-engine","rest-api-filters","query-params","in-memory-filter"],"author":{"name":"Elchin Abilov","email":"abilovelchin@gmail.com"},"license":"MIT","_id":"@elchinabilov/nestjs-restapi-filters@1.0.0","maintainers":[{"name":"abilov","email":"abilovelchin@gmail.com"}],"dist":{"shasum":"adc1be5968dc4dbba31129e86f2bd023acb07977","tarball":"https://registry.npmjs.org/@elchinabilov/nestjs-restapi-filters/-/nestjs-restapi-filters-1.0.0.tgz","fileCount":45,"integrity":"sha512-EAWB8llICOlFFa5Uml3pRMLNdtJGfbuyKA64Wy1BG4ARto32cN4dc6yO5eEkk/UrymiCgJAjNPjTnkGQRjYEeA==","signatures":[{"sig":"MEYCIQDKkNkRemoAmMeUNrHCWgys3aSTIyN++jHe7bnEdG+6gQIhAPtOOwEA0GO364eV9asvMhAC/zC/ogg0uClx9G87enYr","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":241258},"main":"dist/index.js","types":"dist/index.d.ts","engines":{"node":">=18.0.0"},"scripts":{"lint":"eslint \"src/**/*.ts\" --fix","test":"jest","build":"tsc","format":"prettier --write \"src/**/*.ts\" \"test/**/*.ts\"","test:cov":"jest --coverage","prepublish":"npm run build","test:watch":"jest --watch","prepublishOnly":"npm run build"},"_npmUser":{"name":"abilov","email":"abilovelchin@gmail.com"},"_npmVersion":"11.7.0","description":"Strapi-style REST API filter engine for NestJS. Filter any in-memory array with query-parameter syntax like filters[field][$operator]=value","directories":{},"_nodeVersion":"22.19.0","dependencies":{"rxjs":"latest","supertest":"latest","@nestjs/core":"latest","@nestjs/common":"latest","@nestjs/swagger":"latest","@nestjs/testing":"latest","class-validator":"latest","reflect-metadata":"latest","class-transformer":"latest","swagger-ui-express":"latest","@nestjs/platform-express":"latest"},"_hasShrinkwrap":false,"devDependencies":{"jest":"latest","ts-jest":"latest","ts-node":"latest","supertest":"latest","ts-loader":"latest","typescript":"latest","@nestjs/cli":"latest","@types/jest":"latest","@types/node":"latest","@types/express":"latest","tsconfig-paths":"latest","@nestjs/swagger":"latest","@nestjs/testing":"latest","class-validator":"latest","@types/supertest":"latest","class-transformer":"latest","@nestjs/schematics":"latest","source-map-support":"latest","swagger-ui-express":"latest"},"_npmOperationalInternal":{"tmp":"tmp/nestjs-restapi-filters_1.0.0_1772425207583_0.956374520999915","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"@elchinabilov/nestjs-restapi-filters","version":"1.1.0","keywords":["nestjs","filters","restapi","api","strapi","query","filter-engine","rest-api-filters","query-params","in-memory-filter"],"author":{"name":"Elchin Abilov","email":"abilovelchin@gmail.com"},"license":"MIT","_id":"@elchinabilov/nestjs-restapi-filters@1.1.0","maintainers":[{"name":"abilov","email":"abilovelchin@gmail.com"}],"dist":{"shasum":"f681fafa90417cdddef06f0489ddd23f6bedd486","tarball":"https://registry.npmjs.org/@elchinabilov/nestjs-restapi-filters/-/nestjs-restapi-filters-1.1.0.tgz","fileCount":58,"integrity":"sha512-92APnMGTy+EY1PVnB/WBv1vTl9ED+l0rkeMnO5NDux+Du8ambzGdEB6ILgoSf/HKlFIGG12khErKnHC8l/HOOw==","signatures":[{"sig":"MEUCIAozEYs81RGso30XVizrC1v5bGoGcRqE2eabnJCMWVflAiEAxIuZZ/IDoj7H3vA8ebam32JCKb9+IPB4HvOR9kIF1lE=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":271402},"main":"dist/index.js","types":"dist/index.d.ts","engines":{"node":">=18.0.0"},"gitHead":"fb43542874173a6ad92faa59d9f8647873624bf2","scripts":{"lint":"eslint \"src/**/*.ts\" --fix","test":"jest","build":"tsc && node scripts/postbuild.js","format":"prettier --write \"src/**/*.ts\" \"test/**/*.ts\"","test:cov":"jest --coverage","prepublish":"npm run build","test:watch":"jest --watch","prepublishOnly":"npm run build"},"_npmUser":{"name":"abilov","email":"abilovelchin@gmail.com"},"_npmVersion":"11.7.0","description":"Strapi-style REST API filter engine for NestJS. Filter any in-memory array with query-parameter syntax like filters[field][$operator]=value","directories":{},"_nodeVersion":"22.19.0","dependencies":{"rxjs":"latest","supertest":"latest","@nestjs/core":"latest","@nestjs/common":"latest","@nestjs/swagger":"latest","@nestjs/testing":"latest","class-validator":"latest","reflect-metadata":"latest","class-transformer":"latest","swagger-ui-express":"latest","@nestjs/platform-express":"latest"},"_hasShrinkwrap":false,"devDependencies":{"jest":"latest","ts-jest":"latest","ts-node":"latest","typeorm":"^0.3.28","supertest":"latest","ts-loader":"latest","typescript":"latest","@nestjs/cli":"latest","@types/jest":"latest","@types/node":"latest","@types/express":"latest","tsconfig-paths":"latest","@nestjs/swagger":"latest","@nestjs/testing":"latest","class-validator":"latest","@types/supertest":"latest","class-transformer":"latest","@nestjs/schematics":"latest","source-map-support":"latest","swagger-ui-express":"latest"},"peerDependencies":{"typeorm":">=0.3.0"},"peerDependenciesMeta":{"typeorm":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/nestjs-restapi-filters_1.1.0_1772434173657_0.5579708628605646","host":"s3://npm-registry-packages-npm-production"}},"1.1.1":{"name":"@elchinabilov/nestjs-restapi-filters","version":"1.1.1","keywords":["nestjs","filters","restapi","api","strapi","query","filter-engine","rest-api-filters","query-params","in-memory-filter"],"author":{"name":"Elchin Abilov","email":"abilovelchin@gmail.com"},"license":"MIT","_id":"@elchinabilov/nestjs-restapi-filters@1.1.1","maintainers":[{"name":"abilov","email":"abilovelchin@gmail.com"}],"dist":{"shasum":"053b28ec1c81b3c8c15923aa936a23a12be42c2c","tarball":"https://registry.npmjs.org/@elchinabilov/nestjs-restapi-filters/-/nestjs-restapi-filters-1.1.1.tgz","fileCount":54,"integrity":"sha512-PjUVBmS8sGlCt7mNX1SFN4Ngb0kKi+B0Ffy3AKyJxOWj1TC89Zyg6LIfV4pdyf/6bgKhgBtUpf/HAi3FOWgoTw==","signatures":[{"sig":"MEUCIBTXE75SoEt85GlEhraGaUrWp2hx/0Ui9xuQYZLCn8IaAiEAxcQV7L/6nTVhZ74664mBkZGBWn6tQF2Rg18RosRvXfE=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":270420},"main":"dist/index.js","types":"dist/index.d.ts","engines":{"node":">=18.0.0"},"gitHead":"edd5174270f0b4b5fe2a71bd4220eb1565b060ed","scripts":{"lint":"eslint \"src/**/*.ts\" --fix","test":"jest","build":"tsc","format":"prettier --write \"src/**/*.ts\" \"test/**/*.ts\"","test:cov":"jest --coverage","prepublish":"npm run build","test:watch":"jest --watch","prepublishOnly":"npm run build"},"_npmUser":{"name":"abilov","email":"abilovelchin@gmail.com"},"_npmVersion":"11.7.0","description":"Strapi-style REST API filter engine for NestJS. Filter any in-memory array with query-parameter syntax like filters[field][$operator]=value","directories":{},"_nodeVersion":"22.19.0","dependencies":{"rxjs":"latest","supertest":"latest","@nestjs/core":"latest","@nestjs/common":"latest","@nestjs/swagger":"latest","@nestjs/testing":"latest","class-validator":"latest","reflect-metadata":"latest","class-transformer":"latest","swagger-ui-express":"latest","@nestjs/platform-express":"latest"},"_hasShrinkwrap":false,"devDependencies":{"jest":"latest","ts-jest":"latest","ts-node":"latest","typeorm":"^0.3.28","supertest":"latest","ts-loader":"latest","typescript":"latest","@nestjs/cli":"latest","@types/jest":"latest","@types/node":"latest","@types/express":"latest","tsconfig-paths":"latest","@nestjs/swagger":"latest","@nestjs/testing":"latest","class-validator":"latest","@types/supertest":"latest","class-transformer":"latest","@nestjs/schematics":"latest","source-map-support":"latest","swagger-ui-express":"latest"},"peerDependencies":{"typeorm":">=0.3.0"},"peerDependenciesMeta":{"typeorm":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/nestjs-restapi-filters_1.1.1_1772435768906_0.353969315338756","host":"s3://npm-registry-packages-npm-production"}},"1.1.2":{"name":"@elchinabilov/nestjs-restapi-filters","version":"1.1.2","description":"Strapi-style REST API filter engine for NestJS. Filter any in-memory array with query-parameter syntax like filters[field][$operator]=value","author":{"name":"Elchin Abilov","email":"abilovelchin@gmail.com"},"license":"MIT","keywords":["nestjs","filters","restapi","api","strapi","query","filter-engine","rest-api-filters","query-params","in-memory-filter"],"main":"dist/index.js","types":"dist/index.d.ts","scripts":{"build":"tsc","format":"prettier --write \"src/**/*.ts\" \"test/**/*.ts\"","lint":"eslint \"src/**/*.ts\" --fix","test":"jest","test:watch":"jest --watch","test:cov":"jest --coverage","prepublishOnly":"npm run build","prepublish":"npm run build"},"dependencies":{"@nestjs/common":"latest","@nestjs/core":"latest","@nestjs/platform-express":"latest","@nestjs/swagger":"latest","@nestjs/testing":"latest","class-transformer":"latest","class-validator":"latest","reflect-metadata":"latest","rxjs":"latest","supertest":"latest","swagger-ui-express":"latest"},"devDependencies":{"@nestjs/cli":"latest","@nestjs/schematics":"latest","@nestjs/swagger":"latest","@nestjs/testing":"latest","@types/express":"latest","@types/jest":"latest","@types/node":"latest","@types/supertest":"latest","class-transformer":"latest","class-validator":"latest","jest":"latest","source-map-support":"latest","supertest":"latest","swagger-ui-express":"latest","ts-jest":"latest","ts-loader":"latest","ts-node":"latest","tsconfig-paths":"latest","typeorm":"^0.3.28","typescript":"latest"},"peerDependencies":{"typeorm":">=0.3.0"},"peerDependenciesMeta":{"typeorm":{"optional":true}},"engines":{"node":">=18.0.0"},"gitHead":"f7c7d2cdcc585d245c9ee1ac7c2513c66121c976","_id":"@elchinabilov/nestjs-restapi-filters@1.1.2","_nodeVersion":"22.19.0","_npmVersion":"11.7.0","dist":{"integrity":"sha512-E3Uyu0Ugu3zcssWDVO5Y7JnCJPRF14RPHi1dTN9w9EfWMZMJQG2rPieumKwAmKipmPHxHkPTJEhzPTtFfZ9F/g==","shasum":"d561be3662167eb7396fe2cb33c9813254f62092","tarball":"https://registry.npmjs.org/@elchinabilov/nestjs-restapi-filters/-/nestjs-restapi-filters-1.1.2.tgz","fileCount":54,"unpackedSize":276183,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQD3JIOQc1IvZzykGEKcxdSj2SZWoxP1pnrfhAo9pG3VMwIgc0FaZS2qlry8K5PKzFaRjUtkyHvrshVWuSn1ir3M/mo="}]},"_npmUser":{"name":"abilov","email":"abilovelchin@gmail.com"},"directories":{},"maintainers":[{"name":"abilov","email":"abilovelchin@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/nestjs-restapi-filters_1.1.2_1772439550100_0.7636842534285193"},"_hasShrinkwrap":false}},"time":{"created":"2026-03-02T04:20:07.517Z","modified":"2026-03-02T08:19:10.406Z","1.0.0":"2026-03-02T04:20:07.740Z","1.1.0":"2026-03-02T06:49:33.820Z","1.1.1":"2026-03-02T07:16:09.082Z","1.1.2":"2026-03-02T08:19:10.260Z"},"author":{"name":"Elchin Abilov","email":"abilovelchin@gmail.com"},"license":"MIT","keywords":["nestjs","filters","restapi","api","strapi","query","filter-engine","rest-api-filters","query-params","in-memory-filter"],"description":"Strapi-style REST API filter engine for NestJS. Filter any in-memory array with query-parameter syntax like filters[field][$operator]=value","maintainers":[{"name":"abilov","email":"abilovelchin@gmail.com"}],"readme":"# @elchinabilov/nestjs-restapi-filters\n\nStrapi-style REST API filter engine for NestJS. Filter any in-memory array with query-parameter syntax.\n\n```\nGET /api/users?filters[name][$eq]=John&filters[age][$gte]=18\n```\n\n## Installation\n\n```bash\nnpm install @elchinabilov/nestjs-restapi-filters\n```\n\n## Quick Start\n\n### 1. Import the module\n\n```typescript\nimport { Module } from '@nestjs/common';\nimport { RestApiFiltersModule } from '@elchinabilov/nestjs-restapi-filters';\n\n@Module({\n  imports: [RestApiFiltersModule],\n})\nexport class AppModule {}\n```\n\n### 2. Use in a controller\n\n```typescript\nimport { Controller, Get } from '@nestjs/common';\nimport {\n  Filters,\n  FilterQuery,\n  FilterEngineService,\n} from '@elchinabilov/nestjs-restapi-filters';\n\n@Controller('users')\nexport class UsersController {\n  private users = [\n    { id: 1, name: 'John',  age: 25, role: 'admin' },\n    { id: 2, name: 'Jane',  age: 30, role: 'user' },\n    { id: 3, name: 'Bob',   age: 22, role: 'user' },\n    { id: 4, name: 'Alice', age: 35, role: 'admin' },\n  ];\n\n  constructor(private readonly filterEngine: FilterEngineService) {}\n\n  @Get()\n  findAll(@Filters() filters: FilterQuery) {\n    return this.filterEngine.applyFilters(this.users, filters);\n  }\n}\n```\n\nNow you can query:\n\n```\nGET /users?filters[role][$eq]=admin\nGET /users?filters[age][$gte]=25&filters[role][$eq]=user\nGET /users?filters[name][$containsi]=jo\n```\n\n---\n\n## Standalone Usage (without DI)\n\n```typescript\nimport { applyFilters } from '@elchinabilov/nestjs-restapi-filters';\n\nconst data = [\n  { id: 1, name: 'John', age: 25 },\n  { id: 2, name: 'Jane', age: 30 },\n];\n\nconst result = applyFilters(data, {\n  age: { $gte: 25 },\n  name: { $startsWith: 'J' },\n});\n// → [{ id: 1, name: 'John', age: 25 }, { id: 2, name: 'Jane', age: 30 }]\n```\n\n---\n\n## Available Operators\n\n\n| Operator        | Description                         | Example                                                     |\n| --------------- | ----------------------------------- | ----------------------------------------------------------- |\n| `$eq`           | Equal                               | `filters[name][$eq]=John`                                   |\n| `$eqi`          | Equal (case-insensitive)            | `filters[name][$eqi]=john`                                  |\n| `$ne`           | Not equal                           | `filters[role][$ne]=admin`                                  |\n| `$nei`          | Not equal (case-insensitive)        | `filters[role][$nei]=ADMIN`                                 |\n| `$lt`           | Less than                           | `filters[age][$lt]=30`                                      |\n| `$lte`          | Less than or equal to               | `filters[age][$lte]=30`                                     |\n| `$gt`           | Greater than                        | `filters[age][$gt]=18`                                      |\n| `$gte`          | Greater than or equal to            | `filters[age][$gte]=18`                                     |\n| `$in`           | Included in an array                | `filters[id][$in][0]=1&filters[id][$in][1]=2`               |\n| `$notIn`        | Not included in an array            | `filters[id][$notIn][0]=3&filters[id][$notIn][1]=4`         |\n| `$contains`     | Contains substring                  | `filters[name][$contains]=ohn`                              |\n| `$notContains`  | Does not contain substring          | `filters[name][$notContains]=xyz`                           |\n| `$containsi`    | Contains (case-insensitive)         | `filters[name][$containsi]=OHN`                             |\n| `$notContainsi` | Does not contain (case-insensitive) | `filters[name][$notContainsi]=XYZ`                          |\n| `$startsWith`   | Starts with                         | `filters[name][$startsWith]=Jo`                             |\n| `$startsWithi`  | Starts with (case-insensitive)      | `filters[name][$startsWithi]=jo`                            |\n| `$endsWith`     | Ends with                           | `filters[name][$endsWith]=hn`                               |\n| `$endsWithi`    | Ends with (case-insensitive)        | `filters[name][$endsWithi]=HN`                              |\n| `$null`         | Is null                             | `filters[avatar][$null]=true`                               |\n| `$notNull`      | Is not null                         | `filters[avatar][$notNull]=true`                            |\n| `$between`      | Between two values                  | `filters[age][$between][0]=18&filters[age][$between][1]=30` |\n\n\n### Logical Operators\n\n\n| Operator | Description               | Example                                                            |\n| -------- | ------------------------- | ------------------------------------------------------------------ |\n| `$and`   | All conditions must match | `filters[$and][0][age][$gte]=18&filters[$and][1][role][$eq]=admin` |\n| `$or`    | At least one must match   | `filters[$or][0][role][$eq]=admin&filters[$or][1][role][$eq]=user` |\n| `$not`   | Negate a condition        | `filters[$not][role][$eq]=admin`                                   |\n\n\n> **Note:** Multiple fields at the same level are implicitly combined with `$and`.\n\n---\n\n## Deep Filtering (Nested Fields)\n\nFilter on nested object properties:\n\n```\nGET /api/books?filters[author][name][$eq]=John\n```\n\n```typescript\nconst books = [\n  { id: 1, title: 'Book A', author: { name: 'John', country: 'US' } },\n  { id: 2, title: 'Book B', author: { name: 'Jane', country: 'UK' } },\n];\n\nconst result = applyFilters(books, {\n  author: { name: { $eq: 'John' } },\n});\n// → [{ id: 1, title: 'Book A', author: { name: 'John', country: 'US' } }]\n```\n\n---\n\n## Complex Filtering\n\nCombine `$and`, `$or`, and `$not` for advanced queries:\n\n```\nGET /api/books?filters[$and][0][$or][0][date][$eq]=2020-01-01&filters[$and][0][$or][1][date][$eq]=2020-01-02&filters[$and][1][author][name][$eq]=John\n```\n\n```typescript\nconst result = applyFilters(books, {\n  $and: [\n    {\n      $or: [\n        { date: { $eq: '2020-01-01' } },\n        { date: { $eq: '2020-01-02' } },\n      ],\n    },\n    {\n      author: { name: { $eq: 'John' } },\n    },\n  ],\n});\n```\n\n---\n\n## Validation Pipe\n\nUse `ParseFiltersPipe` to validate and sanitize incoming filter queries:\n\n```typescript\nimport { Filters, ParseFiltersPipe, FilterQuery } from '@elchinabilov/nestjs-restapi-filters';\n\n@Get()\nfindAll(\n  @Filters(new ParseFiltersPipe({ maxDepth: 5, strict: true }))\n  filters: FilterQuery,\n) {\n  return this.filterEngine.applyFilters(this.data, filters);\n}\n```\n\n\n| Option     | Default | Description                            |\n| ---------- | ------- | -------------------------------------- |\n| `maxDepth` | `10`    | Maximum nesting depth (DoS protection) |\n| `strict`   | `false` | Throw on unknown operators like `$foo` |\n\n\n---\n\n## Module Configuration\n\n```typescript\nRestApiFiltersModule.forRoot({\n  autoCoerce: true,   // auto-convert '18' → 18, 'true' → true  (default: true)\n  maxDepth: 5,        // max filter nesting depth                (default: 10)\n});\n```\n\n---\n\n## Auto Type Coercion\n\nQuery parameters are always strings. By default, the engine auto-coerces:\n\n\n| Input string | Coerced value |\n| ------------ | ------------- |\n| `'true'`     | `true`        |\n| `'false'`    | `false`       |\n| `'null'`     | `null`        |\n| `'42'`       | `42`          |\n| `'3.14'`     | `3.14`        |\n| `'hello'`    | `'hello'`     |\n\n\nDisable with `{ autoCoerce: false }`.\n\n---\n\n## TypeORM Integration (Database-level Filtering)\n\nFilter directly at the database level by chaining `.applyFilters()` on any TypeORM `SelectQueryBuilder`.\n\n### Setup (2 steps)\n\n**Step 1.** Add the type declaration to your project.\n\nCreate a file `src/types/typeorm-filters.d.ts` (or any `.d.ts` inside your `src/`):\n\n```typescript\nimport { FilterQuery, TypeOrmFilterOptions } from '@elchinabilov/nestjs-restapi-filters';\n\ndeclare module 'typeorm' {\n  interface SelectQueryBuilder<Entity> {\n    applyFilters(filters: FilterQuery, options?: TypeOrmFilterOptions): this;\n  }\n}\n```\n\n**Step 2.** Call `extendQueryBuilderWithFilters()` **once** at application startup:\n\n```typescript\n// main.ts\nimport { extendQueryBuilderWithFilters } from '@elchinabilov/nestjs-restapi-filters';\n\nextendQueryBuilderWithFilters();\n\nasync function bootstrap() {\n  const app = await NestFactory.create(AppModule);\n  await app.listen(3000);\n}\nbootstrap();\n```\n\n> Step 1 provides TypeScript type support (autocomplete, type checking).\n> Step 2 adds the actual runtime method to the QueryBuilder prototype.\n\n### Usage in a service / repository\n\n```typescript\nimport { Injectable } from '@nestjs/common';\nimport { InjectRepository } from '@nestjs/typeorm';\nimport { Repository } from 'typeorm';\nimport { Filters, FilterQuery } from '@elchinabilov/nestjs-restapi-filters';\nimport { Order } from './order.entity';\n\n@Injectable()\nexport class OrderService {\n  constructor(\n    @InjectRepository(Order)\n    private readonly orderRepo: Repository<Order>,\n  ) {}\n\n  async findAll(filters: FilterQuery) {\n    const qb = this.orderRepo.createQueryBuilder('order');\n\n    const [data, total] = await qb\n      .leftJoin('order.user', 'user')\n      .addSelect(['user.name', 'user.surname', 'user.phone'])\n      .leftJoin('order.organization', 'organization')\n      .addSelect([\n        'organization.id',\n        'organization.name',\n        'organization.slug',\n      ])\n      .applyFilters(filters)\n      .orderBy('order.createdAt', 'DESC')\n      .getManyAndCount();\n\n    return { data, total };\n  }\n}\n```\n\n### Controller\n\n```typescript\n@Controller('orders')\nexport class OrderController {\n  constructor(private readonly orderService: OrderService) {}\n\n  @Get()\n  findAll(@Filters() filters: FilterQuery) {\n    return this.orderService.findAll(filters);\n  }\n}\n```\n\n### How alias resolution works\n\nThe method auto-detects all joined aliases from the query builder and resolves fields in 3 ways:\n\n| Scenario | Filter | Generated SQL (MySQL) |\n| -------- | ------ | --------------------- |\n| **Regular column** | `filters[status][$eq]=active` | `order.status = :_filter_0` |\n| **Joined relation** | `filters[user][name][$containsi]=john` | `LOWER(user.name) LIKE :_filter_0` |\n| **JSON column** | `filters[meta][pricing][total][$gte]=100` | `JSON_UNQUOTE(JSON_EXTRACT(order.meta, '$.pricing.total')) >= :_filter_0` |\n| **JSON column (shallow)** | `filters[serviceMethod][name][$containsi]=test` | `LOWER(JSON_UNQUOTE(JSON_EXTRACT(order.serviceMethod, '$.name'))) LIKE :_filter_0` |\n| **Logical** | `filters[$or][0][status][$eq]=pending&...` | `(order.status = :_filter_0 OR order.status = :_filter_1)` |\n\nResolution logic:\n\n1. Field **matches a joined alias** → relation column (e.g. `user.name`)\n2. Field **has direct operators** (`$eq`, `$gte`, ...) → regular column (e.g. `order.status`)\n3. Field **not an alias, nested objects** → **JSON column** with `JSON_EXTRACT` (MySQL), `#>>` (PostgreSQL), `json_extract` (SQLite), `JSON_VALUE` (MSSQL)\n\n### JSON Column Filtering\n\nNested filters on non-joined fields are automatically treated as JSON column paths:\n\n```\nGET /orders?filters[meta][pricing][total][$gte]=100\nGET /orders?filters[serviceMethod][name][$containsi]=test\nGET /orders?filters[deliveryAddress][city][$eq]=Baku\n```\n\nDatabase type is **auto-detected** from the TypeORM connection. You can also set it manually:\n\n```typescript\nqb.applyFilters(filters, { dbType: 'postgres' });\n```\n\n| Database         | JSON extraction syntax                                    |\n| ---------------- | --------------------------------------------------------- |\n| MySQL / MariaDB  | `JSON_UNQUOTE(JSON_EXTRACT(col, '$.path'))`               |\n| PostgreSQL       | `col #>> '{path,to,field}'`                               |\n| SQLite           | `json_extract(col, '$.path')`                             |\n| MSSQL            | `JSON_VALUE(col, '$.path')`                               |\n\n### Options\n\n```typescript\nqb.applyFilters(filters, {\n  alias: 'o',           // override root entity alias (default: qb.alias)\n  autoCoerce: false,    // disable auto type coercion\n  dbType: 'postgres',   // override database type (default: auto-detected)\n});\n```\n\n---\n\n## API Reference\n\n### `FilterEngineService`\n\n\n| Method         | Signature                                                                              | Description                        |\n| -------------- | -------------------------------------------------------------------------------------- | ---------------------------------- |\n| `applyFilters` | `applyFilters<T>(data: T[], filters: FilterQuery, options?: FilterEngineOptions): T[]` | Filter an array by the given query |\n\n\n### `applyFilters()` (standalone)\n\n```typescript\nimport { applyFilters } from '@elchinabilov/nestjs-restapi-filters';\nconst result = applyFilters(data, filters, options?);\n```\n\n### `@Filters()` (decorator)\n\n```typescript\n@Get()\nfindAll(@Filters() filters: FilterQuery) { ... }\n\n// Custom query key:\n@Get()\nfindAll(@Filters('filter') filters: FilterQuery) { ... }\n```\n\n### `.applyFilters()` (TypeORM QueryBuilder)\n\n```typescript\nimport { extendQueryBuilderWithFilters } from '@elchinabilov/nestjs-restapi-filters';\n\n// Call once at startup\nextendQueryBuilderWithFilters();\n\n// Then use on any SelectQueryBuilder\nconst [data, total] = await qb\n  .applyFilters(filters)\n  .getManyAndCount();\n```\n\n---\n\n## License\n\nMIT","readmeFilename":"README.md"}