@vafast/api-client 0.1.3 → 0.1.4
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/dist/index.d.mts +296 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +226 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +7 -3
- package/TODO.md +0 -83
- package/build.ts +0 -37
- package/example/auto-infer.ts +0 -223
- package/example/test-sse.ts +0 -192
- package/src/core/eden.ts +0 -705
- package/src/index.ts +0 -42
- package/src/types/index.ts +0 -34
- package/test/eden.test.ts +0 -425
- package/tsconfig.dts.json +0 -20
- package/tsconfig.json +0 -20
- package/tsdown.config.ts +0 -10
- package/vitest.config.ts +0 -8
package/TODO.md
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
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
|
-
|
package/build.ts
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
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();
|
package/example/auto-infer.ts
DELETED
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ✨ 自动从 vafast 路由推断契约
|
|
3
|
-
*
|
|
4
|
-
* 特性:
|
|
5
|
-
* 1. 使用 defineRoutes() 自动保留字面量类型
|
|
6
|
-
* 2. 支持 SSE 流式响应
|
|
7
|
-
* 3. 完整的类型推断
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import {
|
|
11
|
-
defineRoutes,
|
|
12
|
-
createHandler,
|
|
13
|
-
createSSEHandler,
|
|
14
|
-
Type
|
|
15
|
-
} from 'vafast'
|
|
16
|
-
import { eden, InferEden } from '../src'
|
|
17
|
-
|
|
18
|
-
// ============= 业务类型定义 =============
|
|
19
|
-
|
|
20
|
-
interface User {
|
|
21
|
-
id: string
|
|
22
|
-
name: string
|
|
23
|
-
email: string
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
interface ChatMessage {
|
|
27
|
-
text: string
|
|
28
|
-
timestamp?: number
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// ============= 服务端:定义路由 =============
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* ✨ defineRoutes() 自动保留字面量类型,无需 as const!
|
|
35
|
-
*/
|
|
36
|
-
const routes = defineRoutes([
|
|
37
|
-
// GET /users - 获取用户列表
|
|
38
|
-
{
|
|
39
|
-
method: 'GET',
|
|
40
|
-
path: '/users',
|
|
41
|
-
handler: createHandler(
|
|
42
|
-
{
|
|
43
|
-
query: Type.Object({
|
|
44
|
-
page: Type.Optional(Type.Number({ default: 1 })),
|
|
45
|
-
limit: Type.Optional(Type.Number({ default: 10 }))
|
|
46
|
-
})
|
|
47
|
-
},
|
|
48
|
-
async ({ query }) => ({
|
|
49
|
-
users: [] as User[],
|
|
50
|
-
total: 0,
|
|
51
|
-
page: query.page ?? 1,
|
|
52
|
-
limit: query.limit ?? 10
|
|
53
|
-
})
|
|
54
|
-
)
|
|
55
|
-
},
|
|
56
|
-
|
|
57
|
-
// POST /users - 创建用户
|
|
58
|
-
{
|
|
59
|
-
method: 'POST',
|
|
60
|
-
path: '/users',
|
|
61
|
-
handler: createHandler(
|
|
62
|
-
{ body: Type.Object({ name: Type.String(), email: Type.String() }) },
|
|
63
|
-
async ({ body }) => ({
|
|
64
|
-
id: crypto.randomUUID(),
|
|
65
|
-
name: body.name,
|
|
66
|
-
email: body.email
|
|
67
|
-
} as User)
|
|
68
|
-
)
|
|
69
|
-
},
|
|
70
|
-
|
|
71
|
-
// GET /users/:id - 获取单个用户
|
|
72
|
-
{
|
|
73
|
-
method: 'GET',
|
|
74
|
-
path: '/users/:id',
|
|
75
|
-
handler: createHandler(
|
|
76
|
-
{ params: Type.Object({ id: Type.String() }) },
|
|
77
|
-
async ({ params }) => ({
|
|
78
|
-
id: params.id,
|
|
79
|
-
name: 'User',
|
|
80
|
-
email: 'user@example.com'
|
|
81
|
-
} as User | null)
|
|
82
|
-
)
|
|
83
|
-
},
|
|
84
|
-
|
|
85
|
-
// PUT /users/:id - 更新用户
|
|
86
|
-
{
|
|
87
|
-
method: 'PUT',
|
|
88
|
-
path: '/users/:id',
|
|
89
|
-
handler: createHandler(
|
|
90
|
-
{
|
|
91
|
-
params: Type.Object({ id: Type.String() }),
|
|
92
|
-
body: Type.Object({
|
|
93
|
-
name: Type.Optional(Type.String()),
|
|
94
|
-
email: Type.Optional(Type.String())
|
|
95
|
-
})
|
|
96
|
-
},
|
|
97
|
-
async ({ params, body }) => ({
|
|
98
|
-
id: params.id,
|
|
99
|
-
name: body?.name ?? 'User',
|
|
100
|
-
email: body?.email ?? 'user@example.com'
|
|
101
|
-
} as User)
|
|
102
|
-
)
|
|
103
|
-
},
|
|
104
|
-
|
|
105
|
-
// DELETE /users/:id - 删除用户
|
|
106
|
-
{
|
|
107
|
-
method: 'DELETE',
|
|
108
|
-
path: '/users/:id',
|
|
109
|
-
handler: createHandler(
|
|
110
|
-
{ params: Type.Object({ id: Type.String() }) },
|
|
111
|
-
async () => ({ success: true, deletedAt: new Date().toISOString() })
|
|
112
|
-
)
|
|
113
|
-
},
|
|
114
|
-
|
|
115
|
-
// 🌊 GET /chat/stream - SSE 流式响应
|
|
116
|
-
{
|
|
117
|
-
method: 'GET',
|
|
118
|
-
path: '/chat/stream',
|
|
119
|
-
handler: createSSEHandler(
|
|
120
|
-
{ query: Type.Object({ prompt: Type.String() }) },
|
|
121
|
-
async function* ({ query }) {
|
|
122
|
-
// 模拟 AI 流式响应
|
|
123
|
-
yield { event: 'start', data: { message: 'Starting...' } }
|
|
124
|
-
|
|
125
|
-
const words = `Hello! You said: "${query.prompt}"`.split(' ')
|
|
126
|
-
for (const word of words) {
|
|
127
|
-
yield { data: { text: word + ' ' } as ChatMessage }
|
|
128
|
-
await new Promise(r => setTimeout(r, 100))
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
yield { event: 'end', data: { message: 'Done!' } }
|
|
132
|
-
}
|
|
133
|
-
)
|
|
134
|
-
}
|
|
135
|
-
])
|
|
136
|
-
|
|
137
|
-
// ============= 🎉 自动推断契约类型!=============
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* 从路由定义自动推断 API 契约
|
|
141
|
-
* 无需手动定义任何接口!无需 as const!
|
|
142
|
-
*/
|
|
143
|
-
type Api = InferEden<typeof routes>
|
|
144
|
-
|
|
145
|
-
// ============= 客户端:完全类型安全的调用 =============
|
|
146
|
-
|
|
147
|
-
const api = eden<Api>('http://localhost:3000', {
|
|
148
|
-
headers: {
|
|
149
|
-
'Authorization': 'Bearer your-token-here'
|
|
150
|
-
},
|
|
151
|
-
timeout: 5000,
|
|
152
|
-
onError: (error) => {
|
|
153
|
-
console.error('API Error:', error.message)
|
|
154
|
-
}
|
|
155
|
-
})
|
|
156
|
-
|
|
157
|
-
async function main() {
|
|
158
|
-
console.log('=== 自动推断契约示例(无需 as const)===\n')
|
|
159
|
-
|
|
160
|
-
// ✅ GET /users?page=1&limit=10
|
|
161
|
-
const usersResult = await api.users.get({ page: 1, limit: 10 })
|
|
162
|
-
if (usersResult.data) {
|
|
163
|
-
console.log('📋 用户列表:', usersResult.data.users)
|
|
164
|
-
console.log(' 总数:', usersResult.data.total)
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// ✅ POST /users
|
|
168
|
-
const newUserResult = await api.users.post({
|
|
169
|
-
name: 'John Doe',
|
|
170
|
-
email: 'john@example.com'
|
|
171
|
-
})
|
|
172
|
-
if (newUserResult.data) {
|
|
173
|
-
console.log('\n✨ 新用户:', newUserResult.data.name)
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// ✅ GET /users/:id
|
|
177
|
-
const userResult = await api.users({ id: '123' }).get()
|
|
178
|
-
if (userResult.data) {
|
|
179
|
-
console.log('\n👤 用户详情:', userResult.data.name)
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// ✅ PUT /users/:id
|
|
183
|
-
const updateResult = await api.users({ id: '123' }).put({ name: 'Jane' })
|
|
184
|
-
if (updateResult.data) {
|
|
185
|
-
console.log('\n📝 更新后:', updateResult.data.name)
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// ✅ DELETE /users/:id
|
|
189
|
-
const deleteResult = await api.users({ id: '123' }).delete()
|
|
190
|
-
if (deleteResult.data) {
|
|
191
|
-
console.log('\n🗑️ 删除成功:', deleteResult.data.success)
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// 🌊 SSE 流式响应
|
|
195
|
-
console.log('\n=== SSE 流式响应 ===\n')
|
|
196
|
-
|
|
197
|
-
// SSE 返回类型目前是 unknown,需要手动断言
|
|
198
|
-
// 未来版本会改进 SSE 返回类型推断
|
|
199
|
-
const subscription = api.chat.stream.subscribe(
|
|
200
|
-
{ prompt: 'Hello AI!' },
|
|
201
|
-
{
|
|
202
|
-
onOpen: () => console.log('📡 连接已建立'),
|
|
203
|
-
onMessage: (data: unknown) => {
|
|
204
|
-
console.log('收到消息:', data)
|
|
205
|
-
},
|
|
206
|
-
onError: (err) => console.error('❌ 错误:', err.message),
|
|
207
|
-
onClose: () => console.log('📴 连接已关闭')
|
|
208
|
-
}
|
|
209
|
-
)
|
|
210
|
-
|
|
211
|
-
// 5 秒后取消订阅
|
|
212
|
-
setTimeout(() => {
|
|
213
|
-
subscription.unsubscribe()
|
|
214
|
-
console.log('\n\n=== 示例完成 ===')
|
|
215
|
-
}, 5000)
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
main().catch(console.error)
|
|
219
|
-
|
|
220
|
-
// ============= 导出 =============
|
|
221
|
-
|
|
222
|
-
export { routes, api }
|
|
223
|
-
export type { Api }
|
package/example/test-sse.ts
DELETED
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SSE 端到端测试
|
|
3
|
-
* 测试功能:
|
|
4
|
-
* 1. 请求取消 (AbortController)
|
|
5
|
-
* 2. SSE 自动重连
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import {
|
|
9
|
-
defineRoutes,
|
|
10
|
-
route,
|
|
11
|
-
createHandler,
|
|
12
|
-
createSSEHandler,
|
|
13
|
-
Type,
|
|
14
|
-
serve
|
|
15
|
-
} from 'vafast'
|
|
16
|
-
import { eden, InferEden } from '../src'
|
|
17
|
-
|
|
18
|
-
// 定义路由
|
|
19
|
-
const routes = defineRoutes([
|
|
20
|
-
// 普通 GET 请求
|
|
21
|
-
route('GET', '/hello', createHandler(
|
|
22
|
-
{ query: Type.Object({ name: Type.Optional(Type.String()) }) },
|
|
23
|
-
async ({ query }) => ({ message: `Hello, ${query.name || 'World'}!` })
|
|
24
|
-
)),
|
|
25
|
-
|
|
26
|
-
// 慢请求(用于测试取消)
|
|
27
|
-
route('GET', '/slow', createHandler(
|
|
28
|
-
{},
|
|
29
|
-
async () => {
|
|
30
|
-
await new Promise(r => setTimeout(r, 5000))
|
|
31
|
-
return { message: 'Slow response' }
|
|
32
|
-
}
|
|
33
|
-
)),
|
|
34
|
-
|
|
35
|
-
// SSE 流式响应
|
|
36
|
-
route('GET', '/stream', createSSEHandler(
|
|
37
|
-
{ query: Type.Object({ count: Type.Optional(Type.Number({ default: 5 })) }) },
|
|
38
|
-
async function* ({ query }) {
|
|
39
|
-
const count = query.count ?? 5
|
|
40
|
-
|
|
41
|
-
yield { event: 'start', data: { message: '开始流式传输...' } }
|
|
42
|
-
|
|
43
|
-
for (let i = 1; i <= count; i++) {
|
|
44
|
-
yield { id: String(i), data: { index: i, text: `消息 ${i}/${count}` } }
|
|
45
|
-
await new Promise(r => setTimeout(r, 200))
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
yield { event: 'end', data: { message: '传输完成!' } }
|
|
49
|
-
}
|
|
50
|
-
))
|
|
51
|
-
])
|
|
52
|
-
|
|
53
|
-
type Api = InferEden<typeof routes>
|
|
54
|
-
|
|
55
|
-
async function main() {
|
|
56
|
-
// 启动服务器
|
|
57
|
-
console.log('🚀 启动服务器...')
|
|
58
|
-
const server = serve({
|
|
59
|
-
fetch: (req) => {
|
|
60
|
-
const url = new URL(req.url)
|
|
61
|
-
|
|
62
|
-
// 简单路由
|
|
63
|
-
if (url.pathname === '/hello') {
|
|
64
|
-
const name = url.searchParams.get('name') || 'World'
|
|
65
|
-
return new Response(JSON.stringify({ message: `Hello, ${name}!` }), {
|
|
66
|
-
headers: { 'Content-Type': 'application/json' }
|
|
67
|
-
})
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// 慢请求
|
|
71
|
-
if (url.pathname === '/slow') {
|
|
72
|
-
return new Promise(resolve => {
|
|
73
|
-
setTimeout(() => {
|
|
74
|
-
resolve(new Response(JSON.stringify({ message: 'Slow response' }), {
|
|
75
|
-
headers: { 'Content-Type': 'application/json' }
|
|
76
|
-
}))
|
|
77
|
-
}, 5000)
|
|
78
|
-
})
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if (url.pathname === '/stream') {
|
|
82
|
-
const count = parseInt(url.searchParams.get('count') || '5')
|
|
83
|
-
|
|
84
|
-
const stream = new ReadableStream({
|
|
85
|
-
async start(controller) {
|
|
86
|
-
const encoder = new TextEncoder()
|
|
87
|
-
|
|
88
|
-
controller.enqueue(encoder.encode(`event: start\ndata: ${JSON.stringify({ message: '开始流式传输...' })}\n\n`))
|
|
89
|
-
|
|
90
|
-
for (let i = 1; i <= count; i++) {
|
|
91
|
-
controller.enqueue(encoder.encode(`id: ${i}\ndata: ${JSON.stringify({ index: i, text: `消息 ${i}/${count}` })}\n\n`))
|
|
92
|
-
await new Promise(r => setTimeout(r, 200))
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
controller.enqueue(encoder.encode(`event: end\ndata: ${JSON.stringify({ message: '传输完成!' })}\n\n`))
|
|
96
|
-
controller.close()
|
|
97
|
-
}
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
return new Response(stream, {
|
|
101
|
-
headers: {
|
|
102
|
-
'Content-Type': 'text/event-stream',
|
|
103
|
-
'Cache-Control': 'no-cache',
|
|
104
|
-
'Connection': 'keep-alive'
|
|
105
|
-
}
|
|
106
|
-
})
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
return new Response('Not Found', { status: 404 })
|
|
110
|
-
},
|
|
111
|
-
port: 3456
|
|
112
|
-
})
|
|
113
|
-
|
|
114
|
-
console.log('✅ 服务器启动在 http://localhost:3456\n')
|
|
115
|
-
|
|
116
|
-
// 等待服务器启动
|
|
117
|
-
await new Promise(r => setTimeout(r, 500))
|
|
118
|
-
|
|
119
|
-
// 创建客户端
|
|
120
|
-
const api = eden<Api>('http://localhost:3456')
|
|
121
|
-
|
|
122
|
-
// ============= 测试 1: 请求取消 =============
|
|
123
|
-
console.log('🧪 测试 1: 请求取消')
|
|
124
|
-
|
|
125
|
-
const controller = new AbortController()
|
|
126
|
-
|
|
127
|
-
// 发起慢请求
|
|
128
|
-
const slowPromise = api.slow.get(undefined, { signal: controller.signal })
|
|
129
|
-
|
|
130
|
-
// 100ms 后取消
|
|
131
|
-
setTimeout(() => {
|
|
132
|
-
controller.abort()
|
|
133
|
-
console.log(' ⏹️ 请求已取消')
|
|
134
|
-
}, 100)
|
|
135
|
-
|
|
136
|
-
const result = await slowPromise
|
|
137
|
-
// 取消后 status 为 0,error 可能是 AbortError 或 "This operation was aborted"
|
|
138
|
-
if (result.status === 0 && result.error) {
|
|
139
|
-
console.log(' ✅ 请求取消成功 (error:', result.error.message || result.error.name, ')\n')
|
|
140
|
-
} else {
|
|
141
|
-
console.log(' ❌ 请求取消失败: status=', result.status, '\n')
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// ============= 测试 2: 普通请求 =============
|
|
145
|
-
console.log('🧪 测试 2: 普通请求')
|
|
146
|
-
const helloResult = await api.hello.get({ name: 'TypeScript' })
|
|
147
|
-
console.log(' 响应:', helloResult.data)
|
|
148
|
-
console.log()
|
|
149
|
-
|
|
150
|
-
// ============= 测试 3: SSE 流式响应 =============
|
|
151
|
-
console.log('🧪 测试 3: SSE 流式响应')
|
|
152
|
-
|
|
153
|
-
await new Promise<void>((resolve) => {
|
|
154
|
-
const sub = api.stream.subscribe(
|
|
155
|
-
{ count: 3 },
|
|
156
|
-
{
|
|
157
|
-
onOpen: () => console.log(' 📡 连接已建立'),
|
|
158
|
-
onMessage: (data: unknown) => {
|
|
159
|
-
console.log(' 📨', data)
|
|
160
|
-
},
|
|
161
|
-
onError: (err) => console.log(' ❌ 错误:', err.message),
|
|
162
|
-
onClose: () => {
|
|
163
|
-
console.log(' 📴 连接已关闭')
|
|
164
|
-
resolve()
|
|
165
|
-
},
|
|
166
|
-
onReconnect: (attempt, max) => {
|
|
167
|
-
console.log(` 🔄 重连中 (${attempt}/${max})...`)
|
|
168
|
-
},
|
|
169
|
-
onMaxReconnects: () => {
|
|
170
|
-
console.log(' ⚠️ 达到最大重连次数')
|
|
171
|
-
}
|
|
172
|
-
},
|
|
173
|
-
{
|
|
174
|
-
reconnectInterval: 1000,
|
|
175
|
-
maxReconnects: 3
|
|
176
|
-
}
|
|
177
|
-
)
|
|
178
|
-
|
|
179
|
-
// 5 秒超时
|
|
180
|
-
setTimeout(() => {
|
|
181
|
-
sub.unsubscribe()
|
|
182
|
-
resolve()
|
|
183
|
-
}, 5000)
|
|
184
|
-
})
|
|
185
|
-
|
|
186
|
-
console.log('\n✅ 所有测试完成!')
|
|
187
|
-
|
|
188
|
-
// 关闭服务器
|
|
189
|
-
server.stop()
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
main().catch(console.error)
|