@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.
- package/README.md +282 -0
- package/build.ts +37 -0
- package/bun.lock +569 -0
- package/example/index.ts +255 -0
- package/package.json +53 -0
- package/src/core/api-client.ts +389 -0
- package/src/core/typed-client.ts +305 -0
- package/src/index.ts +73 -0
- package/src/types/index.ts +133 -0
- package/src/utils/index.ts +232 -0
- package/src/websocket/websocket-client.ts +347 -0
- package/test/api-client.test.ts +262 -0
- package/test/basic.test.ts +55 -0
- package/test/typed-client.test.ts +304 -0
- package/test/utils.test.ts +363 -0
- package/test/websocket.test.ts +434 -0
- package/tsconfig.dts.json +20 -0
- package/tsconfig.json +20 -0
- package/tsup.config.ts +23 -0
package/README.md
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
# Vafast API Client
|
|
2
|
+
|
|
3
|
+
一个专门为 [Vafast](https://github.com/vafastjs/vafast) 框架打造的现代化、类型安全的 API 客户端插件。
|
|
4
|
+
|
|
5
|
+
## ✨ 特性
|
|
6
|
+
|
|
7
|
+
- 🚀 **专为 Vafast 设计**: 完全兼容 Vafast 框架架构
|
|
8
|
+
- 🔒 **类型安全**: 完整的 TypeScript 类型支持
|
|
9
|
+
- 🎯 **智能路由**: 自动推断路由类型和方法
|
|
10
|
+
- 🔄 **自动重试**: 内置指数退避重试机制
|
|
11
|
+
- 📡 **WebSocket 支持**: 完整的 WebSocket 客户端
|
|
12
|
+
- 🧩 **中间件系统**: 灵活的请求/响应处理
|
|
13
|
+
- 🎛️ **拦截器**: 强大的请求/响应拦截能力
|
|
14
|
+
- 📁 **文件上传**: 支持文件和 FormData 上传
|
|
15
|
+
- 💾 **缓存系统**: 智能的响应缓存机制
|
|
16
|
+
- 📊 **监控统计**: 详细的请求统计和性能监控
|
|
17
|
+
|
|
18
|
+
## 📦 安装
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
bun add @vafast/api-client
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## 🚀 快速开始
|
|
25
|
+
|
|
26
|
+
### 基础用法
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { VafastApiClient } from '@vafast/api-client'
|
|
30
|
+
|
|
31
|
+
// 创建客户端
|
|
32
|
+
const client = new VafastApiClient({
|
|
33
|
+
baseURL: 'https://api.example.com',
|
|
34
|
+
timeout: 10000,
|
|
35
|
+
retries: 3
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
// 发送请求
|
|
39
|
+
const response = await client.get('/users', { page: 1, limit: 10 })
|
|
40
|
+
if (response.error) {
|
|
41
|
+
console.error('Error:', response.error)
|
|
42
|
+
} else {
|
|
43
|
+
console.log('Users:', response.data)
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### 类型安全客户端
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
import { createTypedClient } from '@vafast/api-client'
|
|
51
|
+
import type { Server } from 'vafast'
|
|
52
|
+
|
|
53
|
+
// 从 Vafast 服务器创建类型安全客户端
|
|
54
|
+
const typedClient = createTypedClient<Server>(server, {
|
|
55
|
+
baseURL: 'https://api.example.com'
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
// 现在有完整的类型检查
|
|
59
|
+
const users = await typedClient.get('/users', { page: 1, limit: 10 })
|
|
60
|
+
const user = await typedClient.post('/users', { name: 'John', email: 'john@example.com' })
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### WebSocket 客户端
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
import { createWebSocketClient } from '@vafast/api-client'
|
|
67
|
+
|
|
68
|
+
const wsClient = createWebSocketClient('wss://ws.example.com', {
|
|
69
|
+
autoReconnect: true,
|
|
70
|
+
maxReconnectAttempts: 5
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
await wsClient.connect()
|
|
74
|
+
|
|
75
|
+
wsClient.on('message', (data) => {
|
|
76
|
+
console.log('Received:', data)
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
wsClient.send({ type: 'chat', message: 'Hello!' })
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## 📚 API 参考
|
|
83
|
+
|
|
84
|
+
### VafastApiClient
|
|
85
|
+
|
|
86
|
+
主要的 API 客户端类。
|
|
87
|
+
|
|
88
|
+
#### 构造函数
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
new VafastApiClient(config?: ApiClientConfig)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
#### 配置选项
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
interface ApiClientConfig {
|
|
98
|
+
baseURL?: string // 基础 URL
|
|
99
|
+
defaultHeaders?: Record<string, string> // 默认请求头
|
|
100
|
+
timeout?: number // 请求超时时间(毫秒)
|
|
101
|
+
retries?: number // 重试次数
|
|
102
|
+
retryDelay?: number // 重试延迟(毫秒)
|
|
103
|
+
validateStatus?: (status: number) => boolean // 状态码验证函数
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
#### 方法
|
|
108
|
+
|
|
109
|
+
- `get(path, query?, config?)` - GET 请求
|
|
110
|
+
- `post(path, body?, config?)` - POST 请求
|
|
111
|
+
- `put(path, body?, config?)` - PUT 请求
|
|
112
|
+
- `delete(path, config?)` - DELETE 请求
|
|
113
|
+
- `patch(path, body?, config?)` - PATCH 请求
|
|
114
|
+
- `head(path, config?)` - HEAD 请求
|
|
115
|
+
- `options(path, config?)` - OPTIONS 请求
|
|
116
|
+
|
|
117
|
+
### 中间件系统
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
client.addMiddleware({
|
|
121
|
+
name: 'logging',
|
|
122
|
+
onRequest: async (request, config) => {
|
|
123
|
+
console.log(`[${new Date().toISOString()}] ${request.method} ${request.url}`)
|
|
124
|
+
return request
|
|
125
|
+
},
|
|
126
|
+
onResponse: async (response, config) => {
|
|
127
|
+
console.log(`Response: ${response.status}`)
|
|
128
|
+
return response
|
|
129
|
+
},
|
|
130
|
+
onError: async (error, config) => {
|
|
131
|
+
console.error('Error:', error.message)
|
|
132
|
+
}
|
|
133
|
+
})
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### 拦截器系统
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
client.addInterceptor({
|
|
140
|
+
request: async (config) => {
|
|
141
|
+
// 添加认证头
|
|
142
|
+
config.headers = { ...config.headers, 'Authorization': 'Bearer token' }
|
|
143
|
+
return config
|
|
144
|
+
},
|
|
145
|
+
response: async (response) => {
|
|
146
|
+
// 处理响应
|
|
147
|
+
return response
|
|
148
|
+
},
|
|
149
|
+
error: async (error) => {
|
|
150
|
+
// 处理错误
|
|
151
|
+
return error
|
|
152
|
+
}
|
|
153
|
+
})
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### WebSocket 客户端
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
const wsClient = createWebSocketClient(url, options)
|
|
160
|
+
|
|
161
|
+
// 连接
|
|
162
|
+
await wsClient.connect()
|
|
163
|
+
|
|
164
|
+
// 监听事件
|
|
165
|
+
wsClient.on('message', (data) => console.log(data))
|
|
166
|
+
wsClient.on('open', () => console.log('Connected'))
|
|
167
|
+
wsClient.on('close', () => console.log('Disconnected'))
|
|
168
|
+
|
|
169
|
+
// 发送数据
|
|
170
|
+
wsClient.send({ type: 'chat', message: 'Hello' })
|
|
171
|
+
|
|
172
|
+
// 断开连接
|
|
173
|
+
wsClient.disconnect()
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## 🔧 高级用法
|
|
177
|
+
|
|
178
|
+
### 文件上传
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
// 单个文件
|
|
182
|
+
const response = await client.post('/upload', {
|
|
183
|
+
file: fileInput.files[0],
|
|
184
|
+
description: 'User avatar'
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
// 多个文件
|
|
188
|
+
const response = await client.post('/upload', {
|
|
189
|
+
files: [file1, file2, file3],
|
|
190
|
+
category: 'images'
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
// 混合数据
|
|
194
|
+
const response = await client.post('/upload', {
|
|
195
|
+
file: fileInput.files[0],
|
|
196
|
+
metadata: {
|
|
197
|
+
name: 'avatar.jpg',
|
|
198
|
+
size: fileInput.files[0].size,
|
|
199
|
+
type: fileInput.files[0].type
|
|
200
|
+
}
|
|
201
|
+
})
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### 路径参数
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
// 使用工具函数替换路径参数
|
|
208
|
+
import { replacePathParams } from '@vafast/api-client'
|
|
209
|
+
|
|
210
|
+
const path = '/users/:id/posts/:postId'
|
|
211
|
+
const params = { id: '123', postId: '456' }
|
|
212
|
+
const resolvedPath = replacePathParams(path, params)
|
|
213
|
+
// 结果: '/users/123/posts/456'
|
|
214
|
+
|
|
215
|
+
const response = await client.get(resolvedPath)
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### 查询参数构建
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
import { buildQueryString } from '@vafast/api-client'
|
|
222
|
+
|
|
223
|
+
const query = { page: 1, limit: 10, search: 'john' }
|
|
224
|
+
const queryString = buildQueryString(query)
|
|
225
|
+
// 结果: '?page=1&limit=10&search=john'
|
|
226
|
+
|
|
227
|
+
const response = await client.get(`/users${queryString}`)
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### 缓存配置
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
client.setCacheConfig({
|
|
234
|
+
enabled: true,
|
|
235
|
+
ttl: 300000, // 5分钟
|
|
236
|
+
maxSize: 100,
|
|
237
|
+
strategy: 'memory'
|
|
238
|
+
})
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### 重试配置
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
client.setRetryConfig({
|
|
245
|
+
enabled: true,
|
|
246
|
+
maxRetries: 5,
|
|
247
|
+
retryDelay: 1000,
|
|
248
|
+
backoffMultiplier: 2,
|
|
249
|
+
retryableStatuses: [408, 429, 500, 502, 503, 504]
|
|
250
|
+
})
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## 🧪 测试
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
bun test
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## 📖 示例
|
|
260
|
+
|
|
261
|
+
查看 `example/` 目录中的完整示例:
|
|
262
|
+
|
|
263
|
+
- `index.ts` - 主要使用示例
|
|
264
|
+
- 基础 HTTP 请求
|
|
265
|
+
- 类型安全客户端
|
|
266
|
+
- WebSocket 客户端
|
|
267
|
+
- 中间件和拦截器
|
|
268
|
+
- 文件上传
|
|
269
|
+
|
|
270
|
+
## 🤝 贡献
|
|
271
|
+
|
|
272
|
+
欢迎提交 Issue 和 Pull Request!
|
|
273
|
+
|
|
274
|
+
## 📄 许可证
|
|
275
|
+
|
|
276
|
+
MIT License
|
|
277
|
+
|
|
278
|
+
## 🔗 相关链接
|
|
279
|
+
|
|
280
|
+
- [Vafast 官方文档](https://vafast.dev)
|
|
281
|
+
- [GitHub 仓库](https://github.com/vafastjs/vafast-api-client)
|
|
282
|
+
- [问题反馈](https://github.com/vafastjs/vafast-api-client/issues)
|
package/build.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { $ } from "bun";
|
|
2
|
+
import { build, type Options } from "tsup";
|
|
3
|
+
|
|
4
|
+
await $`rm -rf dist`;
|
|
5
|
+
|
|
6
|
+
const tsupConfig: Options = {
|
|
7
|
+
entry: ["src/**/*.ts"],
|
|
8
|
+
splitting: false,
|
|
9
|
+
sourcemap: false,
|
|
10
|
+
clean: true,
|
|
11
|
+
bundle: true,
|
|
12
|
+
} satisfies Options;
|
|
13
|
+
|
|
14
|
+
await Promise.all([
|
|
15
|
+
// ? tsup esm
|
|
16
|
+
build({
|
|
17
|
+
outDir: "dist",
|
|
18
|
+
format: "esm",
|
|
19
|
+
target: "node20",
|
|
20
|
+
cjsInterop: false,
|
|
21
|
+
...tsupConfig,
|
|
22
|
+
}),
|
|
23
|
+
// ? tsup cjs
|
|
24
|
+
build({
|
|
25
|
+
outDir: "dist/cjs",
|
|
26
|
+
format: "cjs",
|
|
27
|
+
target: "node20",
|
|
28
|
+
// dts: true,
|
|
29
|
+
...tsupConfig,
|
|
30
|
+
}),
|
|
31
|
+
]);
|
|
32
|
+
|
|
33
|
+
await $`tsc --project tsconfig.dts.json`;
|
|
34
|
+
|
|
35
|
+
await Promise.all([$`cp dist/*.d.ts dist/cjs`]);
|
|
36
|
+
|
|
37
|
+
process.exit();
|