@postxl/generators 1.4.1 → 1.5.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/dist/backend-rest-api/generators/model-controller.generator.js +109 -11
- package/dist/backend-rest-api/generators/model-controller.generator.js.map +1 -1
- package/dist/backend-rest-api/rest-api.generator.js +4 -0
- package/dist/backend-rest-api/rest-api.generator.js.map +1 -1
- package/dist/backend-rest-api/template/utils/src/fieldSelection.spec.ts +252 -0
- package/dist/backend-rest-api/template/utils/src/fieldSelection.ts +66 -0
- package/dist/backend-router-trpc/generators/model-routes.generator.js +27 -2
- package/dist/backend-router-trpc/generators/model-routes.generator.js.map +1 -1
- package/dist/backend-router-trpc/router-trpc.generator.d.ts +1 -0
- package/dist/backend-router-trpc/router-trpc.generator.js +1 -0
- package/dist/backend-router-trpc/router-trpc.generator.js.map +1 -1
- package/dist/backend-update/model-update-service.generator.js +145 -8
- package/dist/backend-update/model-update-service.generator.js.map +1 -1
- package/dist/backend-view/model-view-service.generator.js +276 -19
- package/dist/backend-view/model-view-service.generator.js.map +1 -1
- package/dist/backend-view/template/{filter.utils.test.ts → query.utils.test.ts} +101 -1
- package/dist/backend-view/template/query.utils.ts +444 -0
- package/dist/decoders/excel-field-decoder.helper.js +5 -2
- package/dist/decoders/excel-field-decoder.helper.js.map +1 -1
- package/dist/frontend-admin/generators/model-admin-page.generator.js +50 -32
- package/dist/frontend-admin/generators/model-admin-page.generator.js.map +1 -1
- package/dist/frontend-core/template/src/components/admin/column-header-state-icon.tsx +72 -0
- package/dist/frontend-core/template/src/components/admin/table-filter.tsx +195 -43
- package/dist/frontend-tables/generators/model-table.generator.js +108 -29
- package/dist/frontend-tables/generators/model-table.generator.js.map +1 -1
- package/dist/frontend-trpc-client/generators/model-hook.generator.js +130 -16
- package/dist/frontend-trpc-client/generators/model-hook.generator.js.map +1 -1
- package/dist/types/generators/model-type.generator.js +97 -31
- package/dist/types/generators/model-type.generator.js.map +1 -1
- package/dist/types/template/query.types.ts +151 -0
- package/dist/types/types.generator.d.ts +15 -0
- package/dist/types/types.generator.js +5 -3
- package/dist/types/types.generator.js.map +1 -1
- package/package.json +2 -2
- package/dist/backend-rest-api/generators/zod-exception-filter.generator.d.ts +0 -1
- package/dist/backend-rest-api/generators/zod-exception-filter.generator.js +0 -28
- package/dist/backend-rest-api/generators/zod-exception-filter.generator.js.map +0 -1
- package/dist/backend-view/template/filter.utils.ts +0 -218
- package/dist/frontend-core/template/src/components/admin/table-filter-header-icon.tsx +0 -30
- package/dist/types/template/filter.types.ts +0 -70
|
@@ -40,26 +40,109 @@ function generateModelController({ model, context, }) {
|
|
|
40
40
|
.add(model.types)
|
|
41
41
|
.add(model.types.id.decoderBase)
|
|
42
42
|
.add(model.types.id.toBranded)
|
|
43
|
+
.add(model.decoder.decoder.zodDecoder)
|
|
43
44
|
.add(context.view.service);
|
|
44
45
|
const { PascalCase, PascalCasePlural, camelCase } = model._conjugated;
|
|
46
|
+
// Find all Date fields that need to be overridden for JSON Schema compatibility
|
|
47
|
+
const dateFields = Array.from(model.fields.values()).filter((field) => field.kind === 'scalar' && field.type === 'Date');
|
|
48
|
+
// Find all relation fields (foreign keys) that have transforms
|
|
49
|
+
const relationFields = Array.from(model.fields.values()).filter((field) => field.kind === 'relation');
|
|
50
|
+
// Find all enum fields that have transforms
|
|
51
|
+
const enumFields = Array.from(model.fields.values()).filter((field) => field.kind === 'enum');
|
|
52
|
+
// Find all discriminated union fields (they contain transforms internally)
|
|
53
|
+
const discriminatedUnionFields = Array.from(model.fields.values()).filter((field) => field.kind === 'discriminatedUnion');
|
|
54
|
+
// Fields to omit: id (has transform) + Date fields + relation fields + enum fields + discriminated unions
|
|
55
|
+
const omitFields = [
|
|
56
|
+
'id',
|
|
57
|
+
...dateFields.map((f) => f.name),
|
|
58
|
+
...relationFields.map((f) => f.name),
|
|
59
|
+
...enumFields.map((f) => f.name),
|
|
60
|
+
...discriminatedUnionFields.map((f) => f.name),
|
|
61
|
+
];
|
|
62
|
+
const omitObject = omitFields.map((f) => `${f}: true`).join(', ');
|
|
63
|
+
// Build extend fields for JSON Schema compatible replacements
|
|
64
|
+
const extendFieldsList = [];
|
|
65
|
+
// Add id field
|
|
66
|
+
extendFieldsList.push(`id: ${model.types.id.decoderBase.name}`);
|
|
67
|
+
// Add date fields
|
|
68
|
+
for (const f of dateFields) {
|
|
69
|
+
extendFieldsList.push(`${f.name}: z.iso.datetime()${f.isRequired ? '' : '.nullable()'}`);
|
|
70
|
+
}
|
|
71
|
+
// Add relation fields (use the base decoder without transform)
|
|
72
|
+
for (const f of relationFields) {
|
|
73
|
+
const referencedModel = context.models.get(f.referencedModelName);
|
|
74
|
+
imports.add(referencedModel.types.id.decoderBase);
|
|
75
|
+
extendFieldsList.push(`${f.name}: ${referencedModel.types.id.decoderBase.name}${f.isRequired ? '' : '.nullable()'}`);
|
|
76
|
+
}
|
|
77
|
+
// Add enum fields (use the base enum decoder without transform)
|
|
78
|
+
for (const f of enumFields) {
|
|
79
|
+
const enum_ = context.enums.get(f.enumName);
|
|
80
|
+
imports.add(enum_.decoder.decoder.zodDecoder);
|
|
81
|
+
extendFieldsList.push(`${f.name}: ${enum_.decoder.decoder.zodDecoder.name}${f.isRequired ? '' : '.nullable()'}`);
|
|
82
|
+
}
|
|
83
|
+
const extendObject = extendFieldsList.join(',\n ');
|
|
84
|
+
// Generate transform function for Date fields (relation and enum fields don't need transformation)
|
|
85
|
+
const transformFields = dateFields
|
|
86
|
+
.map((f) => `${f.name}: item.${f.name}${f.isRequired ? '' : '?'}.toISOString()${f.isRequired ? '' : ' ?? null'}`)
|
|
87
|
+
.join(',\n ');
|
|
88
|
+
// Generate comment about omitted discriminated union fields
|
|
89
|
+
const duComment = discriminatedUnionFields.length > 0
|
|
90
|
+
? `\n// Note: Discriminated union fields omitted from REST API: ${discriminatedUnionFields.map((f) => f.name).join(', ')}`
|
|
91
|
+
: '';
|
|
45
92
|
return /* ts */ `
|
|
46
93
|
import '@fastify/cookie'
|
|
47
94
|
import { Controller, Get, Logger, Param, Query } from '@nestjs/common'
|
|
48
|
-
import { createZodDto } from 'nestjs-zod'
|
|
95
|
+
import { createZodDto, ZodResponse } from 'nestjs-zod'
|
|
49
96
|
${imports.generate()}
|
|
50
97
|
import z from 'zod'
|
|
51
98
|
|
|
52
|
-
import {
|
|
53
|
-
import
|
|
99
|
+
import { createFieldsDecoder, selectFields } from '@utils/fieldSelection'
|
|
100
|
+
import { paginate, paginatedResultDecoder, paginationDecoderFromQuery } from '@utils/pagination'
|
|
101
|
+
|
|
102
|
+
// REST API decoder safe for JSON Schema / OpenAPI generation
|
|
103
|
+
// Reuses ${model.decoder.decoder.zodDecoder.name} but overrides fields with transforms or non-JSON-representable types${duComment}
|
|
104
|
+
const z${PascalCase}RestApiDecoder = ${model.decoder.decoder.zodDecoder.name}.omit({ ${omitObject} }).extend({
|
|
105
|
+
${extendObject}
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
type ${PascalCase}RestApi = z.infer<typeof z${PascalCase}RestApiDecoder>
|
|
109
|
+
|
|
110
|
+
const ${camelCase}FieldsDecoder = createFieldsDecoder(z${PascalCase}RestApiDecoder)
|
|
111
|
+
type ${PascalCase}Fields = z.infer<typeof ${camelCase}FieldsDecoder>
|
|
112
|
+
|
|
113
|
+
const to${PascalCase}RestApi = (item: ${model.types.name}, fields: ${PascalCase}Fields): Partial<${PascalCase}RestApi> => {
|
|
114
|
+
const transformed = {
|
|
115
|
+
...item,
|
|
116
|
+
${transformFields}
|
|
117
|
+
}
|
|
118
|
+
return selectFields(transformed, fields)
|
|
119
|
+
}
|
|
54
120
|
|
|
55
|
-
const get${PascalCase}
|
|
121
|
+
const get${PascalCase}ParamsDecoder = z.object({
|
|
56
122
|
id: ${model.types.id.decoderBase.name},
|
|
57
123
|
})
|
|
58
124
|
|
|
59
|
-
const get${
|
|
125
|
+
const get${PascalCase}QueryDecoder = z.object({
|
|
126
|
+
fields: ${camelCase}FieldsDecoder,
|
|
127
|
+
})
|
|
60
128
|
|
|
61
|
-
|
|
129
|
+
const get${PascalCase}ResponseDecoder = z.object({
|
|
130
|
+
data: z${PascalCase}RestApiDecoder.partial().nullable(),
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
const get${PascalCasePlural}Decoder = paginationDecoderFromQuery.extend({
|
|
134
|
+
fields: ${camelCase}FieldsDecoder,
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
const get${PascalCasePlural}ResponseDecoder = paginatedResultDecoder(z${PascalCase}RestApiDecoder.partial())
|
|
138
|
+
|
|
139
|
+
class Get${PascalCase}ParamsDto extends createZodDto(get${PascalCase}ParamsDecoder) {}
|
|
140
|
+
class Get${PascalCase}QueryDto extends createZodDto(get${PascalCase}QueryDecoder) {}
|
|
141
|
+
class Get${PascalCase}ResponseDto extends createZodDto(get${PascalCase}ResponseDecoder) {}
|
|
142
|
+
type Get${PascalCase}Response = z.infer<typeof get${PascalCase}ResponseDecoder>
|
|
62
143
|
class Get${PascalCasePlural}Dto extends createZodDto(get${PascalCasePlural}Decoder) {}
|
|
144
|
+
class Get${PascalCasePlural}ResponseDto extends createZodDto(get${PascalCasePlural}ResponseDecoder) {}
|
|
145
|
+
type Get${PascalCasePlural}Response = z.infer<typeof get${PascalCasePlural}ResponseDecoder>
|
|
63
146
|
|
|
64
147
|
@Controller('${camelCase}')
|
|
65
148
|
export class ${model.restApi.controller.name} {
|
|
@@ -68,20 +151,35 @@ export class ${model.restApi.controller.name} {
|
|
|
68
151
|
constructor(private readonly viewService: ${context.view.service.name}) {}
|
|
69
152
|
|
|
70
153
|
/**
|
|
71
|
-
* Retrieve a ${model.userFriendlyName} by
|
|
154
|
+
* Retrieve a ${model.userFriendlyName} by Id.
|
|
72
155
|
*/
|
|
156
|
+
@ZodResponse({ type: Get${PascalCase}ResponseDto })
|
|
73
157
|
@Get(':id')
|
|
74
|
-
async get${PascalCase}(
|
|
75
|
-
|
|
158
|
+
async get${PascalCase}(
|
|
159
|
+
@Param() params: Get${PascalCase}ParamsDto,
|
|
160
|
+
@Query() query: Get${PascalCase}QueryDto,
|
|
161
|
+
): Promise<Get${PascalCase}Response> {
|
|
162
|
+
const data = await this.viewService.${model.view.service.variableName}.get({
|
|
163
|
+
id: ${model.types.id.toBranded.name}(params.id),
|
|
164
|
+
user: this.viewService.users.data.rootUser,
|
|
165
|
+
})
|
|
166
|
+
return {
|
|
167
|
+
data: data ? to${PascalCase}RestApi(data, query.fields) : null,
|
|
168
|
+
}
|
|
76
169
|
}
|
|
77
170
|
|
|
78
171
|
/**
|
|
79
172
|
* Retrieve all ${model.userFriendlyNamePlural}.
|
|
80
173
|
*/
|
|
174
|
+
@ZodResponse({ type: Get${PascalCasePlural}ResponseDto })
|
|
81
175
|
@Get('')
|
|
82
|
-
async get${PascalCasePlural}(@Query() query: Get${PascalCasePlural}Dto): Promise<
|
|
176
|
+
async get${PascalCasePlural}(@Query() query: Get${PascalCasePlural}Dto): Promise<Get${PascalCasePlural}Response> {
|
|
83
177
|
const data = await this.viewService.${model.view.service.variableName}.getList(this.viewService.users.data.rootUser)
|
|
84
|
-
|
|
178
|
+
const paginated = paginate(data, query)
|
|
179
|
+
return {
|
|
180
|
+
...paginated,
|
|
181
|
+
data: paginated.data.map((item) => to${PascalCase}RestApi(item, query.fields)),
|
|
182
|
+
}
|
|
85
183
|
}
|
|
86
184
|
}
|
|
87
185
|
`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model-controller.generator.js","sourceRoot":"","sources":["../../../src/backend-rest-api/generators/model-controller.generator.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAIA,
|
|
1
|
+
{"version":3,"file":"model-controller.generator.js","sourceRoot":"","sources":["../../../src/backend-rest-api/generators/model-controller.generator.ts"],"names":[],"mappings":"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"}
|
|
@@ -98,6 +98,10 @@ import { ZodExceptionFilter } from '@restApi/zod-exception.filter'
|
|
|
98
98
|
diskPath: path.resolve(__dirname, './template/zod-exception.filter.ts'),
|
|
99
99
|
targetPath: 'libs/restApi/src/zod-exception.filter.ts',
|
|
100
100
|
});
|
|
101
|
+
await vfs.loadFile({
|
|
102
|
+
diskPath: path.resolve(__dirname, './template/utils/src/fieldSelection.ts'),
|
|
103
|
+
targetPath: 'libs/utils/src/fieldSelection.ts',
|
|
104
|
+
});
|
|
101
105
|
context.vfs.insertFromVfs({ targetPath: '/backend', vfs });
|
|
102
106
|
return context;
|
|
103
107
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rest-api.generator.js","sourceRoot":"","sources":["../../src/backend-rest-api/rest-api.generator.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"rest-api.generator.js","sourceRoot":"","sources":["../../src/backend-rest-api/rest-api.generator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
import { createFieldsDecoder, FieldSelection, selectFields } from './fieldSelection'
|
|
4
|
+
import { describe, expect, it } from '@jest/globals'
|
|
5
|
+
import { fail } from 'node:assert'
|
|
6
|
+
|
|
7
|
+
describe('fieldSelection', () => {
|
|
8
|
+
const testSchema = z.object({
|
|
9
|
+
id: z.string(),
|
|
10
|
+
name: z.string(),
|
|
11
|
+
email: z.string(),
|
|
12
|
+
age: z.number(),
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
const fieldsDecoder = createFieldsDecoder(testSchema)
|
|
16
|
+
|
|
17
|
+
describe('createFieldsDecoder', () => {
|
|
18
|
+
describe('valid selections', () => {
|
|
19
|
+
it('should parse a single field', () => {
|
|
20
|
+
const result = fieldsDecoder.parse('id')
|
|
21
|
+
expect(result).toEqual(['id'])
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('should parse multiple comma-separated fields', () => {
|
|
25
|
+
const result = fieldsDecoder.parse('id,name,email')
|
|
26
|
+
expect(result).toEqual(['id', 'name', 'email'])
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('should parse all fields from schema', () => {
|
|
30
|
+
const result = fieldsDecoder.parse('id,name,email,age')
|
|
31
|
+
expect(result).toEqual(['id', 'name', 'email', 'age'])
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('should handle fields with surrounding whitespace', () => {
|
|
35
|
+
const result = fieldsDecoder.parse(' id , name , email ')
|
|
36
|
+
expect(result).toEqual(['id', 'name', 'email'])
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('should handle fields with mixed whitespace', () => {
|
|
40
|
+
const result = fieldsDecoder.parse('id, name,email , age')
|
|
41
|
+
expect(result).toEqual(['id', 'name', 'email', 'age'])
|
|
42
|
+
})
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
describe('empty/whitespace handling', () => {
|
|
46
|
+
it('should return undefined for undefined input', () => {
|
|
47
|
+
const result = fieldsDecoder.parse(undefined)
|
|
48
|
+
expect(result).toBeUndefined()
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('should return undefined for empty string', () => {
|
|
52
|
+
const result = fieldsDecoder.parse('')
|
|
53
|
+
expect(result).toBeUndefined()
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('should return undefined for whitespace-only string', () => {
|
|
57
|
+
const result = fieldsDecoder.parse(' ')
|
|
58
|
+
expect(result).toBeUndefined()
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it('should filter out empty fields from comma-separated list', () => {
|
|
62
|
+
const result = fieldsDecoder.parse('id,,name,,,email')
|
|
63
|
+
expect(result).toEqual(['id', 'name', 'email'])
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('should filter out whitespace-only fields', () => {
|
|
67
|
+
const result = fieldsDecoder.parse('id, ,name')
|
|
68
|
+
expect(result).toEqual(['id', 'name'])
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('should handle trailing comma', () => {
|
|
72
|
+
const result = fieldsDecoder.parse('id,name,')
|
|
73
|
+
expect(result).toEqual(['id', 'name'])
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('should handle leading comma', () => {
|
|
77
|
+
const result = fieldsDecoder.parse(',id,name')
|
|
78
|
+
expect(result).toEqual(['id', 'name'])
|
|
79
|
+
})
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
describe('invalid fields', () => {
|
|
83
|
+
it('should throw error for invalid field name', () => {
|
|
84
|
+
expect(() => fieldsDecoder.parse('invalid')).toThrow()
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it('should throw error when one field is invalid among valid ones', () => {
|
|
88
|
+
expect(() => fieldsDecoder.parse('id,invalid,name')).toThrow()
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('should include valid fields in error message', () => {
|
|
92
|
+
try {
|
|
93
|
+
fieldsDecoder.parse('invalid')
|
|
94
|
+
fail('Expected to throw')
|
|
95
|
+
} catch (error) {
|
|
96
|
+
expect(error).toBeInstanceOf(z.ZodError)
|
|
97
|
+
const zodError = error as z.ZodError
|
|
98
|
+
expect(zodError.issues[0].message).toContain('Invalid field "invalid"')
|
|
99
|
+
expect(zodError.issues[0].message).toContain('id')
|
|
100
|
+
expect(zodError.issues[0].message).toContain('name')
|
|
101
|
+
expect(zodError.issues[0].message).toContain('email')
|
|
102
|
+
expect(zodError.issues[0].message).toContain('age')
|
|
103
|
+
}
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('should reject field names with typos', () => {
|
|
107
|
+
expect(() => fieldsDecoder.parse('Id')).toThrow() // case sensitive
|
|
108
|
+
expect(() => fieldsDecoder.parse('ID')).toThrow()
|
|
109
|
+
expect(() => fieldsDecoder.parse('naem')).toThrow()
|
|
110
|
+
})
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
describe('edge cases', () => {
|
|
114
|
+
it('should handle schema with single field', () => {
|
|
115
|
+
const singleFieldSchema = z.object({ onlyField: z.string() })
|
|
116
|
+
const decoder = createFieldsDecoder(singleFieldSchema)
|
|
117
|
+
|
|
118
|
+
expect(decoder.parse('onlyField')).toEqual(['onlyField'])
|
|
119
|
+
expect(() => decoder.parse('other')).toThrow()
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
it('should handle duplicate fields in input', () => {
|
|
123
|
+
const result = fieldsDecoder.parse('id,id,name,name')
|
|
124
|
+
expect(result).toEqual(['id', 'id', 'name', 'name'])
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
it('should preserve field order from input', () => {
|
|
128
|
+
const result = fieldsDecoder.parse('email,id,age,name')
|
|
129
|
+
expect(result).toEqual(['email', 'id', 'age', 'name'])
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
it('should work with nested schema (top-level keys only)', () => {
|
|
133
|
+
const nestedSchema = z.object({
|
|
134
|
+
id: z.string(),
|
|
135
|
+
nested: z.object({ inner: z.string() }),
|
|
136
|
+
})
|
|
137
|
+
const decoder = createFieldsDecoder(nestedSchema)
|
|
138
|
+
|
|
139
|
+
expect(decoder.parse('id,nested')).toEqual(['id', 'nested'])
|
|
140
|
+
expect(() => decoder.parse('inner')).toThrow()
|
|
141
|
+
})
|
|
142
|
+
})
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
describe('selectFields', () => {
|
|
146
|
+
const testData = {
|
|
147
|
+
id: '123',
|
|
148
|
+
name: 'Test User',
|
|
149
|
+
email: 'test@example.com',
|
|
150
|
+
age: 25,
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
describe('with field selection', () => {
|
|
154
|
+
it('should select single field', () => {
|
|
155
|
+
const result = selectFields(testData, ['id'] as FieldSelection<typeof testData>)
|
|
156
|
+
expect(result).toEqual({ id: '123' })
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
it('should select multiple fields', () => {
|
|
160
|
+
const result = selectFields(testData, ['id', 'name'] as FieldSelection<typeof testData>)
|
|
161
|
+
expect(result).toEqual({ id: '123', name: 'Test User' })
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
it('should select all fields when all specified', () => {
|
|
165
|
+
const result = selectFields(testData, ['id', 'name', 'email', 'age'] as FieldSelection<typeof testData>)
|
|
166
|
+
expect(result).toEqual(testData)
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
it('should preserve field values exactly', () => {
|
|
170
|
+
const dataWithSpecialValues = {
|
|
171
|
+
id: '',
|
|
172
|
+
name: null as unknown as string,
|
|
173
|
+
count: 0,
|
|
174
|
+
flag: false,
|
|
175
|
+
}
|
|
176
|
+
const result = selectFields(dataWithSpecialValues, ['id', 'name', 'count', 'flag'] as FieldSelection<
|
|
177
|
+
typeof dataWithSpecialValues
|
|
178
|
+
>)
|
|
179
|
+
expect(result).toEqual(dataWithSpecialValues)
|
|
180
|
+
})
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
describe('without field selection (returns full object)', () => {
|
|
184
|
+
it('should return full object when fields is undefined', () => {
|
|
185
|
+
const result = selectFields(testData, undefined)
|
|
186
|
+
expect(result).toEqual(testData)
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
it('should return full object when fields is empty array', () => {
|
|
190
|
+
const result = selectFields(testData, [] as FieldSelection<typeof testData>)
|
|
191
|
+
expect(result).toEqual(testData)
|
|
192
|
+
})
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
describe('edge cases', () => {
|
|
196
|
+
it('should handle empty object', () => {
|
|
197
|
+
const emptyData = {} as Record<string, unknown>
|
|
198
|
+
const result = selectFields(emptyData, ['nonexistent'] as FieldSelection<typeof emptyData>)
|
|
199
|
+
expect(result).toEqual({})
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
it('should ignore fields not present in data', () => {
|
|
203
|
+
const partialData = { id: '123', name: 'Test' } as Record<string, unknown>
|
|
204
|
+
const result = selectFields(partialData, ['id', 'name', 'missing'] as FieldSelection<typeof partialData>)
|
|
205
|
+
expect(result).toEqual({ id: '123', name: 'Test' })
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
it('should handle data with undefined values', () => {
|
|
209
|
+
const dataWithUndefined = { id: '123', name: undefined }
|
|
210
|
+
const result = selectFields(dataWithUndefined, ['id', 'name'] as FieldSelection<typeof dataWithUndefined>)
|
|
211
|
+
expect(result).toEqual({ id: '123', name: undefined })
|
|
212
|
+
expect('name' in result).toBe(true)
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
it('should not modify original object', () => {
|
|
216
|
+
const original = { id: '123', name: 'Test', email: 'test@example.com' }
|
|
217
|
+
const originalCopy = { ...original }
|
|
218
|
+
selectFields(original, ['id'] as FieldSelection<typeof original>)
|
|
219
|
+
expect(original).toEqual(originalCopy)
|
|
220
|
+
})
|
|
221
|
+
})
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
describe('integration: createFieldsDecoder + selectFields', () => {
|
|
225
|
+
it('should work together for typical use case', () => {
|
|
226
|
+
const data = { id: '123', name: 'Test', email: 'test@example.com', age: 25 }
|
|
227
|
+
const fields = fieldsDecoder.parse('id,name')
|
|
228
|
+
const result = selectFields(data, fields)
|
|
229
|
+
expect(result).toEqual({ id: '123', name: 'Test' })
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
it('should return full object when no fields specified', () => {
|
|
233
|
+
const data = { id: '123', name: 'Test', email: 'test@example.com', age: 25 }
|
|
234
|
+
const fields = fieldsDecoder.parse(undefined)
|
|
235
|
+
const result = selectFields(data, fields)
|
|
236
|
+
expect(result).toEqual(data)
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
it('should work with array of objects', () => {
|
|
240
|
+
const dataArray = [
|
|
241
|
+
{ id: '1', name: 'First', email: 'first@example.com', age: 20 },
|
|
242
|
+
{ id: '2', name: 'Second', email: 'second@example.com', age: 30 },
|
|
243
|
+
]
|
|
244
|
+
const fields = fieldsDecoder.parse('id,email')
|
|
245
|
+
const results = dataArray.map((item) => selectFields(item, fields))
|
|
246
|
+
expect(results).toEqual([
|
|
247
|
+
{ id: '1', email: 'first@example.com' },
|
|
248
|
+
{ id: '2', email: 'second@example.com' },
|
|
249
|
+
])
|
|
250
|
+
})
|
|
251
|
+
})
|
|
252
|
+
})
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Creates a Zod decoder for parsing a comma-separated fields query parameter.
|
|
5
|
+
* Only allows field names that exist in the provided schema.
|
|
6
|
+
*
|
|
7
|
+
* @param schema - A Zod object schema to extract valid field names from
|
|
8
|
+
* @returns A Zod decoder that validates and parses the fields parameter
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* const mySchema = z.object({ id: z.string(), name: z.string() })
|
|
12
|
+
* const fieldsDecoder = createFieldsDecoder(mySchema)
|
|
13
|
+
* // ?fields=id,name -> ['id', 'name']
|
|
14
|
+
* // ?fields=id,invalid -> throws validation error
|
|
15
|
+
*/
|
|
16
|
+
export function createFieldsDecoder<T extends z.ZodRawShape>(schema: z.ZodObject<T>) {
|
|
17
|
+
const validFields = Object.keys(schema.shape) as (keyof T)[]
|
|
18
|
+
|
|
19
|
+
return z
|
|
20
|
+
.string()
|
|
21
|
+
.transform((value, ctx) => {
|
|
22
|
+
if (!value || value.trim() === '') {
|
|
23
|
+
return undefined
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const fields = value
|
|
27
|
+
.split(',')
|
|
28
|
+
.map((f) => f.trim())
|
|
29
|
+
.filter((f) => f.length > 0)
|
|
30
|
+
|
|
31
|
+
// Validate that all fields are keys of the schema
|
|
32
|
+
for (const field of fields) {
|
|
33
|
+
if (!validFields.includes(field as keyof T)) {
|
|
34
|
+
ctx.addIssue({
|
|
35
|
+
code: 'custom',
|
|
36
|
+
message: `Invalid field "${field}". Valid fields are: ${validFields.join(', ')}`,
|
|
37
|
+
})
|
|
38
|
+
return z.NEVER
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return fields as (keyof T & string)[]
|
|
43
|
+
})
|
|
44
|
+
.optional()
|
|
45
|
+
.describe('Comma-separated list of fields to include in the response')
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export type FieldSelection<T> = (keyof T & string)[] | undefined
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Picks only the specified fields from an object.
|
|
52
|
+
* If no fields are specified, returns the original object.
|
|
53
|
+
*/
|
|
54
|
+
export function selectFields<T extends Record<string, unknown>>(data: T, fields: FieldSelection<T>): Partial<T> {
|
|
55
|
+
if (!fields || fields.length === 0) {
|
|
56
|
+
return data
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const result: Partial<T> = {}
|
|
60
|
+
for (const field of fields) {
|
|
61
|
+
if (field in data) {
|
|
62
|
+
result[field as keyof T] = data[field as keyof T]
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return result
|
|
66
|
+
}
|
|
@@ -43,6 +43,7 @@ function generateModelRoutes({ model, context }) {
|
|
|
43
43
|
.add(model.types.id.decoder)
|
|
44
44
|
.add(model.types.filter.decoder)
|
|
45
45
|
.add(model.types.filter.field)
|
|
46
|
+
.add(model.types.sort.decoder)
|
|
46
47
|
.add({ name: context.trpcRouter._middleware.auth.name, location: context.trpcRouter._middleware._filePath })
|
|
47
48
|
.add({ name: context.trpcRouter._shared.procedure.name, location: context.trpcRouter._shared._filePath })
|
|
48
49
|
.add({ name: context.trpcRouter._shared.router.name, location: context.trpcRouter._shared._filePath });
|
|
@@ -86,8 +87,32 @@ import { z } from 'zod'
|
|
|
86
87
|
|
|
87
88
|
${model.trpcRoute.getFiltered.name}: procedure
|
|
88
89
|
.use(authMiddleware)
|
|
89
|
-
.input(
|
|
90
|
-
|
|
90
|
+
.input(
|
|
91
|
+
z.object({
|
|
92
|
+
filters: ${model.types.filter.decoder.name},
|
|
93
|
+
sort: ${model.types.sort.decoder.name},
|
|
94
|
+
}),
|
|
95
|
+
)
|
|
96
|
+
.query(({ input, ctx }) => ctx.view.${variableName}.getFiltered({ filters: input.filters, sort: input.sort, user: ctx.user })),
|
|
97
|
+
|
|
98
|
+
${model.trpcRoute.getFilteredPaginated.name}: procedure
|
|
99
|
+
.use(authMiddleware)
|
|
100
|
+
.input(
|
|
101
|
+
z.object({
|
|
102
|
+
filters: ${model.types.filter.decoder.name},
|
|
103
|
+
sort: ${model.types.sort.decoder.name},
|
|
104
|
+
// cursor and limit at top level for tRPC infinite query support
|
|
105
|
+
cursor: z.number().int().nonnegative().optional(),
|
|
106
|
+
limit: z.number().int().positive().optional(),
|
|
107
|
+
}),
|
|
108
|
+
)
|
|
109
|
+
.query(({ input, ctx }) => ctx.view.${variableName}.getFilteredPaginated({
|
|
110
|
+
filters: input.filters,
|
|
111
|
+
sort: input.sort,
|
|
112
|
+
cursor: input.cursor ?? 1,
|
|
113
|
+
limit: input.limit ?? 100,
|
|
114
|
+
user: ctx.user
|
|
115
|
+
})),
|
|
91
116
|
|
|
92
117
|
${model.trpcRoute.getFilterOptions.name}: procedure
|
|
93
118
|
.use(authMiddleware)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model-routes.generator.js","sourceRoot":"","sources":["../../../src/backend-router-trpc/generators/model-routes.generator.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAOA,
|
|
1
|
+
{"version":3,"file":"model-routes.generator.js","sourceRoot":"","sources":["../../../src/backend-router-trpc/generators/model-routes.generator.ts"],"names":[],"mappings":"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"}
|
|
@@ -102,6 +102,7 @@ export type TrpcRouterModelContext = Generator.ImportableFunction & {
|
|
|
102
102
|
getMap: Generator.ImportableFunction;
|
|
103
103
|
getList: Generator.ImportableFunction;
|
|
104
104
|
getFiltered: Generator.ImportableFunction;
|
|
105
|
+
getFilteredPaginated: Generator.ImportableFunction;
|
|
105
106
|
getFilterOptions: Generator.ImportableFunction;
|
|
106
107
|
};
|
|
107
108
|
export type RouterEnumContext = Generator.ImportableFunction;
|
|
@@ -184,6 +184,7 @@ function registerModelRouter(model) {
|
|
|
184
184
|
getMap: { name: Generator.toFunctionName(`getMap`), location },
|
|
185
185
|
getList: { name: Generator.toFunctionName(`getList`), location },
|
|
186
186
|
getFiltered: { name: Generator.toFunctionName(`getFiltered`), location },
|
|
187
|
+
getFilteredPaginated: { name: Generator.toFunctionName(`getFilteredPaginated`), location },
|
|
187
188
|
getFilterOptions: { name: Generator.toFunctionName(`getFilterOptions`), location },
|
|
188
189
|
};
|
|
189
190
|
return { ...model, trpcRoute: router };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"router-trpc.generator.js","sourceRoot":"","sources":["../../src/backend-router-trpc/router-trpc.generator.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,yCAAmC;AAEnC,6DAA8C;AAE9C,wDAA2E;AAC3E,sEAAuG;AACvG,kDAA+E;AAC/E,kEAA0F;AAC1F,sDAAyE;AACzE,kDAAkE;AAClE,kCAAmD;AAEnD,oCAAsD;AAEtD,4EAAqE;AACrE,sFAA8E;AAC9E,4EAAsE;AACtE,gFAAyE;AACzE,8EAAuF;AACvF,4FAAoF;AACpF,8EAAuE;AA4GvE,MAAM,eAAe,GAAG;IACtB,IAAI,EAAE,QAAQ;IACd,SAAS,EAAE,OAAO;CACnB,CAAA;
|
|
1
|
+
{"version":3,"file":"router-trpc.generator.js","sourceRoot":"","sources":["../../src/backend-router-trpc/router-trpc.generator.ts"],"names":[],"mappings":"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"}
|