@querykitjs/class-validator 1.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.
- package/README.en.md +68 -15
- package/README.md +68 -15
- package/dist/chunk-AX53IY63.cjs +89 -0
- package/dist/chunk-AX53IY63.cjs.map +1 -0
- package/dist/chunk-HKLXL3NW.cjs +42 -0
- package/dist/chunk-HKLXL3NW.cjs.map +1 -0
- package/dist/chunk-PKGWV2JC.cjs +81 -0
- package/dist/chunk-PKGWV2JC.cjs.map +1 -0
- package/dist/chunk-QMFPUYJO.js +30 -0
- package/dist/chunk-QMFPUYJO.js.map +1 -0
- package/dist/chunk-V4E73HMQ.js +85 -0
- package/dist/chunk-V4E73HMQ.js.map +1 -0
- package/dist/chunk-XF6C4EG2.js +76 -0
- package/dist/chunk-XF6C4EG2.js.map +1 -0
- package/dist/decorators.cjs +33 -0
- package/dist/decorators.cjs.map +1 -0
- package/dist/decorators.js +4 -0
- package/dist/decorators.js.map +1 -0
- package/dist/index.cjs +92 -375
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +101 -73
- package/dist/index.d.ts +101 -73
- package/dist/index.js +30 -363
- package/dist/index.js.map +1 -1
- package/dist/legacy-dto.cjs +26 -0
- package/dist/legacy-dto.cjs.map +1 -0
- package/dist/legacy-dto.js +5 -0
- package/dist/legacy-dto.js.map +1 -0
- package/dist/validate.cjs +20 -0
- package/dist/validate.cjs.map +1 -0
- package/dist/validate.js +3 -0
- package/dist/validate.js.map +1 -0
- package/package.json +20 -11
- package/skills/querykit-class-validator/SKILL.md +124 -0
package/README.en.md
CHANGED
|
@@ -27,6 +27,17 @@ import "reflect-metadata";
|
|
|
27
27
|
|
|
28
28
|
Without it, class-transformer's `@Type()` throws `Reflect.getMetadata is not a function`.
|
|
29
29
|
|
|
30
|
+
### Requirements
|
|
31
|
+
|
|
32
|
+
| | Version | Tested (`bun run test:matrix`) |
|
|
33
|
+
| ---------------------------------- | ------------------- | ------------------------------ |
|
|
34
|
+
| Node.js | `>=22.12` | 24 |
|
|
35
|
+
| `class-validator` (peer) | `>=0.14 <0.16` | 0.14.4, 0.15.1 |
|
|
36
|
+
| `class-transformer` (peer) | `^0.5` | 0.5.1 |
|
|
37
|
+
| `reflect-metadata` (optional peer) | `^0.1.13 \|\| ^0.2` | 0.1.14, 0.2.2 |
|
|
38
|
+
|
|
39
|
+
Node 20 is no longer supported (`engines`: `>=22.12`). Peer ranges cover the last two majors; both ends are tested.
|
|
40
|
+
|
|
30
41
|
## Usage (NestJS)
|
|
31
42
|
|
|
32
43
|
`main.ts`:
|
|
@@ -79,17 +90,18 @@ Both flags are **required**, otherwise the guarantees are only half in place:
|
|
|
79
90
|
|
|
80
91
|
- **Filter** — a flat array (`{key, operation, value}[]`) or a nested `and`/`or`/`not` tree.
|
|
81
92
|
- **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
|
-
- **
|
|
93
|
+
- **Sort** — `{ key, direction }[]` (multi-field, at most 3).
|
|
94
|
+
- **Request limits** — filter size, sort fields, offset depth ([Limits](#limits)).
|
|
95
|
+
- **Pagination** — offset (`page`/`perPage`/`withTotal` — `false` ⇒ no count, totals `null`), infinite (`limit`/`offset`), cursor (`limit`/`cursor`/`cursorKey`/`order`/`direction`).
|
|
84
96
|
- **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`)
|
|
97
|
+
- **A field allowlist** (`allowedKeys`) — zod has none, see below.
|
|
86
98
|
- The legacy `type` field is **ignored** (not rejected) — easy migration.
|
|
87
99
|
|
|
88
100
|
## Safe by default
|
|
89
101
|
|
|
90
102
|
| Layer | Default behaviour |
|
|
91
103
|
| ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
92
|
-
| `perPage` / `limit` | Capped at core's `DEFAULT_MAX_PER_PAGE` / `DEFAULT_MAX_LIMIT` (200). `page` is uncapped
|
|
104
|
+
| `perPage` / `limit` | Capped at core's `DEFAULT_MAX_PER_PAGE` / `DEFAULT_MAX_LIMIT` (200). `page` itself is uncapped, but `(page - 1) × perPage` ≤ `maxOffset` (10 000). |
|
|
93
105
|
| `columns` / `with` / `withDeleted` | Declared on no DTO, so `whitelist: true` strips them. Only `allow` opens them. |
|
|
94
106
|
| `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
107
|
| `maxDepth` | `5`. An unbounded recursive tree is a DoS surface. |
|
|
@@ -98,16 +110,19 @@ If you do open a field with `allow`, add the **second layer at the repository**:
|
|
|
98
110
|
|
|
99
111
|
## Options
|
|
100
112
|
|
|
113
|
+
> ⚠️ The constant DTOs (`BaseParamsDto`, `OffsetParamsDto`, `InfiniteParamsDto`, `CursorParamsDto`) are `@deprecated` (task 014) and go away in the next major. Use `make*ParamsDto` in new code.
|
|
114
|
+
|
|
101
115
|
`makeOffsetParamsDto` / `makeInfiniteParamsDto` / `makeCursorParamsDto`:
|
|
102
116
|
|
|
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"`
|
|
117
|
+
| Option | Default | Meaning |
|
|
118
|
+
| ------------- | --------------------------------- | --------------------------------------------------------------------------------- |
|
|
119
|
+
| `allowedKeys` | — | Shared allowlist shorthand for `filter` and `sort` |
|
|
120
|
+
| `filter` | — | Full options for `filter` (below) — wins over the shorthand |
|
|
121
|
+
| `sort` | — | `allowedKeys` / `messageStyle` for `sort` |
|
|
122
|
+
| `maxPerPage` | core `DEFAULT_MAX_PER_PAGE` (200) | `perPage` ceiling. `Infinity` disables the cap (⚠️ DoS) |
|
|
123
|
+
| `maxLimit` | core `DEFAULT_MAX_LIMIT` (200) | `limit` ceiling |
|
|
124
|
+
| `allow` | `[]` | Deliberately open `"columns"` / `"with"` / `"withDeleted"` |
|
|
125
|
+
| `limits` | core `DEFAULT_QUERY_LIMITS` | request limits ([Limits](#limits)); a dedicated `filter.*` / `sort.*` option wins |
|
|
111
126
|
|
|
112
127
|
`@IsQueryFilter(options)` (and `options.filter`):
|
|
113
128
|
|
|
@@ -115,12 +130,50 @@ If you do open a field with `allow`, add the **second layer at the repository**:
|
|
|
115
130
|
| ------------------ | ------------------------ | ------------------------------------------------------------------------------------------ |
|
|
116
131
|
| `allowedKeys` | — | When set, an unknown filter key is rejected |
|
|
117
132
|
| `allowedOperators` | every `FILTER_OPERATORS` | Narrow the accepted operator set |
|
|
118
|
-
| `maxDepth` | `5` |
|
|
133
|
+
| `maxDepth` | `5` | root-to-leaf node count |
|
|
134
|
+
| `maxConditions` | `30` | condition count |
|
|
135
|
+
| `maxInItems` | `200` | items in an array value |
|
|
136
|
+
| `maxStringLength` | `256` | length of a string value |
|
|
119
137
|
| `strictValue` | `false` | `in`/`notIn` → array, `between`/`notBetween` → two values, `isNull`/`isNotNull` → no value |
|
|
120
138
|
| `messageStyle` | `"text"` | `"key"` returns the i18n key instead of a sentence |
|
|
121
139
|
| `rootPath` | the property name | Root of the reported path (`filter[0].operation`) |
|
|
122
140
|
|
|
123
|
-
`@IsQuerySort(options)` — `allowedKeys`, `messageStyle`, `rootPath`.
|
|
141
|
+
`@IsQuerySort(options)` — `allowedKeys`, `maxSortFields` (`3`), `messageStyle`, `rootPath`.
|
|
142
|
+
|
|
143
|
+
`@IsWithinMaxOffset({ mode, maxOffset? })` — `mode: "page"` (`(page - 1) × perPage`) or `"offset"`; `maxOffset` defaults to `10 000`.
|
|
144
|
+
|
|
145
|
+
## Limits
|
|
146
|
+
|
|
147
|
+
Every DTO and `validateFilter` / `validateSort` apply the core request limits — the same values as `@querykitjs/zod` (`DEFAULT_QUERY_LIMITS`).
|
|
148
|
+
|
|
149
|
+
| Limit | Default | What counts | Where |
|
|
150
|
+
| ----------------- | -------- | --------------------------------------------------------------------------------- | ---------------------------------------------------- |
|
|
151
|
+
| `maxDepth` | `5` | root-to-leaf nodes: the root is 1, every `and`/`or`/`not` and the condition add 1 | `@IsQueryFilter` / `validateFilter` |
|
|
152
|
+
| `maxConditions` | `30` | conditions (leaves) across all groups | `@IsQueryFilter` / `validateFilter` |
|
|
153
|
+
| `maxInItems` | `200` | items in one array value | `@IsQueryFilter` / `validateFilter` |
|
|
154
|
+
| `maxStringLength` | `256` | characters in one string value, also inside an array | `@IsQueryFilter` / `validateFilter` |
|
|
155
|
+
| `maxSortFields` | `3` | fields in the `sort` array | `@IsQuerySort` / `validateSort` |
|
|
156
|
+
| `maxOffset` | `10 000` | `(page - 1) × perPage` (20 without `perPage`); infinite `offset` | `@IsWithinMaxOffset` (`page` / `offset` in the DTOs) |
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
// raise a limit for one endpoint — the others keep their defaults
|
|
160
|
+
const ImportDto = makeOffsetParamsDto({ limits: { maxInItems: 500 } });
|
|
161
|
+
|
|
162
|
+
// a dedicated option wins over `limits`
|
|
163
|
+
const DeepDto = makeOffsetParamsDto({ limits: { maxDepth: 6 }, filter: { maxDepth: 8 } }); // 8
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
- Filter size is checked iteratively, **before** the shape check: a 10 000-level `not` chain does not fill the stack.
|
|
167
|
+
- The error text names the limit and its value (`filter[0].value exceeds maxInItems (200)`). Keys: `querykit.filter.max_depth` for depth, `querykit.limit.exceeded` for the other five (args `limit`, `max`) — in the en/uz/ru catalogs.
|
|
168
|
+
- Pages deeper than `maxOffset` use cursor pagination (`makeCursorParamsDto`).
|
|
169
|
+
- `Infinity` turns a limit off — ⚠️ a DoS surface.
|
|
170
|
+
|
|
171
|
+
## Migrating (DTO limits)
|
|
172
|
+
|
|
173
|
+
- A payload past a limit is now rejected — by the static DTOs and by the factories: an `in` with 201 items, a 257-char string, 31 conditions, 4 sort fields, `page=502, perPage=20` or `offset=10001` now return 400. Raise a limit with `limits` where you need it.
|
|
174
|
+
- `page` now carries `@IsWithinMaxOffset({ mode: "page" })`; if your DTO redeclares `page`, add that decorator too.
|
|
175
|
+
- The `querykit.filter.max_depth` text changed (it now says `maxDepth (5)`); rely on the key, not the text.
|
|
176
|
+
- `DEFAULT_MAX_FILTER_DEPTH` is now re-exported from `@querykitjs/core` (same value, 5).
|
|
124
177
|
|
|
125
178
|
## i18n (more than one language)
|
|
126
179
|
|
|
@@ -181,7 +234,7 @@ if (issues.length > 0) {
|
|
|
181
234
|
| Contract, operators | from core | **the same** |
|
|
182
235
|
| Stripping unknown fields | automatic | via `ValidationPipe({ whitelist: true })` |
|
|
183
236
|
| Field allowlist | none | `allowedKeys` |
|
|
184
|
-
|
|
|
237
|
+
| Request limits | the same (core) | the same (core) — `limits` |
|
|
185
238
|
| `NaN` / `Infinity` | `Infinity` passes | both rejected (neither exists in JSON) |
|
|
186
239
|
| i18n | — | `querykit.*` keys + en/uz/ru catalogs |
|
|
187
240
|
|
package/README.md
CHANGED
|
@@ -27,6 +27,17 @@ import "reflect-metadata";
|
|
|
27
27
|
|
|
28
28
|
Usiz class-transformer'ning `@Type()` dekoratori `Reflect.getMetadata is not a function` xatosini beradi.
|
|
29
29
|
|
|
30
|
+
### Talablar
|
|
31
|
+
|
|
32
|
+
| | Versiya | Tekshiriladi (`bun run test:matrix`) |
|
|
33
|
+
| ----------------------------------- | ------------------- | ------------------------------------ |
|
|
34
|
+
| Node.js | `>=22.12` | 24 |
|
|
35
|
+
| `class-validator` (peer) | `>=0.14 <0.16` | 0.14.4, 0.15.1 |
|
|
36
|
+
| `class-transformer` (peer) | `^0.5` | 0.5.1 |
|
|
37
|
+
| `reflect-metadata` (ixtiyoriy peer) | `^0.1.13 \|\| ^0.2` | 0.1.14, 0.2.2 |
|
|
38
|
+
|
|
39
|
+
Node 20 endi qo'llab-quvvatlanmaydi (`engines`: `>=22.12`). Peer diapazoni — oxirgi ikki major; ikkala cheti testda.
|
|
40
|
+
|
|
30
41
|
## Foydalanish (NestJS)
|
|
31
42
|
|
|
32
43
|
`main.ts`:
|
|
@@ -79,17 +90,18 @@ Ikkala flag ham **zarur**, aks holda kafolatlar yarim ishlaydi:
|
|
|
79
90
|
|
|
80
91
|
- **Filter** — flat massiv (`{key, operation, value}[]`) yoki nested `and`/`or`/`not` daraxt.
|
|
81
92
|
- **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
|
-
- **
|
|
93
|
+
- **Sort** — `{ key, direction }[]` (ko'p-maydon, ko'pi bilan 3).
|
|
94
|
+
- **So'rov limitlari** — filtr hajmi, sort soni, offset chuqurligi ([Limits](#limits)).
|
|
95
|
+
- **Paginatsiya** — offset (`page`/`perPage`/`withTotal` — `false` ⇒ count yo'q, totals `null`), infinite (`limit`/`offset`), cursor (`limit`/`cursor`/`cursorKey`/`order`/`direction`).
|
|
84
96
|
- **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`)
|
|
97
|
+
- **Field allowlist** (`allowedKeys`) — zod'da yo'q, quyiga qarang.
|
|
86
98
|
- Eski `type` maydoni **e'tiborsiz** qoldiriladi (rad etilmaydi) — migratsiya oson.
|
|
87
99
|
|
|
88
100
|
## Xavfsiz default
|
|
89
101
|
|
|
90
102
|
| Qatlam | Default xulq |
|
|
91
103
|
| ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
92
|
-
| `perPage` / `limit` | core `DEFAULT_MAX_PER_PAGE` / `DEFAULT_MAX_LIMIT` (200) bilan cheklangan. `page` cheklanmagan
|
|
104
|
+
| `perPage` / `limit` | core `DEFAULT_MAX_PER_PAGE` / `DEFAULT_MAX_LIMIT` (200) bilan cheklangan. `page` o'zi cheklanmagan, lekin `(page - 1) × perPage` ≤ `maxOffset` (10 000). |
|
|
93
105
|
| `columns` / `with` / `withDeleted` | Hech qaysi DTO'da e'lon qilinmagan → `whitelist: true` ularni o'chiradi. Ochish faqat `allow` bilan. |
|
|
94
106
|
| `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
107
|
| `maxDepth` | `5`. Cheklovsiz rekursiv daraxt DoS yuzasi. |
|
|
@@ -98,16 +110,19 @@ Ikkala flag ham **zarur**, aks holda kafolatlar yarim ishlaydi:
|
|
|
98
110
|
|
|
99
111
|
## Opsiyalar
|
|
100
112
|
|
|
113
|
+
> ⚠️ Konstanta DTO'lar (`BaseParamsDto`, `OffsetParamsDto`, `InfiniteParamsDto`, `CursorParamsDto`) `@deprecated` (task 014): keyingi major'da olib tashlanadi. Yangi kod uchun `make*ParamsDto`.
|
|
114
|
+
|
|
101
115
|
`makeOffsetParamsDto` / `makeInfiniteParamsDto` / `makeCursorParamsDto`:
|
|
102
116
|
|
|
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
|
|
117
|
+
| Opsiya | Default | Ma'nosi |
|
|
118
|
+
| ------------- | --------------------------------- | ------------------------------------------------------------------------------------- |
|
|
119
|
+
| `allowedKeys` | — | `filter` va `sort` uchun umumiy allowlist qisqartmasi |
|
|
120
|
+
| `filter` | — | `filter` uchun to'liq opsiyalar (quyiga qarang) — `allowedKeys` shorthand'dan ustun |
|
|
121
|
+
| `sort` | — | `sort` uchun `allowedKeys` / `messageStyle` |
|
|
122
|
+
| `maxPerPage` | core `DEFAULT_MAX_PER_PAGE` (200) | `perPage` yuqori chegarasi. `Infinity` — cap yo'q (⚠️ DoS) |
|
|
123
|
+
| `maxLimit` | core `DEFAULT_MAX_LIMIT` (200) | `limit` yuqori chegarasi |
|
|
124
|
+
| `allow` | `[]` | `"columns"` / `"with"` / `"withDeleted"`ni ataylab ochish |
|
|
125
|
+
| `limits` | core `DEFAULT_QUERY_LIMITS` | so'rov limitlari ([Limits](#limits)); `filter.*` / `sort.*` dagi alohida opsiya ustun |
|
|
111
126
|
|
|
112
127
|
`@IsQueryFilter(options)` (va `options.filter`):
|
|
113
128
|
|
|
@@ -115,12 +130,50 @@ Ikkala flag ham **zarur**, aks holda kafolatlar yarim ishlaydi:
|
|
|
115
130
|
| ------------------ | ------------------------- | ------------------------------------------------------------------------------------------ |
|
|
116
131
|
| `allowedKeys` | — | Berilsa, noma'lum filter kaliti rad etiladi |
|
|
117
132
|
| `allowedOperators` | barcha `FILTER_OPERATORS` | Ruxsat etilgan operatorlar to'plamini toraytirish |
|
|
118
|
-
| `maxDepth` | `5` |
|
|
133
|
+
| `maxDepth` | `5` | ildizdan bargacha tugunlar soni |
|
|
134
|
+
| `maxConditions` | `30` | shartlar soni |
|
|
135
|
+
| `maxInItems` | `200` | massiv qiymat elementlari |
|
|
136
|
+
| `maxStringLength` | `256` | satr qiymat uzunligi |
|
|
119
137
|
| `strictValue` | `false` | `in`/`notIn` → massiv, `between`/`notBetween` → 2 qiymat, `isNull`/`isNotNull` → qiymatsiz |
|
|
120
138
|
| `messageStyle` | `"text"` | `"key"` — xato o'rniga i18n kaliti qaytariladi |
|
|
121
139
|
| `rootPath` | property nomi | Xato yo'lining ildizi (`filter[0].operation`) |
|
|
122
140
|
|
|
123
|
-
`@IsQuerySort(options)` — `allowedKeys`, `messageStyle`, `rootPath`.
|
|
141
|
+
`@IsQuerySort(options)` — `allowedKeys`, `maxSortFields` (`3`), `messageStyle`, `rootPath`.
|
|
142
|
+
|
|
143
|
+
`@IsWithinMaxOffset({ mode, maxOffset? })` — `mode: "page"` (`(page - 1) × perPage`) yoki `"offset"`; `maxOffset` default `10 000`.
|
|
144
|
+
|
|
145
|
+
## Limits
|
|
146
|
+
|
|
147
|
+
Har DTO va `validateFilter` / `validateSort` core so'rov limitlarini qo'llaydi — `@querykitjs/zod` bilan bir xil qiymatlar (`DEFAULT_QUERY_LIMITS`).
|
|
148
|
+
|
|
149
|
+
| Limit | Default | Nima sanaladi | Qayerda |
|
|
150
|
+
| ----------------- | -------- | --------------------------------------------------------------------- | -------------------------------------------------- |
|
|
151
|
+
| `maxDepth` | `5` | ildizdan bargacha tugunlar: ildiz 1, har `and`/`or`/`not` va shart +1 | `@IsQueryFilter` / `validateFilter` |
|
|
152
|
+
| `maxConditions` | `30` | barcha guruhlardagi shartlar (barglar) | `@IsQueryFilter` / `validateFilter` |
|
|
153
|
+
| `maxInItems` | `200` | bitta massiv qiymatdagi elementlar | `@IsQueryFilter` / `validateFilter` |
|
|
154
|
+
| `maxStringLength` | `256` | bitta satr qiymatdagi belgilar (massiv ichida ham) | `@IsQueryFilter` / `validateFilter` |
|
|
155
|
+
| `maxSortFields` | `3` | `sort` massividagi maydonlar | `@IsQuerySort` / `validateSort` |
|
|
156
|
+
| `maxOffset` | `10 000` | `(page - 1) × perPage` (`perPage` yo'q bo'lsa 20); infinite `offset` | `@IsWithinMaxOffset` (DTO'larda `page` / `offset`) |
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
// bitta endpoint uchun limitni oshirish — qolganlari default
|
|
160
|
+
const ImportDto = makeOffsetParamsDto({ limits: { maxInItems: 500 } });
|
|
161
|
+
|
|
162
|
+
// alohida opsiya `limits`dan ustun
|
|
163
|
+
const DeepDto = makeOffsetParamsDto({ limits: { maxDepth: 6 }, filter: { maxDepth: 8 } }); // 8
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
- Filtr hajmi iterativ, shakl tekshiruvidan **oldin** tekshiriladi: 10 000 darajali `not` zanjiri stekni to'ldirmaydi.
|
|
167
|
+
- Xato matni limit nomi va qiymatini aytadi (`filter[0].value exceeds maxInItems (200)`). Kalitlar: chuqurlik uchun `querykit.filter.max_depth`, qolgan beshtasi uchun `querykit.limit.exceeded` (args `limit`, `max`) — en/uz/ru kataloglarda.
|
|
168
|
+
- `maxOffset`dan chuqur sahifalar — cursor paginatsiya (`makeCursorParamsDto`).
|
|
169
|
+
- `Infinity` limitni o'chiradi — ⚠️ DoS yuzasi.
|
|
170
|
+
|
|
171
|
+
## Migrating (DTO limitlari)
|
|
172
|
+
|
|
173
|
+
- Limitdan oshgan payload endi rad etiladi — static DTO'larda ham, factory'larda ham: `in` 201 element, 257 belgili satr, 31 shart, 4 sort maydoni, `page=502, perPage=20` yoki `offset=10001` endi 400 beradi. Kerak bo'lsa `limits` bilan oshiring.
|
|
174
|
+
- `page` endi `@IsWithinMaxOffset({ mode: "page" })` bilan; o'z DTO'ngizda `page`ni qayta e'lon qilsangiz, shu dekoratorni ham qo'ying.
|
|
175
|
+
- `querykit.filter.max_depth` matni o'zgardi (endi `maxDepth (5)` deydi); matnga emas, kalitga tayaning.
|
|
176
|
+
- `DEFAULT_MAX_FILTER_DEPTH` endi `@querykitjs/core`dan qayta eksport (qiymat o'sha — 5).
|
|
124
177
|
|
|
125
178
|
## i18n (bir nechta til)
|
|
126
179
|
|
|
@@ -181,7 +234,7 @@ if (issues.length > 0) {
|
|
|
181
234
|
| Kontrakt, operatorlar | core'dan | **aynan o'sha** |
|
|
182
235
|
| Noma'lum maydonni strip qilish | avtomatik | `ValidationPipe({ whitelist: true })` orqali |
|
|
183
236
|
| Field allowlist | yo'q | `allowedKeys` |
|
|
184
|
-
|
|
|
237
|
+
| So'rov limitlari | bir xil (core) | bir xil (core) — `limits` |
|
|
185
238
|
| `NaN` / `Infinity` | `Infinity` o'tadi | ikkalasi ham rad etiladi (JSON'da mavjud emas) |
|
|
186
239
|
| i18n | — | `querykit.*` kalitlari + en/uz/ru kataloglar |
|
|
187
240
|
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunkHKLXL3NW_cjs = require('./chunk-HKLXL3NW.cjs');
|
|
4
|
+
var classValidator = require('class-validator');
|
|
5
|
+
var core = require('@querykitjs/core');
|
|
6
|
+
var internal = require('@querykitjs/core/internal');
|
|
7
|
+
|
|
8
|
+
var optionsOf = (args, fallbackPath) => {
|
|
9
|
+
let given = args?.constraints?.[0] ?? {};
|
|
10
|
+
return { rootPath: args?.property ?? fallbackPath, ...given };
|
|
11
|
+
}, firstMessage = (issues, options, args, fallback) => {
|
|
12
|
+
let first = issues[0];
|
|
13
|
+
return first ? options.messageStyle === "key" ? first.key : first.message : `${args?.property ?? "value"} ${fallback}`;
|
|
14
|
+
}; exports.IsQueryFilterConstraint = class IsQueryFilterConstraint {
|
|
15
|
+
validate(value, args) {
|
|
16
|
+
return chunkHKLXL3NW_cjs.validateFilter(value, optionsOf(args, "filter")).length === 0;
|
|
17
|
+
}
|
|
18
|
+
defaultMessage(args) {
|
|
19
|
+
let options = optionsOf(args, "filter");
|
|
20
|
+
return firstMessage(chunkHKLXL3NW_cjs.validateFilter(args?.value, options), options, args, "is not a valid querykit filter");
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
exports.IsQueryFilterConstraint = chunkHKLXL3NW_cjs.__decorateClass([
|
|
24
|
+
classValidator.ValidatorConstraint({ name: "isQueryFilter", async: false })
|
|
25
|
+
], exports.IsQueryFilterConstraint);
|
|
26
|
+
exports.IsQuerySortConstraint = class IsQuerySortConstraint {
|
|
27
|
+
validate(value, args) {
|
|
28
|
+
return chunkHKLXL3NW_cjs.validateSort(value, optionsOf(args, "sort")).length === 0;
|
|
29
|
+
}
|
|
30
|
+
defaultMessage(args) {
|
|
31
|
+
let options = optionsOf(args, "sort");
|
|
32
|
+
return firstMessage(chunkHKLXL3NW_cjs.validateSort(args?.value, options), options, args, "is not a valid querykit sort");
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
exports.IsQuerySortConstraint = chunkHKLXL3NW_cjs.__decorateClass([
|
|
36
|
+
classValidator.ValidatorConstraint({ name: "isQuerySort", async: false })
|
|
37
|
+
], exports.IsQuerySortConstraint);
|
|
38
|
+
var maxOffsetOf = (args) => args?.constraints?.[0]?.maxOffset ?? core.DEFAULT_MAX_OFFSET; exports.IsWithinMaxOffsetConstraint = class IsWithinMaxOffsetConstraint {
|
|
39
|
+
validate(value, args) {
|
|
40
|
+
if (typeof value != "number") return true;
|
|
41
|
+
let perPage = args?.object?.perPage;
|
|
42
|
+
return (args?.constraints?.[0]?.mode === "page" ? internal.offsetOf(value, typeof perPage == "number" ? perPage : void 0) : value) <= maxOffsetOf(args);
|
|
43
|
+
}
|
|
44
|
+
defaultMessage(args) {
|
|
45
|
+
return chunkHKLXL3NW_cjs.formatMessage("querykit.limit.exceeded", { path: args?.property ?? "offset", limit: "maxOffset", max: maxOffsetOf(args) });
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
exports.IsWithinMaxOffsetConstraint = chunkHKLXL3NW_cjs.__decorateClass([
|
|
49
|
+
classValidator.ValidatorConstraint({ name: "isWithinMaxOffset", async: false })
|
|
50
|
+
], exports.IsWithinMaxOffsetConstraint);
|
|
51
|
+
function IsWithinMaxOffset(options, validationOptions) {
|
|
52
|
+
return (target, propertyName) => {
|
|
53
|
+
classValidator.registerDecorator({
|
|
54
|
+
target: target.constructor,
|
|
55
|
+
propertyName,
|
|
56
|
+
options: validationOptions,
|
|
57
|
+
constraints: [options],
|
|
58
|
+
validator: exports.IsWithinMaxOffsetConstraint
|
|
59
|
+
});
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function IsQueryFilter(options, validationOptions) {
|
|
63
|
+
return (target, propertyName) => {
|
|
64
|
+
classValidator.registerDecorator({
|
|
65
|
+
target: target.constructor,
|
|
66
|
+
propertyName,
|
|
67
|
+
options: validationOptions,
|
|
68
|
+
constraints: [options],
|
|
69
|
+
validator: exports.IsQueryFilterConstraint
|
|
70
|
+
});
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function IsQuerySort(options, validationOptions) {
|
|
74
|
+
return (target, propertyName) => {
|
|
75
|
+
classValidator.registerDecorator({
|
|
76
|
+
target: target.constructor,
|
|
77
|
+
propertyName,
|
|
78
|
+
options: validationOptions,
|
|
79
|
+
constraints: [options],
|
|
80
|
+
validator: exports.IsQuerySortConstraint
|
|
81
|
+
});
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
exports.IsQueryFilter = IsQueryFilter;
|
|
86
|
+
exports.IsQuerySort = IsQuerySort;
|
|
87
|
+
exports.IsWithinMaxOffset = IsWithinMaxOffset;
|
|
88
|
+
//# sourceMappingURL=chunk-AX53IY63.cjs.map
|
|
89
|
+
//# sourceMappingURL=chunk-AX53IY63.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/decorators.ts"],"names":["IsQueryFilterConstraint","validateFilter","__decorateClass","ValidatorConstraint","IsQuerySortConstraint","validateSort","DEFAULT_MAX_OFFSET","IsWithinMaxOffsetConstraint","offsetOf","formatMessage","registerDecorator"],"mappings":"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","file":"chunk-AX53IY63.cjs","sourcesContent":["/**\n * `class-validator` o'rovi. Butun mantiq `validate.ts`da — bu yerda faqat\n * konstraint klasslari va ularni property'ga bog'laydigan dekorator factory'lari.\n *\n * Rekursiya `validateFilter` ichida bo'lgani uchun nested DTO klassi ham,\n * `@ValidateNested` ham kerak emas: butun `and`/`or`/`not` daraxti bitta\n * dekorator bilan tekshiriladi.\n */\nimport { registerDecorator, ValidatorConstraint } from \"class-validator\";\nimport type { ValidationArguments, ValidationOptions, ValidatorConstraintInterface } from \"class-validator\";\nimport { DEFAULT_MAX_OFFSET } from \"@querykitjs/core\";\nimport { offsetOf } from \"@querykitjs/core/internal\";\nimport { formatMessage } from \"./messages\";\nimport { validateFilter, validateSort, type QueryFilterOptions, type QuerySortOptions, type ValidationIssue } from \"./validate\";\n\n/** Dekorator darajasidagi qo'shimcha — yadro buni o'qimaydi. */\nexport interface MessageStyleOptions {\n /**\n * Xato matni qanday shaklda qaytsin:\n * - `\"text\"` (default) — tayyor inglizcha jumla, i18n'siz app ham o'qiydi;\n * - `\"key\"` — `querykit.filter.*` kaliti, `nestjs-i18n`ning `I18nValidationPipe`i\n * uni so'rovning tiliga tarjima qiladi.\n */\n messageStyle?: \"text\" | \"key\";\n}\n\nexport type IsQueryFilterOptions = QueryFilterOptions & MessageStyleOptions;\nexport type IsQuerySortOptions = QuerySortOptions & MessageStyleOptions;\n\n/* -------------------------------- helpers --------------------------------- */\n\nconst optionsOf = <TOptions extends MessageStyleOptions>(args: ValidationArguments | undefined, fallbackPath: string): TOptions => {\n const given = (args?.constraints?.[0] ?? {}) as TOptions;\n /* Xato yo'li DTO property nomidan boshlansin: `where` deb nomlangan maydon\n * `filter.operation` emas, `where.operation` deb chiqadi. */\n return { rootPath: args?.property ?? fallbackPath, ...given };\n};\n\nconst firstMessage = (issues: ValidationIssue[], options: MessageStyleOptions, args: ValidationArguments | undefined, fallback: string): string => {\n const first = issues[0];\n if (!first) return `${args?.property ?? \"value\"} ${fallback}`;\n return options.messageStyle === \"key\" ? first.key : first.message;\n};\n\n/* ------------------------------- constraints ------------------------------ */\n/* DIQQAT: class-validator konstraint instansiyasini bir marta yaratib, barcha\n * so'rovlar uchun qayta ishlatadi. Shu sababli issue'lar `this`da HECH QACHON\n * keshlanmaydi — parallel so'rovlarda xabarlar aralashib ketardi. `defaultMessage`\n * `validateFilter`ni qayta chaqiradi: u sof, holatsiz va arzon. */\n\n/** `@IsQueryFilter()` ortidagi konstraint — test va qayta ishlatish uchun ochiq. */\n@ValidatorConstraint({ name: \"isQueryFilter\", async: false })\nexport class IsQueryFilterConstraint implements ValidatorConstraintInterface {\n validate(value: unknown, args?: ValidationArguments): boolean {\n return validateFilter(value, optionsOf<IsQueryFilterOptions>(args, \"filter\")).length === 0;\n }\n\n defaultMessage(args?: ValidationArguments): string {\n const options = optionsOf<IsQueryFilterOptions>(args, \"filter\");\n return firstMessage(validateFilter(args?.value, options), options, args, \"is not a valid querykit filter\");\n }\n}\n\n/** `@IsQuerySort()` ortidagi konstraint. */\n@ValidatorConstraint({ name: \"isQuerySort\", async: false })\nexport class IsQuerySortConstraint implements ValidatorConstraintInterface {\n validate(value: unknown, args?: ValidationArguments): boolean {\n return validateSort(value, optionsOf<IsQuerySortOptions>(args, \"sort\")).length === 0;\n }\n\n defaultMessage(args?: ValidationArguments): string {\n const options = optionsOf<IsQuerySortOptions>(args, \"sort\");\n return firstMessage(validateSort(args?.value, options), options, args, \"is not a valid querykit sort\");\n }\n}\n\n/** `@IsWithinMaxOffset()` opsiyalari. */\nexport interface IsWithinMaxOffsetOptions {\n /**\n * `\"page\"` — property `page`, offset `(page - 1) × perPage` (o'sha obyektning `perPage`i,\n * yo'q bo'lsa core `DEFAULT_PER_PAGE`); `\"offset\"` — property qiymatining o'zi (infinite).\n */\n mode: \"page\" | \"offset\";\n /** Default — core `DEFAULT_MAX_OFFSET` (10 000). `Infinity` tekshiruvni o'chiradi. */\n maxOffset?: number;\n}\n\nconst maxOffsetOf = (args: ValidationArguments | undefined): number =>\n (args?.constraints?.[0] as IsWithinMaxOffsetOptions | undefined)?.maxOffset ?? DEFAULT_MAX_OFFSET;\n\n/** `@IsWithinMaxOffset()` ortidagi konstraint. Raqam bo'lmagan qiymatni `@IsInt()` rad etadi, bu yerda o'tadi. */\n@ValidatorConstraint({ name: \"isWithinMaxOffset\", async: false })\nexport class IsWithinMaxOffsetConstraint implements ValidatorConstraintInterface {\n validate(value: unknown, args?: ValidationArguments): boolean {\n if (typeof value !== \"number\") return true;\n const perPage = (args?.object as { perPage?: unknown } | undefined)?.perPage;\n const isPage = (args?.constraints?.[0] as IsWithinMaxOffsetOptions | undefined)?.mode === \"page\";\n return (isPage ? offsetOf(value, typeof perPage === \"number\" ? perPage : undefined) : value) <= maxOffsetOf(args);\n }\n\n defaultMessage(args?: ValidationArguments): string {\n return formatMessage(\"querykit.limit.exceeded\", { path: args?.property ?? \"offset\", limit: \"maxOffset\", max: maxOffsetOf(args) });\n }\n}\n\n/* ------------------------------- decorators ------------------------------- */\n\n/**\n * Sahifa chuqurligini cheklaydi: `page` uchun `(page - 1) × perPage`, infinite `offset` uchun\n * qiymatning o'zi `maxOffset`dan oshmasin. Chuqurroq sahifalar — cursor paginatsiya.\n *\n * @example\n * ```ts\n * class ListDto {\n * @IsOptional() @Type(() => Number) @IsInt() @IsPositive() @IsWithinMaxOffset({ mode: \"page\" })\n * page?: number;\n * }\n * ```\n */\nexport function IsWithinMaxOffset(options: IsWithinMaxOffsetOptions, validationOptions?: ValidationOptions): PropertyDecorator {\n return (target, propertyName) => {\n registerDecorator({\n target: target.constructor,\n propertyName: propertyName as string,\n options: validationOptions,\n constraints: [options],\n validator: IsWithinMaxOffsetConstraint,\n });\n };\n}\n\n/**\n * Property'ni querykit `Filter` kontraktiga tekshiradi: flat massiv (implicit AND),\n * nested `and`/`or`/`not` daraxti va core `FILTER_OPERATORS`ning to'liq to'plami.\n *\n * `undefined` qiymatni **rad etmaydi** — maydonni ixtiyoriy qilish uchun\n * `@IsOptional()` bilan birga ishlating (DTO'lardagi kabi).\n *\n * @example\n * ```ts\n * class ListUsersDto extends OffsetParamsDto {\n * @IsOptional()\n * @IsQueryFilter({ allowedKeys: [\"id\", \"name\", \"createdAt\"] })\n * declare filter?: Filter;\n * }\n * ```\n */\nexport function IsQueryFilter(options?: IsQueryFilterOptions, validationOptions?: ValidationOptions): PropertyDecorator {\n return (target, propertyName) => {\n registerDecorator({\n target: target.constructor,\n propertyName: propertyName as string,\n options: validationOptions,\n constraints: [options],\n validator: IsQueryFilterConstraint,\n });\n };\n}\n\n/**\n * Property'ni querykit `Sort` kontraktiga tekshiradi — `{ key, direction }[]`.\n * Qarang: {@link IsQueryFilter}.\n */\nexport function IsQuerySort(options?: IsQuerySortOptions, validationOptions?: ValidationOptions): PropertyDecorator {\n return (target, propertyName) => {\n registerDecorator({\n target: target.constructor,\n propertyName: propertyName as string,\n options: validationOptions,\n constraints: [options],\n validator: IsQuerySortConstraint,\n });\n };\n}\n"]}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var core = require('@querykitjs/core');
|
|
4
|
+
var internal = require('@querykitjs/core/internal');
|
|
5
|
+
|
|
6
|
+
var __defProp = Object.defineProperty;
|
|
7
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
8
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
9
|
+
for (var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target, i = decorators.length - 1, decorator; i >= 0; i--)
|
|
10
|
+
(decorator = decorators[i]) && (result = (kind ? decorator(target, key, result) : decorator(result)) || result);
|
|
11
|
+
return kind && result && __defProp(target, key, result), result;
|
|
12
|
+
};
|
|
13
|
+
function localizeIssue(issue, catalog) {
|
|
14
|
+
return internal.formatMessage(issue.key, issue.args, catalog);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// src/validate.ts
|
|
18
|
+
var joinPath = (root, path) => path.reduce((joined, part) => typeof part == "number" ? `${joined}[${part}]` : `${joined}.${part}`, root), toValidationIssue = (root) => (found) => {
|
|
19
|
+
let path = joinPath(root, found.path), args = { path, ...found.args };
|
|
20
|
+
return { path, key: found.key, message: internal.formatMessage(found.key, args), args };
|
|
21
|
+
};
|
|
22
|
+
function validateFilter(value, options = {}) {
|
|
23
|
+
return internal.checkFilter(value, options).map(toValidationIssue(options.rootPath ?? "filter"));
|
|
24
|
+
}
|
|
25
|
+
function validateSort(value, options = {}) {
|
|
26
|
+
return internal.checkSort(value, options).map(toValidationIssue(options.rootPath ?? "sort"));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
Object.defineProperty(exports, "DEFAULT_MAX_FILTER_DEPTH", {
|
|
30
|
+
enumerable: true,
|
|
31
|
+
get: function () { return core.DEFAULT_MAX_FILTER_DEPTH; }
|
|
32
|
+
});
|
|
33
|
+
Object.defineProperty(exports, "formatMessage", {
|
|
34
|
+
enumerable: true,
|
|
35
|
+
get: function () { return internal.formatMessage; }
|
|
36
|
+
});
|
|
37
|
+
exports.__decorateClass = __decorateClass;
|
|
38
|
+
exports.localizeIssue = localizeIssue;
|
|
39
|
+
exports.validateFilter = validateFilter;
|
|
40
|
+
exports.validateSort = validateSort;
|
|
41
|
+
//# sourceMappingURL=chunk-HKLXL3NW.cjs.map
|
|
42
|
+
//# sourceMappingURL=chunk-HKLXL3NW.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/messages.ts","../src/validate.ts"],"names":["formatMessage","checkFilter","checkSort"],"mappings":";;;;;;;;;;;;AAuBO,SAAS,aAAA,CAAc,OAAuD,OAAA,EAAyC;AAC5H,EAAA,OAAOA,sBAAA,CAAc,KAAA,CAAM,GAAA,EAAK,KAAA,CAAM,MAAM,OAAO,CAAA;AACrD;;;AC+CA,IAAM,QAAA,GAAW,CAAC,IAAA,EAAc,IAAA,KAC9B,IAAA,CAAK,MAAA,CAAe,CAAC,MAAA,EAAQ,IAAA,KAAU,OAAO,IAAA,IAAS,QAAA,GAAW,CAAA,EAAG,MAAM,CAAA,CAAA,EAAI,IAAI,CAAA,CAAA,CAAA,GAAM,CAAA,EAAG,MAAM,CAAA,CAAA,EAAI,IAAI,CAAA,CAAA,EAAK,IAAI,CAAA,EAG/G,iBAAA,GACJ,CAAC,IAAA,KACD,CAAC,KAAA,KAA6E;AAC5E,EAAA,IAAM,IAAA,GAAO,QAAA,CAAS,IAAA,EAAM,KAAA,CAAM,IAAI,CAAA,EAChC,IAAA,GAAoB,EAAE,IAAA,EAAM,GAAG,KAAA,CAAM,IAAA,EAAK;AAChD,EAAA,OAAO,EAAE,IAAA,EAAM,GAAA,EAAK,KAAA,CAAM,GAAA,EAAK,OAAA,EAASA,sBAAA,CAAc,KAAA,CAAM,GAAA,EAAK,IAAI,CAAA,EAAG,IAAA,EAAK;AAC/E,CAAA;AAaK,SAAS,cAAA,CAAe,KAAA,EAAgB,OAAA,GAA8B,EAAC,EAAsB;AAClG,EAAA,OAAOC,oBAAA,CAAY,OAAO,OAAO,CAAA,CAAE,IAAI,iBAAA,CAAkB,OAAA,CAAQ,QAAA,IAAY,QAAQ,CAAC,CAAA;AACxF;AAKO,SAAS,YAAA,CAAa,KAAA,EAAgB,OAAA,GAA4B,EAAC,EAAsB;AAC9F,EAAA,OAAOC,kBAAA,CAAU,OAAO,OAAO,CAAA,CAAE,IAAI,iBAAA,CAAkB,OAAA,CAAQ,QAAA,IAAY,MAAM,CAAC,CAAA;AACpF","file":"chunk-HKLXL3NW.cjs","sourcesContent":["/**\n * Xabar formatlash. Kataloglar `locales.ts`da; `formatMessage` core'da\n * (boshqa validatorlar bilan bitta), bu yerda faqat `localizeIssue`.\n *\n * Har bir {@link ValidationIssue} uchta narsani olib yuradi: `key` (tarjima\n * kaliti), `message` (tayyor matn) va `args` (interpolatsiya qiymatlari). Shu\n * sababli paket i18n'ga **majburlamaydi**, lekin unga to'liq mos.\n */\nimport { formatMessage, type MessageArgs, type QueryKitMessageCatalog, type QueryKitMessageKey } from \"@querykitjs/core/internal\";\n\n/** Kalitni matnga aylantirish va argument tipi — core'dan (`formatMessage(key, args, catalog)`, default inglizcha). */\nexport { formatMessage };\nexport type { MessageArgs };\n\n/**\n * Issue'ni tanlangan tilda qayta formatlaydi — `message` inglizcha yozilgan\n * bo'lsa ham, javobni boshqa tilda berish uchun.\n *\n * @example\n * ```ts\n * issues.map(issue => localizeIssue(issue, QUERYKIT_MESSAGES_RU));\n * ```\n */\nexport function localizeIssue(issue: { key: QueryKitMessageKey; args: MessageArgs }, catalog: QueryKitMessageCatalog): string {\n return formatMessage(issue.key, issue.args, catalog);\n}\n","/**\n * Sof TS validatsiya yadrosi — **framework-agnostik**. Tekshiruv mantiqi core\n * `checkFilter`/`checkSort` da (`@querykitjs/core/internal`) — boshqa validatorlar\n * bilan bitta; bu yerda faqat yo'l satri va xabar matni quriladi.\n * `class-validator` dekoratorlari (`decorators.ts`) faqat yupqa o'rov, shuning\n * uchun yadroni Nest'siz ham (Express, Hono, oddiy funksiya) ishlatish mumkin.\n */\nimport { DEFAULT_MAX_FILTER_DEPTH } from \"@querykitjs/core\";\nimport type { FilterOperator } from \"@querykitjs/core\";\nimport { checkFilter, checkSort, type LimitPath, type WireFilterIssueKey, type WireIssue, type WireSortIssueKey } from \"@querykitjs/core/internal\";\nimport { formatMessage, type MessageArgs } from \"./messages\";\nimport type { QueryKitMessageKey } from \"./locales\";\n\n/** Filter daraxti chuqurligi default limiti — core'dan (yagona manba, R7). */\nexport { DEFAULT_MAX_FILTER_DEPTH };\n\nexport interface QueryFilterOptions {\n /**\n * Berilsa — faqat shu kalitlarga ruxsat; noma'lum kalit **rad etiladi**.\n * Backend adapterlari noma'lum kalitni jimgina tashlaydi, bu qatlam esa\n * qat'iyroq: mijoz mavjud bo'lmagan maydon bo'yicha filtr yubora olmaydi.\n * Berilmasa — istalgan string kalit o'tadi (`@querykitjs/zod` bilan parity).\n */\n allowedKeys?: readonly string[];\n /** Ruxsat etilgan operatorlar. Default — core `FILTER_OPERATORS`ning hammasi. */\n allowedOperators?: readonly FilterOperator[];\n /** Ildizdan bargacha tugunlar soni (ildiz 1, har `and`/`or`/`not` va shart +1). Default — core `DEFAULT_MAX_FILTER_DEPTH` (5). */\n maxDepth?: number;\n /** Barcha guruhlardagi shartlar (barglar) soni. Default — core `DEFAULT_MAX_CONDITIONS` (30). */\n maxConditions?: number;\n /** Bitta massiv qiymatdagi elementlar (`in`, `notIn`, …). Default — core `DEFAULT_MAX_IN_ITEMS` (200). */\n maxInItems?: number;\n /** Bitta satr qiymatdagi belgilar (massiv ichida ham). Default — core `DEFAULT_MAX_STRING_LENGTH` (256). */\n maxStringLength?: number;\n /**\n * Operator↔qiymat shakli tekshiruvi (opt-in): `in`/`notIn` massiv talab qiladi,\n * `between`/`notBetween` — aynan ikki element, `isNull`/`isNotNull` — qiymatsiz.\n * Default `false`, ya'ni `@querykitjs/zod` bilan bir xil qat'iylik.\n */\n strictValue?: boolean;\n /**\n * `path`ning ildizi. Default `\"filter\"`. Dekorator buni DTO property nomiga\n * qo'yadi, ya'ni `where` deb nomlangan maydon xatosi `filter.operation` emas,\n * `where.operation` deb chiqadi.\n */\n rootPath?: string;\n}\n\nexport interface QuerySortOptions {\n /** Berilsa — faqat shu kalitlar bo'yicha saralashga ruxsat. */\n allowedKeys?: readonly string[];\n /** `sort` massividagi maydonlar soni. Default — core `DEFAULT_MAX_SORT_FIELDS` (3). */\n maxSortFields?: number;\n /** `path`ning ildizi. Default `\"sort\"`. Qarang: {@link QueryFilterOptions.rootPath}. */\n rootPath?: string;\n}\n\n/** Bitta validatsiya xatosi. */\nexport interface ValidationIssue {\n /** Xato joyi, masalan `filter.and[1].or[0].value`. */\n path: string;\n /** i18n kaliti — iste'molchi tarjima faylida shu kalit qidiriladi. */\n key: QueryKitMessageKey;\n /** Tayyor inglizcha matn — tarjimasiz ham o'qiladi. */\n message: string;\n /** Interpolatsiya argumentlari (`path` har doim bor) — `I18nValidationPipe` uchun. */\n args: MessageArgs;\n}\n\n/* ------------------------------- internals -------------------------------- */\n\n/** Core path → `filter.and[1].or[0].value`. */\nconst joinPath = (root: string, path: LimitPath): string =>\n path.reduce<string>((joined, part) => (typeof part === \"number\" ? `${joined}[${part}]` : `${joined}.${part}`), root);\n\n/** A core issue as this package reports it: a string path under `root`, an English message, `path` first in the args. */\nconst toValidationIssue =\n (root: string) =>\n (found: WireIssue<WireFilterIssueKey | WireSortIssueKey>): ValidationIssue => {\n const path = joinPath(root, found.path);\n const args: MessageArgs = { path, ...found.args };\n return { path, key: found.key, message: formatMessage(found.key, args), args };\n };\n\n/* -------------------------------- public ---------------------------------- */\n\n/**\n * Filter daraxtini core kontraktiga tekshiradi. Bo'sh massiv = valid.\n *\n * @example\n * ```ts\n * const issues = validateFilter(body.filter, { allowedKeys: [\"id\", \"name\"] });\n * if (issues.length) throw new BadRequestException(issues);\n * ```\n */\nexport function validateFilter(value: unknown, options: QueryFilterOptions = {}): ValidationIssue[] {\n return checkFilter(value, options).map(toValidationIssue(options.rootPath ?? \"filter\"));\n}\n\n/**\n * Sort massivini core kontraktiga tekshiradi (`{ key, direction }[]`). Bo'sh massiv = valid.\n */\nexport function validateSort(value: unknown, options: QuerySortOptions = {}): ValidationIssue[] {\n return checkSort(value, options).map(toValidationIssue(options.rootPath ?? \"sort\"));\n}\n"]}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunkAX53IY63_cjs = require('./chunk-AX53IY63.cjs');
|
|
4
|
+
var chunkHKLXL3NW_cjs = require('./chunk-HKLXL3NW.cjs');
|
|
5
|
+
var classTransformer = require('class-transformer');
|
|
6
|
+
var classValidator = require('class-validator');
|
|
7
|
+
var core = require('@querykitjs/core');
|
|
8
|
+
|
|
9
|
+
var BaseParamsDto = class {
|
|
10
|
+
};
|
|
11
|
+
chunkHKLXL3NW_cjs.__decorateClass([
|
|
12
|
+
classValidator.IsOptional(),
|
|
13
|
+
chunkAX53IY63_cjs.IsQueryFilter()
|
|
14
|
+
], BaseParamsDto.prototype, "filter", 2), chunkHKLXL3NW_cjs.__decorateClass([
|
|
15
|
+
classValidator.IsOptional(),
|
|
16
|
+
chunkAX53IY63_cjs.IsQuerySort()
|
|
17
|
+
], BaseParamsDto.prototype, "sort", 2);
|
|
18
|
+
var OffsetParamsDto = class extends BaseParamsDto {
|
|
19
|
+
};
|
|
20
|
+
chunkHKLXL3NW_cjs.__decorateClass([
|
|
21
|
+
classValidator.IsOptional(),
|
|
22
|
+
classTransformer.Type(() => Number),
|
|
23
|
+
classValidator.IsInt(),
|
|
24
|
+
classValidator.IsPositive(),
|
|
25
|
+
chunkAX53IY63_cjs.IsWithinMaxOffset({ mode: "page" })
|
|
26
|
+
], OffsetParamsDto.prototype, "page", 2), chunkHKLXL3NW_cjs.__decorateClass([
|
|
27
|
+
classValidator.IsOptional(),
|
|
28
|
+
classTransformer.Type(() => Number),
|
|
29
|
+
classValidator.IsInt(),
|
|
30
|
+
classValidator.IsPositive(),
|
|
31
|
+
classValidator.Max(core.DEFAULT_MAX_PER_PAGE)
|
|
32
|
+
], OffsetParamsDto.prototype, "perPage", 2), chunkHKLXL3NW_cjs.__decorateClass([
|
|
33
|
+
classValidator.IsOptional(),
|
|
34
|
+
classValidator.IsBoolean()
|
|
35
|
+
], OffsetParamsDto.prototype, "withTotal", 2);
|
|
36
|
+
var InfiniteParamsDto = class extends BaseParamsDto {
|
|
37
|
+
};
|
|
38
|
+
chunkHKLXL3NW_cjs.__decorateClass([
|
|
39
|
+
classValidator.IsOptional(),
|
|
40
|
+
classTransformer.Type(() => Number),
|
|
41
|
+
classValidator.IsInt(),
|
|
42
|
+
classValidator.IsPositive(),
|
|
43
|
+
classValidator.Max(core.DEFAULT_MAX_LIMIT)
|
|
44
|
+
], InfiniteParamsDto.prototype, "limit", 2), chunkHKLXL3NW_cjs.__decorateClass([
|
|
45
|
+
classValidator.IsOptional(),
|
|
46
|
+
classTransformer.Type(() => Number),
|
|
47
|
+
classValidator.IsInt(),
|
|
48
|
+
classValidator.Min(0),
|
|
49
|
+
chunkAX53IY63_cjs.IsWithinMaxOffset({ mode: "offset" })
|
|
50
|
+
], InfiniteParamsDto.prototype, "offset", 2);
|
|
51
|
+
var CursorParamsDto = class {
|
|
52
|
+
};
|
|
53
|
+
chunkHKLXL3NW_cjs.__decorateClass([
|
|
54
|
+
classValidator.IsOptional(),
|
|
55
|
+
chunkAX53IY63_cjs.IsQueryFilter()
|
|
56
|
+
], CursorParamsDto.prototype, "filter", 2), chunkHKLXL3NW_cjs.__decorateClass([
|
|
57
|
+
classValidator.IsOptional(),
|
|
58
|
+
classTransformer.Type(() => Number),
|
|
59
|
+
classValidator.IsInt(),
|
|
60
|
+
classValidator.IsPositive(),
|
|
61
|
+
classValidator.Max(core.DEFAULT_MAX_LIMIT)
|
|
62
|
+
], CursorParamsDto.prototype, "limit", 2), chunkHKLXL3NW_cjs.__decorateClass([
|
|
63
|
+
classValidator.IsOptional(),
|
|
64
|
+
classValidator.IsString()
|
|
65
|
+
], CursorParamsDto.prototype, "cursor", 2), chunkHKLXL3NW_cjs.__decorateClass([
|
|
66
|
+
classValidator.IsOptional(),
|
|
67
|
+
classValidator.IsString()
|
|
68
|
+
], CursorParamsDto.prototype, "cursorKey", 2), chunkHKLXL3NW_cjs.__decorateClass([
|
|
69
|
+
classValidator.IsOptional(),
|
|
70
|
+
classValidator.IsIn(["asc", "desc"])
|
|
71
|
+
], CursorParamsDto.prototype, "order", 2), chunkHKLXL3NW_cjs.__decorateClass([
|
|
72
|
+
classValidator.IsOptional(),
|
|
73
|
+
classValidator.IsIn(["forward", "backward"])
|
|
74
|
+
], CursorParamsDto.prototype, "direction", 2);
|
|
75
|
+
|
|
76
|
+
exports.BaseParamsDto = BaseParamsDto;
|
|
77
|
+
exports.CursorParamsDto = CursorParamsDto;
|
|
78
|
+
exports.InfiniteParamsDto = InfiniteParamsDto;
|
|
79
|
+
exports.OffsetParamsDto = OffsetParamsDto;
|
|
80
|
+
//# sourceMappingURL=chunk-PKGWV2JC.cjs.map
|
|
81
|
+
//# sourceMappingURL=chunk-PKGWV2JC.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/legacy-dto.ts"],"names":["__decorateClass","IsOptional","IsQueryFilter","IsQuerySort","Type","IsInt","IsPositive","IsWithinMaxOffset","Max","DEFAULT_MAX_PER_PAGE","IsBoolean","DEFAULT_MAX_LIMIT","Min","IsString","IsIn"],"mappings":"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","file":"chunk-PKGWV2JC.cjs","sourcesContent":["/**\n * Static param DTOs (deprecated) — `class ListUsersDto extends OffsetParamsDto`.\n * Kept in their own module: their decorators register class-validator metadata\n * when the module loads, so a bundle that does not import them drops the whole module.\n * New code uses the `make*ParamsDto` factories (`./dto`).\n */\nimport { Type } from \"class-transformer\";\nimport { IsIn, IsInt, IsOptional, IsPositive, IsString, Max, Min, IsBoolean } from \"class-validator\";\nimport { DEFAULT_MAX_LIMIT, DEFAULT_MAX_PER_PAGE } from \"@querykitjs/core\";\nimport type { Filter, Sort, SortDirection } from \"@querykitjs/core\";\nimport { IsQueryFilter, IsQuerySort, IsWithinMaxOffset } from \"./decorators\";\n\n/**\n * Paginatsiyasiz umumiy params — `filter` + `sort`.\n *\n * @deprecated Use `makeOffsetParamsDto` / `makeInfiniteParamsDto` / `makeCursorParamsDto` (safe defaults). Removed in the next major.\n */\nexport class BaseParamsDto {\n @IsOptional()\n @IsQueryFilter()\n filter?: Filter;\n\n @IsOptional()\n @IsQuerySort()\n sort?: Sort;\n}\n\n/**\n * Offset (sahifali) params — `page` + `perPage` (`perPage` ≤ core cap).\n *\n * @deprecated Use `makeOffsetParamsDto` (safe defaults). Removed in the next major.\n */\nexport class OffsetParamsDto extends BaseParamsDto {\n /* `page`ning o'zida cap yo'q — chegara `(page - 1) × perPage` ≤ `DEFAULT_MAX_OFFSET`\n * (`@querykitjs/zod` factory'lari ham aynan shunday). */\n @IsOptional()\n @Type(() => Number)\n @IsInt()\n @IsPositive()\n @IsWithinMaxOffset({ mode: \"page\" })\n page?: number;\n\n @IsOptional()\n @Type(() => Number)\n @IsInt()\n @IsPositive()\n @Max(DEFAULT_MAX_PER_PAGE)\n perPage?: number;\n\n /** `false` — no count; the meta totals are `null`. */\n @IsOptional()\n @IsBoolean()\n withTotal?: boolean;\n}\n\n/**\n * Infinite-scroll params — `limit` + `offset`.\n *\n * @deprecated Use `makeInfiniteParamsDto` (safe defaults). Removed in the next major.\n */\nexport class InfiniteParamsDto extends BaseParamsDto {\n @IsOptional()\n @Type(() => Number)\n @IsInt()\n @IsPositive()\n @Max(DEFAULT_MAX_LIMIT)\n limit?: number;\n\n @IsOptional()\n @Type(() => Number)\n @IsInt()\n @Min(0)\n @IsWithinMaxOffset({ mode: \"offset\" })\n offset?: number;\n}\n\n/**\n * Cursor (keyset) params. `sort` **yo'q**: tartib `order` + `direction` bilan\n * boshqariladi. Shu sababli `BaseParamsDto`dan meros olinmaydi — aks holda\n * `sort` kontraktga qarshi ravishda meros bo'lib qolardi.\n *\n * @deprecated Use `makeCursorParamsDto` (safe defaults). Removed in the next major.\n */\nexport class CursorParamsDto {\n @IsOptional()\n @IsQueryFilter()\n filter?: Filter;\n\n @IsOptional()\n @Type(() => Number)\n @IsInt()\n @IsPositive()\n @Max(DEFAULT_MAX_LIMIT)\n limit?: number;\n\n @IsOptional()\n @IsString()\n cursor?: string | null;\n\n @IsOptional()\n @IsString()\n cursorKey?: string;\n\n @IsOptional()\n @IsIn([\"asc\", \"desc\"])\n order?: SortDirection;\n\n @IsOptional()\n @IsIn([\"forward\", \"backward\"])\n direction?: \"forward\" | \"backward\";\n}\n"]}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export { DEFAULT_MAX_FILTER_DEPTH } from '@querykitjs/core';
|
|
2
|
+
import { formatMessage, checkFilter, checkSort } from '@querykitjs/core/internal';
|
|
3
|
+
export { formatMessage } from '@querykitjs/core/internal';
|
|
4
|
+
|
|
5
|
+
var __defProp = Object.defineProperty;
|
|
6
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
7
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
8
|
+
for (var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target, i = decorators.length - 1, decorator; i >= 0; i--)
|
|
9
|
+
(decorator = decorators[i]) && (result = (kind ? decorator(target, key, result) : decorator(result)) || result);
|
|
10
|
+
return kind && result && __defProp(target, key, result), result;
|
|
11
|
+
};
|
|
12
|
+
function localizeIssue(issue, catalog) {
|
|
13
|
+
return formatMessage(issue.key, issue.args, catalog);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// src/validate.ts
|
|
17
|
+
var joinPath = (root, path) => path.reduce((joined, part) => typeof part == "number" ? `${joined}[${part}]` : `${joined}.${part}`, root), toValidationIssue = (root) => (found) => {
|
|
18
|
+
let path = joinPath(root, found.path), args = { path, ...found.args };
|
|
19
|
+
return { path, key: found.key, message: formatMessage(found.key, args), args };
|
|
20
|
+
};
|
|
21
|
+
function validateFilter(value, options = {}) {
|
|
22
|
+
return checkFilter(value, options).map(toValidationIssue(options.rootPath ?? "filter"));
|
|
23
|
+
}
|
|
24
|
+
function validateSort(value, options = {}) {
|
|
25
|
+
return checkSort(value, options).map(toValidationIssue(options.rootPath ?? "sort"));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export { __decorateClass, localizeIssue, validateFilter, validateSort };
|
|
29
|
+
//# sourceMappingURL=chunk-QMFPUYJO.js.map
|
|
30
|
+
//# sourceMappingURL=chunk-QMFPUYJO.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/messages.ts","../src/validate.ts"],"names":[],"mappings":";;;;;;;;;;;AAuBO,SAAS,aAAA,CAAc,OAAuD,OAAA,EAAyC;AAC5H,EAAA,OAAO,aAAA,CAAc,KAAA,CAAM,GAAA,EAAK,KAAA,CAAM,MAAM,OAAO,CAAA;AACrD;;;AC+CA,IAAM,QAAA,GAAW,CAAC,IAAA,EAAc,IAAA,KAC9B,IAAA,CAAK,MAAA,CAAe,CAAC,MAAA,EAAQ,IAAA,KAAU,OAAO,IAAA,IAAS,QAAA,GAAW,CAAA,EAAG,MAAM,CAAA,CAAA,EAAI,IAAI,CAAA,CAAA,CAAA,GAAM,CAAA,EAAG,MAAM,CAAA,CAAA,EAAI,IAAI,CAAA,CAAA,EAAK,IAAI,CAAA,EAG/G,iBAAA,GACJ,CAAC,IAAA,KACD,CAAC,KAAA,KAA6E;AAC5E,EAAA,IAAM,IAAA,GAAO,QAAA,CAAS,IAAA,EAAM,KAAA,CAAM,IAAI,CAAA,EAChC,IAAA,GAAoB,EAAE,IAAA,EAAM,GAAG,KAAA,CAAM,IAAA,EAAK;AAChD,EAAA,OAAO,EAAE,IAAA,EAAM,GAAA,EAAK,KAAA,CAAM,GAAA,EAAK,OAAA,EAAS,aAAA,CAAc,KAAA,CAAM,GAAA,EAAK,IAAI,CAAA,EAAG,IAAA,EAAK;AAC/E,CAAA;AAaK,SAAS,cAAA,CAAe,KAAA,EAAgB,OAAA,GAA8B,EAAC,EAAsB;AAClG,EAAA,OAAO,WAAA,CAAY,OAAO,OAAO,CAAA,CAAE,IAAI,iBAAA,CAAkB,OAAA,CAAQ,QAAA,IAAY,QAAQ,CAAC,CAAA;AACxF;AAKO,SAAS,YAAA,CAAa,KAAA,EAAgB,OAAA,GAA4B,EAAC,EAAsB;AAC9F,EAAA,OAAO,SAAA,CAAU,OAAO,OAAO,CAAA,CAAE,IAAI,iBAAA,CAAkB,OAAA,CAAQ,QAAA,IAAY,MAAM,CAAC,CAAA;AACpF","file":"chunk-QMFPUYJO.js","sourcesContent":["/**\n * Xabar formatlash. Kataloglar `locales.ts`da; `formatMessage` core'da\n * (boshqa validatorlar bilan bitta), bu yerda faqat `localizeIssue`.\n *\n * Har bir {@link ValidationIssue} uchta narsani olib yuradi: `key` (tarjima\n * kaliti), `message` (tayyor matn) va `args` (interpolatsiya qiymatlari). Shu\n * sababli paket i18n'ga **majburlamaydi**, lekin unga to'liq mos.\n */\nimport { formatMessage, type MessageArgs, type QueryKitMessageCatalog, type QueryKitMessageKey } from \"@querykitjs/core/internal\";\n\n/** Kalitni matnga aylantirish va argument tipi — core'dan (`formatMessage(key, args, catalog)`, default inglizcha). */\nexport { formatMessage };\nexport type { MessageArgs };\n\n/**\n * Issue'ni tanlangan tilda qayta formatlaydi — `message` inglizcha yozilgan\n * bo'lsa ham, javobni boshqa tilda berish uchun.\n *\n * @example\n * ```ts\n * issues.map(issue => localizeIssue(issue, QUERYKIT_MESSAGES_RU));\n * ```\n */\nexport function localizeIssue(issue: { key: QueryKitMessageKey; args: MessageArgs }, catalog: QueryKitMessageCatalog): string {\n return formatMessage(issue.key, issue.args, catalog);\n}\n","/**\n * Sof TS validatsiya yadrosi — **framework-agnostik**. Tekshiruv mantiqi core\n * `checkFilter`/`checkSort` da (`@querykitjs/core/internal`) — boshqa validatorlar\n * bilan bitta; bu yerda faqat yo'l satri va xabar matni quriladi.\n * `class-validator` dekoratorlari (`decorators.ts`) faqat yupqa o'rov, shuning\n * uchun yadroni Nest'siz ham (Express, Hono, oddiy funksiya) ishlatish mumkin.\n */\nimport { DEFAULT_MAX_FILTER_DEPTH } from \"@querykitjs/core\";\nimport type { FilterOperator } from \"@querykitjs/core\";\nimport { checkFilter, checkSort, type LimitPath, type WireFilterIssueKey, type WireIssue, type WireSortIssueKey } from \"@querykitjs/core/internal\";\nimport { formatMessage, type MessageArgs } from \"./messages\";\nimport type { QueryKitMessageKey } from \"./locales\";\n\n/** Filter daraxti chuqurligi default limiti — core'dan (yagona manba, R7). */\nexport { DEFAULT_MAX_FILTER_DEPTH };\n\nexport interface QueryFilterOptions {\n /**\n * Berilsa — faqat shu kalitlarga ruxsat; noma'lum kalit **rad etiladi**.\n * Backend adapterlari noma'lum kalitni jimgina tashlaydi, bu qatlam esa\n * qat'iyroq: mijoz mavjud bo'lmagan maydon bo'yicha filtr yubora olmaydi.\n * Berilmasa — istalgan string kalit o'tadi (`@querykitjs/zod` bilan parity).\n */\n allowedKeys?: readonly string[];\n /** Ruxsat etilgan operatorlar. Default — core `FILTER_OPERATORS`ning hammasi. */\n allowedOperators?: readonly FilterOperator[];\n /** Ildizdan bargacha tugunlar soni (ildiz 1, har `and`/`or`/`not` va shart +1). Default — core `DEFAULT_MAX_FILTER_DEPTH` (5). */\n maxDepth?: number;\n /** Barcha guruhlardagi shartlar (barglar) soni. Default — core `DEFAULT_MAX_CONDITIONS` (30). */\n maxConditions?: number;\n /** Bitta massiv qiymatdagi elementlar (`in`, `notIn`, …). Default — core `DEFAULT_MAX_IN_ITEMS` (200). */\n maxInItems?: number;\n /** Bitta satr qiymatdagi belgilar (massiv ichida ham). Default — core `DEFAULT_MAX_STRING_LENGTH` (256). */\n maxStringLength?: number;\n /**\n * Operator↔qiymat shakli tekshiruvi (opt-in): `in`/`notIn` massiv talab qiladi,\n * `between`/`notBetween` — aynan ikki element, `isNull`/`isNotNull` — qiymatsiz.\n * Default `false`, ya'ni `@querykitjs/zod` bilan bir xil qat'iylik.\n */\n strictValue?: boolean;\n /**\n * `path`ning ildizi. Default `\"filter\"`. Dekorator buni DTO property nomiga\n * qo'yadi, ya'ni `where` deb nomlangan maydon xatosi `filter.operation` emas,\n * `where.operation` deb chiqadi.\n */\n rootPath?: string;\n}\n\nexport interface QuerySortOptions {\n /** Berilsa — faqat shu kalitlar bo'yicha saralashga ruxsat. */\n allowedKeys?: readonly string[];\n /** `sort` massividagi maydonlar soni. Default — core `DEFAULT_MAX_SORT_FIELDS` (3). */\n maxSortFields?: number;\n /** `path`ning ildizi. Default `\"sort\"`. Qarang: {@link QueryFilterOptions.rootPath}. */\n rootPath?: string;\n}\n\n/** Bitta validatsiya xatosi. */\nexport interface ValidationIssue {\n /** Xato joyi, masalan `filter.and[1].or[0].value`. */\n path: string;\n /** i18n kaliti — iste'molchi tarjima faylida shu kalit qidiriladi. */\n key: QueryKitMessageKey;\n /** Tayyor inglizcha matn — tarjimasiz ham o'qiladi. */\n message: string;\n /** Interpolatsiya argumentlari (`path` har doim bor) — `I18nValidationPipe` uchun. */\n args: MessageArgs;\n}\n\n/* ------------------------------- internals -------------------------------- */\n\n/** Core path → `filter.and[1].or[0].value`. */\nconst joinPath = (root: string, path: LimitPath): string =>\n path.reduce<string>((joined, part) => (typeof part === \"number\" ? `${joined}[${part}]` : `${joined}.${part}`), root);\n\n/** A core issue as this package reports it: a string path under `root`, an English message, `path` first in the args. */\nconst toValidationIssue =\n (root: string) =>\n (found: WireIssue<WireFilterIssueKey | WireSortIssueKey>): ValidationIssue => {\n const path = joinPath(root, found.path);\n const args: MessageArgs = { path, ...found.args };\n return { path, key: found.key, message: formatMessage(found.key, args), args };\n };\n\n/* -------------------------------- public ---------------------------------- */\n\n/**\n * Filter daraxtini core kontraktiga tekshiradi. Bo'sh massiv = valid.\n *\n * @example\n * ```ts\n * const issues = validateFilter(body.filter, { allowedKeys: [\"id\", \"name\"] });\n * if (issues.length) throw new BadRequestException(issues);\n * ```\n */\nexport function validateFilter(value: unknown, options: QueryFilterOptions = {}): ValidationIssue[] {\n return checkFilter(value, options).map(toValidationIssue(options.rootPath ?? \"filter\"));\n}\n\n/**\n * Sort massivini core kontraktiga tekshiradi (`{ key, direction }[]`). Bo'sh massiv = valid.\n */\nexport function validateSort(value: unknown, options: QuerySortOptions = {}): ValidationIssue[] {\n return checkSort(value, options).map(toValidationIssue(options.rootPath ?? \"sort\"));\n}\n"]}
|