@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/README.md
CHANGED
|
@@ -1,282 +1,298 @@
|
|
|
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`** - `defineRoutes()` 自动保留字面量类型
|
|
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
|
|
21
|
-
|
|
17
|
+
npm install @vafast/api-client
|
|
22
18
|
```
|
|
23
19
|
|
|
24
20
|
## 🚀 快速开始
|
|
25
21
|
|
|
26
|
-
###
|
|
22
|
+
### 1. 定义服务端路由
|
|
27
23
|
|
|
28
24
|
```typescript
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
25
|
+
// server.ts
|
|
26
|
+
import { defineRoutes, createHandler, createSSEHandler, Type } from 'vafast'
|
|
27
|
+
|
|
28
|
+
export const routes = defineRoutes([
|
|
29
|
+
// ✨ defineRoutes() 自动保留字面量类型,无需 as const
|
|
30
|
+
{
|
|
31
|
+
method: 'GET',
|
|
32
|
+
path: '/users',
|
|
33
|
+
handler: createHandler(
|
|
34
|
+
{ query: Type.Object({ page: Type.Optional(Type.Number()) }) },
|
|
35
|
+
async ({ query }) => ({ users: [], total: 0, page: query.page ?? 1 })
|
|
36
|
+
)
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
method: 'POST',
|
|
40
|
+
path: '/users',
|
|
41
|
+
handler: createHandler(
|
|
42
|
+
{ body: Type.Object({ name: Type.String(), email: Type.String() }) },
|
|
43
|
+
async ({ body }) => ({ id: crypto.randomUUID(), ...body })
|
|
44
|
+
)
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
method: 'GET',
|
|
48
|
+
path: '/users/:id',
|
|
49
|
+
handler: createHandler(
|
|
50
|
+
{ params: Type.Object({ id: Type.String() }) },
|
|
51
|
+
async ({ params }) => ({ id: params.id, name: 'User' })
|
|
52
|
+
)
|
|
53
|
+
},
|
|
54
|
+
// 🌊 SSE 流式响应
|
|
55
|
+
{
|
|
56
|
+
method: 'GET',
|
|
57
|
+
path: '/chat/stream',
|
|
58
|
+
handler: createSSEHandler(
|
|
59
|
+
{ query: Type.Object({ prompt: Type.String() }) },
|
|
60
|
+
async function* ({ query }) {
|
|
61
|
+
yield { event: 'start', data: { message: 'Starting...' } }
|
|
62
|
+
|
|
63
|
+
for (const word of query.prompt.split(' ')) {
|
|
64
|
+
yield { data: { text: word } }
|
|
65
|
+
await new Promise(r => setTimeout(r, 100))
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
yield { event: 'end', data: { message: 'Done!' } }
|
|
69
|
+
}
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
])
|
|
57
73
|
|
|
58
|
-
//
|
|
59
|
-
|
|
60
|
-
const user = await typedClient.post('/users', { name: 'John', email: 'john@example.com' })
|
|
74
|
+
// 导出类型供客户端使用
|
|
75
|
+
export type AppRoutes = typeof routes
|
|
61
76
|
```
|
|
62
77
|
|
|
63
|
-
###
|
|
78
|
+
### 2. 创建类型安全客户端
|
|
64
79
|
|
|
65
80
|
```typescript
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
autoReconnect: true,
|
|
70
|
-
maxReconnectAttempts: 5
|
|
71
|
-
})
|
|
81
|
+
// client.ts
|
|
82
|
+
import { eden, InferEden } from '@vafast/api-client'
|
|
83
|
+
import type { AppRoutes } from './server'
|
|
72
84
|
|
|
73
|
-
|
|
85
|
+
// 自动推断 API 类型
|
|
86
|
+
type Api = InferEden<AppRoutes>
|
|
74
87
|
|
|
75
|
-
|
|
76
|
-
|
|
88
|
+
// 创建客户端
|
|
89
|
+
const api = eden<Api>('http://localhost:3000', {
|
|
90
|
+
headers: { 'Authorization': 'Bearer token' },
|
|
91
|
+
timeout: 5000
|
|
77
92
|
})
|
|
78
93
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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 // 状态码验证函数
|
|
94
|
+
// ✅ 完全类型安全的调用
|
|
95
|
+
async function main() {
|
|
96
|
+
// GET /users?page=1
|
|
97
|
+
const { data: users } = await api.users.get({ page: 1 })
|
|
98
|
+
console.log(users?.total) // ✅ 类型推断
|
|
99
|
+
|
|
100
|
+
// POST /users
|
|
101
|
+
const { data: newUser } = await api.users.post({
|
|
102
|
+
name: 'John',
|
|
103
|
+
email: 'john@example.com'
|
|
104
|
+
})
|
|
105
|
+
console.log(newUser?.id) // ✅ 类型推断
|
|
106
|
+
|
|
107
|
+
// GET /users/:id
|
|
108
|
+
const { data: user } = await api.users({ id: '123' }).get()
|
|
109
|
+
console.log(user?.name) // ✅ 类型推断
|
|
104
110
|
}
|
|
105
111
|
```
|
|
106
112
|
|
|
107
|
-
|
|
113
|
+
## 📖 API 文档
|
|
108
114
|
|
|
109
|
-
|
|
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 请求
|
|
115
|
+
### `eden<T>(baseURL, config?)`
|
|
116
116
|
|
|
117
|
-
|
|
117
|
+
创建 Eden 风格的 API 客户端。
|
|
118
118
|
|
|
119
119
|
```typescript
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
120
|
+
const api = eden<Api>('http://localhost:3000', {
|
|
121
|
+
// 默认请求头
|
|
122
|
+
headers: { 'Authorization': 'Bearer token' },
|
|
123
|
+
|
|
124
|
+
// 全局超时(毫秒)
|
|
125
|
+
timeout: 5000,
|
|
126
|
+
|
|
127
|
+
// 请求拦截器
|
|
128
|
+
onRequest: (request) => {
|
|
129
|
+
console.log('Request:', request.url)
|
|
124
130
|
return request
|
|
125
131
|
},
|
|
126
|
-
|
|
127
|
-
|
|
132
|
+
|
|
133
|
+
// 响应拦截器
|
|
134
|
+
onResponse: (response) => {
|
|
135
|
+
console.log('Response:', response.status)
|
|
128
136
|
return response
|
|
129
137
|
},
|
|
130
|
-
|
|
138
|
+
|
|
139
|
+
// 错误处理
|
|
140
|
+
onError: (error) => {
|
|
131
141
|
console.error('Error:', error.message)
|
|
132
142
|
}
|
|
133
143
|
})
|
|
134
144
|
```
|
|
135
145
|
|
|
136
|
-
###
|
|
146
|
+
### HTTP 方法
|
|
137
147
|
|
|
138
148
|
```typescript
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
})
|
|
149
|
+
// GET 请求(带 query 参数)
|
|
150
|
+
api.users.get({ page: 1, limit: 10 })
|
|
151
|
+
|
|
152
|
+
// POST 请求(带 body)
|
|
153
|
+
api.users.post({ name: 'John', email: 'john@example.com' })
|
|
154
|
+
|
|
155
|
+
// PUT 请求
|
|
156
|
+
api.users({ id: '123' }).put({ name: 'Jane' })
|
|
157
|
+
|
|
158
|
+
// DELETE 请求
|
|
159
|
+
api.users({ id: '123' }).delete()
|
|
160
|
+
|
|
161
|
+
// PATCH 请求
|
|
162
|
+
api.users({ id: '123' }).patch({ name: 'Updated' })
|
|
154
163
|
```
|
|
155
164
|
|
|
156
|
-
###
|
|
165
|
+
### 路径参数
|
|
157
166
|
|
|
158
167
|
```typescript
|
|
159
|
-
|
|
168
|
+
// 使用函数调用传递参数
|
|
169
|
+
api.users({ id: '123' }).get() // GET /users/123
|
|
170
|
+
api.users({ id: '123' }).posts.get() // GET /users/123/posts
|
|
171
|
+
api.users({ id: '123' }).posts({ postId: '456' }).get() // GET /users/123/posts/456
|
|
172
|
+
```
|
|
160
173
|
|
|
161
|
-
|
|
162
|
-
await wsClient.connect()
|
|
174
|
+
### 请求取消
|
|
163
175
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
wsClient.on('open', () => console.log('Connected'))
|
|
167
|
-
wsClient.on('close', () => console.log('Disconnected'))
|
|
176
|
+
```typescript
|
|
177
|
+
const controller = new AbortController()
|
|
168
178
|
|
|
169
|
-
//
|
|
170
|
-
|
|
179
|
+
// 发起请求
|
|
180
|
+
const promise = api.users.get({ page: 1 }, { signal: controller.signal })
|
|
171
181
|
|
|
172
|
-
//
|
|
173
|
-
|
|
174
|
-
```
|
|
182
|
+
// 取消请求
|
|
183
|
+
controller.abort()
|
|
175
184
|
|
|
176
|
-
|
|
185
|
+
const result = await promise
|
|
186
|
+
if (result.error) {
|
|
187
|
+
console.log('请求已取消')
|
|
188
|
+
}
|
|
189
|
+
```
|
|
177
190
|
|
|
178
|
-
###
|
|
191
|
+
### 单次请求配置
|
|
179
192
|
|
|
180
193
|
```typescript
|
|
181
|
-
//
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
|
|
194
|
+
// 覆盖全局配置
|
|
195
|
+
const result = await api.users.get({ page: 1 }, {
|
|
196
|
+
headers: { 'X-Custom-Header': 'value' },
|
|
197
|
+
timeout: 10000,
|
|
198
|
+
signal: abortController.signal
|
|
185
199
|
})
|
|
200
|
+
```
|
|
186
201
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
category: 'images'
|
|
191
|
-
})
|
|
202
|
+
## 🌊 SSE 流式响应
|
|
203
|
+
|
|
204
|
+
### 基本用法
|
|
192
205
|
|
|
193
|
-
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
206
|
+
```typescript
|
|
207
|
+
const subscription = api.chat.stream.subscribe(
|
|
208
|
+
{ prompt: 'Hello AI!' }, // query 参数
|
|
209
|
+
{
|
|
210
|
+
onOpen: () => console.log('连接已建立'),
|
|
211
|
+
onMessage: (data) => console.log('收到:', data),
|
|
212
|
+
onError: (err) => console.error('错误:', err),
|
|
213
|
+
onClose: () => console.log('连接已关闭'),
|
|
214
|
+
onReconnect: (attempt, max) => console.log(`重连中 ${attempt}/${max}`),
|
|
215
|
+
onMaxReconnects: () => console.log('达到最大重连次数')
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
reconnectInterval: 3000, // 重连间隔(毫秒)
|
|
219
|
+
maxReconnects: 5 // 最大重连次数
|
|
200
220
|
}
|
|
201
|
-
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
// 取消订阅
|
|
224
|
+
subscription.unsubscribe()
|
|
202
225
|
```
|
|
203
226
|
|
|
204
|
-
###
|
|
227
|
+
### SSE 特性
|
|
205
228
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
229
|
+
- ✅ **自动重连** - 网络断开后自动重连
|
|
230
|
+
- ✅ **断点续传** - 使用 `Last-Event-ID` 从断点继续
|
|
231
|
+
- ✅ **可配置重连策略** - 自定义重连间隔和最大次数
|
|
232
|
+
- ✅ **事件类型支持** - 支持自定义事件名称
|
|
209
233
|
|
|
210
|
-
|
|
211
|
-
const params = { id: '123', postId: '456' }
|
|
212
|
-
const resolvedPath = replacePathParams(path, params)
|
|
213
|
-
// 结果: '/users/123/posts/456'
|
|
234
|
+
## 🔧 类型定义
|
|
214
235
|
|
|
215
|
-
|
|
216
|
-
```
|
|
236
|
+
### `InferEden<T>`
|
|
217
237
|
|
|
218
|
-
|
|
238
|
+
从 vafast 路由数组推断 API 契约类型。
|
|
219
239
|
|
|
220
240
|
```typescript
|
|
221
|
-
import {
|
|
222
|
-
|
|
223
|
-
const query = { page: 1, limit: 10, search: 'john' }
|
|
224
|
-
const queryString = buildQueryString(query)
|
|
225
|
-
// 结果: '?page=1&limit=10&search=john'
|
|
241
|
+
import { InferEden } from '@vafast/api-client'
|
|
226
242
|
|
|
227
|
-
const
|
|
243
|
+
const routes = defineRoutes([...])
|
|
244
|
+
type Api = InferEden<typeof routes>
|
|
228
245
|
```
|
|
229
246
|
|
|
230
|
-
###
|
|
247
|
+
### `EdenClient<T>`
|
|
248
|
+
|
|
249
|
+
Eden 客户端类型。
|
|
231
250
|
|
|
232
251
|
```typescript
|
|
233
|
-
client
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
maxSize: 100,
|
|
237
|
-
strategy: 'memory'
|
|
238
|
-
})
|
|
252
|
+
import { EdenClient } from '@vafast/api-client'
|
|
253
|
+
|
|
254
|
+
type MyClient = EdenClient<Api>
|
|
239
255
|
```
|
|
240
256
|
|
|
241
|
-
###
|
|
257
|
+
### `ApiResponse<T>`
|
|
258
|
+
|
|
259
|
+
API 响应类型。
|
|
242
260
|
|
|
243
261
|
```typescript
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
}
|
|
262
|
+
interface ApiResponse<T> {
|
|
263
|
+
data: T | null // 响应数据
|
|
264
|
+
error: Error | null // 错误信息
|
|
265
|
+
status: number // HTTP 状态码
|
|
266
|
+
headers: Headers // 响应头
|
|
267
|
+
response: Response // 原始 Response
|
|
268
|
+
}
|
|
251
269
|
```
|
|
252
270
|
|
|
253
|
-
|
|
271
|
+
### `RequestConfig`
|
|
254
272
|
|
|
255
|
-
|
|
256
|
-
|
|
273
|
+
请求配置类型。
|
|
274
|
+
|
|
275
|
+
```typescript
|
|
276
|
+
interface RequestConfig {
|
|
277
|
+
headers?: Record<string, string> // 请求头
|
|
278
|
+
timeout?: number // 超时(毫秒)
|
|
279
|
+
signal?: AbortSignal // 取消信号
|
|
280
|
+
}
|
|
257
281
|
```
|
|
258
282
|
|
|
259
|
-
##
|
|
283
|
+
## 📁 示例
|
|
260
284
|
|
|
261
|
-
查看 `example/`
|
|
285
|
+
查看 `example/` 目录获取完整示例:
|
|
262
286
|
|
|
263
|
-
- `
|
|
264
|
-
-
|
|
265
|
-
- 类型安全客户端
|
|
266
|
-
- WebSocket 客户端
|
|
267
|
-
- 中间件和拦截器
|
|
268
|
-
- 文件上传
|
|
287
|
+
- `auto-infer.ts` - 自动类型推断示例
|
|
288
|
+
- `test-sse.ts` - SSE 流式响应测试
|
|
269
289
|
|
|
270
|
-
##
|
|
290
|
+
## 🧪 测试
|
|
271
291
|
|
|
272
|
-
|
|
292
|
+
```bash
|
|
293
|
+
npm test
|
|
294
|
+
```
|
|
273
295
|
|
|
274
296
|
## 📄 许可证
|
|
275
297
|
|
|
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)
|
|
298
|
+
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
|
+
|