vona-cli-set-api 1.1.174 → 1.1.176
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/cli/templates/tools/crudAnalysis/aggregate/src/dto/{{resourceName}}AggregateRes.tsx_ +11 -0
- package/cli/templates/tools/crudAnalysis/common/src/lib/{{resourceName}}Analysis.ts_ +14 -0
- package/cli/templates/tools/crudAnalysis/common/test/{{resourceName}}Analysis.test.ts_ +81 -0
- package/cli/templates/tools/crudAnalysis/group/src/dto/{{resourceName}}GroupResItem.tsx_ +11 -0
- package/cli/templates/tools/crudBasic/boilerplate/src/controller/{{resourceName}}.ts_ +18 -1
- package/cli/templates/tools/crudBasic/boilerplate/src/dto/{{resourceName}}SelectReq.tsx_ +2 -0
- package/cli/templates/tools/crudBasic/boilerplate/src/service/{{resourceName}}.ts_ +14 -1
- package/cli/templates/tools/crudBasic/boilerplate/test/{{resourceName}}.test.ts_ +4 -0
- package/cli/templates/tools/crudBasic/snippets/3-en-us.ts +2 -0
- package/cli/templates/tools/crudBasic/snippets/4-zh-cn.ts +2 -0
- package/cli/templates/tools/crudStart/boilerplate/src/controller/{{resourceName}}.ts_ +18 -1
- package/cli/templates/tools/crudStart/boilerplate/src/dto/{{resourceName}}SelectReq.tsx_ +2 -0
- package/cli/templates/tools/crudStart/boilerplate/src/service/{{resourceName}}.ts_ +14 -1
- package/cli/templates/tools/crudStart/boilerplate/test/{{resourceName}}.test.ts_ +1 -1
- package/cli/templates/tools/crudStart/snippets/3-en-us.ts +2 -0
- package/cli/templates/tools/crudStart/snippets/4-zh-cn.ts +2 -0
- package/dist/index.js +15 -4
- package/dist/lib/crudAnalysis.d.ts +2 -0
- package/dist-cli/templates/tools/crudBasic/snippets/3-en-us.js +2 -0
- package/dist-cli/templates/tools/crudBasic/snippets/4-zh-cn.js +2 -0
- package/dist-cli/templates/tools/crudStart/snippets/3-en-us.js +2 -0
- package/dist-cli/templates/tools/crudStart/snippets/4-zh-cn.js +2 -0
- package/dist-toolsIsolate/test.js +3 -6
- package/dist-toolsIsolate/testDiagnostics.d.ts +1 -0
- package/dist-toolsIsolate/testDiagnostics.js +6 -0
- package/package.json +4 -4
|
@@ -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 { <%=argv.resourceName%>Analysis } from '../lib/<%=argv.resourceName%>Analysis.ts';
|
|
7
|
+
|
|
8
|
+
export interface IDtoOptions<%=argv.resourceNameCapitalize%>AggregateRes extends IDecoratorDtoOptions {}
|
|
9
|
+
|
|
10
|
+
@Dto<IDtoOptions<%=argv.resourceNameCapitalize%>AggregateRes>()
|
|
11
|
+
export class Dto<%=argv.resourceNameCapitalize%>AggregateRes extends $Dto.analysisAggregate(<%=argv.resourceName%>Analysis) {}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { $defineAnalysis } from 'vona-module-a-orm';
|
|
2
|
+
|
|
3
|
+
import { Model<%=argv.resourceNameCapitalize%> } from '../model/<%=argv.resourceName%>.ts';
|
|
4
|
+
|
|
5
|
+
// One allowlist drives request controls, response fields, and bounded execution.
|
|
6
|
+
export const <%=argv.resourceName%>Analysis = $defineAnalysis(() => Model<%=argv.resourceNameCapitalize%>, {
|
|
7
|
+
groups: ['name'],
|
|
8
|
+
aggrs: { count: ['*'] },
|
|
9
|
+
defaults: {
|
|
10
|
+
group: { groups: ['name'], aggrs: { count: ['*'] } },
|
|
11
|
+
aggregate: { aggrs: { count: ['*'] } },
|
|
12
|
+
},
|
|
13
|
+
limits: { maxGroups: 3, maxMetrics: 10, maxGroupRows: 1000 },
|
|
14
|
+
});
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { describe, it } from 'node:test';
|
|
4
|
+
import { app } from 'vona-mock';
|
|
5
|
+
|
|
6
|
+
import { <%=argv.resourceName%>Analysis } from '../src/lib/<%=argv.resourceName%>Analysis.ts';
|
|
7
|
+
|
|
8
|
+
describe('<%=argv.resourceName%>Analysis.test.ts', { concurrency: false }, () => {
|
|
9
|
+
it('returns default and requested-only string counts with validated controls', async () => {
|
|
10
|
+
await app.bean.executor.mockCtx(async () => {
|
|
11
|
+
const scope = app.scope('<%=argv.moduleInfo.relativeName%>');
|
|
12
|
+
const name = `__analysis_${randomUUID()}`;
|
|
13
|
+
const fixture = await scope.model.<%=argv.resourceName%>.insert({ name });
|
|
14
|
+
try {
|
|
15
|
+
await app.bean.passport.signinMock();
|
|
16
|
+
const queries = [
|
|
17
|
+
{ name },
|
|
18
|
+
// List controls are validated but do not paginate the analysis population.
|
|
19
|
+
{ where: { id: fixture.id }, columns: ['id'], orders: 'name,desc', pageNo: 2, pageSize: 1 },
|
|
20
|
+
];
|
|
21
|
+
for (const query of queries) {
|
|
22
|
+
const groups = await app.bean.executor.performAction('get', '<%=argv.moduleActionPathRaw%>/group', { query });
|
|
23
|
+
assert.deepEqual(groups, [{ name, count_all: '1' }]);
|
|
24
|
+
assert.deepEqual(await app.bean.executor.performAction('get', '<%=argv.moduleActionPathRaw%>/group', {
|
|
25
|
+
query: { ...query, groups: JSON.stringify(['name']), aggrs: JSON.stringify({ count: ['*'] }) },
|
|
26
|
+
}), groups);
|
|
27
|
+
assert.deepEqual(await scope.service.<%=argv.resourceName%>.group({
|
|
28
|
+
where: { id: fixture.id }, groups: ['name'], aggrs: { count: ['*'] },
|
|
29
|
+
}), groups);
|
|
30
|
+
const aggregate = await app.bean.executor.performAction('get', '<%=argv.moduleActionPathRaw%>/aggregate', { query });
|
|
31
|
+
assert.deepEqual(aggregate, { count_all: '1' });
|
|
32
|
+
assert.deepEqual(await app.bean.executor.performAction('get', '<%=argv.moduleActionPathRaw%>/aggregate', {
|
|
33
|
+
query: { ...query, aggrs: JSON.stringify({ count: ['*'] }) },
|
|
34
|
+
}), aggregate);
|
|
35
|
+
assert.deepEqual(await scope.service.<%=argv.resourceName%>.aggregate({
|
|
36
|
+
where: { id: fixture.id }, aggrs: { count: ['*'] },
|
|
37
|
+
}), aggregate);
|
|
38
|
+
}
|
|
39
|
+
for (const action of ['group', 'aggregate'] as const) {
|
|
40
|
+
for (const controls of [
|
|
41
|
+
{ pageNo: 2, pageSize: 0 },
|
|
42
|
+
{ aggrs: JSON.stringify({}) },
|
|
43
|
+
{ aggrs: JSON.stringify({ count: ['*', '*'] }) },
|
|
44
|
+
{ aggrs: JSON.stringify({ count: ['unknown'] }) },
|
|
45
|
+
{ aggrs: '{invalid-json' },
|
|
46
|
+
]) {
|
|
47
|
+
await assert.rejects(() => app.bean.executor.performAction('get', `<%=argv.moduleActionPathRaw%>/${action}`, {
|
|
48
|
+
query: { name, ...controls },
|
|
49
|
+
}));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// The shared policy does not enable analysis controls on ordinary select.
|
|
53
|
+
await assert.rejects(() => app.bean.executor.performAction('get', '<%=argv.moduleActionPathRaw%>', {
|
|
54
|
+
query: { name, aggrs: JSON.stringify({ count: ['*'] }) },
|
|
55
|
+
}));
|
|
56
|
+
for (const groups of [[], ['name', 'name'], ['description'], ['unknown']]) {
|
|
57
|
+
await assert.rejects(() => app.bean.executor.performAction('get', '<%=argv.moduleActionPathRaw%>/group', {
|
|
58
|
+
query: { name, groups: JSON.stringify(groups) },
|
|
59
|
+
}));
|
|
60
|
+
}
|
|
61
|
+
await assert.rejects(() => scope.service.<%=argv.resourceName%>.group({ aggrs: { count: [] } }));
|
|
62
|
+
assert.deepEqual(<%=argv.resourceName%>Analysis.defaults.group, {
|
|
63
|
+
groups: ['name'], aggrs: { count: ['*'] },
|
|
64
|
+
});
|
|
65
|
+
await assert.rejects(() => app.bean.executor.performAction('get', '<%=argv.moduleActionPathRaw%>/aggregate', {
|
|
66
|
+
query: { name, groups: JSON.stringify(['name']) },
|
|
67
|
+
}));
|
|
68
|
+
await assert.rejects(() => scope.service.<%=argv.resourceName%>.aggregate({ aggrs: { count: [] } }));
|
|
69
|
+
assert.deepEqual(<%=argv.resourceName%>Analysis.defaults.aggregate, { aggrs: { count: ['*'] } });
|
|
70
|
+
await scope.model.<%=argv.resourceName%>.deleteById(fixture.id);
|
|
71
|
+
for (const query of queries) {
|
|
72
|
+
assert.deepEqual(await app.bean.executor.performAction('get', '<%=argv.moduleActionPathRaw%>/group', { query }), []);
|
|
73
|
+
assert.deepEqual(await app.bean.executor.performAction('get', '<%=argv.moduleActionPathRaw%>/aggregate', { query }), { count_all: '0' });
|
|
74
|
+
}
|
|
75
|
+
} finally {
|
|
76
|
+
await scope.model.<%=argv.resourceName%>.deleteById(fixture.id, { disableDeleted: true });
|
|
77
|
+
await app.bean.passport.signout();
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
});
|
|
@@ -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 { <%=argv.resourceName%>Analysis } from '../lib/<%=argv.resourceName%>Analysis.ts';
|
|
7
|
+
|
|
8
|
+
export interface IDtoOptions<%=argv.resourceNameCapitalize%>GroupResItem extends IDecoratorDtoOptions {}
|
|
9
|
+
|
|
10
|
+
@Dto<IDtoOptions<%=argv.resourceNameCapitalize%>GroupResItem>()
|
|
11
|
+
export class Dto<%=argv.resourceNameCapitalize%>GroupResItem extends $Dto.analysisGroup(<%=argv.resourceName%>Analysis) {}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { TableIdentity } from 'table-identity';
|
|
2
|
-
import type { IQueryParams } from 'vona-module-a-orm';
|
|
2
|
+
import type { IQueryParams, TypeAnalysisGroupParams, TypeAnalysisAggregateParams } from 'vona-module-a-orm';
|
|
3
3
|
import type { IDecoratorControllerOptions } from 'vona-module-a-web';
|
|
4
4
|
|
|
5
5
|
import { BeanBase } from 'vona';
|
|
@@ -17,6 +17,9 @@ import { Dto<%=argv.resourceNameCapitalize%>SelectReq } from '../dto/<%=argv.res
|
|
|
17
17
|
import { Dto<%=argv.resourceNameCapitalize%>SelectRes } from '../dto/<%=argv.resourceName%>SelectRes.tsx';
|
|
18
18
|
import { Dto<%=argv.resourceNameCapitalize%>Update } from '../dto/<%=argv.resourceName%>Update.tsx';
|
|
19
19
|
import { Dto<%=argv.resourceNameCapitalize%>View } from '../dto/<%=argv.resourceName%>View.tsx';
|
|
20
|
+
import { Dto<%=argv.resourceNameCapitalize%>GroupResItem } from '../dto/<%=argv.resourceName%>GroupResItem.tsx';
|
|
21
|
+
import { Dto<%=argv.resourceNameCapitalize%>AggregateRes } from '../dto/<%=argv.resourceName%>AggregateRes.tsx';
|
|
22
|
+
import type { <%=argv.resourceName%>Analysis } from '../lib/<%=argv.resourceName%>Analysis.ts';
|
|
20
23
|
|
|
21
24
|
export interface IControllerOptions<%=argv.resourceNameCapitalize%> extends IDecoratorControllerOptions {}
|
|
22
25
|
|
|
@@ -39,6 +42,20 @@ export class Controller<%=argv.resourceNameCapitalize%> extends BeanBase {
|
|
|
39
42
|
return await this.scope.service.<%=argv.resourceName%>.select(params);
|
|
40
43
|
}
|
|
41
44
|
|
|
45
|
+
@Web.get('group', { summary: $locale('<%=argv.resourceNameCapitalize%>Group') })
|
|
46
|
+
@Api.body(v.array(v.object(Dto<%=argv.resourceNameCapitalize%>GroupResItem)))
|
|
47
|
+
@Passport.rbac({ actionInherit: 'select' })
|
|
48
|
+
async group(@Arg.filter(Dto<%=argv.resourceNameCapitalize%>SelectReq, { mode: 'group' }) params: TypeAnalysisGroupParams<typeof <%=argv.resourceName%>Analysis>): Promise<Dto<%=argv.resourceNameCapitalize%>GroupResItem[]> {
|
|
49
|
+
return await this.scope.service.<%=argv.resourceName%>.group(params);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@Web.get('aggregate', { summary: $locale('<%=argv.resourceNameCapitalize%>Aggregate') })
|
|
53
|
+
@Api.body(Dto<%=argv.resourceNameCapitalize%>AggregateRes)
|
|
54
|
+
@Passport.rbac({ actionInherit: 'select' })
|
|
55
|
+
async aggregate(@Arg.filter(Dto<%=argv.resourceNameCapitalize%>SelectReq, { mode: 'aggregate' }) params: TypeAnalysisAggregateParams<typeof <%=argv.resourceName%>Analysis>): Promise<Dto<%=argv.resourceNameCapitalize%>AggregateRes> {
|
|
56
|
+
return await this.scope.service.<%=argv.resourceName%>.aggregate(params);
|
|
57
|
+
}
|
|
58
|
+
|
|
42
59
|
@Web.get(':id', { summary: $locale('<%=argv.resourceNameCapitalize%>View') })
|
|
43
60
|
@Api.body(v.optional(), v.object(Dto<%=argv.resourceNameCapitalize%>View))
|
|
44
61
|
@Passport.rbac()
|
|
@@ -7,10 +7,12 @@ import z from 'zod';
|
|
|
7
7
|
import { ZovaRender } from 'zova-rest-cabloy-basic-admin';
|
|
8
8
|
|
|
9
9
|
import { Entity<%=argv.resourceNameCapitalize%> } from '../entity/<%=argv.resourceName%>.tsx';
|
|
10
|
+
import { <%=argv.resourceName%>Analysis } from '../lib/<%=argv.resourceName%>Analysis.ts';
|
|
10
11
|
|
|
11
12
|
export interface IDtoOptions<%=argv.resourceNameCapitalize%>SelectReq extends IDecoratorDtoOptions {}
|
|
12
13
|
|
|
13
14
|
@Dto<IDtoOptions<%=argv.resourceNameCapitalize%>SelectReq>({
|
|
15
|
+
analysis: <%=argv.resourceName%>Analysis,
|
|
14
16
|
openapi: { filter: { table: '<%=argv.moduleResourceName%>' } },
|
|
15
17
|
fields: {
|
|
16
18
|
name: $makeSchema(v.optional(), z.string()),
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { TableIdentity } from 'table-identity';
|
|
2
|
-
import type { IQueryParams } from 'vona-module-a-orm';
|
|
2
|
+
import type { IQueryParams, TypeAnalysisGroupParams, TypeAnalysisAggregateParams } from 'vona-module-a-orm';
|
|
3
3
|
|
|
4
4
|
import { BeanBase } from 'vona';
|
|
5
5
|
import { Service } from 'vona-module-a-bean';
|
|
6
6
|
import { Core } from 'vona-module-a-core';
|
|
7
|
+
import { $executeAnalysisGroup, $executeAnalysisAggregate } from 'vona-module-a-orm';
|
|
7
8
|
|
|
8
9
|
import type { Dto<%=argv.resourceNameCapitalize%>Create } from '../dto/<%=argv.resourceName%>Create.tsx';
|
|
9
10
|
import type { Dto<%=argv.resourceNameCapitalize%>SelectRes } from '../dto/<%=argv.resourceName%>SelectRes.tsx';
|
|
@@ -11,6 +12,10 @@ import type { Dto<%=argv.resourceNameCapitalize%>Update } from '../dto/<%=argv.r
|
|
|
11
12
|
import type { Dto<%=argv.resourceNameCapitalize%>View } from '../dto/<%=argv.resourceName%>View.tsx';
|
|
12
13
|
import type { Entity<%=argv.resourceNameCapitalize%> } from '../entity/<%=argv.resourceName%>.tsx';
|
|
13
14
|
import type { Model<%=argv.resourceNameCapitalize%> } from '../model/<%=argv.resourceName%>.ts';
|
|
15
|
+
import type { Dto<%=argv.resourceNameCapitalize%>GroupResItem } from '../dto/<%=argv.resourceName%>GroupResItem.tsx';
|
|
16
|
+
import type { Dto<%=argv.resourceNameCapitalize%>AggregateRes } from '../dto/<%=argv.resourceName%>AggregateRes.tsx';
|
|
17
|
+
|
|
18
|
+
import { <%=argv.resourceName%>Analysis } from '../lib/<%=argv.resourceName%>Analysis.ts';
|
|
14
19
|
|
|
15
20
|
@Service()
|
|
16
21
|
export class Service<%=argv.resourceNameCapitalize%> extends BeanBase {
|
|
@@ -22,6 +27,14 @@ export class Service<%=argv.resourceNameCapitalize%> extends BeanBase {
|
|
|
22
27
|
return await this.scope.model.<%=argv.resourceName%>.selectAndCount(params);
|
|
23
28
|
}
|
|
24
29
|
|
|
30
|
+
async group(params?: TypeAnalysisGroupParams<typeof <%=argv.resourceName%>Analysis>): Promise<Dto<%=argv.resourceNameCapitalize%>GroupResItem[]> {
|
|
31
|
+
return await $executeAnalysisGroup(this.scope.model.<%=argv.resourceName%>, <%=argv.resourceName%>Analysis, params);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async aggregate(params?: TypeAnalysisAggregateParams<typeof <%=argv.resourceName%>Analysis>): Promise<Dto<%=argv.resourceNameCapitalize%>AggregateRes> {
|
|
35
|
+
return await $executeAnalysisAggregate(this.scope.model.<%=argv.resourceName%>, <%=argv.resourceName%>Analysis, params);
|
|
36
|
+
}
|
|
37
|
+
|
|
25
38
|
async view(id: TableIdentity): Promise<Dto<%=argv.resourceNameCapitalize%>View | undefined> {
|
|
26
39
|
return await this.scope.model.<%=argv.resourceName%>.getById(id);
|
|
27
40
|
}
|
|
@@ -29,6 +29,8 @@ describe('<%=argv.resourceName%>.test.ts', { concurrency: false }, () => {
|
|
|
29
29
|
const expectedSummaries = {
|
|
30
30
|
create: ['post', moduleApiPath, 'Create <%=argv.resourceNameCapitalize%>'],
|
|
31
31
|
select: ['get', moduleApiPath, 'Query <%=argv.resourceNameCapitalize%> List'],
|
|
32
|
+
group: ['get', `${moduleApiPath}/group`, 'Group <%=argv.resourceNameCapitalize%>'],
|
|
33
|
+
aggregate: ['get', `${moduleApiPath}/aggregate`, 'Aggregate <%=argv.resourceNameCapitalize%>'],
|
|
32
34
|
view: ['get', `${moduleApiPath}/{id}`, 'View <%=argv.resourceNameCapitalize%>'],
|
|
33
35
|
update: ['patch', `${moduleApiPath}/{id}`, 'Update <%=argv.resourceNameCapitalize%>'],
|
|
34
36
|
delete: ['delete', `${moduleApiPath}/{id}`, 'Delete <%=argv.resourceNameCapitalize%>'],
|
|
@@ -54,6 +56,8 @@ describe('<%=argv.resourceName%>.test.ts', { concurrency: false }, () => {
|
|
|
54
56
|
const expectedInherits = {
|
|
55
57
|
create: undefined,
|
|
56
58
|
select: undefined,
|
|
59
|
+
group: `${controllerBeanFullName}#select`,
|
|
60
|
+
aggregate: `${controllerBeanFullName}#select`,
|
|
57
61
|
view: undefined,
|
|
58
62
|
update: undefined,
|
|
59
63
|
delete: undefined,
|
|
@@ -40,6 +40,8 @@ export default metadataCustomSnippet({
|
|
|
40
40
|
[`${resource}Update`]: `Update ${resource}`,
|
|
41
41
|
[`${resource}Delete`]: `Delete ${resource}`,
|
|
42
42
|
[`${resource}DeleteBulk`]: `Bulk Delete ${resource}`,
|
|
43
|
+
[`${resource}Group`]: `Group ${resource}`,
|
|
44
|
+
[`${resource}Aggregate`]: `Aggregate ${resource}`,
|
|
43
45
|
};
|
|
44
46
|
return locale_transform({ ast, argv, resources });
|
|
45
47
|
},
|
|
@@ -23,6 +23,8 @@ export default metadataCustomSnippet({
|
|
|
23
23
|
[`${resource}Update`]: `更新${resource}`,
|
|
24
24
|
[`${resource}Delete`]: `删除${resource}`,
|
|
25
25
|
[`${resource}DeleteBulk`]: `批量删除${resource}`,
|
|
26
|
+
[`${resource}Group`]: `${resource}分组统计`,
|
|
27
|
+
[`${resource}Aggregate`]: `${resource}汇总统计`,
|
|
26
28
|
};
|
|
27
29
|
return locale_transform({ ast, argv, resources });
|
|
28
30
|
},
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { TableIdentity } from 'table-identity';
|
|
2
|
-
import type { IQueryParams } from 'vona-module-a-orm';
|
|
2
|
+
import type { IQueryParams, TypeAnalysisGroupParams, TypeAnalysisAggregateParams } from 'vona-module-a-orm';
|
|
3
3
|
import type { IRbacScopeAccess } from 'vona-module-a-rbac';
|
|
4
4
|
import type { IDecoratorControllerOptions } from 'vona-module-a-web';
|
|
5
5
|
|
|
@@ -18,6 +18,9 @@ import { Dto<%=argv.resourceNameCapitalize%>SelectReq } from '../dto/<%=argv.res
|
|
|
18
18
|
import { Dto<%=argv.resourceNameCapitalize%>SelectRes } from '../dto/<%=argv.resourceName%>SelectRes.tsx';
|
|
19
19
|
import { Dto<%=argv.resourceNameCapitalize%>Update } from '../dto/<%=argv.resourceName%>Update.tsx';
|
|
20
20
|
import { Dto<%=argv.resourceNameCapitalize%>View } from '../dto/<%=argv.resourceName%>View.tsx';
|
|
21
|
+
import { Dto<%=argv.resourceNameCapitalize%>GroupResItem } from '../dto/<%=argv.resourceName%>GroupResItem.tsx';
|
|
22
|
+
import { Dto<%=argv.resourceNameCapitalize%>AggregateRes } from '../dto/<%=argv.resourceName%>AggregateRes.tsx';
|
|
23
|
+
import type { <%=argv.resourceName%>Analysis } from '../lib/<%=argv.resourceName%>Analysis.ts';
|
|
21
24
|
|
|
22
25
|
export interface IControllerOptions<%=argv.resourceNameCapitalize%> extends IDecoratorControllerOptions {}
|
|
23
26
|
|
|
@@ -40,6 +43,20 @@ export class Controller<%=argv.resourceNameCapitalize%> extends BeanBase {
|
|
|
40
43
|
return await this.scope.service.<%=argv.resourceName%>.select(params);
|
|
41
44
|
}
|
|
42
45
|
|
|
46
|
+
@Web.get('group', { summary: $locale('<%=argv.resourceNameCapitalize%>Group') })
|
|
47
|
+
@Api.body(v.array(v.object(Dto<%=argv.resourceNameCapitalize%>GroupResItem)))
|
|
48
|
+
@Passport.rbac({ actionInherit: 'select' })
|
|
49
|
+
async group(@Arg.filter(Dto<%=argv.resourceNameCapitalize%>SelectReq, { mode: 'group' }) params: TypeAnalysisGroupParams<typeof <%=argv.resourceName%>Analysis>): Promise<Dto<%=argv.resourceNameCapitalize%>GroupResItem[]> {
|
|
50
|
+
return await this.scope.service.<%=argv.resourceName%>.group(params);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
@Web.get('aggregate', { summary: $locale('<%=argv.resourceNameCapitalize%>Aggregate') })
|
|
54
|
+
@Api.body(Dto<%=argv.resourceNameCapitalize%>AggregateRes)
|
|
55
|
+
@Passport.rbac({ actionInherit: 'select' })
|
|
56
|
+
async aggregate(@Arg.filter(Dto<%=argv.resourceNameCapitalize%>SelectReq, { mode: 'aggregate' }) params: TypeAnalysisAggregateParams<typeof <%=argv.resourceName%>Analysis>): Promise<Dto<%=argv.resourceNameCapitalize%>AggregateRes> {
|
|
57
|
+
return await this.scope.service.<%=argv.resourceName%>.aggregate(params);
|
|
58
|
+
}
|
|
59
|
+
|
|
43
60
|
@Web.get(':id', { summary: $locale('<%=argv.resourceNameCapitalize%>View') })
|
|
44
61
|
@Api.body(v.optional(), v.object(Dto<%=argv.resourceNameCapitalize%>View))
|
|
45
62
|
@Passport.rbac()
|
|
@@ -7,10 +7,12 @@ import z from 'zod';
|
|
|
7
7
|
import { ZovaRender } from 'zova-rest-cabloy-start-admin';
|
|
8
8
|
|
|
9
9
|
import { Entity<%=argv.resourceNameCapitalize%> } from '../entity/<%=argv.resourceName%>.tsx';
|
|
10
|
+
import { <%=argv.resourceName%>Analysis } from '../lib/<%=argv.resourceName%>Analysis.ts';
|
|
10
11
|
|
|
11
12
|
export interface IDtoOptions<%=argv.resourceNameCapitalize%>SelectReq extends IDecoratorDtoOptions {}
|
|
12
13
|
|
|
13
14
|
@Dto<IDtoOptions<%=argv.resourceNameCapitalize%>SelectReq>({
|
|
15
|
+
analysis: <%=argv.resourceName%>Analysis,
|
|
14
16
|
openapi: { filter: { table: '<%=argv.moduleResourceName%>' } },
|
|
15
17
|
fields: {
|
|
16
18
|
name: $makeSchema(v.optional(), z.string()),
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { TableIdentity } from 'table-identity';
|
|
2
|
-
import type { IQueryParams } from 'vona-module-a-orm';
|
|
2
|
+
import type { IQueryParams, TypeAnalysisGroupParams, TypeAnalysisAggregateParams } from 'vona-module-a-orm';
|
|
3
3
|
|
|
4
4
|
import { BeanBase } from 'vona';
|
|
5
5
|
import { Service } from 'vona-module-a-bean';
|
|
6
6
|
import { Core } from 'vona-module-a-core';
|
|
7
|
+
import { $executeAnalysisGroup, $executeAnalysisAggregate } from 'vona-module-a-orm';
|
|
7
8
|
|
|
8
9
|
import type { Dto<%=argv.resourceNameCapitalize%>Create } from '../dto/<%=argv.resourceName%>Create.tsx';
|
|
9
10
|
import type { Dto<%=argv.resourceNameCapitalize%>SelectRes } from '../dto/<%=argv.resourceName%>SelectRes.tsx';
|
|
@@ -11,6 +12,10 @@ import type { Dto<%=argv.resourceNameCapitalize%>Update } from '../dto/<%=argv.r
|
|
|
11
12
|
import type { Dto<%=argv.resourceNameCapitalize%>View } from '../dto/<%=argv.resourceName%>View.tsx';
|
|
12
13
|
import type { Entity<%=argv.resourceNameCapitalize%> } from '../entity/<%=argv.resourceName%>.tsx';
|
|
13
14
|
import type { Model<%=argv.resourceNameCapitalize%> } from '../model/<%=argv.resourceName%>.ts';
|
|
15
|
+
import type { Dto<%=argv.resourceNameCapitalize%>GroupResItem } from '../dto/<%=argv.resourceName%>GroupResItem.tsx';
|
|
16
|
+
import type { Dto<%=argv.resourceNameCapitalize%>AggregateRes } from '../dto/<%=argv.resourceName%>AggregateRes.tsx';
|
|
17
|
+
|
|
18
|
+
import { <%=argv.resourceName%>Analysis } from '../lib/<%=argv.resourceName%>Analysis.ts';
|
|
14
19
|
|
|
15
20
|
@Service()
|
|
16
21
|
export class Service<%=argv.resourceNameCapitalize%> extends BeanBase {
|
|
@@ -22,6 +27,14 @@ export class Service<%=argv.resourceNameCapitalize%> extends BeanBase {
|
|
|
22
27
|
return await this.scope.model.<%=argv.resourceName%>.selectAndCount(params);
|
|
23
28
|
}
|
|
24
29
|
|
|
30
|
+
async group(params?: TypeAnalysisGroupParams<typeof <%=argv.resourceName%>Analysis>): Promise<Dto<%=argv.resourceNameCapitalize%>GroupResItem[]> {
|
|
31
|
+
return await $executeAnalysisGroup(this.scope.model.<%=argv.resourceName%>, <%=argv.resourceName%>Analysis, params);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async aggregate(params?: TypeAnalysisAggregateParams<typeof <%=argv.resourceName%>Analysis>): Promise<Dto<%=argv.resourceNameCapitalize%>AggregateRes> {
|
|
35
|
+
return await $executeAnalysisAggregate(this.scope.model.<%=argv.resourceName%>, <%=argv.resourceName%>Analysis, params);
|
|
36
|
+
}
|
|
37
|
+
|
|
25
38
|
async view(id: TableIdentity): Promise<Dto<%=argv.resourceNameCapitalize%>View | undefined> {
|
|
26
39
|
return await this.scope.model.<%=argv.resourceName%>.getById(id);
|
|
27
40
|
}
|
|
@@ -19,7 +19,7 @@ describe('<%=argv.resourceName%>.test.ts', () => {
|
|
|
19
19
|
await app.bean.passport.signinMock();
|
|
20
20
|
try {
|
|
21
21
|
app.bean.passport.current!.roles = [];
|
|
22
|
-
const actions = ['create', 'select', 'view', 'update', 'delete'];
|
|
22
|
+
const actions = ['create', 'select', 'view', 'update', 'delete', 'group', 'aggregate'];
|
|
23
23
|
const permissions = await Promise.all(
|
|
24
24
|
actions.map(action =>
|
|
25
25
|
app.bean.permission.retrievePermissionAction(
|
|
@@ -40,6 +40,8 @@ export default metadataCustomSnippet({
|
|
|
40
40
|
[`${resource}View`]: `View ${resource}`,
|
|
41
41
|
[`${resource}Update`]: `Update ${resource}`,
|
|
42
42
|
[`${resource}Delete`]: `Delete ${resource}`,
|
|
43
|
+
[`${resource}Group`]: `Group ${resource}`,
|
|
44
|
+
[`${resource}Aggregate`]: `Aggregate ${resource}`,
|
|
43
45
|
};
|
|
44
46
|
return locale_transform({ ast, argv, resources });
|
|
45
47
|
},
|
|
@@ -23,6 +23,8 @@ export default metadataCustomSnippet({
|
|
|
23
23
|
[`${resource}View`]: `查看${resource}`,
|
|
24
24
|
[`${resource}Update`]: `更新${resource}`,
|
|
25
25
|
[`${resource}Delete`]: `删除${resource}`,
|
|
26
|
+
[`${resource}Group`]: `${resource}分组统计`,
|
|
27
|
+
[`${resource}Aggregate`]: `${resource}汇总统计`,
|
|
26
28
|
};
|
|
27
29
|
return locale_transform({ ast, argv, resources });
|
|
28
30
|
},
|
package/dist/index.js
CHANGED
|
@@ -1134,9 +1134,6 @@ class CliBinTest extends BeanCliBase {
|
|
|
1134
1134
|
if (argv.coverage) {
|
|
1135
1135
|
args.push('--experimental-test-coverage');
|
|
1136
1136
|
}
|
|
1137
|
-
if (process.env.TEST_WHYISNODERUNNING === 'true') {
|
|
1138
|
-
args.push('--import=why-is-node-running/include');
|
|
1139
|
-
}
|
|
1140
1137
|
args = args.concat([getImportEsm(resolveVonaLocalPaths(projectPath, 'test').generatedDir), testFile, projectPath, (!!argv.coverage).toString(), patterns.join(',')]);
|
|
1141
1138
|
// args = args.concat(['--experimental-transform-types', getImportEsm(), testFile, projectPath, (!!argv.coverage).toString(), patterns.join(',')]);
|
|
1142
1139
|
await runTestChild(args, projectPath);
|
|
@@ -1968,6 +1965,16 @@ class CliToolsCrud extends BeanCliBase {
|
|
|
1968
1965
|
}
|
|
1969
1966
|
}
|
|
1970
1967
|
|
|
1968
|
+
async function renderCrudAnalysis(cli, targetDir, setName) {
|
|
1969
|
+
for (const part of ['common', 'group', 'aggregate']) {
|
|
1970
|
+
await cli.template.renderBoilerplateAndSnippets({
|
|
1971
|
+
targetDir,
|
|
1972
|
+
setName,
|
|
1973
|
+
boilerplatePath: `tools/crudAnalysis/${part}`
|
|
1974
|
+
});
|
|
1975
|
+
}
|
|
1976
|
+
}
|
|
1977
|
+
|
|
1971
1978
|
class CliToolsCrudBasic extends BeanCliBase {
|
|
1972
1979
|
async execute() {
|
|
1973
1980
|
const {
|
|
@@ -1999,6 +2006,7 @@ class CliToolsCrudBasic extends BeanCliBase {
|
|
|
1999
2006
|
snippetsPath: 'tools/crudBasic/snippets',
|
|
2000
2007
|
boilerplatePath: 'tools/crudBasic/boilerplate'
|
|
2001
2008
|
});
|
|
2009
|
+
await renderCrudAnalysis(this, targetDir, __ThisSetName__);
|
|
2002
2010
|
// tools.metadata
|
|
2003
2011
|
if (!argv.nometadata) {
|
|
2004
2012
|
await this.helper.invokeCli([':tools:metadata', moduleName], {
|
|
@@ -2039,6 +2047,7 @@ class CliToolsCrudStart extends BeanCliBase {
|
|
|
2039
2047
|
snippetsPath: 'tools/crudStart/snippets',
|
|
2040
2048
|
boilerplatePath: 'tools/crudStart/boilerplate'
|
|
2041
2049
|
});
|
|
2050
|
+
await renderCrudAnalysis(this, targetDir, __ThisSetName__);
|
|
2042
2051
|
// tools.metadata
|
|
2043
2052
|
if (!argv.nometadata) {
|
|
2044
2053
|
await this.helper.invokeCli([':tools:metadata', moduleName], {
|
|
@@ -2662,6 +2671,8 @@ class CliToolsMasterDetail extends BeanCliBase {
|
|
|
2662
2671
|
service: path.join(detailRoot, 'src/service', `${argv.detailResourceName}.ts`),
|
|
2663
2672
|
dtoDir: path.join(detailRoot, 'src/dto'),
|
|
2664
2673
|
test: path.join(detailRoot, 'test', `${argv.detailResourceName}.test.ts`),
|
|
2674
|
+
analysisTest: path.join(detailRoot, 'test', `${argv.detailResourceName}Analysis.test.ts`),
|
|
2675
|
+
analysis: path.join(detailRoot, 'src/lib', `${argv.detailResourceName}Analysis.ts`),
|
|
2665
2676
|
metaVersion: path.join(detailRoot, 'src/bean', 'meta.version.ts'),
|
|
2666
2677
|
metaIndex: path.join(detailRoot, 'src/bean', 'meta.index.ts')
|
|
2667
2678
|
};
|
|
@@ -2672,7 +2683,7 @@ class CliToolsMasterDetail extends BeanCliBase {
|
|
|
2672
2683
|
}
|
|
2673
2684
|
_removeStandaloneDetailSurface() {
|
|
2674
2685
|
const files = this._detailPaths();
|
|
2675
|
-
for (const file of [files.controller, files.service, files.test]) {
|
|
2686
|
+
for (const file of [files.controller, files.service, files.test, files.analysisTest, files.analysis]) {
|
|
2676
2687
|
if (fs.existsSync(file)) {
|
|
2677
2688
|
fs.rmSync(file, {
|
|
2678
2689
|
force: true
|
|
@@ -30,6 +30,8 @@ export default metadataCustomSnippet({
|
|
|
30
30
|
[`${resource}Update`]: `Update ${resource}`,
|
|
31
31
|
[`${resource}Delete`]: `Delete ${resource}`,
|
|
32
32
|
[`${resource}DeleteBulk`]: `Bulk Delete ${resource}`,
|
|
33
|
+
[`${resource}Group`]: `Group ${resource}`,
|
|
34
|
+
[`${resource}Aggregate`]: `Aggregate ${resource}`,
|
|
33
35
|
};
|
|
34
36
|
return locale_transform({ ast, argv, resources });
|
|
35
37
|
},
|
|
@@ -20,6 +20,8 @@ export default metadataCustomSnippet({
|
|
|
20
20
|
[`${resource}Update`]: `更新${resource}`,
|
|
21
21
|
[`${resource}Delete`]: `删除${resource}`,
|
|
22
22
|
[`${resource}DeleteBulk`]: `批量删除${resource}`,
|
|
23
|
+
[`${resource}Group`]: `${resource}分组统计`,
|
|
24
|
+
[`${resource}Aggregate`]: `${resource}汇总统计`,
|
|
23
25
|
};
|
|
24
26
|
return locale_transform({ ast, argv, resources });
|
|
25
27
|
},
|
|
@@ -30,6 +30,8 @@ export default metadataCustomSnippet({
|
|
|
30
30
|
[`${resource}View`]: `View ${resource}`,
|
|
31
31
|
[`${resource}Update`]: `Update ${resource}`,
|
|
32
32
|
[`${resource}Delete`]: `Delete ${resource}`,
|
|
33
|
+
[`${resource}Group`]: `Group ${resource}`,
|
|
34
|
+
[`${resource}Aggregate`]: `Aggregate ${resource}`,
|
|
33
35
|
};
|
|
34
36
|
return locale_transform({ ast, argv, resources });
|
|
35
37
|
},
|
|
@@ -20,6 +20,8 @@ export default metadataCustomSnippet({
|
|
|
20
20
|
[`${resource}View`]: `查看${resource}`,
|
|
21
21
|
[`${resource}Update`]: `更新${resource}`,
|
|
22
22
|
[`${resource}Delete`]: `删除${resource}`,
|
|
23
|
+
[`${resource}Group`]: `${resource}分组统计`,
|
|
24
|
+
[`${resource}Aggregate`]: `${resource}汇总统计`,
|
|
23
25
|
};
|
|
24
26
|
return locale_transform({ ast, argv, resources });
|
|
25
27
|
},
|
|
@@ -10,7 +10,7 @@ import { run } from 'node:test';
|
|
|
10
10
|
import { lcov, spec } from 'node:test/reporters';
|
|
11
11
|
import { fileURLToPath } from 'node:url';
|
|
12
12
|
import { cast, createGeneralApp } from 'vona-core';
|
|
13
|
-
import
|
|
13
|
+
import { formatActiveResourceCounts } from "./testDiagnostics.js";
|
|
14
14
|
const argv = process.argv.slice(2);
|
|
15
15
|
const projectPath = argv[0];
|
|
16
16
|
const coverage = argv[1] === 'true';
|
|
@@ -66,13 +66,10 @@ async function testRun(projectPath, coverage, patterns) {
|
|
|
66
66
|
const closeApplication = async () => {
|
|
67
67
|
const [_, error] = await catchError(() => app.close());
|
|
68
68
|
closeError = error;
|
|
69
|
-
//
|
|
69
|
+
// resource types only; no stack/source inspection
|
|
70
70
|
if (process.env.TEST_WHYISNODERUNNING === 'true') {
|
|
71
71
|
await sleep(2000);
|
|
72
|
-
|
|
73
|
-
if (handles.length > 3) {
|
|
74
|
-
whyIsNodeRunning();
|
|
75
|
-
}
|
|
72
|
+
console.error(`Active resource counts: ${formatActiveResourceCounts(process.getActiveResourcesInfo())}`);
|
|
76
73
|
}
|
|
77
74
|
};
|
|
78
75
|
const closeApplicationOnce = () => {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function formatActiveResourceCounts(types: readonly string[]): string;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export function formatActiveResourceCounts(types) {
|
|
2
|
+
const counts = new Map();
|
|
3
|
+
for (const type of types)
|
|
4
|
+
counts.set(type, (counts.get(type) ?? 0) + 1);
|
|
5
|
+
return JSON.stringify(Object.fromEntries([...counts].toSorted(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))));
|
|
6
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vona-cli-set-api",
|
|
3
|
-
"version": "1.1.
|
|
4
|
-
"gitHead": "
|
|
3
|
+
"version": "1.1.176",
|
|
4
|
+
"gitHead": "d4ca173cd034aee6e73d037f86745c9919218f80",
|
|
5
5
|
"description": "vona cli-set-api",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"framework",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"@rollup/plugin-typescript": "^12.3.0",
|
|
64
64
|
"@swc-node/register": "^1.12.1",
|
|
65
65
|
"babel-plugin-transform-typescript-metadata": "^0.3.2",
|
|
66
|
-
"babel-plugin-vona-bean-module": "^1.1.
|
|
66
|
+
"babel-plugin-vona-bean-module": "^1.1.11",
|
|
67
67
|
"chalk": "^3.0.0",
|
|
68
68
|
"cli-table3": "^0.6.5",
|
|
69
69
|
"compressing": "^1.10.4",
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
"rollup": "^4.60.1",
|
|
78
78
|
"urllib": "^4.9.0",
|
|
79
79
|
"uuid": "^11.1.0",
|
|
80
|
-
"vona-core": "^5.1.
|
|
80
|
+
"vona-core": "^5.1.56",
|
|
81
81
|
"why-is-node-running": "^3.2.2",
|
|
82
82
|
"yargs-parser": "^22.0.0"
|
|
83
83
|
}
|