openxiangda-devkit-core 2.19.0 → 2.20.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/backend-image-build.d.ts +1 -0
- package/dist/backend-image-build.d.ts.map +1 -1
- package/dist/backend-image-build.js +6 -1
- package/dist/backend-image-build.js.map +1 -1
- package/dist/backend-image-upload.d.ts +3 -1
- package/dist/backend-image-upload.d.ts.map +1 -1
- package/dist/backend-image-upload.js +3 -1
- package/dist/backend-image-upload.js.map +1 -1
- package/dist/compiler/bundle.d.ts.map +1 -1
- package/dist/compiler/bundle.js +105 -0
- package/dist/compiler/bundle.js.map +1 -1
- package/dist/compiler/config.d.ts +10 -1
- package/dist/compiler/config.d.ts.map +1 -1
- package/dist/compiler/config.js +310 -11
- package/dist/compiler/config.js.map +1 -1
- package/dist/control-plane-client.d.ts +2 -2
- package/dist/control-plane-client.d.ts.map +1 -1
- package/dist/control-plane-client.js +4 -2
- package/dist/control-plane-client.js.map +1 -1
- package/dist/internal/workflow.d.ts.map +1 -1
- package/dist/internal/workflow.js +4 -0
- package/dist/internal/workflow.js.map +1 -1
- package/package.json +2 -2
package/dist/compiler/config.js
CHANGED
|
@@ -22,6 +22,7 @@ const DATE_TRIGGER_RESERVED_DATA_FIELDS = new Set([
|
|
|
22
22
|
'dueAt',
|
|
23
23
|
]);
|
|
24
24
|
const APP_DATA_FIELD_TYPES = new Set(DATA_FIELD_TYPES);
|
|
25
|
+
const WORKFLOW_RUNTIME_CODE_PATTERN = /^[a-z][a-z0-9-]{2,63}$/;
|
|
25
26
|
function adminNavigationItem(page, options = {}) {
|
|
26
27
|
return Object.freeze({ page: Object.freeze(page), ...options });
|
|
27
28
|
}
|
|
@@ -448,6 +449,11 @@ export const openXiangdaAppConfigSchema = {
|
|
|
448
449
|
required: ['resources'],
|
|
449
450
|
properties: {
|
|
450
451
|
resources: { type: 'array', maxItems: 100, items: { type: 'object' } },
|
|
452
|
+
subjectReadSurfaces: {
|
|
453
|
+
type: 'array',
|
|
454
|
+
maxItems: 50,
|
|
455
|
+
items: { type: 'object' },
|
|
456
|
+
},
|
|
451
457
|
},
|
|
452
458
|
},
|
|
453
459
|
authz: {
|
|
@@ -878,11 +884,36 @@ export function validateAppConfig(value) {
|
|
|
878
884
|
})),
|
|
879
885
|
];
|
|
880
886
|
}));
|
|
887
|
+
const declaredResourceFields = new Map((Array.isArray(object(config.data).resources)
|
|
888
|
+
? object(config.data).resources
|
|
889
|
+
: []).map(resource => {
|
|
890
|
+
const item = object(resource);
|
|
891
|
+
const schema = object(item.schema);
|
|
892
|
+
const fields = Array.isArray(schema.fields) ? schema.fields : [];
|
|
893
|
+
return [
|
|
894
|
+
string(item.code),
|
|
895
|
+
new Map(fields.map(field => {
|
|
896
|
+
const declaration = object(field);
|
|
897
|
+
return [string(declaration.code), declaration];
|
|
898
|
+
})),
|
|
899
|
+
];
|
|
900
|
+
}));
|
|
881
901
|
const declaredResourceCodes = new Set(declaredResources.keys());
|
|
902
|
+
validateSubjectReadSurfaces(string(object(config.app).code), object(config.data).subjectReadSurfaces, object(config.data).resources, new Set((Array.isArray(object(config.authz).capabilities)
|
|
903
|
+
? object(config.authz).capabilities
|
|
904
|
+
: []).map(item => string(object(item).code))), diagnostics);
|
|
882
905
|
const declaredWorkflowCodes = new Set((Array.isArray(object(config.workflows).activations)
|
|
883
906
|
? object(config.workflows).activations
|
|
884
907
|
: []).map(item => string(object(item).workflowCode)));
|
|
885
|
-
validateBackendOperations(backend.operations, declaredResources, declaredWorkflowCodes, new Set((Array.isArray(object(config.authz).roles) ? object(config.authz).roles : []).map(role => string(object(role).code))),
|
|
908
|
+
validateBackendOperations(backend.operations, declaredResources, declaredResourceFields, declaredWorkflowCodes, new Set((Array.isArray(object(config.authz).roles) ? object(config.authz).roles : []).map(role => string(object(role).code))), new Map((Array.isArray(object(config.data).subjectReadSurfaces)
|
|
909
|
+
? object(config.data).subjectReadSurfaces
|
|
910
|
+
: []).map(surface => {
|
|
911
|
+
const declaration = object(surface);
|
|
912
|
+
return [
|
|
913
|
+
string(declaration.code),
|
|
914
|
+
string(object(declaration.subject).resourceCode),
|
|
915
|
+
];
|
|
916
|
+
})), diagnostics);
|
|
886
917
|
const eventConfig = object(config.events);
|
|
887
918
|
const workflowConfig = object(config.workflows);
|
|
888
919
|
if (backend.enabled === false &&
|
|
@@ -2244,7 +2275,13 @@ export function validateAppConfig(value) {
|
|
|
2244
2275
|
string(object(declaration.definition).code)) {
|
|
2245
2276
|
diagnostics.push(diagnostic('APP_CONFIG_WORKFLOW_USER_TITLE_REQUIRED', 'Workflow title 必须是用户可读本地化名称,不能等于内部 code', `${path}.definition.title`));
|
|
2246
2277
|
}
|
|
2278
|
+
if (!WORKFLOW_RUNTIME_CODE_PATTERN.test(string(definition.code))) {
|
|
2279
|
+
diagnostics.push(diagnostic('APP_CONFIG_WORKFLOW_CODE_INVALID', 'Workflow code 必须匹配 /^[a-z][a-z0-9-]{2,63}$/,不能包含下划线或大写字母', `${path}.definition.code`));
|
|
2280
|
+
}
|
|
2247
2281
|
const subject = object(definition.subject);
|
|
2282
|
+
if (!WORKFLOW_RUNTIME_CODE_PATTERN.test(string(subject.resourceCode))) {
|
|
2283
|
+
diagnostics.push(diagnostic('APP_CONFIG_WORKFLOW_SUBJECT_RESOURCE_CODE_INVALID', 'Workflow subject.resourceCode 必须匹配 /^[a-z][a-z0-9-]{2,63}$/', `${path}.definition.subject.resourceCode`));
|
|
2284
|
+
}
|
|
2248
2285
|
const factProjection = object(subject.factProjection);
|
|
2249
2286
|
const factEntries = Object.entries(factProjection);
|
|
2250
2287
|
const subjectFields = eventResourceFields.get(string(subject.resourceCode));
|
|
@@ -2283,9 +2320,8 @@ export function validateAppConfig(value) {
|
|
|
2283
2320
|
if (summaryFieldsInvalid) {
|
|
2284
2321
|
diagnostics.push(diagnostic('APP_CONFIG_WORKFLOW_SUMMARY_FIELDS_INVALID', `Workflow summaryFields 最多 ${WORKFLOW_SUMMARY_MAX_FIELDS} 个,必须是不重复的已声明 scalar/reference 字段;禁止 system/rich/json/subtable/file/image/signature,且 text.long 不超过 ${WORKFLOW_SUMMARY_TEXT_LONG_MAX_BYTES} bytes`, `${path}.definition.subject.summaryFields`));
|
|
2285
2322
|
}
|
|
2286
|
-
//
|
|
2287
|
-
//
|
|
2288
|
-
// 编译期强制形状一致,把错误从"部署后静默重试"提前到"编写时"。
|
|
2323
|
+
// 快照字段的投影形状必须与 Native 存储一致;否则命令会在运行时
|
|
2324
|
+
// INPUT_SCHEMA_MISMATCH 并无限重试。编译期把错误提前到编写时。
|
|
2289
2325
|
const SNAPSHOT_FIELD_PROJECTION_TYPES = new Set([
|
|
2290
2326
|
'option.single',
|
|
2291
2327
|
'option.multiple',
|
|
@@ -2310,11 +2346,30 @@ export function validateAppConfig(value) {
|
|
|
2310
2346
|
continue;
|
|
2311
2347
|
}
|
|
2312
2348
|
const property = workflowInputProperties[factKey];
|
|
2313
|
-
const
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2349
|
+
const propertyRecord = isRecord(property) ? property : {};
|
|
2350
|
+
const propertyType = string(propertyRecord.type);
|
|
2351
|
+
const itemRecord = isRecord(propertyRecord.items)
|
|
2352
|
+
? propertyRecord.items
|
|
2353
|
+
: {};
|
|
2354
|
+
const itemType = string(itemRecord.type);
|
|
2355
|
+
const nestedItemRecord = isRecord(itemRecord.items)
|
|
2356
|
+
? itemRecord.items
|
|
2357
|
+
: {};
|
|
2358
|
+
const nestedItemType = string(nestedItemRecord.type);
|
|
2359
|
+
const expected = fieldType === 'cascade.multiple'
|
|
2360
|
+
? 'array(items.type=array,items.items.type=object)'
|
|
2361
|
+
: fieldType.endsWith('.multiple')
|
|
2362
|
+
? 'array(items.type=object)'
|
|
2363
|
+
: 'object';
|
|
2364
|
+
const shapeValid = fieldType === 'cascade.multiple'
|
|
2365
|
+
? propertyType === 'array' &&
|
|
2366
|
+
itemType === 'array' &&
|
|
2367
|
+
nestedItemType === 'object'
|
|
2368
|
+
: fieldType.endsWith('.multiple')
|
|
2369
|
+
? propertyType === 'array' && itemType === 'object'
|
|
2370
|
+
: propertyType === 'object';
|
|
2371
|
+
if (!shapeValid) {
|
|
2372
|
+
diagnostics.push(diagnostic('APP_CONFIG_WORKFLOW_FACT_PROJECTION_SHAPE_INVALID', `fact "${factKey}" 投影的字段 ${fieldCode} 是 ${fieldType} 快照字段;inputSchema.properties.${factKey} 必须声明 ${expected}`, `${path}.definition.inputSchema.properties.${factKey}`));
|
|
2318
2373
|
}
|
|
2319
2374
|
}
|
|
2320
2375
|
}
|
|
@@ -2514,7 +2569,126 @@ function validateCapabilityDeclarations(appCode, declarations, diagnostics) {
|
|
|
2514
2569
|
seen.add(code);
|
|
2515
2570
|
});
|
|
2516
2571
|
}
|
|
2517
|
-
function
|
|
2572
|
+
function validateSubjectReadSurfaces(appCode, rawSurfaces, rawResources, declaredCapabilityCodes, diagnostics) {
|
|
2573
|
+
if (rawSurfaces !== undefined && !Array.isArray(rawSurfaces)) {
|
|
2574
|
+
diagnostics.push(diagnostic('APP_CONFIG_SUBJECT_READ_SURFACES_INVALID', 'data.subjectReadSurfaces 必须是数组', 'data.subjectReadSurfaces'));
|
|
2575
|
+
return;
|
|
2576
|
+
}
|
|
2577
|
+
const resources = new Map((Array.isArray(rawResources) ? rawResources : []).map(raw => {
|
|
2578
|
+
const resource = object(raw);
|
|
2579
|
+
const schema = object(resource.schema);
|
|
2580
|
+
const fields = Array.isArray(schema.fields) ? schema.fields : [];
|
|
2581
|
+
return [
|
|
2582
|
+
string(resource.code),
|
|
2583
|
+
new Map(fields.map(field => [string(object(field).code), object(field)])),
|
|
2584
|
+
];
|
|
2585
|
+
}));
|
|
2586
|
+
const systemFields = new Set([
|
|
2587
|
+
'id',
|
|
2588
|
+
'revision',
|
|
2589
|
+
'created_at',
|
|
2590
|
+
'updated_at',
|
|
2591
|
+
'created_by',
|
|
2592
|
+
'updated_by',
|
|
2593
|
+
]);
|
|
2594
|
+
const surfaceCodes = new Set();
|
|
2595
|
+
(Array.isArray(rawSurfaces) ? rawSurfaces : []).forEach((raw, index) => {
|
|
2596
|
+
const surface = object(raw);
|
|
2597
|
+
const path = `data.subjectReadSurfaces[${index}]`;
|
|
2598
|
+
const code = string(surface.code);
|
|
2599
|
+
const capability = string(surface.capability);
|
|
2600
|
+
const subject = object(surface.subject);
|
|
2601
|
+
const subjectResourceCode = string(subject.resourceCode);
|
|
2602
|
+
const subjectFields = Array.isArray(subject.fields)
|
|
2603
|
+
? subject.fields.map(string)
|
|
2604
|
+
: [];
|
|
2605
|
+
const subjectResource = resources.get(subjectResourceCode);
|
|
2606
|
+
let invalid = Object.keys(surface).some(key => !['code', 'name', 'capability', 'subject', 'relations'].includes(key)) ||
|
|
2607
|
+
!OPERATION_CODE_PATTERN.test(code) ||
|
|
2608
|
+
surfaceCodes.has(code) ||
|
|
2609
|
+
!string(surface.name) ||
|
|
2610
|
+
!CAPABILITY_PATTERN.test(capability) ||
|
|
2611
|
+
!capability.startsWith(`app:${appCode}:`) ||
|
|
2612
|
+
!declaredCapabilityCodes.has(capability) ||
|
|
2613
|
+
Object.keys(subject).some(key => !['resourceCode', 'fields'].includes(key)) ||
|
|
2614
|
+
!subjectResource ||
|
|
2615
|
+
subjectFields.length < 1 ||
|
|
2616
|
+
subjectFields.length > 100 ||
|
|
2617
|
+
new Set(subjectFields).size !== subjectFields.length ||
|
|
2618
|
+
subjectFields.some(field => {
|
|
2619
|
+
const declaration = subjectResource?.get(field);
|
|
2620
|
+
return !declaration || string(declaration.type) === 'subtable';
|
|
2621
|
+
});
|
|
2622
|
+
const relations = Array.isArray(surface.relations)
|
|
2623
|
+
? surface.relations
|
|
2624
|
+
: [];
|
|
2625
|
+
invalid ||= relations.length < 1 || relations.length > 8;
|
|
2626
|
+
const relationCodes = new Set();
|
|
2627
|
+
let totalRows = 0;
|
|
2628
|
+
relations.forEach(rawRelation => {
|
|
2629
|
+
const relation = object(rawRelation);
|
|
2630
|
+
const relationCode = string(relation.code);
|
|
2631
|
+
const resourceCode = string(relation.resourceCode);
|
|
2632
|
+
const foreignKeyField = string(relation.foreignKeyField);
|
|
2633
|
+
const relationFields = Array.isArray(relation.fields)
|
|
2634
|
+
? relation.fields.map(string)
|
|
2635
|
+
: [];
|
|
2636
|
+
const resource = resources.get(resourceCode);
|
|
2637
|
+
const foreignKey = resource?.get(foreignKeyField);
|
|
2638
|
+
const source = object(foreignKey?.source);
|
|
2639
|
+
const limit = Number(relation.limit);
|
|
2640
|
+
const order = Array.isArray(relation.order) ? relation.order : [];
|
|
2641
|
+
invalid ||=
|
|
2642
|
+
Object.keys(relation).some(key => ![
|
|
2643
|
+
'code',
|
|
2644
|
+
'resourceCode',
|
|
2645
|
+
'foreignKeyField',
|
|
2646
|
+
'fields',
|
|
2647
|
+
'order',
|
|
2648
|
+
'limit',
|
|
2649
|
+
].includes(key)) ||
|
|
2650
|
+
!OPERATION_CODE_PATTERN.test(relationCode) ||
|
|
2651
|
+
relationCodes.has(relationCode) ||
|
|
2652
|
+
!resource ||
|
|
2653
|
+
!foreignKey ||
|
|
2654
|
+
!['uuid', 'resource-ref.single'].includes(string(foreignKey.type)) ||
|
|
2655
|
+
(string(foreignKey.type) === 'resource-ref.single' &&
|
|
2656
|
+
(string(source.kind) !== 'resource' ||
|
|
2657
|
+
string(source.resourceCode) !== subjectResourceCode)) ||
|
|
2658
|
+
relationFields.length < 1 ||
|
|
2659
|
+
relationFields.length > 100 ||
|
|
2660
|
+
new Set(relationFields).size !== relationFields.length ||
|
|
2661
|
+
relationFields.some(field => {
|
|
2662
|
+
const declaration = resource?.get(field);
|
|
2663
|
+
return !declaration || string(declaration.type) === 'subtable';
|
|
2664
|
+
}) ||
|
|
2665
|
+
!Number.isSafeInteger(limit) ||
|
|
2666
|
+
limit < 1 ||
|
|
2667
|
+
limit > 100 ||
|
|
2668
|
+
order.length > 10 ||
|
|
2669
|
+
order.some(rawOrder => {
|
|
2670
|
+
const item = object(rawOrder);
|
|
2671
|
+
const field = string(item.field);
|
|
2672
|
+
const declaration = resource?.get(field);
|
|
2673
|
+
return (Object.keys(item).some(key => !['field', 'direction'].includes(key)) ||
|
|
2674
|
+
(!systemFields.has(field) && !declaration) ||
|
|
2675
|
+
string(declaration?.type) === 'subtable' ||
|
|
2676
|
+
(!systemFields.has(field) &&
|
|
2677
|
+
!supportsSort(string(declaration?.type))) ||
|
|
2678
|
+
!['asc', 'desc'].includes(string(item.direction)));
|
|
2679
|
+
});
|
|
2680
|
+
relationCodes.add(relationCode);
|
|
2681
|
+
if (Number.isSafeInteger(limit))
|
|
2682
|
+
totalRows += limit;
|
|
2683
|
+
});
|
|
2684
|
+
invalid ||= totalRows > 400;
|
|
2685
|
+
if (invalid) {
|
|
2686
|
+
diagnostics.push(diagnostic('APP_CONFIG_SUBJECT_READ_SURFACE_INVALID', 'subject read surface 必须声明唯一 code、显式 capability、一个可读父资源,以及最多 8 个直接等值且总计不超过 400 行的有界关系', path, '关系外键仅支持 uuid 或指向父资源的 resource-ref.single;字段、排序和行数必须全部由声明固定,不能由浏览器传入。'));
|
|
2687
|
+
}
|
|
2688
|
+
surfaceCodes.add(code);
|
|
2689
|
+
});
|
|
2690
|
+
}
|
|
2691
|
+
function validateBackendOperations(rawOperations, declaredResources, declaredResourceFields, declaredWorkflowCodes, declaredRoleCodes, declaredSubjectSurfaces, diagnostics) {
|
|
2518
2692
|
const declaredResourceCodes = new Set(declaredResources.keys());
|
|
2519
2693
|
if (rawOperations !== undefined && !Array.isArray(rawOperations)) {
|
|
2520
2694
|
diagnostics.push(diagnostic('APP_CONFIG_BACKEND_OPERATIONS_INVALID', 'backend.operations 必须是数组', 'backend.operations'));
|
|
@@ -2678,6 +2852,123 @@ function validateBackendOperations(rawOperations, declaredResources, declaredWor
|
|
|
2678
2852
|
diagnostics.push(diagnostic('APP_CONFIG_BACKEND_OPERATION_PLATFORM_ACCESS_INVALID', 'operation.platformAccess 必须只声明有界的发起人目录、托管文件、托管文件复制、标准通知、现有工作流或已声明角色条件依赖', `${path}.platformAccess`));
|
|
2679
2853
|
}
|
|
2680
2854
|
}
|
|
2855
|
+
if (operation.browser !== undefined) {
|
|
2856
|
+
const browser = object(operation.browser);
|
|
2857
|
+
const subject = object(browser.subject);
|
|
2858
|
+
const subjectResourceCode = string(subject.resourceCode);
|
|
2859
|
+
const inputField = string(subject.inputField);
|
|
2860
|
+
const requestSchema = object(operation.requestSchema);
|
|
2861
|
+
const responseSchema = object(operation.responseSchema);
|
|
2862
|
+
const requestProperties = object(requestSchema.properties);
|
|
2863
|
+
const subjectInputSchema = object(requestProperties[inputField]);
|
|
2864
|
+
const requiredFields = Array.isArray(requestSchema.required)
|
|
2865
|
+
? requestSchema.required.map(string)
|
|
2866
|
+
: [];
|
|
2867
|
+
const refreshTargets = Array.isArray(browser.refreshTargets)
|
|
2868
|
+
? browser.refreshTargets
|
|
2869
|
+
: [];
|
|
2870
|
+
const refreshKeys = refreshTargets.map(rawTarget => {
|
|
2871
|
+
const target = object(rawTarget);
|
|
2872
|
+
return `${string(target.kind)}:${string(target.code)}`;
|
|
2873
|
+
});
|
|
2874
|
+
let invalid = !isRecord(operation.browser) ||
|
|
2875
|
+
Object.keys(browser).some(key => ![
|
|
2876
|
+
'exposure',
|
|
2877
|
+
'behavior',
|
|
2878
|
+
'idempotency',
|
|
2879
|
+
'subject',
|
|
2880
|
+
'refreshTargets',
|
|
2881
|
+
'fileIntent',
|
|
2882
|
+
].includes(key)) ||
|
|
2883
|
+
browser.exposure !== 'authenticated' ||
|
|
2884
|
+
!['read', 'controlled'].includes(string(browser.behavior)) ||
|
|
2885
|
+
!['none', 'required'].includes(string(browser.idempotency)) ||
|
|
2886
|
+
(browser.behavior === 'controlled' &&
|
|
2887
|
+
browser.idempotency !== 'required') ||
|
|
2888
|
+
(browser.behavior === 'read' && browser.idempotency !== 'none') ||
|
|
2889
|
+
!isRecord(browser.subject) ||
|
|
2890
|
+
Object.keys(subject).some(key => !['resourceCode', 'inputField'].includes(key)) ||
|
|
2891
|
+
!declaredResources.has(subjectResourceCode) ||
|
|
2892
|
+
requestSchema.type !== 'object' ||
|
|
2893
|
+
requestSchema.additionalProperties !== false ||
|
|
2894
|
+
responseSchema.type !== 'object' ||
|
|
2895
|
+
responseSchema.additionalProperties !== false ||
|
|
2896
|
+
!/^[A-Za-z][A-Za-z0-9_]{0,127}$/.test(inputField) ||
|
|
2897
|
+
!(inputField in requestProperties) ||
|
|
2898
|
+
!requiredFields.includes(inputField) ||
|
|
2899
|
+
subjectInputSchema.type !== 'string' ||
|
|
2900
|
+
subjectInputSchema.format !== 'uuid' ||
|
|
2901
|
+
refreshTargets.length > 16 ||
|
|
2902
|
+
new Set(refreshKeys).size !== refreshKeys.length;
|
|
2903
|
+
for (const rawTarget of refreshTargets) {
|
|
2904
|
+
const target = object(rawTarget);
|
|
2905
|
+
const targetCode = string(target.code);
|
|
2906
|
+
invalid ||=
|
|
2907
|
+
Object.keys(target).some(key => !['kind', 'code'].includes(key)) ||
|
|
2908
|
+
target.kind !== 'subject-surface' ||
|
|
2909
|
+
!declaredSubjectSurfaces.has(targetCode) ||
|
|
2910
|
+
declaredSubjectSurfaces.get(targetCode) !== subjectResourceCode;
|
|
2911
|
+
}
|
|
2912
|
+
if (browser.fileIntent !== undefined) {
|
|
2913
|
+
const fileIntent = object(browser.fileIntent);
|
|
2914
|
+
const recordResourceCode = string(fileIntent.recordResourceCode);
|
|
2915
|
+
const fields = declaredResourceFields.get(recordResourceCode);
|
|
2916
|
+
const recordIdInputField = string(fileIntent.recordIdInputField);
|
|
2917
|
+
const recordInputSchema = object(requestProperties[recordIdInputField]);
|
|
2918
|
+
const relationField = string(fileIntent.relationField);
|
|
2919
|
+
const relation = fields?.get(relationField);
|
|
2920
|
+
const relationSource = object(relation?.source);
|
|
2921
|
+
const fileNameField = string(fileIntent.fileNameField);
|
|
2922
|
+
const contentTypeField = string(fileIntent.contentTypeField);
|
|
2923
|
+
const sizeField = string(fileIntent.sizeField);
|
|
2924
|
+
const purposes = Array.isArray(fileIntent.purposes)
|
|
2925
|
+
? fileIntent.purposes.map(string)
|
|
2926
|
+
: [];
|
|
2927
|
+
const allowedKeys = new Set([
|
|
2928
|
+
'recordResourceCode',
|
|
2929
|
+
'recordIdInputField',
|
|
2930
|
+
'relationField',
|
|
2931
|
+
'fileNameField',
|
|
2932
|
+
'contentTypeField',
|
|
2933
|
+
'sizeField',
|
|
2934
|
+
'purposes',
|
|
2935
|
+
'maxTtlSeconds',
|
|
2936
|
+
'maxBytes',
|
|
2937
|
+
]);
|
|
2938
|
+
invalid ||=
|
|
2939
|
+
!isRecord(browser.fileIntent) ||
|
|
2940
|
+
Object.keys(fileIntent).some(key => !allowedKeys.has(key)) ||
|
|
2941
|
+
Object.keys(fileIntent).some(key => fileIntent[key] === undefined) ||
|
|
2942
|
+
method !== 'GET' ||
|
|
2943
|
+
browser.behavior !== 'read' ||
|
|
2944
|
+
browser.idempotency !== 'none' ||
|
|
2945
|
+
!fields ||
|
|
2946
|
+
!requiredFields.includes(recordIdInputField) ||
|
|
2947
|
+
recordInputSchema.type !== 'string' ||
|
|
2948
|
+
recordInputSchema.format !== 'uuid' ||
|
|
2949
|
+
!relation ||
|
|
2950
|
+
!['uuid', 'resource-ref.single'].includes(string(relation.type)) ||
|
|
2951
|
+
(relation.type === 'resource-ref.single' &&
|
|
2952
|
+
(relationSource.kind !== 'resource' ||
|
|
2953
|
+
relationSource.resourceCode !== subjectResourceCode)) ||
|
|
2954
|
+
!['text.short', 'text.long'].includes(string(fields?.get(fileNameField)?.type)) ||
|
|
2955
|
+
!['text.short', 'text.long'].includes(string(fields?.get(contentTypeField)?.type)) ||
|
|
2956
|
+
string(fields?.get(sizeField)?.type) !== 'number.integer' ||
|
|
2957
|
+
purposes.length < 1 ||
|
|
2958
|
+
purposes.length > 2 ||
|
|
2959
|
+
new Set(purposes).size !== purposes.length ||
|
|
2960
|
+
purposes.some(purpose => !['preview', 'download'].includes(purpose)) ||
|
|
2961
|
+
!Number.isSafeInteger(Number(fileIntent.maxTtlSeconds)) ||
|
|
2962
|
+
Number(fileIntent.maxTtlSeconds) < 1 ||
|
|
2963
|
+
Number(fileIntent.maxTtlSeconds) > 300 ||
|
|
2964
|
+
!Number.isSafeInteger(Number(fileIntent.maxBytes)) ||
|
|
2965
|
+
Number(fileIntent.maxBytes) < 1 ||
|
|
2966
|
+
Number(fileIntent.maxBytes) > 104857600;
|
|
2967
|
+
}
|
|
2968
|
+
if (invalid) {
|
|
2969
|
+
diagnostics.push(diagnostic('APP_CONFIG_BACKEND_OPERATION_BROWSER_INVALID', 'browser operation 必须绑定已声明父资源和必填请求字段;controlled 必须强制幂等,刷新目标必须引用同一父资源的 subject surface;fileIntent 必须是只读、绑定关系记录与元数据字段,并限制为 5 分钟/100 MiB 内', `${path}.browser`));
|
|
2970
|
+
}
|
|
2971
|
+
}
|
|
2681
2972
|
if (operation.ai !== undefined) {
|
|
2682
2973
|
const ai = object(operation.ai);
|
|
2683
2974
|
const aiResources = Array.isArray(ai.resources) ? ai.resources : [];
|
|
@@ -4460,7 +4751,12 @@ export function defineOpenXiangdaApp(declaration) {
|
|
|
4460
4751
|
},
|
|
4461
4752
|
platform: { root: 'platform', ...declaration.platform },
|
|
4462
4753
|
...(modules || declaration.data ? {
|
|
4463
|
-
data: {
|
|
4754
|
+
data: {
|
|
4755
|
+
resources: [...(declaration.data?.resources || []), ...projected.resources],
|
|
4756
|
+
...(declaration.data?.subjectReadSurfaces
|
|
4757
|
+
? { subjectReadSurfaces: declaration.data.subjectReadSurfaces }
|
|
4758
|
+
: {}),
|
|
4759
|
+
},
|
|
4464
4760
|
} : {}),
|
|
4465
4761
|
});
|
|
4466
4762
|
// 生成式用户标准面:登录默认路由仍是模板占位时,指向首页资源的标准页。
|
|
@@ -4491,6 +4787,9 @@ export function defineOpenXiangdaApp(declaration) {
|
|
|
4491
4787
|
? {
|
|
4492
4788
|
data: {
|
|
4493
4789
|
resources: data.resources.map(resource => materializeDataResource(normalizedDeclaration.app.code, resource)),
|
|
4790
|
+
...(data.subjectReadSurfaces
|
|
4791
|
+
? { subjectReadSurfaces: data.subjectReadSurfaces }
|
|
4792
|
+
: {}),
|
|
4494
4793
|
},
|
|
4495
4794
|
}
|
|
4496
4795
|
: {}),
|