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.
@@ -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: "/{scope}/{scopeID}/productplans";
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 scope: {
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: "范围ID";
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: "/{scope}/{scopeID}/epics";
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 scope: {
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: "/{scope}/{scopeID}/requirements";
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 scope: {
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: "范围ID";
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: "范围ID";
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: "/{scope}/{scopeID}/tasks";
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 scope: {
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: "/{scope}/{scopeID}/feedbacks";
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 scope: {
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: "/{scope}/{scopeID}/tickets";
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 scope: {
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: "/{scope}/{scopeID}/systems";
3677
+ readonly path: "/products/{productID}/systems";
3720
3678
  readonly resultType: "list";
3721
3679
  readonly pagerGetter: "pager";
3722
3680
  readonly pathParams: {
3723
- readonly scope: {
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: "范围ID";
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: "范围ID";
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: "/{scope}/{scopeID}/releases";
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 scope: {
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';
@@ -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, ModuleDefinition, ResolvedModuleCommand } from '../types/index.js';
1
+ import type { ListPagerInfo, ModuleAction, ModuleActionRequest, ModuleDefinition } from '../types/index.js';
2
2
  /** 将模块名、动作名和调用参数解析为实际 API 请求路径、查询参数和请求体。 */
3
- export declare function resolveModuleCommand(module: ModuleDefinition, actionName: string, params?: Record<string, unknown>): ResolvedModuleCommand;
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,从原始响应里提取分页信息。 */
@@ -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 === undefined || value === null || 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 === undefined || 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 parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : undefined;
40
+ return isRecord(parsed) ? parsed : undefined;
41
41
  }
42
42
  catch {
43
43
  return undefined;
44
44
  }
45
45
  }
46
- return value && typeof value === 'object' && !Array.isArray(value) ? value : undefined;
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
- /** 按 OpenAPI schema 的基础类型对参数做轻量转换。 */
51
- function coerceValue(value, type, paramName) {
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
- /** 将模块名、动作名和调用参数解析为实际 API 请求路径、查询参数和请求体。 */
81
- export function resolveModuleCommand(module, actionName, params = {}) {
82
- const action = getModuleAction(module.name, actionName);
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
- pathValues.scope = scope.scope;
91
- pathValues.scopeID = scope.scopeID;
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 id = idValue === undefined ? undefined : Number(idValue);
96
- if (idParamName && id !== undefined && !Number.isNaN(id)) {
97
- pathValues[idParamName] = id;
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' || pathValues[key] !== undefined)
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
- pathValues[key] = value;
120
+ values[key] = value;
107
121
  continue;
108
122
  }
109
123
  if (typeof definition === 'object') {
110
124
  if (definition.defaultValue !== undefined) {
111
- pathValues[key] = definition.defaultValue;
125
+ values[key] = definition.defaultValue;
112
126
  }
113
127
  else if (definition.options?.[0]?.value !== undefined) {
114
- pathValues[key] = definition.options[0].value;
128
+ values[key] = definition.options[0].value;
115
129
  }
116
130
  }
117
- if (pathValues[key] === undefined) {
131
+ if (values[key] === undefined) {
118
132
  throw new ZentaoError('E_MISSING_PARAM', { param: key });
119
133
  }
120
134
  }
121
- // 查询参数只从 action.params 中声明过的字段生成,避免把 body 字段误放到 URL 上。
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
- let data = parseData(params.data);
139
- if (action.requestBody?.schema?.type === 'object') {
140
- data = data ? { ...data } : {};
141
- const schema = action.requestBody.schema;
142
- const required = new Set(schema.required ?? []);
143
- for (const [key, property] of Object.entries(schema.properties ?? {})) {
144
- // body 字段优先级:params.data 中的字段 > 平铺 params 字段 > schema 默认值。
145
- const hasDataValue = Object.prototype.hasOwnProperty.call(data, key);
146
- const hasParamValue = Object.prototype.hasOwnProperty.call(params, key);
147
- let value = hasDataValue ? data[key] : hasParamValue ? params[key] : property.defaultValue;
148
- if (value === undefined && (property.required || required.has(key))) {
149
- throw new ZentaoError('E_MISSING_PARAM', { param: key });
150
- }
151
- value = coerceValue(value, property.type, key);
152
- if (property.type === 'array' && value !== undefined && value !== null && !Array.isArray(value)) {
153
- if (typeof value === 'string') {
154
- value = value.split(',');
155
- }
156
- else if (!hasDataValue || !isRecord(value)) {
157
- value = [value];
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, pathValues),
170
- query,
171
- data,
172
- id: id === undefined || Number.isNaN(id) ? undefined : 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,从原始响应里提取业务数据。 */
@@ -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, resolveModuleCommand } from '../modules/resolve.js';
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 失败或返回非对象时同样跳过填充,交由后续 PUT 正常处理。
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: options.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 = resolveModuleCommand(module, actionName, finalParams);
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);
@@ -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 ResolvedModuleCommand {
132
+ export interface ModuleActionRequest {
129
133
  /** 模块名称。 */
130
134
  module: string;
131
135
  /** 匹配到的动作定义。 */
@@ -48,4 +48,6 @@ export interface RequestOptions extends ProcessListOptions {
48
48
  * 不传时回落到全局 `autoFill`,默认 false。
49
49
  */
50
50
  autoFill?: boolean;
51
+ /** 是否返回原始响应体,默认 false。 */
52
+ raw?: boolean;
51
53
  }
@@ -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';
@@ -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';
@@ -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;
@@ -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
@@ -1,5 +1,5 @@
1
- const fallbackBuild = "2026-06-30T10:00:59.615Z";
2
- const fallbackVersion = "0.3.3";
1
+ const fallbackBuild = "2026-07-21T02:40:34.356Z";
2
+ const fallbackVersion = "0.4.0";
3
3
  /**
4
4
  * 构建标识,由构建脚本通过 `__ZENTAO_API_BUILD__` 注入。
5
5
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zentao-api",
3
- "version": "0.3.3",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "description": "Browser and Node.js SDK for ZenTao API",
6
6
  "license": "MIT",