cabloy 5.1.249 → 5.1.250
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/.cabloy-version +1 -1
- package/.github/workflows/create-cabloy.yml +50 -3
- package/CHANGELOG.md +21 -0
- package/package.json +1 -1
- package/repo-agent-governance/managed-assets.json +12 -12
- package/repo-agent-governance/skills/cabloy-spec-execution/references/status-and-evidence.md +1 -1
- package/repo-agent-governance/skills/cabloy-spec-generation/references/canonical-spec-input.md +1 -1
- package/repo-agent-governance/skills/cabloy-spec-generation/references/repo-specs-document-set.md +2 -2
- package/repo-agent-governance/skills/cabloy-spec-generation/references/traceability-and-status-rules.md +1 -1
- package/repo-agent-governance/tools/spec-charts/generate-implementation-charts.mjs +73 -22
- package/repo-agent-governance/tools/spec-charts/generate-implementation-charts.test.mjs +98 -7
- package/repo-docs/ai/playbook-spec-execution.md +1 -1
- package/repo-docs/backend/crud-workflow.md +92 -1
- package/repo-docs/backend/dto-guide.md +16 -1
- package/repo-docs/backend/dto-infer-generation.md +23 -5
- package/repo-docs/backend/orm-aggregate-group-guide.md +32 -0
- package/repo-docs/backend/validation-guide.md +38 -2
- package/repo-e2e/specs/cabloy-basic.spec.ts +9 -2
- package/vona/packages-cli/cli/package.json +1 -1
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudAnalysis/aggregate/src/dto/{{resourceName}}AggregateRes.tsx_ +11 -0
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudAnalysis/common/src/lib/{{resourceName}}Analysis.ts_ +14 -0
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudAnalysis/common/test/{{resourceName}}Analysis.test.ts_ +81 -0
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudAnalysis/group/src/dto/{{resourceName}}GroupResItem.tsx_ +11 -0
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudBasic/boilerplate/src/controller/{{resourceName}}.ts_ +18 -1
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudBasic/boilerplate/src/dto/{{resourceName}}SelectReq.tsx_ +2 -0
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudBasic/boilerplate/src/service/{{resourceName}}.ts_ +14 -1
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudBasic/boilerplate/test/{{resourceName}}.test.ts_ +4 -0
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudBasic/snippets/3-en-us.ts +2 -0
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudBasic/snippets/4-zh-cn.ts +2 -0
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudStart/boilerplate/src/controller/{{resourceName}}.ts_ +18 -1
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudStart/boilerplate/src/dto/{{resourceName}}SelectReq.tsx_ +2 -0
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudStart/boilerplate/src/service/{{resourceName}}.ts_ +14 -1
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudStart/boilerplate/test/{{resourceName}}.test.ts_ +1 -1
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudStart/snippets/3-en-us.ts +2 -0
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudStart/snippets/4-zh-cn.ts +2 -0
- package/vona/packages-cli/cli-set-api/package.json +1 -1
- package/vona/packages-cli/cli-set-api/src/lib/bean/cli.tools.crudBasic.ts +2 -0
- package/vona/packages-cli/cli-set-api/src/lib/bean/cli.tools.crudStart.ts +2 -0
- package/vona/packages-cli/cli-set-api/src/lib/bean/cli.tools.masterDetail.ts +9 -1
- package/vona/packages-cli/cli-set-api/src/lib/crudAnalysis.ts +11 -0
- package/vona/packages-cli/cli-set-api/test/crudAnalysis.test.ts +385 -0
- package/vona/packages-utils/babel-plugin-vona-bean-module/package.json +1 -1
- package/vona/packages-vona/vona/package.json +1 -1
- package/vona/packages-vona/vona-core/package.json +1 -1
- package/vona/packages-vona/vona-mock/package.json +1 -1
- package/vona/pnpm-lock.yaml +7 -7
- package/vona/src/suite/a-training/modules/training-student/src/.metadata/index.ts +18 -0
- package/vona/src/suite/a-training/modules/training-student/src/config/locale/en-us.ts +2 -0
- package/vona/src/suite/a-training/modules/training-student/src/config/locale/zh-cn.ts +2 -0
- package/vona/src/suite/a-training/modules/training-student/src/controller/student.ts +28 -1
- package/vona/src/suite/a-training/modules/training-student/src/dto/studentAggregateRes.tsx +11 -0
- package/vona/src/suite/a-training/modules/training-student/src/dto/studentGroupResItem.tsx +11 -0
- package/vona/src/suite/a-training/modules/training-student/src/dto/studentSelectReq.tsx +2 -0
- package/vona/src/suite/a-training/modules/training-student/src/lib/studentAnalysis.ts +12 -0
- package/vona/src/suite/a-training/modules/training-student/src/service/student.ts +22 -1
- package/vona/src/suite/a-training/modules/training-student/test/student.test.ts +4 -0
- package/vona/src/suite/a-training/modules/training-student/test/studentAnalysis.test.ts +422 -0
- package/vona/src/suite-vendor/a-cabloy/modules/a-rbac/package.json +1 -1
- package/vona/src/suite-vendor/a-cabloy/package.json +2 -2
- package/vona/src/suite-vendor/a-captcha/modules/a-captcha/package.json +1 -1
- package/vona/src/suite-vendor/a-captcha/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-core/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-hmr/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-locale/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-openapi/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-openapiutils/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-openapiutils/src/lib/utils.ts +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/.metadata/index.ts +26 -26
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/aggregateResult.ts +53 -0
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/analysis/controls.ts +163 -0
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/analysis/execute.ts +49 -0
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/analysis/index.ts +3 -0
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/analysis/policy.ts +140 -0
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/bean.model/bean.model_cache.ts +4 -2
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/bean.model/bean.model_crud_inner.ts +8 -3
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/dto/dto.ts +3 -0
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/dto/dtoAggregate.ts +4 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/dto/dtoAnalysis.ts +34 -0
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/index.ts +1 -0
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/types/analysis.ts +137 -0
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/types/index.ts +1 -0
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/types/relationsAggr.ts +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/test/aggregateResult.test.ts +81 -0
- package/vona/src/suite-vendor/a-vona/modules/a-orm/test/analysis.test.ts +208 -0
- package/vona/src/suite-vendor/a-vona/modules/a-orm/test/analysis.types.test.ts +112 -0
- package/vona/src/suite-vendor/a-vona/modules/a-permission/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-queue/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-ratelimit/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-user/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-web/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-web/src/.metadata/index.ts +40 -40
- package/vona/src/suite-vendor/a-vona/modules/a-web/src/bean/pipe.filter.ts +44 -3
- package/vona/src/suite-vendor/a-vona/modules/a-web/src/lib/analysisFilter.ts +69 -0
- package/vona/src/suite-vendor/a-vona/modules/a-web/src/lib/decorator/arguments.ts +6 -4
- package/vona/src/suite-vendor/a-vona/modules/a-web/src/lib/index.ts +1 -0
- package/vona/src/suite-vendor/a-vona/modules/a-web/src/types/dto.ts +2 -1
- package/vona/src/suite-vendor/a-vona/modules/a-web/src/types/filter.ts +7 -2
- package/vona/src/suite-vendor/a-vona/package.json +1 -1
- package/zova/packages-cli/cli/package.json +2 -2
- package/zova/packages-cli/cli-set-front/package.json +2 -2
- package/zova/packages-utils/zova-jsx/package.json +2 -2
- package/zova/packages-utils/zova-vite/package.json +1 -1
- package/zova/packages-zova/zova/package.json +3 -3
- package/zova/packages-zova/zova-core/package.json +1 -1
- package/zova/pnpm-lock.yaml +11 -11
- package/zova/src/suite/a-training/modules/training-student/src/api/openapi/schemas.ts +16 -2
- package/zova/src/suite/a-training/modules/training-student/src/api/openapi/types.ts +201 -7
- package/zova/src/suite/a-training/modules/training-student/src/api/trainingStudent.ts +42 -0
- package/zova/src/suite/a-training/modules/training-student/src/apiSchema/trainingStudent.ts +10 -0
- package/zova/src/suite-vendor/a-zova/modules/a-ssr/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-ssrserver/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-table/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-zova/package.json +2 -2
- package/zova/src/suite-vendor/a-zova/package.json +5 -5
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { execFileSync } from 'node:child_process';
|
|
3
|
+
import fs from 'node:fs/promises';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { describe, it } from 'node:test';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
import ts from 'typescript';
|
|
9
|
+
|
|
10
|
+
import { LocalTemplate } from '../../cabloy-cli/src/lib/local.template.ts';
|
|
11
|
+
import { CliToolsCrud } from '../src/lib/bean/cli.tools.crud.ts';
|
|
12
|
+
import { CliToolsCrudBasic } from '../src/lib/bean/cli.tools.crudBasic.ts';
|
|
13
|
+
import { CliToolsCrudStart } from '../src/lib/bean/cli.tools.crudStart.ts';
|
|
14
|
+
import { CliToolsMasterDetail } from '../src/lib/bean/cli.tools.masterDetail.ts';
|
|
15
|
+
import crud from '../src/lib/command/tools.crud.ts';
|
|
16
|
+
import basic from '../src/lib/command/tools.crudBasic.ts';
|
|
17
|
+
import start from '../src/lib/command/tools.crudStart.ts';
|
|
18
|
+
|
|
19
|
+
const packageRoot = fileURLToPath(new URL('../', import.meta.url));
|
|
20
|
+
const templateRoot = path.join(packageRoot, 'cli/templates');
|
|
21
|
+
const editions = { basic: CliToolsCrudBasic, start: CliToolsCrudStart };
|
|
22
|
+
|
|
23
|
+
// Exercise the authored templates with the production renderer; packaged templates
|
|
24
|
+
// are checked separately because existing dist-cli output can intentionally be stale.
|
|
25
|
+
class SourceTemplate extends LocalTemplate {
|
|
26
|
+
resolveTemplatePath(_set: string, target: string) {
|
|
27
|
+
return path.isAbsolute(target) ? target : path.join(templateRoot, target);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function fixture<T>(fn: (root: string) => Promise<T>) {
|
|
32
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'crud-analysis-'));
|
|
33
|
+
try {
|
|
34
|
+
return await fn(root);
|
|
35
|
+
} finally {
|
|
36
|
+
await fs.rm(root, { recursive: true, force: true });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function makeCli(
|
|
41
|
+
root: string,
|
|
42
|
+
Class: typeof CliToolsCrudBasic | typeof CliToolsCrudStart | typeof CliToolsCrud,
|
|
43
|
+
Template: typeof LocalTemplate = SourceTemplate,
|
|
44
|
+
) {
|
|
45
|
+
const moduleRoot = path.join(root, 'module');
|
|
46
|
+
await fs.mkdir(path.join(moduleRoot, 'src/bean'), { recursive: true });
|
|
47
|
+
await fs.mkdir(path.join(moduleRoot, 'src/config/locale'), { recursive: true });
|
|
48
|
+
await fs.writeFile(
|
|
49
|
+
path.join(moduleRoot, 'package.json'),
|
|
50
|
+
JSON.stringify({ type: 'module', vonaModule: { fileVersion: 0 } }),
|
|
51
|
+
);
|
|
52
|
+
await fs.writeFile(
|
|
53
|
+
path.join(moduleRoot, 'src/bean/meta.index.ts'),
|
|
54
|
+
"import { Meta } from 'vona-module-a-meta';\n@Meta({ indexes: {} }) class MetaIndex {}\n",
|
|
55
|
+
);
|
|
56
|
+
await fs.writeFile(
|
|
57
|
+
path.join(moduleRoot, 'src/bean/meta.version.ts'),
|
|
58
|
+
'class MetaVersion { async update(_options: IMetaVersionUpdateOptions) {} }\n',
|
|
59
|
+
);
|
|
60
|
+
for (const locale of ['en-us', 'zh-cn']) {
|
|
61
|
+
await fs.writeFile(
|
|
62
|
+
path.join(moduleRoot, `src/config/locale/${locale}.ts`),
|
|
63
|
+
'export default {};\n',
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
const argv = {
|
|
67
|
+
_: [],
|
|
68
|
+
module: 'test-record',
|
|
69
|
+
resourceName: 'record',
|
|
70
|
+
projectPath: root,
|
|
71
|
+
};
|
|
72
|
+
const cli = new Class({ context: { argv } } as never);
|
|
73
|
+
cli.modulesMeta = { modules: { 'test-record': { root: moduleRoot } } } as never;
|
|
74
|
+
cli.__template = new Template(cli);
|
|
75
|
+
cli.console.log = async () => {};
|
|
76
|
+
cli.helper.formatFile = async () => {};
|
|
77
|
+
const invocations: string[][] = [];
|
|
78
|
+
cli.helper.invokeCli = async args => {
|
|
79
|
+
invocations.push(args);
|
|
80
|
+
};
|
|
81
|
+
return { cli, moduleRoot, invocations };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function filesUnder(root: string): Promise<string[]> {
|
|
85
|
+
const files: string[] = [];
|
|
86
|
+
for (const entry of await fs.readdir(root, { withFileTypes: true })) {
|
|
87
|
+
if (entry.isDirectory()) {
|
|
88
|
+
files.push(
|
|
89
|
+
...(await filesUnder(path.join(root, entry.name))).map(file => `${entry.name}/${file}`),
|
|
90
|
+
);
|
|
91
|
+
} else {
|
|
92
|
+
files.push(entry.name);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return files.sort();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function read(root: string, file: string) {
|
|
99
|
+
return await fs.readFile(path.join(root, file), 'utf8');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
describe('default CRUD analysis generation', { concurrency: false }, () => {
|
|
103
|
+
it('removes analysis flags from command options and usage without adding questions', () => {
|
|
104
|
+
for (const definition of [crud, basic, start]) {
|
|
105
|
+
assert.deepEqual(Object.keys(definition.options), ['module']);
|
|
106
|
+
assert.doesNotMatch(definition.info.usage, /--aggregate|--groupBy/);
|
|
107
|
+
assert.deepEqual(Object.keys(definition.groups.default.questions), [
|
|
108
|
+
'resourceName',
|
|
109
|
+
'module',
|
|
110
|
+
]);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
for (const [edition, Class] of Object.entries(editions)) {
|
|
115
|
+
it(`${edition}: generates both actions with one finite name/count policy by default`, async () =>
|
|
116
|
+
fixture(async root => {
|
|
117
|
+
const { cli, moduleRoot, invocations } = await makeCli(root, Class);
|
|
118
|
+
await cli.execute();
|
|
119
|
+
const files = await filesUnder(moduleRoot);
|
|
120
|
+
for (const file of [
|
|
121
|
+
'src/lib/recordAnalysis.ts',
|
|
122
|
+
'test/recordAnalysis.test.ts',
|
|
123
|
+
'src/dto/recordGroupResItem.tsx',
|
|
124
|
+
'src/dto/recordAggregateRes.tsx',
|
|
125
|
+
]) {
|
|
126
|
+
assert.ok(files.includes(file), file);
|
|
127
|
+
}
|
|
128
|
+
assert.equal(
|
|
129
|
+
files.some(file => file.includes('AnalysisReq')),
|
|
130
|
+
false,
|
|
131
|
+
);
|
|
132
|
+
assert.deepEqual(invocations, [[':tools:metadata', 'test-record']]);
|
|
133
|
+
const controller = await read(moduleRoot, 'src/controller/record.ts');
|
|
134
|
+
const service = await read(moduleRoot, 'src/service/record.ts');
|
|
135
|
+
const req = await read(moduleRoot, 'src/dto/recordSelectReq.tsx');
|
|
136
|
+
const policy = await read(moduleRoot, 'src/lib/recordAnalysis.ts');
|
|
137
|
+
assert.doesNotMatch(controller, /AnalysisReq|\$schema|dataScope/);
|
|
138
|
+
assert.ok(
|
|
139
|
+
controller.includes(
|
|
140
|
+
'async select(@Arg.filter(DtoRecordSelectReq) params: IQueryParams<ModelRecord>)',
|
|
141
|
+
),
|
|
142
|
+
);
|
|
143
|
+
assert.match(req, /\$Dto.queryPage\(EntityRecord, \['name', 'createdAt'\]\)/);
|
|
144
|
+
assert.match(req, /analysis: recordAnalysis/);
|
|
145
|
+
assert.match(req, /import \{ recordAnalysis \} from '\.\.\/lib\/recordAnalysis\.ts'/);
|
|
146
|
+
assert.match(
|
|
147
|
+
policy,
|
|
148
|
+
/export const recordAnalysis = \$defineAnalysis\(\(\) => ModelRecord, \{/,
|
|
149
|
+
);
|
|
150
|
+
assert.match(policy, /groups: \['name'\]/);
|
|
151
|
+
assert.match(policy, /aggrs: \{ count: \['\*'\] \}/);
|
|
152
|
+
assert.match(policy, /group: \{ groups: \['name'\], aggrs: \{ count: \['\*'\] \} \}/);
|
|
153
|
+
assert.match(policy, /aggregate: \{ aggrs: \{ count: \['\*'\] \} \}/);
|
|
154
|
+
assert.match(policy, /limits: \{ maxGroups: 3, maxMetrics: 10, maxGroupRows: 1000 \}/);
|
|
155
|
+
assert.doesNotMatch(policy, /description|AnalysisAggrs|AnalysisGroup/);
|
|
156
|
+
assert.doesNotMatch(
|
|
157
|
+
service,
|
|
158
|
+
/\.\.\.params|\.group\(|\.aggregate\(|items\.map|Object\.hasOwn|normalizeRecordCount|count_all:/,
|
|
159
|
+
);
|
|
160
|
+
assert.equal(controller.match(/actionInherit: 'select'/g)?.length, 2);
|
|
161
|
+
for (const action of ['group', 'aggregate']) {
|
|
162
|
+
const mode = action === 'group' ? 'Group' : 'Aggregate';
|
|
163
|
+
assert.ok(
|
|
164
|
+
controller.includes(
|
|
165
|
+
`async ${action}(@Arg.filter(DtoRecordSelectReq, { mode: '${action}' }) params: TypeAnalysis${mode}Params<typeof recordAnalysis>)`,
|
|
166
|
+
),
|
|
167
|
+
`${action} must derive action controls from the same SelectReq policy`,
|
|
168
|
+
);
|
|
169
|
+
assert.ok(
|
|
170
|
+
controller.indexOf(`@Web.get('${action}'`) < controller.indexOf("@Web.get(':id'"),
|
|
171
|
+
);
|
|
172
|
+
assert.ok(
|
|
173
|
+
service.includes(
|
|
174
|
+
`async ${action}(params?: TypeAnalysis${mode}Params<typeof recordAnalysis>)`,
|
|
175
|
+
),
|
|
176
|
+
);
|
|
177
|
+
assert.ok(
|
|
178
|
+
service.includes(
|
|
179
|
+
`return await $executeAnalysis${mode}(this.scope.model.record, recordAnalysis, params)`,
|
|
180
|
+
),
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
const groupDto = await read(moduleRoot, 'src/dto/recordGroupResItem.tsx');
|
|
184
|
+
assert.match(groupDto, /\$Dto\.analysisGroup\(recordAnalysis\)/);
|
|
185
|
+
assert.doesNotMatch(groupDto, /description|\$Class|\$makeSchema|zod/);
|
|
186
|
+
assert.match(
|
|
187
|
+
await read(moduleRoot, 'src/dto/recordAggregateRes.tsx'),
|
|
188
|
+
/\$Dto\.analysisAggregate\(recordAnalysis\)/,
|
|
189
|
+
);
|
|
190
|
+
for (const locale of ['en-us', 'zh-cn']) {
|
|
191
|
+
const source = await read(moduleRoot, `src/config/locale/${locale}.ts`);
|
|
192
|
+
assert.match(source, /RecordGroup/);
|
|
193
|
+
assert.match(source, /RecordAggregate/);
|
|
194
|
+
}
|
|
195
|
+
const analysisTest = await read(moduleRoot, 'test/recordAnalysis.test.ts');
|
|
196
|
+
assert.doesNotMatch(analysisTest, /normalizeRecordCount|normalizes only/);
|
|
197
|
+
assert.match(
|
|
198
|
+
analysisTest,
|
|
199
|
+
/returns default and requested-only string counts with validated controls/,
|
|
200
|
+
);
|
|
201
|
+
assert.match(analysisTest, /JSON\.stringify\(\{ count: \['\*'\] \}\)/);
|
|
202
|
+
assert.match(
|
|
203
|
+
analysisTest,
|
|
204
|
+
/The shared policy does not enable analysis controls on ordinary select/,
|
|
205
|
+
);
|
|
206
|
+
assert.match(analysisTest, /\{ name \}/);
|
|
207
|
+
assert.match(analysisTest, /where: \{ id: fixture.id \}/);
|
|
208
|
+
assert.match(
|
|
209
|
+
analysisTest,
|
|
210
|
+
/columns: \['id'\], orders: 'name,desc', pageNo: 2, pageSize: 1/,
|
|
211
|
+
);
|
|
212
|
+
assert.match(analysisTest, /assert.rejects\(/);
|
|
213
|
+
assert.match(analysisTest, /for \(const action of \['group', 'aggregate'\] as const\)/);
|
|
214
|
+
assert.match(analysisTest, /\['description'\]/);
|
|
215
|
+
assert.match(analysisTest, /pageNo: 2, pageSize: 0/);
|
|
216
|
+
const actionTest = await read(moduleRoot, 'test/record.test.ts');
|
|
217
|
+
if (edition === 'basic') {
|
|
218
|
+
assert.match(
|
|
219
|
+
actionTest,
|
|
220
|
+
/group: \['get', `\$\{moduleApiPath\}\/group`, 'Group Record'\]/,
|
|
221
|
+
);
|
|
222
|
+
assert.match(
|
|
223
|
+
actionTest,
|
|
224
|
+
/aggregate: \['get', `\$\{moduleApiPath\}\/aggregate`, 'Aggregate Record'\]/,
|
|
225
|
+
);
|
|
226
|
+
assert.match(actionTest, /group: `\$\{controllerBeanFullName\}#select`/);
|
|
227
|
+
assert.match(actionTest, /aggregate: `\$\{controllerBeanFullName\}#select`/);
|
|
228
|
+
} else {
|
|
229
|
+
assert.match(
|
|
230
|
+
actionTest,
|
|
231
|
+
/const actions = \['create', 'select', 'view', 'update', 'delete', 'group', 'aggregate'\]/,
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
for (const file of files.filter(file => /\.tsx?$/.test(file))) {
|
|
235
|
+
const source = await read(moduleRoot, file);
|
|
236
|
+
assert.ok(source.trim(), file);
|
|
237
|
+
assert.doesNotMatch(source, /<%/);
|
|
238
|
+
const result = ts.transpileModule(source, {
|
|
239
|
+
fileName: file,
|
|
240
|
+
reportDiagnostics: true,
|
|
241
|
+
compilerOptions: {
|
|
242
|
+
target: ts.ScriptTarget.ESNext,
|
|
243
|
+
module: ts.ModuleKind.ESNext,
|
|
244
|
+
experimentalDecorators: true,
|
|
245
|
+
},
|
|
246
|
+
});
|
|
247
|
+
assert.deepEqual(
|
|
248
|
+
result.diagnostics?.filter(
|
|
249
|
+
diagnostic => diagnostic.category === ts.DiagnosticCategory.Error,
|
|
250
|
+
),
|
|
251
|
+
[],
|
|
252
|
+
file,
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
}));
|
|
256
|
+
|
|
257
|
+
it(`${edition}: aggregate master-detail cleanup removes generated standalone analysis artifacts`, async () =>
|
|
258
|
+
fixture(async root => {
|
|
259
|
+
const { cli, moduleRoot } = await makeCli(root, Class);
|
|
260
|
+
await cli.execute();
|
|
261
|
+
const masterDetail = new CliToolsMasterDetail({
|
|
262
|
+
context: {
|
|
263
|
+
argv: {
|
|
264
|
+
_detailModule: { root: moduleRoot },
|
|
265
|
+
detailResourceName: 'record',
|
|
266
|
+
createdDetailResource: true,
|
|
267
|
+
detailMode: 'aggregate',
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
} as never);
|
|
271
|
+
(masterDetail as any)._handleAggregateDetailMode(false);
|
|
272
|
+
const files = await filesUnder(moduleRoot);
|
|
273
|
+
for (const file of [
|
|
274
|
+
'src/controller/record.ts',
|
|
275
|
+
'src/service/record.ts',
|
|
276
|
+
'src/lib/recordAnalysis.ts',
|
|
277
|
+
'test/record.test.ts',
|
|
278
|
+
'test/recordAnalysis.test.ts',
|
|
279
|
+
]) {
|
|
280
|
+
assert.ok(!files.includes(file), file);
|
|
281
|
+
}
|
|
282
|
+
assert.ok(!files.some(file => file.startsWith('src/dto/')));
|
|
283
|
+
assert.ok(files.includes('src/entity/record.tsx'));
|
|
284
|
+
assert.ok(files.includes('src/model/record.ts'));
|
|
285
|
+
}));
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
it(
|
|
289
|
+
'freshly packaged beans and production templates match default source generation',
|
|
290
|
+
{ skip: process.env.CABLOY_TEST_PACKAGED_CLI !== '1' },
|
|
291
|
+
async () => {
|
|
292
|
+
// Run after tsc:publish. Published packages ship raw EJS in cli/ and compiled
|
|
293
|
+
// snippets in dist-cli/; verify both are packaged, not just present locally.
|
|
294
|
+
const [pack] = JSON.parse(
|
|
295
|
+
execFileSync('npm', ['pack', '--dry-run', '--ignore-scripts', '--json'], {
|
|
296
|
+
cwd: packageRoot,
|
|
297
|
+
encoding: 'utf8',
|
|
298
|
+
}),
|
|
299
|
+
) as { files: { path: string }[] }[];
|
|
300
|
+
const packedFiles = new Set(pack.files.map(file => file.path));
|
|
301
|
+
assert.ok(packedFiles.has('dist/index.js'));
|
|
302
|
+
const { beans, commands } = await import('../dist/index.js');
|
|
303
|
+
for (const command of ['crud', 'crudBasic', 'crudStart']) {
|
|
304
|
+
assert.deepEqual(Object.keys(commands.tools[command].options), ['module']);
|
|
305
|
+
assert.doesNotMatch(commands.tools[command].info.usage, /--aggregate|--groupBy/);
|
|
306
|
+
}
|
|
307
|
+
const previousBrand = process.env.CabloyCliBrandName;
|
|
308
|
+
process.env.CabloyCliBrandName = 'vona';
|
|
309
|
+
try {
|
|
310
|
+
for (const [edition, Class] of Object.entries(editions)) {
|
|
311
|
+
await fixture(async root => {
|
|
312
|
+
const source = await makeCli(path.join(root, 'source'), Class);
|
|
313
|
+
const packaged = await makeCli(
|
|
314
|
+
path.join(root, 'packaged'),
|
|
315
|
+
beans[edition === 'basic' ? 'tools.crudBasic' : 'tools.crudStart'],
|
|
316
|
+
LocalTemplate,
|
|
317
|
+
);
|
|
318
|
+
for (const target of [
|
|
319
|
+
`tools/crud${edition === 'basic' ? 'Basic' : 'Start'}/boilerplate`,
|
|
320
|
+
`tools/crud${edition === 'basic' ? 'Basic' : 'Start'}/snippets`,
|
|
321
|
+
'tools/crudAnalysis/common',
|
|
322
|
+
'tools/crudAnalysis/group',
|
|
323
|
+
'tools/crudAnalysis/aggregate',
|
|
324
|
+
]) {
|
|
325
|
+
const resolved = packaged.cli.template.resolveTemplatePath('api', target);
|
|
326
|
+
const prefix = target.endsWith('/snippets') ? 'dist-cli/templates' : 'cli/templates';
|
|
327
|
+
assert.equal(resolved, path.join(packageRoot, prefix, target));
|
|
328
|
+
const templateFiles = await filesUnder(resolved);
|
|
329
|
+
assert.ok(templateFiles.length, target);
|
|
330
|
+
for (const file of templateFiles) {
|
|
331
|
+
assert.ok(
|
|
332
|
+
packedFiles.has(`${prefix}/${target}/${file}`),
|
|
333
|
+
`missing packaged template: ${target}/${file}`,
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
await source.cli.execute();
|
|
338
|
+
await packaged.cli.execute();
|
|
339
|
+
const files = await filesUnder(source.moduleRoot);
|
|
340
|
+
assert.deepEqual(await filesUnder(packaged.moduleRoot), files);
|
|
341
|
+
for (const file of files) {
|
|
342
|
+
assert.equal(
|
|
343
|
+
await read(packaged.moduleRoot, file),
|
|
344
|
+
await read(source.moduleRoot, file),
|
|
345
|
+
`${edition} ${file}`,
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
assert.deepEqual(packaged.invocations, source.invocations);
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
} finally {
|
|
352
|
+
if (previousBrand === undefined) delete process.env.CabloyCliBrandName;
|
|
353
|
+
else process.env.CabloyCliBrandName = previousBrand;
|
|
354
|
+
}
|
|
355
|
+
},
|
|
356
|
+
);
|
|
357
|
+
|
|
358
|
+
it('wrapper invokes default edition generation and refreshes metadata after all rendering', async () => {
|
|
359
|
+
for (const edition of ['basic', 'start']) {
|
|
360
|
+
await fixture(async root => {
|
|
361
|
+
const { cli, moduleRoot, invocations } = await makeCli(root, CliToolsCrud);
|
|
362
|
+
await fs.writeFile(
|
|
363
|
+
path.join(root, edition === 'start' ? '__CABLOY_START__' : '__CABLOY_BASIC__'),
|
|
364
|
+
'',
|
|
365
|
+
);
|
|
366
|
+
cli.helper.invokeCli = async args => {
|
|
367
|
+
invocations.push(args);
|
|
368
|
+
if (args[0] === ':tools:metadata') {
|
|
369
|
+
assert.match(await read(moduleRoot, 'src/bean/ssrMenu.record.ts'), /record/);
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
await cli.execute();
|
|
373
|
+
assert.deepEqual(invocations, [
|
|
374
|
+
[
|
|
375
|
+
edition === 'start' ? ':tools:crudStart' : ':tools:crudBasic',
|
|
376
|
+
'record',
|
|
377
|
+
'--module=test-record',
|
|
378
|
+
'--nometadata',
|
|
379
|
+
],
|
|
380
|
+
[':tools:metadata', 'test-record'],
|
|
381
|
+
]);
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vona",
|
|
3
|
-
"version": "5.1.
|
|
3
|
+
"version": "5.1.125",
|
|
4
4
|
"gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
|
|
5
5
|
"description": "Vona is an intuitive, elegant and powerful Node.js framework for rapidly developing enterprise applications of any size",
|
|
6
6
|
"keywords": [
|
package/vona/pnpm-lock.yaml
CHANGED
|
@@ -851,8 +851,8 @@ importers:
|
|
|
851
851
|
specifier: npm:@cabloy/zod@4.3.8
|
|
852
852
|
version: '@cabloy/zod@4.3.8'
|
|
853
853
|
zova:
|
|
854
|
-
specifier: ^5.1.
|
|
855
|
-
version: 5.1.
|
|
854
|
+
specifier: ^5.1.179
|
|
855
|
+
version: 5.1.179(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)(vue@3.5.43(typescript@5.9.3))
|
|
856
856
|
zova-jsx:
|
|
857
857
|
specifier: ^1.1.95
|
|
858
858
|
version: 1.1.95(typescript@5.9.3)
|
|
@@ -917,14 +917,14 @@ importers:
|
|
|
917
917
|
specifier: ^5.1.36
|
|
918
918
|
version: 5.1.36
|
|
919
919
|
zova-module-a-ssr:
|
|
920
|
-
specifier: ^5.1.
|
|
921
|
-
version: 5.1.
|
|
920
|
+
specifier: ^5.1.43
|
|
921
|
+
version: 5.1.43
|
|
922
922
|
zova-module-a-ssrhmr:
|
|
923
923
|
specifier: ^5.1.24
|
|
924
924
|
version: 5.1.24
|
|
925
925
|
zova-module-a-ssrserver:
|
|
926
|
-
specifier: ^5.1.
|
|
927
|
-
version: 5.1.
|
|
926
|
+
specifier: ^5.1.28
|
|
927
|
+
version: 5.1.28
|
|
928
928
|
zova-module-a-style:
|
|
929
929
|
specifier: ^5.1.36
|
|
930
930
|
version: 5.1.36
|
|
@@ -1975,7 +1975,7 @@ importers:
|
|
|
1975
1975
|
specifier: ^5.0.3
|
|
1976
1976
|
version: link:modules/a-markdown
|
|
1977
1977
|
vona-module-a-rbac:
|
|
1978
|
-
specifier: ^5.0.
|
|
1978
|
+
specifier: ^5.0.20
|
|
1979
1979
|
version: link:modules/a-rbac
|
|
1980
1980
|
vona-module-a-socket:
|
|
1981
1981
|
specifier: workspace:^
|
|
@@ -216,8 +216,10 @@ export * from '../dto/detailRecordBase.tsx';
|
|
|
216
216
|
export * from '../dto/detailRecordMutate.tsx';
|
|
217
217
|
export * from '../dto/detailRecordResItem.tsx';
|
|
218
218
|
export * from '../dto/detailRecordView.tsx';
|
|
219
|
+
export * from '../dto/studentAggregateRes.tsx';
|
|
219
220
|
export * from '../dto/studentCreate.tsx';
|
|
220
221
|
export * from '../dto/studentDeleteBulk.tsx';
|
|
222
|
+
export * from '../dto/studentGroupResItem.tsx';
|
|
221
223
|
export * from '../dto/studentSelectReq.tsx';
|
|
222
224
|
export * from '../dto/studentSelectRes.tsx';
|
|
223
225
|
export * from '../dto/studentSelectResItem.tsx';
|
|
@@ -228,8 +230,10 @@ import type { IDtoOptionsDetailRecordBase } from '../dto/detailRecordBase.tsx';
|
|
|
228
230
|
import type { IDtoOptionsDetailRecordMutate } from '../dto/detailRecordMutate.tsx';
|
|
229
231
|
import type { IDtoOptionsDetailRecordResItem } from '../dto/detailRecordResItem.tsx';
|
|
230
232
|
import type { IDtoOptionsDetailRecordView } from '../dto/detailRecordView.tsx';
|
|
233
|
+
import type { IDtoOptionsStudentAggregateRes } from '../dto/studentAggregateRes.tsx';
|
|
231
234
|
import type { IDtoOptionsStudentCreate } from '../dto/studentCreate.tsx';
|
|
232
235
|
import type { IDtoOptionsStudentDeleteBulk } from '../dto/studentDeleteBulk.tsx';
|
|
236
|
+
import type { IDtoOptionsStudentGroupResItem } from '../dto/studentGroupResItem.tsx';
|
|
233
237
|
import type { IDtoOptionsStudentSelectReq } from '../dto/studentSelectReq.tsx';
|
|
234
238
|
import type { IDtoOptionsStudentSelectRes } from '../dto/studentSelectRes.tsx';
|
|
235
239
|
import type { IDtoOptionsStudentSelectResItem } from '../dto/studentSelectResItem.tsx';
|
|
@@ -244,8 +248,10 @@ declare module 'vona-module-a-web' {
|
|
|
244
248
|
'training-student:detailRecordMutate': IDtoOptionsDetailRecordMutate;
|
|
245
249
|
'training-student:detailRecordResItem': IDtoOptionsDetailRecordResItem;
|
|
246
250
|
'training-student:detailRecordView': IDtoOptionsDetailRecordView;
|
|
251
|
+
'training-student:studentAggregateRes': IDtoOptionsStudentAggregateRes;
|
|
247
252
|
'training-student:studentCreate': IDtoOptionsStudentCreate;
|
|
248
253
|
'training-student:studentDeleteBulk': IDtoOptionsStudentDeleteBulk;
|
|
254
|
+
'training-student:studentGroupResItem': IDtoOptionsStudentGroupResItem;
|
|
249
255
|
'training-student:studentSelectReq': IDtoOptionsStudentSelectReq;
|
|
250
256
|
'training-student:studentSelectRes': IDtoOptionsStudentSelectRes;
|
|
251
257
|
'training-student:studentSelectResItem': IDtoOptionsStudentSelectResItem;
|
|
@@ -265,8 +271,10 @@ import type { DtoDetailRecordBase } from '../dto/detailRecordBase.tsx';
|
|
|
265
271
|
import type { DtoDetailRecordMutate } from '../dto/detailRecordMutate.tsx';
|
|
266
272
|
import type { DtoDetailRecordResItem } from '../dto/detailRecordResItem.tsx';
|
|
267
273
|
import type { DtoDetailRecordView } from '../dto/detailRecordView.tsx';
|
|
274
|
+
import type { DtoStudentAggregateRes } from '../dto/studentAggregateRes.tsx';
|
|
268
275
|
import type { DtoStudentCreate } from '../dto/studentCreate.tsx';
|
|
269
276
|
import type { DtoStudentDeleteBulk } from '../dto/studentDeleteBulk.tsx';
|
|
277
|
+
import type { DtoStudentGroupResItem } from '../dto/studentGroupResItem.tsx';
|
|
270
278
|
import type { DtoStudentSelectReq } from '../dto/studentSelectReq.tsx';
|
|
271
279
|
import type { DtoStudentSelectRes } from '../dto/studentSelectRes.tsx';
|
|
272
280
|
import type { DtoStudentSelectResItem } from '../dto/studentSelectResItem.tsx';
|
|
@@ -291,6 +299,10 @@ declare module 'vona-module-training-student' {
|
|
|
291
299
|
fields?: TypeEntityOptionsFields<DtoDetailRecordView, IDtoOptionsDetailRecordView[TypeSymbolKeyFieldsMore]>;
|
|
292
300
|
}
|
|
293
301
|
|
|
302
|
+
export interface IDtoOptionsStudentAggregateRes {
|
|
303
|
+
fields?: TypeEntityOptionsFields<DtoStudentAggregateRes, IDtoOptionsStudentAggregateRes[TypeSymbolKeyFieldsMore]>;
|
|
304
|
+
}
|
|
305
|
+
|
|
294
306
|
export interface IDtoOptionsStudentCreate {
|
|
295
307
|
fields?: TypeEntityOptionsFields<DtoStudentCreate, IDtoOptionsStudentCreate[TypeSymbolKeyFieldsMore]>;
|
|
296
308
|
}
|
|
@@ -299,6 +311,10 @@ declare module 'vona-module-training-student' {
|
|
|
299
311
|
fields?: TypeEntityOptionsFields<DtoStudentDeleteBulk, IDtoOptionsStudentDeleteBulk[TypeSymbolKeyFieldsMore]>;
|
|
300
312
|
}
|
|
301
313
|
|
|
314
|
+
export interface IDtoOptionsStudentGroupResItem {
|
|
315
|
+
fields?: TypeEntityOptionsFields<DtoStudentGroupResItem, IDtoOptionsStudentGroupResItem[TypeSymbolKeyFieldsMore]>;
|
|
316
|
+
}
|
|
317
|
+
|
|
302
318
|
export interface IDtoOptionsStudentSelectReq {
|
|
303
319
|
fields?: TypeEntityOptionsFields<DtoStudentSelectReq, IDtoOptionsStudentSelectReq[TypeSymbolKeyFieldsMore]>;
|
|
304
320
|
}
|
|
@@ -366,6 +382,8 @@ declare module 'vona-module-a-web' {
|
|
|
366
382
|
}
|
|
367
383
|
export interface IApiPathGetRecord{
|
|
368
384
|
'/training/student': undefined;
|
|
385
|
+
'/training/student/group': undefined;
|
|
386
|
+
'/training/student/aggregate': undefined;
|
|
369
387
|
'/training/student/:id': undefined;
|
|
370
388
|
'/training/student/summary/:id': undefined;
|
|
371
389
|
}
|
|
@@ -22,6 +22,8 @@ export default {
|
|
|
22
22
|
StudentController: 'Student Management',
|
|
23
23
|
StudentCreate: 'Create Student',
|
|
24
24
|
StudentSelect: 'List Students',
|
|
25
|
+
StudentGroup: 'Group Students',
|
|
26
|
+
StudentAggregate: 'Aggregate Students',
|
|
25
27
|
StudentView: 'View Student',
|
|
26
28
|
StudentUpdate: 'Update Student',
|
|
27
29
|
StudentSummary: 'View Student Summary',
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import type { TableIdentity } from 'table-identity';
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
IQueryParams,
|
|
4
|
+
TypeAnalysisAggregateParams,
|
|
5
|
+
TypeAnalysisGroupParams,
|
|
6
|
+
} from 'vona-module-a-orm';
|
|
3
7
|
import type { IDecoratorControllerOptions } from 'vona-module-a-web';
|
|
4
8
|
|
|
5
9
|
import { BeanBase } from 'vona';
|
|
@@ -9,11 +13,14 @@ import { Passport } from 'vona-module-a-user';
|
|
|
9
13
|
import { Arg, Controller, Web } from 'vona-module-a-web';
|
|
10
14
|
import { z } from 'zod';
|
|
11
15
|
|
|
16
|
+
import type { studentAnalysis } from '../lib/studentAnalysis.ts';
|
|
12
17
|
import type { ModelStudent } from '../model/student.ts';
|
|
13
18
|
|
|
14
19
|
import { $locale } from '../.metadata/locales.ts';
|
|
20
|
+
import { DtoStudentAggregateRes } from '../dto/studentAggregateRes.tsx';
|
|
15
21
|
import { DtoStudentCreate } from '../dto/studentCreate.tsx';
|
|
16
22
|
import { DtoStudentDeleteBulk } from '../dto/studentDeleteBulk.tsx';
|
|
23
|
+
import { DtoStudentGroupResItem } from '../dto/studentGroupResItem.tsx';
|
|
17
24
|
import { DtoStudentSelectReq } from '../dto/studentSelectReq.tsx';
|
|
18
25
|
import { DtoStudentSelectRes } from '../dto/studentSelectRes.tsx';
|
|
19
26
|
import { DtoStudentSummary } from '../dto/studentSummary.tsx';
|
|
@@ -44,6 +51,26 @@ export class ControllerStudent extends BeanBase {
|
|
|
44
51
|
return await this.scope.service.student.select(params);
|
|
45
52
|
}
|
|
46
53
|
|
|
54
|
+
@Web.get('group', { summary: $locale('StudentGroup') })
|
|
55
|
+
@Api.body(v.array(DtoStudentGroupResItem))
|
|
56
|
+
@Passport.rbac({ actionInherit: 'select' })
|
|
57
|
+
async group(
|
|
58
|
+
@Arg.filter(DtoStudentSelectReq, { mode: 'group' })
|
|
59
|
+
params: TypeAnalysisGroupParams<typeof studentAnalysis>,
|
|
60
|
+
): Promise<DtoStudentGroupResItem[]> {
|
|
61
|
+
return await this.scope.service.student.group(params);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
@Web.get('aggregate', { summary: $locale('StudentAggregate') })
|
|
65
|
+
@Api.body(DtoStudentAggregateRes)
|
|
66
|
+
@Passport.rbac({ actionInherit: 'select' })
|
|
67
|
+
async aggregate(
|
|
68
|
+
@Arg.filter(DtoStudentSelectReq, { mode: 'aggregate' })
|
|
69
|
+
params: TypeAnalysisAggregateParams<typeof studentAnalysis>,
|
|
70
|
+
): Promise<DtoStudentAggregateRes> {
|
|
71
|
+
return await this.scope.service.student.aggregate(params);
|
|
72
|
+
}
|
|
73
|
+
|
|
47
74
|
@Web.get(':id', { summary: $locale('StudentView') })
|
|
48
75
|
@Api.body(v.optional(), v.object(DtoStudentView))
|
|
49
76
|
@Core.serializer()
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { IDecoratorDtoOptions } from 'vona-module-a-web';
|
|
2
|
+
|
|
3
|
+
import { $Dto } from 'vona-module-a-orm';
|
|
4
|
+
import { Dto } from 'vona-module-a-web';
|
|
5
|
+
|
|
6
|
+
import { studentAnalysis } from '../lib/studentAnalysis.ts';
|
|
7
|
+
|
|
8
|
+
export interface IDtoOptionsStudentAggregateRes extends IDecoratorDtoOptions {}
|
|
9
|
+
|
|
10
|
+
@Dto<IDtoOptionsStudentAggregateRes>()
|
|
11
|
+
export class DtoStudentAggregateRes extends $Dto.analysisAggregate(studentAnalysis) {}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { IDecoratorDtoOptions } from 'vona-module-a-web';
|
|
2
|
+
|
|
3
|
+
import { $Dto } from 'vona-module-a-orm';
|
|
4
|
+
import { Dto } from 'vona-module-a-web';
|
|
5
|
+
|
|
6
|
+
import { studentAnalysis } from '../lib/studentAnalysis.ts';
|
|
7
|
+
|
|
8
|
+
export interface IDtoOptionsStudentGroupResItem extends IDecoratorDtoOptions {}
|
|
9
|
+
|
|
10
|
+
@Dto<IDtoOptionsStudentGroupResItem>()
|
|
11
|
+
export class DtoStudentGroupResItem extends $Dto.analysisGroup(studentAnalysis) {}
|
|
@@ -8,10 +8,12 @@ import { ZovaRender } from 'zova-rest-cabloy-basic-admin';
|
|
|
8
8
|
|
|
9
9
|
import { $locale } from '../.metadata/locales.ts';
|
|
10
10
|
import { EntityStudent, studentLevelItems } from '../entity/student.tsx';
|
|
11
|
+
import { studentAnalysis } from '../lib/studentAnalysis.ts';
|
|
11
12
|
|
|
12
13
|
export interface IDtoOptionsStudentSelectReq extends IDecoratorDtoOptions {}
|
|
13
14
|
|
|
14
15
|
@Dto<IDtoOptionsStudentSelectReq>({
|
|
16
|
+
analysis: studentAnalysis,
|
|
15
17
|
openapi: { filter: { table: 'trainingStudent' } },
|
|
16
18
|
fields: {
|
|
17
19
|
name: $makeSchema(v.optional(), z.string()),
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { $defineAnalysis } from 'vona-module-a-orm';
|
|
2
|
+
|
|
3
|
+
import { ModelStudent } from '../model/student.ts';
|
|
4
|
+
|
|
5
|
+
export const studentAnalysis = $defineAnalysis(() => ModelStudent, {
|
|
6
|
+
groups: ['level'],
|
|
7
|
+
aggrs: { count: ['*', 'imageId'] },
|
|
8
|
+
defaults: {
|
|
9
|
+
group: { groups: ['level'], aggrs: { count: ['*'] } },
|
|
10
|
+
aggregate: { aggrs: { count: ['*', 'imageId'] } },
|
|
11
|
+
},
|
|
12
|
+
});
|