zentao-api 0.3.0 → 0.3.1
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/client/index.d.ts +14 -5
- package/dist/client/index.js +101 -36
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/misc/environment.js +25 -3
- package/dist/misc/errors.d.ts +1 -1
- package/dist/misc/errors.js +1 -1
- package/dist/modules/generated.d.ts +4329 -2
- package/dist/modules/registry.d.ts +4329 -1
- package/dist/modules/registry.js +5 -2
- package/dist/request/index.d.ts +104 -4
- package/dist/request/index.js +41 -21
- package/dist/types/index.d.ts +28 -10
- package/dist/version.js +2 -2
- package/package.json +3 -2
package/dist/client/index.js
CHANGED
|
@@ -15,8 +15,92 @@ function buildUrl(baseUrl, path, query) {
|
|
|
15
15
|
}
|
|
16
16
|
return url.toString();
|
|
17
17
|
}
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
function isPlainObject(value) {
|
|
19
|
+
if (!value || typeof value !== 'object')
|
|
20
|
+
return false;
|
|
21
|
+
const prototype = Object.getPrototypeOf(value);
|
|
22
|
+
return prototype === Object.prototype || prototype === null;
|
|
23
|
+
}
|
|
24
|
+
function isInstanceOfGlobal(value, name) {
|
|
25
|
+
const ctor = globalThis[name];
|
|
26
|
+
return typeof ctor === 'function' && value instanceof ctor;
|
|
27
|
+
}
|
|
28
|
+
function isArrayBufferBody(value) {
|
|
29
|
+
return value instanceof ArrayBuffer || ArrayBuffer.isView(value);
|
|
30
|
+
}
|
|
31
|
+
function appendFormValue(form, key, value) {
|
|
32
|
+
if (value === undefined)
|
|
33
|
+
return;
|
|
34
|
+
if (Array.isArray(value)) {
|
|
35
|
+
for (const item of value)
|
|
36
|
+
appendFormValue(form, key, item);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
form.append(key, value === null ? '' : String(value));
|
|
40
|
+
}
|
|
41
|
+
function serializeBody(body, bodyType, headers) {
|
|
42
|
+
if (body === undefined || body === null)
|
|
43
|
+
return undefined;
|
|
44
|
+
if (bodyType === 'form') {
|
|
45
|
+
const form = body instanceof URLSearchParams ? body : new URLSearchParams();
|
|
46
|
+
if (!(body instanceof URLSearchParams) && isPlainObject(body)) {
|
|
47
|
+
for (const [key, value] of Object.entries(body))
|
|
48
|
+
appendFormValue(form, key, value);
|
|
49
|
+
}
|
|
50
|
+
else if (!(body instanceof URLSearchParams)) {
|
|
51
|
+
throw new ZentaoError('E_INVALID_PARAM', { param: 'body', value: String(body) });
|
|
52
|
+
}
|
|
53
|
+
if (!headers.has('Content-Type')) {
|
|
54
|
+
headers.set('Content-Type', 'application/x-www-form-urlencoded;charset=UTF-8');
|
|
55
|
+
}
|
|
56
|
+
return form;
|
|
57
|
+
}
|
|
58
|
+
if (bodyType === 'raw' ||
|
|
59
|
+
typeof body === 'string' ||
|
|
60
|
+
isArrayBufferBody(body) ||
|
|
61
|
+
isInstanceOfGlobal(body, 'FormData') ||
|
|
62
|
+
isInstanceOfGlobal(body, 'URLSearchParams') ||
|
|
63
|
+
isInstanceOfGlobal(body, 'Blob') ||
|
|
64
|
+
isInstanceOfGlobal(body, 'ReadableStream')) {
|
|
65
|
+
return body;
|
|
66
|
+
}
|
|
67
|
+
if (!headers.has('Content-Type')) {
|
|
68
|
+
headers.set('Content-Type', 'application/json');
|
|
69
|
+
}
|
|
70
|
+
return JSON.stringify(body);
|
|
71
|
+
}
|
|
72
|
+
function createRequestSignal(timeout, externalSignal) {
|
|
73
|
+
const controller = new AbortController();
|
|
74
|
+
const abortFromExternal = () => controller.abort(externalSignal?.reason);
|
|
75
|
+
const timer = setTimeout(() => controller.abort(), timeout);
|
|
76
|
+
if (externalSignal?.aborted) {
|
|
77
|
+
abortFromExternal();
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
externalSignal?.addEventListener('abort', abortFromExternal, { once: true });
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
signal: controller.signal,
|
|
84
|
+
cleanup: () => {
|
|
85
|
+
clearTimeout(timer);
|
|
86
|
+
externalSignal?.removeEventListener('abort', abortFromExternal);
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/** 按指定策略解析响应;默认优先 JSON,失败后保留原始文本。 */
|
|
91
|
+
async function parseResponse(response, responseType = 'auto') {
|
|
92
|
+
if (responseType === 'response')
|
|
93
|
+
return response;
|
|
94
|
+
if (responseType === 'arrayBuffer')
|
|
95
|
+
return response.arrayBuffer();
|
|
96
|
+
if (responseType === 'blob')
|
|
97
|
+
return response.blob();
|
|
98
|
+
if (responseType === 'text')
|
|
99
|
+
return response.text();
|
|
100
|
+
if (responseType === 'json') {
|
|
101
|
+
const text = await response.text();
|
|
102
|
+
return text === '' ? undefined : JSON.parse(text);
|
|
103
|
+
}
|
|
20
104
|
const text = await response.text();
|
|
21
105
|
if (text === '')
|
|
22
106
|
return undefined;
|
|
@@ -56,23 +140,6 @@ export class ZentaoClient {
|
|
|
56
140
|
this.timeout = options.timeout;
|
|
57
141
|
this.insecure = options.insecure;
|
|
58
142
|
}
|
|
59
|
-
/**
|
|
60
|
-
* 发起一次原始 API 请求。
|
|
61
|
-
*
|
|
62
|
-
* 选项优先级:本次调用 `options` > 全局选项({@link getGlobalOptions}) > 客户端构造时默认值。
|
|
63
|
-
*
|
|
64
|
-
* 特殊处理:
|
|
65
|
-
* - 默认 HTTP 方法为 `GET`,`GET` 请求即使提供了 `options.body` 也不会发送,避免被部分代理/浏览器拒绝。
|
|
66
|
-
* - 非空响应优先按 JSON 解析;解析失败时回落为字符串原文。
|
|
67
|
-
* - 业务层失败(即响应体 `{ status: "fail" }`)不会抛出,仍按原样返回;只有 HTTP/网络/超时等传输层错误才会抛错。
|
|
68
|
-
* - `insecure` 仅在 Node.js 下可用,浏览器中传入会抛 `E_INSECURE_BROWSER`。
|
|
69
|
-
*
|
|
70
|
-
* @param path - 相对 {@link baseUrl} 的路径,可省略前导 `/`。
|
|
71
|
-
* @param options - 单次请求选项,参见 {@link ClientRequestOptions}。
|
|
72
|
-
* @returns 解析后的响应体;当响应为空字符串时返回 `undefined`。
|
|
73
|
-
* @throws {ZentaoError} 可能抛出 `E_HTTP_ERROR`(非 2xx 状态)、`E_NETWORK_ERROR`(底层 fetch 失败)、
|
|
74
|
-
* `E_TIMEOUT`(超过 `timeout`)或 `E_INSECURE_BROWSER`(浏览器中开启了 `insecure`)。
|
|
75
|
-
*/
|
|
76
143
|
async request(path, options = {}) {
|
|
77
144
|
const globals = getGlobalOptions();
|
|
78
145
|
const method = options.method ?? 'GET';
|
|
@@ -80,21 +147,19 @@ export class ZentaoClient {
|
|
|
80
147
|
const insecure = options.insecure ?? globals.insecure ?? this.insecure;
|
|
81
148
|
assertInsecureSupported(insecure);
|
|
82
149
|
const url = buildUrl(this.baseUrl, path, options.query);
|
|
83
|
-
const
|
|
84
|
-
const timer = setTimeout(() => controller.abort(), timeout);
|
|
85
|
-
const headers = {};
|
|
150
|
+
const headers = new Headers(options.headers);
|
|
86
151
|
if (this.token) {
|
|
87
|
-
headers.Token
|
|
152
|
+
headers.set('Token', this.token);
|
|
88
153
|
}
|
|
154
|
+
const { signal, cleanup } = createRequestSignal(timeout, options.signal);
|
|
89
155
|
const init = {
|
|
90
156
|
method,
|
|
91
157
|
headers,
|
|
92
|
-
signal
|
|
158
|
+
signal,
|
|
93
159
|
};
|
|
94
160
|
// GET 请求不携带 body,避免浏览器和部分代理拒绝请求。
|
|
95
161
|
if (options.body !== undefined && method !== 'GET') {
|
|
96
|
-
|
|
97
|
-
init.body = JSON.stringify(options.body);
|
|
162
|
+
init.body = serializeBody(options.body, options.bodyType, headers);
|
|
98
163
|
}
|
|
99
164
|
try {
|
|
100
165
|
const response = await fetchWithInsecureTls(insecure, url, init);
|
|
@@ -109,7 +174,7 @@ export class ZentaoClient {
|
|
|
109
174
|
body: await response.text().catch(() => undefined),
|
|
110
175
|
});
|
|
111
176
|
}
|
|
112
|
-
return parseResponse(response);
|
|
177
|
+
return parseResponse(response, options.responseType);
|
|
113
178
|
}
|
|
114
179
|
catch (error) {
|
|
115
180
|
if (error instanceof ZentaoError)
|
|
@@ -120,7 +185,7 @@ export class ZentaoClient {
|
|
|
120
185
|
throw new ZentaoError('E_NETWORK_ERROR', { message: error.message ?? String(error) }, error);
|
|
121
186
|
}
|
|
122
187
|
finally {
|
|
123
|
-
|
|
188
|
+
cleanup();
|
|
124
189
|
}
|
|
125
190
|
}
|
|
126
191
|
/**
|
|
@@ -131,8 +196,8 @@ export class ZentaoClient {
|
|
|
131
196
|
* @returns 解析后的响应体(强转为 `T`)。
|
|
132
197
|
* @throws {ZentaoError} 传输层失败时抛出,详见 {@link ZentaoClient.request}。
|
|
133
198
|
*/
|
|
134
|
-
async get(path) {
|
|
135
|
-
return this.request(path, { method: 'GET' });
|
|
199
|
+
async get(path, options = {}) {
|
|
200
|
+
return this.request(path, { ...options, method: 'GET' });
|
|
136
201
|
}
|
|
137
202
|
/**
|
|
138
203
|
* 发起 `POST` 请求,`body` 会被序列化为 JSON。
|
|
@@ -143,8 +208,8 @@ export class ZentaoClient {
|
|
|
143
208
|
* @returns 解析后的响应体(强转为 `T`)。
|
|
144
209
|
* @throws {ZentaoError} 传输层失败时抛出,详见 {@link ZentaoClient.request}。
|
|
145
210
|
*/
|
|
146
|
-
async post(path, body) {
|
|
147
|
-
return this.request(path, { method: 'POST', body });
|
|
211
|
+
async post(path, body, options = {}) {
|
|
212
|
+
return this.request(path, { ...options, method: 'POST', body });
|
|
148
213
|
}
|
|
149
214
|
/**
|
|
150
215
|
* 发起 `PUT` 请求,`body` 会被序列化为 JSON。
|
|
@@ -155,8 +220,8 @@ export class ZentaoClient {
|
|
|
155
220
|
* @returns 解析后的响应体(强转为 `T`)。
|
|
156
221
|
* @throws {ZentaoError} 传输层失败时抛出,详见 {@link ZentaoClient.request}。
|
|
157
222
|
*/
|
|
158
|
-
async put(path, body) {
|
|
159
|
-
return this.request(path, { method: 'PUT', body });
|
|
223
|
+
async put(path, body, options = {}) {
|
|
224
|
+
return this.request(path, { ...options, method: 'PUT', body });
|
|
160
225
|
}
|
|
161
226
|
/**
|
|
162
227
|
* 发起 `DELETE` 请求。
|
|
@@ -166,8 +231,8 @@ export class ZentaoClient {
|
|
|
166
231
|
* @returns 解析后的响应体(强转为 `T`)。
|
|
167
232
|
* @throws {ZentaoError} 传输层失败时抛出,详见 {@link ZentaoClient.request}。
|
|
168
233
|
*/
|
|
169
|
-
async delete(path) {
|
|
170
|
-
return this.request(path, { method: 'DELETE' });
|
|
234
|
+
async delete(path, options = {}) {
|
|
235
|
+
return this.request(path, { ...options, method: 'DELETE' });
|
|
171
236
|
}
|
|
172
237
|
/**
|
|
173
238
|
* 使用账号密码登录禅道。
|
package/dist/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ export { ERRORS, ZentaoError, type ErrorCode } from './misc/errors.js';
|
|
|
3
3
|
export { getGlobalOptions, setGlobalOptions } from './misc/global-options.js';
|
|
4
4
|
export { ZENTAO_PROFILES_STORAGE_KEY, addProfile, deleteProfile, getAllProfiles, getProfile, getProfileKey, switchProfile, } from './profiles/index.js';
|
|
5
5
|
export { defineModuleActions, defineModules, type DefineModulesOptions, getModuleNames, getModule, getModuleAction, } from './modules/registry.js';
|
|
6
|
-
export { request } from './request/index.js';
|
|
6
|
+
export { request, type BuiltinRequestName, type RequestParamsFor, type RequestResultFor, } from './request/index.js';
|
|
7
7
|
export { pickFields, pickFieldsSingle, filterData, searchData, sortData, processData, } from './utils/index.js';
|
|
8
8
|
export { BUILD, VERSION } from './version.js';
|
|
9
|
-
export type { ApiListResponse, ApiResponse, ClientRequestOptions, DataRecord, DataRecordFilter, DataRecordFilterGroup, GlobalOptions, HttpMethod, ListPagerInfo, LoginResponse, ModuleAction, ModuleActionMethod, ModuleActionName, ModuleActionPagerGetterMap, ModuleActionParam, ModuleActionParamOption, ModuleActionRequestBody, ModuleActionResponse, ModuleActionResultRender, ModuleActionResultRenderType, ModuleActionResultType, ModuleActionType, ModuleDefinition, ModuleName, Pager, ProcessListOptions, ProcessSingleOptions, RequestOptions, ResolvedModuleCommand, ResponseData, ServerConfig, SortExpr, SortFn, ZentaoProfile, ZentaoProfileConfig, ZentaoProfileRecord, ZentaoProfilesStore, ZentaoClientOptions, } from './types/index.js';
|
|
9
|
+
export type { ApiListResponse, ApiResponse, ClientRequestBodyType, ClientRequestOptions, ClientResponseType, DataRecord, DataRecordFilter, DataRecordFilterGroup, GlobalOptions, HttpMethod, ListPagerInfo, LoginResponse, ModuleAction, ModuleActionMethod, ModuleActionName, ModuleActionPagerGetterMap, ModuleActionParam, ModuleActionParamOption, ModuleActionRequestBody, ModuleActionResponse, ModuleActionResultRender, ModuleActionResultRenderType, ModuleActionResultType, ModuleActionType, ModuleDefinition, ModuleName, Pager, ProcessListOptions, ProcessSingleOptions, RequestOptions, ResolvedModuleCommand, ResponseData, ServerConfig, SortExpr, SortFn, ZentaoProfile, ZentaoProfileConfig, ZentaoProfileRecord, ZentaoProfilesStore, ZentaoClientOptions, } from './types/index.js';
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,6 @@ export { ERRORS, ZentaoError } from './misc/errors.js';
|
|
|
3
3
|
export { getGlobalOptions, setGlobalOptions } from './misc/global-options.js';
|
|
4
4
|
export { ZENTAO_PROFILES_STORAGE_KEY, addProfile, deleteProfile, getAllProfiles, getProfile, getProfileKey, switchProfile, } from './profiles/index.js';
|
|
5
5
|
export { defineModuleActions, defineModules, getModuleNames, getModule, getModuleAction, } from './modules/registry.js';
|
|
6
|
-
export { request } from './request/index.js';
|
|
6
|
+
export { request, } from './request/index.js';
|
|
7
7
|
export { pickFields, pickFieldsSingle, filterData, searchData, sortData, processData, } from './utils/index.js';
|
|
8
8
|
export { BUILD, VERSION } from './version.js';
|
package/dist/misc/environment.js
CHANGED
|
@@ -25,7 +25,11 @@ function toResponseHeaders(headers) {
|
|
|
25
25
|
}
|
|
26
26
|
return result;
|
|
27
27
|
}
|
|
28
|
-
|
|
28
|
+
function hasHeader(headers, name) {
|
|
29
|
+
const normalized = name.toLowerCase();
|
|
30
|
+
return Object.keys(headers).some((key) => key.toLowerCase() === normalized);
|
|
31
|
+
}
|
|
32
|
+
async function toNodeBody(body, headers) {
|
|
29
33
|
if (body === undefined || body === null)
|
|
30
34
|
return undefined;
|
|
31
35
|
if (typeof body === 'string')
|
|
@@ -38,8 +42,25 @@ async function toNodeBody(body) {
|
|
|
38
42
|
return new Uint8Array(body.buffer, body.byteOffset, body.byteLength);
|
|
39
43
|
}
|
|
40
44
|
if (body instanceof Blob) {
|
|
45
|
+
if (body.type && !hasHeader(headers, 'content-type')) {
|
|
46
|
+
headers['content-type'] = body.type;
|
|
47
|
+
}
|
|
41
48
|
return new Uint8Array(await body.arrayBuffer());
|
|
42
49
|
}
|
|
50
|
+
if (typeof FormData !== 'undefined' && body instanceof FormData) {
|
|
51
|
+
const request = new Request('https://zentao-api.local/', { method: 'POST', body });
|
|
52
|
+
const contentType = request.headers.get('content-type');
|
|
53
|
+
if (contentType && !hasHeader(headers, 'content-type')) {
|
|
54
|
+
headers['content-type'] = contentType;
|
|
55
|
+
}
|
|
56
|
+
return new Uint8Array(await request.arrayBuffer());
|
|
57
|
+
}
|
|
58
|
+
if (typeof URLSearchParams !== 'undefined' && body instanceof URLSearchParams) {
|
|
59
|
+
if (!hasHeader(headers, 'content-type')) {
|
|
60
|
+
headers['content-type'] = 'application/x-www-form-urlencoded;charset=UTF-8';
|
|
61
|
+
}
|
|
62
|
+
return body.toString();
|
|
63
|
+
}
|
|
43
64
|
return String(body);
|
|
44
65
|
}
|
|
45
66
|
function abortError() {
|
|
@@ -60,7 +81,8 @@ async function nodeFetchWithTlsOptions(url, init, rejectUnauthorized) {
|
|
|
60
81
|
const transport = parsed.protocol === 'https:'
|
|
61
82
|
? await importNodeModule('node:https')
|
|
62
83
|
: await importNodeModule('node:http');
|
|
63
|
-
const
|
|
84
|
+
const headers = toNodeRequestHeaders(init.headers);
|
|
85
|
+
const body = await toNodeBody(init.body, headers);
|
|
64
86
|
return new Promise((resolve, reject) => {
|
|
65
87
|
if (init.signal?.aborted) {
|
|
66
88
|
reject(abortError());
|
|
@@ -68,7 +90,7 @@ async function nodeFetchWithTlsOptions(url, init, rejectUnauthorized) {
|
|
|
68
90
|
}
|
|
69
91
|
const request = transport.request(parsed, {
|
|
70
92
|
method: init.method ?? 'GET',
|
|
71
|
-
headers
|
|
93
|
+
headers,
|
|
72
94
|
rejectUnauthorized,
|
|
73
95
|
}, (response) => {
|
|
74
96
|
const chunks = [];
|
package/dist/misc/errors.d.ts
CHANGED
|
@@ -23,7 +23,7 @@ export declare const ERRORS: {
|
|
|
23
23
|
readonly E_INVALID_ACTION_DEFINITION: "Invalid module action definition.";
|
|
24
24
|
readonly E_MISSING_PARAM: "Missing required parameter: {param}";
|
|
25
25
|
readonly E_INVALID_PARAM: "Invalid value for parameter {param}: {value}";
|
|
26
|
-
readonly E_INVALID_REQUEST_NAME: "Request name must use the form \"moduleName/methodName\".";
|
|
26
|
+
readonly E_INVALID_REQUEST_NAME: "Request name must use the form \"moduleName\", \"moduleName/methodName\", or \"moduleName/<objectID>\".";
|
|
27
27
|
readonly E_API_FAILED: "ZenTao API returned failure: {message}";
|
|
28
28
|
};
|
|
29
29
|
/** SDK 已知错误码,对应 {@link ERRORS} 的 key。 */
|
package/dist/misc/errors.js
CHANGED
|
@@ -23,7 +23,7 @@ export const ERRORS = {
|
|
|
23
23
|
E_INVALID_ACTION_DEFINITION: 'Invalid module action definition.',
|
|
24
24
|
E_MISSING_PARAM: 'Missing required parameter: {param}',
|
|
25
25
|
E_INVALID_PARAM: 'Invalid value for parameter {param}: {value}',
|
|
26
|
-
E_INVALID_REQUEST_NAME: 'Request name must use the form "moduleName/methodName".',
|
|
26
|
+
E_INVALID_REQUEST_NAME: 'Request name must use the form "moduleName", "moduleName/methodName", or "moduleName/<objectID>".',
|
|
27
27
|
E_API_FAILED: 'ZenTao API returned failure: {message}',
|
|
28
28
|
};
|
|
29
29
|
/**
|