openxiangda-devkit-core 2.48.2 → 2.52.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/application-services.d.ts +1 -0
- package/dist/application-services.d.ts.map +1 -1
- package/dist/application-services.js +32 -6
- package/dist/application-services.js.map +1 -1
- package/dist/compiler/ai-catalog.d.ts.map +1 -1
- package/dist/compiler/ai-catalog.js +2 -10
- package/dist/compiler/ai-catalog.js.map +1 -1
- package/dist/compiler/application-model.d.ts +2 -1
- package/dist/compiler/application-model.d.ts.map +1 -1
- package/dist/compiler/application-model.js +2 -1
- package/dist/compiler/application-model.js.map +1 -1
- package/dist/compiler/bundle.d.ts.map +1 -1
- package/dist/compiler/bundle.js +59 -18
- package/dist/compiler/bundle.js.map +1 -1
- package/dist/compiler/config.d.ts +3 -1
- package/dist/compiler/config.d.ts.map +1 -1
- package/dist/compiler/config.js +83 -20
- package/dist/compiler/config.js.map +1 -1
- package/dist/compiler/permission-review.d.ts.map +1 -1
- package/dist/compiler/schema-composition.js +3 -0
- package/dist/compiler/schema-composition.js.map +1 -1
- package/dist/connected-development-vite.d.ts +15 -0
- package/dist/connected-development-vite.d.ts.map +1 -0
- package/dist/connected-development-vite.js +27 -0
- package/dist/connected-development-vite.js.map +1 -0
- package/dist/connected-development.d.ts +5 -0
- package/dist/connected-development.d.ts.map +1 -1
- package/dist/connected-development.js +99 -26
- package/dist/connected-development.js.map +1 -1
- package/dist/control-plane-client.d.ts +8 -0
- package/dist/control-plane-client.d.ts.map +1 -1
- package/dist/control-plane-client.js +36 -0
- package/dist/control-plane-client.js.map +1 -1
- package/dist/development-backend-relay.d.ts +28 -0
- package/dist/development-backend-relay.d.ts.map +1 -0
- package/dist/development-backend-relay.js +184 -0
- package/dist/development-backend-relay.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/internal/workflow.d.ts.map +1 -1
- package/dist/internal/workflow.js +18 -5
- package/dist/internal/workflow.js.map +1 -1
- package/dist/workspace-loader.js +1 -1
- package/dist/workspace-loader.js.map +1 -1
- package/package.json +2 -2
- package/templates/backend/Dockerfile +1 -0
package/dist/compiler/config.js
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
|
+
import { assertWorkflowNativeStagePolicy } from 'openxiangda-contracts';
|
|
2
|
+
import { validateAuthenticatedPublicRead, AuthenticatedPublicReadContractError } from 'openxiangda-contracts/native-compiler';
|
|
3
|
+
import { DATE_EVENT_RESERVED_DATA_FIELDS_V2, dateEventProjectionFieldsV2, dateEventProjectionEnvelopeSchemaV2 } from 'openxiangda-contracts/native-compiler';
|
|
1
4
|
import { validateUserCandidateReferences, validateWorkflowUserCandidateBindings, UserCandidateContractError } from 'openxiangda-contracts';
|
|
2
|
-
import { parseNativeUniqueKeys,
|
|
5
|
+
import { parseNativeUniqueKeys, DATA_SUBTABLE_DEFAULT_TOTAL_ROWS, dataOwnedRowLimit, isDataOwnedRowLimit } from 'openxiangda-contracts';
|
|
6
|
+
import { assertWorkflowOwnedSubjectCreate } from 'openxiangda-contracts';
|
|
7
|
+
export { workflowBusinessCommandInvocationSchema } from 'openxiangda-contracts';
|
|
3
8
|
import { validateManagedConcurrency } from 'openxiangda-contracts/native-compiler';
|
|
4
9
|
import { normalizeDecimalReservationEventDeclaration, decimalReservationEventContext } from 'openxiangda-contracts';
|
|
5
10
|
import { materializeApplicationModules } from './application-model.js';
|
|
@@ -18,14 +23,7 @@ import { AUTHORIZATION_TRANSITION_CAPABILITY_CODE_PATTERN, AUTHORIZATION_TRANSIT
|
|
|
18
23
|
// The compiler owns the application source contract. Devkit and every adapter
|
|
19
24
|
// re-export this module instead of maintaining separate configuration models.
|
|
20
25
|
import { validateWorkflowBinding, validateWorkflowDefinition, } from '../internal/workflow.js';
|
|
21
|
-
const DATE_TRIGGER_RESERVED_DATA_FIELDS = new Set(
|
|
22
|
-
'triggerCode',
|
|
23
|
-
'resourceCode',
|
|
24
|
-
'recordId',
|
|
25
|
-
'recordRevision',
|
|
26
|
-
'field',
|
|
27
|
-
'dueAt',
|
|
28
|
-
]);
|
|
26
|
+
const DATE_TRIGGER_RESERVED_DATA_FIELDS = new Set(DATE_EVENT_RESERVED_DATA_FIELDS_V2);
|
|
29
27
|
const APP_DATA_FIELD_TYPES = new Set(DATA_FIELD_TYPES);
|
|
30
28
|
const WORKFLOW_RUNTIME_CODE_PATTERN = /^[a-z][a-z0-9-]{2,63}$/;
|
|
31
29
|
function adminNavigationItem(page, options = {}) {
|
|
@@ -1452,6 +1450,7 @@ export function validateAppConfig(value) {
|
|
|
1452
1450
|
'name',
|
|
1453
1451
|
'resourceCode',
|
|
1454
1452
|
'unrestrictedRoleCodes',
|
|
1453
|
+
'publicRead',
|
|
1455
1454
|
'operations',
|
|
1456
1455
|
'matchMode',
|
|
1457
1456
|
'rules',
|
|
@@ -1494,6 +1493,20 @@ export function validateAppConfig(value) {
|
|
|
1494
1493
|
if (!/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/.test(string(policy.resourceCode))) {
|
|
1495
1494
|
diagnostics.push(diagnostic('APP_CONFIG_AUTHZ_POLICY_RESOURCE_REQUIRED', 'data policy 必须声明唯一目标 resourceCode', `authz.dataPolicies[${index}].resourceCode`));
|
|
1496
1495
|
}
|
|
1496
|
+
if (policy.publicRead !== undefined) {
|
|
1497
|
+
const resource = dataResources.map(object).find(resource => resource.code === policy.resourceCode);
|
|
1498
|
+
const baseline = roles.map(object).find(role => role.code === authenticatedUserRoleCode);
|
|
1499
|
+
try {
|
|
1500
|
+
validateAuthenticatedPublicRead({ policy, resource, baselineRoleCode: authenticatedUserRoleCode,
|
|
1501
|
+
baselineCapabilities: Array.isArray(baseline?.capabilities) ? baseline.capabilities : [],
|
|
1502
|
+
baselineDeniedCapabilities: Array.isArray(baseline?.deniedCapabilities) ? baseline.deniedCapabilities : [], pointer: policyPath });
|
|
1503
|
+
}
|
|
1504
|
+
catch (error) {
|
|
1505
|
+
if (!(error instanceof AuthenticatedPublicReadContractError))
|
|
1506
|
+
throw error;
|
|
1507
|
+
diagnostics.push(diagnostic(error.code, '认证公共投影必须闭合公共字段,所有非公开字段与历史继续拒绝基线成员', error.pointer));
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1497
1510
|
if (policy.unrestrictedRoleCodes !== undefined) {
|
|
1498
1511
|
const unrestrictedRoleCodes = Array.isArray(policy.unrestrictedRoleCodes)
|
|
1499
1512
|
? policy.unrestrictedRoleCodes
|
|
@@ -2012,6 +2025,16 @@ export function validateAppConfig(value) {
|
|
|
2012
2025
|
const field = string(trigger.field);
|
|
2013
2026
|
const fieldType = eventResourceFields.get(resourceCode)?.get(field);
|
|
2014
2027
|
const payload = object(trigger.payload);
|
|
2028
|
+
let projectionFields = [];
|
|
2029
|
+
let generatedSchema = applicationEventSchemas.get(string(trigger.eventType));
|
|
2030
|
+
try {
|
|
2031
|
+
projectionFields = dateEventProjectionFieldsV2(resourceCode, (Array.isArray(events.subscriptions) ? events.subscriptions : []).map(object)
|
|
2032
|
+
.filter(subscription => Array.isArray(subscription.eventTypes) && subscription.eventTypes.includes(trigger.eventType)), eventDataResources.map(object).find(resource => resource.code === resourceCode));
|
|
2033
|
+
generatedSchema = dateEventProjectionEnvelopeSchemaV2(generatedSchema, projectionFields);
|
|
2034
|
+
}
|
|
2035
|
+
catch (error) {
|
|
2036
|
+
diagnostics.push(diagnostic('APP_CONFIG_DATE_TRIGGER_PROJECTION_INVALID', `日期事件资料投影无效:${error instanceof Error ? error.message : 'invalid'}`, path));
|
|
2037
|
+
}
|
|
2015
2038
|
let payloadBytes = Number.POSITIVE_INFINITY;
|
|
2016
2039
|
try {
|
|
2017
2040
|
payloadBytes = Buffer.byteLength(JSON.stringify(trigger.payload || {}), 'utf8');
|
|
@@ -2030,7 +2053,7 @@ export function validateAppConfig(value) {
|
|
|
2030
2053
|
Array.isArray(trigger.payload) ||
|
|
2031
2054
|
payloadBytes > 64 * 1024 ||
|
|
2032
2055
|
Object.keys(payload).some(key => DATE_TRIGGER_RESERVED_DATA_FIELDS.has(key)) ||
|
|
2033
|
-
!eventDataMatchesSchema(
|
|
2056
|
+
!eventDataMatchesSchema(generatedSchema, {
|
|
2034
2057
|
...payload,
|
|
2035
2058
|
triggerCode: code,
|
|
2036
2059
|
resourceCode,
|
|
@@ -2038,6 +2061,7 @@ export function validateAppConfig(value) {
|
|
|
2038
2061
|
recordRevision: 1,
|
|
2039
2062
|
field,
|
|
2040
2063
|
dueAt: '2026-01-01T00:00:00.000Z',
|
|
2064
|
+
...(projectionFields.length ? { projection: {} } : {}),
|
|
2041
2065
|
})) {
|
|
2042
2066
|
diagnostics.push(diagnostic('APP_CONFIG_DATE_TRIGGER_INVALID', 'dateTrigger 必须使用唯一 code、date/datetime 字段、十年内 ISO-8601 offset、已注册应用事件、无保留字段且完整生成数据满足 JSON Schema', path));
|
|
2043
2067
|
}
|
|
@@ -2388,6 +2412,11 @@ export function validateAppConfig(value) {
|
|
|
2388
2412
|
const property = workflowInputProperties[factKey];
|
|
2389
2413
|
const propertyRecord = isRecord(property) ? property : {};
|
|
2390
2414
|
const propertyType = string(propertyRecord.type);
|
|
2415
|
+
const nullableObject = Array.isArray(propertyRecord.type) &&
|
|
2416
|
+
propertyRecord.type.length === 2 &&
|
|
2417
|
+
new Set(propertyRecord.type).size === 2 &&
|
|
2418
|
+
propertyRecord.type.includes('object') &&
|
|
2419
|
+
propertyRecord.type.includes('null');
|
|
2391
2420
|
const itemRecord = isRecord(propertyRecord.items)
|
|
2392
2421
|
? propertyRecord.items
|
|
2393
2422
|
: {};
|
|
@@ -2400,14 +2429,14 @@ export function validateAppConfig(value) {
|
|
|
2400
2429
|
? 'array(items.type=array,items.items.type=object)'
|
|
2401
2430
|
: fieldType.endsWith('.multiple')
|
|
2402
2431
|
? 'array(items.type=object)'
|
|
2403
|
-
: 'object';
|
|
2432
|
+
: 'object 或 [object, null]';
|
|
2404
2433
|
const shapeValid = fieldType === 'cascade.multiple'
|
|
2405
2434
|
? propertyType === 'array' &&
|
|
2406
2435
|
itemType === 'array' &&
|
|
2407
2436
|
nestedItemType === 'object'
|
|
2408
2437
|
: fieldType.endsWith('.multiple')
|
|
2409
2438
|
? propertyType === 'array' && itemType === 'object'
|
|
2410
|
-
: propertyType === 'object';
|
|
2439
|
+
: propertyType === 'object' || nullableObject;
|
|
2411
2440
|
if (!shapeValid) {
|
|
2412
2441
|
diagnostics.push(diagnostic('APP_CONFIG_WORKFLOW_FACT_PROJECTION_SHAPE_INVALID', `fact "${factKey}" 投影的字段 ${fieldCode} 是 ${fieldType} 快照字段;inputSchema.properties.${factKey} 必须声明 ${expected}`, `${path}.definition.inputSchema.properties.${factKey}`));
|
|
2413
2442
|
}
|
|
@@ -2421,8 +2450,8 @@ export function validateAppConfig(value) {
|
|
|
2421
2450
|
for (const error of validateWorkflowTaskPages(definition, new Map((Array.isArray(policyFields) ? policyFields : []).map(raw => {
|
|
2422
2451
|
const field = object(raw);
|
|
2423
2452
|
const surfaceField = object(object(policyResource.surface).fields)[string(field.code)];
|
|
2424
|
-
return [string(field.code), { ...object(surfaceField), type: string(field.type) }];
|
|
2425
|
-
}))))
|
|
2453
|
+
return [string(field.code), { ...object(surfaceField), ...field, type: string(field.type) }];
|
|
2454
|
+
})), undefined, dataOwnedRowLimit(object(policyResource.schema))))
|
|
2426
2455
|
diagnostics.push(diagnostic('APP_CONFIG_WORKFLOW_TASK_PAGE_INVALID', error, `${path}.definition.taskPages`));
|
|
2427
2456
|
for (const error of validateWorkflowInstanceCommandPolicies(definition, {
|
|
2428
2457
|
appCode,
|
|
@@ -2800,15 +2829,38 @@ function validateBackendOperations(rawOperations, declaredResources, declaredRes
|
|
|
2800
2829
|
'roleAssertions',
|
|
2801
2830
|
'decimalReservation',
|
|
2802
2831
|
'dataCommands',
|
|
2832
|
+
'workflowStage',
|
|
2803
2833
|
'recordEdit',
|
|
2834
|
+
'ownedSubject',
|
|
2804
2835
|
]);
|
|
2805
2836
|
let invalid = !isRecord(operation.platformAccess) ||
|
|
2806
2837
|
Object.keys(access).length === 0 ||
|
|
2807
2838
|
Object.keys(access).some(key => !allowedAccessKeys.has(key));
|
|
2839
|
+
if (access.ownedSubject !== undefined) {
|
|
2840
|
+
try {
|
|
2841
|
+
assertWorkflowOwnedSubjectCreate(access.ownedSubject);
|
|
2842
|
+
}
|
|
2843
|
+
catch {
|
|
2844
|
+
invalid = true;
|
|
2845
|
+
}
|
|
2846
|
+
invalid ||= access.workflow === undefined || access.decimalReservation !== undefined;
|
|
2847
|
+
}
|
|
2808
2848
|
if (access.dataCommands !== undefined) {
|
|
2809
2849
|
const commands = object(access.dataCommands);
|
|
2810
2850
|
invalid ||= commands.mode !== 'recoverable-native' || Object.keys(commands).some(key => key !== 'mode');
|
|
2811
2851
|
}
|
|
2852
|
+
if (access.workflowStage !== undefined) {
|
|
2853
|
+
try {
|
|
2854
|
+
assertWorkflowNativeStagePolicy(access.workflowStage);
|
|
2855
|
+
}
|
|
2856
|
+
catch {
|
|
2857
|
+
invalid = true;
|
|
2858
|
+
}
|
|
2859
|
+
const stage = object(access.workflowStage);
|
|
2860
|
+
invalid ||= object(access.dataCommands).mode !== 'recoverable-native' || access.decimalReservation !== undefined ||
|
|
2861
|
+
!declaredResources.has(string(stage.resourceCode)) || !declaredWorkflowCodes.has(string(stage.workflowCode)) ||
|
|
2862
|
+
!Array.isArray(object(access.workflow).codes) || !object(access.workflow).codes.includes(stage.workflowCode);
|
|
2863
|
+
}
|
|
2812
2864
|
if (access.recordEdit !== undefined) {
|
|
2813
2865
|
const edit = object(access.recordEdit);
|
|
2814
2866
|
const fields = Array.isArray(edit.fieldCodes) ? edit.fieldCodes : [];
|
|
@@ -2822,7 +2874,8 @@ function validateBackendOperations(rawOperations, declaredResources, declaredRes
|
|
|
2822
2874
|
const roles = Array.isArray(assertion.roleCodes) ? assertion.roleCodes : [];
|
|
2823
2875
|
invalid ||= !isRecord(access.roleAssertions) || roles.length < 1 || roles.length > 20 ||
|
|
2824
2876
|
new Set(roles).size !== roles.length || roles.some(role => !declaredRoleCodes.has(string(role))) ||
|
|
2825
|
-
Object.keys(assertion).some(key =>
|
|
2877
|
+
Object.keys(assertion).some(key => !['roleCodes', 'actorAuthority'].includes(key)) ||
|
|
2878
|
+
(Object.hasOwn(assertion, 'actorAuthority') && assertion.actorAuthority !== true);
|
|
2826
2879
|
}
|
|
2827
2880
|
if (access.directory !== undefined) {
|
|
2828
2881
|
const directory = object(access.directory);
|
|
@@ -2937,8 +2990,8 @@ function validateBackendOperations(rawOperations, declaredResources, declaredRes
|
|
|
2937
2990
|
Object.keys(workflow).some(key => !['codes', 'businessCommands'].includes(key));
|
|
2938
2991
|
if (workflow.businessCommands !== undefined) {
|
|
2939
2992
|
const commands = workflow.businessCommands;
|
|
2940
|
-
invalid ||= !Array.isArray(commands) || commands.length === 0 || commands.length >
|
|
2941
|
-
new Set(commands).size !== commands.length || commands.some(command => !['approve', 'reject', 'withdraw'].includes(command));
|
|
2993
|
+
invalid ||= !Array.isArray(commands) || commands.length === 0 || commands.length > 4 ||
|
|
2994
|
+
new Set(commands).size !== commands.length || commands.some(command => !['approve', 'reject', 'withdraw', 'resubmit'].includes(command));
|
|
2942
2995
|
}
|
|
2943
2996
|
}
|
|
2944
2997
|
if (access.decimalReservation !== undefined) {
|
|
@@ -4645,7 +4698,7 @@ export function materializeDataResource(appCode, declaration) {
|
|
|
4645
4698
|
appCode,
|
|
4646
4699
|
code: declaration.code,
|
|
4647
4700
|
name: declaration.name,
|
|
4648
|
-
schema: { fields },
|
|
4701
|
+
schema: { fields, ...(declaration.ownedRowLimit !== undefined ? { ownedRowLimit: declaration.ownedRowLimit } : {}) },
|
|
4649
4702
|
...(declaration.invariants ? { invariants: declaration.invariants } : {}),
|
|
4650
4703
|
...(declaration.decimalReservationLifecycle !== undefined ? { decimalReservationLifecycle: declaration.decimalReservationLifecycle } : {}),
|
|
4651
4704
|
...(uniqueKeys !== undefined ? { uniqueKeys } : {}),
|
|
@@ -4720,6 +4773,7 @@ export function validateAppDeclaration(value) {
|
|
|
4720
4773
|
'recordPrint',
|
|
4721
4774
|
'recordComments',
|
|
4722
4775
|
'recordDeletion',
|
|
4776
|
+
'ownedRowLimit',
|
|
4723
4777
|
]);
|
|
4724
4778
|
const fieldKeys = new Set([
|
|
4725
4779
|
'code',
|
|
@@ -5034,8 +5088,12 @@ export function validateAppDeclaration(value) {
|
|
|
5034
5088
|
const configured = Number(object(field.subtable).maxRows);
|
|
5035
5089
|
return total + (Number.isSafeInteger(configured) ? configured : 20);
|
|
5036
5090
|
}, 0);
|
|
5037
|
-
|
|
5038
|
-
|
|
5091
|
+
const declaredLimit = declaration.resource.ownedRowLimit;
|
|
5092
|
+
const limit = declaredLimit === undefined ? DATA_SUBTABLE_DEFAULT_TOTAL_ROWS : isDataOwnedRowLimit(declaredLimit) ? declaredLimit : 0;
|
|
5093
|
+
if (declaredLimit !== undefined && !isDataOwnedRowLimit(declaredLimit))
|
|
5094
|
+
diagnostics.push(diagnostic('NATIVE_DATA_OWNED_ROW_LIMIT_INVALID', 'ownedRowLimit 必须是1到1000的整数', `data.resources[${declaration.resourceIndex}].ownedRowLimit`));
|
|
5095
|
+
if (aggregateMaxRows > limit) {
|
|
5096
|
+
diagnostics.push(diagnostic('APP_CONFIG_DATA_SUBTABLE_AGGREGATE_MAX_ROWS_EXCEEDED', `同一父资源的所有子表 maxRows 总和不能超过 ${limit}`, `data.resources[${declaration.resourceIndex}].fields`));
|
|
5039
5097
|
}
|
|
5040
5098
|
fields.forEach((rawField, fieldIndex) => {
|
|
5041
5099
|
const field = object(rawField);
|
|
@@ -5043,6 +5101,11 @@ export function validateAppDeclaration(value) {
|
|
|
5043
5101
|
return;
|
|
5044
5102
|
const path = `data.resources[${declaration.resourceIndex}].fields[${fieldIndex}].subtable`;
|
|
5045
5103
|
const subtable = object(field.subtable);
|
|
5104
|
+
if (subtable.minRows !== undefined &&
|
|
5105
|
+
(!Number.isSafeInteger(subtable.minRows) || Number(subtable.minRows) < 0 ||
|
|
5106
|
+
Number(subtable.minRows) > Number(subtable.maxRows ?? 20))) {
|
|
5107
|
+
diagnostics.push(diagnostic('APP_CONFIG_DATA_SUBTABLE_MIN_ROWS_INVALID', '子表 minRows 必须是非负整数且不能超过 maxRows(默认20)', `${path}.minRows`));
|
|
5108
|
+
}
|
|
5046
5109
|
const targetCode = string(subtable.resourceCode);
|
|
5047
5110
|
const target = declaredResources.get(targetCode);
|
|
5048
5111
|
if (!target || targetCode === resourceCode) {
|