@vafast/api-client 0.1.1 → 0.1.2

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 CHANGED
@@ -1,282 +1,289 @@
1
- # Vafast API Client
1
+ # @vafast/api-client
2
2
 
3
- 一个专门为 [Vafast](https://github.com/vafastjs/vafast) 框架打造的现代化、类型安全的 API 客户端插件。
3
+ 🚀 类型安全的 Eden 风格 API 客户端,专为 [Vafast](https://github.com/user/vafast) 框架设计。
4
4
 
5
5
  ## ✨ 特性
6
6
 
7
- - 🚀 **专为 Vafast 设计**: 完全兼容 Vafast 框架架构
8
- - 🔒 **类型安全**: 完整的 TypeScript 类型支持
9
- - 🎯 **智能路由**: 自动推断路由类型和方法
10
- - 🔄 **自动重试**: 内置指数退避重试机制
11
- - 📡 **WebSocket 支持**: 完整的 WebSocket 客户端
12
- - 🧩 **中间件系统**: 灵活的请求/响应处理
13
- - 🎛️ **拦截器**: 强大的请求/响应拦截能力
14
- - 📁 **文件上传**: 支持文件和 FormData 上传
15
- - 💾 **缓存系统**: 智能的响应缓存机制
16
- - 📊 **监控统计**: 详细的请求统计和性能监控
7
+ - 🔒 **完整类型推断** - 从路由定义自动推断 API 类型,无需手动定义接口
8
+ - 🎯 **无需 `as const`** - 使用 `route()` 函数,类型自动保留
9
+ - 🌊 **SSE 流式响应** - 内置 Server-Sent Events 支持,包含自动重连
10
+ - ⏹️ **请求取消** - 支持 AbortController 取消进行中的请求
11
+ - 🔗 **链式调用** - 优雅的 `api.users({ id }).posts.get()` 语法
12
+ - 📦 **轻量** - 仅 8KB (gzip)
17
13
 
18
14
  ## 📦 安装
19
15
 
20
16
  ```bash
17
+ npm install @vafast/api-client
18
+ # 或
21
19
  bun add @vafast/api-client
22
20
  ```
23
21
 
24
22
  ## 🚀 快速开始
25
23
 
26
- ### 基础用法
24
+ ### 1. 定义服务端路由
27
25
 
28
26
  ```typescript
29
- import { VafastApiClient } from '@vafast/api-client'
27
+ // server.ts
28
+ import { defineRoutes, route, createHandler, createSSEHandler, Type } from 'vafast'
29
+
30
+ export const routes = defineRoutes([
31
+ // ✨ 使用 route() 函数,无需 as const
32
+ route('GET', '/users', createHandler(
33
+ { query: Type.Object({ page: Type.Optional(Type.Number()) }) },
34
+ async ({ query }) => ({ users: [], total: 0, page: query.page ?? 1 })
35
+ )),
36
+
37
+ route('POST', '/users', createHandler(
38
+ { body: Type.Object({ name: Type.String(), email: Type.String() }) },
39
+ async ({ body }) => ({ id: crypto.randomUUID(), ...body })
40
+ )),
41
+
42
+ route('GET', '/users/:id', createHandler(
43
+ { params: Type.Object({ id: Type.String() }) },
44
+ async ({ params }) => ({ id: params.id, name: 'User' })
45
+ )),
46
+
47
+ // 🌊 SSE 流式响应
48
+ route('GET', '/chat/stream', createSSEHandler(
49
+ { query: Type.Object({ prompt: Type.String() }) },
50
+ async function* ({ query }) {
51
+ yield { event: 'start', data: { message: 'Starting...' } }
52
+
53
+ for (const word of query.prompt.split(' ')) {
54
+ yield { data: { text: word } }
55
+ await new Promise(r => setTimeout(r, 100))
56
+ }
57
+
58
+ yield { event: 'end', data: { message: 'Done!' } }
59
+ }
60
+ ))
61
+ ])
62
+
63
+ // 导出类型供客户端使用
64
+ export type AppRoutes = typeof routes
65
+ ```
66
+
67
+ ### 2. 创建类型安全客户端
68
+
69
+ ```typescript
70
+ // client.ts
71
+ import { eden, InferEden } from '@vafast/api-client'
72
+ import type { AppRoutes } from './server'
73
+
74
+ // 自动推断 API 类型
75
+ type Api = InferEden<AppRoutes>
30
76
 
31
77
  // 创建客户端
32
- const client = new VafastApiClient({
33
- baseURL: 'https://api.example.com',
34
- timeout: 10000,
35
- retries: 3
78
+ const api = eden<Api>('http://localhost:3000', {
79
+ headers: { 'Authorization': 'Bearer token' },
80
+ timeout: 5000
36
81
  })
37
82
 
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)
83
+ // ✅ 完全类型安全的调用
84
+ async function main() {
85
+ // GET /users?page=1
86
+ const { data: users } = await api.users.get({ page: 1 })
87
+ console.log(users?.total) // ✅ 类型推断
88
+
89
+ // POST /users
90
+ const { data: newUser } = await api.users.post({
91
+ name: 'John',
92
+ email: 'john@example.com'
93
+ })
94
+ console.log(newUser?.id) // ✅ 类型推断
95
+
96
+ // GET /users/:id
97
+ const { data: user } = await api.users({ id: '123' }).get()
98
+ console.log(user?.name) // ✅ 类型推断
44
99
  }
45
100
  ```
46
101
 
47
- ### 类型安全客户端
102
+ ## 📖 API 文档
48
103
 
49
- ```typescript
50
- import { createTypedClient } from '@vafast/api-client'
51
- import type { Server } from 'vafast'
104
+ ### `eden<T>(baseURL, config?)`
52
105
 
53
- // Vafast 服务器创建类型安全客户端
54
- const typedClient = createTypedClient<Server>(server, {
55
- baseURL: 'https://api.example.com'
56
- })
106
+ 创建 Eden 风格的 API 客户端。
57
107
 
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' })
108
+ ```typescript
109
+ const api = eden<Api>('http://localhost:3000', {
110
+ // 默认请求头
111
+ headers: { 'Authorization': 'Bearer token' },
112
+
113
+ // 全局超时(毫秒)
114
+ timeout: 5000,
115
+
116
+ // 请求拦截器
117
+ onRequest: (request) => {
118
+ console.log('Request:', request.url)
119
+ return request
120
+ },
121
+
122
+ // 响应拦截器
123
+ onResponse: (response) => {
124
+ console.log('Response:', response.status)
125
+ return response
126
+ },
127
+
128
+ // 错误处理
129
+ onError: (error) => {
130
+ console.error('Error:', error.message)
131
+ }
132
+ })
61
133
  ```
62
134
 
63
- ### WebSocket 客户端
135
+ ### HTTP 方法
64
136
 
65
137
  ```typescript
66
- import { createWebSocketClient } from '@vafast/api-client'
138
+ // GET 请求(带 query 参数)
139
+ api.users.get({ page: 1, limit: 10 })
67
140
 
68
- const wsClient = createWebSocketClient('wss://ws.example.com', {
69
- autoReconnect: true,
70
- maxReconnectAttempts: 5
71
- })
141
+ // POST 请求(带 body)
142
+ api.users.post({ name: 'John', email: 'john@example.com' })
72
143
 
73
- await wsClient.connect()
144
+ // PUT 请求
145
+ api.users({ id: '123' }).put({ name: 'Jane' })
74
146
 
75
- wsClient.on('message', (data) => {
76
- console.log('Received:', data)
77
- })
147
+ // DELETE 请求
148
+ api.users({ id: '123' }).delete()
78
149
 
79
- wsClient.send({ type: 'chat', message: 'Hello!' })
150
+ // PATCH 请求
151
+ api.users({ id: '123' }).patch({ name: 'Updated' })
80
152
  ```
81
153
 
82
- ## 📚 API 参考
83
-
84
- ### VafastApiClient
85
-
86
- 主要的 API 客户端类。
87
-
88
- #### 构造函数
154
+ ### 路径参数
89
155
 
90
156
  ```typescript
91
- new VafastApiClient(config?: ApiClientConfig)
157
+ // 使用函数调用传递参数
158
+ api.users({ id: '123' }).get() // GET /users/123
159
+ api.users({ id: '123' }).posts.get() // GET /users/123/posts
160
+ api.users({ id: '123' }).posts({ postId: '456' }).get() // GET /users/123/posts/456
92
161
  ```
93
162
 
94
- #### 配置选项
163
+ ### 请求取消
95
164
 
96
165
  ```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
- #### 方法
166
+ const controller = new AbortController()
108
167
 
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 请求
168
+ // 发起请求
169
+ const promise = api.users.get({ page: 1 }, { signal: controller.signal })
116
170
 
117
- ### 中间件系统
171
+ // 取消请求
172
+ controller.abort()
118
173
 
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
- })
174
+ const result = await promise
175
+ if (result.error) {
176
+ console.log('请求已取消')
177
+ }
134
178
  ```
135
179
 
136
- ### 拦截器系统
180
+ ### 单次请求配置
137
181
 
138
182
  ```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
