@querykitjs/class-validator 1.0.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +68 -15
- package/README.md +68 -15
- package/dist/chunk-BZRPXHJC.cjs +81 -0
- package/dist/chunk-BZRPXHJC.cjs.map +1 -0
- package/dist/chunk-F3KZNLYZ.js +76 -0
- package/dist/chunk-F3KZNLYZ.js.map +1 -0
- package/dist/chunk-KT5JRG6S.js +194 -0
- package/dist/chunk-KT5JRG6S.js.map +1 -0
- package/dist/chunk-KXCJO556.cjs +207 -0
- package/dist/chunk-KXCJO556.cjs.map +1 -0
- package/dist/chunk-UICYH7OH.js +85 -0
- package/dist/chunk-UICYH7OH.js.map +1 -0
- package/dist/chunk-X7HMP7H7.cjs +89 -0
- package/dist/chunk-X7HMP7H7.cjs.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 +94 -408
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +82 -10
- package/dist/index.d.ts +82 -10
- package/dist/index.js +20 -394
- 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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"validate.cjs"}
|
package/dist/validate.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"validate.js"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@querykitjs/class-validator",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "class-validator decorators and DTOs that validate the querykit request contract (filters, sort, pagination) — types stay in sync with @querykitjs/core.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": {
|
|
@@ -23,13 +23,19 @@
|
|
|
23
23
|
"types": "./dist/index.d.ts",
|
|
24
24
|
"exports": {
|
|
25
25
|
".": {
|
|
26
|
-
"
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
"import": {
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"default": "./dist/index.js"
|
|
29
|
+
},
|
|
30
|
+
"require": {
|
|
31
|
+
"types": "./dist/index.d.cts",
|
|
32
|
+
"default": "./dist/index.cjs"
|
|
33
|
+
}
|
|
29
34
|
}
|
|
30
35
|
},
|
|
31
36
|
"files": [
|
|
32
37
|
"dist",
|
|
38
|
+
"skills/querykit-class-validator",
|
|
33
39
|
"README.md",
|
|
34
40
|
"README.en.md",
|
|
35
41
|
"LICENSE"
|
|
@@ -38,6 +44,9 @@
|
|
|
38
44
|
"publishConfig": {
|
|
39
45
|
"access": "public"
|
|
40
46
|
},
|
|
47
|
+
"engines": {
|
|
48
|
+
"node": ">=22.12"
|
|
49
|
+
},
|
|
41
50
|
"scripts": {
|
|
42
51
|
"build": "tsup",
|
|
43
52
|
"dev": "tsup --watch",
|
|
@@ -45,12 +54,12 @@
|
|
|
45
54
|
"test:smoke": "cd test/.bun && bun ../smoke.ts"
|
|
46
55
|
},
|
|
47
56
|
"dependencies": {
|
|
48
|
-
"@querykitjs/core": "
|
|
57
|
+
"@querykitjs/core": "^2.0.0"
|
|
49
58
|
},
|
|
50
59
|
"peerDependencies": {
|
|
51
|
-
"class-transformer": "
|
|
52
|
-
"class-validator": ">=0.14",
|
|
53
|
-
"reflect-metadata": "
|
|
60
|
+
"class-transformer": "^0.5",
|
|
61
|
+
"class-validator": ">=0.14 <0.16",
|
|
62
|
+
"reflect-metadata": "^0.1.13 || ^0.2"
|
|
54
63
|
},
|
|
55
64
|
"peerDependenciesMeta": {
|
|
56
65
|
"reflect-metadata": {
|
|
@@ -58,9 +67,9 @@
|
|
|
58
67
|
}
|
|
59
68
|
},
|
|
60
69
|
"devDependencies": {
|
|
61
|
-
"class-transformer": "
|
|
62
|
-
"class-validator": "
|
|
63
|
-
"reflect-metadata": "
|
|
70
|
+
"class-transformer": "0.5.1",
|
|
71
|
+
"class-validator": "0.15.1",
|
|
72
|
+
"reflect-metadata": "0.2.2"
|
|
64
73
|
},
|
|
65
74
|
"keywords": [
|
|
66
75
|
"querykit",
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: querykit-class-validator
|
|
3
|
+
description: Use when code validates a querykit list request with class-validator DTOs (@querykitjs/class-validator) - makeOffsetParamsDto / makeInfiniteParamsDto / makeCursorParamsDto, allowedKeys, maxDepth and limits, the allow option for columns / with / withDeleted, NestJS ValidationPipe settings, IsQueryFilter / IsQuerySort, i18n message catalogs (QUERYKIT_LOCALES), or validateFilter / validateSort without Nest.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Safety rules
|
|
7
|
+
|
|
8
|
+
- DO build DTOs with the factories `makeOffsetParamsDto`, `makeInfiniteParamsDto`, `makeCursorParamsDto`. DON'T use the deprecated constant DTOs in new code.
|
|
9
|
+
- DO run Nest's `ValidationPipe` with `whitelist: true` and `transform: true`. Without `whitelist` the server-owned `columns`, `with` and `withDeleted` survive on the object and reach the repository; without `transform` numeric query-string fields stay strings.
|
|
10
|
+
- DON'T open `columns`, `with` or `withDeleted` by default. Only `allow: ["withDeleted"]` (or `"columns"`, `"with"`) opens one, on purpose, and the repository's `forcedColumns` / `allowedColumns` / `relations` still decide.
|
|
11
|
+
- DO pass `allowedKeys` (the filter and sort fields a client may name). An unknown key is then rejected, and `[]` allows nothing. Without it any key passes.
|
|
12
|
+
- DO keep `maxDepth` (default `5`) and the other request limits from core `DEFAULT_QUERY_LIMITS`: conditions `30`, array items `200`, string length `256`, offset `10000`, sort fields `3`. `perPage` and `limit` are capped at `200`. Raise one with `limits` on the factory. DON'T write your own size checks.
|
|
13
|
+
- DO treat the DTO as the first line only: the repository's `filterable` / `sortable` policy on the server is still the authority on which fields and operators a client may use.
|
|
14
|
+
|
|
15
|
+
## When to use
|
|
16
|
+
|
|
17
|
+
- A NestJS controller that receives the JSON body `@querykitjs/web` sends and passes it to a querykit repository.
|
|
18
|
+
- Express, Hono or any other framework with `validateFilter` / `validateSort` (no Nest, no decorators).
|
|
19
|
+
- Validation messages in more than one language (`en`, `ru`, `uz` catalogs, or your own).
|
|
20
|
+
|
|
21
|
+
With zod use `@querykitjs/zod`; the request contract is the same.
|
|
22
|
+
|
|
23
|
+
## Setup
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { makeOffsetParamsDto } from "@querykitjs/class-validator";
|
|
27
|
+
|
|
28
|
+
// main.ts: app.useGlobalPipes(new ValidationPipe({ transform: true, whitelist: true }));
|
|
29
|
+
export const ListBuyersDto = makeOffsetParamsDto({ allowedKeys: ["buyerName", "createdAt", "status"] });
|
|
30
|
+
export type ListBuyersDto = InstanceType<typeof ListBuyersDto>;
|
|
31
|
+
|
|
32
|
+
declare function findList(params: ListBuyersDto): Promise<{ data: unknown[] }>;
|
|
33
|
+
|
|
34
|
+
// @Post("list") list(@Body() params: ListBuyersDto) { … }
|
|
35
|
+
export const list = (params: ListBuyersDto) => findList(params); // core-aligned, no cast
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Common tasks
|
|
39
|
+
|
|
40
|
+
### Infinite scroll and cursor DTOs
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import { makeCursorParamsDto, makeInfiniteParamsDto } from "@querykitjs/class-validator";
|
|
44
|
+
|
|
45
|
+
export const FeedDto = makeInfiniteParamsDto({ allowedKeys: ["status"], maxLimit: 50 }); // limit / offset
|
|
46
|
+
export const TimelineDto = makeCursorParamsDto({ allowedKeys: ["status"] }); // limit / cursor / cursorKey / order / direction
|
|
47
|
+
export type TimelineDto = InstanceType<typeof TimelineDto>;
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Add a field of your own with a decorator
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
import { IsOptional, IsString, MaxLength } from "class-validator";
|
|
54
|
+
import { makeOffsetParamsDto } from "@querykitjs/class-validator";
|
|
55
|
+
|
|
56
|
+
const BaseListDto = makeOffsetParamsDto({ allowedKeys: ["buyerName", "status"] });
|
|
57
|
+
|
|
58
|
+
export class ListBuyersDto extends BaseListDto {
|
|
59
|
+
@IsOptional()
|
|
60
|
+
@IsString()
|
|
61
|
+
@MaxLength(64)
|
|
62
|
+
search?: string; // decorated, so whitelist: true keeps it
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Tighten the limits and the operators
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
import { makeOffsetParamsDto } from "@querykitjs/class-validator";
|
|
70
|
+
|
|
71
|
+
export const ReportDto = makeOffsetParamsDto({
|
|
72
|
+
allowedKeys: ["status", "createdAt"],
|
|
73
|
+
maxPerPage: 100,
|
|
74
|
+
limits: { maxConditions: 10, maxInItems: 50 },
|
|
75
|
+
filter: { maxDepth: 3, allowedOperators: ["=", "in", "between"], strictValue: true },
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Skip the count query with `withTotal`
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
import { makeOffsetParamsDto } from "@querykitjs/class-validator";
|
|
83
|
+
|
|
84
|
+
const ListDto = makeOffsetParamsDto({ allowedKeys: ["status"] });
|
|
85
|
+
const params: InstanceType<typeof ListDto> = { page: 3, perPage: 50, withTotal: false };
|
|
86
|
+
// withTotal: false ⇒ the repository runs no count; meta.total_items / total_pages are null
|
|
87
|
+
console.log(params.withTotal);
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Messages in the request's language
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
import { formatMessage, makeOffsetParamsDto, QUERYKIT_LOCALES, QUERYKIT_MESSAGES_UZ } from "@querykitjs/class-validator";
|
|
94
|
+
|
|
95
|
+
// messageStyle "key" returns i18n keys (for nestjs-i18n's I18nValidationPipe) instead of sentences
|
|
96
|
+
export const ListDto = makeOffsetParamsDto({ allowedKeys: ["buyerName"], filter: { messageStyle: "key" }, sort: { messageStyle: "key" } });
|
|
97
|
+
|
|
98
|
+
console.log(Object.keys(QUERYKIT_LOCALES)); // en, ru, uz
|
|
99
|
+
console.log(formatMessage("querykit.filter.unknown_key", { path: "filter[0]", key: "zzz" }, QUERYKIT_MESSAGES_UZ));
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Validate without Nest
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
import { validateFilter, validateSort } from "@querykitjs/class-validator";
|
|
106
|
+
|
|
107
|
+
declare const body: { filter?: unknown; sort?: unknown };
|
|
108
|
+
const allowedKeys = ["status", "createdAt"];
|
|
109
|
+
|
|
110
|
+
const issues = [...validateFilter(body.filter, { allowedKeys, strictValue: true }), ...validateSort(body.sort, { allowedKeys })];
|
|
111
|
+
if (issues.length > 0) console.log(issues.map(issue => `${issue.path}: ${issue.message}`)); // return 400 with { path, key, message, args }
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Request contract
|
|
115
|
+
|
|
116
|
+
The same JSON shape as every querykit package (`@querykitjs/core`): conditions `{ key, operation?, value? }`, groups `{ and }` / `{ or }` / `{ not }` (a flat array = AND), `sort` always `[{ key, direction? }]`, offset `page` / `perPage` / `withTotal`, infinite `limit` / `offset`, cursor `limit` / `cursor` / `cursorKey` / `order` / `direction` (no `sort`). Meta is snake_case: `total_items`, `total_pages`, `has_next`, `has_more`, `next_offset`, `next_cursor`, `prev_cursor`. The body types are `WireOffsetParams`, `WireInfiniteParams` and `WireCursorParams`; validate with `@querykitjs/zod` or `@querykitjs/class-validator`, then pass the body as is. Refusals are `QueryKitError` with `{ source, site, key, operation?, reason }`.
|
|
117
|
+
|
|
118
|
+
## Mistakes to avoid
|
|
119
|
+
|
|
120
|
+
- Using the deprecated constant DTOs `OffsetParamsDto`, `InfiniteParamsDto`, `CursorParamsDto`, `BaseParamsDto` in new code.
|
|
121
|
+
- Forgetting `whitelist: true` or `transform: true` on `ValidationPipe`.
|
|
122
|
+
- Overriding `filter` in a subclass without `declare`: the property declaration shadows the inherited value.
|
|
123
|
+
- Sending a `Date` object, `NaN` or `Infinity` as a filter value: the wire is JSON, so they are rejected; send ISO strings.
|
|
124
|
+
- Expecting `allowedKeys` to stop a field the repository allows: it only narrows the request; the repository's `filterable` is checked again on the server.
|