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.
@@ -1,221 +1,19 @@
1
- import { ZentaoError } from '../misc/errors.js';
2
- import { asArray } from '../utils/index.js';
1
+ // 模块注册表的统一出入口(barrel)。
2
+ // 实际实现按职责拆分:
3
+ // - ./registry-store —— 共享运行时状态与 克隆/冻结/合并/校验 等底层原语(内部使用)。
4
+ // - ./define —— 定义/写入模块(defineModules / defineModuleActions / resetModuleDefinitions)。
5
+ // - ./override —— 内置覆盖/扩展(基于 define,随 SDK 发布并在加载/重置时自动应用)。
6
+ // - ./query —— 获取模块信息(getModule / getModuleAction / getModuleNames / isModuleName)。
3
7
  import { BUILTIN_MODULES } from './generated.js';
8
+ import { applyBuiltinOverrides } from './override.js';
9
+ import { setPostResetHook } from './registry-store.js';
4
10
  export { BUILTIN_MODULES };
5
11
  export const MODULES = BUILTIN_MODULES;
6
- // 运行时注册表存放「深克隆 + 深冻结」后的模块定义:
7
- // - 深克隆:避免用户后续修改自己的输入对象时污染注册表;
8
- // - 深冻结:让 getModule / getModuleAction 可以零拷贝返回引用,
9
- // 外部尝试改写会在严格模式下抛 TypeError,开销也降到 O(1) 查询。
10
- let modules = freezeModules(deepClone(BUILTIN_MODULES));
11
- let moduleMap = buildModuleMap(modules);
12
- function deepClone(value) {
13
- if (Array.isArray(value)) {
14
- return value.map((item) => deepClone(item));
15
- }
16
- if (value && typeof value === 'object' && !(value instanceof Function)) {
17
- const result = {};
18
- for (const [key, nestedValue] of Object.entries(value)) {
19
- result[key] = deepClone(nestedValue);
20
- }
21
- return result;
22
- }
23
- return value;
24
- }
25
- function deepFreeze(value) {
26
- if (value === null || typeof value !== 'object')
27
- return value;
28
- if (Object.isFrozen(value))
29
- return value;
30
- for (const key of Object.keys(value)) {
31
- deepFreeze(value[key]);
32
- }
33
- return Object.freeze(value);
34
- }
35
- function freezeAction(action) {
36
- return deepFreeze(action);
37
- }
38
- function freezeModule(module) {
39
- module.actions.forEach(freezeAction);
40
- return deepFreeze(module);
41
- }
42
- function freezeModules(source) {
43
- source.forEach(freezeModule);
44
- return source;
45
- }
46
- function findActionIndex(source, actionName) {
47
- const key = actionName.toLowerCase();
48
- return source.findIndex((action) => String(action.name).toLowerCase() === key);
49
- }
50
- function mergeActions(base, extension) {
51
- const next = base.slice();
52
- for (const action of extension) {
53
- const index = findActionIndex(next, String(action.name));
54
- const frozen = freezeAction(deepClone(action));
55
- if (index >= 0) {
56
- next[index] = frozen;
57
- }
58
- else {
59
- next.push(frozen);
60
- }
61
- }
62
- return next;
63
- }
64
- function mergeModule(base, extension) {
65
- return freezeModule({
66
- ...base,
67
- ...deepClone(extension),
68
- actions: mergeActions(base.actions, extension.actions),
69
- });
70
- }
71
- function buildModuleMap(source) {
72
- return new Map(source.map((module) => [module.name.toLowerCase(), module]));
73
- }
74
- function rebuildMap() {
75
- moduleMap = buildModuleMap(modules);
76
- }
77
- function validateModule(module) {
78
- if (!module || typeof module.name !== 'string' || !Array.isArray(module.actions)) {
79
- throw new ZentaoError('E_INVALID_MODULE_DEFINITION');
80
- }
81
- }
82
- function validateAction(action) {
83
- if (!action || typeof action.name !== 'string' || typeof action.path !== 'string' || typeof action.method !== 'string') {
84
- throw new ZentaoError('E_INVALID_ACTION_DEFINITION');
85
- }
86
- }
87
- /**
88
- * 注册或扩展模块定义。
89
- *
90
- * 行为细节:
91
- * - 模块名匹配大小写不敏感。
92
- * - 未知模块直接追加到注册表末尾。
93
- * - 已存在的模块默认按 `mergeModule` 合并:模块元数据浅合并、动作按名同名替换/未知追加;
94
- * `options.replace` 为 `true` 时整体替换。
95
- * - 所有写入都会做深克隆 + 深冻结:调用方后续修改自己的对象不会污染注册表,注册表也不可被外部改写。
96
- *
97
- * @param input - 单个或一组模块定义。
98
- * @param options - 写入策略,参见 {@link DefineModulesOptions}。
99
- * @throws {ZentaoError} `E_INVALID_MODULE_DEFINITION` —— 缺少 `name` 或 `actions` 字段。
100
- */
101
- export function defineModules(input, options = {}) {
102
- for (const module of asArray(input)) {
103
- validateModule(module);
104
- const key = module.name.toLowerCase();
105
- const index = modules.findIndex((item) => item.name.toLowerCase() === key);
106
- if (index >= 0) {
107
- modules[index] = options.replace
108
- ? freezeModule(deepClone(module))
109
- : mergeModule(modules[index], module);
110
- }
111
- else {
112
- modules.push(freezeModule(deepClone(module)));
113
- }
114
- }
115
- rebuildMap();
116
- }
117
- /**
118
- * 为已存在的模块追加或覆盖动作。
119
- *
120
- * 不做深度合并:同名动作整体替换,未知动作追加。这避免在 schema、参数数组等字段上出现隐式合并规则。
121
- *
122
- * @param moduleName - 目标模块名(大小写不敏感)。
123
- * @param input - 单个或一组动作定义。
124
- * @throws {ZentaoError} `E_INVALID_MODULE`(模块未注册)或 `E_INVALID_ACTION_DEFINITION`
125
- * (动作缺少 `name` / `path` / `method` 等必填字段)。
126
- */
127
- export function defineModuleActions(moduleName, input) {
128
- const key = moduleName.toLowerCase();
129
- const module = moduleMap.get(key);
130
- if (!module) {
131
- throw new ZentaoError('E_INVALID_MODULE', { module: moduleName });
132
- }
133
- const actions = module.actions.slice();
134
- for (const action of asArray(input)) {
135
- validateAction(action);
136
- const index = findActionIndex(actions, String(action.name));
137
- const frozen = freezeAction(deepClone(action));
138
- // 同名动作替换,未知动作追加;不做深度合并,避免 schema/数组字段出现隐式规则。
139
- if (index >= 0) {
140
- actions[index] = frozen;
141
- }
142
- else {
143
- actions.push(frozen);
144
- }
145
- }
146
- const nextModule = freezeModule({ ...module, actions });
147
- const index = modules.findIndex((item) => item.name.toLowerCase() === key);
148
- modules[index] = nextModule;
149
- rebuildMap();
150
- }
151
- /**
152
- * 获取模块定义。
153
- *
154
- * 模块名匹配大小写不敏感。返回值是注册表内部的已深冻结引用(O(1) 查询、零拷贝),
155
- * 任何写入尝试在严格模式下会抛 `TypeError`;如需修改请使用 {@link defineModules}。
156
- *
157
- * @param moduleName - 模块名。
158
- * @returns 已注册的模块定义。
159
- * @throws {ZentaoError} `E_INVALID_MODULE` —— 模块未注册。
160
- */
161
- export function getModule(moduleName) {
162
- const module = moduleMap.get(moduleName.toLowerCase());
163
- if (!module) {
164
- throw new ZentaoError('E_INVALID_MODULE', { module: moduleName });
165
- }
166
- return module;
167
- }
168
- /**
169
- * 获取指定模块下的某个动作。
170
- *
171
- * 解析顺序:
172
- * 1. `actionName === 'ls'` 时映射为 `list`(仅作为别名,不会修改注册表)。
173
- * 2. 在该模块的动作中按名称大小写不敏感匹配。
174
- * 3. 当请求的动作不是基础 CRUD(`list`/`get`/`create`/`update`/`delete`)时,
175
- * 额外允许命中 `type === 'action'` 的自定义动作(即使名字不在基础 CRUD 中)。
176
- *
177
- * 返回值同样是已深冻结的引用,请勿尝试修改。
178
- *
179
- * @param moduleName - 模块名(大小写不敏感)。
180
- * @param actionName - 动作名(大小写不敏感);支持 `ls` 作为 `list` 的别名。
181
- * @returns 匹配到的动作定义。
182
- * @throws {ZentaoError} `E_INVALID_MODULE`(模块未注册)或 `E_INVALID_ACTION`(动作不存在)。
183
- */
184
- export function getModuleAction(moduleName, actionName) {
185
- const module = getModule(moduleName);
186
- const normalized = actionName === 'ls' ? 'list' : actionName;
187
- const direct = module.actions.find((action) => String(action.name).toLowerCase() === normalized.toLowerCase());
188
- if (direct)
189
- return direct;
190
- const crud = new Set(['list', 'get', 'create', 'update', 'delete']);
191
- if (!crud.has(normalized)) {
192
- const custom = module.actions.find((action) => action.type === 'action' && String(action.name).toLowerCase() === normalized.toLowerCase());
193
- if (custom)
194
- return custom;
195
- }
196
- throw new ZentaoError('E_INVALID_ACTION', { module: moduleName, action: actionName });
197
- }
198
- /**
199
- * 返回当前运行时注册表中的所有模块名。
200
- *
201
- * 顺序与模块写入注册表的顺序一致;包括内置模块和通过 {@link defineModules} 追加的用户模块。
202
- *
203
- * @returns 模块名数组(保留原始大小写)。
204
- */
205
- export function getModuleNames() {
206
- return modules.map((module) => module.name);
207
- }
208
- /**
209
- * 判断模块名是否已注册。
210
- *
211
- * @param moduleName - 模块名;匹配大小写不敏感。
212
- * @returns 已注册返回 `true`,否则 `false`。
213
- */
214
- export function isModuleName(moduleName) {
215
- return moduleMap.has(moduleName.toLowerCase());
216
- }
217
- /** @internal */
218
- export function resetModuleDefinitions() {
219
- modules = freezeModules(deepClone(BUILTIN_MODULES));
220
- rebuildMap();
221
- }
12
+ export { defineModules, defineModuleActions, extendModuleAction, resetModuleDefinitions, } from './define.js';
13
+ export { applyBuiltinOverrides };
14
+ // 内置覆盖接线:避免 define ↔ override 之间的循环依赖。
15
+ // - 加载时应用一次;
16
+ // - 注册为 store 的「重置后钩子」,使 resetModuleDefinitions 还原内置基线后自动重新应用。
17
+ setPostResetHook(applyBuiltinOverrides);
18
+ applyBuiltinOverrides();
19
+ export { getModule, getModuleAction, getModuleNames, isModuleName, } from './query.js';
@@ -1,16 +1,116 @@
1
- import type { RequestOptions, ResponseData } from '../types/index.js';
1
+ import type { DataRecord, RequestOptions, ResponseData } from '../types/index.js';
2
+ import type { BUILTIN_MODULES } from '../modules/generated.js';
3
+ type BuiltinModuleDefinition = (typeof BUILTIN_MODULES)[number];
4
+ type BuiltinModuleName = BuiltinModuleDefinition['name'];
5
+ type BuiltinAction<M extends BuiltinModuleName> = Extract<BuiltinModuleDefinition, {
6
+ name: M;
7
+ }>['actions'][number];
8
+ type BuiltinActionName<M extends BuiltinModuleName> = BuiltinAction<M>['name'] & string;
9
+ type BuiltinListRequestName = BuiltinModuleName;
10
+ type BuiltinNamedRequestName = {
11
+ [M in BuiltinModuleName]: `${M}/${BuiltinActionName<M>}`;
12
+ }[BuiltinModuleName];
13
+ type BuiltinIdRequestName = `${BuiltinModuleName}/${number}`;
14
+ /** 内置模块支持的请求名:`module`、`module/action` 或 `module/123`。 */
15
+ export type BuiltinRequestName = BuiltinListRequestName | BuiltinNamedRequestName | BuiltinIdRequestName;
16
+ type ModuleNameOf<Name extends BuiltinRequestName> = Name extends `${infer M}/${string}` ? Extract<M, BuiltinModuleName> : Extract<Name, BuiltinModuleName>;
17
+ type ActionNameOf<Name extends BuiltinRequestName> = Name extends `${string}/${infer A}` ? A extends `${number}` ? 'get' : A : 'list';
18
+ type ActionOfRequest<Name extends BuiltinRequestName> = Extract<BuiltinAction<ModuleNameOf<Name>>, {
19
+ name: ActionNameOf<Name>;
20
+ }>;
21
+ type UnionToIntersection<T> = (T extends unknown ? (value: T) => void : never) extends (value: infer R) => void ? R : never;
22
+ type NumericInput = number | `${number}`;
23
+ type BooleanInput = boolean | 0 | 1 | 'true' | 'false' | '1' | '0' | 'yes' | 'no' | 'on' | 'off';
24
+ type OptionValue<T> = T extends readonly (infer Option)[] ? Option extends {
25
+ value: infer Value;
26
+ } ? Value : never : never;
27
+ type ParamInput<T> = (T extends {
28
+ options: infer Options;
29
+ } ? OptionValue<Options> : never) | (T extends {
30
+ type: 'number' | 'integer';
31
+ } ? NumericInput : T extends {
32
+ type: 'boolean';
33
+ } ? BooleanInput : string);
34
+ type QueryParams<A> = A extends {
35
+ params: readonly (infer Param)[];
36
+ } ? UnionToIntersection<Param extends {
37
+ name: infer Name extends string;
38
+ } ? {
39
+ [K in Name]?: ParamInput<Param>;
40
+ } : unknown> : {};
41
+ type SchemaProperties<S> = S extends {
42
+ properties: infer Properties;
43
+ } ? Properties : {};
44
+ type SchemaRequiredKeys<S> = S extends {
45
+ required: readonly (infer Key)[];
46
+ } ? Extract<Key, keyof SchemaProperties<S> & string> : never;
47
+ type SchemaValue<S> = S extends {
48
+ type: 'integer' | 'number';
49
+ } ? NumericInput : S extends {
50
+ type: 'boolean';
51
+ } ? BooleanInput : S extends {
52
+ type: 'array';
53
+ items?: infer Items;
54
+ } ? Array<SchemaValue<Items>> | readonly SchemaValue<Items>[] | string | Record<string, unknown> : S extends {
55
+ type: 'object';
56
+ } ? Record<string, unknown> : string;
57
+ type BodyParams<A> = A extends {
58
+ requestBody: {
59
+ schema: infer Schema;
60
+ };
61
+ } ? {
62
+ [K in SchemaRequiredKeys<Schema>]: SchemaValue<SchemaProperties<Schema>[K]>;
63
+ } & {
64
+ [K in Exclude<keyof SchemaProperties<Schema> & string, SchemaRequiredKeys<Schema>>]?: SchemaValue<SchemaProperties<Schema>[K]>;
65
+ } & {
66
+ data?: string | Partial<{
67
+ [K in keyof SchemaProperties<Schema> & string]: SchemaValue<SchemaProperties<Schema>[K]>;
68
+ }>;
69
+ } : {
70
+ data?: string | Record<string, unknown>;
71
+ };
72
+ type ScopedParams<PathParams> = 'scope' extends keyof PathParams ? {
73
+ product?: string | number;
74
+ productID?: string | number;
75
+ project?: string | number;
76
+ projectID?: string | number;
77
+ execution?: string | number;
78
+ executionID?: string | number;
79
+ } : {};
80
+ type PathParams<A> = A extends {
81
+ pathParams: infer Params;
82
+ } ? {
83
+ [K in Exclude<keyof Params & string, 'scope' | 'scopeID'>]?: string | number;
84
+ } & ScopedParams<Params> & {
85
+ id?: string | number;
86
+ } : {
87
+ id?: string | number;
88
+ };
89
+ /** 根据内置请求名推导出的参数类型。 */
90
+ export type RequestParamsFor<Name extends BuiltinRequestName> = PathParams<ActionOfRequest<Name>> & QueryParams<ActionOfRequest<Name>> & BodyParams<ActionOfRequest<Name>> & {
91
+ page?: string | number;
92
+ recPerPage?: string | number;
93
+ } & Record<string, unknown>;
94
+ /** 根据内置请求名推导出的 `ResponseData.data` 类型。 */
95
+ export type RequestResultFor<Name extends BuiltinRequestName> = ActionOfRequest<Name> extends {
96
+ resultType: 'list';
97
+ } ? DataRecord[] : ActionOfRequest<Name> extends {
98
+ resultType: 'object';
99
+ } ? DataRecord : unknown;
2
100
  /**
3
- * 按模块动作名请求禅道 API。
101
+ * 按模块名或模块动作名请求禅道 API。
4
102
  *
5
103
  * 选项优先级为:本次调用 options > 全局 options > 客户端默认值。
6
104
  * 当响应 `status` 为 `"fail"` 时,默认按原样返回;若 `options.throwOnFail`
7
105
  * 或全局 `throwOnFail` 为真,则改为抛出 `E_API_FAILED`。
8
106
  *
9
107
  * @typeParam T 期望的 `data` 字段类型;不传时为 `unknown`,调用方需要自行收窄。
10
- * @param name - 模块动作名,例如 `product/list`。
108
+ * @param name - 请求名,例如 `product`、`product/list` 或 `product/1`。
11
109
  * @param params - 请求参数。
12
110
  * @param options - 请求选项。
13
111
  * @returns 归一化后的禅道 API 响应。
14
112
  * @throws {ZentaoError} 传输层错误、参数缺失或 `throwOnFail` 启用时的业务失败。
15
113
  */
