@querykitjs/class-validator 1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Suhrobbek Soatov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.en.md ADDED
@@ -0,0 +1,190 @@
1
+ <div align="right">
2
+
3
+ **English** · [O'zbekcha](./README.md)
4
+
5
+ </div>
6
+
7
+ # @querykitjs/class-validator
8
+
9
+ > **class-validator** decorators and DTOs that validate the querykit request contract (filters, sort, pagination). Operators come from `@querykitjs/core`; DTO output is **assignable** to core types.
10
+
11
+ Validates the incoming JSON body of NestJS list endpoints — the full operator set, nested `and/or/not` filters, and **all three paginations** (offset/infinite/cursor). Pass the validated payload straight to a querykit repository.
12
+
13
+ Same role as [`@querykitjs/zod`](../zod/README.en.md), different tool: the same contract and the same operator source, expressed as class-validator decorators instead of zod schemas.
14
+
15
+ ## Install
16
+
17
+ ```bash
18
+ bun add @querykitjs/class-validator class-validator class-transformer
19
+ # @querykitjs/core comes transitively (dependency)
20
+ ```
21
+
22
+ `reflect-metadata` is required too — Nest apps already import it in `main.ts`. Outside Nest, put this at the very top of your entry file:
23
+
24
+ ```ts
25
+ import "reflect-metadata";
26
+ ```
27
+
28
+ Without it, class-transformer's `@Type()` throws `Reflect.getMetadata is not a function`.
29
+
30
+ ## Usage (NestJS)
31
+
32
+ `main.ts`:
33
+
34
+ ```ts
35
+ app.useGlobalPipes(new ValidationPipe({ transform: true, whitelist: true }));
36
+ ```
37
+
38
+ Controller — the **factory** form (recommended):
39
+
40
+ ```ts
41
+ import { makeOffsetParamsDto } from "@querykitjs/class-validator";
42
+
43
+ const ListBuyersDto = makeOffsetParamsDto({ allowedKeys: ["buyerName", "createdAt", "status"] });
44
+ type ListBuyersDto = InstanceType<typeof ListBuyersDto>;
45
+
46
+ @Post("list")
47
+ list(@Body() params: ListBuyersDto) {
48
+ return this.buyersRepository.findList(params); // core-aligned, no `as` cast
49
+ }
50
+ ```
51
+
52
+ Or the **subclass** form, when you want to add fields of your own:
53
+
54
+ ```ts
55
+ import { IsQueryFilter, OffsetParamsDto } from "@querykitjs/class-validator";
56
+ import type { Filter } from "@querykitjs/core";
57
+
58
+ class ListBuyersDto extends OffsetParamsDto {
59
+ @IsOptional()
60
+ @IsQueryFilter({ allowedKeys: ["buyerName", "createdAt", "status"] })
61
+ declare filter?: Filter;
62
+ }
63
+ ```
64
+
65
+ > `declare` matters: without it the property declaration shadows the inherited value.
66
+
67
+ Use `makeInfiniteParamsDto` / `makeCursorParamsDto` (or `InfiniteParamsDto` / `CursorParamsDto`) for the other modes.
68
+
69
+ ## ⚠️ `ValidationPipe` settings
70
+
71
+ Both flags are **required**, otherwise the guarantees are only half in place:
72
+
73
+ | Flag | Why |
74
+ | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
75
+ | `whitelist: true` | class-validator does not strip like zod does. Without it, `columns` / `with` / `withDeleted` **survive** on the client object and reach the repository. This flag removes properties that carry no decorator — the equivalent of zod's automatic stripping. |
76
+ | `transform: true` | `page`/`perPage`/`limit`/`offset` arrive as strings from a query string; `@Type(() => Number)` only runs when transform is on. |
77
+
78
+ ## What it validates
79
+
80
+ - **Filter** — a flat array (`{key, operation, value}[]`) or a nested `and`/`or`/`not` tree.
81
+ - **Operators** — the full core set: `= != > >= < <=`, `like/ilike/notLike`, `contains/startsWith/endsWith` (+ tokens `%_%`/`%_`/`_%`), `in/notIn`, `between/notBetween`, `isNull/isNotNull`.
82
+ - **Sort** — `{ key, direction }[]` (multi-field).
83
+ - **Pagination** — offset (`page`/`perPage`), infinite (`limit`/`offset`), cursor (`limit`/`cursor`/`cursorKey`/`order`/`direction`).
84
+ - **Values** — a scalar or an array of scalars. Because the wire is JSON, a `Date` object, `NaN` and `Infinity` are **rejected** (dates arrive as ISO strings).
85
+ - **A field allowlist** (`allowedKeys`) and a **depth cap** (`maxDepth`) — neither exists in zod, see below.
86
+ - The legacy `type` field is **ignored** (not rejected) — easy migration.
87
+
88
+ ## Safe by default
89
+
90
+ | Layer | Default behaviour |
91
+ | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
92
+ | `perPage` / `limit` | Capped at core's `DEFAULT_MAX_PER_PAGE` / `DEFAULT_MAX_LIMIT` (200). `page` is uncapped — the DoS surface is page **size**, not page number. |
93
+ | `columns` / `with` / `withDeleted` | Declared on no DTO, so `whitelist: true` strips them. Only `allow` opens them. |
94
+ | `allowedKeys` | Omitted (`undefined`), any key passes — the allowlist is **opt-in** (zod parity). Provided, an unknown key is **rejected**; `[]` allows nothing, so an allowlist computed from an empty set fails closed rather than open. |
95
+ | `maxDepth` | `5`. An unbounded recursive tree is a DoS surface. |
96
+
97
+ If you do open a field with `allow`, add the **second layer at the repository**: `forcedColumns` / `allowedColumns` ([drizzle-pg](../drizzle-pg/README.en.md) · [mongoose](../mongoose/README.en.md)).
98
+
99
+ ## Options
100
+
101
+ `makeOffsetParamsDto` / `makeInfiniteParamsDto` / `makeCursorParamsDto`:
102
+
103
+ | Option | Default | Meaning |
104
+ | ------------- | --------------------------------- | ----------------------------------------------------------- |
105
+ | `allowedKeys` | — | Shared allowlist shorthand for `filter` and `sort` |
106
+ | `filter` | — | Full options for `filter` (below) — wins over the shorthand |
107
+ | `sort` | — | `allowedKeys` / `messageStyle` for `sort` |
108
+ | `maxPerPage` | core `DEFAULT_MAX_PER_PAGE` (200) | `perPage` ceiling. `Infinity` disables the cap (⚠️ DoS) |
109
+ | `maxLimit` | core `DEFAULT_MAX_LIMIT` (200) | `limit` ceiling |
110
+ | `allow` | `[]` | Deliberately open `"columns"` / `"with"` / `"withDeleted"` |
111
+
112
+ `@IsQueryFilter(options)` (and `options.filter`):
113
+
114
+ | Option | Default | Meaning |
115
+ | ------------------ | ------------------------ | ------------------------------------------------------------------------------------------ |
116
+ | `allowedKeys` | — | When set, an unknown filter key is rejected |
117
+ | `allowedOperators` | every `FILTER_OPERATORS` | Narrow the accepted operator set |
118
+ | `maxDepth` | `5` | `and`/`or`/`not` depth cap |
119
+ | `strictValue` | `false` | `in`/`notIn` → array, `between`/`notBetween` → two values, `isNull`/`isNotNull` → no value |
120
+ | `messageStyle` | `"text"` | `"key"` returns the i18n key instead of a sentence |
121
+ | `rootPath` | the property name | Root of the reported path (`filter[0].operation`) |
122
+
123
+ `@IsQuerySort(options)` — `allowedKeys`, `messageStyle`, `rootPath`.
124
+
125
+ ## i18n (more than one language)
126
+
127
+ The package identifies each message by **key**, so the number of languages is not limited. Every issue carries three things: `key` (translation key), `message` (ready-made text) and `args` (interpolation values).
128
+
129
+ With `messageStyle: "key"` the constraint returns the key instead of a sentence, and `nestjs-i18n` translates it into the request's language:
130
+
131
+ ```ts
132
+ const ListBuyersDto = makeOffsetParamsDto({
133
+ allowedKeys: ["buyerName"],
134
+ filter: { messageStyle: "key" },
135
+ sort: { messageStyle: "key" },
136
+ });
137
+
138
+ // main.ts
139
+ app.useGlobalPipes(new I18nValidationPipe({ transform: true, whitelist: true }));
140
+ ```
141
+
142
+ Three catalogs ship in the box, so you don't write a translation file from scratch:
143
+
144
+ ```ts
145
+ import { QUERYKIT_LOCALES, QUERYKIT_MESSAGES_UZ, formatMessage } from "@querykitjs/class-validator";
146
+
147
+ QUERYKIT_LOCALES; // { en, uz, ru }
148
+ formatMessage("querykit.filter.unknown_key", { path: "filter[0]", key: "zzz" }, QUERYKIT_MESSAGES_UZ);
149
+ // → "filter[0]: `zzz` ruxsat etilgan filter kaliti emas"
150
+ ```
151
+
152
+ To add your own language, satisfy the `QueryKitMessageCatalog` type — when a key is added, TypeScript forces your catalog to be updated:
153
+
154
+ ```ts
155
+ import type { QueryKitMessageCatalog } from "@querykitjs/class-validator";
156
+
157
+ const KK: QueryKitMessageCatalog = {
158
+ "querykit.filter.unknown_key": "{path}: `{key}` рұқсат етілген кілт емес",
159
+ // … every remaining key is required
160
+ };
161
+ ```
162
+
163
+ ## Without Nest — the pure core
164
+
165
+ `validateFilter` / `validateSort` are tied to no framework; they run under Express, Hono, or a plain function:
166
+
167
+ ```ts
168
+ import { validateFilter, validateSort } from "@querykitjs/class-validator";
169
+
170
+ const issues = [...validateFilter(body.filter, { allowedKeys, strictValue: true }), ...validateSort(body.sort, { allowedKeys })];
171
+
172
+ if (issues.length > 0) {
173
+ return res.status(400).json({ errors: issues }); // { path, key, message, args }
174
+ }
175
+ ```
176
+
177
+ ## Differences from `@querykitjs/zod`
178
+
179
+ | | `@querykitjs/zod` | `@querykitjs/class-validator` |
180
+ | ------------------------ | ----------------- | ----------------------------------------- |
181
+ | Contract, operators | from core | **the same** |
182
+ | Stripping unknown fields | automatic | via `ValidationPipe({ whitelist: true })` |
183
+ | Field allowlist | none | `allowedKeys` |
184
+ | Depth cap | none | `maxDepth` (default 5) |
185
+ | `NaN` / `Infinity` | `Infinity` passes | both rejected (neither exists in JSON) |
186
+ | i18n | — | `querykit.*` keys + en/uz/ru catalogs |
187
+
188
+ ## License
189
+
190
+ MIT © [Suhrobbek Soatov](https://soatov.uz)
package/README.md ADDED
@@ -0,0 +1,190 @@
1
+ <div align="right">
2
+
3
+ [English](./README.en.md) · **O'zbekcha**
4
+
5
+ </div>
6
+
7
+ # @querykitjs/class-validator
8
+
9
+ > querykit so'rov kontraktini (filter/sort/pagination) tekshiruvchi **class-validator** dekoratorlari va DTO'lari. Operatorlar `@querykitjs/core`dan; DTO chiqishi core tiplariga **mos**.
10
+
11
+ NestJS list endpoint'lariga kelayotgan JSON body'ni validatsiya qiladi — to'liq operatorlar to'plami, nested `and/or/not` filter, va **uchala paginatsiya** (offset/infinite/cursor). Validatsiyalangan payload'ni to'g'ridan-to'g'ri querykit repository'siga uzatasiz.
12
+
13
+ Bu — [`@querykitjs/zod`](../zod/README.md) bilan **bir xil rol**, faqat boshqa vosita: o'sha kontrakt, o'sha operator manbai, lekin zod schema'lari o'rniga class-validator dekoratorlari.
14
+
15
+ ## O'rnatish
16
+
17
+ ```bash
18
+ bun add @querykitjs/class-validator class-validator class-transformer
19
+ # @querykitjs/core avtomatik keladi (dependency)
20
+ ```
21
+
22
+ `reflect-metadata` ham kerak — Nest app'lari uni allaqachon `main.ts`da import qiladi. Nest'siz ishlatsangiz, entry faylining **eng boshida**:
23
+
24
+ ```ts
25
+ import "reflect-metadata";
26
+ ```
27
+
28
+ Usiz class-transformer'ning `@Type()` dekoratori `Reflect.getMetadata is not a function` xatosini beradi.
29
+
30
+ ## Foydalanish (NestJS)
31
+
32
+ `main.ts`:
33
+
34
+ ```ts
35
+ app.useGlobalPipes(new ValidationPipe({ transform: true, whitelist: true }));
36
+ ```
37
+
38
+ Controller — **factory** varianti (tavsiya etiladi):
39
+
40
+ ```ts
41
+ import { makeOffsetParamsDto } from "@querykitjs/class-validator";
42
+
43
+ const ListBuyersDto = makeOffsetParamsDto({ allowedKeys: ["buyerName", "createdAt", "status"] });
44
+ type ListBuyersDto = InstanceType<typeof ListBuyersDto>;
45
+
46
+ @Post("list")
47
+ list(@Body() params: ListBuyersDto) {
48
+ return this.buyersRepository.findList(params); // core-mos, `as` cast kerak emas
49
+ }
50
+ ```
51
+
52
+ Yoki **subclass** varianti — qo'shimcha maydonlar qo'shmoqchi bo'lsangiz:
53
+
54
+ ```ts
55
+ import { IsQueryFilter, OffsetParamsDto } from "@querykitjs/class-validator";
56
+ import type { Filter } from "@querykitjs/core";
57
+
58
+ class ListBuyersDto extends OffsetParamsDto {
59
+ @IsOptional()
60
+ @IsQueryFilter({ allowedKeys: ["buyerName", "createdAt", "status"] })
61
+ declare filter?: Filter;
62
+ }
63
+ ```
64
+
65
+ > `declare` muhim: usiz property deklaratsiyasi ota-klassdagi qiymatni soya qiladi.
66
+
67
+ Infinite/cursor uchun `makeInfiniteParamsDto` / `makeCursorParamsDto` (yoki `InfiniteParamsDto` / `CursorParamsDto`).
68
+
69
+ ## ⚠️ `ValidationPipe` sozlamalari
70
+
71
+ Ikkala flag ham **zarur**, aks holda kafolatlar yarim ishlaydi:
72
+
73
+ | Flag | Nega kerak |
74
+ | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
75
+ | `whitelist: true` | class-validator zod kabi avtomatik strip qilmaydi. Usiz `columns` / `with` / `withDeleted` client obyektida **qolib ketadi** va repository'ga tushadi. Bu flag dekoratorsiz propertylarni o'chiradi — zod'dagi avtomatik strip'ning ekvivalenti. |
76
+ | `transform: true` | `page`/`perPage`/`limit`/`offset` query string'dan kelganda string bo'ladi; `@Type(() => Number)` faqat transform yoqilganda ishlaydi. |
77
+
78
+ ## Nimani tekshiradi
79
+
80
+ - **Filter** — flat massiv (`{key, operation, value}[]`) yoki nested `and`/`or`/`not` daraxt.
81
+ - **Operatorlar** — core'ning to'liq to'plami: `= != > >= < <=`, `like/ilike/notLike`, `contains/startsWith/endsWith` (+ token `%_%`/`%_`/`_%`), `in/notIn`, `between/notBetween`, `isNull/isNotNull`.
82
+ - **Sort** — `{ key, direction }[]` (ko'p-maydon).
83
+ - **Paginatsiya** — offset (`page`/`perPage`), infinite (`limit`/`offset`), cursor (`limit`/`cursor`/`cursorKey`/`order`/`direction`).
84
+ - **Qiymat** — skalyar yoki skalyar massiv. Wire JSON bo'lgani uchun `Date` obyekti, `NaN` va `Infinity` **rad etiladi** (sana ISO string sifatida keladi).
85
+ - **Field allowlist** (`allowedKeys`) va **chuqurlik limiti** (`maxDepth`) — zod'da yo'q, quyiga qarang.
86
+ - Eski `type` maydoni **e'tiborsiz** qoldiriladi (rad etilmaydi) — migratsiya oson.
87
+
88
+ ## Xavfsiz default
89
+
90
+ | Qatlam | Default xulq |
91
+ | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
92
+ | `perPage` / `limit` | core `DEFAULT_MAX_PER_PAGE` / `DEFAULT_MAX_LIMIT` (200) bilan cheklangan. `page` cheklanmagan — DoS yuzasi sahifa **o'lchami**, raqami emas. |
93
+ | `columns` / `with` / `withDeleted` | Hech qaysi DTO'da e'lon qilinmagan → `whitelist: true` ularni o'chiradi. Ochish faqat `allow` bilan. |
94
+ | `allowedKeys` | Berilmasa (`undefined`) istalgan kalit o'tadi — allowlist **opt-in** (zod bilan parity). Berilsa, noma'lum kalit **rad etiladi**; `[]` esa hech biriga ruxsat bermaydi (hisoblab chiqarilgan bo'sh ro'yxat fail-open bo'lib qolmasin). |
95
+ | `maxDepth` | `5`. Cheklovsiz rekursiv daraxt DoS yuzasi. |
96
+
97
+ `allow` bilan ochsangiz, **repository darajasidagi ikkinchi qatlam**ni ham qo'ying: `forcedColumns` / `allowedColumns` ([drizzle-pg](../drizzle-pg/README.md) · [mongoose](../mongoose/README.md)).
98
+
99
+ ## Opsiyalar
100
+
101
+ `makeOffsetParamsDto` / `makeInfiniteParamsDto` / `makeCursorParamsDto`:
102
+
103
+ | Opsiya | Default | Ma'nosi |
104
+ | ------------- | --------------------------------- | ----------------------------------------------------------------------------------- |
105
+ | `allowedKeys` | — | `filter` va `sort` uchun umumiy allowlist qisqartmasi |
106
+ | `filter` | — | `filter` uchun to'liq opsiyalar (quyiga qarang) — `allowedKeys` shorthand'dan ustun |
107
+ | `sort` | — | `sort` uchun `allowedKeys` / `messageStyle` |
108
+ | `maxPerPage` | core `DEFAULT_MAX_PER_PAGE` (200) | `perPage` yuqori chegarasi. `Infinity` — cap yo'q (⚠️ DoS) |
109
+ | `maxLimit` | core `DEFAULT_MAX_LIMIT` (200) | `limit` yuqori chegarasi |
110
+ | `allow` | `[]` | `"columns"` / `"with"` / `"withDeleted"`ni ataylab ochish |
111
+
112
+ `@IsQueryFilter(options)` (va `options.filter`):
113
+
114
+ | Opsiya | Default | Ma'nosi |
115
+ | ------------------ | ------------------------- | ------------------------------------------------------------------------------------------ |
116
+ | `allowedKeys` | — | Berilsa, noma'lum filter kaliti rad etiladi |
117
+ | `allowedOperators` | barcha `FILTER_OPERATORS` | Ruxsat etilgan operatorlar to'plamini toraytirish |
118
+ | `maxDepth` | `5` | `and`/`or`/`not` chuqurlik limiti |
119
+ | `strictValue` | `false` | `in`/`notIn` → massiv, `between`/`notBetween` → 2 qiymat, `isNull`/`isNotNull` → qiymatsiz |
120
+ | `messageStyle` | `"text"` | `"key"` — xato o'rniga i18n kaliti qaytariladi |
121
+ | `rootPath` | property nomi | Xato yo'lining ildizi (`filter[0].operation`) |
122
+
123
+ `@IsQuerySort(options)` — `allowedKeys`, `messageStyle`, `rootPath`.
124
+
125
+ ## i18n (bir nechta til)
126
+
127
+ Paket xabarni **kalit** bilan belgilaydi, shuning uchun tillar soni cheklanmagan. Har bir xato uchta narsani beradi: `key` (tarjima kaliti), `message` (tayyor matn) va `args` (interpolatsiya qiymatlari).
128
+
129
+ `messageStyle: "key"` bilan xato matni o'rniga kalit qaytadi va `nestjs-i18n` uni so'rov tiliga tarjima qiladi:
130
+
131
+ ```ts
132
+ const ListBuyersDto = makeOffsetParamsDto({
133
+ allowedKeys: ["buyerName"],
134
+ filter: { messageStyle: "key" },
135
+ sort: { messageStyle: "key" },
136
+ });
137
+
138
+ // main.ts
139
+ app.useGlobalPipes(new I18nValidationPipe({ transform: true, whitelist: true }));
140
+ ```
141
+
142
+ Uchta katalog qutidan chiqadi — tarjima faylingizni noldan yozish shart emas:
143
+
144
+ ```ts
145
+ import { QUERYKIT_LOCALES, QUERYKIT_MESSAGES_UZ, formatMessage } from "@querykitjs/class-validator";
146
+
147
+ QUERYKIT_LOCALES; // { en, uz, ru }
148
+ formatMessage("querykit.filter.unknown_key", { path: "filter[0]", key: "zzz" }, QUERYKIT_MESSAGES_UZ);
149
+ // → "filter[0]: `zzz` ruxsat etilgan filter kaliti emas"
150
+ ```
151
+
152
+ O'z tilingizni qo'shish — `QueryKitMessageCatalog` tipiga amal qiling; kalit qo'shilsa TypeScript katalogingizni yangilashga majbur qiladi:
153
+
154
+ ```ts
155
+ import type { QueryKitMessageCatalog } from "@querykitjs/class-validator";
156
+
157
+ const KK: QueryKitMessageCatalog = {
158
+ "querykit.filter.unknown_key": "{path}: `{key}` рұқсат етілген кілт емес",
159
+ // … qolgan kalitlar majburiy
160
+ };
161
+ ```
162
+
163
+ ## Nest'siz — sof yadro
164
+
165
+ `validateFilter` / `validateSort` hech qanday framework'ga bog'lanmagan; Express, Hono yoki oddiy funksiyada ishlaydi:
166
+
167
+ ```ts
168
+ import { validateFilter, validateSort } from "@querykitjs/class-validator";
169
+
170
+ const issues = [...validateFilter(body.filter, { allowedKeys, strictValue: true }), ...validateSort(body.sort, { allowedKeys })];
171
+
172
+ if (issues.length > 0) {
173
+ return res.status(400).json({ errors: issues }); // { path, key, message, args }
174
+ }
175
+ ```
176
+
177
+ ## `@querykitjs/zod` bilan farqlar
178
+
179
+ | | `@querykitjs/zod` | `@querykitjs/class-validator` |
180
+ | ------------------------------ | ----------------- | ---------------------------------------------- |
181
+ | Kontrakt, operatorlar | core'dan | **aynan o'sha** |
182
+ | Noma'lum maydonni strip qilish | avtomatik | `ValidationPipe({ whitelist: true })` orqali |
183
+ | Field allowlist | yo'q | `allowedKeys` |
184
+ | Chuqurlik limiti | yo'q | `maxDepth` (default 5) |
185
+ | `NaN` / `Infinity` | `Infinity` o'tadi | ikkalasi ham rad etiladi (JSON'da mavjud emas) |
186
+ | i18n | — | `querykit.*` kalitlari + en/uz/ru kataloglar |
187
+
188
+ ## Litsenziya
189
+
190
+ MIT © [Suhrobbek Soatov](https://soatov.uz)