zentao-api 0.3.2 → 0.3.3
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 +1 -1
- package/dist/misc/errors.d.ts +2 -0
- package/dist/misc/errors.js +2 -0
- package/dist/modules/define.d.ts +8 -4
- package/dist/modules/define.js +8 -4
- package/dist/modules/generated.d.ts +0 -38
- package/dist/modules/generated.js +0 -38
- package/dist/modules/registry-store.d.ts +8 -0
- package/dist/modules/registry-store.js +50 -2
- package/dist/modules/registry.d.ts +0 -38
- package/dist/modules/resolve.d.ts +2 -2
- package/dist/modules/resolve.js +8 -8
- package/dist/request/index.d.ts +3 -0
- package/dist/request/index.js +58 -4
- package/dist/types/module.d.ts +38 -19
- package/dist/types/options.d.ts +16 -0
- package/dist/version.js +2 -2
- package/package.json +1 -1
|
@@ -1,6 +1,23 @@
|
|
|
1
1
|
import { ZentaoError } from '../misc/errors.js';
|
|
2
2
|
import { isRecord } from '../utils/object.js';
|
|
3
3
|
import { BUILTIN_MODULES } from './generated.js';
|
|
4
|
+
// 动作类型到 HTTP 方法 / 结果形态的默认推导表:当动作未显式声明 method / resultType 时按 type 补齐。
|
|
5
|
+
const METHOD_BY_TYPE = {
|
|
6
|
+
list: 'get',
|
|
7
|
+
get: 'get',
|
|
8
|
+
create: 'post',
|
|
9
|
+
update: 'put',
|
|
10
|
+
delete: 'delete',
|
|
11
|
+
action: 'post',
|
|
12
|
+
};
|
|
13
|
+
const RESULT_TYPE_BY_TYPE = {
|
|
14
|
+
list: 'list',
|
|
15
|
+
get: 'object',
|
|
16
|
+
create: 'object',
|
|
17
|
+
update: 'object',
|
|
18
|
+
delete: 'text',
|
|
19
|
+
action: 'text',
|
|
20
|
+
};
|
|
4
21
|
// 运行时注册表存放「深克隆 + 深冻结」后的模块定义:
|
|
5
22
|
// - 深克隆:避免用户后续修改自己的输入对象时污染注册表;
|
|
6
23
|
// - 深冻结:让 getModule / getModuleAction 可以零拷贝返回引用,
|
|
@@ -57,8 +74,32 @@ export function deepFreeze(value) {
|
|
|
57
74
|
}
|
|
58
75
|
return Object.freeze(value);
|
|
59
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* 按 {@link ModuleAction.type} 就地补齐省略的 `method` / `resultType`。
|
|
79
|
+
*
|
|
80
|
+
* 已显式声明的字段保持不变;当 `type` 无法推导出对应默认值时抛错,提示显式指定。
|
|
81
|
+
*
|
|
82
|
+
* @throws {ZentaoError} `E_INDETERMINATE_ACTION_METHOD` / `E_INDETERMINATE_ACTION_RESULT_TYPE`
|
|
83
|
+
*/
|
|
84
|
+
export function normalizeAction(action) {
|
|
85
|
+
if (action.method === undefined) {
|
|
86
|
+
const method = METHOD_BY_TYPE[action.type];
|
|
87
|
+
if (method === undefined) {
|
|
88
|
+
throw new ZentaoError('E_INDETERMINATE_ACTION_METHOD', { action: String(action.name), type: String(action.type) });
|
|
89
|
+
}
|
|
90
|
+
action.method = method;
|
|
91
|
+
}
|
|
92
|
+
if (action.resultType === undefined) {
|
|
93
|
+
const resultType = RESULT_TYPE_BY_TYPE[action.type];
|
|
94
|
+
if (resultType === undefined) {
|
|
95
|
+
throw new ZentaoError('E_INDETERMINATE_ACTION_RESULT_TYPE', { action: String(action.name), type: String(action.type) });
|
|
96
|
+
}
|
|
97
|
+
action.resultType = resultType;
|
|
98
|
+
}
|
|
99
|
+
return action;
|
|
100
|
+
}
|
|
60
101
|
export function freezeAction(action) {
|
|
61
|
-
return deepFreeze(action);
|
|
102
|
+
return deepFreeze(normalizeAction(action));
|
|
62
103
|
}
|
|
63
104
|
export function freezeModule(module) {
|
|
64
105
|
module.actions.forEach(freezeAction);
|
|
@@ -102,7 +143,14 @@ export function validateModule(module) {
|
|
|
102
143
|
}
|
|
103
144
|
}
|
|
104
145
|
export function validateAction(action) {
|
|
105
|
-
if (!action || typeof action.name !== 'string' || typeof action.path !== 'string'
|
|
146
|
+
if (!action || typeof action.name !== 'string' || typeof action.path !== 'string') {
|
|
147
|
+
throw new ZentaoError('E_INVALID_ACTION_DEFINITION');
|
|
148
|
+
}
|
|
149
|
+
// method / resultType 可省略(由 normalizeAction 按 type 推导),但显式给出时必须是字符串。
|
|
150
|
+
if (action.method !== undefined && typeof action.method !== 'string') {
|
|
151
|
+
throw new ZentaoError('E_INVALID_ACTION_DEFINITION');
|
|
152
|
+
}
|
|
153
|
+
if (action.resultType !== undefined && typeof action.resultType !== 'string') {
|
|
106
154
|
throw new ZentaoError('E_INVALID_ACTION_DEFINITION');
|
|
107
155
|
}
|
|
108
156
|
}
|
|
@@ -173,7 +173,6 @@ export declare const MODULES: readonly [{
|
|
|
173
173
|
readonly pathParams: {
|
|
174
174
|
readonly userID: "用户ID";
|
|
175
175
|
};
|
|
176
|
-
readonly render: "action";
|
|
177
176
|
}];
|
|
178
177
|
}, {
|
|
179
178
|
readonly name: "program";
|
|
@@ -354,7 +353,6 @@ export declare const MODULES: readonly [{
|
|
|
354
353
|
readonly pathParams: {
|
|
355
354
|
readonly programID: "项目集ID";
|
|
356
355
|
};
|
|
357
|
-
readonly render: "action";
|
|
358
356
|
}];
|
|
359
357
|
}, {
|
|
360
358
|
readonly name: "product";
|
|
@@ -579,7 +577,6 @@ export declare const MODULES: readonly [{
|
|
|
579
577
|
readonly pathParams: {
|
|
580
578
|
readonly productID: "产品ID";
|
|
581
579
|
};
|
|
582
|
-
readonly render: "action";
|
|
583
580
|
}];
|
|
584
581
|
}, {
|
|
585
582
|
readonly name: "project";
|
|
@@ -775,7 +772,6 @@ export declare const MODULES: readonly [{
|
|
|
775
772
|
readonly pathParams: {
|
|
776
773
|
readonly projectID: "项目ID";
|
|
777
774
|
};
|
|
778
|
-
readonly render: "action";
|
|
779
775
|
}];
|
|
780
776
|
}, {
|
|
781
777
|
readonly name: "execution";
|
|
@@ -1028,7 +1024,6 @@ export declare const MODULES: readonly [{
|
|
|
1028
1024
|
readonly pathParams: {
|
|
1029
1025
|
readonly executionID: "执行ID";
|
|
1030
1026
|
};
|
|
1031
|
-
readonly render: "action";
|
|
1032
1027
|
}];
|
|
1033
1028
|
}, {
|
|
1034
1029
|
readonly name: "productplan";
|
|
@@ -1231,7 +1226,6 @@ export declare const MODULES: readonly [{
|
|
|
1231
1226
|
readonly pathParams: {
|
|
1232
1227
|
readonly productplanID: "产品计划ID";
|
|
1233
1228
|
};
|
|
1234
|
-
readonly render: "action";
|
|
1235
1229
|
}];
|
|
1236
1230
|
}, {
|
|
1237
1231
|
readonly name: "story";
|
|
@@ -1479,7 +1473,6 @@ export declare const MODULES: readonly [{
|
|
|
1479
1473
|
readonly pathParams: {
|
|
1480
1474
|
readonly storyID: "需求ID";
|
|
1481
1475
|
};
|
|
1482
|
-
readonly render: "action";
|
|
1483
1476
|
}, {
|
|
1484
1477
|
readonly name: "activate";
|
|
1485
1478
|
readonly display: "激活需求";
|
|
@@ -1507,7 +1500,6 @@ export declare const MODULES: readonly [{
|
|
|
1507
1500
|
};
|
|
1508
1501
|
};
|
|
1509
1502
|
};
|
|
1510
|
-
readonly render: "action";
|
|
1511
1503
|
}, {
|
|
1512
1504
|
readonly name: "change";
|
|
1513
1505
|
readonly display: "变更需求";
|
|
@@ -1547,7 +1539,6 @@ export declare const MODULES: readonly [{
|
|
|
1547
1539
|
readonly required: readonly ["reviewer"];
|
|
1548
1540
|
};
|
|
1549
1541
|
};
|
|
1550
|
-
readonly render: "action";
|
|
1551
1542
|
}, {
|
|
1552
1543
|
readonly name: "close";
|
|
1553
1544
|
readonly display: "关闭需求";
|
|
@@ -1576,7 +1567,6 @@ export declare const MODULES: readonly [{
|
|
|
1576
1567
|
readonly required: readonly ["closedReason"];
|
|
1577
1568
|
};
|
|
1578
1569
|
};
|
|
1579
|
-
readonly render: "action";
|
|
1580
1570
|
}];
|
|
1581
1571
|
}, {
|
|
1582
1572
|
readonly name: "epic";
|
|
@@ -1809,7 +1799,6 @@ export declare const MODULES: readonly [{
|
|
|
1809
1799
|
readonly pathParams: {
|
|
1810
1800
|
readonly epicID: "业务需求ID";
|
|
1811
1801
|
};
|
|
1812
|
-
readonly render: "action";
|
|
1813
1802
|
}, {
|
|
1814
1803
|
readonly name: "activate";
|
|
1815
1804
|
readonly display: "激活业务需求";
|
|
@@ -1837,7 +1826,6 @@ export declare const MODULES: readonly [{
|
|
|
1837
1826
|
};
|
|
1838
1827
|
};
|
|
1839
1828
|
};
|
|
1840
|
-
readonly render: "action";
|
|
1841
1829
|
}, {
|
|
1842
1830
|
readonly name: "change";
|
|
1843
1831
|
readonly display: "变更业务需求";
|
|
@@ -1877,7 +1865,6 @@ export declare const MODULES: readonly [{
|
|
|
1877
1865
|
readonly required: readonly ["reviewer"];
|
|
1878
1866
|
};
|
|
1879
1867
|
};
|
|
1880
|
-
readonly render: "action";
|
|
1881
1868
|
}, {
|
|
1882
1869
|
readonly name: "close";
|
|
1883
1870
|
readonly display: "关闭业务需求";
|
|
@@ -1906,7 +1893,6 @@ export declare const MODULES: readonly [{
|
|
|
1906
1893
|
readonly required: readonly ["closedReason"];
|
|
1907
1894
|
};
|
|
1908
1895
|
};
|
|
1909
|
-
readonly render: "action";
|
|
1910
1896
|
}];
|
|
1911
1897
|
}, {
|
|
1912
1898
|
readonly name: "requirement";
|
|
@@ -2138,7 +2124,6 @@ export declare const MODULES: readonly [{
|
|
|
2138
2124
|
readonly pathParams: {
|
|
2139
2125
|
readonly requirementID: "用户需求ID";
|
|
2140
2126
|
};
|
|
2141
|
-
readonly render: "action";
|
|
2142
2127
|
}, {
|
|
2143
2128
|
readonly name: "activate";
|
|
2144
2129
|
readonly display: "激活用户需求";
|
|
@@ -2166,7 +2151,6 @@ export declare const MODULES: readonly [{
|
|
|
2166
2151
|
};
|
|
2167
2152
|
};
|
|
2168
2153
|
};
|
|
2169
|
-
readonly render: "action";
|
|
2170
2154
|
}, {
|
|
2171
2155
|
readonly name: "change";
|
|
2172
2156
|
readonly display: "变更用户需求";
|
|
@@ -2198,7 +2182,6 @@ export declare const MODULES: readonly [{
|
|
|
2198
2182
|
};
|
|
2199
2183
|
};
|
|
2200
2184
|
};
|
|
2201
|
-
readonly render: "action";
|
|
2202
2185
|
}, {
|
|
2203
2186
|
readonly name: "close";
|
|
2204
2187
|
readonly display: "关闭用户需求";
|
|
@@ -2227,7 +2210,6 @@ export declare const MODULES: readonly [{
|
|
|
2227
2210
|
readonly required: readonly ["closedReason"];
|
|
2228
2211
|
};
|
|
2229
2212
|
};
|
|
2230
|
-
readonly render: "action";
|
|
2231
2213
|
}];
|
|
2232
2214
|
}, {
|
|
2233
2215
|
readonly name: "bug";
|
|
@@ -2465,7 +2447,6 @@ export declare const MODULES: readonly [{
|
|
|
2465
2447
|
readonly pathParams: {
|
|
2466
2448
|
readonly bugID: "Bug ID";
|
|
2467
2449
|
};
|
|
2468
|
-
readonly render: "action";
|
|
2469
2450
|
}, {
|
|
2470
2451
|
readonly name: "activate";
|
|
2471
2452
|
readonly display: "激活Bug";
|
|
@@ -2500,7 +2481,6 @@ export declare const MODULES: readonly [{
|
|
|
2500
2481
|
};
|
|
2501
2482
|
};
|
|
2502
2483
|
};
|
|
2503
|
-
readonly render: "action";
|
|
2504
2484
|
}, {
|
|
2505
2485
|
readonly name: "close";
|
|
2506
2486
|
readonly display: "关闭Bug";
|
|
@@ -2524,7 +2504,6 @@ export declare const MODULES: readonly [{
|
|
|
2524
2504
|
};
|
|
2525
2505
|
};
|
|
2526
2506
|
};
|
|
2527
|
-
readonly render: "action";
|
|
2528
2507
|
}, {
|
|
2529
2508
|
readonly name: "resolve";
|
|
2530
2509
|
readonly display: "解决Bug";
|
|
@@ -2565,7 +2544,6 @@ export declare const MODULES: readonly [{
|
|
|
2565
2544
|
readonly required: readonly ["resolution"];
|
|
2566
2545
|
};
|
|
2567
2546
|
};
|
|
2568
|
-
readonly render: "action";
|
|
2569
2547
|
}];
|
|
2570
2548
|
}, {
|
|
2571
2549
|
readonly name: "testcase";
|
|
@@ -2816,7 +2794,6 @@ export declare const MODULES: readonly [{
|
|
|
2816
2794
|
readonly pathParams: {
|
|
2817
2795
|
readonly testcasID: "测试用例ID";
|
|
2818
2796
|
};
|
|
2819
|
-
readonly render: "action";
|
|
2820
2797
|
}];
|
|
2821
2798
|
}, {
|
|
2822
2799
|
readonly name: "task";
|
|
@@ -3050,7 +3027,6 @@ export declare const MODULES: readonly [{
|
|
|
3050
3027
|
readonly pathParams: {
|
|
3051
3028
|
readonly taskID: "任务ID";
|
|
3052
3029
|
};
|
|
3053
|
-
readonly render: "action";
|
|
3054
3030
|
}, {
|
|
3055
3031
|
readonly name: "activate";
|
|
3056
3032
|
readonly display: "激活任务";
|
|
@@ -3083,7 +3059,6 @@ export declare const MODULES: readonly [{
|
|
|
3083
3059
|
};
|
|
3084
3060
|
};
|
|
3085
3061
|
};
|
|
3086
|
-
readonly render: "action";
|
|
3087
3062
|
}, {
|
|
3088
3063
|
readonly name: "close";
|
|
3089
3064
|
readonly display: "关闭任务";
|
|
@@ -3107,7 +3082,6 @@ export declare const MODULES: readonly [{
|
|
|
3107
3082
|
};
|
|
3108
3083
|
};
|
|
3109
3084
|
};
|
|
3110
|
-
readonly render: "action";
|
|
3111
3085
|
}, {
|
|
3112
3086
|
readonly name: "finish";
|
|
3113
3087
|
readonly display: "完成任务";
|
|
@@ -3154,7 +3128,6 @@ export declare const MODULES: readonly [{
|
|
|
3154
3128
|
readonly required: readonly ["currentConsumed", "realStarted", "finishedDate"];
|
|
3155
3129
|
};
|
|
3156
3130
|
};
|
|
3157
|
-
readonly render: "action";
|
|
3158
3131
|
}, {
|
|
3159
3132
|
readonly name: "start";
|
|
3160
3133
|
readonly display: "启动任务";
|
|
@@ -3197,7 +3170,6 @@ export declare const MODULES: readonly [{
|
|
|
3197
3170
|
readonly required: readonly ["realStarted"];
|
|
3198
3171
|
};
|
|
3199
3172
|
};
|
|
3200
|
-
readonly render: "action";
|
|
3201
3173
|
}];
|
|
3202
3174
|
}, {
|
|
3203
3175
|
readonly name: "feedback";
|
|
@@ -3403,7 +3375,6 @@ export declare const MODULES: readonly [{
|
|
|
3403
3375
|
readonly pathParams: {
|
|
3404
3376
|
readonly feedbackID: "反馈ID";
|
|
3405
3377
|
};
|
|
3406
|
-
readonly render: "action";
|
|
3407
3378
|
}, {
|
|
3408
3379
|
readonly name: "activate";
|
|
3409
3380
|
readonly display: "激活反馈";
|
|
@@ -3431,7 +3402,6 @@ export declare const MODULES: readonly [{
|
|
|
3431
3402
|
};
|
|
3432
3403
|
};
|
|
3433
3404
|
};
|
|
3434
|
-
readonly render: "action";
|
|
3435
3405
|
}, {
|
|
3436
3406
|
readonly name: "close";
|
|
3437
3407
|
readonly display: "关闭反馈";
|
|
@@ -3460,7 +3430,6 @@ export declare const MODULES: readonly [{
|
|
|
3460
3430
|
readonly required: readonly ["closedReason"];
|
|
3461
3431
|
};
|
|
3462
3432
|
};
|
|
3463
|
-
readonly render: "action";
|
|
3464
3433
|
}];
|
|
3465
3434
|
}, {
|
|
3466
3435
|
readonly name: "ticket";
|
|
@@ -3682,7 +3651,6 @@ export declare const MODULES: readonly [{
|
|
|
3682
3651
|
readonly pathParams: {
|
|
3683
3652
|
readonly ticketID: "工单ID";
|
|
3684
3653
|
};
|
|
3685
|
-
readonly render: "action";
|
|
3686
3654
|
}, {
|
|
3687
3655
|
readonly name: "activate";
|
|
3688
3656
|
readonly display: "激活工单";
|
|
@@ -3710,7 +3678,6 @@ export declare const MODULES: readonly [{
|
|
|
3710
3678
|
};
|
|
3711
3679
|
};
|
|
3712
3680
|
};
|
|
3713
|
-
readonly render: "action";
|
|
3714
3681
|
}, {
|
|
3715
3682
|
readonly name: "close";
|
|
3716
3683
|
readonly display: "关闭工单";
|
|
@@ -3739,7 +3706,6 @@ export declare const MODULES: readonly [{
|
|
|
3739
3706
|
readonly required: readonly ["closedReason", "comment"];
|
|
3740
3707
|
};
|
|
3741
3708
|
};
|
|
3742
|
-
readonly render: "action";
|
|
3743
3709
|
}];
|
|
3744
3710
|
}, {
|
|
3745
3711
|
readonly name: "system";
|
|
@@ -3992,7 +3958,6 @@ export declare const MODULES: readonly [{
|
|
|
3992
3958
|
readonly pathParams: {
|
|
3993
3959
|
readonly buildID: "版本ID";
|
|
3994
3960
|
};
|
|
3995
|
-
readonly render: "action";
|
|
3996
3961
|
}];
|
|
3997
3962
|
}, {
|
|
3998
3963
|
readonly name: "testtask";
|
|
@@ -4157,7 +4122,6 @@ export declare const MODULES: readonly [{
|
|
|
4157
4122
|
readonly pathParams: {
|
|
4158
4123
|
readonly testtaskID: "测试单ID";
|
|
4159
4124
|
};
|
|
4160
|
-
readonly render: "action";
|
|
4161
4125
|
}];
|
|
4162
4126
|
}, {
|
|
4163
4127
|
readonly name: "release";
|
|
@@ -4290,7 +4254,6 @@ export declare const MODULES: readonly [{
|
|
|
4290
4254
|
readonly pathParams: {
|
|
4291
4255
|
readonly releasID: "发布ID";
|
|
4292
4256
|
};
|
|
4293
|
-
readonly render: "action";
|
|
4294
4257
|
}];
|
|
4295
4258
|
}, {
|
|
4296
4259
|
readonly name: "file";
|
|
@@ -4327,7 +4290,6 @@ export declare const MODULES: readonly [{
|
|
|
4327
4290
|
readonly pathParams: {
|
|
4328
4291
|
readonly fileID: "附件ID";
|
|
4329
4292
|
};
|
|
4330
|
-
readonly render: "action";
|
|
4331
4293
|
}];
|
|
4332
4294
|
}];
|
|
4333
4295
|
export { type DefineModulesOptions, defineModules, defineModuleActions, extendModuleAction, resetModuleDefinitions, } from './define.js';
|
|
@@ -2,6 +2,6 @@ import type { ListPagerInfo, ModuleAction, ModuleDefinition, ResolvedModuleComma
|
|
|
2
2
|
/** 将模块名、动作名和调用参数解析为实际 API 请求路径、查询参数和请求体。 */
|
|
3
3
|
export declare function resolveModuleCommand(module: ModuleDefinition, actionName: string, params?: Record<string, unknown>): ResolvedModuleCommand;
|
|
4
4
|
/** 根据动作定义中的 resultGetter,从原始响应里提取业务数据。 */
|
|
5
|
-
export declare function extractResult(action: ModuleAction, response: Record<string, unknown>): unknown;
|
|
5
|
+
export declare function extractResult(action: ModuleAction, response: Record<string, unknown>, params?: Record<string, unknown>): unknown;
|
|
6
6
|
/** 根据动作定义中的 pagerGetter,从原始响应里提取分页信息。 */
|
|
7
|
-
export declare function extractPager(action: ModuleAction, response: Record<string, unknown>): ListPagerInfo | undefined;
|
|
7
|
+
export declare function extractPager(action: ModuleAction, response: Record<string, unknown>, params?: Record<string, unknown>): ListPagerInfo | undefined;
|
package/dist/modules/resolve.js
CHANGED
|
@@ -173,32 +173,32 @@ export function resolveModuleCommand(module, actionName, params = {}) {
|
|
|
173
173
|
};
|
|
174
174
|
}
|
|
175
175
|
/** 根据动作定义中的 resultGetter,从原始响应里提取业务数据。 */
|
|
176
|
-
export function extractResult(action, response) {
|
|
176
|
+
export function extractResult(action, response, params = {}) {
|
|
177
177
|
const getter = action.resultGetter;
|
|
178
178
|
if (!getter)
|
|
179
179
|
return response.data ?? response;
|
|
180
180
|
if (typeof getter === 'function')
|
|
181
|
-
return getter(response,
|
|
181
|
+
return getter(response, params);
|
|
182
182
|
if (typeof getter === 'string')
|
|
183
183
|
return getNestedValue(response, getter);
|
|
184
184
|
const result = {};
|
|
185
185
|
for (const [targetKey, sourceKey] of Object.entries(getter)) {
|
|
186
|
-
result[targetKey] = response
|
|
186
|
+
result[targetKey] = getNestedValue(response, sourceKey);
|
|
187
187
|
}
|
|
188
188
|
return result;
|
|
189
189
|
}
|
|
190
190
|
/** 根据动作定义中的 pagerGetter,从原始响应里提取分页信息。 */
|
|
191
|
-
export function extractPager(action, response) {
|
|
191
|
+
export function extractPager(action, response, params = {}) {
|
|
192
192
|
const getter = action.pagerGetter;
|
|
193
193
|
if (!getter)
|
|
194
194
|
return response.pager;
|
|
195
195
|
if (typeof getter === 'function')
|
|
196
|
-
return getter(response,
|
|
196
|
+
return getter(response, params);
|
|
197
197
|
if (typeof getter === 'string')
|
|
198
198
|
return getNestedValue(response, getter);
|
|
199
|
-
const page = response
|
|
200
|
-
const recPerPage = response
|
|
201
|
-
const recTotal = response
|
|
199
|
+
const page = getNestedValue(response, getter.pageID);
|
|
200
|
+
const recPerPage = getNestedValue(response, getter.recPerPage);
|
|
201
|
+
const recTotal = getNestedValue(response, getter.recTotal);
|
|
202
202
|
if (page === undefined || recPerPage === undefined || recTotal === undefined)
|
|
203
203
|
return undefined;
|
|
204
204
|
return {
|
package/dist/request/index.d.ts
CHANGED
|
@@ -104,6 +104,9 @@ export type RequestResultFor<Name extends BuiltinRequestName> = ActionOfRequest<
|
|
|
104
104
|
* 当响应 `status` 为 `"fail"` 时,默认按原样返回;若 `options.throwOnFail`
|
|
105
105
|
* 或全局 `throwOnFail` 为真,则改为抛出 `E_API_FAILED`。
|
|
106
106
|
*
|
|
107
|
+
* 对 `update` 动作,当 `options.autoFill` 或全局 `autoFill` 为真时,会先 GET 当前对象,
|
|
108
|
+
* 用现值补齐用户未显式传入的 body 字段后再 PUT,避免禅道覆盖未提交字段。详见 {@link RequestOptions.autoFill}。
|
|
109
|
+
*
|
|
107
110
|
* @typeParam T 期望的 `data` 字段类型;不传时为 `unknown`,调用方需要自行收窄。
|
|
108
111
|
* @param name - 请求名,例如 `product`、`product/list` 或 `product/1`。
|
|
109
112
|
* @param params - 请求参数。
|
package/dist/request/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ZentaoError } from '../misc/errors.js';
|
|
2
2
|
import { getGlobalOptions } from '../misc/global-options.js';
|
|
3
|
-
import { getModule } from '../modules/registry.js';
|
|
3
|
+
import { getModule, getModuleAction } from '../modules/registry.js';
|
|
4
4
|
import { extractPager, extractResult, resolveModuleCommand } from '../modules/resolve.js';
|
|
5
5
|
import { isRecord, processData } from '../utils/index.js';
|
|
6
6
|
/** 将 `moduleName`、`moduleName/methodName` 或 `moduleName/<objectID>` 请求名拆成模块名、动作名和对象 ID。 */
|
|
@@ -30,6 +30,54 @@ function splitRequestName(name) {
|
|
|
30
30
|
actionName,
|
|
31
31
|
};
|
|
32
32
|
}
|
|
33
|
+
/** 解析 `params.data` 中用户显式传入的 body 字段名,用于 autoFill 判断字段归属。 */
|
|
34
|
+
function getExplicitDataKeys(data) {
|
|
35
|
+
let value = data;
|
|
36
|
+
if (typeof value === 'string') {
|
|
37
|
+
try {
|
|
38
|
+
value = JSON.parse(value);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return new Set();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
45
|
+
return new Set(Object.keys(value));
|
|
46
|
+
}
|
|
47
|
+
return new Set();
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* 在执行 `update` 动作前,用当前对象的现值填充用户未显式传入的 body 字段。
|
|
51
|
+
*
|
|
52
|
+
* 仅当模块存在 `type: 'get'` 动作且 update 动作声明了对象类型 body schema 时生效;
|
|
53
|
+
* 否则原样返回参数。GET 失败或返回非对象时同样跳过填充,交由后续 PUT 正常处理。
|
|
54
|
+
*
|
|
55
|
+
* 字段归属判断同时覆盖平铺 `params` 字段与 `params.data` 中的字段;只有 schema 中声明、
|
|
56
|
+
* 用户未传且当前对象存在的字段才会被补齐,避免覆盖用户本次想修改的字段。
|
|
57
|
+
*/
|
|
58
|
+
async function autoFillUpdateParams(module, action, params, options) {
|
|
59
|
+
const properties = action.requestBody?.schema?.properties;
|
|
60
|
+
const getAction = module.actions.find((candidate) => candidate.type === 'get');
|
|
61
|
+
if (!properties || !getAction)
|
|
62
|
+
return params;
|
|
63
|
+
const current = (await request(`${module.name}/${getAction.name}`, params, {
|
|
64
|
+
client: options.client,
|
|
65
|
+
timeout: options.timeout,
|
|
66
|
+
insecure: options.insecure,
|
|
67
|
+
throwOnFail: options.throwOnFail,
|
|
68
|
+
})).data;
|
|
69
|
+
if (!isRecord(current))
|
|
70
|
+
return params;
|
|
71
|
+
const explicitDataKeys = getExplicitDataKeys(params.data);
|
|
72
|
+
const filled = { ...params };
|
|
73
|
+
for (const key of Object.keys(properties)) {
|
|
74
|
+
const userProvided = Object.prototype.hasOwnProperty.call(params, key) || explicitDataKeys.has(key);
|
|
75
|
+
if (!userProvided && Object.prototype.hasOwnProperty.call(current, key)) {
|
|
76
|
+
filled[key] = current[key];
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return filled;
|
|
80
|
+
}
|
|
33
81
|
function stringifyMessage(value) {
|
|
34
82
|
if (typeof value === 'string')
|
|
35
83
|
return value;
|
|
@@ -96,9 +144,9 @@ function normalizeResponse(command, raw, options) {
|
|
|
96
144
|
}
|
|
97
145
|
const record = raw;
|
|
98
146
|
const status = record.status === 'fail' ? 'fail' : 'success';
|
|
99
|
-
const data = applyProcessing(extractResult(command.action, record), options);
|
|
147
|
+
const data = applyProcessing(extractResult(command.action, record, command.params), options);
|
|
100
148
|
const rawMessage = record.message;
|
|
101
|
-
const pager = extractPager(command.action, record);
|
|
149
|
+
const pager = extractPager(command.action, record, command.params);
|
|
102
150
|
const response = {
|
|
103
151
|
status,
|
|
104
152
|
message: stringifyMessage(rawMessage),
|
|
@@ -134,7 +182,13 @@ export async function request(name, params = {}, options = {}) {
|
|
|
134
182
|
...(id !== undefined ? { id } : {}),
|
|
135
183
|
...(recPerPage !== undefined ? { recPerPage } : {}),
|
|
136
184
|
};
|
|
137
|
-
|
|
185
|
+
// autoFill:update 动作先 GET 当前对象,用现值补齐用户未显式传入的字段,
|
|
186
|
+
// 避免禅道 PUT 把未提交字段覆盖为空。
|
|
187
|
+
const action = getModuleAction(moduleName, actionName);
|
|
188
|
+
const finalParams = action.type === 'update' && (options.autoFill ?? globals.autoFill)
|
|
189
|
+
? await autoFillUpdateParams(module, action, mergedParams, options)
|
|
190
|
+
: mergedParams;
|
|
191
|
+
const command = resolveModuleCommand(module, actionName, finalParams);
|
|
138
192
|
const raw = await client.request(command.path, {
|
|
139
193
|
method: String(command.action.method).toUpperCase(),
|
|
140
194
|
query: command.query,
|
package/dist/types/module.d.ts
CHANGED
|
@@ -50,31 +50,42 @@ export interface ModuleActionResponse {
|
|
|
50
50
|
/** 响应示例。 */
|
|
51
51
|
example?: unknown;
|
|
52
52
|
}
|
|
53
|
-
/**
|
|
54
|
-
export type ModuleActionResultRenderType = 'markdown' | 'json' | 'raw';
|
|
55
|
-
/** 模块动作自定义渲染函数类型;SDK 本身不直接渲染终端输出。 */
|
|
56
|
-
export type ModuleActionResultRender = (result: unknown, type: ModuleActionResultRenderType, action: ModuleAction) => string;
|
|
57
|
-
/** 从原始响应中提取分页字段时使用的字段映射。 */
|
|
53
|
+
/** 从原始响应中提取分页字段时使用的字段映射,值为原始响应中的字段路径(支持 `a.b` 嵌套)。 */
|
|
58
54
|
export interface ModuleActionPagerGetterMap {
|
|
59
|
-
/**
|
|
55
|
+
/** 当前页码字段路径。 */
|
|
60
56
|
pageID: string;
|
|
61
|
-
/**
|
|
57
|
+
/** 每页记录数字段路径。 */
|
|
62
58
|
recPerPage: string;
|
|
63
|
-
/**
|
|
59
|
+
/** 总记录数字段路径。 */
|
|
64
60
|
recTotal: string;
|
|
65
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* 从原始响应中重映射业务数据字段的映射表。
|
|
64
|
+
* 键为输出字段名,值为原始响应中的字段路径(支持 `a.b` 嵌套)。
|
|
65
|
+
*/
|
|
66
|
+
export type ModuleActionResultFieldMap = Readonly<Record<string, string>>;
|
|
67
|
+
/**
|
|
68
|
+
* 从原始响应中提取数据时使用的函数形态。
|
|
69
|
+
* @param data 原始响应对象。
|
|
70
|
+
* @param params 触发本次请求的原始调用参数。
|
|
71
|
+
*/
|
|
72
|
+
export type ModuleActionGetterFn<T> = (data: unknown, params: Record<string, unknown>) => T;
|
|
66
73
|
/** 禅道模块中的单个 API 动作定义。 */
|
|
67
74
|
export interface ModuleAction {
|
|
68
75
|
/** 动作名称,例如 `list`、`get`、`close`。 */
|
|
69
76
|
name: ModuleActionName;
|
|
70
|
-
/** 动作类型,决定高阶 request
|
|
77
|
+
/** 动作类型,决定高阶 request 的路径/参数解析策略,并在 `method`、`resultType` 省略时作为推导依据。 */
|
|
71
78
|
type: ModuleActionType;
|
|
72
79
|
/** 面向用户展示的动作名称。 */
|
|
73
80
|
display?: string;
|
|
74
81
|
/** 动作说明。 */
|
|
75
82
|
description?: string;
|
|
76
|
-
/**
|
|
77
|
-
|
|
83
|
+
/**
|
|
84
|
+
* HTTP 方法;省略时按 {@link type} 自动推导:
|
|
85
|
+
* `list`/`get` → `GET`、`create`/`action` → `POST`、`update` → `PUT`、`delete` → `DELETE`。
|
|
86
|
+
* 当 `type` 无法推导出方法时抛出 `E_INDETERMINATE_ACTION_METHOD`。
|
|
87
|
+
*/
|
|
88
|
+
method?: ModuleActionMethod;
|
|
78
89
|
/** API 路径模板,可包含 `{productID}` 等路径参数。 */
|
|
79
90
|
path: string;
|
|
80
91
|
/** 路径参数定义;字符串为说明,对象可携带默认值和可选项。 */
|
|
@@ -83,14 +94,22 @@ export interface ModuleAction {
|
|
|
83
94
|
params?: readonly ModuleActionParam[];
|
|
84
95
|
/** 请求体定义。 */
|
|
85
96
|
requestBody?: ModuleActionRequestBody;
|
|
86
|
-
/**
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
|
|
97
|
+
/**
|
|
98
|
+
* 结果形态;省略时按 {@link type} 自动推导:
|
|
99
|
+
* `list` → `list`、`get`/`create`/`update` → `object`、`delete`/`action` → `text`。
|
|
100
|
+
* 当 `type` 无法推导出结果形态时抛出 `E_INDETERMINATE_ACTION_RESULT_TYPE`。
|
|
101
|
+
*/
|
|
102
|
+
resultType?: ModuleActionResultType;
|
|
103
|
+
/**
|
|
104
|
+
* 从原始响应中提取分页信息的位置或函数:
|
|
105
|
+
* 字符串为字段路径(支持 `a.b` 嵌套)、对象为字段映射、函数则接收原始响应与调用参数。
|
|
106
|
+
*/
|
|
107
|
+
pagerGetter?: string | ModuleActionPagerGetterMap | ModuleActionGetterFn<ListPagerInfo>;
|
|
108
|
+
/**
|
|
109
|
+
* 从原始响应中提取业务数据的位置或函数:
|
|
110
|
+
* 字符串为字段路径(支持 `a.b` 嵌套)、对象为字段映射、函数则接收原始响应与调用参数。
|
|
111
|
+
*/
|
|
112
|
+
resultGetter?: string | ModuleActionResultFieldMap | ModuleActionGetterFn<unknown>;
|
|
94
113
|
}
|
|
95
114
|
/** 内置模块名称,同时允许用户扩展自定义模块名。 */
|
|
96
115
|
export type ModuleName = 'user' | 'program' | 'product' | 'project' | 'execution' | 'productplan' | 'story' | 'epic' | 'requirement' | 'bug' | 'testcase' | 'task' | 'feedback' | 'ticket' | 'system' | 'build' | 'testtask' | 'release' | 'file' | (string & {});
|
package/dist/types/options.d.ts
CHANGED
|
@@ -16,6 +16,12 @@ export interface GlobalOptions {
|
|
|
16
16
|
persistProfiles?: boolean;
|
|
17
17
|
/** 当禅道服务端返回 `{ status: "fail" }` 时是否抛出 `E_API_FAILED`,默认 false。 */
|
|
18
18
|
throwOnFail?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* 是否在执行 `update` 操作时自动填充未传入的字段,默认 false。
|
|
21
|
+
*
|
|
22
|
+
* 优先级低于单次请求选项;语义见 {@link RequestOptions.autoFill}。
|
|
23
|
+
*/
|
|
24
|
+
autoFill?: boolean;
|
|
19
25
|
}
|
|
20
26
|
/** 高阶 `request("moduleName")` / `request("moduleName/methodName")` / `request("moduleName/<objectID>")` 的单次调用选项。 */
|
|
21
27
|
export interface RequestOptions extends ProcessListOptions {
|
|
@@ -32,4 +38,14 @@ export interface RequestOptions extends ProcessListOptions {
|
|
|
32
38
|
* 不传时回落到全局 `throwOnFail`,默认 false(保留原始失败响应)。
|
|
33
39
|
*/
|
|
34
40
|
throwOnFail?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* 是否在执行 `update` 操作时自动填充未传入的字段。
|
|
43
|
+
*
|
|
44
|
+
* 设为 `true` 后,会先 GET 当前对象,把用户未显式传入(含 `params.data`)且
|
|
45
|
+
* 动作 body schema 中声明的字段用现值补齐,再发起 PUT,避免禅道用空值覆盖未提交字段。
|
|
46
|
+
* 因此只需传想修改的字段即可。仅对 `type: 'update'` 且模块存在 `type: 'get'` 动作时生效。
|
|
47
|
+
*
|
|
48
|
+
* 不传时回落到全局 `autoFill`,默认 false。
|
|
49
|
+
*/
|
|
50
|
+
autoFill?: boolean;
|
|
35
51
|
}
|
package/dist/version.js
CHANGED