16
- export declare function request<T = unknown>(name: `${string}/${string}`, params?: Record<string, unknown>, options?: RequestOptions): Promise<ResponseData<T>>;
114
+ export declare function request<Name extends BuiltinRequestName>(name: Name, params?: RequestParamsFor<Name>, options?: RequestOptions): Promise<ResponseData<RequestResultFor<Name>>>;
115
+ export declare function request<T = unknown>(name: string, params?: Record<string, unknown>, options?: RequestOptions): Promise<ResponseData<T>>;
116
+ export {};
@@ -3,17 +3,21 @@ import { getGlobalOptions } from '../misc/global-options.js';
3
3
  import { getModule } from '../modules/registry.js';
4
4
  import { extractPager, extractResult, resolveModuleCommand } from '../modules/resolve.js';
5
5
  import { isRecord, processData } from '../utils/index.js';
6
- /** 将 `moduleName/methodName` 形式的请求名拆成模块名和动作名。 */
6
+ /** 将 `moduleName`、`moduleName/methodName` 或 `moduleName/<objectID>` 请求名拆成模块名、动作名和对象 ID。 */
7
7
  function splitRequestName(name) {
8
- const [moduleName, actionName] = name.split('/');
9
- // 如果没有指定 actionName
8
+ const parts = name.split('/');
9
+ if (parts.length > 2 || !parts[0]) {
10
+ throw new ZentaoError('E_INVALID_REQUEST_NAME');
11
+ }
12
+ const [moduleName, actionName] = parts;
13
+ // 如果没有指定 actionName,按列表动作处理。
10
14
  if (!actionName?.length) {
11
15
  return {
12
16
  moduleName,
13
17
  actionName: 'list',
14
18
  };
15
19
  }
16
- // 如果 actionName 为数值
20
+ // 如果 actionName 为数值,按详情快捷写法处理。
17
21
  if (Number.isInteger(Number(actionName))) {
18
22
  return {
19
23
  moduleName,
@@ -26,6 +30,26 @@ function splitRequestName(name) {
26
30
  actionName,
27
31
  };
28
32
  }
33
+ function stringifyMessage(value) {
34
+ if (typeof value === 'string')
35
+ return value;
36
+ if (value === undefined)
37
+ return undefined;
38
+ try {
39
+ return JSON.stringify(value);
40
+ }
41
+ catch {
42
+ return String(value);
43
+ }
44
+ }
45
+ function extractApiCode(record) {
46
+ for (const key of ['code', 'errorCode', 'errno']) {
47
+ const value = record[key];
48
+ if (typeof value === 'string' || typeof value === 'number')
49
+ return value;
50
+ }
51
+ return undefined;
52
+ }
29
53
  /** 判断本次调用是否携带了需要本地处理列表的选项。 */
30
54
  function hasListProcessing(options) {
31
55
  return Boolean((options.filter && options.filter.length > 0) ||
@@ -73,10 +97,11 @@ function normalizeResponse(command, raw, options) {
73
97
  const record = raw;
74
98
  const status = record.status === 'fail' ? 'fail' : 'success';
75
99
  const data = applyProcessing(extractResult(command.action, record), options);
100
+ const rawMessage = record.message;
76
101
  const pager = extractPager(command.action, record);
77
- return {
102
+ const response = {
78
103
  status,
79
- message: typeof record.message === 'string' ? record.message : undefined,
104
+ message: stringifyMessage(rawMessage),
80
105
  data: data,
81
106
  pager: pager ? {
82
107
  total: Number(pager.recTotal),
@@ -84,21 +109,16 @@ function normalizeResponse(command, raw, options) {
84
109
  recPerPage: Number(pager.recPerPage),
85
110
  } : undefined,
86
111
  };
112
+ if (rawMessage !== undefined && typeof rawMessage !== 'string') {
113
+ response.rawMessage = rawMessage;
114
+ }
115
+ const apiCode = extractApiCode(record);
116
+ if (apiCode !== undefined)
117
+ response.apiCode = apiCode;
118
+ if (status === 'fail')
119
+ response.raw = record;
120
+ return response;
87
121
  }
88
- /**
89
- * 按模块动作名请求禅道 API。
90
- *
91
- * 选项优先级为:本次调用 options > 全局 options > 客户端默认值。
92
- * 当响应 `status` 为 `"fail"` 时,默认按原样返回;若 `options.throwOnFail`
93
- * 或全局 `throwOnFail` 为真,则改为抛出 `E_API_FAILED`。
94
- *
95
- * @typeParam T 期望的 `data` 字段类型;不传时为 `unknown`,调用方需要自行收窄。
96
- * @param name - 模块动作名,例如 `product/list`。
97
- * @param params - 请求参数。
98
- * @param options - 请求选项。
99
- * @returns 归一化后的禅道 API 响应。
100
- * @throws {ZentaoError} 传输层错误、参数缺失或 `throwOnFail` 启用时的业务失败。
101
- */
102
122
  export async function request(name, params = {}, options = {}) {
103
123
  const globals = getGlobalOptions();
104
124
  const client = options.client ?? globals.client;
@@ -110,8 +130,8 @@ export async function request(name, params = {}, options = {}) {
110
130
  // recPerPage 是最常用的列表参数,允许在全局或本次调用中统一覆盖。
111
131
  const recPerPage = params.recPerPage ?? options.recPerPage ?? globals.recPerPage;
112
132
  const mergedParams = {
113
- ...(id !== undefined ? { id } : {}),
114
133
  ...params,
134
+ ...(id !== undefined ? { id } : {}),
115
135
  ...(recPerPage !== undefined ? { recPerPage } : {}),
116
136
  };
117
137
  const command = resolveModuleCommand(module, actionName, mergedParams);
@@ -0,0 +1,38 @@
1
+ /** 创建 {@link ZentaoClient} 时使用的配置。 */
2
+ export interface ZentaoClientOptions {
3
+ /** 禅道站点根地址,例如 `https://zentao.example.com`;SDK 会自动拼接 `/api.php/v2`。 */
4
+ baseUrl: string;
5
+ /** 禅道 API Token;未提供时可稍后通过 {@link ZentaoClient.login} 获取并写入实例。 */
6
+ token?: string;
7
+ /** 默认请求超时时间,单位毫秒。 */
8
+ timeout?: number;
9
+ /** 是否跳过 TLS 证书验证;仅 Node.js 运行时支持,浏览器中会抛错。 */
10
+ insecure?: boolean;
11
+ }
12
+ /** SDK 支持的 HTTP 方法。 */
13
+ export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
14
+ /** 请求体序列化方式。 */
15
+ export type ClientRequestBodyType = 'json' | 'form' | 'raw';
16
+ /** 响应体解析方式。 */
17
+ export type ClientResponseType = 'auto' | 'json' | 'text' | 'arrayBuffer' | 'blob' | 'response';
18
+ /** `ZentaoClient.request()` 的单次请求选项。 */
19
+ export interface ClientRequestOptions {
20
+ /** HTTP 方法,默认 `GET`。 */
21
+ method?: HttpMethod;
22
+ /** 请求体;`GET` 请求会忽略该字段。普通对象默认按 JSON 发送,`FormData` / `Blob` / `ArrayBuffer` 等会原样发送。 */
23
+ body?: unknown;
24
+ /** 请求体序列化方式。默认 `json`;传入 `FormData` 等原生 body 时会自动按 `raw` 处理。 */
25
+ bodyType?: ClientRequestBodyType;
26
+ /** 响应体解析方式。默认 `auto`,会优先尝试 JSON,失败后回落为文本。 */
27
+ responseType?: ClientResponseType;
28
+ /** 额外请求头;会与 SDK 自动注入的 `Token` / `Content-Type` 合并。 */
29
+ headers?: HeadersInit;
30
+ /** URL 查询参数;`undefined` 值会被跳过。 */
31
+ query?: Record<string, string | number | boolean | undefined>;
32
+ /** 外部取消信号;会与 SDK 自身的超时控制合并。 */
33
+ signal?: AbortSignal;
34
+ /** 单次请求超时时间,优先级高于全局和客户端默认值。 */
35
+ timeout?: number;
36
+ /** 单次请求 TLS 跳过证书验证选项;仅 Node.js 运行时支持。 */
37
+ insecure?: boolean;
38
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,42 @@
1
+ /** 本地数据处理的基础记录类型,对应一条对象数据。 */
2
+ export type DataRecord = Record<string, unknown>;
3
+ /** 单条过滤条件,字段名支持 `.` 访问子字段。 */
4
+ export interface DataRecordFilter {
5
+ /** 字段路径,例如 `status` 或 `assignedTo.id`。 */
6
+ key: string;
7
+ /** 比较运算符。 */
8
+ operator: '=' | '!=' | '>' | '<' | '>=' | '<=' | '~' | '!~';
9
+ /** 比较值;数组用于 `=`/`!=`/`~`/`!~` 的“任一/全不”匹配。 */
10
+ value: string | number | boolean | string[];
11
+ }
12
+ /** 一组过滤条件,组内按 `operator` 组合;多组之间按 AND 组合。 */
13
+ export interface DataRecordFilterGroup {
14
+ /** 组内条件的组合方式。 */
15
+ operator: 'AND' | 'OR';
16
+ /** 组内条件列表。 */
17
+ conditions: DataRecordFilter[];
18
+ }
19
+ /** 排序表达式,格式为 `字段:asc|desc`。 */
20
+ export type SortExpr = `${string}:${'asc' | 'desc'}`;
21
+ /** 自定义排序比较函数。 */
22
+ export type SortFn = (a: DataRecord, b: DataRecord) => number;
23
+ /** {@link processData} 处理列表时的选项;执行顺序为 过滤 → 搜索 → 排序 → 限制数量 → 摘取。 */
24
+ export interface ProcessListOptions {
25
+ /** 过滤表达式列表,例如 `["status=active", "pri>=2"]`,多条之间按 AND 组合。 */
26
+ filter?: string[];
27
+ /** 模糊搜索关键词组,组内空格分隔为 OR,多组之间按 AND 组合。 */
28
+ search?: string[];
29
+ /** 限定搜索字段,缺省时搜索全部字段。 */
30
+ searchFields?: string[];
31
+ /** 排序表达式,多个字段以英文逗号分隔,例如 `pri:desc,id:asc`。 */
32
+ sort?: string;
33
+ /** 限制返回列表数量,在排序后、摘取前截断;不改变服务端页大小。 */
34
+ limit?: string;
35
+ /** 摘取字段路径列表。 */
36
+ pick?: string[];
37
+ }
38
+ /** {@link processData} 处理单条对象时的选项。 */
39
+ export interface ProcessSingleOptions {
40
+ /** 摘取字段路径列表。 */
41
+ pick?: string[];
42
+ }
@@ -0,0 +1 @@
1
+ export {};