@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 +202 -195
- package/TODO.md +83 -0
- package/example/auto-infer.ts +202 -0
- package/example/test-sse.ts +192 -0
- package/package.json +6 -7
- package/src/core/eden.ts +697 -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/vitest.config.ts +8 -0
- 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/README.md
CHANGED
|
@@ -1,282 +1,289 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @vafast/api-client
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
🚀 类型安全的 Eden 风格 API 客户端,专为 [Vafast](https://github.com/user/vafast) 框架设计。
|
|
4
4
|
|
|
5
5
|
## ✨ 特性
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
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
|
-
|
|
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
|
|
33
|
-
|
|
34
|
-
timeout:
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
50
|
-
import { createTypedClient } from '@vafast/api-client'
|
|
51
|
-
import type { Server } from 'vafast'
|
|
104
|
+
### `eden<T>(baseURL, config?)`
|
|
52
105
|
|
|
53
|
-
|
|
54
|
-
const typedClient = createTypedClient<Server>(server, {
|
|
55
|
-
baseURL: 'https://api.example.com'
|
|
56
|
-
})
|
|
106
|
+
创建 Eden 风格的 API 客户端。
|
|
57
107
|
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
|
|
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
|
-
###
|
|
135
|
+
### HTTP 方法
|
|
64
136
|
|
|
65
137
|
```typescript
|
|
66
|
-
|
|
138
|
+
// GET 请求(带 query 参数)
|
|
139
|
+
api.users.get({ page: 1, limit: 10 })
|
|
67
140
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
maxReconnectAttempts: 5
|
|
71
|
-
})
|
|
141
|
+
// POST 请求(带 body)
|
|
142
|
+
api.users.post({ name: 'John', email: 'john@example.com' })
|
|
72
143
|
|
|
73
|
-
|
|
144
|
+
// PUT 请求
|
|
145
|
+
api.users({ id: '123' }).put({ name: 'Jane' })
|
|
74
146
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
})
|
|
147
|
+
// DELETE 请求
|
|
148
|
+
api.users({ id: '123' }).delete()
|
|
78
149
|
|
|
79
|
-
|
|
150
|
+
// PATCH 请求
|
|
151
|
+
api.users({ id: '123' }).patch({ name: 'Updated' })
|
|
80
152
|
```
|
|
81
153
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
### VafastApiClient
|
|
85
|
-
|
|
86
|
-
主要的 API 客户端类。
|
|
87
|
-
|
|
88
|
-
#### 构造函数
|
|
154
|
+
### 路径参数
|
|
89
155
|
|
|
90
156
|
```typescript
|
|
91
|
-
|
|
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
|
-
|
|
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
|
-
|
|
110
|
-
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
-
|
|
191
|
+
## 🌊 SSE 流式响应
|
|
192
|
+
|
|
193
|
+
### 基本用法
|
|
157
194
|
|
|
158
195
|
```typescript
|
|
159
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
207
|
-
// 使用工具函数替换路径参数
|
|
208
|
-
import { replacePathParams } from '@vafast/api-client'
|
|
238
|
+
Eden 客户端类型。
|
|
209
239
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
const resolvedPath = replacePathParams(path, params)
|
|
213
|
-
// 结果: '/users/123/posts/456'
|
|
240
|
+
```typescript
|
|
241
|
+
import { EdenClient } from '@vafast/api-client'
|
|
214
242
|
|
|
215
|
-
|
|
243
|
+
type MyClient = EdenClient<Api>
|
|
216
244
|
```
|
|
217
245
|
|
|
218
|
-
###
|
|
246
|
+
### `ApiResponse<T>`
|
|
219
247
|
|
|
220
|
-
|
|
221
|
-
import { buildQueryString } from '@vafast/api-client'
|
|
248
|
+
API 响应类型。
|
|
222
249
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
//
|
|
226
|
-
|
|
227
|
-
|
|
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
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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
|
|
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
|
+
|