zentao-api 0.5.5 → 0.6.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/README.md +16 -0
- package/dist/browser/zentao-api.global.js +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/misc/errors.d.ts +5 -0
- package/dist/misc/errors.js +5 -0
- package/dist/modules/export.d.ts +3 -1
- package/dist/modules/export.js +1 -0
- package/dist/modules/generated.d.ts +9543 -3955
- package/dist/modules/generated.js +4850 -702
- package/dist/modules/object-props.js +330 -0
- package/dist/modules/registry.d.ts +8839 -3955
- package/dist/request/index.d.ts +10 -88
- package/dist/request/index.js +6 -1
- package/dist/request/prepare-body.d.ts +12 -0
- package/dist/request/prepare-body.js +221 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +1 -0
- package/dist/types/module.d.ts +6 -0
- package/dist/types/options.d.ts +2 -0
- package/dist/types/upload.d.ts +38 -0
- package/dist/types/upload.js +1 -0
- package/dist/version.js +2 -2
- package/package.json +1 -1
package/dist/request/index.d.ts
CHANGED
|
@@ -1,11 +1,6 @@
|
|
|
1
|
-
import type { DataRecord, RequestOptions, ResponseData } from '../types/index.js';
|
|
2
|
-
import type {
|
|
3
|
-
type
|
|
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;
|
|
1
|
+
import type { DataRecord, FileCreateParams, FileCreateResult, RequestOptions, ResponseData } from '../types/index.js';
|
|
2
|
+
import type { BuiltinActionMeta, BuiltinModuleName } from '../modules/generated.js';
|
|
3
|
+
type BuiltinActionName<M extends BuiltinModuleName> = Extract<keyof BuiltinActionMeta[M], string>;
|
|
9
4
|
type BuiltinListRequestName = BuiltinModuleName;
|
|
10
5
|
type BuiltinNamedRequestName = {
|
|
11
6
|
[M in BuiltinModuleName]: `${M}/${BuiltinActionName<M>}`;
|
|
@@ -15,88 +10,15 @@ type BuiltinIdRequestName = `${BuiltinModuleName}/${number}`;
|
|
|
15
10
|
export type BuiltinRequestName = BuiltinListRequestName | BuiltinNamedRequestName | BuiltinIdRequestName;
|
|
16
11
|
type ModuleNameOf<Name extends BuiltinRequestName> = Name extends `${infer M}/${string}` ? Extract<M, BuiltinModuleName> : Extract<Name, BuiltinModuleName>;
|
|
17
12
|
type ActionNameOf<Name extends BuiltinRequestName> = Name extends `${string}/${infer A}` ? A extends `${number}` ? 'get' : A : 'list';
|
|
18
|
-
type
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
type
|
|
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
|
-
scope?: 'products' | 'projects' | 'executions' | (string & {});
|
|
74
|
-
scopeID?: string | number;
|
|
75
|
-
product?: string | number;
|
|
76
|
-
productID?: string | number;
|
|
77
|
-
project?: string | number;
|
|
78
|
-
projectID?: string | number;
|
|
79
|
-
execution?: string | number;
|
|
80
|
-
executionID?: string | number;
|
|
81
|
-
} : {};
|
|
82
|
-
type PathParams<A> = A extends {
|
|
83
|
-
pathParams: infer Params;
|
|
84
|
-
} ? {
|
|
85
|
-
[K in Exclude<keyof Params & string, 'scope' | 'scopeID'>]?: string | number;
|
|
86
|
-
} & ScopedParams<Params> & {
|
|
87
|
-
id?: string | number;
|
|
88
|
-
} : {
|
|
89
|
-
id?: string | number;
|
|
90
|
-
};
|
|
91
|
-
/** 根据内置请求名推导出的参数类型。 */
|
|
92
|
-
export type RequestParamsFor<Name extends BuiltinRequestName> = PathParams<ActionOfRequest<Name>> & QueryParams<ActionOfRequest<Name>> & BodyParams<ActionOfRequest<Name>> & {
|
|
93
|
-
page?: string | number;
|
|
94
|
-
recPerPage?: string | number;
|
|
95
|
-
} & Record<string, unknown>;
|
|
13
|
+
type ActionMetaOf<Name extends BuiltinRequestName> = ModuleNameOf<Name> extends infer M extends BuiltinModuleName ? ActionNameOf<Name> extends infer A ? A extends keyof BuiltinActionMeta[M] ? BuiltinActionMeta[M][A] : {
|
|
14
|
+
resultType: 'object';
|
|
15
|
+
} : never : never;
|
|
16
|
+
/** 根据内置请求名推导出的参数类型。字段细节以运行时 registry 为准,这里保持可扩展。 */
|
|
17
|
+
export type RequestParamsFor<Name extends BuiltinRequestName> = Name extends 'file/create' ? FileCreateParams : Record<string, unknown>;
|
|
96
18
|
/** 根据内置请求名推导出的 `ResponseData.data` 类型。 */
|
|
97
|
-
export type RequestResultFor<Name extends BuiltinRequestName> =
|
|
19
|
+
export type RequestResultFor<Name extends BuiltinRequestName> = ActionMetaOf<Name> extends {
|
|
98
20
|
resultType: 'list';
|
|
99
|
-
} ? DataRecord[] :
|
|
21
|
+
} ? DataRecord[] : Name extends 'file/create' ? FileCreateResult : ActionMetaOf<Name> extends {
|
|
100
22
|
resultType: 'object';
|
|
101
23
|
} ? DataRecord : unknown;
|
|
102
24
|
/**
|
package/dist/request/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import { getGlobalOptions } from '../misc/global-options.js';
|
|
|
3
3
|
import { getModule, getModuleAction } from '../modules/registry.js';
|
|
4
4
|
import { extractPager, extractResult, resolveActionRequest } from '../modules/resolve.js';
|
|
5
5
|
import { isRecord, processData } from '../utils/index.js';
|
|
6
|
+
import { prepareActionBody } from './prepare-body.js';
|
|
6
7
|
/** 将 `moduleName`、`moduleName/methodName` 或 `moduleName/<objectID>` 请求名拆成模块名、动作名和对象 ID。 */
|
|
7
8
|
function splitRequestName(name) {
|
|
8
9
|
const parts = name.split('/');
|
|
@@ -198,10 +199,14 @@ export async function request(name, params = {}, options = {}) {
|
|
|
198
199
|
? await autoFillUpdateParams(module, action, mergedParams, options)
|
|
199
200
|
: mergedParams;
|
|
200
201
|
const command = resolveActionRequest(module, actionName, finalParams);
|
|
202
|
+
const preparedBody = await prepareActionBody(command, {
|
|
203
|
+
maxUploadBytes: options.maxUploadBytes,
|
|
204
|
+
});
|
|
201
205
|
const raw = await client.request(command.path, {
|
|
202
206
|
method: String(command.action.method).toUpperCase(),
|
|
203
207
|
query: command.query,
|
|
204
|
-
body:
|
|
208
|
+
body: preparedBody.body,
|
|
209
|
+
bodyType: preparedBody.bodyType,
|
|
205
210
|
timeout: options.timeout ?? globals.timeout,
|
|
206
211
|
insecure: options.insecure ?? globals.insecure,
|
|
207
212
|
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ClientRequestBodyType, ModuleActionRequest } from '../types/index.js';
|
|
2
|
+
/** 本地路径上传的默认大小上限(50 MiB)。 */
|
|
3
|
+
export declare const DEFAULT_MAX_UPLOAD_BYTES: number;
|
|
4
|
+
export interface PrepareActionBodyOptions {
|
|
5
|
+
maxUploadBytes?: number;
|
|
6
|
+
}
|
|
7
|
+
export interface PreparedActionBody {
|
|
8
|
+
body: unknown;
|
|
9
|
+
bodyType?: ClientRequestBodyType;
|
|
10
|
+
}
|
|
11
|
+
/** 根据模块动作的媒体类型,把解析后的普通请求体转换为底层客户端可发送的请求体。 */
|
|
12
|
+
export declare function prepareActionBody(command: ModuleActionRequest, options?: PrepareActionBodyOptions): Promise<PreparedActionBody>;
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { ZentaoError } from '../misc/errors.js';
|
|
2
|
+
import { isNodeRuntime } from '../misc/environment.js';
|
|
3
|
+
import { isRecord } from '../utils/index.js';
|
|
4
|
+
/** 本地路径上传的默认大小上限(50 MiB)。 */
|
|
5
|
+
export const DEFAULT_MAX_UPLOAD_BYTES = 50 * 1024 * 1024;
|
|
6
|
+
const MIME_TYPES = {
|
|
7
|
+
'.avif': 'image/avif',
|
|
8
|
+
'.csv': 'text/csv',
|
|
9
|
+
'.gif': 'image/gif',
|
|
10
|
+
'.htm': 'text/html',
|
|
11
|
+
'.html': 'text/html',
|
|
12
|
+
'.jpeg': 'image/jpeg',
|
|
13
|
+
'.jpg': 'image/jpeg',
|
|
14
|
+
'.json': 'application/json',
|
|
15
|
+
'.md': 'text/markdown',
|
|
16
|
+
'.pdf': 'application/pdf',
|
|
17
|
+
'.png': 'image/png',
|
|
18
|
+
'.svg': 'image/svg+xml',
|
|
19
|
+
'.txt': 'text/plain',
|
|
20
|
+
'.webp': 'image/webp',
|
|
21
|
+
'.zip': 'application/zip',
|
|
22
|
+
};
|
|
23
|
+
// 与 environment.ts 相同:通过变量形式的动态 import 避免浏览器 bundle 静态拉入 node:*。
|
|
24
|
+
function importNodeModule(specifier) {
|
|
25
|
+
return import(specifier);
|
|
26
|
+
}
|
|
27
|
+
function isBlob(value) {
|
|
28
|
+
return typeof Blob !== 'undefined' && value instanceof Blob;
|
|
29
|
+
}
|
|
30
|
+
function isFileUploadPathInput(value) {
|
|
31
|
+
return isRecord(value) && typeof value.path === 'string';
|
|
32
|
+
}
|
|
33
|
+
function isFileUploadDataInput(value) {
|
|
34
|
+
return isRecord(value) && Object.prototype.hasOwnProperty.call(value, 'data') && typeof value.filename === 'string';
|
|
35
|
+
}
|
|
36
|
+
function inferContentType(filename) {
|
|
37
|
+
const normalized = filename.toLowerCase();
|
|
38
|
+
const dotIndex = normalized.lastIndexOf('.');
|
|
39
|
+
return dotIndex >= 0 ? MIME_TYPES[normalized.slice(dotIndex)] ?? 'application/octet-stream' : 'application/octet-stream';
|
|
40
|
+
}
|
|
41
|
+
function inferExtension(contentType) {
|
|
42
|
+
const normalized = contentType.split(';', 1)[0]?.trim().toLowerCase();
|
|
43
|
+
const entry = Object.entries(MIME_TYPES).find(([, type]) => type === normalized);
|
|
44
|
+
return entry?.[0] ?? '.bin';
|
|
45
|
+
}
|
|
46
|
+
function resolveMaxUploadBytes(value) {
|
|
47
|
+
if (value === undefined)
|
|
48
|
+
return DEFAULT_MAX_UPLOAD_BYTES;
|
|
49
|
+
if (!Number.isFinite(value) || value < 1) {
|
|
50
|
+
throw new ZentaoError('E_INVALID_PARAM', { param: 'maxUploadBytes', value: String(value) });
|
|
51
|
+
}
|
|
52
|
+
return Math.floor(value);
|
|
53
|
+
}
|
|
54
|
+
function assertUploadSize(size, limit) {
|
|
55
|
+
if (size > limit) {
|
|
56
|
+
throw new ZentaoError('E_UPLOAD_FILE_TOO_LARGE', { size, limit });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function normalizeFilename(value, fallback, field) {
|
|
60
|
+
const requested = value?.trim() || fallback.trim();
|
|
61
|
+
const filename = requested.split(/[\\/]/).at(-1)?.trim() ?? '';
|
|
62
|
+
if (!filename) {
|
|
63
|
+
throw new ZentaoError('E_INVALID_UPLOAD_SOURCE', { field });
|
|
64
|
+
}
|
|
65
|
+
return filename;
|
|
66
|
+
}
|
|
67
|
+
function errorCode(error) {
|
|
68
|
+
return isRecord(error) && typeof error.code === 'string' ? error.code : undefined;
|
|
69
|
+
}
|
|
70
|
+
function copyView(view) {
|
|
71
|
+
const source = new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
|
|
72
|
+
const copy = new Uint8Array(source.byteLength);
|
|
73
|
+
copy.set(source);
|
|
74
|
+
return copy;
|
|
75
|
+
}
|
|
76
|
+
function blobFromData(data, contentType, field) {
|
|
77
|
+
if (isBlob(data)) {
|
|
78
|
+
return data.type === contentType ? data : new Blob([data], { type: contentType });
|
|
79
|
+
}
|
|
80
|
+
if (data instanceof ArrayBuffer) {
|
|
81
|
+
return new Blob([data], { type: contentType });
|
|
82
|
+
}
|
|
83
|
+
if (ArrayBuffer.isView(data)) {
|
|
84
|
+
return new Blob([copyView(data)], { type: contentType });
|
|
85
|
+
}
|
|
86
|
+
throw new ZentaoError('E_INVALID_UPLOAD_SOURCE', { field });
|
|
87
|
+
}
|
|
88
|
+
async function preparePathFile(source, field, maxUploadBytes) {
|
|
89
|
+
if (!isNodeRuntime()) {
|
|
90
|
+
throw new ZentaoError('E_UPLOAD_PATH_UNSUPPORTED');
|
|
91
|
+
}
|
|
92
|
+
const path = typeof source === 'string' ? source : source.path;
|
|
93
|
+
const explicitFilename = typeof source === 'string' ? undefined : source.filename;
|
|
94
|
+
const explicitContentType = typeof source === 'string' ? undefined : source.contentType;
|
|
95
|
+
const [fs, pathModule] = await Promise.all([
|
|
96
|
+
importNodeModule('node:fs/promises'),
|
|
97
|
+
importNodeModule('node:path'),
|
|
98
|
+
]);
|
|
99
|
+
let stats;
|
|
100
|
+
try {
|
|
101
|
+
stats = await fs.stat(path);
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
if (errorCode(error) === 'ENOENT') {
|
|
105
|
+
throw new ZentaoError('E_UPLOAD_FILE_NOT_FOUND', { path }, error);
|
|
106
|
+
}
|
|
107
|
+
throw new ZentaoError('E_UPLOAD_FILE_READ_FAILED', { path }, error);
|
|
108
|
+
}
|
|
109
|
+
if (!stats.isFile()) {
|
|
110
|
+
throw new ZentaoError('E_INVALID_UPLOAD_SOURCE', { field }, { path });
|
|
111
|
+
}
|
|
112
|
+
assertUploadSize(stats.size, maxUploadBytes);
|
|
113
|
+
const filename = normalizeFilename(explicitFilename, pathModule.basename(path), field);
|
|
114
|
+
const contentType = explicitContentType?.trim() || inferContentType(filename);
|
|
115
|
+
const bun = globalThis.Bun;
|
|
116
|
+
if (bun?.file) {
|
|
117
|
+
const file = bun.file(path);
|
|
118
|
+
assertUploadSize(file.size, maxUploadBytes);
|
|
119
|
+
const shouldApplyContentType = Boolean(explicitContentType?.trim()) || !file.type;
|
|
120
|
+
return {
|
|
121
|
+
blob: shouldApplyContentType && file.type !== contentType
|
|
122
|
+
? new Blob([file], { type: contentType })
|
|
123
|
+
: file,
|
|
124
|
+
filename,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
const bytes = await fs.readFile(path);
|
|
129
|
+
assertUploadSize(bytes.byteLength, maxUploadBytes);
|
|
130
|
+
return {
|
|
131
|
+
blob: new Blob([copyView(bytes)], { type: contentType }),
|
|
132
|
+
filename,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
throw new ZentaoError('E_UPLOAD_FILE_READ_FAILED', { path }, error);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
async function prepareFile(value, field, maxUploadBytes) {
|
|
140
|
+
if (typeof value === 'string' || isFileUploadPathInput(value)) {
|
|
141
|
+
return preparePathFile(value, field, maxUploadBytes);
|
|
142
|
+
}
|
|
143
|
+
if (isBlob(value)) {
|
|
144
|
+
assertUploadSize(value.size, maxUploadBytes);
|
|
145
|
+
const contentType = value.type || 'application/octet-stream';
|
|
146
|
+
const name = typeof value.name === 'string'
|
|
147
|
+
? value.name
|
|
148
|
+
: `${field}${inferExtension(contentType)}`;
|
|
149
|
+
const filename = normalizeFilename(name, `${field}.bin`, field);
|
|
150
|
+
return {
|
|
151
|
+
blob: value.type === contentType ? value : new Blob([value], { type: contentType }),
|
|
152
|
+
filename,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
if (isFileUploadDataInput(value)) {
|
|
156
|
+
const filename = normalizeFilename(value.filename, field, field);
|
|
157
|
+
const contentType = value.contentType?.trim()
|
|
158
|
+
|| (isBlob(value.data) ? value.data.type : '')
|
|
159
|
+
|| inferContentType(filename);
|
|
160
|
+
const blob = blobFromData(value.data, contentType, field);
|
|
161
|
+
assertUploadSize(blob.size, maxUploadBytes);
|
|
162
|
+
return { blob, filename };
|
|
163
|
+
}
|
|
164
|
+
throw new ZentaoError('E_INVALID_UPLOAD_SOURCE', { field });
|
|
165
|
+
}
|
|
166
|
+
function isBinarySchema(schema) {
|
|
167
|
+
if (!isRecord(schema))
|
|
168
|
+
return false;
|
|
169
|
+
if (schema.format === 'binary')
|
|
170
|
+
return true;
|
|
171
|
+
return schema.type === 'array' && isRecord(schema.items) && schema.items.format === 'binary';
|
|
172
|
+
}
|
|
173
|
+
function appendTextValue(form, field, value) {
|
|
174
|
+
if (value === undefined)
|
|
175
|
+
return;
|
|
176
|
+
if (Array.isArray(value)) {
|
|
177
|
+
for (const item of value)
|
|
178
|
+
appendTextValue(form, field, item);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
if (value === null) {
|
|
182
|
+
form.append(field, '');
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
form.append(field, typeof value === 'object' ? JSON.stringify(value) : String(value));
|
|
186
|
+
}
|
|
187
|
+
async function createMultipartBody(command, maxUploadBytes) {
|
|
188
|
+
const form = new FormData();
|
|
189
|
+
const properties = isRecord(command.action.requestBody?.schema.properties)
|
|
190
|
+
? command.action.requestBody.schema.properties
|
|
191
|
+
: {};
|
|
192
|
+
for (const [field, value] of Object.entries(command.data ?? {})) {
|
|
193
|
+
if (!isBinarySchema(properties[field])) {
|
|
194
|
+
appendTextValue(form, field, value);
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
const values = Array.isArray(value) ? value : [value];
|
|
198
|
+
if (values.length === 0) {
|
|
199
|
+
throw new ZentaoError('E_INVALID_UPLOAD_SOURCE', { field });
|
|
200
|
+
}
|
|
201
|
+
for (const item of values) {
|
|
202
|
+
const prepared = await prepareFile(item, field, maxUploadBytes);
|
|
203
|
+
form.append(field, prepared.blob, prepared.filename);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return form;
|
|
207
|
+
}
|
|
208
|
+
/** 根据模块动作的媒体类型,把解析后的普通请求体转换为底层客户端可发送的请求体。 */
|
|
209
|
+
export async function prepareActionBody(command, options = {}) {
|
|
210
|
+
const mediaType = command.action.requestBody?.mediaType ?? 'application/json';
|
|
211
|
+
if (mediaType === 'multipart/form-data') {
|
|
212
|
+
return {
|
|
213
|
+
body: await createMultipartBody(command, resolveMaxUploadBytes(options.maxUploadBytes)),
|
|
214
|
+
bodyType: 'raw',
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
if (mediaType === 'application/x-www-form-urlencoded') {
|
|
218
|
+
return { body: command.data, bodyType: 'form' };
|
|
219
|
+
}
|
|
220
|
+
return { body: command.data };
|
|
221
|
+
}
|
package/dist/types/index.d.ts
CHANGED
package/dist/types/index.js
CHANGED
package/dist/types/module.d.ts
CHANGED
|
@@ -27,6 +27,8 @@ export interface ModuleActionParam {
|
|
|
27
27
|
defaultValue?: unknown;
|
|
28
28
|
/** 参数值类型,用于基础类型转换。 */
|
|
29
29
|
type?: 'string' | 'number' | 'boolean';
|
|
30
|
+
/** OpenAPI schema format,例如 `binary` 或 `int32`。 */
|
|
31
|
+
format?: string;
|
|
30
32
|
/** 参数可选值。 */
|
|
31
33
|
options?: readonly ModuleActionParamOption[];
|
|
32
34
|
}
|
|
@@ -34,12 +36,16 @@ export interface ModuleActionParam {
|
|
|
34
36
|
export type ModuleActionResultType = 'text' | 'object' | 'list';
|
|
35
37
|
/** 列表分页信息别名。 */
|
|
36
38
|
export type ListPagerInfo = Pager;
|
|
39
|
+
/** 高阶请求支持的请求体媒体类型。 */
|
|
40
|
+
export type ModuleActionRequestMediaType = 'application/json' | 'multipart/form-data' | 'application/x-www-form-urlencoded';
|
|
37
41
|
/** 模块动作请求体定义。 */
|
|
38
42
|
export interface ModuleActionRequestBody {
|
|
39
43
|
/** 请求体类型。 */
|
|
40
44
|
type?: 'object' | 'string';
|
|
41
45
|
/** 请求体是否必填。 */
|
|
42
46
|
required?: boolean;
|
|
47
|
+
/** 请求体媒体类型;省略时按 `application/json` 处理。 */
|
|
48
|
+
mediaType?: ModuleActionRequestMediaType;
|
|
43
49
|
/** OpenAPI 风格 schema,用于从 params 组装 body。 */
|
|
44
50
|
schema: Readonly<Record<string, unknown>>;
|
|
45
51
|
/** 请求体示例。 */
|
package/dist/types/options.d.ts
CHANGED
|
@@ -33,6 +33,8 @@ export interface RequestOptions extends ProcessListOptions {
|
|
|
33
33
|
timeout?: number;
|
|
34
34
|
/** 本次调用 TLS 跳过证书验证选项;仅 Node.js 运行时支持。 */
|
|
35
35
|
insecure?: boolean;
|
|
36
|
+
/** 单个本地文件或 Blob 上传的最大字节数,默认 50 MiB。 */
|
|
37
|
+
maxUploadBytes?: number;
|
|
36
38
|
/** 单条对象转换函数,在字段摘取前执行;列表转换请使用 `convert`。 */
|
|
37
39
|
convertSingle?: ProcessSingleOptions['convert'];
|
|
38
40
|
/**
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/** Node.js/Bun 本地文件路径形式的上传输入。 */
|
|
2
|
+
export interface FileUploadPathInput {
|
|
3
|
+
/** 本地文件路径。 */
|
|
4
|
+
path: string;
|
|
5
|
+
/** 上传时使用的文件名;省略时取路径 basename。 */
|
|
6
|
+
filename?: string;
|
|
7
|
+
/** 显式 MIME 类型;省略时按文件名推断。 */
|
|
8
|
+
contentType?: string;
|
|
9
|
+
}
|
|
10
|
+
/** 内存数据形式的上传输入。 */
|
|
11
|
+
export interface FileUploadDataInput {
|
|
12
|
+
/** 文件内容。 */
|
|
13
|
+
data: Blob | ArrayBuffer | ArrayBufferView;
|
|
14
|
+
/** 上传时使用的文件名。 */
|
|
15
|
+
filename: string;
|
|
16
|
+
/** 显式 MIME 类型;省略时优先使用 Blob.type,再按文件名推断。 */
|
|
17
|
+
contentType?: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* 高阶 `request()` 可接受的文件输入。
|
|
21
|
+
*
|
|
22
|
+
* - Node.js/Bun:可直接传本地路径或 {@link FileUploadPathInput}。
|
|
23
|
+
* - 浏览器:传 `File`(属于 `Blob`)、`Blob` 或 {@link FileUploadDataInput};
|
|
24
|
+
* 无文件名的 `Blob` 会按 MIME 类型生成文件名,需要精确文件名时使用数据对象形式。
|
|
25
|
+
*/
|
|
26
|
+
export type FileUploadSource = string | Blob | FileUploadPathInput | FileUploadDataInput;
|
|
27
|
+
/** `request("file/create")` 的参数。 */
|
|
28
|
+
export type FileCreateParams = Record<string, unknown> & {
|
|
29
|
+
file: FileUploadSource;
|
|
30
|
+
objectType: 'bug' | 'story' | 'task' | 'testcase' | (string & {});
|
|
31
|
+
objectID: number;
|
|
32
|
+
};
|
|
33
|
+
/** `request("file/create")` 归一化后的结果。 */
|
|
34
|
+
export interface FileCreateResult {
|
|
35
|
+
id: number;
|
|
36
|
+
url?: string;
|
|
37
|
+
[key: string]: unknown;
|
|
38
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/version.js
CHANGED