- }
183
+ // 覆盖全局配置
184
+ const result = await api.users.get({ page: 1 }, {
185
+ headers: { 'X-Custom-Header': 'value' },
186
+ timeout: 10000,
187
+ signal: abortController.signal
153
188
  })
154
189
  ```
155
190
 
156
- ### WebSocket 客户端
191
+ ## 🌊 SSE 流式响应
192
+
193
+ ### 基本用法
157
194
 
158
195
  ```typescript
159
- const wsClient = createWebSocketClient(url, options)
196
+ const subscription = api.chat.stream.subscribe(
197
+ { prompt: 'Hello AI!' }, // query 参数
198
+ {
199
+ onOpen: () => console.log('连接已建立'),
200
+ onMessage: (data) => console.log('收到:', data),
201
+ onError: (err) => console.error('错误:', err),
202
+ onClose: () => console.log('连接已关闭'),
203
+ onReconnect: (attempt, max) => console.log(`重连中 ${attempt}/${max}`),
204
+ onMaxReconnects: () => console.log('达到最大重连次数')
205
+ },
206
+ {
207
+ reconnectInterval: 3000, // 重连间隔(毫秒)
208
+ maxReconnects: 5 // 最大重连次数
209
+ }
210
+ )
160
211
 
161
- // 连接
162
- await wsClient.connect()
212
+ // 取消订阅
213
+ subscription.unsubscribe()
214
+ ```
163
215
 
164
- // 监听事件
165
- wsClient.on('message', (data) => console.log(data))
166
- wsClient.on('open', () => console.log('Connected'))
167
- wsClient.on('close', () => console.log('Disconnected'))
216
+ ### SSE 特性
168
217
 
169
- // 发送数据
170
- wsClient.send({ type: 'chat', message: 'Hello' })
218
+ - ✅ **自动重连** - 网络断开后自动重连
219
+ - **断点续传** - 使用 `Last-Event-ID` 从断点继续
220
+ - ✅ **可配置重连策略** - 自定义重连间隔和最大次数
221
+ - ✅ **事件类型支持** - 支持自定义事件名称
171
222
 
172
- // 断开连接
173
- wsClient.disconnect()
174
- ```
223
+ ## 🔧 类型定义
175
224
 
