openxiangda-devkit-core 2.0.0-alpha.71 → 2.0.0-alpha.73
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/application-services.d.ts +9 -0
- package/dist/application-services.d.ts.map +1 -1
- package/dist/application-services.js +11 -1
- package/dist/application-services.js.map +1 -1
- package/dist/compiler/ai-catalog.d.ts.map +1 -1
- package/dist/compiler/ai-catalog.js +38 -4
- package/dist/compiler/ai-catalog.js.map +1 -1
- package/dist/compiler/bundle.d.ts.map +1 -1
- package/dist/compiler/bundle.js +218 -6
- package/dist/compiler/bundle.js.map +1 -1
- package/dist/compiler/config.d.ts +44 -1
- package/dist/compiler/config.d.ts.map +1 -1
- package/dist/compiler/config.js +380 -6
- package/dist/compiler/config.js.map +1 -1
- package/dist/compiler/field-codec.d.ts +7 -0
- package/dist/compiler/field-codec.d.ts.map +1 -1
- package/dist/compiler/field-codec.js +7 -0
- package/dist/compiler/field-codec.js.map +1 -1
- package/dist/compiler/schema-composition.js +8 -0
- package/dist/compiler/schema-composition.js.map +1 -1
- package/dist/workspace-loader.js +1 -1
- package/dist/workspace-loader.js.map +1 -1
- package/package.json +2 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type AppApiOperationDeclaration, type AppAuthorizationTransitionDeclaration, type AppBackendSecretDeclaration, type AppCapabilityDeclaration, type AppDataPolicyDeclaration, type AppDataPolicyExpressionDeclaration, type AppDataPolicyRuleDeclaration, type AppFrontendRouteDeclaration, type AppPerspectiveDeclaration, type AppRoleDeclaration, type AppRoleMembershipSourceDeclaration, type AppRelationshipGrantSourceDeclaration, type AppScopeDimensionDeclaration, type AppScopeSourceDeclaration, type NativeWorkflowEditableParameterDeclaration, type DataFieldDefinition, type DataFieldPolicy, type DataFieldSurfaceWidget, type DataResourceSurface, type DataResource, type Diagnostic, type ApplicationEventSchemaDeclaration, type DateTrigger, type EventSubscription, type TimerSubscription, type WorkflowBinding, type WorkflowDefinition } from 'openxiangda-contracts';
|
|
1
|
+
import { type AppApiOperationDeclaration, type AppAuthorizationTransitionDeclaration, type AppBackendSecretDeclaration, type AppCapabilityDeclaration, type AppDataPolicyDeclaration, type AppDataPolicyExpressionDeclaration, type AppDataPolicyRuleDeclaration, type AppFrontendRouteDeclaration, type AppAdminNavigationDeclaration, type AppAdminNavigationGroupDeclaration, type AppAdminNavigationItemDeclaration, type AppWorkflowLaunchDeclaration, type AppPerspectiveDeclaration, type AppRoleDeclaration, type AppRoleMembershipSourceDeclaration, type AppRelationshipGrantSourceDeclaration, type AppScopeDimensionDeclaration, type AppScopeSourceDeclaration, type NativeWorkflowEditableParameterDeclaration, type DataFieldDefinition, type DataFieldPolicy, type DataFieldSurfaceWidget, type DataResourceSurface, type DataResource, type Diagnostic, type ApplicationEventSchemaDeclaration, type DateTrigger, type EventSubscription, type TimerSubscription, type WorkflowBinding, type WorkflowDefinition } from 'openxiangda-contracts';
|
|
2
2
|
export type EventSubscriptionDeclaration = Pick<EventSubscription, 'code' | 'eventTypes'> & {
|
|
3
3
|
description?: string;
|
|
4
4
|
filter?: EventSubscription['filter'];
|
|
@@ -12,6 +12,7 @@ export type DateTriggerDeclaration = Pick<DateTrigger, 'code' | 'resourceCode' |
|
|
|
12
12
|
export interface WorkflowDefinitionDeclaration {
|
|
13
13
|
version: number;
|
|
14
14
|
definition: WorkflowDefinition;
|
|
15
|
+
launch?: AppWorkflowLaunchDeclaration;
|
|
15
16
|
}
|
|
16
17
|
export interface WorkflowBindingDeclaration {
|
|
17
18
|
version: number;
|
|
@@ -57,6 +58,8 @@ export interface AppDataFieldDeclaration {
|
|
|
57
58
|
maxLength?: DataFieldDefinition['maxLength'];
|
|
58
59
|
precision?: DataFieldDefinition['precision'];
|
|
59
60
|
scale?: DataFieldDefinition['scale'];
|
|
61
|
+
min?: DataFieldDefinition['min'];
|
|
62
|
+
max?: DataFieldDefinition['max'];
|
|
60
63
|
timePrecision?: DataFieldDefinition['timePrecision'];
|
|
61
64
|
file?: DataFieldDefinition['file'];
|
|
62
65
|
serial?: DataFieldDefinition['serial'];
|
|
@@ -73,7 +76,16 @@ export interface AppDataFieldDeclaration {
|
|
|
73
76
|
export interface AppDataResourceDeclaration {
|
|
74
77
|
code: string;
|
|
75
78
|
name: string;
|
|
79
|
+
mutationOwner?: 'native' | 'action' | 'readonly' | 'workflow';
|
|
80
|
+
generated?: {
|
|
81
|
+
list?: boolean;
|
|
82
|
+
detail?: boolean;
|
|
83
|
+
create?: boolean;
|
|
84
|
+
update?: boolean;
|
|
85
|
+
delete?: boolean;
|
|
86
|
+
};
|
|
76
87
|
fields: AppDataFieldDeclaration[];
|
|
88
|
+
invariants?: DataResource['invariants'];
|
|
77
89
|
list?: {
|
|
78
90
|
defaultPageSize?: number;
|
|
79
91
|
defaultSort?: {
|
|
@@ -113,6 +125,9 @@ export interface OpenXiangdaAppConfig {
|
|
|
113
125
|
frontend: {
|
|
114
126
|
root: string;
|
|
115
127
|
routes?: AppFrontendRouteDeclaration[];
|
|
128
|
+
admin?: {
|
|
129
|
+
navigation: AppAdminNavigationDeclaration;
|
|
130
|
+
};
|
|
116
131
|
};
|
|
117
132
|
backend: {
|
|
118
133
|
root: string;
|
|
@@ -156,6 +171,33 @@ export interface OpenXiangdaAppConfig {
|
|
|
156
171
|
editableParameters?: NativeWorkflowEditableParameterDeclaration[];
|
|
157
172
|
};
|
|
158
173
|
}
|
|
174
|
+
type AdminNavigationItemOptions = Omit<AppAdminNavigationItemDeclaration, 'page'>;
|
|
175
|
+
type AdminNavigationGroupOptions = Pick<AppAdminNavigationGroupDeclaration, 'icon' | 'order'>;
|
|
176
|
+
/** Explicitly owns the complete admin menu; the compiler never appends pages. */
|
|
177
|
+
export declare function defineAdminNavigation<const Groups extends AppAdminNavigationDeclaration>(groups: Groups): Groups;
|
|
178
|
+
export declare function adminNavigationGroup(code: string, label: string, items: readonly AppAdminNavigationItemDeclaration[], options?: AdminNavigationGroupOptions): AppAdminNavigationGroupDeclaration;
|
|
179
|
+
export declare function adminResourcePage(resourceCode: string, options?: AdminNavigationItemOptions): AppAdminNavigationItemDeclaration;
|
|
180
|
+
export declare function adminOperationPage(routeCode: string, options?: AdminNavigationItemOptions): AppAdminNavigationItemDeclaration;
|
|
181
|
+
export declare function adminWorkflowWorkCenterPage(options?: AdminNavigationItemOptions): AppAdminNavigationItemDeclaration;
|
|
182
|
+
export declare function adminWorkflowLaunchPage(workflowCode: string, options?: AdminNavigationItemOptions): AppAdminNavigationItemDeclaration;
|
|
183
|
+
/**
|
|
184
|
+
* Produces a deterministic one-time authoring suggestion for tooling. The
|
|
185
|
+
* compiler never calls this function and never mutates a saved declaration.
|
|
186
|
+
*/
|
|
187
|
+
export declare function suggestAdminNavigation(config: OpenXiangdaAppConfig): AppAdminNavigationDeclaration;
|
|
188
|
+
export interface AdminNavigationSuggestionSource {
|
|
189
|
+
module: 'openxiangda/config';
|
|
190
|
+
imports: readonly string[];
|
|
191
|
+
proposal: AppAdminNavigationDeclaration;
|
|
192
|
+
expression: string;
|
|
193
|
+
groupCount: number;
|
|
194
|
+
itemCount: number;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Renders the deterministic proposal as a typed expression that an AI author
|
|
198
|
+
* can copy once into frontend.admin.navigation and then edit normally.
|
|
199
|
+
*/
|
|
200
|
+
export declare function renderAdminNavigationSuggestion(config: OpenXiangdaAppConfig): AdminNavigationSuggestionSource;
|
|
159
201
|
export declare const openXiangdaAppConfigSchema: {
|
|
160
202
|
readonly $id: "openxiangda.app-config/v3";
|
|
161
203
|
readonly type: "object";
|
|
@@ -731,4 +773,5 @@ export declare function resourceReadPolicy(input: {
|
|
|
731
773
|
export declare function materializeDataResource(appCode: string, declaration: AppDataResourceDeclaration): DataResource;
|
|
732
774
|
export declare function validateAppDeclaration(value: unknown): Diagnostic[];
|
|
733
775
|
export declare function defineOpenXiangdaApp(declaration: OpenXiangdaAppDeclaration): OpenXiangdaAppConfig;
|
|
776
|
+
export {};
|
|
734
777
|
//# sourceMappingURL=config.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/compiler/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,0BAA0B,EAC/B,KAAK,qCAAqC,EAC1C,KAAK,2BAA2B,EAChC,KAAK,wBAAwB,EAC7B,KAAK,wBAAwB,EAC7B,KAAK,kCAAkC,EACvC,KAAK,4BAA4B,EACjC,KAAK,2BAA2B,EAChC,KAAK,yBAAyB,EAC9B,KAAK,kBAAkB,EACvB,KAAK,kCAAkC,EACvC,KAAK,qCAAqC,EAC1C,KAAK,4BAA4B,EACjC,KAAK,yBAAyB,EAC9B,KAAK,0CAA0C,EAC/C,KAAK,mBAAmB,EACxB,KAAK,eAAe,EACpB,KAAK,sBAAsB,EAC3B,KAAK,mBAAmB,EACxB,KAAK,YAAY,EACjB,KAAK,UAAU,EACf,KAAK,iCAAiC,EACtC,KAAK,WAAW,EAChB,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,kBAAkB,EACxB,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/compiler/config.ts"],"names":[],"mappings":"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"}
|
package/dist/compiler/config.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { SCHEMA_VERSIONS, DATA_FIELD_TYPES, PLATFORM_EVENT_TYPES_V2, nativePlatformCapabilityCatalog, validateDataResource, } from 'openxiangda-contracts';
|
|
2
2
|
import { Ajv2020 } from 'ajv/dist/2020.js';
|
|
3
3
|
import { CronExpressionParser } from 'cron-parser';
|
|
4
|
-
import { fieldNullable,
|
|
4
|
+
import { fieldNullable, supportsGeneratedMutation, } from './field-codec.js';
|
|
5
5
|
import { supportsFilter, supportsSearch, supportsSort, } from './field-query-plan.js';
|
|
6
6
|
import { physicalPlanForField } from './field-physical-plan.js';
|
|
7
7
|
import { SEMANTIC_FIELD_PATH_PATTERN, semanticFieldPathRoot, validateAuthzSemanticBindings, } from './field-policy-path.js';
|
|
@@ -19,6 +19,143 @@ const DATE_TRIGGER_RESERVED_DATA_FIELDS = new Set([
|
|
|
19
19
|
'dueAt',
|
|
20
20
|
]);
|
|
21
21
|
const APP_DATA_FIELD_TYPES = new Set(DATA_FIELD_TYPES);
|
|
22
|
+
function adminNavigationItem(page, options = {}) {
|
|
23
|
+
return Object.freeze({ page: Object.freeze(page), ...options });
|
|
24
|
+
}
|
|
25
|
+
/** Explicitly owns the complete admin menu; the compiler never appends pages. */
|
|
26
|
+
export function defineAdminNavigation(groups) {
|
|
27
|
+
return Object.freeze(groups.map(group => Object.freeze({
|
|
28
|
+
...group,
|
|
29
|
+
items: Object.freeze(group.items.map(item => Object.freeze({ ...item }))),
|
|
30
|
+
})));
|
|
31
|
+
}
|
|
32
|
+
export function adminNavigationGroup(code, label, items, options = {}) {
|
|
33
|
+
return Object.freeze({ code, label, items: Object.freeze([...items]), ...options });
|
|
34
|
+
}
|
|
35
|
+
export function adminResourcePage(resourceCode, options) {
|
|
36
|
+
return adminNavigationItem({ kind: 'resource', resourceCode }, options);
|
|
37
|
+
}
|
|
38
|
+
export function adminOperationPage(routeCode, options) {
|
|
39
|
+
return adminNavigationItem({ kind: 'operation', routeCode }, options);
|
|
40
|
+
}
|
|
41
|
+
export function adminWorkflowWorkCenterPage(options) {
|
|
42
|
+
return adminNavigationItem({ kind: 'workflow-work-center' }, options);
|
|
43
|
+
}
|
|
44
|
+
export function adminWorkflowLaunchPage(workflowCode, options) {
|
|
45
|
+
return adminNavigationItem({ kind: 'workflow-launch', workflowCode }, options);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Produces a deterministic one-time authoring suggestion for tooling. The
|
|
49
|
+
* compiler never calls this function and never mutates a saved declaration.
|
|
50
|
+
*/
|
|
51
|
+
export function suggestAdminNavigation(config) {
|
|
52
|
+
const groups = [];
|
|
53
|
+
const resources = [...(config.data?.resources || [])]
|
|
54
|
+
.filter(resource => resource.surface?.generated?.list !== false)
|
|
55
|
+
.sort((left, right) => left.code < right.code ? -1 : left.code > right.code ? 1 : 0);
|
|
56
|
+
if (resources.length > 0) {
|
|
57
|
+
groups.push(adminNavigationGroup('data-management', '数据管理', resources.map(resource => adminResourcePage(resource.code, { label: resource.name })), { icon: 'database', order: 100 }));
|
|
58
|
+
}
|
|
59
|
+
const routes = [...(config.frontend.routes || [])]
|
|
60
|
+
.filter(route => route.surface === 'admin' && !/[:*]/.test(route.path))
|
|
61
|
+
.sort((left, right) => left.code < right.code ? -1 : left.code > right.code ? 1 : 0);
|
|
62
|
+
if (routes.length > 0) {
|
|
63
|
+
groups.push(adminNavigationGroup('business-operations', '业务操作', routes.map(route => adminOperationPage(route.code, { label: route.label })), { icon: 'operations', order: 200 }));
|
|
64
|
+
}
|
|
65
|
+
const workflowCodes = [
|
|
66
|
+
...new Set((config.workflows?.definitions || []).map(declaration => declaration.definition.code)),
|
|
67
|
+
].sort();
|
|
68
|
+
const workflowItems = [];
|
|
69
|
+
if (workflowCodes.length > 0) {
|
|
70
|
+
workflowItems.push(adminWorkflowWorkCenterPage({ label: '我的审批' }));
|
|
71
|
+
}
|
|
72
|
+
for (const code of workflowCodes) {
|
|
73
|
+
const declarations = (config.workflows?.definitions || []).filter(declaration => declaration.definition.code === code);
|
|
74
|
+
const activeVersion = config.workflows?.activations.find(activation => activation.workflowCode === code)?.definitionVersion;
|
|
75
|
+
const selected = declarations.find(declaration => declaration.version === activeVersion) ||
|
|
76
|
+
[...declarations].sort((left, right) => right.version - left.version)[0];
|
|
77
|
+
if (selected?.launch?.mode === 'standalone') {
|
|
78
|
+
workflowItems.push(adminWorkflowLaunchPage(code, {
|
|
79
|
+
label: `发起${selected.definition.title}`,
|
|
80
|
+
}));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (workflowItems.length > 0) {
|
|
84
|
+
groups.push(adminNavigationGroup('workflow-center', '流程中心', workflowItems, {
|
|
85
|
+
icon: 'workflow',
|
|
86
|
+
order: 300,
|
|
87
|
+
}));
|
|
88
|
+
}
|
|
89
|
+
return defineAdminNavigation(groups);
|
|
90
|
+
}
|
|
91
|
+
function renderAdminNavigationOptions(options) {
|
|
92
|
+
const entries = [
|
|
93
|
+
...(options.label !== undefined
|
|
94
|
+
? [`label: ${JSON.stringify(options.label)}`]
|
|
95
|
+
: []),
|
|
96
|
+
...(options.icon !== undefined
|
|
97
|
+
? [`icon: ${JSON.stringify(options.icon)}`]
|
|
98
|
+
: []),
|
|
99
|
+
...(options.order !== undefined ? [`order: ${options.order}`] : []),
|
|
100
|
+
];
|
|
101
|
+
return entries.length > 0 ? `{ ${entries.join(', ')} }` : undefined;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Renders the deterministic proposal as a typed expression that an AI author
|
|
105
|
+
* can copy once into frontend.admin.navigation and then edit normally.
|
|
106
|
+
*/
|
|
107
|
+
export function renderAdminNavigationSuggestion(config) {
|
|
108
|
+
const navigation = suggestAdminNavigation(config);
|
|
109
|
+
const imports = new Set(['defineAdminNavigation']);
|
|
110
|
+
const groups = navigation.map(group => {
|
|
111
|
+
imports.add('adminNavigationGroup');
|
|
112
|
+
const items = group.items.map(item => {
|
|
113
|
+
const options = renderAdminNavigationOptions(item);
|
|
114
|
+
let expression;
|
|
115
|
+
switch (item.page.kind) {
|
|
116
|
+
case 'resource':
|
|
117
|
+
imports.add('adminResourcePage');
|
|
118
|
+
expression = `adminResourcePage(${JSON.stringify(item.page.resourceCode)}${options ? `, ${options}` : ''})`;
|
|
119
|
+
break;
|
|
120
|
+
case 'operation':
|
|
121
|
+
imports.add('adminOperationPage');
|
|
122
|
+
expression = `adminOperationPage(${JSON.stringify(item.page.routeCode)}${options ? `, ${options}` : ''})`;
|
|
123
|
+
break;
|
|
124
|
+
case 'workflow-work-center':
|
|
125
|
+
imports.add('adminWorkflowWorkCenterPage');
|
|
126
|
+
expression = `adminWorkflowWorkCenterPage(${options || ''})`;
|
|
127
|
+
break;
|
|
128
|
+
case 'workflow-launch':
|
|
129
|
+
imports.add('adminWorkflowLaunchPage');
|
|
130
|
+
expression = `adminWorkflowLaunchPage(${JSON.stringify(item.page.workflowCode)}${options ? `, ${options}` : ''})`;
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
return ` ${expression},`;
|
|
134
|
+
});
|
|
135
|
+
const options = renderAdminNavigationOptions({
|
|
136
|
+
icon: group.icon,
|
|
137
|
+
order: group.order,
|
|
138
|
+
});
|
|
139
|
+
return [
|
|
140
|
+
' adminNavigationGroup(',
|
|
141
|
+
` ${JSON.stringify(group.code)},`,
|
|
142
|
+
` ${JSON.stringify(group.label)},`,
|
|
143
|
+
' [',
|
|
144
|
+
...items,
|
|
145
|
+
' ],',
|
|
146
|
+
...(options ? [` ${options}`] : []),
|
|
147
|
+
' ),',
|
|
148
|
+
].join('\n');
|
|
149
|
+
});
|
|
150
|
+
return {
|
|
151
|
+
module: 'openxiangda/config',
|
|
152
|
+
imports: [...imports].sort(),
|
|
153
|
+
proposal: navigation,
|
|
154
|
+
expression: ['defineAdminNavigation([', ...groups, '])'].join('\n'),
|
|
155
|
+
groupCount: navigation.length,
|
|
156
|
+
itemCount: navigation.reduce((count, group) => count + group.items.length, 0),
|
|
157
|
+
};
|
|
158
|
+
}
|
|
22
159
|
const authorizationProjectionFieldPathSchema = {
|
|
23
160
|
type: 'string',
|
|
24
161
|
pattern: '^[A-Za-z_][A-Za-z0-9_]{0,127}(?:\\.(?:value|snapshot\\.[A-Za-z_][A-Za-z0-9_]{0,127}))?$',
|
|
@@ -625,6 +762,7 @@ export function validateAppConfig(value) {
|
|
|
625
762
|
diagnostics.push(diagnostic('APP_CONFIG_BACKEND_RESOURCE_PROFILE_UNSUPPORTED', 'backend.resourceProfile 必须是 light 或 standard', 'backend.resourceProfile'));
|
|
626
763
|
}
|
|
627
764
|
validateFrontendRoutes(frontend.routes, diagnostics);
|
|
765
|
+
validateAdminNavigation(config, diagnostics);
|
|
628
766
|
validatePerspectives(config.perspectives, object(config.authz).roles, diagnostics);
|
|
629
767
|
const declaredResourceCodes = new Set((Array.isArray(object(config.data).resources)
|
|
630
768
|
? object(config.data).resources
|
|
@@ -765,6 +903,24 @@ export function validateAppConfig(value) {
|
|
|
765
903
|
if (!Array.isArray(authz.roles)) {
|
|
766
904
|
diagnostics.push(diagnostic('APP_CONFIG_AUTHZ_ROLES_REQUIRED', 'authz.roles 必须是数组', 'authz.roles'));
|
|
767
905
|
}
|
|
906
|
+
const forbiddenNativeMutationCapabilities = new Map();
|
|
907
|
+
for (const rawResource of Array.isArray(data.resources)
|
|
908
|
+
? data.resources
|
|
909
|
+
: []) {
|
|
910
|
+
const resource = object(rawResource);
|
|
911
|
+
const mutationOwner = string(object(resource.surface).mutationOwner) || 'native';
|
|
912
|
+
if (mutationOwner === 'native')
|
|
913
|
+
continue;
|
|
914
|
+
for (const operation of ['create', 'update', 'delete']) {
|
|
915
|
+
const capability = string(object(resource.capabilities)[operation]);
|
|
916
|
+
if (capability) {
|
|
917
|
+
forbiddenNativeMutationCapabilities.set(capability, {
|
|
918
|
+
resourceCode: string(resource.code),
|
|
919
|
+
operation,
|
|
920
|
+
});
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
}
|
|
768
924
|
const roleCodes = new Set();
|
|
769
925
|
roles.forEach((raw, index) => {
|
|
770
926
|
const role = object(raw);
|
|
@@ -791,6 +947,10 @@ export function validateAppConfig(value) {
|
|
|
791
947
|
seenCapabilities.has(capability)) {
|
|
792
948
|
diagnostics.push(diagnostic('APP_CONFIG_AUTHZ_CAPABILITY_INVALID', 'capability 必须是非空稳定标识,且同一角色内不能重复', `authz.roles[${index}].capabilities[${capabilityIndex}]`));
|
|
793
949
|
}
|
|
950
|
+
const forbiddenMutation = forbiddenNativeMutationCapabilities.get(capability);
|
|
951
|
+
if (forbiddenMutation) {
|
|
952
|
+
diagnostics.push(diagnostic('APP_CONFIG_DATA_RESOURCE_NATIVE_MUTATION_GRANT_FORBIDDEN', `${forbiddenMutation.resourceCode} 的 ${forbiddenMutation.operation} 由非 Native owner 管理,应用角色不能获得对应 Native Data capability`, `authz.roles[${index}].capabilities[${capabilityIndex}]`));
|
|
953
|
+
}
|
|
794
954
|
seenCapabilities.add(capability);
|
|
795
955
|
});
|
|
796
956
|
});
|
|
@@ -1630,9 +1790,24 @@ export function validateAppConfig(value) {
|
|
|
1630
1790
|
const definition = object(declaration.definition);
|
|
1631
1791
|
const version = Number(declaration.version);
|
|
1632
1792
|
const path = `workflows.definitions[${index}]`;
|
|
1793
|
+
const launch = object(declaration.launch);
|
|
1794
|
+
if (declaration.launch !== undefined &&
|
|
1795
|
+
(Object.keys(launch).some(key => key !== 'mode') ||
|
|
1796
|
+
![
|
|
1797
|
+
'standalone',
|
|
1798
|
+
'custom-page',
|
|
1799
|
+
'hidden-handoff',
|
|
1800
|
+
'work-center-only',
|
|
1801
|
+
].includes(string(launch.mode)))) {
|
|
1802
|
+
diagnostics.push(diagnostic('APP_CONFIG_WORKFLOW_LAUNCH_MODE_INVALID', 'Workflow launch.mode 必须是 standalone、custom-page、hidden-handoff 或 work-center-only', `${path}.launch.mode`));
|
|
1803
|
+
}
|
|
1633
1804
|
if (!Number.isInteger(version) || version < 1) {
|
|
1634
1805
|
diagnostics.push(diagnostic('APP_CONFIG_WORKFLOW_VERSION_INVALID', 'Workflow definition version 必须是正整数', `${path}.version`));
|
|
1635
1806
|
}
|
|
1807
|
+
if (string(object(declaration.definition).title) ===
|
|
1808
|
+
string(object(declaration.definition).code)) {
|
|
1809
|
+
diagnostics.push(diagnostic('APP_CONFIG_WORKFLOW_USER_TITLE_REQUIRED', 'Workflow title 必须是用户可读本地化名称,不能等于内部 code', `${path}.definition.title`));
|
|
1810
|
+
}
|
|
1636
1811
|
for (const error of validateWorkflowDefinition(definition)) {
|
|
1637
1812
|
diagnostics.push(diagnostic('APP_CONFIG_WORKFLOW_DEFINITION_INVALID', error, path));
|
|
1638
1813
|
}
|
|
@@ -1892,7 +2067,6 @@ function validateFrontendRoutes(rawRoutes, diagnostics) {
|
|
|
1892
2067
|
'path',
|
|
1893
2068
|
'label',
|
|
1894
2069
|
'surface',
|
|
1895
|
-
'navigation',
|
|
1896
2070
|
'parentCode',
|
|
1897
2071
|
'capability',
|
|
1898
2072
|
'access',
|
|
@@ -1932,8 +2106,6 @@ function validateFrontendRoutes(rawRoutes, diagnostics) {
|
|
|
1932
2106
|
paths.has(routePath) ||
|
|
1933
2107
|
!string(route.label) ||
|
|
1934
2108
|
!['admin', 'user'].includes(string(route.surface)) ||
|
|
1935
|
-
(route.navigation !== undefined &&
|
|
1936
|
-
!['menu', 'hidden'].includes(string(route.navigation))) ||
|
|
1937
2109
|
(route.capability !== undefined &&
|
|
1938
2110
|
!CAPABILITY_PATTERN.test(string(route.capability))) ||
|
|
1939
2111
|
accessInvalid ||
|
|
@@ -1968,6 +2140,148 @@ function validateFrontendRoutes(rawRoutes, diagnostics) {
|
|
|
1968
2140
|
}
|
|
1969
2141
|
}
|
|
1970
2142
|
}
|
|
2143
|
+
const ADMIN_NAVIGATION_ICONS = new Set([
|
|
2144
|
+
'overview',
|
|
2145
|
+
'database',
|
|
2146
|
+
'operations',
|
|
2147
|
+
'workflow',
|
|
2148
|
+
'members',
|
|
2149
|
+
'calendar',
|
|
2150
|
+
'document',
|
|
2151
|
+
'folder',
|
|
2152
|
+
'settings',
|
|
2153
|
+
]);
|
|
2154
|
+
function validAdminOrder(value) {
|
|
2155
|
+
return (value === undefined ||
|
|
2156
|
+
(Number.isSafeInteger(value) && Math.abs(Number(value)) <= 10_000));
|
|
2157
|
+
}
|
|
2158
|
+
function validateAdminNavigation(config, diagnostics) {
|
|
2159
|
+
const frontend = object(config.frontend);
|
|
2160
|
+
const admin = object(frontend.admin);
|
|
2161
|
+
const rawNavigation = admin.navigation;
|
|
2162
|
+
if (frontend.admin !== undefined &&
|
|
2163
|
+
(Object.keys(admin).some(key => key !== 'navigation') ||
|
|
2164
|
+
!Array.isArray(rawNavigation))) {
|
|
2165
|
+
diagnostics.push(diagnostic('APP_CONFIG_ADMIN_NAVIGATION_INVALID', 'frontend.admin 只接受 navigation 数组', 'frontend.admin.navigation'));
|
|
2166
|
+
return;
|
|
2167
|
+
}
|
|
2168
|
+
const groups = Array.isArray(rawNavigation) ? rawNavigation : [];
|
|
2169
|
+
if (groups.length > 100) {
|
|
2170
|
+
diagnostics.push(diagnostic('APP_CONFIG_ADMIN_NAVIGATION_LIMIT_EXCEEDED', '后台导航最多包含 100 个业务分组', 'frontend.admin.navigation'));
|
|
2171
|
+
}
|
|
2172
|
+
const resources = new Map((config.data?.resources || []).map(resource => [resource.code, resource]));
|
|
2173
|
+
const routes = new Map((config.frontend.routes || []).map(route => [route.code, route]));
|
|
2174
|
+
const workflows = new Map();
|
|
2175
|
+
const workflowDefinitionsByCode = new Map();
|
|
2176
|
+
for (const declaration of config.workflows?.definitions || []) {
|
|
2177
|
+
const declarations = workflowDefinitionsByCode.get(declaration.definition.code) || [];
|
|
2178
|
+
declarations.push(declaration);
|
|
2179
|
+
workflowDefinitionsByCode.set(declaration.definition.code, declarations);
|
|
2180
|
+
}
|
|
2181
|
+
for (const [code, declarations] of workflowDefinitionsByCode) {
|
|
2182
|
+
const activeVersion = config.workflows?.activations.find(activation => activation.workflowCode === code)?.definitionVersion;
|
|
2183
|
+
const selected = declarations.find(declaration => declaration.version === activeVersion) ||
|
|
2184
|
+
[...declarations].sort((left, right) => right.version - left.version)[0];
|
|
2185
|
+
if (selected) {
|
|
2186
|
+
workflows.set(code, {
|
|
2187
|
+
title: selected.definition.title,
|
|
2188
|
+
mode: selected.launch?.mode || 'work-center-only',
|
|
2189
|
+
});
|
|
2190
|
+
}
|
|
2191
|
+
}
|
|
2192
|
+
const groupCodes = new Set();
|
|
2193
|
+
const pageKeys = new Set();
|
|
2194
|
+
let itemCount = 0;
|
|
2195
|
+
groups.forEach((rawGroup, groupIndex) => {
|
|
2196
|
+
const group = object(rawGroup);
|
|
2197
|
+
const path = `frontend.admin.navigation[${groupIndex}]`;
|
|
2198
|
+
const code = string(group.code);
|
|
2199
|
+
const label = string(group.label);
|
|
2200
|
+
const items = Array.isArray(group.items) ? group.items : [];
|
|
2201
|
+
if (Object.keys(group).some(key => !['code', 'label', 'icon', 'order', 'items'].includes(key)) ||
|
|
2202
|
+
!OPERATION_CODE_PATTERN.test(code) ||
|
|
2203
|
+
groupCodes.has(code) ||
|
|
2204
|
+
!label ||
|
|
2205
|
+
label === code ||
|
|
2206
|
+
(group.icon !== undefined &&
|
|
2207
|
+
!ADMIN_NAVIGATION_ICONS.has(string(group.icon))) ||
|
|
2208
|
+
!validAdminOrder(group.order)) {
|
|
2209
|
+
diagnostics.push(diagnostic('APP_CONFIG_ADMIN_GROUP_INVALID', '导航分组必须声明唯一 code、用户 label、受支持 icon/order 和 items', path));
|
|
2210
|
+
}
|
|
2211
|
+
if (items.length === 0) {
|
|
2212
|
+
diagnostics.push(diagnostic('APP_CONFIG_ADMIN_GROUP_ORPHANED', '导航分组必须至少引用一个可见页面', `${path}.items`));
|
|
2213
|
+
}
|
|
2214
|
+
groupCodes.add(code);
|
|
2215
|
+
items.forEach((rawItem, itemIndex) => {
|
|
2216
|
+
itemCount += 1;
|
|
2217
|
+
const item = object(rawItem);
|
|
2218
|
+
const itemPath = `${path}.items[${itemIndex}]`;
|
|
2219
|
+
const page = object(item.page);
|
|
2220
|
+
const kind = string(page.kind);
|
|
2221
|
+
let pageKey = '';
|
|
2222
|
+
let defaultLabel = '';
|
|
2223
|
+
let internalCode = '';
|
|
2224
|
+
let exists = false;
|
|
2225
|
+
let standaloneViolation = false;
|
|
2226
|
+
if (kind === 'resource') {
|
|
2227
|
+
const resourceCode = string(page.resourceCode);
|
|
2228
|
+
const resource = resources.get(resourceCode);
|
|
2229
|
+
pageKey = `resource:${resourceCode}:list`;
|
|
2230
|
+
defaultLabel = resource?.name || '';
|
|
2231
|
+
internalCode = resourceCode;
|
|
2232
|
+
exists = Boolean(resource?.surface?.generated?.list);
|
|
2233
|
+
}
|
|
2234
|
+
else if (kind === 'operation') {
|
|
2235
|
+
const routeCode = string(page.routeCode);
|
|
2236
|
+
const route = routes.get(routeCode);
|
|
2237
|
+
pageKey = `operation:${routeCode}`;
|
|
2238
|
+
defaultLabel = route?.label || '';
|
|
2239
|
+
internalCode = routeCode;
|
|
2240
|
+
exists = Boolean(route && route.surface === 'admin' && !/[:*]/.test(route.path));
|
|
2241
|
+
}
|
|
2242
|
+
else if (kind === 'workflow-work-center') {
|
|
2243
|
+
pageKey = 'workflow:work-center';
|
|
2244
|
+
defaultLabel = '我的审批';
|
|
2245
|
+
internalCode = pageKey;
|
|
2246
|
+
exists = workflows.size > 0;
|
|
2247
|
+
}
|
|
2248
|
+
else if (kind === 'workflow-launch') {
|
|
2249
|
+
const workflowCode = string(page.workflowCode);
|
|
2250
|
+
const workflow = workflows.get(workflowCode);
|
|
2251
|
+
pageKey = `workflow:${workflowCode}:launch`;
|
|
2252
|
+
defaultLabel = workflow ? `发起${workflow.title}` : '';
|
|
2253
|
+
internalCode = workflowCode;
|
|
2254
|
+
standaloneViolation = Boolean(workflow && workflow.mode !== 'standalone');
|
|
2255
|
+
exists = Boolean(workflow && workflow.mode === 'standalone');
|
|
2256
|
+
}
|
|
2257
|
+
if (Object.keys(item).some(key => !['page', 'label', 'icon', 'order'].includes(key)) ||
|
|
2258
|
+
!['resource', 'operation', 'workflow-work-center', 'workflow-launch'].includes(kind) ||
|
|
2259
|
+
(item.icon !== undefined &&
|
|
2260
|
+
!ADMIN_NAVIGATION_ICONS.has(string(item.icon))) ||
|
|
2261
|
+
!validAdminOrder(item.order)) {
|
|
2262
|
+
diagnostics.push(diagnostic('APP_CONFIG_ADMIN_ITEM_INVALID', '导航项必须使用受支持的页面 helper、label、icon 和 order', itemPath));
|
|
2263
|
+
}
|
|
2264
|
+
if (standaloneViolation) {
|
|
2265
|
+
diagnostics.push(diagnostic('APP_CONFIG_ADMIN_WORKFLOW_NOT_STANDALONE', '只有 self-contained standalone Workflow launch page 可以进入菜单', `${itemPath}.page`));
|
|
2266
|
+
}
|
|
2267
|
+
else if (!exists) {
|
|
2268
|
+
diagnostics.push(diagnostic('APP_CONFIG_ADMIN_PAGE_NOT_FOUND', '导航引用必须指向存在且可进入菜单的 page registry 页面', `${itemPath}.page`));
|
|
2269
|
+
}
|
|
2270
|
+
if (pageKey && pageKeys.has(pageKey)) {
|
|
2271
|
+
diagnostics.push(diagnostic('APP_CONFIG_ADMIN_PAGE_DUPLICATE', `同一页面不能重复出现在导航中: ${pageKey}`, `${itemPath}.page`));
|
|
2272
|
+
}
|
|
2273
|
+
if (pageKey)
|
|
2274
|
+
pageKeys.add(pageKey);
|
|
2275
|
+
const label = string(item.label) || defaultLabel;
|
|
2276
|
+
if (!label || label === internalCode || label === pageKey) {
|
|
2277
|
+
diagnostics.push(diagnostic('APP_CONFIG_ADMIN_USER_LABEL_REQUIRED', '导航页面必须解析为用户可读 label,不能泄漏内部 code', item.label === undefined ? `${itemPath}.page` : `${itemPath}.label`));
|
|
2278
|
+
}
|
|
2279
|
+
});
|
|
2280
|
+
});
|
|
2281
|
+
if (itemCount > 500) {
|
|
2282
|
+
diagnostics.push(diagnostic('APP_CONFIG_ADMIN_NAVIGATION_LIMIT_EXCEEDED', '后台导航最多包含 500 个页面引用', 'frontend.admin.navigation'));
|
|
2283
|
+
}
|
|
2284
|
+
}
|
|
1971
2285
|
function validateAuthorizationTransitions(rawTransitions, _roleCodes, diagnostics) {
|
|
1972
2286
|
if (rawTransitions !== undefined && !Array.isArray(rawTransitions)) {
|
|
1973
2287
|
diagnostics.push(diagnostic('APP_CONFIG_AUTHZ_TRANSITIONS_INVALID', 'authz.authorizationTransitions 必须是数组', 'authz.authorizationTransitions'));
|
|
@@ -2161,6 +2475,15 @@ export function resourceReadPolicy(input) {
|
|
|
2161
2475
|
}
|
|
2162
2476
|
export function materializeDataResource(appCode, declaration) {
|
|
2163
2477
|
const capabilities = resourceCapabilityCodes(appCode, declaration.code);
|
|
2478
|
+
const mutationOwner = declaration.mutationOwner || 'native';
|
|
2479
|
+
const nativeMutations = mutationOwner === 'native';
|
|
2480
|
+
const generated = {
|
|
2481
|
+
list: declaration.generated?.list ?? true,
|
|
2482
|
+
detail: declaration.generated?.detail ?? true,
|
|
2483
|
+
create: declaration.generated?.create ?? nativeMutations,
|
|
2484
|
+
update: declaration.generated?.update ?? nativeMutations,
|
|
2485
|
+
delete: declaration.generated?.delete ?? nativeMutations,
|
|
2486
|
+
};
|
|
2164
2487
|
const fields = declaration.fields.map(field => {
|
|
2165
2488
|
const definition = {
|
|
2166
2489
|
code: field.code,
|
|
@@ -2176,6 +2499,8 @@ export function materializeDataResource(appCode, declaration) {
|
|
|
2176
2499
|
...(field.maxLength !== undefined ? { maxLength: field.maxLength } : {}),
|
|
2177
2500
|
...(field.precision !== undefined ? { precision: field.precision } : {}),
|
|
2178
2501
|
...(field.scale !== undefined ? { scale: field.scale } : {}),
|
|
2502
|
+
...(field.min !== undefined ? { min: field.min } : {}),
|
|
2503
|
+
...(field.max !== undefined ? { max: field.max } : {}),
|
|
2179
2504
|
...(field.timePrecision ? { timePrecision: field.timePrecision } : {}),
|
|
2180
2505
|
...(field.file ? { file: field.file } : {}),
|
|
2181
2506
|
...(field.serial ? { serial: field.serial } : {}),
|
|
@@ -2194,8 +2519,13 @@ export function materializeDataResource(appCode, declaration) {
|
|
|
2194
2519
|
.filter(field => field.filter === true)
|
|
2195
2520
|
.map(field => field.code);
|
|
2196
2521
|
const access = (field, operation) => {
|
|
2197
|
-
if (
|
|
2522
|
+
if (operation !== 'read' &&
|
|
2523
|
+
!supportsGeneratedMutation({
|
|
2524
|
+
type: field.type,
|
|
2525
|
+
system: field.system,
|
|
2526
|
+
})) {
|
|
2198
2527
|
return [];
|
|
2528
|
+
}
|
|
2199
2529
|
const override = field.access?.[operation];
|
|
2200
2530
|
if (override === false)
|
|
2201
2531
|
return [];
|
|
@@ -2204,7 +2534,8 @@ export function materializeDataResource(appCode, declaration) {
|
|
|
2204
2534
|
return [capabilities[operation]];
|
|
2205
2535
|
};
|
|
2206
2536
|
const surface = {
|
|
2207
|
-
|
|
2537
|
+
mutationOwner,
|
|
2538
|
+
generated,
|
|
2208
2539
|
fields: Object.fromEntries(declaration.fields.map(field => [
|
|
2209
2540
|
field.code,
|
|
2210
2541
|
{
|
|
@@ -2220,6 +2551,8 @@ export function materializeDataResource(appCode, declaration) {
|
|
|
2220
2551
|
...(field.maxLength !== undefined ? { maxLength: field.maxLength } : {}),
|
|
2221
2552
|
...(field.precision !== undefined ? { precision: field.precision } : {}),
|
|
2222
2553
|
...(field.scale !== undefined ? { scale: field.scale } : {}),
|
|
2554
|
+
...(field.min !== undefined ? { min: field.min } : {}),
|
|
2555
|
+
...(field.max !== undefined ? { max: field.max } : {}),
|
|
2223
2556
|
...(field.file?.maxCount !== undefined
|
|
2224
2557
|
? { maxCount: field.file.maxCount }
|
|
2225
2558
|
: {}),
|
|
@@ -2267,6 +2600,7 @@ export function materializeDataResource(appCode, declaration) {
|
|
|
2267
2600
|
code: declaration.code,
|
|
2268
2601
|
name: declaration.name,
|
|
2269
2602
|
schema: { fields },
|
|
2603
|
+
...(declaration.invariants ? { invariants: declaration.invariants } : {}),
|
|
2270
2604
|
surface,
|
|
2271
2605
|
capabilities,
|
|
2272
2606
|
...(declaration.dataPolicyCode
|
|
@@ -2292,7 +2626,10 @@ export function validateAppDeclaration(value) {
|
|
|
2292
2626
|
const resourceKeys = new Set([
|
|
2293
2627
|
'code',
|
|
2294
2628
|
'name',
|
|
2629
|
+
'mutationOwner',
|
|
2630
|
+
'generated',
|
|
2295
2631
|
'fields',
|
|
2632
|
+
'invariants',
|
|
2296
2633
|
'list',
|
|
2297
2634
|
'form',
|
|
2298
2635
|
'detail',
|
|
@@ -2310,6 +2647,8 @@ export function validateAppDeclaration(value) {
|
|
|
2310
2647
|
'maxLength',
|
|
2311
2648
|
'precision',
|
|
2312
2649
|
'scale',
|
|
2650
|
+
'min',
|
|
2651
|
+
'max',
|
|
2313
2652
|
'timePrecision',
|
|
2314
2653
|
'file',
|
|
2315
2654
|
'serial',
|
|
@@ -2332,6 +2671,26 @@ export function validateAppDeclaration(value) {
|
|
|
2332
2671
|
}
|
|
2333
2672
|
}
|
|
2334
2673
|
const fields = Array.isArray(resource.fields) ? resource.fields : [];
|
|
2674
|
+
const mutationOwner = string(resource.mutationOwner) || 'native';
|
|
2675
|
+
const generated = object(resource.generated);
|
|
2676
|
+
if (!['native', 'action', 'readonly', 'workflow'].includes(mutationOwner)) {
|
|
2677
|
+
diagnostics.push(diagnostic('APP_CONFIG_DATA_RESOURCE_MUTATION_OWNER_INVALID', 'mutationOwner 必须是 native、action、readonly 或 workflow', `${resourcePath}.mutationOwner`));
|
|
2678
|
+
}
|
|
2679
|
+
for (const key of Object.keys(generated)) {
|
|
2680
|
+
if (!['list', 'detail', 'create', 'update', 'delete'].includes(key)) {
|
|
2681
|
+
diagnostics.push(diagnostic('APP_CONFIG_DATA_RESOURCE_GENERATED_SURFACE_INVALID', `generated.${key} 不是受支持的标准页面/操作`, `${resourcePath}.generated.${key}`));
|
|
2682
|
+
}
|
|
2683
|
+
else if (typeof generated[key] !== 'boolean') {
|
|
2684
|
+
diagnostics.push(diagnostic('APP_CONFIG_DATA_RESOURCE_GENERATED_SURFACE_INVALID', `generated.${key} 必须是 boolean`, `${resourcePath}.generated.${key}`));
|
|
2685
|
+
}
|
|
2686
|
+
}
|
|
2687
|
+
if (mutationOwner !== 'native') {
|
|
2688
|
+
for (const operation of ['create', 'update', 'delete']) {
|
|
2689
|
+
if (generated[operation] === true) {
|
|
2690
|
+
diagnostics.push(diagnostic('APP_CONFIG_DATA_RESOURCE_NATIVE_MUTATION_OWNER_CONFLICT', `${mutationOwner} owns mutations; Native ${operation} cannot be generated`, `${resourcePath}.generated.${operation}`));
|
|
2691
|
+
}
|
|
2692
|
+
}
|
|
2693
|
+
}
|
|
2335
2694
|
if (fields.length === 0) {
|
|
2336
2695
|
diagnostics.push(diagnostic('APP_CONFIG_DATA_RESOURCE_FIELDS_REQUIRED', '资源必须直接声明非空 fields 数组', `${resourcePath}.fields`));
|
|
2337
2696
|
return;
|
|
@@ -2414,6 +2773,21 @@ export function validateAppDeclaration(value) {
|
|
|
2414
2773
|
if (code)
|
|
2415
2774
|
declaredCodes.add(code);
|
|
2416
2775
|
});
|
|
2776
|
+
for (const operation of ['create', 'update']) {
|
|
2777
|
+
const enabled = generated[operation] ?? mutationOwner === 'native';
|
|
2778
|
+
const writable = fields.some(rawField => {
|
|
2779
|
+
const field = object(rawField);
|
|
2780
|
+
return (APP_DATA_FIELD_TYPES.has(string(field.type)) &&
|
|
2781
|
+
supportsGeneratedMutation({
|
|
2782
|
+
type: string(field.type),
|
|
2783
|
+
system: field.system === true,
|
|
2784
|
+
}) &&
|
|
2785
|
+
object(field.access)[operation] !== false);
|
|
2786
|
+
});
|
|
2787
|
+
if (materializable && enabled && !writable) {
|
|
2788
|
+
diagnostics.push(diagnostic('APP_CONFIG_DATA_RESOURCE_WRITABLE_FIELDS_REQUIRED', `generated.${operation} 需要至少一个可写业务字段`, `${resourcePath}.generated.${operation}`));
|
|
2789
|
+
}
|
|
2790
|
+
}
|
|
2417
2791
|
const defaultSortField = string(object(resource.list).defaultSort && object(object(resource.list).defaultSort).field);
|
|
2418
2792
|
if (defaultSortField && !declaredCodes.has(defaultSortField)) {
|
|
2419
2793
|
diagnostics.push(diagnostic('APP_CONFIG_DATA_RESOURCE_SORT_FIELD_INVALID', '默认排序字段必须在同一资源 fields 中声明', `${resourcePath}.list.defaultSort.field`));
|