zentao-api 0.3.3 → 0.4.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/browser/zentao-api.global.js +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/modules/generated.d.ts +26 -82
- package/dist/modules/generated.js +26 -34
- package/dist/modules/query.d.ts +13 -1
- package/dist/modules/query.js +59 -0
- package/dist/modules/registry.d.ts +27 -83
- package/dist/modules/registry.js +1 -1
- package/dist/modules/resolve.d.ts +2 -2
- package/dist/modules/resolve.js +70 -51
- package/dist/request/index.js +8 -4
- package/dist/types/module.d.ts +5 -1
- package/dist/types/options.d.ts +2 -0
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.js +1 -1
- package/dist/utils/object.d.ts +2 -0
- package/dist/utils/object.js +4 -0
- package/dist/version.js +2 -2
- package/package.json +1 -1
|
@@ -1034,19 +1034,12 @@ export declare const MODULES: readonly [{
|
|
|
1034
1034
|
readonly display: "获取产品计划列表,支持获取产品下的产品计划";
|
|
1035
1035
|
readonly type: "list";
|
|
1036
1036
|
readonly method: "get";
|
|
1037
|
-
readonly path: "/
|
|
1037
|
+
readonly path: "/products/{productID}/productplans";
|
|
1038
1038
|
readonly resultType: "list";
|
|
1039
1039
|
readonly pagerGetter: "pager";
|
|
1040
1040
|
readonly resultGetter: "productplans";
|
|
1041
1041
|
readonly pathParams: {
|
|
1042
|
-
readonly
|
|
1043
|
-
readonly description: "产品计划范围";
|
|
1044
|
-
readonly options: readonly [{
|
|
1045
|
-
readonly value: "products";
|
|
1046
|
-
readonly label: "产品";
|
|
1047
|
-
}];
|
|
1048
|
-
};
|
|
1049
|
-
readonly scopeID: "范围ID";
|
|
1042
|
+
readonly productID: "所属产品ID";
|
|
1050
1043
|
};
|
|
1051
1044
|
readonly params: readonly [{
|
|
1052
1045
|
readonly name: "browseType";
|
|
@@ -1242,7 +1235,7 @@ export declare const MODULES: readonly [{
|
|
|
1242
1235
|
readonly resultGetter: "stories";
|
|
1243
1236
|
readonly pathParams: {
|
|
1244
1237
|
readonly scope: {
|
|
1245
|
-
readonly description: "
|
|
1238
|
+
readonly description: "需求所属范围";
|
|
1246
1239
|
readonly options: readonly [{
|
|
1247
1240
|
readonly value: "products";
|
|
1248
1241
|
readonly label: "产品";
|
|
@@ -1254,7 +1247,7 @@ export declare const MODULES: readonly [{
|
|
|
1254
1247
|
readonly label: "执行";
|
|
1255
1248
|
}];
|
|
1256
1249
|
};
|
|
1257
|
-
readonly scopeID: "
|
|
1250
|
+
readonly scopeID: "所属范围ID";
|
|
1258
1251
|
};
|
|
1259
1252
|
readonly params: readonly [{
|
|
1260
1253
|
readonly name: "browseType";
|
|
@@ -1577,19 +1570,12 @@ export declare const MODULES: readonly [{
|
|
|
1577
1570
|
readonly display: "获取业务需求列表,支持获取产品下的业务需求";
|
|
1578
1571
|
readonly type: "list";
|
|
1579
1572
|
readonly method: "get";
|
|
1580
|
-
readonly path: "/
|
|
1573
|
+
readonly path: "/products/{productID}/epics";
|
|
1581
1574
|
readonly resultType: "list";
|
|
1582
1575
|
readonly pagerGetter: "pager";
|
|
1583
1576
|
readonly resultGetter: "epics";
|
|
1584
1577
|
readonly pathParams: {
|
|
1585
|
-
readonly
|
|
1586
|
-
readonly description: "业务需求范围";
|
|
1587
|
-
readonly options: readonly [{
|
|
1588
|
-
readonly value: "products";
|
|
1589
|
-
readonly label: "产品";
|
|
1590
|
-
}];
|
|
1591
|
-
};
|
|
1592
|
-
readonly scopeID: "范围ID";
|
|
1578
|
+
readonly productID: "所属产品ID";
|
|
1593
1579
|
};
|
|
1594
1580
|
readonly params: readonly [{
|
|
1595
1581
|
readonly name: "browseType";
|
|
@@ -1903,19 +1889,12 @@ export declare const MODULES: readonly [{
|
|
|
1903
1889
|
readonly display: "获取用户需求列表,支持获取产品下的用户需求";
|
|
1904
1890
|
readonly type: "list";
|
|
1905
1891
|
readonly method: "get";
|
|
1906
|
-
readonly path: "/
|
|
1892
|
+
readonly path: "/products/{productID}/requirements";
|
|
1907
1893
|
readonly resultType: "list";
|
|
1908
1894
|
readonly pagerGetter: "pager";
|
|
1909
1895
|
readonly resultGetter: "requirements";
|
|
1910
1896
|
readonly pathParams: {
|
|
1911
|
-
readonly
|
|
1912
|
-
readonly description: "用户需求范围";
|
|
1913
|
-
readonly options: readonly [{
|
|
1914
|
-
readonly value: "products";
|
|
1915
|
-
readonly label: "产品";
|
|
1916
|
-
}];
|
|
1917
|
-
};
|
|
1918
|
-
readonly scopeID: "范围ID";
|
|
1897
|
+
readonly productID: "所属产品ID";
|
|
1919
1898
|
};
|
|
1920
1899
|
readonly params: readonly [{
|
|
1921
1900
|
readonly name: "browseType";
|
|
@@ -2226,7 +2205,7 @@ export declare const MODULES: readonly [{
|
|
|
2226
2205
|
readonly resultGetter: "bugs";
|
|
2227
2206
|
readonly pathParams: {
|
|
2228
2207
|
readonly scope: {
|
|
2229
|
-
readonly description: "Bug
|
|
2208
|
+
readonly description: "Bug所属范围";
|
|
2230
2209
|
readonly options: readonly [{
|
|
2231
2210
|
readonly value: "products";
|
|
2232
2211
|
readonly label: "产品";
|
|
@@ -2238,7 +2217,7 @@ export declare const MODULES: readonly [{
|
|
|
2238
2217
|
readonly label: "执行";
|
|
2239
2218
|
}];
|
|
2240
2219
|
};
|
|
2241
|
-
readonly scopeID: "
|
|
2220
|
+
readonly scopeID: "所属范围ID";
|
|
2242
2221
|
};
|
|
2243
2222
|
readonly params: readonly [{
|
|
2244
2223
|
readonly name: "browseType";
|
|
@@ -2560,7 +2539,7 @@ export declare const MODULES: readonly [{
|
|
|
2560
2539
|
readonly resultGetter: "testcases";
|
|
2561
2540
|
readonly pathParams: {
|
|
2562
2541
|
readonly scope: {
|
|
2563
|
-
readonly description: "
|
|
2542
|
+
readonly description: "测试用例所属范围";
|
|
2564
2543
|
readonly options: readonly [{
|
|
2565
2544
|
readonly value: "products";
|
|
2566
2545
|
readonly label: "产品";
|
|
@@ -2572,7 +2551,7 @@ export declare const MODULES: readonly [{
|
|
|
2572
2551
|
readonly label: "执行";
|
|
2573
2552
|
}];
|
|
2574
2553
|
};
|
|
2575
|
-
readonly scopeID: "
|
|
2554
|
+
readonly scopeID: "所属范围ID";
|
|
2576
2555
|
};
|
|
2577
2556
|
readonly params: readonly [{
|
|
2578
2557
|
readonly name: "browseType";
|
|
@@ -2804,19 +2783,12 @@ export declare const MODULES: readonly [{
|
|
|
2804
2783
|
readonly display: "获取任务列表,支持获取执行下的任务";
|
|
2805
2784
|
readonly type: "list";
|
|
2806
2785
|
readonly method: "get";
|
|
2807
|
-
readonly path: "/
|
|
2786
|
+
readonly path: "/executions/{executionID}/tasks";
|
|
2808
2787
|
readonly resultType: "list";
|
|
2809
2788
|
readonly pagerGetter: "pager";
|
|
2810
2789
|
readonly resultGetter: "tasks";
|
|
2811
2790
|
readonly pathParams: {
|
|
2812
|
-
readonly
|
|
2813
|
-
readonly description: "任务范围";
|
|
2814
|
-
readonly options: readonly [{
|
|
2815
|
-
readonly value: "executions";
|
|
2816
|
-
readonly label: "执行";
|
|
2817
|
-
}];
|
|
2818
|
-
};
|
|
2819
|
-
readonly scopeID: "范围ID";
|
|
2791
|
+
readonly executionID: "所属执行ID";
|
|
2820
2792
|
};
|
|
2821
2793
|
readonly params: readonly [{
|
|
2822
2794
|
readonly name: "status";
|
|
@@ -3180,19 +3152,12 @@ export declare const MODULES: readonly [{
|
|
|
3180
3152
|
readonly display: "获取反馈列表,支持获取产品下的反馈";
|
|
3181
3153
|
readonly type: "list";
|
|
3182
3154
|
readonly method: "get";
|
|
3183
|
-
readonly path: "/
|
|
3155
|
+
readonly path: "/products/{productID}/feedbacks";
|
|
3184
3156
|
readonly resultType: "list";
|
|
3185
3157
|
readonly pagerGetter: "pager";
|
|
3186
3158
|
readonly resultGetter: "feedbacks";
|
|
3187
3159
|
readonly pathParams: {
|
|
3188
|
-
readonly
|
|
3189
|
-
readonly description: "反馈范围";
|
|
3190
|
-
readonly options: readonly [{
|
|
3191
|
-
readonly value: "products";
|
|
3192
|
-
readonly label: "产品";
|
|
3193
|
-
}];
|
|
3194
|
-
};
|
|
3195
|
-
readonly scopeID: "范围ID";
|
|
3160
|
+
readonly productID: "所属产品ID";
|
|
3196
3161
|
};
|
|
3197
3162
|
readonly params: readonly [{
|
|
3198
3163
|
readonly name: "browseType";
|
|
@@ -3440,19 +3405,12 @@ export declare const MODULES: readonly [{
|
|
|
3440
3405
|
readonly display: "获取工单列表,支持获取产品下的工单";
|
|
3441
3406
|
readonly type: "list";
|
|
3442
3407
|
readonly method: "get";
|
|
3443
|
-
readonly path: "/
|
|
3408
|
+
readonly path: "/products/{productID}/tickets";
|
|
3444
3409
|
readonly resultType: "list";
|
|
3445
3410
|
readonly pagerGetter: "pager";
|
|
3446
3411
|
readonly resultGetter: "tickets";
|
|
3447
3412
|
readonly pathParams: {
|
|
3448
|
-
readonly
|
|
3449
|
-
readonly description: "工单范围";
|
|
3450
|
-
readonly options: readonly [{
|
|
3451
|
-
readonly value: "products";
|
|
3452
|
-
readonly label: "产品";
|
|
3453
|
-
}];
|
|
3454
|
-
};
|
|
3455
|
-
readonly scopeID: "范围ID";
|
|
3413
|
+
readonly productID: "所属产品ID";
|
|
3456
3414
|
};
|
|
3457
3415
|
readonly params: readonly [{
|
|
3458
3416
|
readonly name: "browseType";
|
|
@@ -3716,18 +3674,11 @@ export declare const MODULES: readonly [{
|
|
|
3716
3674
|
readonly display: "获取应用列表,支持获取产品下的应用";
|
|
3717
3675
|
readonly type: "list";
|
|
3718
3676
|
readonly method: "get";
|
|
3719
|
-
readonly path: "/
|
|
3677
|
+
readonly path: "/products/{productID}/systems";
|
|
3720
3678
|
readonly resultType: "list";
|
|
3721
3679
|
readonly pagerGetter: "pager";
|
|
3722
3680
|
readonly pathParams: {
|
|
3723
|
-
readonly
|
|
3724
|
-
readonly description: "应用范围";
|
|
3725
|
-
readonly options: readonly [{
|
|
3726
|
-
readonly value: "products";
|
|
3727
|
-
readonly label: "产品";
|
|
3728
|
-
}];
|
|
3729
|
-
};
|
|
3730
|
-
readonly scopeID: "范围ID";
|
|
3681
|
+
readonly productID: "所属产品ID";
|
|
3731
3682
|
};
|
|
3732
3683
|
}, {
|
|
3733
3684
|
readonly name: "create";
|
|
@@ -3822,7 +3773,7 @@ export declare const MODULES: readonly [{
|
|
|
3822
3773
|
readonly resultGetter: "builds";
|
|
3823
3774
|
readonly pathParams: {
|
|
3824
3775
|
readonly scope: {
|
|
3825
|
-
readonly description: "
|
|
3776
|
+
readonly description: "版本所属范围";
|
|
3826
3777
|
readonly options: readonly [{
|
|
3827
3778
|
readonly value: "projects";
|
|
3828
3779
|
readonly label: "项目";
|
|
@@ -3831,7 +3782,7 @@ export declare const MODULES: readonly [{
|
|
|
3831
3782
|
readonly label: "执行";
|
|
3832
3783
|
}];
|
|
3833
3784
|
};
|
|
3834
|
-
readonly scopeID: "
|
|
3785
|
+
readonly scopeID: "所属范围ID";
|
|
3835
3786
|
};
|
|
3836
3787
|
}, {
|
|
3837
3788
|
readonly name: "create";
|
|
@@ -3974,7 +3925,7 @@ export declare const MODULES: readonly [{
|
|
|
3974
3925
|
readonly resultGetter: "testtasks";
|
|
3975
3926
|
readonly pathParams: {
|
|
3976
3927
|
readonly scope: {
|
|
3977
|
-
readonly description: "
|
|
3928
|
+
readonly description: "测试单所属范围";
|
|
3978
3929
|
readonly options: readonly [{
|
|
3979
3930
|
readonly value: "products";
|
|
3980
3931
|
readonly label: "产品";
|
|
@@ -3986,7 +3937,7 @@ export declare const MODULES: readonly [{
|
|
|
3986
3937
|
readonly label: "执行";
|
|
3987
3938
|
}];
|
|
3988
3939
|
};
|
|
3989
|
-
readonly scopeID: "
|
|
3940
|
+
readonly scopeID: "所属范围ID";
|
|
3990
3941
|
};
|
|
3991
3942
|
}, {
|
|
3992
3943
|
readonly name: "create";
|
|
@@ -4132,19 +4083,12 @@ export declare const MODULES: readonly [{
|
|
|
4132
4083
|
readonly display: "获取发布列表,支持获取产品下的发布";
|
|
4133
4084
|
readonly type: "list";
|
|
4134
4085
|
readonly method: "get";
|
|
4135
|
-
readonly path: "/
|
|
4086
|
+
readonly path: "/products/{productID}/releases";
|
|
4136
4087
|
readonly resultType: "list";
|
|
4137
4088
|
readonly pagerGetter: "pager";
|
|
4138
4089
|
readonly resultGetter: "releases";
|
|
4139
4090
|
readonly pathParams: {
|
|
4140
|
-
readonly
|
|
4141
|
-
readonly description: "发布范围";
|
|
4142
|
-
readonly options: readonly [{
|
|
4143
|
-
readonly value: "products";
|
|
4144
|
-
readonly label: "产品";
|
|
4145
|
-
}];
|
|
4146
|
-
};
|
|
4147
|
-
readonly scopeID: "范围ID";
|
|
4091
|
+
readonly productID: "所属产品ID";
|
|
4148
4092
|
};
|
|
4149
4093
|
}, {
|
|
4150
4094
|
readonly name: "create";
|
|
@@ -4294,4 +4238,4 @@ export declare const MODULES: readonly [{
|
|
|
4294
4238
|
}];
|
|
4295
4239
|
export { type DefineModulesOptions, defineModules, defineModuleActions, extendModuleAction, resetModuleDefinitions, } from './define.js';
|
|
4296
4240
|
export { applyBuiltinOverrides };
|
|
4297
|
-
export { getModule, getModuleAction, getModuleNames, isModuleName, } from './query.js';
|
|
4241
|
+
export { getModule, getModuleAction, getModuleActionParams, getModuleNames, isModuleName, } from './query.js';
|
package/dist/modules/registry.js
CHANGED
|
@@ -16,4 +16,4 @@ export { applyBuiltinOverrides };
|
|
|
16
16
|
// - 注册为 store 的「重置后钩子」,使 resetModuleDefinitions 还原内置基线后自动重新应用。
|
|
17
17
|
setPostResetHook(applyBuiltinOverrides);
|
|
18
18
|
applyBuiltinOverrides();
|
|
19
|
-
export { getModule, getModuleAction, getModuleNames, isModuleName, } from './query.js';
|
|
19
|
+
export { getModule, getModuleAction, getModuleActionParams, getModuleNames, isModuleName, } from './query.js';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { ListPagerInfo, ModuleAction,
|
|
1
|
+
import type { ListPagerInfo, ModuleAction, ModuleActionRequest, ModuleDefinition } from '../types/index.js';
|
|
2
2
|
/** 将模块名、动作名和调用参数解析为实际 API 请求路径、查询参数和请求体。 */
|
|
3
|
-
export declare function
|
|
3
|
+
export declare function resolveActionRequest(module: ModuleDefinition, actionName: string, params?: Record<string, unknown>): ModuleActionRequest;
|
|
4
4
|
/** 根据动作定义中的 resultGetter,从原始响应里提取业务数据。 */
|
|
5
5
|
export declare function extractResult(action: ModuleAction, response: Record<string, unknown>, params?: Record<string, unknown>): unknown;
|
|
6
6
|
/** 根据动作定义中的 pagerGetter,从原始响应里提取分页信息。 */
|
package/dist/modules/resolve.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ZentaoError } from '../misc/errors.js';
|
|
2
|
-
import { getNestedValue, isRecord } from '../utils/index.js';
|
|
2
|
+
import { getNestedValue, isBlank, isRecord } from '../utils/index.js';
|
|
3
3
|
import { getModuleAction } from './registry.js';
|
|
4
4
|
const SCOPE_MAP = {
|
|
5
5
|
product: 'products',
|
|
@@ -11,7 +11,7 @@ const SCOPE_KEY_ORDER = ['execution', 'project', 'product'];
|
|
|
11
11
|
function pickScope(params) {
|
|
12
12
|
for (const key of SCOPE_KEY_ORDER) {
|
|
13
13
|
const value = params[key] ?? params[`${key}ID`];
|
|
14
|
-
if (value
|
|
14
|
+
if (isBlank(value))
|
|
15
15
|
continue;
|
|
16
16
|
const numberValue = Number(value);
|
|
17
17
|
if (!Number.isNaN(numberValue)) {
|
|
@@ -24,7 +24,7 @@ function pickScope(params) {
|
|
|
24
24
|
function resolvePath(action, values) {
|
|
25
25
|
return action.path.replace(/\{(\w+)\}/g, (_, key) => {
|
|
26
26
|
const value = values[key];
|
|
27
|
-
if (value
|
|
27
|
+
if (isBlank(value)) {
|
|
28
28
|
throw new ZentaoError('E_MISSING_PARAM', { param: key });
|
|
29
29
|
}
|
|
30
30
|
return String(value);
|
|
@@ -37,18 +37,21 @@ function parseData(value) {
|
|
|
37
37
|
if (typeof value === 'string') {
|
|
38
38
|
try {
|
|
39
39
|
const parsed = JSON.parse(value);
|
|
40
|
-
return
|
|
40
|
+
return isRecord(parsed) ? parsed : undefined;
|
|
41
41
|
}
|
|
42
42
|
catch {
|
|
43
43
|
return undefined;
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
|
-
return
|
|
46
|
+
return isRecord(value) ? value : undefined;
|
|
47
47
|
}
|
|
48
48
|
const TRUTHY_STRINGS = new Set(['true', '1', 'yes', 'on']);
|
|
49
49
|
const FALSY_STRINGS = new Set(['false', '0', 'no', 'off']);
|
|
50
|
-
/**
|
|
51
|
-
|
|
50
|
+
/**
|
|
51
|
+
* 按 OpenAPI schema 的基础类型对参数做轻量转换。
|
|
52
|
+
* @param fromData 值是否来自 `params.data`(影响数组字段是否包装对象)。
|
|
53
|
+
*/
|
|
54
|
+
function coerceValue(value, type, paramName, fromData = false) {
|
|
52
55
|
if (value === undefined || value === null)
|
|
53
56
|
return value;
|
|
54
57
|
if (type === 'number' || type === 'integer') {
|
|
@@ -75,50 +78,64 @@ function coerceValue(value, type, paramName) {
|
|
|
75
78
|
}
|
|
76
79
|
throw new ZentaoError('E_INVALID_PARAM', { param: paramName, value: String(value) });
|
|
77
80
|
}
|
|
81
|
+
if (type === 'array') {
|
|
82
|
+
if (Array.isArray(value))
|
|
83
|
+
return value;
|
|
84
|
+
if (typeof value === 'string')
|
|
85
|
+
return value.split(',');
|
|
86
|
+
// 来自 params.data 的对象按原样保留,不强行包成单元素数组。
|
|
87
|
+
if (fromData && isRecord(value))
|
|
88
|
+
return value;
|
|
89
|
+
return [value];
|
|
90
|
+
}
|
|
78
91
|
return value;
|
|
79
92
|
}
|
|
80
|
-
/**
|
|
81
|
-
|
|
82
|
-
const
|
|
83
|
-
const pathValues = {};
|
|
93
|
+
/** 解析路径占位符的取值:scope 列表前缀、对象 ID,以及默认值补齐。 */
|
|
94
|
+
function resolvePathValues(module, action, params) {
|
|
95
|
+
const values = {};
|
|
84
96
|
const pathParamNames = Object.keys(action.pathParams ?? {});
|
|
85
97
|
// 生成定义中的 scope 列表接口会统一成 /{scope}/{scopeID}/xxx。
|
|
86
98
|
if (pathParamNames.includes('scope')) {
|
|
87
99
|
const scope = pickScope(params);
|
|
88
100
|
if (!scope)
|
|
89
101
|
throw new ZentaoError('E_MISSING_PARAM', { param: 'product/project/execution' });
|
|
90
|
-
|
|
91
|
-
|
|
102
|
+
values.scope = scope.scope;
|
|
103
|
+
values.scopeID = scope.scopeID;
|
|
92
104
|
}
|
|
105
|
+
// 对象 ID 可来自 `id`、`{module}ID` 或路径中的 `xxxID` 占位符。
|
|
93
106
|
const idParamName = pathParamNames.find((key) => key.endsWith('ID') && key !== 'scopeID');
|
|
94
107
|
const idValue = params.id ?? params[`${module.name}ID`] ?? (idParamName ? params[idParamName] : undefined);
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
108
|
+
const idNumber = idValue === undefined ? Number.NaN : Number(idValue);
|
|
109
|
+
const id = Number.isNaN(idNumber) ? undefined : idNumber;
|
|
110
|
+
if (idParamName && id !== undefined) {
|
|
111
|
+
values[idParamName] = id;
|
|
98
112
|
}
|
|
99
113
|
// pathParams 中未显式传值的参数,可从定义里的默认值或第一个可选项补齐。
|
|
100
114
|
for (const key of pathParamNames) {
|
|
101
|
-
if (key === 'scope' || key === 'scopeID' ||
|
|
115
|
+
if (key === 'scope' || key === 'scopeID' || values[key] !== undefined)
|
|
102
116
|
continue;
|
|
103
117
|
const definition = action.pathParams?.[key];
|
|
104
118
|
const value = params[key];
|
|
105
119
|
if (value !== undefined) {
|
|
106
|
-
|
|
120
|
+
values[key] = value;
|
|
107
121
|
continue;
|
|
108
122
|
}
|
|
109
123
|
if (typeof definition === 'object') {
|
|
110
124
|
if (definition.defaultValue !== undefined) {
|
|
111
|
-
|
|
125
|
+
values[key] = definition.defaultValue;
|
|
112
126
|
}
|
|
113
127
|
else if (definition.options?.[0]?.value !== undefined) {
|
|
114
|
-
|
|
128
|
+
values[key] = definition.options[0].value;
|
|
115
129
|
}
|
|
116
130
|
}
|
|
117
|
-
if (
|
|
131
|
+
if (values[key] === undefined) {
|
|
118
132
|
throw new ZentaoError('E_MISSING_PARAM', { param: key });
|
|
119
133
|
}
|
|
120
134
|
}
|
|
121
|
-
|
|
135
|
+
return { values, id };
|
|
136
|
+
}
|
|
137
|
+
/** 仅从 action.params 中声明过的字段生成查询参数,避免把 body 字段误放到 URL 上。 */
|
|
138
|
+
function buildQuery(action, params) {
|
|
122
139
|
const query = {};
|
|
123
140
|
for (const param of action.params ?? []) {
|
|
124
141
|
let value = params[param.name];
|
|
@@ -135,41 +152,43 @@ export function resolveModuleCommand(module, actionName, params = {}) {
|
|
|
135
152
|
query[param.name] = value;
|
|
136
153
|
}
|
|
137
154
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
}
|
|
160
|
-
if (value !== undefined) {
|
|
161
|
-
data[key] = value;
|
|
162
|
-
}
|
|
155
|
+
return query;
|
|
156
|
+
}
|
|
157
|
+
/** 按 requestBody schema 组装请求体,并对各字段做取值优先级解析与类型转换。 */
|
|
158
|
+
function buildRequestBody(action, params) {
|
|
159
|
+
const base = parseData(params.data);
|
|
160
|
+
if (action.requestBody?.schema?.type !== 'object')
|
|
161
|
+
return base;
|
|
162
|
+
const data = base ? { ...base } : {};
|
|
163
|
+
const schema = action.requestBody.schema;
|
|
164
|
+
const required = new Set(schema.required ?? []);
|
|
165
|
+
for (const [key, property] of Object.entries(schema.properties ?? {})) {
|
|
166
|
+
// body 字段优先级:params.data 中的字段 > 平铺 params 字段 > schema 默认值。
|
|
167
|
+
const hasDataValue = Object.prototype.hasOwnProperty.call(data, key);
|
|
168
|
+
const hasParamValue = Object.prototype.hasOwnProperty.call(params, key);
|
|
169
|
+
const raw = hasDataValue ? data[key] : hasParamValue ? params[key] : property.defaultValue;
|
|
170
|
+
if (raw === undefined && (property.required || required.has(key))) {
|
|
171
|
+
throw new ZentaoError('E_MISSING_PARAM', { param: key });
|
|
172
|
+
}
|
|
173
|
+
const value = coerceValue(raw, property.type, key, hasDataValue);
|
|
174
|
+
if (value !== undefined) {
|
|
175
|
+
data[key] = value;
|
|
163
176
|
}
|
|
164
177
|
}
|
|
178
|
+
return data;
|
|
179
|
+
}
|
|
180
|
+
/** 将模块名、动作名和调用参数解析为实际 API 请求路径、查询参数和请求体。 */
|
|
181
|
+
export function resolveActionRequest(module, actionName, params = {}) {
|
|
182
|
+
const action = getModuleAction(module.name, actionName);
|
|
183
|
+
const { values, id } = resolvePathValues(module, action, params);
|
|
165
184
|
return {
|
|
166
185
|
module: module.name,
|
|
167
186
|
action,
|
|
168
187
|
params,
|
|
169
|
-
path: resolvePath(action,
|
|
170
|
-
query,
|
|
171
|
-
data,
|
|
172
|
-
id
|
|
188
|
+
path: resolvePath(action, values),
|
|
189
|
+
query: buildQuery(action, params),
|
|
190
|
+
data: buildRequestBody(action, params),
|
|
191
|
+
id,
|
|
173
192
|
};
|
|
174
193
|
}
|
|
175
194
|
/** 根据动作定义中的 resultGetter,从原始响应里提取业务数据。 */
|
package/dist/request/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ZentaoError } from '../misc/errors.js';
|
|
2
2
|
import { getGlobalOptions } from '../misc/global-options.js';
|
|
3
3
|
import { getModule, getModuleAction } from '../modules/registry.js';
|
|
4
|
-
import { extractPager, extractResult,
|
|
4
|
+
import { extractPager, extractResult, resolveActionRequest } from '../modules/resolve.js';
|
|
5
5
|
import { isRecord, processData } from '../utils/index.js';
|
|
6
6
|
/** 将 `moduleName`、`moduleName/methodName` 或 `moduleName/<objectID>` 请求名拆成模块名、动作名和对象 ID。 */
|
|
7
7
|
function splitRequestName(name) {
|
|
@@ -50,7 +50,8 @@ function getExplicitDataKeys(data) {
|
|
|
50
50
|
* 在执行 `update` 动作前,用当前对象的现值填充用户未显式传入的 body 字段。
|
|
51
51
|
*
|
|
52
52
|
* 仅当模块存在 `type: 'get'` 动作且 update 动作声明了对象类型 body schema 时生效;
|
|
53
|
-
* 否则原样返回参数。GET
|
|
53
|
+
* 否则原样返回参数。GET 返回失败状态时会抛出 `E_API_FAILED`,避免继续发送未补齐的 PUT;
|
|
54
|
+
* GET 成功但返回非对象时跳过填充,交由后续 PUT 正常处理。
|
|
54
55
|
*
|
|
55
56
|
* 字段归属判断同时覆盖平铺 `params` 字段与 `params.data` 中的字段;只有 schema 中声明、
|
|
56
57
|
* 用户未传且当前对象存在的字段才会被补齐,避免覆盖用户本次想修改的字段。
|
|
@@ -64,7 +65,7 @@ async function autoFillUpdateParams(module, action, params, options) {
|
|
|
64
65
|
client: options.client,
|
|
65
66
|
timeout: options.timeout,
|
|
66
67
|
insecure: options.insecure,
|
|
67
|
-
throwOnFail:
|
|
68
|
+
throwOnFail: true,
|
|
68
69
|
})).data;
|
|
69
70
|
if (!isRecord(current))
|
|
70
71
|
return params;
|
|
@@ -188,7 +189,7 @@ export async function request(name, params = {}, options = {}) {
|
|
|
188
189
|
const finalParams = action.type === 'update' && (options.autoFill ?? globals.autoFill)
|
|
189
190
|
? await autoFillUpdateParams(module, action, mergedParams, options)
|
|
190
191
|
: mergedParams;
|
|
191
|
-
const command =
|
|
192
|
+
const command = resolveActionRequest(module, actionName, finalParams);
|
|
192
193
|
const raw = await client.request(command.path, {
|
|
193
194
|
method: String(command.action.method).toUpperCase(),
|
|
194
195
|
query: command.query,
|
|
@@ -196,6 +197,9 @@ export async function request(name, params = {}, options = {}) {
|
|
|
196
197
|
timeout: options.timeout ?? globals.timeout,
|
|
197
198
|
insecure: options.insecure ?? globals.insecure,
|
|
198
199
|
});
|
|
200
|
+
if (options.raw) {
|
|
201
|
+
return raw;
|
|
202
|
+
}
|
|
199
203
|
// limit 现归入本地处理选项;本次调用优先,缺省回落到全局默认。
|
|
200
204
|
const processOptions = { ...options, limit: options.limit ?? globals.limit };
|
|
201
205
|
const response = normalizeResponse(command, raw, processOptions);
|
package/dist/types/module.d.ts
CHANGED
|
@@ -11,10 +11,14 @@ export type ModuleActionParamOption = {
|
|
|
11
11
|
readonly value: unknown;
|
|
12
12
|
readonly label: string;
|
|
13
13
|
};
|
|
14
|
+
/** 模块动作参数角色。 */
|
|
15
|
+
export type ModuleActionParamRole = 'query' | 'path' | 'body';
|
|
14
16
|
/** 模块动作的查询参数定义。 */
|
|
15
17
|
export interface ModuleActionParam {
|
|
16
18
|
/** 参数名称。 */
|
|
17
19
|
name: string;
|
|
20
|
+
/** 参数角色。 */
|
|
21
|
+
role?: ModuleActionParamRole;
|
|
18
22
|
/** 参数说明。 */
|
|
19
23
|
description?: string;
|
|
20
24
|
/** 是否必填。 */
|
|
@@ -125,7 +129,7 @@ export interface ModuleDefinition {
|
|
|
125
129
|
actions: readonly ModuleAction[];
|
|
126
130
|
}
|
|
127
131
|
/** 将模块动作和参数解析后的可执行请求描述。 */
|
|
128
|
-
export interface
|
|
132
|
+
export interface ModuleActionRequest {
|
|
129
133
|
/** 模块名称。 */
|
|
130
134
|
module: string;
|
|
131
135
|
/** 匹配到的动作定义。 */
|
package/dist/types/options.d.ts
CHANGED
package/dist/utils/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { isRecord, getNestedValue } from './object.js';
|
|
1
|
+
export { isRecord, isBlank, getNestedValue } from './object.js';
|
|
2
2
|
export { asArray } from './array.js';
|
|
3
3
|
export { normalizeSiteUrl } from './url.js';
|
|
4
4
|
export { pickFields, pickFieldsSingle, filterData, searchData, sortData, processData, } from './data.js';
|
package/dist/utils/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { isRecord, getNestedValue } from './object.js';
|
|
1
|
+
export { isRecord, isBlank, getNestedValue } from './object.js';
|
|
2
2
|
export { asArray } from './array.js';
|
|
3
3
|
export { normalizeSiteUrl } from './url.js';
|
|
4
4
|
export { pickFields, pickFieldsSingle, filterData, searchData, sortData, processData, } from './data.js';
|
package/dist/utils/object.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
/** 判断值是否为普通对象(非数组、非 null)。 */
|
|
2
2
|
export declare function isRecord(value: unknown): value is Record<string, unknown>;
|
|
3
|
+
/** 判断值是否为“空”(undefined、null 或空字符串)。 */
|
|
4
|
+
export declare function isBlank(value: unknown): boolean;
|
|
3
5
|
export declare function getNestedValue(obj: unknown, path: string): unknown;
|
package/dist/utils/object.js
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
export function isRecord(value) {
|
|
3
3
|
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
4
4
|
}
|
|
5
|
+
/** 判断值是否为“空”(undefined、null 或空字符串)。 */
|
|
6
|
+
export function isBlank(value) {
|
|
7
|
+
return value === undefined || value === null || value === '';
|
|
8
|
+
}
|
|
5
9
|
export function getNestedValue(obj, path) {
|
|
6
10
|
const keys = path.split('.');
|
|
7
11
|
let current = obj;
|
package/dist/version.js
CHANGED