176
- ## 🔧 高级用法
225
+ ### `InferEden<T>`
177
226
 
178
- ### 文件上传
227
+ vafast 路由数组推断 API 契约类型。
179
228
 
180
229
  ```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
- })
230
+ import { InferEden } from '@vafast/api-client'
192
231
 
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
- })
232
+ const routes = defineRoutes([...])
233
+ type Api = InferEden<typeof routes>
202
234
  ```
203
235
 
204
- ### 路径参数
236
+ ### `EdenClient<T>`
205
237
 
206
- ```typescript
207
- // 使用工具函数替换路径参数
208
- import { replacePathParams } from '@vafast/api-client'
238
+ Eden 客户端类型。
209
239
 
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'
240
+ ```typescript
241
+ import { EdenClient } from '@vafast/api-client'
214
242
 
215
- const response = await client.get(resolvedPath)
243
+ type MyClient = EdenClient<Api>
216
244
  ```
217
245
 
218
- ### 查询参数构建
246
+ ### `ApiResponse<T>`
219
247
 
220
- ```typescript
221
- import { buildQueryString } from '@vafast/api-client'
248
+ API 响应类型。
222
249
 
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}`)
250
+ ```typescript
251
+ interface ApiResponse<T> {
252
+ data: T | null // 响应数据
253
+ error: Error | null // 错误信息
254
+ status: number // HTTP 状态码
255
+ headers: Headers // 响应头
256
+ response: Response // 原始 Response
257
+ }
228
258
  ```
229
259
 
230
- ### 缓存配置
260
+ ### `RequestConfig`
261
+
262
+ 请求配置类型。
231
263
 
232
264
  ```typescript
