@vafast/api-client 0.1.1 → 0.1.3
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 +212 -196
- package/TODO.md +83 -0
- package/example/auto-infer.ts +223 -0
- package/example/test-sse.ts +192 -0
- package/package.json +12 -13
- package/src/core/eden.ts +705 -0
- package/src/index.ts +34 -65
- package/src/types/index.ts +17 -116
- package/test/eden.test.ts +425 -0
- package/tsconfig.json +1 -1
- package/tsdown.config.ts +10 -0
- package/vitest.config.ts +8 -0
- package/bun.lock +0 -569
- package/example/index.ts +0 -255
- package/src/core/api-client.ts +0 -389
- package/src/core/typed-client.ts +0 -305
- package/src/utils/index.ts +0 -232
- package/src/websocket/websocket-client.ts +0 -347
- package/test/api-client.test.ts +0 -262
- package/test/basic.test.ts +0 -55
- package/test/typed-client.test.ts +0 -304
- package/test/utils.test.ts +0 -363
- package/test/websocket.test.ts +0 -434
- package/tsup.config.ts +0 -23
package/src/core/typed-client.ts
DELETED
|
@@ -1,305 +0,0 @@
|
|
|
1
|
-
// 导入必要的类型
|
|
2
|
-
import type {
|
|
3
|
-
ApiClientConfig,
|
|
4
|
-
RequestConfig,
|
|
5
|
-
ApiResponse,
|
|
6
|
-
QueryParams,
|
|
7
|
-
PathParams,
|
|
8
|
-
RequestBody,
|
|
9
|
-
Server,
|
|
10
|
-
Route,
|
|
11
|
-
RouteHandler,
|
|
12
|
-
InferRouteHandler,
|
|
13
|
-
InferServer,
|
|
14
|
-
RoutePath,
|
|
15
|
-
RouteMethod,
|
|
16
|
-
RouteHandlerType,
|
|
17
|
-
} from "../types";
|
|
18
|
-
import { VafastApiClient } from "./api-client";
|
|
19
|
-
import { replacePathParams } from "../utils";
|
|
20
|
-
|
|
21
|
-
// 类型推断类型 - 重新导出
|
|
22
|
-
export type { InferRouteHandler, InferServer, RoutePath, RouteMethod, RouteHandlerType };
|
|
23
|
-
|
|
24
|
-
// 定义 HTTP 方法类型
|
|
25
|
-
type HttpMethod = "get" | "post" | "put" | "delete" | "patch" | "head" | "options";
|
|
26
|
-
|
|
27
|
-
// 类型守卫函数
|
|
28
|
-
function isHttpMethod(prop: string): prop is HttpMethod {
|
|
29
|
-
return ["get", "post", "put", "delete", "patch", "head", "options"].includes(prop);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// 类型安全的 HTTP 方法映射
|
|
33
|
-
type HttpMethodMap = {
|
|
34
|
-
get: (path: string, query?: QueryParams, config?: RequestConfig) => Promise<ApiResponse<unknown>>;
|
|
35
|
-
post: (path: string, body?: RequestBody, config?: RequestConfig) => Promise<ApiResponse<unknown>>;
|
|
36
|
-
put: (path: string, body?: RequestBody, config?: RequestConfig) => Promise<ApiResponse<unknown>>;
|
|
37
|
-
delete: (path: string, config?: RequestConfig) => Promise<ApiResponse<unknown>>;
|
|
38
|
-
patch: (
|
|
39
|
-
path: string,
|
|
40
|
-
body?: RequestBody,
|
|
41
|
-
config?: RequestConfig
|
|
42
|
-
) => Promise<ApiResponse<unknown>>;
|
|
43
|
-
head: (path: string, config?: RequestConfig) => Promise<ApiResponse<unknown>>;
|
|
44
|
-
options: (path: string, config?: RequestConfig) => Promise<ApiResponse<unknown>>;
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
// 改进的参数类型判断
|
|
48
|
-
function isRequestBody(value: unknown): value is RequestBody {
|
|
49
|
-
return (
|
|
50
|
-
value !== null &&
|
|
51
|
-
typeof value === "object" &&
|
|
52
|
-
!Array.isArray(value) &&
|
|
53
|
-
!(value instanceof FormData)
|
|
54
|
-
);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function isQueryParams(value: unknown): value is QueryParams {
|
|
58
|
-
return (
|
|
59
|
-
value !== null &&
|
|
60
|
-
typeof value === "object" &&
|
|
61
|
-
!Array.isArray(value) &&
|
|
62
|
-
!(value instanceof FormData)
|
|
63
|
-
);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// 改进的路径构建函数
|
|
67
|
-
function normalizePath(basePath: string, prop: string): string {
|
|
68
|
-
const cleanBase = basePath.endsWith("/") ? basePath.slice(0, -1) : basePath;
|
|
69
|
-
const cleanProp = prop.startsWith("/") ? prop.slice(1) : prop;
|
|
70
|
-
return `${cleanBase}/${cleanProp}`;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// 类型安全的客户端接口
|
|
74
|
-
export interface TypedApiClient<T> {
|
|
75
|
-
// 基础 HTTP 方法
|
|
76
|
-
get<P extends string>(
|
|
77
|
-
path: P,
|
|
78
|
-
query?: QueryParams,
|
|
79
|
-
config?: RequestConfig
|
|
80
|
-
): Promise<ApiResponse<unknown>>;
|
|
81
|
-
|
|
82
|
-
post<P extends string>(
|
|
83
|
-
path: P,
|
|
84
|
-
body?: RequestBody,
|
|
85
|
-
config?: RequestConfig
|
|
86
|
-
): Promise<ApiResponse<unknown>>;
|
|
87
|
-
|
|
88
|
-
put<P extends string>(
|
|
89
|
-
path: P,
|
|
90
|
-
body?: RequestBody,
|
|
91
|
-
config?: RequestConfig
|
|
92
|
-
): Promise<ApiResponse<unknown>>;
|
|
93
|
-
|
|
94
|
-
delete<P extends string>(path: P, config?: RequestConfig): Promise<ApiResponse<unknown>>;
|
|
95
|
-
|
|
96
|
-
patch<P extends string>(
|
|
97
|
-
path: P,
|
|
98
|
-
body?: RequestBody,
|
|
99
|
-
config?: RequestConfig
|
|
100
|
-
): Promise<ApiResponse<unknown>>;
|
|
101
|
-
|
|
102
|
-
head<P extends string>(path: P, config?: RequestConfig): Promise<ApiResponse<unknown>>;
|
|
103
|
-
|
|
104
|
-
options<P extends string>(path: P, config?: RequestConfig): Promise<ApiResponse<unknown>>;
|
|
105
|
-
|
|
106
|
-
// 动态路径方法
|
|
107
|
-
[key: string]: unknown;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* 创建类型安全的 API 客户端
|
|
112
|
-
*/
|
|
113
|
-
export function createTypedClient<T extends Server>(
|
|
114
|
-
server: T,
|
|
115
|
-
config?: ApiClientConfig
|
|
116
|
-
): TypedApiClient<T> {
|
|
117
|
-
const apiClient = new VafastApiClient(config);
|
|
118
|
-
|
|
119
|
-
// 创建代理对象,支持链式调用
|
|
120
|
-
return new Proxy({} as TypedApiClient<T>, {
|
|
121
|
-
get(target, prop: string) {
|
|
122
|
-
// 如果是 HTTP 方法,返回对应的请求方法
|
|
123
|
-
if (isHttpMethod(prop)) {
|
|
124
|
-
return (path: string, bodyOrQuery?: RequestBody | QueryParams, config?: RequestConfig) => {
|
|
125
|
-
const method = prop.toUpperCase();
|
|
126
|
-
|
|
127
|
-
if (method === "GET" || method === "HEAD" || method === "OPTIONS") {
|
|
128
|
-
// 暂时使用类型断言,保持功能正常
|
|
129
|
-
return (apiClient as any)[prop](path, bodyOrQuery as QueryParams, config);
|
|
130
|
-
} else {
|
|
131
|
-
return (apiClient as any)[prop](path, bodyOrQuery as RequestBody, config);
|
|
132
|
-
}
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// 如果是路径段,返回新的代理对象
|
|
137
|
-
return createPathProxy(apiClient, prop);
|
|
138
|
-
},
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* 创建路径代理
|
|
144
|
-
*/
|
|
145
|
-
function createPathProxy(apiClient: VafastApiClient, basePath: string) {
|
|
146
|
-
return new Proxy({} as Record<string, unknown>, {
|
|
147
|
-
get(target, prop: string) {
|
|
148
|
-
const currentPath = normalizePath(basePath, prop);
|
|
149
|
-
|
|
150
|
-
// 如果是 HTTP 方法,返回对应的请求方法
|
|
151
|
-
if (isHttpMethod(prop)) {
|
|
152
|
-
return (bodyOrQuery?: RequestBody | QueryParams, config?: RequestConfig) => {
|
|
153
|
-
const method = prop.toUpperCase();
|
|
154
|
-
|
|
155
|
-
if (method === "GET" || method === "HEAD" || method === "OPTIONS") {
|
|
156
|
-
// 使用类型安全的方法调用
|
|
157
|
-
const clientMethod = apiClient[prop as keyof HttpMethodMap];
|
|
158
|
-
const queryParams = bodyOrQuery as QueryParams | undefined;
|
|
159
|
-
return (clientMethod as any)(basePath, queryParams, config);
|
|
160
|
-
} else {
|
|
161
|
-
const clientMethod = apiClient[prop as keyof HttpMethodMap];
|
|
162
|
-
const requestBody = bodyOrQuery as RequestBody | undefined;
|
|
163
|
-
return (clientMethod as any)(basePath, requestBody, config);
|
|
164
|
-
}
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// 如果是路径段,返回新的代理对象
|
|
169
|
-
return createPathProxy(apiClient, currentPath);
|
|
170
|
-
},
|
|
171
|
-
|
|
172
|
-
// 处理函数调用(用于动态路径)
|
|
173
|
-
apply(target, thisArg, args) {
|
|
174
|
-
const [params, bodyOrQuery, config] = args as [
|
|
175
|
-
PathParams,
|
|
176
|
-
RequestBody | QueryParams,
|
|
177
|
-
RequestConfig?
|
|
178
|
-
];
|
|
179
|
-
|
|
180
|
-
// 替换路径参数
|
|
181
|
-
const resolvedPath = replacePathParams(basePath, params || {});
|
|
182
|
-
|
|
183
|
-
// 使用改进的参数类型判断
|
|
184
|
-
if (bodyOrQuery && isRequestBody(bodyOrQuery) && !config) {
|
|
185
|
-
// 如果有 body 参数,使用 POST 方法
|
|
186
|
-
return apiClient.post(resolvedPath, bodyOrQuery, config);
|
|
187
|
-
} else {
|
|
188
|
-
// 否则使用 GET
|
|
189
|
-
return apiClient.get(resolvedPath, bodyOrQuery as QueryParams, config);
|
|
190
|
-
}
|
|
191
|
-
},
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
/**
|
|
196
|
-
* 创建基于路由的客户端
|
|
197
|
-
*/
|
|
198
|
-
export function createRouteBasedClient<T extends Server>(
|
|
199
|
-
server: T,
|
|
200
|
-
config?: ApiClientConfig
|
|
201
|
-
): TypedApiClient<T> {
|
|
202
|
-
const apiClient = new VafastApiClient(config);
|
|
203
|
-
|
|
204
|
-
// 分析服务器路由,创建类型安全的客户端
|
|
205
|
-
return createTypedClientFromRoutes(server, apiClient);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* 从路由创建类型安全的客户端
|
|
210
|
-
*/
|
|
211
|
-
function createTypedClientFromRoutes<T extends Server>(
|
|
212
|
-
server: T,
|
|
213
|
-
apiClient: VafastApiClient
|
|
214
|
-
): TypedApiClient<T> {
|
|
215
|
-
// 这里可以根据实际的路由结构来生成客户端
|
|
216
|
-
// 由于 Vafast 的路由结构,我们需要动态分析
|
|
217
|
-
|
|
218
|
-
return new Proxy({} as TypedApiClient<T>, {
|
|
219
|
-
get(target, prop: string) {
|
|
220
|
-
// 返回一个可以处理动态路径的对象
|
|
221
|
-
return createDynamicPathHandler(apiClient, prop);
|
|
222
|
-
},
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
/**
|
|
227
|
-
* 创建动态路径处理器
|
|
228
|
-
*/
|
|
229
|
-
function createDynamicPathHandler(apiClient: VafastApiClient, basePath: string) {
|
|
230
|
-
return new Proxy({} as Record<string, unknown>, {
|
|
231
|
-
get(target, prop: string) {
|
|
232
|
-
const currentPath = normalizePath(basePath, prop);
|
|
233
|
-
|
|
234
|
-
// 如果是 HTTP 方法
|
|
235
|
-
if (isHttpMethod(prop)) {
|
|
236
|
-
return (bodyOrQuery?: RequestBody | QueryParams, config?: RequestConfig) => {
|
|
237
|
-
const method = prop.toUpperCase();
|
|
238
|
-
|
|
239
|
-
if (method === "GET" || method === "HEAD" || method === "OPTIONS") {
|
|
240
|
-
// 使用类型安全的方法调用
|
|
241
|
-
const clientMethod = apiClient[prop as keyof HttpMethodMap];
|
|
242
|
-
const queryParams = bodyOrQuery as QueryParams | undefined;
|
|
243
|
-
return clientMethod(basePath, queryParams, config);
|
|
244
|
-
} else {
|
|
245
|
-
const clientMethod = apiClient[prop as keyof HttpMethodMap];
|
|
246
|
-
const requestBody = bodyOrQuery as RequestBody | undefined;
|
|
247
|
-
return (clientMethod as any)(basePath, requestBody, config);
|
|
248
|
-
}
|
|
249
|
-
};
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
// 继续构建路径
|
|
253
|
-
return createDynamicPathHandler(apiClient, currentPath);
|
|
254
|
-
},
|
|
255
|
-
|
|
256
|
-
// 处理函数调用
|
|
257
|
-
apply(target, thisArg, args) {
|
|
258
|
-
const [params, bodyOrQuery, config] = args as [
|
|
259
|
-
PathParams,
|
|
260
|
-
RequestBody | QueryParams,
|
|
261
|
-
RequestConfig?
|
|
262
|
-
];
|
|
263
|
-
|
|
264
|
-
// 替换路径参数
|
|
265
|
-
const resolvedPath = replacePathParams(basePath, params || {});
|
|
266
|
-
|
|
267
|
-
// 使用改进的参数类型判断
|
|
268
|
-
if (bodyOrQuery && isRequestBody(bodyOrQuery) && !config) {
|
|
269
|
-
return apiClient.post(resolvedPath, bodyOrQuery);
|
|
270
|
-
} else {
|
|
271
|
-
return apiClient.get(resolvedPath, bodyOrQuery as QueryParams, config);
|
|
272
|
-
}
|
|
273
|
-
},
|
|
274
|
-
});
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
/**
|
|
278
|
-
* 创建简单的类型安全客户端
|
|
279
|
-
*/
|
|
280
|
-
export function createSimpleClient<T extends Server>(
|
|
281
|
-
server: T,
|
|
282
|
-
config?: ApiClientConfig
|
|
283
|
-
): TypedApiClient<T> {
|
|
284
|
-
const apiClient = new VafastApiClient(config);
|
|
285
|
-
|
|
286
|
-
return {
|
|
287
|
-
get: (path: string, query?: QueryParams, config?: RequestConfig) =>
|
|
288
|
-
(apiClient as any).get(path, query, config),
|
|
289
|
-
|
|
290
|
-
post: (path: string, body?: RequestBody, config?: RequestConfig) =>
|
|
291
|
-
(apiClient as any).post(path, body, config),
|
|
292
|
-
|
|
293
|
-
put: (path: string, body?: RequestBody, config?: RequestConfig) =>
|
|
294
|
-
(apiClient as any).put(path, body, config),
|
|
295
|
-
|
|
296
|
-
delete: (path: string, config?: RequestConfig) => (apiClient as any).delete(path, config),
|
|
297
|
-
|
|
298
|
-
patch: (path: string, body?: RequestBody, config?: RequestConfig) =>
|
|
299
|
-
(apiClient as any).patch(path, body, config),
|
|
300
|
-
|
|
301
|
-
head: (path: string, config?: RequestConfig) => (apiClient as any).head(path, config),
|
|
302
|
-
|
|
303
|
-
options: (path: string, config?: RequestConfig) => (apiClient as any).options(path, config),
|
|
304
|
-
} as TypedApiClient<T>;
|
|
305
|
-
}
|
package/src/utils/index.ts
DELETED
|
@@ -1,232 +0,0 @@
|
|
|
1
|
-
import type { QueryParams, PathParams, RequestBody, FileUpload, ApiFormData } from "../types";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* 构建查询字符串
|
|
5
|
-
*/
|
|
6
|
-
export function buildQueryString(params: QueryParams): string {
|
|
7
|
-
if (!params || Object.keys(params).length === 0) return "";
|
|
8
|
-
|
|
9
|
-
const searchParams = new URLSearchParams();
|
|
10
|
-
|
|
11
|
-
for (const [key, value] of Object.entries(params)) {
|
|
12
|
-
if (value !== undefined && value !== null) {
|
|
13
|
-
if (Array.isArray(value)) {
|
|
14
|
-
value.forEach((v) => searchParams.append(key, String(v)));
|
|
15
|
-
} else {
|
|
16
|
-
searchParams.append(key, String(value));
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const queryString = searchParams.toString();
|
|
22
|
-
return queryString ? `?${queryString}` : "";
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* 替换路径参数
|
|
27
|
-
*/
|
|
28
|
-
export function replacePathParams(path: string, params: PathParams): string {
|
|
29
|
-
let result = path;
|
|
30
|
-
|
|
31
|
-
for (const [key, value] of Object.entries(params)) {
|
|
32
|
-
result = result.replace(`:${key}`, String(value));
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return result;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* 检查是否为文件对象
|
|
40
|
-
*/
|
|
41
|
-
export function isFile(value: unknown): value is File | Blob {
|
|
42
|
-
return value instanceof File || value instanceof Blob;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* 检查是否为文件上传对象
|
|
47
|
-
*/
|
|
48
|
-
export function isFileUpload(value: unknown): value is FileUpload {
|
|
49
|
-
if (value === null || value === undefined) return false;
|
|
50
|
-
return (
|
|
51
|
-
value && typeof value === "object" && "file" in value && isFile((value as FileUpload).file)
|
|
52
|
-
);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* 检查对象是否包含文件
|
|
57
|
-
*/
|
|
58
|
-
export function hasFiles(obj: unknown): boolean {
|
|
59
|
-
if (!obj || typeof obj !== "object") return false;
|
|
60
|
-
|
|
61
|
-
for (const value of Object.values(obj as Record<string, unknown>)) {
|
|
62
|
-
if (isFile(value) || isFileUpload(value)) return true;
|
|
63
|
-
if (Array.isArray(value) && value.some(isFile)) return true;
|
|
64
|
-
if (typeof value === "object" && hasFiles(value)) return true;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return false;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* 创建 FormData
|
|
72
|
-
*/
|
|
73
|
-
export function createFormData(data: ApiFormData): globalThis.FormData {
|
|
74
|
-
const formData = new globalThis.FormData();
|
|
75
|
-
|
|
76
|
-
for (const [key, value] of Object.entries(data)) {
|
|
77
|
-
if (value === undefined || value === null) continue;
|
|
78
|
-
|
|
79
|
-
if (isFileUpload(value)) {
|
|
80
|
-
formData.append(key, value.file, value.filename);
|
|
81
|
-
} else if (isFile(value)) {
|
|
82
|
-
formData.append(key, value);
|
|
83
|
-
} else if (Array.isArray(value)) {
|
|
84
|
-
value.forEach((v) => {
|
|
85
|
-
if (isFileUpload(v)) {
|
|
86
|
-
formData.append(key, v.file, v.filename);
|
|
87
|
-
} else if (isFile(v)) {
|
|
88
|
-
formData.append(key, v);
|
|
89
|
-
} else {
|
|
90
|
-
formData.append(key, String(v));
|
|
91
|
-
}
|
|
92
|
-
});
|
|
93
|
-
} else {
|
|
94
|
-
formData.append(key, String(value));
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return formData;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* 深度合并对象
|
|
103
|
-
*/
|
|
104
|
-
export function deepMerge<T extends Record<string, any>>(target: T, source: Partial<T>): T {
|
|
105
|
-
const result = { ...target } as T;
|
|
106
|
-
|
|
107
|
-
for (const [key, value] of Object.entries(source)) {
|
|
108
|
-
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
109
|
-
const targetValue = result[key as keyof T];
|
|
110
|
-
if (targetValue && typeof targetValue === "object" && !Array.isArray(targetValue)) {
|
|
111
|
-
result[key as keyof T] = deepMerge(targetValue, value) as T[keyof T];
|
|
112
|
-
} else {
|
|
113
|
-
result[key as keyof T] = value as T[keyof T];
|
|
114
|
-
}
|
|
115
|
-
} else {
|
|
116
|
-
result[key as keyof T] = value as T[keyof T];
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
return result;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* 延迟函数
|
|
125
|
-
*/
|
|
126
|
-
export function delay(ms: number): Promise<void> {
|
|
127
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* 指数退避重试延迟
|
|
132
|
-
*/
|
|
133
|
-
export function exponentialBackoff(attempt: number, baseDelay: number, maxDelay: number): number {
|
|
134
|
-
const delay = Math.min(baseDelay * Math.pow(2, attempt), maxDelay);
|
|
135
|
-
return delay + Math.random() * 1000; // 添加随机抖动
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* 验证状态码
|
|
140
|
-
*/
|
|
141
|
-
export function validateStatus(status: number): boolean {
|
|
142
|
-
return status >= 200 && status < 300;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* 解析响应内容类型
|
|
147
|
-
*/
|
|
148
|
-
export function parseContentType(contentType: string | null): string {
|
|
149
|
-
if (!contentType) return "text/plain";
|
|
150
|
-
return contentType.split(";")[0].trim();
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* 解析响应数据
|
|
155
|
-
*/
|
|
156
|
-
export async function parseResponse(response: Response): Promise<any> {
|
|
157
|
-
const contentType = parseContentType(response.headers.get("content-type"));
|
|
158
|
-
|
|
159
|
-
switch (contentType) {
|
|
160
|
-
case "application/json":
|
|
161
|
-
return response.json();
|
|
162
|
-
case "application/octet-stream":
|
|
163
|
-
return response.arrayBuffer();
|
|
164
|
-
case "multipart/form-data":
|
|
165
|
-
const formData = await response.formData();
|
|
166
|
-
const data: Record<string, any> = {};
|
|
167
|
-
formData.forEach((value, key) => {
|
|
168
|
-
data[key] = value;
|
|
169
|
-
});
|
|
170
|
-
return data;
|
|
171
|
-
case "text/event-stream":
|
|
172
|
-
return response.body;
|
|
173
|
-
default:
|
|
174
|
-
return response.text();
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* 创建错误对象
|
|
180
|
-
*/
|
|
181
|
-
export function createError(status: number, message: string, data?: unknown): Error {
|
|
182
|
-
const error = new Error(message) as Error & { status: number; data?: unknown; name: string };
|
|
183
|
-
error.status = status;
|
|
184
|
-
error.data = data;
|
|
185
|
-
error.name = "ApiError";
|
|
186
|
-
return error;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* 克隆请求对象
|
|
191
|
-
*/
|
|
192
|
-
export function cloneRequest(request: Request): Request {
|
|
193
|
-
return new Request(request.url, {
|
|
194
|
-
method: request.method,
|
|
195
|
-
headers: request.headers,
|
|
196
|
-
body: request.body,
|
|
197
|
-
mode: request.mode,
|
|
198
|
-
credentials: request.credentials,
|
|
199
|
-
cache: request.cache,
|
|
200
|
-
redirect: request.redirect,
|
|
201
|
-
referrer: request.referrer,
|
|
202
|
-
referrerPolicy: request.referrerPolicy,
|
|
203
|
-
integrity: request.integrity,
|
|
204
|
-
keepalive: request.keepalive,
|
|
205
|
-
signal: request.signal,
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* 检查是否为可重试的错误
|
|
211
|
-
*/
|
|
212
|
-
export function isRetryableError(error: Error, status?: number): boolean {
|
|
213
|
-
if (status) {
|
|
214
|
-
return [408, 429, 500, 502, 503, 504].includes(status);
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// 网络错误通常是可重试的
|
|
218
|
-
if (error.name === "TypeError" && error.message.includes("fetch")) {
|
|
219
|
-
return true;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// 检查其他网络相关错误
|
|
223
|
-
if (
|
|
224
|
-
error.message.includes("fetch") ||
|
|
225
|
-
error.message.includes("network") ||
|
|
226
|
-
error.message.includes("connection")
|
|
227
|
-
) {
|
|
228
|
-
return true;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
return false;
|
|
232
|
-
}
|