adorn-api 1.1.2 → 1.1.3
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.md +126 -17
- package/dist/adapter/metal-orm/crud-dtos.d.ts +5 -4
- package/dist/adapter/metal-orm/crud-dtos.js +199 -6
- package/dist/adapter/metal-orm/index.d.ts +2 -1
- package/dist/adapter/metal-orm/index.js +3 -1
- package/dist/adapter/metal-orm/sort.d.ts +7 -0
- package/dist/adapter/metal-orm/sort.js +54 -0
- package/dist/adapter/metal-orm/types.d.ts +132 -5
- package/package.json +1 -1
- package/src/adapter/metal-orm/crud-dtos.ts +376 -106
- package/src/adapter/metal-orm/index.ts +25 -11
- package/src/adapter/metal-orm/sort.ts +82 -0
- package/src/adapter/metal-orm/types.ts +160 -11
- package/tests/e2e/metal-crud-openapi.e2e.test.ts +36 -19
- package/tests/unit/crud-dtos.test.ts +98 -26
- package/tests/unit/parse-filter.test.ts +1 -1
- package/tests/unit/parse-sort.test.ts +48 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
import type { BelongsToReference, HasManyCollection, HasOneReference, ManyToManyCollection } from "metal-orm";
|
|
3
|
-
import type { DtoOptions, FieldOverride } from "../../core/decorators";
|
|
3
|
+
import type { DtoOptions, ErrorResponseOptions, FieldOverride } from "../../core/decorators";
|
|
4
4
|
import type { SchemaNode } from "../../core/schema";
|
|
5
5
|
import type { DtoConstructor } from "../../core/types";
|
|
6
6
|
|
|
@@ -139,6 +139,41 @@ export interface ParseFilterOptions<T = Record<string, unknown>> {
|
|
|
139
139
|
fieldMappings?: Record<string, FilterMapping<T>>;
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
+
/**
|
|
143
|
+
* Sort direction.
|
|
144
|
+
*/
|
|
145
|
+
export type SortDirection = "asc" | "desc";
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Sort parsing options.
|
|
149
|
+
*/
|
|
150
|
+
export interface ParseSortOptions<T = Record<string, unknown>> {
|
|
151
|
+
/** Query parameters */
|
|
152
|
+
query?: Record<string, unknown>;
|
|
153
|
+
/** Allowed sortable columns */
|
|
154
|
+
sortableColumns?: Record<string, FilterFieldInput<T>>;
|
|
155
|
+
/** Sort field query key */
|
|
156
|
+
sortByKey?: string;
|
|
157
|
+
/** Sort direction query key */
|
|
158
|
+
sortDirectionKey?: string;
|
|
159
|
+
/** Default sort field */
|
|
160
|
+
defaultSortBy?: string;
|
|
161
|
+
/** Default sort direction */
|
|
162
|
+
defaultSortDirection?: SortDirection;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Parsed sort result.
|
|
167
|
+
*/
|
|
168
|
+
export interface ParsedSort<T = Record<string, unknown>> {
|
|
169
|
+
/** Requested sort key */
|
|
170
|
+
sortBy?: string;
|
|
171
|
+
/** Direction */
|
|
172
|
+
sortDirection: SortDirection;
|
|
173
|
+
/** Resolved entity field */
|
|
174
|
+
field?: FilterFieldInput<T>;
|
|
175
|
+
}
|
|
176
|
+
|
|
142
177
|
/**
|
|
143
178
|
* Filter operator.
|
|
144
179
|
*/
|
|
@@ -204,12 +239,87 @@ export interface PagedResponseDtoOptions {
|
|
|
204
239
|
/**
|
|
205
240
|
* Filter field definition.
|
|
206
241
|
*/
|
|
207
|
-
export interface FilterFieldDef {
|
|
208
|
-
/** Field schema */
|
|
209
|
-
schema?: SchemaNode;
|
|
210
|
-
/** Filter operator */
|
|
211
|
-
operator?: "equals" | "contains" | "startsWith" | "endsWith" | "gt" | "gte" | "lt" | "lte";
|
|
212
|
-
}
|
|
242
|
+
export interface FilterFieldDef {
|
|
243
|
+
/** Field schema */
|
|
244
|
+
schema?: SchemaNode;
|
|
245
|
+
/** Filter operator */
|
|
246
|
+
operator?: "equals" | "contains" | "startsWith" | "endsWith" | "gt" | "gte" | "lt" | "lte";
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Single filter definition for CRUD query generation.
|
|
251
|
+
*/
|
|
252
|
+
export interface MetalCrudQueryFilterDef<T = Record<string, unknown>> {
|
|
253
|
+
/** Field schema for query validation/OpenAPI */
|
|
254
|
+
schema: SchemaNode;
|
|
255
|
+
/** Entity field path mapping */
|
|
256
|
+
field: FilterFieldInput<T>;
|
|
257
|
+
/** Filter operator (default: equals) */
|
|
258
|
+
operator?: FilterOperator;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Sortable columns mapping for CRUD query generation.
|
|
263
|
+
* Key is accepted `sortBy` value and value is mapped entity field path.
|
|
264
|
+
*/
|
|
265
|
+
export type MetalCrudSortableColumns<T = Record<string, unknown>> =
|
|
266
|
+
Record<string, FilterFieldInput<T>>;
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Options-query generation options.
|
|
270
|
+
*/
|
|
271
|
+
export interface MetalCrudOptionsQueryOptions<T = Record<string, unknown>>
|
|
272
|
+
extends PaginationConfig {
|
|
273
|
+
/** Whether options query artifacts are generated (default: true) */
|
|
274
|
+
enabled?: boolean;
|
|
275
|
+
/** Query key used for label search (default: "search") */
|
|
276
|
+
searchKey?: string;
|
|
277
|
+
/** Entity field used as option label (default: "nome") */
|
|
278
|
+
labelField?: keyof T & string;
|
|
279
|
+
/** Entity field used as option value (default: "id") */
|
|
280
|
+
valueField?: keyof T & string;
|
|
281
|
+
/** Operator used for label search (default: "contains") */
|
|
282
|
+
searchOperator?: FilterOperator;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Query generation options for CRUD DTO factory.
|
|
287
|
+
*/
|
|
288
|
+
export interface MetalCrudQueryOptions<T = Record<string, unknown>>
|
|
289
|
+
extends PaginationConfig {
|
|
290
|
+
/** Filters in one place: schema + mapping + operator */
|
|
291
|
+
filters?: Record<string, MetalCrudQueryFilterDef<T>>;
|
|
292
|
+
/** Allowed sortBy values mapped to entity fields */
|
|
293
|
+
sortableColumns?: MetalCrudSortableColumns<T>;
|
|
294
|
+
/** Query key for sort field (default: "sortBy") */
|
|
295
|
+
sortByKey?: string;
|
|
296
|
+
/** Query key for sort direction (default: "sortDirection") */
|
|
297
|
+
sortDirectionKey?: string;
|
|
298
|
+
/** Default sort field key */
|
|
299
|
+
defaultSortBy?: string;
|
|
300
|
+
/** Default sort direction */
|
|
301
|
+
defaultSortDirection?: SortDirection;
|
|
302
|
+
/** Options endpoint query generation options */
|
|
303
|
+
options?: MetalCrudOptionsQueryOptions<T>;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Standard CRUD errors generation options.
|
|
308
|
+
*/
|
|
309
|
+
export interface MetalCrudStandardErrorsOptions {
|
|
310
|
+
/** Enable generation (default: false) */
|
|
311
|
+
enabled?: boolean;
|
|
312
|
+
/** Reuse a specific error DTO schema */
|
|
313
|
+
schema?: DtoConstructor;
|
|
314
|
+
/** Generate default schema with details */
|
|
315
|
+
withDetails?: boolean;
|
|
316
|
+
/** Include traceId in generated schema */
|
|
317
|
+
includeTraceId?: boolean;
|
|
318
|
+
/** 400 invalid id error config (set false to disable) */
|
|
319
|
+
invalidId?: false | ErrorResponseOptions;
|
|
320
|
+
/** 404 not found error config (set false to disable) */
|
|
321
|
+
notFound?: false | ErrorResponseOptions;
|
|
322
|
+
}
|
|
213
323
|
|
|
214
324
|
/**
|
|
215
325
|
* Options for paged filter query DTOs.
|
|
@@ -225,7 +335,7 @@ export interface PagedFilterQueryDtoOptions extends PagedQueryDtoOptions {
|
|
|
225
335
|
/**
|
|
226
336
|
* Options for Metal CRUD DTOs.
|
|
227
337
|
*/
|
|
228
|
-
export interface MetalCrudDtoOptions {
|
|
338
|
+
export interface MetalCrudDtoOptions<T = Record<string, unknown>> {
|
|
229
339
|
/** Field overrides */
|
|
230
340
|
overrides?: Record<string, FieldOverride>;
|
|
231
341
|
/** Response DTO options */
|
|
@@ -244,6 +354,10 @@ export interface MetalCrudDtoOptions {
|
|
|
244
354
|
paramsInclude?: string[];
|
|
245
355
|
/** Immutable fields */
|
|
246
356
|
immutable?: string[];
|
|
357
|
+
/** Query/options/paged artifact generation */
|
|
358
|
+
query?: MetalCrudQueryOptions<T>;
|
|
359
|
+
/** Standard CRUD errors generation */
|
|
360
|
+
errors?: boolean | MetalCrudStandardErrorsOptions;
|
|
247
361
|
/** Whether to throw errors instead of warnings for invalid metadata (default: false) */
|
|
248
362
|
strict?: boolean;
|
|
249
363
|
}
|
|
@@ -267,13 +381,32 @@ export interface MetalCrudDtoDecorators {
|
|
|
267
381
|
/**
|
|
268
382
|
* Metal CRUD DTO class names.
|
|
269
383
|
*/
|
|
270
|
-
export type
|
|
384
|
+
export type RouteErrorsDecorator = (
|
|
385
|
+
value: unknown,
|
|
386
|
+
context: ClassMethodDecoratorContext
|
|
387
|
+
) => void;
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Metal CRUD generated class names.
|
|
391
|
+
*/
|
|
392
|
+
export type MetalCrudDtoClassNameKey =
|
|
393
|
+
keyof MetalCrudDtoDecorators
|
|
394
|
+
| "queryDto"
|
|
395
|
+
| "optionsQueryDto"
|
|
396
|
+
| "pagedResponseDto"
|
|
397
|
+
| "optionDto"
|
|
398
|
+
| "optionsDto";
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Metal CRUD DTO class names.
|
|
402
|
+
*/
|
|
403
|
+
export type MetalCrudDtoClassNames = Partial<Record<MetalCrudDtoClassNameKey, string>>;
|
|
271
404
|
|
|
272
405
|
/**
|
|
273
406
|
* Options for Metal CRUD DTO classes.
|
|
274
407
|
* @extends MetalCrudDtoOptions
|
|
275
408
|
*/
|
|
276
|
-
export interface MetalCrudDtoClassOptions extends MetalCrudDtoOptions {
|
|
409
|
+
export interface MetalCrudDtoClassOptions<T = Record<string, unknown>> extends MetalCrudDtoOptions<T> {
|
|
277
410
|
/** Base name for DTO classes */
|
|
278
411
|
baseName?: string;
|
|
279
412
|
/** Custom class names */
|
|
@@ -285,7 +418,7 @@ export interface MetalCrudDtoClassOptions extends MetalCrudDtoOptions {
|
|
|
285
418
|
/**
|
|
286
419
|
* Metal CRUD DTO classes.
|
|
287
420
|
*/
|
|
288
|
-
export interface MetalCrudDtoClasses {
|
|
421
|
+
export interface MetalCrudDtoClasses<T = Record<string, unknown>> {
|
|
289
422
|
/** Response DTO class */
|
|
290
423
|
response: DtoConstructor;
|
|
291
424
|
/** Create DTO class */
|
|
@@ -296,6 +429,22 @@ export interface MetalCrudDtoClasses {
|
|
|
296
429
|
update: DtoConstructor;
|
|
297
430
|
/** Params DTO class */
|
|
298
431
|
params: DtoConstructor;
|
|
432
|
+
/** Query DTO class (paged + filters + sort) */
|
|
433
|
+
queryDto: DtoConstructor;
|
|
434
|
+
/** Options query DTO class */
|
|
435
|
+
optionsQueryDto: DtoConstructor;
|
|
436
|
+
/** Paged response DTO class for main list endpoints */
|
|
437
|
+
pagedResponseDto: DtoConstructor;
|
|
438
|
+
/** Option DTO class (value + label fields) */
|
|
439
|
+
optionDto: DtoConstructor;
|
|
440
|
+
/** Paged response DTO class for options endpoints */
|
|
441
|
+
optionsDto: DtoConstructor;
|
|
442
|
+
/** Prebuilt CRUD error decorator (400 invalid id, 404 not found) */
|
|
443
|
+
errors?: RouteErrorsDecorator;
|
|
444
|
+
/** Execution-ready filter mappings for parseFilter */
|
|
445
|
+
filterMappings: Record<string, FilterMapping<T>>;
|
|
446
|
+
/** Execution-ready sortable column mappings */
|
|
447
|
+
sortableColumns: MetalCrudSortableColumns<T>;
|
|
299
448
|
}
|
|
300
449
|
|
|
301
450
|
/**
|
|
@@ -8,8 +8,6 @@ import {
|
|
|
8
8
|
Query,
|
|
9
9
|
buildOpenApi,
|
|
10
10
|
createMetalCrudDtoClasses,
|
|
11
|
-
createPagedResponseDtoClass,
|
|
12
|
-
createPagedFilterQueryDtoClass,
|
|
13
11
|
t,
|
|
14
12
|
type RequestContext
|
|
15
13
|
} from "../../src/index";
|
|
@@ -40,29 +38,34 @@ class NotaVersao {
|
|
|
40
38
|
}
|
|
41
39
|
|
|
42
40
|
const notaVersaoCrud = createMetalCrudDtoClasses(NotaVersao, {
|
|
43
|
-
mutationExclude: ["id", "data_exclusao", "data_inativacao"]
|
|
41
|
+
mutationExclude: ["id", "data_exclusao", "data_inativacao"],
|
|
42
|
+
query: {
|
|
43
|
+
filters: {
|
|
44
|
+
sprint: { schema: t.integer({ minimum: 1 }), field: "sprint", operator: "equals" },
|
|
45
|
+
ativo: { schema: t.boolean(), field: "ativo", operator: "equals" },
|
|
46
|
+
mensagemContains: { schema: t.string({ minLength: 1 }), field: "mensagem", operator: "contains" }
|
|
47
|
+
},
|
|
48
|
+
sortableColumns: {
|
|
49
|
+
id: "id",
|
|
50
|
+
sprint: "sprint",
|
|
51
|
+
data: "data"
|
|
52
|
+
},
|
|
53
|
+
options: {
|
|
54
|
+
labelField: "mensagem"
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
errors: true
|
|
44
58
|
});
|
|
45
59
|
|
|
46
60
|
const {
|
|
47
61
|
response: NotaVersaoDto,
|
|
48
|
-
create: CreateNotaVersaoDto
|
|
62
|
+
create: CreateNotaVersaoDto,
|
|
63
|
+
queryDto: NotaVersaoQueryDtoClass,
|
|
64
|
+
pagedResponseDto: NotaVersaoPagedResponseDto,
|
|
65
|
+
filterMappings: NotaVersaoFilterMappings,
|
|
66
|
+
sortableColumns: NotaVersaoSortableColumns
|
|
49
67
|
} = notaVersaoCrud;
|
|
50
68
|
|
|
51
|
-
const NotaVersaoQueryDtoClass = createPagedFilterQueryDtoClass({
|
|
52
|
-
name: "NotaVersaoQueryDto",
|
|
53
|
-
filters: {
|
|
54
|
-
sprint: { schema: t.integer({ minimum: 1 }), operator: "equals" },
|
|
55
|
-
ativo: { schema: t.boolean(), operator: "equals" },
|
|
56
|
-
mensagemContains: { schema: t.string({ minLength: 1 }), operator: "contains" }
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
const NotaVersaoPagedResponseDto = createPagedResponseDtoClass({
|
|
61
|
-
name: "NotaVersaoPagedResponseDto",
|
|
62
|
-
itemDto: NotaVersaoDto,
|
|
63
|
-
description: "Lista paginada de notas de versão."
|
|
64
|
-
});
|
|
65
|
-
|
|
66
69
|
@Controller({ path: "/nota-versao", tags: ["Nota Versão"] })
|
|
67
70
|
class NotaVersaoController {
|
|
68
71
|
@Get("/")
|
|
@@ -81,6 +84,20 @@ class NotaVersaoController {
|
|
|
81
84
|
}
|
|
82
85
|
|
|
83
86
|
describe("e2e metal-orm CRUD DTOs to OpenAPI", () => {
|
|
87
|
+
it("provides execution-ready query metadata", () => {
|
|
88
|
+
expect(NotaVersaoFilterMappings).toEqual({
|
|
89
|
+
sprint: { field: "sprint", operator: "equals" },
|
|
90
|
+
ativo: { field: "ativo", operator: "equals" },
|
|
91
|
+
mensagemContains: { field: "mensagem", operator: "contains" },
|
|
92
|
+
search: { field: "mensagem", operator: "contains" }
|
|
93
|
+
});
|
|
94
|
+
expect(NotaVersaoSortableColumns).toEqual({
|
|
95
|
+
id: "id",
|
|
96
|
+
sprint: "sprint",
|
|
97
|
+
data: "data"
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
84
101
|
it("generates OpenAPI schemas with properties from createMetalCrudDtoClasses", () => {
|
|
85
102
|
const doc = buildOpenApi({
|
|
86
103
|
info: { title: "Test API", version: "1.0.0" },
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import {
|
|
3
|
-
createMetalCrudDtos,
|
|
4
|
-
createMetalCrudDtoClasses
|
|
5
|
-
} from "../../src/adapter/metal-orm/index";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
createMetalCrudDtos,
|
|
4
|
+
createMetalCrudDtoClasses
|
|
5
|
+
} from "../../src/adapter/metal-orm/index";
|
|
6
|
+
import type { FilterMapping } from "../../src/adapter/metal-orm/index";
|
|
7
|
+
import { getDtoMeta } from "../../src/core/metadata";
|
|
8
|
+
import { t } from "../../src/core/schema";
|
|
9
|
+
import { Alphanumeric, Column, Email, Entity, Length, Pattern, PrimaryKey, col } from "metal-orm";
|
|
8
10
|
|
|
9
11
|
describe("createMetalCrudDtos", () => {
|
|
10
12
|
@Entity({ tableName: "crud_dto_entities" })
|
|
@@ -97,20 +99,34 @@ describe("createMetalCrudDtoClasses", () => {
|
|
|
97
99
|
nickname?: string | null;
|
|
98
100
|
}
|
|
99
101
|
|
|
100
|
-
it("builds ready-to-export DTO classes", () => {
|
|
101
|
-
const classes = createMetalCrudDtoClasses(CrudDtoClassEntity, {
|
|
102
|
-
mutationExclude: ["id"]
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
const responseMeta = getDtoMeta(classes.response);
|
|
106
|
-
const createMeta = getDtoMeta(classes.create);
|
|
107
|
-
const paramsMeta = getDtoMeta(classes.params);
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
expect(
|
|
112
|
-
expect(
|
|
113
|
-
|
|
102
|
+
it("builds ready-to-export DTO classes", () => {
|
|
103
|
+
const classes = createMetalCrudDtoClasses(CrudDtoClassEntity, {
|
|
104
|
+
mutationExclude: ["id"]
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const responseMeta = getDtoMeta(classes.response);
|
|
108
|
+
const createMeta = getDtoMeta(classes.create);
|
|
109
|
+
const paramsMeta = getDtoMeta(classes.params);
|
|
110
|
+
const pagedResponseMeta = getDtoMeta(classes.pagedResponseDto);
|
|
111
|
+
const optionsMeta = getDtoMeta(classes.optionsDto);
|
|
112
|
+
|
|
113
|
+
expect(classes.response.name).toBe("CrudDtoClassEntityDto");
|
|
114
|
+
expect(classes.queryDto.name).toBe("CrudDtoClassEntityQueryDto");
|
|
115
|
+
expect(classes.optionsQueryDto.name).toBe("CrudDtoClassEntityOptionsQueryDto");
|
|
116
|
+
expect(classes.optionDto.name).toBe("CrudDtoClassEntityOptionDto");
|
|
117
|
+
expect(pagedResponseMeta?.name).toBe("CrudDtoClassEntityPagedResponseDto");
|
|
118
|
+
expect(optionsMeta?.name).toBe("CrudDtoClassEntityOptionsDto");
|
|
119
|
+
expect(responseMeta?.fields.id).toBeDefined();
|
|
120
|
+
expect(createMeta?.fields.id).toBeUndefined();
|
|
121
|
+
expect(paramsMeta?.fields).toEqual({ id: expect.any(Object) });
|
|
122
|
+
expect(classes.filterMappings).toEqual({
|
|
123
|
+
search: {
|
|
124
|
+
field: "nome",
|
|
125
|
+
operator: "contains"
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
expect(classes.sortableColumns).toEqual({});
|
|
129
|
+
});
|
|
114
130
|
|
|
115
131
|
it("applies custom name overrides", () => {
|
|
116
132
|
const classes = createMetalCrudDtoClasses(CrudDtoClassEntity, {
|
|
@@ -121,8 +137,64 @@ describe("createMetalCrudDtoClasses", () => {
|
|
|
121
137
|
}
|
|
122
138
|
});
|
|
123
139
|
|
|
124
|
-
expect(classes.response.name).toBe("PersonDto");
|
|
125
|
-
expect(classes.params.name).toBe("PersonIdDto");
|
|
126
|
-
expect(classes.create.name).toBe("CreatePersonDto");
|
|
127
|
-
});
|
|
128
|
-
|
|
140
|
+
expect(classes.response.name).toBe("PersonDto");
|
|
141
|
+
expect(classes.params.name).toBe("PersonIdDto");
|
|
142
|
+
expect(classes.create.name).toBe("CreatePersonDto");
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("generates query/options artifacts and execution-ready metadata from one config", () => {
|
|
146
|
+
const classes = createMetalCrudDtoClasses(CrudDtoClassEntity, {
|
|
147
|
+
query: {
|
|
148
|
+
filters: {
|
|
149
|
+
nameContains: {
|
|
150
|
+
schema: t.string({ minLength: 1 }),
|
|
151
|
+
field: "name",
|
|
152
|
+
operator: "contains"
|
|
153
|
+
},
|
|
154
|
+
nickname: {
|
|
155
|
+
schema: t.string({ minLength: 1 }),
|
|
156
|
+
field: "nickname"
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
sortableColumns: {
|
|
160
|
+
name: "name",
|
|
161
|
+
nickname: "nickname"
|
|
162
|
+
},
|
|
163
|
+
options: {
|
|
164
|
+
labelField: "name",
|
|
165
|
+
searchKey: "labelContains"
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
errors: true
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
const queryMeta = getDtoMeta(classes.queryDto);
|
|
172
|
+
const optionsQueryMeta = getDtoMeta(classes.optionsQueryDto);
|
|
173
|
+
const optionMeta = getDtoMeta(classes.optionDto);
|
|
174
|
+
|
|
175
|
+
expect(queryMeta?.fields.page).toBeDefined();
|
|
176
|
+
expect(queryMeta?.fields.pageSize).toBeDefined();
|
|
177
|
+
expect(queryMeta?.fields.nameContains).toBeDefined();
|
|
178
|
+
expect(queryMeta?.fields.nickname).toBeDefined();
|
|
179
|
+
expect(queryMeta?.fields.sortBy).toBeDefined();
|
|
180
|
+
expect(queryMeta?.fields.sortDirection).toBeDefined();
|
|
181
|
+
|
|
182
|
+
expect(optionsQueryMeta?.fields.labelContains).toBeDefined();
|
|
183
|
+
expect(optionMeta?.fields.id).toBeDefined();
|
|
184
|
+
expect(optionMeta?.fields.name).toBeDefined();
|
|
185
|
+
|
|
186
|
+
expect(classes.filterMappings).toEqual({
|
|
187
|
+
nameContains: { field: "name", operator: "contains" },
|
|
188
|
+
nickname: { field: "nickname", operator: "equals" },
|
|
189
|
+
labelContains: { field: "name", operator: "contains" }
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
const typedMappings: Record<string, FilterMapping<CrudDtoClassEntity>> = classes.filterMappings;
|
|
193
|
+
expect(typedMappings.nameContains.field).toBe("name");
|
|
194
|
+
expect(classes.sortableColumns).toEqual({
|
|
195
|
+
name: "name",
|
|
196
|
+
nickname: "nickname"
|
|
197
|
+
});
|
|
198
|
+
expect(typeof classes.errors).toBe("function");
|
|
199
|
+
});
|
|
200
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
2
|
import { parseFilter } from "../../src/adapter/metal-orm/index";
|
|
3
|
-
import type { FilterMapping } from "../../src/adapter/metal-orm/
|
|
3
|
+
import type { FilterMapping } from "../../src/adapter/metal-orm/index";
|
|
4
4
|
|
|
5
5
|
describe("parseFilter", () => {
|
|
6
6
|
it("returns undefined when query is undefined", () => {
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { parseSort } from "../../src/adapter/metal-orm/index";
|
|
3
|
+
|
|
4
|
+
describe("parseSort", () => {
|
|
5
|
+
it("returns undefined when sortBy is missing", () => {
|
|
6
|
+
const result = parseSort({
|
|
7
|
+
query: { page: 1 },
|
|
8
|
+
sortableColumns: { name: "name" }
|
|
9
|
+
});
|
|
10
|
+
expect(result).toBeUndefined();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("returns undefined when sortBy is not allowed", () => {
|
|
14
|
+
const result = parseSort({
|
|
15
|
+
query: { sortBy: "email" },
|
|
16
|
+
sortableColumns: { name: "name" }
|
|
17
|
+
});
|
|
18
|
+
expect(result).toBeUndefined();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("parses valid sort and resolves mapped field", () => {
|
|
22
|
+
const result = parseSort({
|
|
23
|
+
query: { sortBy: "userName", sortDirection: "desc" },
|
|
24
|
+
sortableColumns: { userName: "name" }
|
|
25
|
+
});
|
|
26
|
+
expect(result).toEqual({
|
|
27
|
+
sortBy: "userName",
|
|
28
|
+
sortDirection: "desc",
|
|
29
|
+
field: "name"
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("supports custom sort keys and defaults", () => {
|
|
34
|
+
const result = parseSort({
|
|
35
|
+
query: { direction: "desc" },
|
|
36
|
+
sortableColumns: { createdAt: "createdAt" },
|
|
37
|
+
sortByKey: "orderBy",
|
|
38
|
+
sortDirectionKey: "direction",
|
|
39
|
+
defaultSortBy: "createdAt",
|
|
40
|
+
defaultSortDirection: "asc"
|
|
41
|
+
});
|
|
42
|
+
expect(result).toEqual({
|
|
43
|
+
sortBy: "createdAt",
|
|
44
|
+
sortDirection: "desc",
|
|
45
|
+
field: "createdAt"
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
});
|