zentao-api 0.6.0 → 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/dist/index.d.ts CHANGED
@@ -6,4 +6,4 @@ export { defineModuleActions, defineModules, type DefineModulesOptions, type Exp
6
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, ClientRequestBodyType, ClientRequestOptions, ClientResponseType, DataRecord, DataRecordFilter, DataRecordFilterGroup, GlobalOptions, HttpMethod, ListPagerInfo, LoginResponse, ModuleAction, ModuleActionMethod, ModuleActionName, ModuleActionPagerGetterMap, ModuleActionParam, ModuleActionParamOption, ModuleActionRequest, ModuleActionRequestBody, ModuleActionResponse, ModuleActionResultType, ModuleActionType, ModuleDefinition, ModuleName, Pager, ProcessListOptions, ProcessSingleOptions, RequestOptions, ResponseData, ServerConfig, SortExpr, SortFn, ZentaoProfile, ZentaoProfileConfig, ZentaoProfileRecord, ZentaoProfilesStore, ZentaoClientOptions, } from './types/index.js';
9
+ export type { ApiListResponse, ApiResponse, ClientRequestBodyType, ClientRequestOptions, ClientResponseType, DataRecord, DataRecordFilter, DataRecordFilterGroup, FileCreateParams, FileCreateResult, FileUploadDataInput, FileUploadPathInput, FileUploadSource, GlobalOptions, HttpMethod, ListPagerInfo, LoginResponse, ModuleAction, ModuleActionMethod, ModuleActionName, ModuleActionPagerGetterMap, ModuleActionParam, ModuleActionParamOption, ModuleActionRequest, ModuleActionRequestBody, ModuleActionRequestMediaType, ModuleActionResponse, ModuleActionResultType, ModuleActionType, ModuleDefinition, ModuleName, Pager, ProcessListOptions, ProcessSingleOptions, RequestOptions, ResponseData, ServerConfig, SortExpr, SortFn, ZentaoProfile, ZentaoProfileConfig, ZentaoProfileRecord, ZentaoProfilesStore, ZentaoClientOptions, } from './types/index.js';
@@ -27,6 +27,11 @@ export declare const ERRORS: {
27
27
  readonly E_INVALID_PARAM: "Invalid value for parameter {param}: {value}";
28
28
  readonly E_INVALID_REQUEST_NAME: "Request name must use the form \"moduleName\", \"moduleName/methodName\", or \"moduleName/<objectID>\".";
29
29
  readonly E_API_FAILED: "ZenTao API returned failure: {message}";
30
+ readonly E_UPLOAD_PATH_UNSUPPORTED: "Local file paths are not supported in browser runtimes. Pass a File or Blob instead.";
31
+ readonly E_UPLOAD_FILE_NOT_FOUND: "Upload file not found: {path}";
32
+ readonly E_UPLOAD_FILE_READ_FAILED: "Failed to read upload file: {path}";
33
+ readonly E_UPLOAD_FILE_TOO_LARGE: "Upload file is {size} bytes and exceeds the {limit} byte limit.";
34
+ readonly E_INVALID_UPLOAD_SOURCE: "Invalid upload source for field: {field}";
30
35
  };
31
36
  /** SDK 已知错误码,对应 {@link ERRORS} 的 key。 */
32
37
  export type ErrorCode = keyof typeof ERRORS;
@@ -27,6 +27,11 @@ export const ERRORS = {
27
27
  E_INVALID_PARAM: 'Invalid value for parameter {param}: {value}',
28
28
  E_INVALID_REQUEST_NAME: 'Request name must use the form "moduleName", "moduleName/methodName", or "moduleName/<objectID>".',
29
29
  E_API_FAILED: 'ZenTao API returned failure: {message}',
30
+ E_UPLOAD_PATH_UNSUPPORTED: 'Local file paths are not supported in browser runtimes. Pass a File or Blob instead.',
31
+ E_UPLOAD_FILE_NOT_FOUND: 'Upload file not found: {path}',
32
+ E_UPLOAD_FILE_READ_FAILED: 'Failed to read upload file: {path}',
33
+ E_UPLOAD_FILE_TOO_LARGE: 'Upload file is {size} bytes and exceeds the {limit} byte limit.',
34
+ E_INVALID_UPLOAD_SOURCE: 'Invalid upload source for field: {field}',
30
35
  };
31
36
  /**
32
37
  * SDK 统一错误类型。
@@ -1,7 +1,9 @@
1
- import type { ModuleAction, ModuleActionParam, ModuleDefinition } from '../types/index.js';
1
+ import type { ModuleAction, ModuleActionParam, ModuleActionRequestMediaType, ModuleDefinition } from '../types/index.js';
2
2
  /** 导出的模块动作定义。 */
3
3
  export type ExportedModuleAction = Omit<ModuleAction, 'requestBody'> & {
4
4
  bodyParams: ModuleActionParam[];
5
+ /** 请求体媒体类型;省略时为 `application/json`。 */
6
+ bodyMediaType?: ModuleActionRequestMediaType;
5
7
  };
6
8
  /** 导出的模块定义。 */
