openxiangda-devkit-core 2.0.0-alpha.79 → 2.0.0-alpha.81
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 +5 -0
- package/dist/application-services.d.ts.map +1 -1
- package/dist/application-services.js +29 -36
- package/dist/application-services.js.map +1 -1
- package/dist/compiler/ai-catalog.js +1 -1
- package/dist/compiler/ai-catalog.js.map +1 -1
- package/dist/compiler/bundle.d.ts +1 -1
- package/dist/compiler/bundle.d.ts.map +1 -1
- package/dist/compiler/bundle.js +112 -2
- package/dist/compiler/bundle.js.map +1 -1
- package/dist/compiler/config.d.ts +3 -0
- package/dist/compiler/config.d.ts.map +1 -1
- package/dist/compiler/config.js +158 -7
- package/dist/compiler/config.js.map +1 -1
- package/dist/compiler/field-physical-plan.d.ts +1 -0
- package/dist/compiler/field-physical-plan.d.ts.map +1 -1
- package/dist/compiler/field-physical-plan.js +8 -1
- package/dist/compiler/field-physical-plan.js.map +1 -1
- package/dist/compiler/package-compiler.d.ts +3 -3
- package/dist/compiler/package-compiler.d.ts.map +1 -1
- package/dist/compiler/package-compiler.js +174 -30
- package/dist/compiler/package-compiler.js.map +1 -1
- package/dist/compiler/schema-composition.js +2 -2
- package/dist/compiler/schema-composition.js.map +1 -1
- package/dist/control-plane-client.d.ts +6 -28
- package/dist/control-plane-client.d.ts.map +1 -1
- package/dist/control-plane-client.js +10 -19
- package/dist/control-plane-client.js.map +1 -1
- package/dist/deployment.d.ts +4 -4
- package/dist/deployment.d.ts.map +1 -1
- package/dist/deployment.js +28 -11
- package/dist/deployment.js.map +1 -1
- package/dist/internal/workflow.d.ts.map +1 -1
- package/dist/internal/workflow.js +29 -0
- package/dist/internal/workflow.js.map +1 -1
- package/package.json +2 -2
package/dist/compiler/config.js
CHANGED
|
@@ -344,7 +344,9 @@ export const openXiangdaAppConfigSchema = {
|
|
|
344
344
|
type: 'object',
|
|
345
345
|
additionalProperties: false,
|
|
346
346
|
required: ['root'],
|
|
347
|
-
properties: {
|
|
347
|
+
properties: {
|
|
348
|
+
root: { type: 'string', minLength: 1 },
|
|
349
|
+
},
|
|
348
350
|
},
|
|
349
351
|
perspectives: {
|
|
350
352
|
type: 'array',
|
|
@@ -724,9 +726,14 @@ export function validateAppConfig(value) {
|
|
|
724
726
|
if (!string(app.name)) {
|
|
725
727
|
diagnostics.push(diagnostic('APP_CONFIG_NAME_REQUIRED', 'app.name 必须是非空字符串', 'app.name'));
|
|
726
728
|
}
|
|
729
|
+
const platform = object(config.platform);
|
|
730
|
+
for (const key of Object.keys(platform)) {
|
|
731
|
+
if (key !== 'root') {
|
|
732
|
+
diagnostics.push(diagnostic('APP_CONFIG_PLATFORM_PROPERTY_UNSUPPORTED', `platform.${key} 不属于应用声明合同;平台能力要求只能由 compiler 派生`, `platform.${key}`));
|
|
733
|
+
}
|
|
734
|
+
}
|
|
727
735
|
const frontend = object(config.frontend);
|
|
728
736
|
const backend = object(config.backend);
|
|
729
|
-
const platform = object(config.platform);
|
|
730
737
|
const backendKeys = new Set([
|
|
731
738
|
'root',
|
|
732
739
|
'runtime',
|
|
@@ -772,10 +779,25 @@ export function validateAppConfig(value) {
|
|
|
772
779
|
validateFrontendRoutes(config, diagnostics);
|
|
773
780
|
validateAdminNavigation(config, diagnostics);
|
|
774
781
|
validatePerspectives(config.perspectives, object(config.authz).roles, diagnostics);
|
|
775
|
-
const
|
|
782
|
+
const declaredResources = new Map((Array.isArray(object(config.data).resources)
|
|
776
783
|
? object(config.data).resources
|
|
777
|
-
: []).map(resource =>
|
|
778
|
-
|
|
784
|
+
: []).map(resource => {
|
|
785
|
+
const item = object(resource);
|
|
786
|
+
const schema = object(item.schema);
|
|
787
|
+
const fields = Array.isArray(schema.fields) ? schema.fields : [];
|
|
788
|
+
return [
|
|
789
|
+
string(item.code),
|
|
790
|
+
new Map(fields.map(field => {
|
|
791
|
+
const declaration = object(field);
|
|
792
|
+
return [string(declaration.code), string(declaration.type)];
|
|
793
|
+
})),
|
|
794
|
+
];
|
|
795
|
+
}));
|
|
796
|
+
const declaredResourceCodes = new Set(declaredResources.keys());
|
|
797
|
+
const declaredWorkflowCodes = new Set((Array.isArray(object(config.workflows).activations)
|
|
798
|
+
? object(config.workflows).activations
|
|
799
|
+
: []).map(item => string(object(item).workflowCode)));
|
|
800
|
+
validateBackendOperations(backend.operations, declaredResources, declaredWorkflowCodes, diagnostics);
|
|
779
801
|
const eventConfig = object(config.events);
|
|
780
802
|
const workflowConfig = object(config.workflows);
|
|
781
803
|
if (backend.enabled === false &&
|
|
@@ -1623,7 +1645,7 @@ export function validateAppConfig(value) {
|
|
|
1623
1645
|
!Number.isInteger(delivery.maxBackoffMs) ||
|
|
1624
1646
|
Number(delivery.maxBackoffMs) < Number(delivery.initialBackoffMs) ||
|
|
1625
1647
|
Number(delivery.maxBackoffMs) > 1_800_000 ||
|
|
1626
|
-
!['none', 'record'].includes(string(delivery.ordering)) ||
|
|
1648
|
+
!['none', 'record', 'workflow-instance'].includes(string(delivery.ordering)) ||
|
|
1627
1649
|
!Number.isInteger(delivery.concurrency) ||
|
|
1628
1650
|
Number(delivery.concurrency) < 1 ||
|
|
1629
1651
|
Number(delivery.concurrency) > 50) {
|
|
@@ -1858,6 +1880,20 @@ export function validateAppConfig(value) {
|
|
|
1858
1880
|
string(object(declaration.definition).code)) {
|
|
1859
1881
|
diagnostics.push(diagnostic('APP_CONFIG_WORKFLOW_USER_TITLE_REQUIRED', 'Workflow title 必须是用户可读本地化名称,不能等于内部 code', `${path}.definition.title`));
|
|
1860
1882
|
}
|
|
1883
|
+
const subject = object(definition.subject);
|
|
1884
|
+
const factProjection = object(subject.factProjection);
|
|
1885
|
+
const factEntries = Object.entries(factProjection);
|
|
1886
|
+
const subjectFields = eventResourceFields.get(string(subject.resourceCode));
|
|
1887
|
+
if (Object.keys(subject).some(key => !['resourceCode', 'factProjection'].includes(key)) ||
|
|
1888
|
+
!subjectFields ||
|
|
1889
|
+
!isRecord(subject.factProjection) ||
|
|
1890
|
+
factEntries.length < 1 ||
|
|
1891
|
+
factEntries.length > 64 ||
|
|
1892
|
+
factEntries.some(([factKey, fieldCode]) => !/^[A-Za-z][A-Za-z0-9_.]{0,127}$/.test(factKey) ||
|
|
1893
|
+
!/^[A-Za-z][A-Za-z0-9_]{0,62}$/.test(string(fieldCode)) ||
|
|
1894
|
+
!subjectFields.has(string(fieldCode)))) {
|
|
1895
|
+
diagnostics.push(diagnostic('APP_CONFIG_WORKFLOW_SUBJECT_INVALID', 'Workflow subject 必须绑定已声明资源,并将 1 到 64 个 fact key 映射到该资源的已声明字段', `${path}.definition.subject`));
|
|
1896
|
+
}
|
|
1861
1897
|
for (const error of validateWorkflowDefinition(definition)) {
|
|
1862
1898
|
diagnostics.push(diagnostic('APP_CONFIG_WORKFLOW_DEFINITION_INVALID', error, path));
|
|
1863
1899
|
}
|
|
@@ -1881,10 +1917,23 @@ export function validateAppConfig(value) {
|
|
|
1881
1917
|
}
|
|
1882
1918
|
bindingByKey.set(key, binding);
|
|
1883
1919
|
});
|
|
1920
|
+
const activationWorkflowCodes = new Set();
|
|
1884
1921
|
activations.forEach((item, index) => {
|
|
1885
1922
|
const activation = object(item);
|
|
1886
1923
|
const path = `workflows.activations[${index}]`;
|
|
1924
|
+
if (Object.keys(activation).some(key => ![
|
|
1925
|
+
'workflowCode',
|
|
1926
|
+
'definitionVersion',
|
|
1927
|
+
'bindingVersion',
|
|
1928
|
+
'acceptedCommandDeactivationPolicy',
|
|
1929
|
+
].includes(key))) {
|
|
1930
|
+
diagnostics.push(diagnostic('APP_CONFIG_WORKFLOW_ACTIVATION_INVALID', 'Workflow activation 只能声明固定版本与停用策略', path));
|
|
1931
|
+
}
|
|
1887
1932
|
const workflowCode = string(activation.workflowCode);
|
|
1933
|
+
if (activationWorkflowCodes.has(workflowCode)) {
|
|
1934
|
+
diagnostics.push(diagnostic('APP_CONFIG_WORKFLOW_ACTIVATION_DUPLICATE', `Workflow activation 重复: ${workflowCode}`, path));
|
|
1935
|
+
}
|
|
1936
|
+
activationWorkflowCodes.add(workflowCode);
|
|
1888
1937
|
if ('environmentKey' in activation) {
|
|
1889
1938
|
diagnostics.push(diagnostic('APP_CONFIG_ENVIRONMENT_OVERRIDE_FORBIDDEN', 'Workflow activation 属于环境中立 AppVersion,不能声明 environmentKey', `${path}.environmentKey`));
|
|
1890
1939
|
}
|
|
@@ -1896,6 +1945,10 @@ export function validateAppConfig(value) {
|
|
|
1896
1945
|
diagnostics.push(diagnostic('APP_CONFIG_WORKFLOW_ACTIVATION_VERSION_NOT_FOUND', 'Workflow activation 引用的 definition 或 binding 版本不存在', path));
|
|
1897
1946
|
return;
|
|
1898
1947
|
}
|
|
1948
|
+
if (activation.acceptedCommandDeactivationPolicy !==
|
|
1949
|
+
definition.acceptedCommandDeactivationPolicy) {
|
|
1950
|
+
diagnostics.push(diagnostic('APP_CONFIG_WORKFLOW_ACTIVATION_DEACTIVATION_POLICY_MISMATCH', 'Workflow activation 停用策略必须等于所选 definition', `${path}.acceptedCommandDeactivationPolicy`));
|
|
1951
|
+
}
|
|
1899
1952
|
for (const error of validateWorkflowBinding(definition, binding)) {
|
|
1900
1953
|
diagnostics.push(diagnostic('APP_CONFIG_WORKFLOW_BINDING_INVALID', error, path));
|
|
1901
1954
|
}
|
|
@@ -2024,7 +2077,8 @@ function validateCapabilityDeclarations(appCode, declarations, diagnostics) {
|
|
|
2024
2077
|
seen.add(code);
|
|
2025
2078
|
});
|
|
2026
2079
|
}
|
|
2027
|
-
function validateBackendOperations(rawOperations,
|
|
2080
|
+
function validateBackendOperations(rawOperations, declaredResources, declaredWorkflowCodes, diagnostics) {
|
|
2081
|
+
const declaredResourceCodes = new Set(declaredResources.keys());
|
|
2028
2082
|
if (rawOperations !== undefined && !Array.isArray(rawOperations)) {
|
|
2029
2083
|
diagnostics.push(diagnostic('APP_CONFIG_BACKEND_OPERATIONS_INVALID', 'backend.operations 必须是数组', 'backend.operations'));
|
|
2030
2084
|
return;
|
|
@@ -2052,6 +2106,87 @@ function validateBackendOperations(rawOperations, declaredResourceCodes, diagnos
|
|
|
2052
2106
|
if ('resources' in operation) {
|
|
2053
2107
|
diagnostics.push(diagnostic('APP_CONFIG_BACKEND_OPERATION_RESOURCES_REMOVED', '资源范围只能在显式 backend.operations[].ai 中声明', `${path}.resources`));
|
|
2054
2108
|
}
|
|
2109
|
+
if (operation.platformAccess !== undefined) {
|
|
2110
|
+
const access = object(operation.platformAccess);
|
|
2111
|
+
const allowedAccessKeys = new Set([
|
|
2112
|
+
'directory',
|
|
2113
|
+
'managedFiles',
|
|
2114
|
+
'notification',
|
|
2115
|
+
'workflow',
|
|
2116
|
+
]);
|
|
2117
|
+
let invalid = !isRecord(operation.platformAccess) ||
|
|
2118
|
+
Object.keys(access).length === 0 ||
|
|
2119
|
+
Object.keys(access).some(key => !allowedAccessKeys.has(key));
|
|
2120
|
+
if (access.directory !== undefined) {
|
|
2121
|
+
const directory = object(access.directory);
|
|
2122
|
+
const fields = Array.isArray(directory.fields) ? directory.fields : [];
|
|
2123
|
+
const allowedFields = new Set([
|
|
2124
|
+
'displayName',
|
|
2125
|
+
'employeeNumber',
|
|
2126
|
+
'primaryDepartment',
|
|
2127
|
+
'departments',
|
|
2128
|
+
]);
|
|
2129
|
+
invalid ||=
|
|
2130
|
+
directory.mode !== 'current-initiator' ||
|
|
2131
|
+
fields.length === 0 ||
|
|
2132
|
+
fields.length > allowedFields.size ||
|
|
2133
|
+
new Set(fields).size !== fields.length ||
|
|
2134
|
+
fields.some(field => !allowedFields.has(string(field))) ||
|
|
2135
|
+
Object.keys(directory).some(key => !['mode', 'fields'].includes(key));
|
|
2136
|
+
}
|
|
2137
|
+
if (access.managedFiles !== undefined) {
|
|
2138
|
+
const managedFiles = Array.isArray(access.managedFiles)
|
|
2139
|
+
? access.managedFiles
|
|
2140
|
+
: [];
|
|
2141
|
+
invalid ||= managedFiles.length === 0 || managedFiles.length > 16;
|
|
2142
|
+
const resources = new Set();
|
|
2143
|
+
for (const rawManagedFile of managedFiles) {
|
|
2144
|
+
const managedFile = object(rawManagedFile);
|
|
2145
|
+
const resourceCode = string(managedFile.resourceCode);
|
|
2146
|
+
const fieldCodes = Array.isArray(managedFile.fieldCodes)
|
|
2147
|
+
? managedFile.fieldCodes.map(string)
|
|
2148
|
+
: [];
|
|
2149
|
+
const intents = Array.isArray(managedFile.intents)
|
|
2150
|
+
? managedFile.intents.map(string)
|
|
2151
|
+
: [];
|
|
2152
|
+
const resourceFields = declaredResources.get(resourceCode);
|
|
2153
|
+
invalid ||=
|
|
2154
|
+
!resourceFields ||
|
|
2155
|
+
resources.has(resourceCode) ||
|
|
2156
|
+
fieldCodes.length === 0 ||
|
|
2157
|
+
fieldCodes.length > 16 ||
|
|
2158
|
+
new Set(fieldCodes).size !== fieldCodes.length ||
|
|
2159
|
+
fieldCodes.some(fieldCode => !['file', 'image', 'signature'].includes(string(resourceFields?.get(fieldCode)))) ||
|
|
2160
|
+
intents.length === 0 ||
|
|
2161
|
+
intents.length > 2 ||
|
|
2162
|
+
new Set(intents).size !== intents.length ||
|
|
2163
|
+
intents.some(intent => !['create', 'update'].includes(intent)) ||
|
|
2164
|
+
Object.keys(managedFile).some(key => !['resourceCode', 'fieldCodes', 'intents'].includes(key));
|
|
2165
|
+
resources.add(resourceCode);
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
if (access.notification !== undefined) {
|
|
2169
|
+
const notification = object(access.notification);
|
|
2170
|
+
invalid ||=
|
|
2171
|
+
notification.mode !== 'business-standard' ||
|
|
2172
|
+
Object.keys(notification).some(key => key !== 'mode');
|
|
2173
|
+
}
|
|
2174
|
+
if (access.workflow !== undefined) {
|
|
2175
|
+
const workflow = object(access.workflow);
|
|
2176
|
+
const workflowCodes = Array.isArray(workflow.codes)
|
|
2177
|
+
? workflow.codes.map(string)
|
|
2178
|
+
: [];
|
|
2179
|
+
invalid ||=
|
|
2180
|
+
workflowCodes.length === 0 ||
|
|
2181
|
+
workflowCodes.length > 16 ||
|
|
2182
|
+
new Set(workflowCodes).size !== workflowCodes.length ||
|
|
2183
|
+
workflowCodes.some(code => !declaredWorkflowCodes.has(code)) ||
|
|
2184
|
+
Object.keys(workflow).some(key => key !== 'codes');
|
|
2185
|
+
}
|
|
2186
|
+
if (invalid) {
|
|
2187
|
+
diagnostics.push(diagnostic('APP_CONFIG_BACKEND_OPERATION_PLATFORM_ACCESS_INVALID', 'operation.platformAccess 必须只声明有界的发起人目录、托管文件、标准通知或现有工作流依赖', `${path}.platformAccess`));
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2055
2190
|
if (operation.ai !== undefined) {
|
|
2056
2191
|
const ai = object(operation.ai);
|
|
2057
2192
|
const aiResources = Array.isArray(ai.resources) ? ai.resources : [];
|
|
@@ -2801,6 +2936,9 @@ export function materializeDataResource(appCode, declaration) {
|
|
|
2801
2936
|
...(field.scale !== undefined ? { scale: field.scale } : {}),
|
|
2802
2937
|
...(field.min !== undefined ? { min: field.min } : {}),
|
|
2803
2938
|
...(field.max !== undefined ? { max: field.max } : {}),
|
|
2939
|
+
...(field.rangeBoundary
|
|
2940
|
+
? { rangeBoundary: field.rangeBoundary }
|
|
2941
|
+
: {}),
|
|
2804
2942
|
...(field.timePrecision ? { timePrecision: field.timePrecision } : {}),
|
|
2805
2943
|
...(field.file ? { file: field.file } : {}),
|
|
2806
2944
|
...(field.serial ? { serial: field.serial } : {}),
|
|
@@ -2853,6 +2991,9 @@ export function materializeDataResource(appCode, declaration) {
|
|
|
2853
2991
|
...(field.scale !== undefined ? { scale: field.scale } : {}),
|
|
2854
2992
|
...(field.min !== undefined ? { min: field.min } : {}),
|
|
2855
2993
|
...(field.max !== undefined ? { max: field.max } : {}),
|
|
2994
|
+
...(field.rangeBoundary
|
|
2995
|
+
? { rangeBoundary: field.rangeBoundary }
|
|
2996
|
+
: {}),
|
|
2856
2997
|
...(field.file?.maxCount !== undefined
|
|
2857
2998
|
? { maxCount: field.file.maxCount }
|
|
2858
2999
|
: {}),
|
|
@@ -2949,6 +3090,7 @@ export function validateAppDeclaration(value) {
|
|
|
2949
3090
|
'scale',
|
|
2950
3091
|
'min',
|
|
2951
3092
|
'max',
|
|
3093
|
+
'rangeBoundary',
|
|
2952
3094
|
'timePrecision',
|
|
2953
3095
|
'file',
|
|
2954
3096
|
'serial',
|
|
@@ -3026,6 +3168,15 @@ export function validateAppDeclaration(value) {
|
|
|
3026
3168
|
if (field.sortable === true && !supportsSort(semanticType)) {
|
|
3027
3169
|
diagnostics.push(diagnostic('APP_CONFIG_DATA_FIELD_SORT_UNSUPPORTED', `${semanticType} 不支持稳定排序`, `${fieldPath}.sortable`));
|
|
3028
3170
|
}
|
|
3171
|
+
const rangeType = semanticType === 'date-range' || semanticType === 'datetime-range';
|
|
3172
|
+
if (rangeType &&
|
|
3173
|
+
!['closed', 'half-open'].includes(string(field.rangeBoundary))) {
|
|
3174
|
+
materializable = false;
|
|
3175
|
+
diagnostics.push(diagnostic('APP_CONFIG_DATA_FIELD_RANGE_BOUNDARY_REQUIRED', `${semanticType} 必须显式声明 rangeBoundary: closed 或 half-open`, `${fieldPath}.rangeBoundary`));
|
|
3176
|
+
}
|
|
3177
|
+
else if (!rangeType && field.rangeBoundary !== undefined) {
|
|
3178
|
+
diagnostics.push(diagnostic('APP_CONFIG_DATA_FIELD_RANGE_BOUNDARY_TYPE_INVALID', 'rangeBoundary 只能用于 date-range 或 datetime-range', `${fieldPath}.rangeBoundary`));
|
|
3179
|
+
}
|
|
3029
3180
|
if (field.indexed === false &&
|
|
3030
3181
|
(field.filter === true ||
|
|
3031
3182
|
field.searchable === true ||
|