@vafast/api-client 0.1.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.
@@ -0,0 +1,305 @@
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/index.ts ADDED
@@ -0,0 +1,73 @@
1
+ // 核心 API 客户端
2
+ export { VafastApiClient } from './core/api-client'
3
+ export {
4
+ createTypedClient,
5
+ createRouteBasedClient,
6
+ createSimpleClient,
7
+ type TypedApiClient
8
+ } from './core/typed-client'
9
+
10
+ // WebSocket 客户端
11
+ export {
12
+ VafastWebSocketClient,
13
+ createWebSocketClient,
14
+ createTypedWebSocketClient
15
+ } from './websocket/websocket-client'
16
+
17
+ // 类型定义
18
+ export type {
19
+ // 基础类型
20
+ HTTPMethod,
21
+ RequestConfig,
22
+ ApiResponse,
23
+ QueryParams,
24
+ PathParams,
25
+ RequestBody,
26
+ ApiClientConfig,
27
+
28
+ // 类型推断
29
+ InferRouteHandler,
30
+ InferServer,
31
+ RoutePath,
32
+ RouteMethod,
33
+ RouteHandlerType,
34
+
35
+ // WebSocket 类型
36
+ WebSocketEvent,
37
+ WebSocketClient,
38
+
39
+ // 文件类型
40
+ FileUpload,
41
+ ApiFormData,
42
+
43
+ // 中间件和拦截器
44
+ ApiMiddleware,
45
+ Interceptor,
46
+
47
+ // 配置类型
48
+ CacheConfig,
49
+ RetryConfig,
50
+ LogConfig
51
+ } from './types'
52
+
53
+ // 工具函数
54
+ export {
55
+ buildQueryString,
56
+ replacePathParams,
57
+ isFile,
58
+ isFileUpload,
59
+ hasFiles,
60
+ createFormData,
61
+ deepMerge,
62
+ delay,
63
+ exponentialBackoff,
64
+ validateStatus,
65
+ parseResponse,
66
+ createError,
67
+ cloneRequest,
68
+ isRetryableError
69
+ } from './utils'
70
+
71
+ // 默认导出
72
+ import { VafastApiClient } from './core/api-client'
73
+ export default VafastApiClient
@@ -0,0 +1,133 @@
1
+ // 基础类型定义
2
+ export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS'
3
+
4
+ // 简化的 vafast 类型定义
5
+ export interface Server {
6
+ routes: Record<string, Route>
7
+ }
8
+
9
+ export interface Route {
10
+ method: string
11
+ path: string
12
+ handler: RouteHandler
13
+ }
14
+
15
+ export interface RouteHandler {
16
+ (req: unknown, res: unknown): unknown
17
+ }
18
+
19
+ /**
20
+ * 请求配置
21
+ */
22
+ export interface RequestConfig {
23
+ headers?: Record<string, string>
24
+ timeout?: number
25
+ retries?: number
26
+ retryDelay?: number
27
+ body?: RequestBody
28
+ }
29
+
30
+ // 响应类型
31
+ export interface ApiResponse<T = unknown> {
32
+ data: T | null
33
+ error: Error | null
34
+ status: number
35
+ headers: Headers
36
+ response: Response
37
+ }
38
+
39
+ // 查询参数类型
40
+ export type QueryParams = Record<string, string | number | boolean | undefined | null>
41
+
42
+ // 路径参数类型
43
+ export type PathParams = Record<string, string | number>
44
+
45
+ // 请求体类型
46
+ export type RequestBody = unknown
47
+
48
+ // API 客户端配置
49
+ export interface ApiClientConfig extends RequestConfig {
50
+ baseURL?: string
51
+ defaultHeaders?: Record<string, string>
52
+ timeout?: number
53
+ retries?: number
54
+ retryDelay?: number
55
+ validateStatus?: (status: number) => boolean
56
+ }
57
+
58
+ // 类型推断类型
59
+ export type InferRouteHandler<T> = T extends RouteHandler ? T : never
60
+ export type InferServer<T> = T extends Server ? T : never
61
+ export type RoutePath<T> = T extends Route ? T['path'] : never
62
+ export type RouteMethod<T> = T extends Route ? T['method'] : never
63
+ export type RouteHandlerType<T> = T extends Route ? T['handler'] : never
64
+
65
+ // WebSocket 事件类型
66
+ export interface WebSocketEvent<T = unknown> {
67
+ type: string
68
+ data: T
69
+ timestamp: number
70
+ }
71
+
72
+ // WebSocket 客户端类型
73
+ export interface WebSocketClient {
74
+ connect(): Promise<void>
75
+ disconnect(): void
76
+ send(data: unknown): void
77
+ on(event: string, callback: (data: unknown) => void): void
78
+ off(event: string, callback: (data: unknown) => void): void
79
+ isConnected(): boolean
80
+ }
81
+
82
+ // 文件上传类型
83
+ export interface FileUpload {
84
+ file: File | Blob
85
+ filename?: string
86
+ contentType?: string
87
+ }
88
+
89
+ // 表单数据类型(重命名避免与全局 FormData 冲突)
90
+ export interface ApiFormData {
91
+ [key: string]: string | number | boolean | File | Blob | FileUpload | ApiFormData | (string | number | boolean | File | Blob | FileUpload | ApiFormData)[] | unknown
92
+ }
93
+
94
+ // 中间件类型
95
+ export interface ApiMiddleware {
96
+ name: string
97
+ onRequest?: (request: Request, config: RequestConfig) => Request | Promise<Request>
98
+ onResponse?: (response: Response, config: RequestConfig) => Response | Promise<Response>
99
+ onError?: (error: Error, config: RequestConfig) => Error | Promise<Error>
100
+ }
101
+
102
+ // 缓存配置类型
103
+ export interface CacheConfig {
104
+ enabled: boolean
105
+ ttl: number
106
+ maxSize: number
107
+ strategy: 'memory' | 'localStorage' | 'sessionStorage'
108
+ }
109
+
110
+ // 重试配置类型
111
+ export interface RetryConfig {
112
+ enabled: boolean
113
+ maxRetries: number
114
+ retryDelay: number
115
+ backoffMultiplier: number
116
+ retryableStatuses: number[]
117
+ }
118
+
119
+ // 拦截器类型
120
+ export interface Interceptor {
121
+ request?: (config: RequestConfig) => RequestConfig | Promise<RequestConfig>
122
+ response?: (response: Response) => Response | Promise<Response>
123
+ error?: (error: Error) => Error | Promise<Error>
124
+ }
125
+
126
+ // 日志配置类型
127
+ export interface LogConfig {
128
+ enabled: boolean
129
+ level: 'debug' | 'info' | 'warn' | 'error'
130
+ format: 'json' | 'text'
131
+ includeHeaders: boolean
132
+ includeBody: boolean
133
+ }