zentao-api 0.3.0 → 0.3.2
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/README.md +38 -2
- 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 +3 -3
- package/dist/index.js +2 -2
- 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/define.d.ts +63 -0
- package/dist/modules/define.js +119 -0
- package/dist/modules/generated.d.ts +4329 -2
- package/dist/modules/override.d.ts +60 -0
- package/dist/modules/override.js +113 -0
- package/dist/modules/query.d.ts +44 -0
- package/dist/modules/query.js +68 -0
- package/dist/modules/registry-store.d.ts +31 -0
- package/dist/modules/registry-store.js +133 -0
- package/dist/modules/registry.d.ts +4333 -83
- package/dist/modules/registry.js +16 -218
- package/dist/request/index.d.ts +104 -4
- package/dist/request/index.js +41 -21
- package/dist/types/client.d.ts +38 -0
- package/dist/types/client.js +1 -0
- package/dist/types/data.d.ts +42 -0
- package/dist/types/data.js +1 -0
- package/dist/types/index.d.ts +17 -341
- package/dist/types/index.js +17 -1
- package/dist/types/module.d.ts +124 -0
- package/dist/types/module.js +1 -0
- package/dist/types/options.d.ts +35 -0
- package/dist/types/options.js +1 -0
- package/dist/types/profile.d.ts +54 -0
- package/dist/types/profile.js +1 -0
- package/dist/types/response.d.ts +70 -0
- package/dist/types/response.js +1 -0
- package/dist/version.js +2 -2
- package/package.json +3 -2
package/dist/client/index.d.ts
CHANGED
|
@@ -52,7 +52,16 @@ export declare class ZentaoClient {
|
|
|
52
52
|
* @throws {ZentaoError} 可能抛出 `E_HTTP_ERROR`(非 2xx 状态)、`E_NETWORK_ERROR`(底层 fetch 失败)、
|
|
53
53
|
* `E_TIMEOUT`(超过 `timeout`)或 `E_INSECURE_BROWSER`(浏览器中开启了 `insecure`)。
|
|
54
54
|
*/
|
|
55
|
-
request(path: string, options
|
|
55
|
+
request(path: string, options: ClientRequestOptions & {
|
|
56
|
+
responseType: 'response';
|
|
57
|
+
}): Promise<Response>;
|
|
58
|
+
request(path: string, options: ClientRequestOptions & {
|
|
59
|
+
responseType: 'arrayBuffer';
|
|
60
|
+
}): Promise<ArrayBuffer>;
|
|
61
|
+
request(path: string, options: ClientRequestOptions & {
|
|
62
|
+
responseType: 'blob';
|
|
63
|
+
}): Promise<Blob>;
|
|
64
|
+
request<T = unknown>(path: string, options?: ClientRequestOptions): Promise<T>;
|
|
56
65
|
/**
|
|
57
66
|
* 发起 `GET` 请求。
|
|
58
67
|
*
|
|
@@ -61,7 +70,7 @@ export declare class ZentaoClient {
|
|
|
61
70
|
* @returns 解析后的响应体(强转为 `T`)。
|
|
62
71
|
* @throws {ZentaoError} 传输层失败时抛出,详见 {@link ZentaoClient.request}。
|
|
63
72
|
*/
|
|
64
|
-
get<T>(path: string): Promise<T>;
|
|
73
|
+
get<T>(path: string, options?: Omit<ClientRequestOptions, 'method' | 'body' | 'bodyType'>): Promise<T>;
|
|
65
74
|
/**
|
|
66
75
|
* 发起 `POST` 请求,`body` 会被序列化为 JSON。
|
|
67
76
|
*
|
|
@@ -71,7 +80,7 @@ export declare class ZentaoClient {
|
|
|
71
80
|
* @returns 解析后的响应体(强转为 `T`)。
|
|
72
81
|
* @throws {ZentaoError} 传输层失败时抛出,详见 {@link ZentaoClient.request}。
|
|
73
82
|
*/
|
|
74
|
-
post<T>(path: string, body: unknown): Promise<T>;
|
|
83
|
+
post<T>(path: string, body: unknown, options?: Omit<ClientRequestOptions, 'method'>): Promise<T>;
|
|
75
84
|
/**
|
|
76
85
|
* 发起 `PUT` 请求,`body` 会被序列化为 JSON。
|
|
77
86
|
*
|
|
@@ -81,7 +90,7 @@ export declare class ZentaoClient {
|
|
|
81
90
|
* @returns 解析后的响应体(强转为 `T`)。
|
|
82
91
|
* @throws {ZentaoError} 传输层失败时抛出,详见 {@link ZentaoClient.request}。
|
|
83
92
|
*/
|
|
84
|
-
put<T>(path: string, body: unknown): Promise<T>;
|
|
93
|
+
put<T>(path: string, body: unknown, options?: Omit<ClientRequestOptions, 'method'>): Promise<T>;
|
|
85
94
|
/**
|
|
86
95
|
* 发起 `DELETE` 请求。
|
|
87
96
|
*
|
|
@@ -90,7 +99,7 @@ export declare class ZentaoClient {
|
|
|
90
99
|
* @returns 解析后的响应体(强转为 `T`)。
|
|
91
100
|
* @throws {ZentaoError} 传输层失败时抛出,详见 {@link ZentaoClient.request}。
|
|
92
101
|
*/
|
|
93
|
-
delete<T>(path: string): Promise<T>;
|
|
102
|
+
delete<T>(path: string, options?: Omit<ClientRequestOptions, 'method' | 'body' | 'bodyType'>): Promise<T>;
|
|
94
103
|
/**
|
|
95
104
|
* 使用账号密码登录禅道。
|
|
96
105
|
*
|
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
|
@@ -2,8 +2,8 @@ export { ZentaoClient } from './client/index.js';
|
|
|
2
2
|
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
|
-
export { defineModuleActions, defineModules, type DefineModulesOptions, getModuleNames, getModule, getModuleAction, } from './modules/registry.js';
|
|
6
|
-
export { request } from './request/index.js';
|
|
5
|
+
export { defineModuleActions, defineModules, type DefineModulesOptions, extendModuleAction, getModuleNames, getModule, getModuleAction, } from './modules/registry.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
|
@@ -2,7 +2,7 @@ export { ZentaoClient } from './client/index.js';
|
|
|
2
2
|
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
|
-
export { defineModuleActions, defineModules, getModuleNames, getModule, getModuleAction, } from './modules/registry.js';
|
|
6
|
-
export { request } from './request/index.js';
|
|
5
|
+
export { defineModuleActions, defineModules, extendModuleAction, getModuleNames, getModule, getModuleAction, } from './modules/registry.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
|
/**
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { ModuleAction, ModuleDefinition } from '../types/index.js';
|
|
2
|
+
/** {@link defineModules} 的选项。 */
|
|
3
|
+
export interface DefineModulesOptions {
|
|
4
|
+
/**
|
|
5
|
+
* 同名模块的写入策略。
|
|
6
|
+
*
|
|
7
|
+
* - `false`(默认):合并模块的元数据,并按动作名对动作做"同名替换、未知追加"。
|
|
8
|
+
* - `true`:整体替换已存在的模块定义,原有动作会被丢弃。
|
|
9
|
+
*/
|
|
10
|
+
replace?: boolean;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* 注册或扩展模块定义。
|
|
14
|
+
*
|
|
15
|
+
* 行为细节:
|
|
16
|
+
* - 模块名匹配大小写不敏感。
|
|
17
|
+
* - 未知模块直接追加到注册表末尾。
|
|
18
|
+
* - 已存在的模块默认按 `mergeModule` 合并:模块元数据浅合并、动作按名同名替换/未知追加;
|
|
19
|
+
* `options.replace` 为 `true` 时整体替换。
|
|
20
|
+
* - 所有写入都会做深克隆 + 深冻结:调用方后续修改自己的对象不会污染注册表,注册表也不可被外部改写。
|
|
21
|
+
*
|
|
22
|
+
* @param input - 单个或一组模块定义。
|
|
23
|
+
* @param options - 写入策略,参见 {@link DefineModulesOptions}。
|
|
24
|
+
* @throws {ZentaoError} `E_INVALID_MODULE_DEFINITION` —— 缺少 `name` 或 `actions` 字段。
|
|
25
|
+
*/
|
|
26
|
+
export declare function defineModules(input: ModuleDefinition | ModuleDefinition[], options?: DefineModulesOptions): void;
|
|
27
|
+
/**
|
|
28
|
+
* 为已存在的模块追加或覆盖动作。
|
|
29
|
+
*
|
|
30
|
+
* 不做深度合并:同名动作整体替换,未知动作追加。这避免在 schema、参数数组等字段上出现隐式合并规则。
|
|
31
|
+
*
|
|
32
|
+
* @param moduleName - 目标模块名(大小写不敏感)。
|
|
33
|
+
* @param input - 单个或一组动作定义。
|
|
34
|
+
* @throws {ZentaoError} `E_INVALID_MODULE`(模块未注册)或 `E_INVALID_ACTION_DEFINITION`
|
|
35
|
+
* (动作缺少 `name` / `path` / `method` 等必填字段)。
|
|
36
|
+
*/
|
|
37
|
+
export declare function defineModuleActions(moduleName: string, input: ModuleAction | ModuleAction[]): void;
|
|
38
|
+
/**
|
|
39
|
+
* 扩展已存在的模块动作。
|
|
40
|
+
*
|
|
41
|
+
* 与 {@link defineModuleActions} 的「整体替换」不同,这里对单个动作做**深度合并**:
|
|
42
|
+
* 只需给出待修改的字段,其余字段沿用原动作定义。普通对象递归合并,
|
|
43
|
+
* 数组及其他值由补丁整体替换,值为 `undefined` 的键会被忽略。
|
|
44
|
+
*
|
|
45
|
+
* 当 `action` 为函数时**不做合并**:会以当前动作的深克隆为入参调用它,
|
|
46
|
+
* 其返回值作为完整的动作定义直接取代原动作定义。
|
|
47
|
+
*
|
|
48
|
+
* @param moduleName - 目标模块名(大小写不敏感)。
|
|
49
|
+
* @param actionName - 目标动作名(大小写不敏感)。
|
|
50
|
+
* @param action - 深度合并的补丁对象,或接收当前动作深克隆并返回完整动作定义的函数。
|
|
51
|
+
* @throws {ZentaoError} `E_INVALID_MODULE`(模块未注册)、`E_INVALID_ACTION`(动作不存在)
|
|
52
|
+
* 或 `E_INVALID_ACTION_DEFINITION`(合并结果缺少 `name` / `path` / `method` 等必填字段)。
|
|
53
|
+
*/
|
|
54
|
+
export declare function extendModuleAction(moduleName: string, actionName: string, action: Partial<ModuleAction> | ((action: ModuleAction) => ModuleAction)): void;
|
|
55
|
+
/**
|
|
56
|
+
* 将注册表重置为内置基线。
|
|
57
|
+
*
|
|
58
|
+
* 重置会触发 store 的「重置后钩子」,由 barrel(`./registry.ts`)在其中重新应用内置覆盖
|
|
59
|
+
* (见 `./override.ts`),因此重置后内置扩展依旧生效。
|
|
60
|
+
*
|
|
61
|
+
* @internal
|
|
62
|
+
*/
|
|
63
|
+
export declare function resetModuleDefinitions(): void;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { ZentaoError } from '../misc/errors.js';
|
|
2
|
+
import { asArray } from '../utils/index.js';
|
|
3
|
+
import { deepClone, deepMerge, findActionIndex, freezeAction, freezeModule, getModuleMapState, getModulesState, mergeModule, rebuildModuleMap, resetState, validateAction, validateModule, } from './registry-store.js';
|
|
4
|
+
/**
|
|
5
|
+
* 注册或扩展模块定义。
|
|
6
|
+
*
|
|
7
|
+
* 行为细节:
|
|
8
|
+
* - 模块名匹配大小写不敏感。
|
|
9
|
+
* - 未知模块直接追加到注册表末尾。
|
|
10
|
+
* - 已存在的模块默认按 `mergeModule` 合并:模块元数据浅合并、动作按名同名替换/未知追加;
|
|
11
|
+
* `options.replace` 为 `true` 时整体替换。
|
|
12
|
+
* - 所有写入都会做深克隆 + 深冻结:调用方后续修改自己的对象不会污染注册表,注册表也不可被外部改写。
|
|
13
|
+
*
|
|
14
|
+
* @param input - 单个或一组模块定义。
|
|
15
|
+
* @param options - 写入策略,参见 {@link DefineModulesOptions}。
|
|
16
|
+
* @throws {ZentaoError} `E_INVALID_MODULE_DEFINITION` —— 缺少 `name` 或 `actions` 字段。
|
|
17
|
+
*/
|
|
18
|
+
export function defineModules(input, options = {}) {
|
|
19
|
+
const modules = getModulesState();
|
|
20
|
+
for (const module of asArray(input)) {
|
|
21
|
+
validateModule(module);
|
|
22
|
+
const key = module.name.toLowerCase();
|
|
23
|
+
const index = modules.findIndex((item) => item.name.toLowerCase() === key);
|
|
24
|
+
if (index >= 0) {
|
|
25
|
+
modules[index] = options.replace
|
|
26
|
+
? freezeModule(deepClone(module))
|
|
27
|
+
: mergeModule(modules[index], module);
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
modules.push(freezeModule(deepClone(module)));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
rebuildModuleMap();
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* 为已存在的模块追加或覆盖动作。
|
|
37
|
+
*
|
|
38
|
+
* 不做深度合并:同名动作整体替换,未知动作追加。这避免在 schema、参数数组等字段上出现隐式合并规则。
|
|
39
|
+
*
|
|
40
|
+
* @param moduleName - 目标模块名(大小写不敏感)。
|
|
41
|
+
* @param input - 单个或一组动作定义。
|
|
42
|
+
* @throws {ZentaoError} `E_INVALID_MODULE`(模块未注册)或 `E_INVALID_ACTION_DEFINITION`
|
|
43
|
+
* (动作缺少 `name` / `path` / `method` 等必填字段)。
|
|
44
|
+
*/
|
|
45
|
+
export function defineModuleActions(moduleName, input) {
|
|
46
|
+
const key = moduleName.toLowerCase();
|
|
47
|
+
const module = getModuleMapState().get(key);
|
|
48
|
+
if (!module) {
|
|
49
|
+
throw new ZentaoError('E_INVALID_MODULE', { module: moduleName });
|
|
50
|
+
}
|
|
51
|
+
const actions = module.actions.slice();
|
|
52
|
+
for (const action of asArray(input)) {
|
|
53
|
+
validateAction(action);
|
|
54
|
+
const index = findActionIndex(actions, String(action.name));
|
|
55
|
+
const frozen = freezeAction(deepClone(action));
|
|
56
|
+
// 同名动作替换,未知动作追加;不做深度合并,避免 schema/数组字段出现隐式规则。
|
|
57
|
+
if (index >= 0) {
|
|
58
|
+
actions[index] = frozen;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
actions.push(frozen);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const nextModule = freezeModule({ ...module, actions });
|
|
65
|
+
const modules = getModulesState();
|
|
66
|
+
const index = modules.findIndex((item) => item.name.toLowerCase() === key);
|
|
67
|
+
modules[index] = nextModule;
|
|
68
|
+
rebuildModuleMap();
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* 扩展已存在的模块动作。
|
|
72
|
+
*
|
|
73
|
+
* 与 {@link defineModuleActions} 的「整体替换」不同,这里对单个动作做**深度合并**:
|
|
74
|
+
* 只需给出待修改的字段,其余字段沿用原动作定义。普通对象递归合并,
|
|
75
|
+
* 数组及其他值由补丁整体替换,值为 `undefined` 的键会被忽略。
|
|
76
|
+
*
|
|
77
|
+
* 当 `action` 为函数时**不做合并**:会以当前动作的深克隆为入参调用它,
|
|
78
|
+
* 其返回值作为完整的动作定义直接取代原动作定义。
|
|
79
|
+
*
|
|
80
|
+
* @param moduleName - 目标模块名(大小写不敏感)。
|
|
81
|
+
* @param actionName - 目标动作名(大小写不敏感)。
|
|
82
|
+
* @param action - 深度合并的补丁对象,或接收当前动作深克隆并返回完整动作定义的函数。
|
|
83
|
+
* @throws {ZentaoError} `E_INVALID_MODULE`(模块未注册)、`E_INVALID_ACTION`(动作不存在)
|
|
84
|
+
* 或 `E_INVALID_ACTION_DEFINITION`(合并结果缺少 `name` / `path` / `method` 等必填字段)。
|
|
85
|
+
*/
|
|
86
|
+
export function extendModuleAction(moduleName, actionName, action) {
|
|
87
|
+
const key = moduleName.toLowerCase();
|
|
88
|
+
const module = getModuleMapState().get(key);
|
|
89
|
+
if (!module) {
|
|
90
|
+
throw new ZentaoError('E_INVALID_MODULE', { module: moduleName });
|
|
91
|
+
}
|
|
92
|
+
const actions = module.actions.slice();
|
|
93
|
+
const actionIndex = findActionIndex(actions, actionName);
|
|
94
|
+
if (actionIndex < 0) {
|
|
95
|
+
throw new ZentaoError('E_INVALID_ACTION', { module: moduleName, action: actionName });
|
|
96
|
+
}
|
|
97
|
+
const current = actions[actionIndex];
|
|
98
|
+
// 函数:以当前动作的深克隆为入参,返回值作为完整动作定义直接取代原定义,不做合并。
|
|
99
|
+
// 对象:作为补丁与原动作深度合并。
|
|
100
|
+
const next = typeof action === 'function' ? action(deepClone(current)) : deepMerge(current, action);
|
|
101
|
+
validateAction(next);
|
|
102
|
+
actions[actionIndex] = freezeAction(next);
|
|
103
|
+
const nextModule = freezeModule({ ...module, actions });
|
|
104
|
+
const modules = getModulesState();
|
|
105
|
+
const index = modules.findIndex((item) => item.name.toLowerCase() === key);
|
|
106
|
+
modules[index] = nextModule;
|
|
107
|
+
rebuildModuleMap();
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* 将注册表重置为内置基线。
|
|
111
|
+
*
|
|
112
|
+
* 重置会触发 store 的「重置后钩子」,由 barrel(`./registry.ts`)在其中重新应用内置覆盖
|
|
113
|
+
* (见 `./override.ts`),因此重置后内置扩展依旧生效。
|
|
114
|
+
*
|
|
115
|
+
* @internal
|
|
116
|
+
*/
|
|
117
|
+
export function resetModuleDefinitions() {
|
|
118
|
+
resetState();
|
|
119
|
+
}
|