233
- client.setCacheConfig({
234
- enabled: true,
235
- ttl: 300000, // 5分钟
236
- maxSize: 100,
237
- strategy: 'memory'
238
- })
265
+ interface RequestConfig {
266
+ headers?: Record<string, string> // 请求头
267
+ timeout?: number // 超时(毫秒)
268
+ signal?: AbortSignal // 取消信号
269
+ }
239
270
  ```
240
271
 
241
- ### 重试配置
272
+ ## 📁 示例
242
273
 
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
- ```
274
+ 查看 `example/` 目录获取完整示例:
275
+
276
+ - `auto-infer.ts` - 自动类型推断示例
277
+ - `test-sse.ts` - SSE 流式响应测试
252
278
 
253
279
  ## 🧪 测试
254
280
 
255
281
  ```bash
282
+ npm test
283
+ # 或
256
284
  bun test
257
285
  ```
258
286
 
259
- ## 📖 示例
260
-
261
- 查看 `example/` 目录中的完整示例:
262
-
263
- - `index.ts` - 主要使用示例
264
- - 基础 HTTP 请求
265
- - 类型安全客户端
266
- - WebSocket 客户端
267
- - 中间件和拦截器
268
- - 文件上传
269
-
270
- ## 🤝 贡献
271
-
272
- 欢迎提交 Issue 和 Pull Request!
273
-
274
287
  ## 📄 许可证
275
288
 
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)
289
+ MIT
package/TODO.md ADDED
@@ -0,0 +1,83 @@
1
+ # TODO: 类型同步功能
2
+
3
+ ## 背景
4
+
5
+ 前后端分离项目中,共享类型需要发布 npm 包,流程繁琐且需要私有 npm 服务。
6
+
7
+ ## 目标
8
+
9
+ 实现一条命令同步 API 类型,无需 npm 发包。
10
+
11
+ ## 方案设计
12
+
13
+ ### 1. 服务端:暴露类型端点
14
+
15
+ ```typescript
16
+ // vafast 自动注册端点
17
+ // GET /__vafast__/types
18
+
19
+ import { serve } from 'vafast'
20
+
21
+ serve({
22
+ routes,
23
+ exposeTypes: true // 开启类型导出端点
24
+ })
25
+ ```
26
+
27
+ 返回内容:
28
+ ```typescript
29
+ // 自动生成的类型定义
30
+ export const routes = [...] as const
31
+ export type AppRoutes = typeof routes
32
+ ```
33
+
34
+ ### 2. 客户端 CLI
35
+
36
+ ```bash
37
+ # 安装
38
+ npm install -D @vafast/cli
39
+
40
+ # 同步类型
41
+ npx vafast sync --url http://localhost:3000
42
+
43
+ # 或指定输出路径
44
+ npx vafast sync --url http://localhost:3000 --out src/api.d.ts
45
+ ```
46
+
47
+ ### 3. 自动化(可选)
48
+
49
+ ```json
50
+ // package.json
51
+ {
52
+ "scripts": {
53
+ "dev": "vafast sync --url $API_URL && vite",
54
+ "build": "vafast sync --url $API_URL && vite build"
55
+ }
56
+ }
57
+ ```
58
+
59
+ ## 实现步骤
60
+
61
+ - [ ] vafast 核心:添加 `exposeTypes` 选项
62
+ - [ ] vafast 核心:实现 `/__vafast__/types` 端点
63
+ - [ ] vafast 核心:实现类型序列化(保留字面量类型)
64
+ - [ ] @vafast/cli:创建 CLI 包
65
+ - [ ] @vafast/cli:实现 `sync` 命令
66
+ - [ ] @vafast/cli:支持配置文件 `.vafastrc`
67
+
68
+ ## 技术难点
69
+
70
+ 1. **类型序列化**:如何将运行时的路由定义导出为 TypeScript 类型字符串
71
+ 2. **TypeBox Schema 转换**:将 TypeBox schema 转为 TypeScript 类型
72
+ 3. **字面量保留**:确保 `'GET'`、`'/users'` 等字面量类型不被扩展
73
+
74
+ ## 参考
75
+
76
+ - tRPC:需要 monorepo 或 npm 包共享类型
77
+ - Elysia Eden:同样需要共享代码
78
+ - OpenAPI Generator:从 JSON Schema 生成类型(可参考)
79
+
80
+ ## 优先级
81
+
82
+ 中等 - 当前可用方案(npm link / monorepo)能满足需求,此功能为优化体验。
83
+