7
9
  export type ExportedModuleDefinition = Omit<ModuleDefinition, 'actions'> & {
@@ -30,6 +30,7 @@ export function exportRegistry(options = {}) {
30
30
  const { requestBody, ...exportedAction } = action;
31
31
  return {
32
32
  ...exportedAction,
33
+ ...(requestBody?.mediaType ? { bodyMediaType: requestBody.mediaType } : {}),
33
34
  bodyParams: requestBody
34
35
  ? getModuleActionParams(module.name, action.name, { roles: ['body'] })
35
36
  : [],
@@ -7103,11 +7103,13 @@ export declare const BUILTIN_MODULES: ({
7103
7103
  requestBody: {
7104
7104
  required: true;
7105
7105
  type: "object";
7106
+ mediaType: "multipart/form-data";
7106
7107
  schema: {
7107
7108
  type: string;
7108
7109
  properties: {
7109
7110
  file: {
7110
7111
  type: string;
7112
+ format: string;
7111
7113
  description: string;
7112
7114
  };
7113
7115
  objectType: {
@@ -7151,6 +7153,7 @@ export declare const BUILTIN_MODULES: ({
7151
7153
  };
7152
7154
  required: string[];
7153
7155
  };
7156
+ mediaType?: undefined;
7154
7157
  };
7155
7158
  } | {
7156
7159
  name: "delete";
@@ -6306,12 +6306,14 @@ export const BUILTIN_MODULES = [
6306
6306
  requestBody: {
6307
6307
  required: true,
6308
6308
  type: 'object',
6309
+ mediaType: 'multipart/form-data',
6309
6310
  schema: {
6310
6311
  "type": "object",
6311
6312
  "properties": {
6312
6313
  "file": {
6313
6314
  "type": "string",
6314
- "description": "本地文件路径,将按 multipart/form-data 上传"
6315
+ "format": "binary",
6316
+ "description": "待上传文件;Node.js/Bun 可传本地文件路径,浏览器传 File 或 Blob"
6315
6317
  },
6316
6318
  "objectType": {
6317
6319
  "type": "string",
@@ -7100,11 +7100,13 @@ export declare const MODULES: ({
7100
7100
  requestBody: {
7101
7101
  required: true;
7102
7102
  type: "object";
7103
+ mediaType: "multipart/form-data";
7103
7104
  schema: {
7104
7105
  type: string;
7105
7106
  properties: {
7106
7107
  file: {
7107
7108
  type: string;
7109
+ format: string;
7108
7110
  description: string;
7109
7111
  };
7110
7112
  objectType: {
@@ -7148,6 +7150,7 @@ export declare const MODULES: ({
7148
7150
  };
7149
7151
  required: string[];
7150
7152
  };
7153
+ mediaType?: undefined;
7151
7154
  };
7152
7155
  } | {
7153
7156
  name: "delete";
@@ -1,4 +1,4 @@
1
- import type { DataRecord, RequestOptions, ResponseData } from '../types/index.js';
1
+ import type { DataRecord, FileCreateParams, FileCreateResult, RequestOptions, ResponseData } from '../types/index.js';
2
2
  import type { BuiltinActionMeta, BuiltinModuleName } from '../modules/generated.js';
3
3
  type BuiltinActionName<M extends BuiltinModuleName> = Extract<keyof BuiltinActionMeta[M], string>;
4
4
  type BuiltinListRequestName = BuiltinModuleName;
@@ -14,11 +14,11 @@ type ActionMetaOf<Name extends BuiltinRequestName> = ModuleNameOf<Name> extends
14
14
  resultType: 'object';
15
15
  } : never : never;
16
16
  /** 根据内置请求名推导出的参数类型。字段细节以运行时 registry 为准,这里保持可扩展。 */
17
- export type RequestParamsFor<Name extends BuiltinRequestName> = Record<string, unknown>;
17
+ export type RequestParamsFor<Name extends BuiltinRequestName> = Name extends 'file/create' ? FileCreateParams : Record<string, unknown>;
18
18
  /** 根据内置请求名推导出的 `ResponseData.data` 类型。 */
19
19
  export type RequestResultFor<Name extends BuiltinRequestName> = ActionMetaOf<Name> extends {
20
20
  resultType: 'list';
21
- } ? DataRecord[] : ActionMetaOf<Name> extends {
21
+ } ? DataRecord[] : Name extends 'file/create' ? FileCreateResult : ActionMetaOf<Name> extends {
22
22
  resultType: 'object';
23
23
  } ? DataRecord : unknown;
24
24
  /**
@@ -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: command.data,
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
+ }
@@ -15,3 +15,4 @@ export * from './options.js';
15
15
  export * from './profile.js';
16
16
  export * from './data.js';
17
17
  export * from './module.js';
18
+ export * from './upload.js';
@@ -15,3 +15,4 @@ export * from './options.js';
15
15
  export * from './profile.js';
16
16
  export * from './data.js';
17
17
  export * from './module.js';
18
+ export * from './upload.js';
@@ -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
  /** 请求体示例。 */
@@ -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
@@ -1,5 +1,5 @@
1
- const fallbackBuild = "2026-08-18T03:57:32.732Z";
2
- const fallbackVersion = "0.6.0";
1
+ const fallbackBuild = "2026-08-18T05:15:46.553Z";
2
+ const fallbackVersion = "0.6.1";
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.6.0",
3
+ "version": "0.6.1",
4
4
  "type": "module",
5
5
  "description": "Browser and Node.js SDK for ZenTao API",
6
6
  "license": "MIT",