@vafast/api-client 0.1.5 → 0.2.0

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,221 +1,199 @@
1
1
  # @vafast/api-client
2
2
 
3
- 🚀 类型安全的 Eden 风格 API 客户端,专为 [Vafast](https://github.com/user/vafast) 框架设计。
3
+ 类型安全的 Eden 风格 API 客户端,支持从 vafast 路由自动推断类型。
4
4
 
5
- ## ✨ 特性
6
-
7
- - 🔒 **完整类型推断** - 从路由定义自动推断 API 类型,无需手动定义接口
8
- - 🎯 **无需 `as const`** - `defineRoutes()` 自动保留字面量类型
9
- - 🌊 **SSE 流式响应** - 内置 Server-Sent Events 支持,包含自动重连
10
- - ⏹️ **请求取消** - 支持 AbortController 取消进行中的请求
11
- - 🔗 **链式调用** - 优雅的 `api.users({ id }).posts.get()` 语法
12
- - 📦 **轻量** - 仅 8KB (gzip)
13
-
14
- ## 📦 安装
5
+ ## 安装
15
6
 
16
7
  ```bash
17
- npm install @vafast/api-client
8
+ npm install @vafast/api-client vafast
18
9
  ```
19
10
 
20
- ## 🚀 快速开始
11
+ ## 快速开始
21
12
 
22
- ### 1. 定义服务端路由
13
+ ### 方式 1:从 vafast 路由自动推断类型(推荐)
23
14
 
24
15
  ```typescript
25
- // server.ts
26
- import { defineRoutes, createHandler, createSSEHandler, Type } from 'vafast'
16
+ // ============= 服务端 =============
17
+ import { defineRoute, defineRoutes, Type, Server } from 'vafast'
27
18
 
28
- export const routes = defineRoutes([
29
- // defineRoutes() 自动保留字面量类型,无需 as const
30
- {
19
+ // 定义路由(使用 as const 保留字面量类型)
20
+ const routeDefinitions = [
21
+ defineRoute({
31
22
  method: 'GET',
32
23
  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
- {
24
+ schema: { query: Type.Object({ page: Type.Number() }) },
25
+ handler: ({ query }) => ({ users: [], page: query.page })
26
+ }),
27
+ defineRoute({
39
28
  method: 'POST',
40
29
  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
- {
30
+ schema: { body: Type.Object({ name: Type.String() }) },
31
+ handler: ({ body }) => ({ id: '1', name: body.name })
32
+ }),
33
+ defineRoute({
47
34
  method: 'GET',
48
35
  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
- ])
36
+ schema: { params: Type.Object({ id: Type.String() }) },
37
+ handler: ({ params }) => ({ id: params.id, name: 'John' })
38
+ })
39
+ ] as const
40
+
41
+ // 创建服务器
42
+ const routes = defineRoutes(routeDefinitions)
43
+ const server = new Server(routes)
44
+
45
+ // ============= 客户端 =============
46
+ import { eden, InferEden } from '@vafast/api-client'
47
+
48
+ // 自动推断类型
49
+ type Api = InferEden<typeof routeDefinitions>
50
+ const api = eden<Api>('http://localhost:3000')
73
51
 
74
- // 导出类型供客户端使用
75
- export type AppRoutes = typeof routes
52
+ // ✅ 完全类型安全
53
+ const { data } = await api.users.get({ page: 1 }) // query 有类型提示
54
+ const { data: user } = await api.users({ id: '123' }).get() // 动态参数
76
55
  ```
77
56
 
78
- ### 2. 创建类型安全客户端
57
+ ### 方式 2:手动定义契约(非 vafast API)
79
58
 
80
59
  ```typescript
81
- // client.ts
82
- import { eden, InferEden } from '@vafast/api-client'
83
- import type { AppRoutes } from './server'
60
+ import { eden } from '@vafast/api-client'
61
+
62
+ // 手动定义契约类型
63
+ type MyApi = {
64
+ users: {
65
+ get: { query: { page: number }; return: { users: User[]; total: number } }
66
+ post: { body: { name: string }; return: User }
67
+ ':id': {
68
+ get: { return: User | null }
69
+ put: { body: Partial<User>; return: User }
70
+ delete: { return: { success: boolean } }
71
+ }
72
+ }
73
+ }
84
74
 
85
- // 自动推断 API 类型
86
- type Api = InferEden<AppRoutes>
75
+ const api = eden<MyApi>('https://api.example.com')
87
76
 
88
- // 创建客户端
89
- const api = eden<Api>('http://localhost:3000', {
90
- headers: { 'Authorization': 'Bearer token' },
91
- timeout: 5000
92
- })
77
+ // 调用方式完全相同
78
+ const { data } = await api.users.get({ page: 1 })
79
+ const { data: user } = await api.users({ id: '123' }).get()
80
+ ```
93
81
 
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) // ✅ 类型推断
82
+ ## 调用方式
99
83
 
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) // ✅ 类型推断
84
+ ```typescript
85
+ // GET 请求 + query 参数
86
+ const { data, error } = await api.users.get({ page: 1, limit: 10 })
106
87
 
107
- // GET /users/:id
108
- const { data: user } = await api.users({ id: '123' }).get()
109
- console.log(user?.name) // ✅ 类型推断
110
- }
88
+ // POST 请求 + body
89
+ const { data, error } = await api.users.post({ name: 'John', email: 'john@example.com' })
90
+
91
+ // 动态路径参数
92
+ const { data, error } = await api.users({ id: '123' }).get()
93
+ const { data, error } = await api.users({ id: '123' }).put({ name: 'Jane' })
94
+ const { data, error } = await api.users({ id: '123' }).delete()
95
+
96
+ // 嵌套路径
97
+ const { data, error } = await api.users({ id: '123' }).posts.get()
98
+ const { data, error } = await api.users({ id: '123' }).posts({ id: '456' }).get()
111
99
  ```
112
100
 
113
- ## 📖 API 文档
101
+ ## Go 风格错误处理
114
102
 
115
- ### `eden<T>(baseURL, config?)`
103
+ ```typescript
104
+ const { data, error } = await api.users.get()
116
105
 
117
- 创建 Eden 风格的 API 客户端。
106
+ if (error) {
107
+ // error: { code: number; message: string }
108
+ console.error(`错误 ${error.code}: ${error.message}`)
109
+ return
110
+ }
111
+
112
+ // data 在这里保证非 null
113
+ console.log(data.users)
114
+ ```
115
+
116
+ ## 配置选项
118
117
 
119
118
  ```typescript
120
119
  const api = eden<Api>('http://localhost:3000', {
121
120
  // 默认请求头
122
- headers: { 'Authorization': 'Bearer token' },
121
+ headers: {
122
+ 'Authorization': 'Bearer token123'
123
+ },
123
124
 
124
- // 全局超时(毫秒)
125
- timeout: 5000,
125
+ // 请求超时(毫秒)
126
+ timeout: 30000,
126
127
 
127
128
  // 请求拦截器
128
- onRequest: (request) => {
129
- console.log('Request:', request.url)
129
+ onRequest: async (request) => {
130
+ // 可以修改请求
130
131
  return request
131
132
  },
132
133
 
133
134
  // 响应拦截器
134
- onResponse: (response) => {
135
- console.log('Response:', response.status)
135
+ onResponse: async (response) => {
136
+ // 可以修改响应
136
137
  return response
137
138
  },
138
139
 
139
- // 错误处理
140
+ // 错误回调
140
141
  onError: (error) => {
141
- console.error('Error:', error.message)
142
+ console.error('API Error:', error.code, error.message)
142
143
  }
143
144
  })
144
145
  ```
145
146
 
146
- ### HTTP 方法
147
-
148
- ```typescript
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' })
163
- ```
164
-
165
- ### 路径参数
166
-
167
- ```typescript
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
- ```
173
-
174
- ### 请求取消
147
+ ## SSE 流式响应
175
148
 
176
149
  ```typescript
177
- const controller = new AbortController()
150
+ import { defineRoute, Type } from 'vafast'
178
151
 
179
- // 发起请求
180
- const promise = api.users.get({ page: 1 }, { signal: controller.signal })
181
-
182
- // 取消请求
183
- controller.abort()
184
-
185
- const result = await promise
186
- if (result.error) {
187
- console.log('请求已取消')
152
+ // 服务端定义 SSE 路由
153
+ const routeDefinitions = [
154
+ defineRoute({
155
+ method: 'GET',
156
+ path: '/chat/stream',
157
+ schema: { query: Type.Object({ prompt: Type.String() }) },
158
+ handler: async function* ({ query }) {
159
+ yield { data: { text: 'Hello' } }
160
+ yield { data: { text: 'World' } }
161
+ }
162
+ })
163
+ ] as const
164
+
165
+ // 客户端(手动标记 SSE)
166
+ type Api = {
167
+ chat: {
168
+ stream: {
169
+ get: {
170
+ query: { prompt: string }
171
+ return: { text: string }
172
+ sse: { readonly __brand: 'SSE' }
173
+ }
174
+ }
175
+ }
188
176
  }
189
- ```
190
177
 
191
- ### 单次请求配置
178
+ const api = eden<Api>('http://localhost:3000')
192
179
 
193
- ```typescript
194
- // 覆盖全局配置
195
- const result = await api.users.get({ page: 1 }, {
196
- headers: { 'X-Custom-Header': 'value' },
197
- timeout: 10000,
198
- signal: abortController.signal
199
- })
200
- ```
201
-
202
- ## 🌊 SSE 流式响应
203
-
204
- ### 基本用法
205
-
206
- ```typescript
180
+ // 订阅 SSE 流
207
181
  const subscription = api.chat.stream.subscribe(
208
- { prompt: 'Hello AI!' }, // query 参数
182
+ { prompt: 'Hello' },
209
183
  {
210
- onOpen: () => console.log('连接已建立'),
211
- onMessage: (data) => console.log('收到:', data),
212
- onError: (err) => console.error('错误:', err),
213
- onClose: () => console.log('连接已关闭'),
184
+ onMessage: (data) => {
185
+ console.log('收到消息:', data.text)
186
+ },
187
+ onError: (error) => {
188
+ console.error('错误:', error.message)
189
+ },
190
+ onOpen: () => console.log('连接建立'),
191
+ onClose: () => console.log('连接关闭'),
214
192
  onReconnect: (attempt, max) => console.log(`重连中 ${attempt}/${max}`),
215
193
  onMaxReconnects: () => console.log('达到最大重连次数')
216
194
  },
217
195
  {
218
- reconnectInterval: 3000, // 重连间隔(毫秒)
196
+ reconnectInterval: 3000, // 重连间隔
219
197
  maxReconnects: 5 // 最大重连次数
220
198
  }
221
199
  )
@@ -224,75 +202,51 @@ const subscription = api.chat.stream.subscribe(
224
202
  subscription.unsubscribe()
225
203
  ```
226
204
 
227
- ### SSE 特性
228
-
229
- - ✅ **自动重连** - 网络断开后自动重连
230
- - ✅ **断点续传** - 使用 `Last-Event-ID` 从断点继续
231
- - ✅ **可配置重连策略** - 自定义重连间隔和最大次数
232
- - ✅ **事件类型支持** - 支持自定义事件名称
233
-
234
- ## 🔧 类型定义
235
-
236
- ### `InferEden<T>`
237
-
238
- 从 vafast 路由数组推断 API 契约类型。
205
+ ## 请求取消
239
206
 
240
207
  ```typescript
241
- import { InferEden } from '@vafast/api-client'
242
-
243
- const routes = defineRoutes([...])
244
- type Api = InferEden<typeof routes>
245
- ```
246
-
247
- ### `EdenClient<T>`
248
-
249
- Eden 客户端类型。
208
+ const controller = new AbortController()
250
209
 
251
- ```typescript
252
- import { EdenClient } from '@vafast/api-client'
210
+ const promise = api.users.get({ page: 1 }, { signal: controller.signal })
253
211
 
254
- type MyClient = EdenClient<Api>
212
+ // 取消请求
213
+ controller.abort()
255
214
  ```
256
215
 
257
- ### `ApiResponse<T>`
258
-
259
- API 响应类型。
260
-
261
- ```typescript
262
- interface ApiResponse<T> {
263
- data: T | null // 响应数据
264
- error: Error | null // 错误信息
265
- status: number // HTTP 状态码
266
- headers: Headers // 响应头
267
- response: Response // 原始 Response
268
- }
269
- ```
216
+ ## API
270
217
 
271
- ### `RequestConfig`
218
+ ### `eden<T>(baseURL, config?)`
272
219
 
273
- 请求配置类型。
220
+ 创建 API 客户端实例。
274
221
 
275
- ```typescript
276
- interface RequestConfig {
277
- headers?: Record<string, string> // 请求头
278
- timeout?: number // 超时(毫秒)
279
- signal?: AbortSignal // 取消信号
280
- }
281
- ```
222
+ - `baseURL` - API 基础 URL
223
+ - `config` - 可选配置
224
+ - `headers` - 默认请求头
225
+ - `timeout` - 请求超时(毫秒)
226
+ - `onRequest` - 请求拦截器
227
+ - `onResponse` - 响应拦截器
228
+ - `onError` - 错误回调
282
229
 
283
- ## 📁 示例
230
+ ### `InferEden<T>`
284
231
 
285
- 查看 `example/` 目录获取完整示例:
232
+ `defineRoute` 数组推断 Eden 契约类型。
286
233
 
287
- - `auto-infer.ts` - 自动类型推断示例
288
- - `test-sse.ts` - SSE 流式响应测试
234
+ ```typescript
235
+ import { defineRoute, Type } from 'vafast'
236
+ import { InferEden } from '@vafast/api-client'
289
237
 
290
- ## 🧪 测试
238
+ const routeDefinitions = [
239
+ defineRoute({
240
+ method: 'GET',
241
+ path: '/users',
242
+ schema: { query: Type.Object({ page: Type.Number() }) },
243
+ handler: ({ query }) => ({ users: [], page: query.page })
244
+ })
245
+ ] as const
291
246
 
292
- ```bash
293
- npm test
247
+ type Api = InferEden<typeof routeDefinitions>
294
248
  ```
295
249
 
296
- ## 📄 许可证
250
+ ## License
297
251
 
298
252
  MIT
package/dist/index.d.mts CHANGED
@@ -16,114 +16,109 @@ interface RequestConfig {
16
16
  signal?: AbortSignal;
17
17
  }
18
18
  /**
19
- * API 响应
19
+ * API 错误 - Go 风格结构化错误
20
+ */
21
+ interface ApiError {
22
+ /** 错误码(HTTP 状态码或业务错误码) */
23
+ code: number;
24
+ /** 错误消息 */
25
+ message: string;
26
+ }
27
+ /**
28
+ * API 响应 - Go 风格
29
+ *
30
+ * @example
31
+ * ```typescript
32
+ * const { data, error } = await api.users.get()
33
+ * if (error) {
34
+ * console.error(`错误码: ${error.code}, 消息: ${error.message}`)
35
+ * return
36
+ * }
37
+ * console.log(data)
38
+ * ```
20
39
  */
21
40
  interface ApiResponse<T = unknown> {
22
- /** 响应数据 */
41
+ /** 响应数据,成功时有值,失败时为 null */
23
42
  data: T | null;
24
- /** 错误信息 */
25
- error: Error | null;
26
- /** HTTP 状态码 */
27
- status: number;
28
- /** 响应头 */
29
- headers: Headers;
30
- /** 原始响应对象 */
31
- response: Response;
43
+ /** 错误信息,成功时为 null,失败时有值 */
44
+ error: ApiError | null;
32
45
  }
33
46
  //#endregion
34
47
  //#region src/core/eden.d.ts
48
+
35
49
  interface EdenConfig {
36
50
  headers?: Record<string, string>;
37
51
  onRequest?: (request: Request) => Request | Promise<Request>;
38
52
  onResponse?: <T>(response: ApiResponse<T>) => ApiResponse<T> | Promise<ApiResponse<T>>;
39
- onError?: (error: Error) => void;
53
+ onError?: (error: ApiError) => void;
40
54
  timeout?: number;
41
55
  }
42
- /**
43
- * SSE 事件
44
- */
45
56
  interface SSEEvent<T = unknown> {
46
57
  event?: string;
47
58
  data: T;
48
59
  id?: string;
49
60
  retry?: number;
50
61
  }
51
- /**
52
- * SSE 订阅选项
53
- */
54
62
  interface SSESubscribeOptions {
55
- /** 自定义请求头 */
56
63
  headers?: Record<string, string>;
57
- /** 重连间隔(毫秒) */
58
64
  reconnectInterval?: number;
59
- /** 最大重连次数 */
60
65
  maxReconnects?: number;
61
- /** 连接超时(毫秒) */
62
66
  timeout?: number;
63
67
  }
64
- /**
65
- * SSE 订阅结果
66
- */
67
68
  interface SSESubscription<T = unknown> {
68
- /** 取消订阅 */
69
69
  unsubscribe: () => void;
70
- /** 是否已连接 */
71
70
  readonly connected: boolean;
72
71
  }
73
- /**
74
- * 从 TypeBox Schema 提取静态类型
75
- * 使用 TypeBox 内部的 static 属性提取类型
76
- */
72
+ /** 从 TypeBox Schema 提取静态类型 */
77
73
  type InferStatic<T> = T extends {
78
74
  static: infer S;
79
75
  } ? S : T;
80
- /** 从 InferableHandler 提取返回类型 */
81
- type ExtractReturn<T> = T extends {
82
- __returnType: infer R;
83
- } ? R : unknown;
84
- /** 从 InferableHandler 提取 Schema */
85
- type ExtractSchema<T> = T extends {
86
- __schema: infer S;
87
- } ? S : {};
88
- /** 检查是否是 SSE Handler(使用品牌类型检测) */
89
- type IsSSEHandler<T> = T extends {
90
- __sse: {
91
- readonly __brand: 'SSE';
92
- };
93
- } ? true : false;
94
- /** 从 Schema 提取各部分类型 */
95
- type GetQuery<S> = S extends {
76
+ /** 从 Schema 对象提取各部分类型 */
77
+ type GetSchemaQuery<S> = S extends {
96
78
  query: infer Q;
97
79
  } ? InferStatic<Q> : undefined;
98
- type GetBody<S> = S extends {
80
+ type GetSchemaBody<S> = S extends {
99
81
  body: infer B;
100
82
  } ? InferStatic<B> : undefined;
101
- type GetParams<S> = S extends {
83
+ type GetSchemaParams<S> = S extends {
102
84
  params: infer P;
103
85
  } ? InferStatic<P> : undefined;
86
+ /**
87
+ * 从 handler 函数推断返回类型
88
+ * handler: (ctx) => TReturn | Promise<TReturn>
89
+ */
90
+ type InferHandlerReturn<H$1> = H$1 extends ((...args: never[]) => infer R) ? R extends Promise<infer T> ? T : R : unknown;
104
91
  /** 移除开头斜杠:/users → users */
105
92
  type TrimSlash<P$1 extends string> = P$1 extends `/${infer R}` ? R : P$1;
106
93
  /** 检查是否是动态参数段::id → true */
107
94
  type IsDynamicSegment<S extends string> = S extends `:${string}` ? true : false;
108
- /** 清理 undefined 字段 */
109
95
  type Clean<T> = { [K in keyof T as T[K] extends undefined ? never : K]: T[K] };
110
- /** SSE 标记类型 */
111
96
  type SSEBrand = {
112
97
  readonly __brand: 'SSE';
113
98
  };
114
- /** 从路由构建方法定义 */
115
- type BuildMethodDef<R extends {
116
- readonly handler: unknown;
117
- }> = IsSSEHandler<R['handler']> extends true ? Clean<{
118
- query: GetQuery<ExtractSchema<R['handler']>>;
119
- params: GetParams<ExtractSchema<R['handler']>>;
120
- return: ExtractReturn<R['handler']>;
121
- sse: SSEBrand;
99
+ /**
100
+ * defineRoute 返回的路由配置构建方法定义
101
+ *
102
+ * defineRoute 返回的 LeafRouteConfig 结构:
103
+ * {
104
+ * method: TMethod,
105
+ * path: TPath,
106
+ * schema?: TSchema,
107
+ * handler: (ctx) => TReturn | Promise<TReturn>
108
+ * }
109
+ */
110
+ type BuildMethodDef<R> = R extends {
111
+ readonly schema?: infer TSchema;
112
+ readonly handler: infer THandler;
113
+ } ? Clean<{
114
+ query: GetSchemaQuery<TSchema>;
115
+ body: GetSchemaBody<TSchema>;
116
+ params: GetSchemaParams<TSchema>;
117
+ return: InferHandlerReturn<THandler>;
122
118
  }> : Clean<{
123
- query: GetQuery<ExtractSchema<R['handler']>>;
124
- body: GetBody<ExtractSchema<R['handler']>>;
125
- params: GetParams<ExtractSchema<R['handler']>>;
126
- return: ExtractReturn<R['handler']>;
119
+ return: R extends {
120
+ readonly handler: infer H;
121
+ } ? InferHandlerReturn<H> : unknown;
127
122
  }>;
128
123
  /**
129
124
  * 递归构建嵌套路径结构
@@ -134,79 +129,55 @@ type BuildPath<Path extends string, Method extends string, Def> = Path extends `
134
129
  ':id': BuildPath<Rest, Method, Def>;
135
130
  } : { [K in First]: BuildPath<Rest, Method, Def> } : IsDynamicSegment<Path> extends true ? {
136
131
  ':id': { [M in Method]: Def };
137
- } : { [K in Path]: { [M in Method]: Def } };
132
+ } : Path extends '' ? { [M in Method]: Def } : { [K in Path]: { [M in Method]: Def } };
138
133
  /**
139
134
  * 从单个路由生成嵌套类型结构
140
135
  */
141
- type RouteToTree<R extends {
142
- readonly method: string;
143
- readonly path: string;
144
- readonly handler: unknown;
145
- }> = BuildPath<TrimSlash<R['path']>, Lowercase<R['method']>, BuildMethodDef<R>>;
136
+ type RouteToTree<R> = R extends {
137
+ readonly method: infer M extends string;
138
+ readonly path: infer P extends string;
139
+ } ? BuildPath<TrimSlash<P>, Lowercase<M>, BuildMethodDef<R>> : {};
146
140
  type DeepMerge<A, B$1> = { [K in keyof A | keyof B$1]: K extends keyof A & keyof B$1 ? A[K] extends object ? B$1[K] extends object ? DeepMerge<A[K], B$1[K]> : A[K] & B$1[K] : A[K] & B$1[K] : K extends keyof A ? A[K] : K extends keyof B$1 ? B$1[K] : never };
147
141
  /** 递归合并路由数组为单一类型结构 */
148
- type MergeRoutes<T extends readonly unknown[]> = T extends readonly [infer First extends {
149
- readonly method: string;
150
- readonly path: string;
151
- readonly handler: unknown;
152
- }] ? RouteToTree<First> : T extends readonly [infer First extends {
153
- readonly method: string;
154
- readonly path: string;
155
- readonly handler: unknown;
156
- }, ...infer Rest extends readonly {
157
- readonly method: string;
158
- readonly path: string;
159
- readonly handler: unknown;
160
- }[]] ? DeepMerge<RouteToTree<First>, MergeRoutes<Rest>> : {};
142
+ type MergeRoutes<T extends readonly unknown[]> = T extends readonly [infer First] ? RouteToTree<First> : T extends readonly [infer First, ...infer Rest] ? DeepMerge<RouteToTree<First>, MergeRoutes<Rest>> : {};
161
143
  /**
162
- * 从 vafast 路由数组自动推断 Eden 契约
144
+ * 从 defineRoute 数组自动推断 Eden 契约
163
145
  *
164
146
  * @example
165
147
  * ```typescript
166
- * import { defineRoutes, createHandler, Type } from 'vafast'
167
- * import { eden, InferEden } from 'vafast-api-client'
148
+ * import { defineRoute, defineRoutes, Type } from 'vafast'
149
+ * import { eden, InferEden } from '@vafast/api-client'
168
150
  *
169
- * // defineRoutes() 自动保留字面量类型,无需 as const
170
- * const routes = defineRoutes([
171
- * {
151
+ * // 定义路由(使用 as const 保留字面量类型)
152
+ * const routeDefinitions = [
153
+ * defineRoute({
172
154
  * method: 'GET',
173
155
  * path: '/users',
174
- * handler: createHandler(
175
- * { query: Type.Object({ page: Type.Number() }) },
176
- * async ({ query }) => ({ users: [], total: 0 })
177
- * )
178
- * },
179
- * {
180
- * method: 'GET',
181
- * path: '/chat/stream',
182
- * handler: createSSEHandler(
183
- * { query: Type.Object({ prompt: Type.String() }) },
184
- * async function* ({ query }) {
185
- * yield { data: { text: 'Hello' } }
186
- * }
187
- * )
188
- * }
189
- * ])
156
+ * schema: { query: Type.Object({ page: Type.Number() }) },
157
+ * handler: ({ query }) => ({ users: [], total: 0 })
158
+ * }),
159
+ * defineRoute({
160
+ * method: 'POST',
161
+ * path: '/users',
162
+ * schema: { body: Type.Object({ name: Type.String() }) },
163
+ * handler: ({ body }) => ({ id: '1', name: body.name })
164
+ * })
165
+ * ] as const
190
166
  *
191
- * type Api = InferEden<typeof routes>
192
- * const api = eden<Api>('http://localhost:3000')
167
+ * // 服务端
168
+ * const routes = defineRoutes(routeDefinitions)
169
+ * const server = new Server(routes)
193
170
  *
194
- * // 普通请求
195
- * const { data } = await api.users.get({ page: 1 })
171
+ * // 客户端类型推断
172
+ * type Api = InferEden<typeof routeDefinitions>
173
+ * const api = eden<Api>('http://localhost:3000')
196
174
  *
197
- * // SSE 流式请求
198
- * api.chat.stream.subscribe({ prompt: 'Hi' }, {
199
- * onMessage: (data) => console.log(data),
200
- * onError: (err) => console.error(err)
201
- * })
175
+ * // 类型安全的调用
176
+ * const { data } = await api.users.get({ page: 1 }) // ✅ query 类型推断
177
+ * const { data: user } = await api.users.post({ name: 'John' }) // ✅ body 类型推断
202
178
  * ```
203
179
  */
204
- type InferEden<T extends readonly {
205
- readonly method: string;
206
- readonly path: string;
207
- readonly handler: unknown;
208
- }[]> = MergeRoutes<T>;
209
- /** HTTP 方法定义 */
180
+ type InferEden<T extends readonly unknown[]> = MergeRoutes<T>;
210
181
  interface MethodDef {
211
182
  query?: unknown;
212
183
  body?: unknown;
@@ -214,22 +185,14 @@ interface MethodDef {
214
185
  return: unknown;
215
186
  sse?: SSEBrand;
216
187
  }
217
- /** SSE 订阅回调 */
218
188
  interface SSECallbacks<T> {
219
- /** 收到消息 */
220
189
  onMessage: (data: T) => void;
221
- /** 发生错误 */
222
- onError?: (error: Error) => void;
223
- /** 连接打开 */
190
+ onError?: (error: ApiError) => void;
224
191
  onOpen?: () => void;
225
- /** 连接关闭 */
226
192
  onClose?: () => void;
227
- /** 正在重连 */
228
193
  onReconnect?: (attempt: number, maxAttempts: number) => void;
229
- /** 达到最大重连次数 */
230
194
  onMaxReconnects?: () => void;
231
195
  }
232
- /** 从方法定义提取调用签名 */
233
196
  type MethodCall<M$1 extends MethodDef, HasParams extends boolean = false> = M$1 extends {
234
197
  sse: SSEBrand;
235
198
  } ? M$1 extends {
@@ -241,56 +204,24 @@ type MethodCall<M$1 extends MethodDef, HasParams extends boolean = false> = M$1
241
204
  } ? (query?: Q, config?: RequestConfig) => Promise<ApiResponse<M$1['return']>> : M$1 extends {
242
205
  body: infer B;
243
206
  } ? (body: B, config?: RequestConfig) => Promise<ApiResponse<M$1['return']>> : (config?: RequestConfig) => Promise<ApiResponse<M$1['return']>>;
244
- /** 检查是否是 SSE 端点(检测品牌类型标记) */
245
207
  type IsSSEEndpoint<M$1> = M$1 extends {
246
208
  sse: {
247
209
  readonly __brand: 'SSE';
248
210
  };
249
211
  } ? true : false;
250
- /** 端点类型 - 包含 subscribe 方法用于 SSE */
251
212
  type Endpoint<T, HasParams extends boolean = false> = { [K in 'get' | 'post' | 'put' | 'patch' | 'delete' as T extends { [P in K]: MethodDef } ? K : never]: T extends { [P in K]: infer M extends MethodDef } ? MethodCall<M, HasParams> : never } & (T extends {
252
213
  get: infer M extends MethodDef;
253
214
  } ? IsSSEEndpoint<M> extends true ? {
254
215
  subscribe: MethodCall<M, HasParams>;
255
216
  } : {} : {});
256
- /** HTTP 方法名 */
257
217
  type HTTPMethods = 'get' | 'post' | 'put' | 'patch' | 'delete';
258
- /** 递归构建客户端类型 */
259
218
  type EdenClient<T, HasParams extends boolean = false> = { [K in keyof T as K extends HTTPMethods | `:${string}` ? never : K]: T[K] extends {
260
219
  ':id': infer Child;
261
220
  } ? ((params: Record<string, string>) => EdenClient<Child, true>) & EdenClient<T[K], false> : EdenClient<T[K], false> } & Endpoint<T, HasParams>;
262
221
  /**
263
222
  * 创建 Eden 风格的类型安全 API 客户端
264
- *
265
- * @param baseURL - API 基础 URL
266
- * @param config - 可选配置
267
- * @returns 类型安全的 API 客户端
268
- *
269
- * @example
270
- * ```typescript
271
- * // 使用自动推断的契约(无需 as const)
272
- * const routes = defineRoutes([
273
- * route('GET', '/users', createHandler(...)),
274
- * route('GET', '/chat/stream', createSSEHandler(...))
275
- * ])
276
- *
277
- * type Api = InferEden<typeof routes>
278
- * const api = eden<Api>('http://localhost:3000')
279
- *
280
- * // 普通请求
281
- * const { data } = await api.users.get({ page: 1 })
282
- *
283
- * // SSE 流式请求
284
- * const sub = api.chat.stream.subscribe({ prompt: 'Hello' }, {
285
- * onMessage: (data) => console.log(data),
286
- * onError: (err) => console.error(err)
287
- * })
288
- *
289
- * // 取消订阅
290
- * sub.unsubscribe()
291
- * ```
292
223
  */
293
224
  declare function eden<T>(baseURL: string, config?: EdenConfig): EdenClient<T>;
294
225
  //#endregion
295
- export { type ApiResponse, type EdenClient, type EdenConfig, type HTTPMethod, type InferEden, type RequestConfig, type SSEEvent, type SSESubscribeOptions, type SSESubscription, eden };
226
+ export { type ApiError, type ApiResponse, type EdenClient, type EdenConfig, type HTTPMethod, type InferEden, type RequestConfig, type SSEEvent, type SSESubscribeOptions, type SSESubscription, eden };
296
227
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/types/index.ts","../src/core/eden.ts"],"sourcesContent":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/types/index.ts","../src/core/eden.ts"],"sourcesContent":[],"mappings":"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"}
package/dist/index.mjs CHANGED
@@ -1,7 +1,4 @@
1
1
  //#region src/core/eden.ts
2
- /**
3
- * 解析 SSE 事件流
4
- */
5
2
  async function* parseSSEStream(reader) {
6
3
  const decoder = new TextDecoder();
7
4
  let buffer = "";
@@ -32,34 +29,6 @@ async function* parseSSEStream(reader) {
32
29
  }
33
30
  /**
34
31
  * 创建 Eden 风格的类型安全 API 客户端
35
- *
36
- * @param baseURL - API 基础 URL
37
- * @param config - 可选配置
38
- * @returns 类型安全的 API 客户端
39
- *
40
- * @example
41
- * ```typescript
42
- * // 使用自动推断的契约(无需 as const)
43
- * const routes = defineRoutes([
44
- * route('GET', '/users', createHandler(...)),
45
- * route('GET', '/chat/stream', createSSEHandler(...))
46
- * ])
47
- *
48
- * type Api = InferEden<typeof routes>
49
- * const api = eden<Api>('http://localhost:3000')
50
- *
51
- * // 普通请求
52
- * const { data } = await api.users.get({ page: 1 })
53
- *
54
- * // SSE 流式请求
55
- * const sub = api.chat.stream.subscribe({ prompt: 'Hello' }, {
56
- * onMessage: (data) => console.log(data),
57
- * onError: (err) => console.error(err)
58
- * })
59
- *
60
- * // 取消订阅
61
- * sub.unsubscribe()
62
- * ```
63
32
  */
64
33
  function eden(baseURL, config) {
65
34
  const { headers: defaultHeaders, onRequest, onResponse, onError, timeout } = config ?? {};
@@ -95,25 +64,33 @@ function eden(baseURL, config) {
95
64
  let responseData = null;
96
65
  if (contentType?.includes("application/json")) responseData = await response.json();
97
66
  else if (contentType?.includes("text/")) responseData = await response.text();
98
- let result = {
67
+ let result;
68
+ if (response.ok) result = {
99
69
  data: responseData,
100
- error: response.ok ? null : /* @__PURE__ */ new Error(`HTTP ${response.status}`),
101
- status: response.status,
102
- headers: response.headers,
103
- response
70
+ error: null
104
71
  };
72
+ else {
73
+ const errorBody = responseData;
74
+ result = {
75
+ data: null,
76
+ error: {
77
+ code: errorBody?.code ?? response.status,
78
+ message: errorBody?.message ?? `HTTP ${response.status}`
79
+ }
80
+ };
81
+ }
105
82
  if (onResponse) result = await onResponse(result);
106
- if (!response.ok && onError) onError(result.error);
83
+ if (result.error && onError) onError(result.error);
107
84
  return result;
108
85
  } catch (error) {
109
- const err = error instanceof Error ? error : new Error(String(error));
110
- if (onError) onError(err);
86
+ const apiError = {
87
+ code: 0,
88
+ message: (error instanceof Error ? error : new Error(String(error))).message || "网络错误"
89
+ };
90
+ if (onError) onError(apiError);
111
91
  return {
112
92
  data: null,
113
- error: err,
114
- status: 0,
115
- headers: new Headers(),
116
- response: new Response()
93
+ error: apiError
117
94
  };
118
95
  }
119
96
  }
@@ -152,7 +129,10 @@ function eden(baseURL, config) {
152
129
  const reader = response.body.getReader();
153
130
  for await (const event of parseSSEStream(reader)) {
154
131
  if (event.id) lastEventId = event.id;
155
- if (event.event === "error") callbacks.onError?.(new Error(String(event.data)));
132
+ if (event.event === "error") callbacks.onError?.({
133
+ code: -1,
134
+ message: String(event.data)
135
+ });
156
136
  else callbacks.onMessage(event.data);
157
137
  }
158
138
  connected = false;
@@ -160,7 +140,11 @@ function eden(baseURL, config) {
160
140
  } catch (error) {
161
141
  connected = false;
162
142
  if (error.name === "AbortError" || isUnsubscribed) return;
163
- callbacks.onError?.(error);
143
+ const err = error instanceof Error ? error : new Error(String(error));
144
+ callbacks.onError?.({
145
+ code: 0,
146
+ message: err.message || "SSE 连接错误"
147
+ });
164
148
  if (reconnectCount < maxReconnects) {
165
149
  reconnectCount++;
166
150
  callbacks.onReconnect?.(reconnectCount, maxReconnects);
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/core/eden.ts"],"sourcesContent":["/**\n * Eden 风格 API 客户端\n * \n * 最自然的链式调用:\n * - api.users.get() // GET /users\n * - api.users.post({ name }) // POST /users\n * - api.users({ id }).get() // GET /users/:id\n * - api.users({ id }).delete() // DELETE /users/:id\n * - api.chat.stream.subscribe() // SSE 流式响应\n */\n\nimport type { ApiResponse, RequestConfig } from '../types'\n\n// ============= 配置 =============\n\nexport interface EdenConfig {\n headers?: Record<string, string>\n onRequest?: (request: Request) => Request | Promise<Request>\n onResponse?: <T>(response: ApiResponse<T>) => ApiResponse<T> | Promise<ApiResponse<T>>\n onError?: (error: Error) => void\n timeout?: number\n}\n\n// ============= SSE 类型 =============\n\n/**\n * SSE 事件\n */\nexport interface SSEEvent<T = unknown> {\n event?: string\n data: T\n id?: string\n retry?: number\n}\n\n/**\n * SSE 订阅选项\n */\nexport interface SSESubscribeOptions {\n /** 自定义请求头 */\n headers?: Record<string, string>\n /** 重连间隔(毫秒) */\n reconnectInterval?: number\n /** 最大重连次数 */\n maxReconnects?: number\n /** 连接超时(毫秒) */\n timeout?: number\n}\n\n/**\n * SSE 订阅结果\n */\nexport interface SSESubscription<T = unknown> {\n /** 取消订阅 */\n unsubscribe: () => void\n /** 是否已连接 */\n readonly connected: boolean\n}\n\n// ============= 基础类型工具 =============\n\n/**\n * 从 TypeBox Schema 提取静态类型\n * 使用 TypeBox 内部的 static 属性提取类型\n */\ntype InferStatic<T> = T extends { static: infer S } ? S : T\n\n/** 从 InferableHandler 提取返回类型 */\ntype ExtractReturn<T> = T extends { __returnType: infer R } ? R : unknown\n\n/** 从 InferableHandler 提取 Schema */\ntype ExtractSchema<T> = T extends { __schema: infer S } ? S : {}\n\n/** 检查是否是 SSE Handler(使用品牌类型检测) */\ntype IsSSEHandler<T> = T extends { __sse: { readonly __brand: 'SSE' } } ? true : false\n\n/** 从 Schema 提取各部分类型 */\ntype GetQuery<S> = S extends { query: infer Q } ? InferStatic<Q> : undefined\ntype GetBody<S> = S extends { body: infer B } ? InferStatic<B> : undefined\ntype GetParams<S> = S extends { params: infer P } ? InferStatic<P> : undefined\n\n// ============= 路径处理 =============\n\n/** 移除开头斜杠:/users → users */\ntype TrimSlash<P extends string> = P extends `/${infer R}` ? R : P\n\n/** 获取第一段:users/posts → users */\ntype Head<P extends string> = P extends `${infer H}/${string}` ? H : P\n\n/** 获取剩余段:users/posts → posts */\ntype Tail<P extends string> = P extends `${string}/${infer T}` ? T : never\n\n/** 检查是否是动态参数段::id → true */\ntype IsDynamicSegment<S extends string> = S extends `:${string}` ? true : false\n\n// ============= 核心类型推断 =============\n\n/** 清理 undefined 字段 */\ntype Clean<T> = { [K in keyof T as T[K] extends undefined ? never : K]: T[K] }\n\n/** SSE 标记类型 */\ntype SSEBrand = { readonly __brand: 'SSE' }\n\n/** 从路由构建方法定义 */\ntype BuildMethodDef<R extends { readonly handler: unknown }> = \n IsSSEHandler<R['handler']> extends true\n ? Clean<{\n query: GetQuery<ExtractSchema<R['handler']>>\n params: GetParams<ExtractSchema<R['handler']>>\n return: ExtractReturn<R['handler']>\n sse: SSEBrand\n }>\n : Clean<{\n query: GetQuery<ExtractSchema<R['handler']>>\n body: GetBody<ExtractSchema<R['handler']>>\n params: GetParams<ExtractSchema<R['handler']>>\n return: ExtractReturn<R['handler']>\n }>\n\n/**\n * 递归构建嵌套路径结构\n * \n * 处理动态参数:/users/:id → { users: { ':id': { ... } } }\n */\ntype BuildPath<Path extends string, Method extends string, Def> =\n Path extends `${infer First}/${infer Rest}`\n ? IsDynamicSegment<First> extends true\n ? { ':id': BuildPath<Rest, Method, Def> }\n : { [K in First]: BuildPath<Rest, Method, Def> }\n : IsDynamicSegment<Path> extends true\n ? { ':id': { [M in Method]: Def } }\n : { [K in Path]: { [M in Method]: Def } }\n\n/**\n * 从单个路由生成嵌套类型结构\n */\ntype RouteToTree<R extends { readonly method: string; readonly path: string; readonly handler: unknown }> =\n BuildPath<TrimSlash<R['path']>, Lowercase<R['method']>, BuildMethodDef<R>>\n\n// ============= 深度合并多个路由 =============\n\ntype DeepMerge<A, B> = {\n [K in keyof A | keyof B]: \n K extends keyof A & keyof B\n ? A[K] extends object\n ? B[K] extends object\n ? DeepMerge<A[K], B[K]>\n : A[K] & B[K]\n : A[K] & B[K]\n : K extends keyof A\n ? A[K]\n : K extends keyof B\n ? B[K]\n : never\n}\n\n/** 递归合并路由数组为单一类型结构 */\ntype MergeRoutes<T extends readonly unknown[]> = \n T extends readonly [infer First extends { readonly method: string; readonly path: string; readonly handler: unknown }]\n ? RouteToTree<First>\n : T extends readonly [\n infer First extends { readonly method: string; readonly path: string; readonly handler: unknown }, \n ...infer Rest extends readonly { readonly method: string; readonly path: string; readonly handler: unknown }[]\n ]\n ? DeepMerge<RouteToTree<First>, MergeRoutes<Rest>>\n : {}\n\n/**\n * 从 vafast 路由数组自动推断 Eden 契约\n * \n * @example\n * ```typescript\n * import { defineRoutes, createHandler, Type } from 'vafast'\n * import { eden, InferEden } from 'vafast-api-client'\n * \n * // ✨ defineRoutes() 自动保留字面量类型,无需 as const\n * const routes = defineRoutes([\n * {\n * method: 'GET',\n * path: '/users',\n * handler: createHandler(\n * { query: Type.Object({ page: Type.Number() }) },\n * async ({ query }) => ({ users: [], total: 0 })\n * )\n * },\n * {\n * method: 'GET',\n * path: '/chat/stream',\n * handler: createSSEHandler(\n * { query: Type.Object({ prompt: Type.String() }) },\n * async function* ({ query }) {\n * yield { data: { text: 'Hello' } }\n * }\n * )\n * }\n * ])\n * \n * type Api = InferEden<typeof routes>\n * const api = eden<Api>('http://localhost:3000')\n * \n * // 普通请求\n * const { data } = await api.users.get({ page: 1 })\n * \n * // SSE 流式请求\n * api.chat.stream.subscribe({ prompt: 'Hi' }, {\n * onMessage: (data) => console.log(data),\n * onError: (err) => console.error(err)\n * })\n * ```\n */\nexport type InferEden<T extends readonly { readonly method: string; readonly path: string; readonly handler: unknown }[]> = \n MergeRoutes<T>\n\n// ============= 契约类型(手动定义时使用) =============\n\n/** HTTP 方法定义 */\ninterface MethodDef {\n query?: unknown\n body?: unknown\n params?: unknown\n return: unknown\n sse?: SSEBrand\n}\n\n/** 路由节点 */\ntype RouteNode = {\n get?: MethodDef\n post?: MethodDef\n put?: MethodDef\n patch?: MethodDef\n delete?: MethodDef\n ':id'?: RouteNode\n [key: string]: MethodDef | RouteNode | undefined\n}\n\n// ============= 客户端类型 =============\n\n/** SSE 订阅回调 */\ninterface SSECallbacks<T> {\n /** 收到消息 */\n onMessage: (data: T) => void\n /** 发生错误 */\n onError?: (error: Error) => void\n /** 连接打开 */\n onOpen?: () => void\n /** 连接关闭 */\n onClose?: () => void\n /** 正在重连 */\n onReconnect?: (attempt: number, maxAttempts: number) => void\n /** 达到最大重连次数 */\n onMaxReconnects?: () => void\n}\n\n/** 从方法定义提取调用签名 */\ntype MethodCall<M extends MethodDef, HasParams extends boolean = false> = \n M extends { sse: SSEBrand }\n ? M extends { query: infer Q }\n ? (query: Q, callbacks: SSECallbacks<M['return']>, options?: SSESubscribeOptions) => SSESubscription<M['return']>\n : (callbacks: SSECallbacks<M['return']>, options?: SSESubscribeOptions) => SSESubscription<M['return']>\n : HasParams extends true\n ? M extends { body: infer B }\n ? (body: B, config?: RequestConfig) => Promise<ApiResponse<M['return']>>\n : (config?: RequestConfig) => Promise<ApiResponse<M['return']>>\n : M extends { query: infer Q }\n ? (query?: Q, config?: RequestConfig) => Promise<ApiResponse<M['return']>>\n : M extends { body: infer B }\n ? (body: B, config?: RequestConfig) => Promise<ApiResponse<M['return']>>\n : (config?: RequestConfig) => Promise<ApiResponse<M['return']>>\n\n/** 检查是否是 SSE 端点(检测品牌类型标记) */\ntype IsSSEEndpoint<M> = M extends { sse: { readonly __brand: 'SSE' } } ? true : false\n\n/** 端点类型 - 包含 subscribe 方法用于 SSE */\ntype Endpoint<T, HasParams extends boolean = false> = \n // HTTP 方法\n {\n [K in 'get' | 'post' | 'put' | 'patch' | 'delete' as T extends { [P in K]: MethodDef } ? K : never]: \n T extends { [P in K]: infer M extends MethodDef } ? MethodCall<M, HasParams> : never\n } \n // SSE subscribe 方法(如果 GET 是 SSE)\n & (T extends { get: infer M extends MethodDef }\n ? IsSSEEndpoint<M> extends true \n ? { subscribe: MethodCall<M, HasParams> }\n : {}\n : {})\n\n/** 检查节点是否有动态参数子路由 */\ntype HasDynamicChild<T> = T extends { ':id': unknown } ? true : false\n\n/** HTTP 方法名 */\ntype HTTPMethods = 'get' | 'post' | 'put' | 'patch' | 'delete'\n\n/** 递归构建客户端类型 */\nexport type EdenClient<T, HasParams extends boolean = false> = {\n // 嵌套路径(排除 HTTP 方法和动态参数)\n [K in keyof T as K extends HTTPMethods | `:${string}` ? never : K]: \n T[K] extends { ':id': infer Child }\n // 有动态参数子路由\n ? ((params: Record<string, string>) => EdenClient<Child, true>) & EdenClient<T[K], false>\n // 普通嵌套路由\n : EdenClient<T[K], false>\n} & Endpoint<T, HasParams>\n\n// ============= SSE 解析器 =============\n\n/**\n * 解析 SSE 事件流\n */\nasync function* parseSSEStream(\n reader: ReadableStreamDefaultReader<Uint8Array>\n): AsyncGenerator<SSEEvent, void, unknown> {\n const decoder = new TextDecoder();\n let buffer = '';\n \n while (true) {\n const { done, value } = await reader.read();\n \n if (done) break;\n \n buffer += decoder.decode(value, { stream: true });\n \n // 按双换行分割事件\n const events = buffer.split('\\n\\n');\n buffer = events.pop() || ''; // 保留未完成的部分\n \n for (const eventStr of events) {\n if (!eventStr.trim()) continue;\n \n const event: SSEEvent = { data: '' };\n const lines = eventStr.split('\\n');\n let dataLines: string[] = [];\n \n for (const line of lines) {\n if (line.startsWith('event:')) {\n event.event = line.slice(6).trim();\n } else if (line.startsWith('data:')) {\n dataLines.push(line.slice(5).trim());\n } else if (line.startsWith('id:')) {\n event.id = line.slice(3).trim();\n } else if (line.startsWith('retry:')) {\n event.retry = parseInt(line.slice(6).trim(), 10);\n }\n }\n \n // 合并多行 data\n const dataStr = dataLines.join('\\n');\n \n // 尝试解析 JSON\n try {\n event.data = JSON.parse(dataStr);\n } catch {\n event.data = dataStr;\n }\n \n yield event;\n }\n }\n}\n\n// ============= 实现 =============\n\n/**\n * 创建 Eden 风格的类型安全 API 客户端\n * \n * @param baseURL - API 基础 URL\n * @param config - 可选配置\n * @returns 类型安全的 API 客户端\n * \n * @example\n * ```typescript\n * // 使用自动推断的契约(无需 as const)\n * const routes = defineRoutes([\n * route('GET', '/users', createHandler(...)),\n * route('GET', '/chat/stream', createSSEHandler(...))\n * ])\n * \n * type Api = InferEden<typeof routes>\n * const api = eden<Api>('http://localhost:3000')\n * \n * // 普通请求\n * const { data } = await api.users.get({ page: 1 })\n * \n * // SSE 流式请求\n * const sub = api.chat.stream.subscribe({ prompt: 'Hello' }, {\n * onMessage: (data) => console.log(data),\n * onError: (err) => console.error(err)\n * })\n * \n * // 取消订阅\n * sub.unsubscribe()\n * ```\n */\nexport function eden<T>(\n baseURL: string,\n config?: EdenConfig\n): EdenClient<T> {\n const { headers: defaultHeaders, onRequest, onResponse, onError, timeout } = config ?? {}\n\n // 发送普通请求\n async function request<TReturn>(\n method: string,\n path: string,\n data?: unknown,\n requestConfig?: RequestConfig\n ): Promise<ApiResponse<TReturn>> {\n const url = new URL(path, baseURL)\n \n // Query 参数\n if (method === 'GET' && data && typeof data === 'object') {\n for (const [key, value] of Object.entries(data as Record<string, unknown>)) {\n if (value !== undefined && value !== null) {\n url.searchParams.set(key, String(value))\n }\n }\n }\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n ...defaultHeaders,\n ...requestConfig?.headers,\n }\n\n // 支持用户传入的取消信号,或内部超时取消\n const controller = new AbortController()\n let timeoutId: ReturnType<typeof setTimeout> | undefined\n \n // 合并用户的 signal 和内部的超时 signal\n const userSignal = requestConfig?.signal\n const requestTimeout = requestConfig?.timeout ?? timeout\n \n if (userSignal) {\n // 如果用户已取消,直接中止\n if (userSignal.aborted) {\n controller.abort()\n } else {\n // 监听用户取消\n userSignal.addEventListener('abort', () => controller.abort())\n }\n }\n \n if (requestTimeout) {\n timeoutId = setTimeout(() => controller.abort(), requestTimeout)\n }\n\n const fetchOptions: RequestInit = { \n method, \n headers,\n signal: controller.signal \n }\n\n // Body\n if (method !== 'GET' && method !== 'HEAD' && data) {\n fetchOptions.body = JSON.stringify(data)\n }\n\n let req = new Request(url.toString(), fetchOptions)\n \n if (onRequest) {\n req = await onRequest(req)\n }\n\n try {\n const response = await fetch(req)\n \n if (timeoutId) clearTimeout(timeoutId)\n \n const contentType = response.headers.get('content-type')\n let responseData: TReturn | null = null\n \n if (contentType?.includes('application/json')) {\n responseData = await response.json()\n } else if (contentType?.includes('text/')) {\n responseData = await response.text() as unknown as TReturn\n }\n\n let result: ApiResponse<TReturn> = {\n data: responseData,\n error: response.ok ? null : new Error(`HTTP ${response.status}`),\n status: response.status,\n headers: response.headers,\n response,\n }\n\n if (onResponse) {\n result = await onResponse(result)\n }\n\n if (!response.ok && onError) {\n onError(result.error!)\n }\n\n return result\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error))\n if (onError) onError(err)\n return {\n data: null,\n error: err,\n status: 0,\n headers: new Headers(),\n response: new Response(),\n }\n }\n }\n\n // SSE 订阅(支持自动重连)\n function subscribe<TData>(\n path: string,\n query: Record<string, unknown> | undefined,\n callbacks: SSECallbacks<TData>,\n options?: SSESubscribeOptions\n ): SSESubscription<TData> {\n const url = new URL(path, baseURL)\n \n // 添加 query 参数\n if (query) {\n for (const [key, value] of Object.entries(query)) {\n if (value !== undefined && value !== null) {\n url.searchParams.set(key, String(value))\n }\n }\n }\n\n let abortController: AbortController | null = new AbortController()\n let connected = false\n let reconnectCount = 0\n let isUnsubscribed = false\n let lastEventId: string | undefined\n \n // 重连配置\n const reconnectInterval = options?.reconnectInterval ?? 3000\n const maxReconnects = options?.maxReconnects ?? 5\n\n const connect = async () => {\n if (isUnsubscribed) return\n \n try {\n // 重新创建 AbortController(重连时需要新的)\n abortController = new AbortController()\n \n const headers: Record<string, string> = {\n 'Accept': 'text/event-stream',\n ...defaultHeaders,\n ...options?.headers,\n }\n \n // SSE 规范:发送 Last-Event-ID 用于断点续传\n if (lastEventId) {\n headers['Last-Event-ID'] = lastEventId\n }\n \n const response = await fetch(url.toString(), {\n method: 'GET',\n headers,\n signal: abortController.signal,\n })\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`)\n }\n\n if (!response.body) {\n throw new Error('No response body')\n }\n\n // 连接成功,重置重连计数\n connected = true\n reconnectCount = 0\n callbacks.onOpen?.()\n\n const reader = response.body.getReader()\n \n for await (const event of parseSSEStream(reader)) {\n // 保存最后的事件 ID 用于重连\n if (event.id) {\n lastEventId = event.id\n }\n \n // 服务端可以通过 retry 字段动态调整重连间隔\n // 这里不改变配置,仅记录\n \n if (event.event === 'error') {\n callbacks.onError?.(new Error(String(event.data)))\n } else {\n callbacks.onMessage(event.data as TData)\n }\n }\n\n // 流正常结束\n connected = false\n callbacks.onClose?.()\n \n } catch (error) {\n connected = false\n \n // 用户主动取消,不重连\n if ((error as Error).name === 'AbortError' || isUnsubscribed) {\n return\n }\n \n callbacks.onError?.(error as Error)\n \n // 自动重连\n if (reconnectCount < maxReconnects) {\n reconnectCount++\n callbacks.onReconnect?.(reconnectCount, maxReconnects)\n \n // 延迟后重连\n setTimeout(() => {\n if (!isUnsubscribed) {\n connect()\n }\n }, reconnectInterval)\n } else {\n // 达到最大重连次数\n callbacks.onMaxReconnects?.()\n }\n }\n }\n\n connect()\n\n return {\n unsubscribe: () => {\n isUnsubscribed = true\n abortController?.abort()\n abortController = null\n connected = false\n },\n get connected() {\n return connected\n }\n }\n }\n\n // 创建端点代理\n function createEndpoint(basePath: string): unknown {\n const methods = ['get', 'post', 'put', 'patch', 'delete']\n \n // 创建可调用的代理(支持参数化路由 api.users({ id }) 调用)\n const handler = (params: Record<string, string>) => {\n const paramValue = Object.values(params)[0]\n const newPath = `${basePath}/${encodeURIComponent(paramValue)}`\n return createEndpoint(newPath)\n }\n\n return new Proxy(handler as unknown as object, {\n get(_, prop: string) {\n // HTTP 方法\n if (methods.includes(prop)) {\n const httpMethod = prop.toUpperCase()\n return (data?: unknown, cfg?: RequestConfig) => {\n return request(httpMethod, basePath, data, cfg)\n }\n }\n \n // SSE 订阅\n if (prop === 'subscribe') {\n return <TData>(\n queryOrCallbacks: Record<string, unknown> | SSECallbacks<TData>,\n callbacksOrOptions?: SSECallbacks<TData> | SSESubscribeOptions,\n options?: SSESubscribeOptions\n ) => {\n // 判断第一个参数是 query 还是 callbacks\n if (typeof queryOrCallbacks === 'object' && 'onMessage' in queryOrCallbacks) {\n // subscribe(callbacks, options)\n return subscribe<TData>(\n basePath, \n undefined, \n queryOrCallbacks as SSECallbacks<TData>,\n callbacksOrOptions as SSESubscribeOptions\n )\n } else {\n // subscribe(query, callbacks, options)\n return subscribe<TData>(\n basePath,\n queryOrCallbacks as Record<string, unknown>,\n callbacksOrOptions as SSECallbacks<TData>,\n options\n )\n }\n }\n }\n \n // 嵌套路径\n const childPath = `${basePath}/${prop}`\n return createEndpoint(childPath)\n },\n apply(_, __, args) {\n // 调用函数处理参数化路由\n const params = args[0] as Record<string, string>\n const paramValue = Object.values(params)[0]\n const newPath = `${basePath}/${encodeURIComponent(paramValue)}`\n return createEndpoint(newPath)\n }\n })\n }\n\n // 根代理\n return new Proxy({} as EdenClient<T>, {\n get(_, prop: string) {\n return createEndpoint(`/${prop}`)\n }\n })\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/core/eden.ts"],"sourcesContent":["/**\n * Eden 风格 API 客户端\n * \n * 最自然的链式调用:\n * - api.users.get() // GET /users\n * - api.users.post({ name }) // POST /users\n * - api.users({ id }).get() // GET /users/:id\n * - api.users({ id }).delete() // DELETE /users/:id\n * - api.chat.stream.subscribe() // SSE 流式响应\n * \n * @example\n * ```typescript\n * import { defineRoute } from 'vafast'\n * import { eden, InferEden } from '@vafast/api-client'\n * \n * // 定义路由(保留类型)\n * const routeDefinitions = [\n * defineRoute({\n * method: 'GET',\n * path: '/users',\n * schema: { query: Type.Object({ page: Type.Number() }) },\n * handler: ({ query }) => ({ users: [], page: query.page })\n * })\n * ] as const\n * \n * // 客户端推断类型\n * type Api = InferEden<typeof routeDefinitions>\n * const api = eden<Api>('http://localhost:3000')\n * \n * // 类型安全调用\n * const { data } = await api.users.get({ page: 1 })\n * ```\n */\n\nimport type { ApiResponse, ApiError, RequestConfig } from '../types'\n\n// ============= 配置 =============\n\nexport interface EdenConfig {\n headers?: Record<string, string>\n onRequest?: (request: Request) => Request | Promise<Request>\n onResponse?: <T>(response: ApiResponse<T>) => ApiResponse<T> | Promise<ApiResponse<T>>\n onError?: (error: ApiError) => void\n timeout?: number\n}\n\n// ============= SSE 类型 =============\n\nexport interface SSEEvent<T = unknown> {\n event?: string\n data: T\n id?: string\n retry?: number\n}\n\nexport interface SSESubscribeOptions {\n headers?: Record<string, string>\n reconnectInterval?: number\n maxReconnects?: number\n timeout?: number\n}\n\nexport interface SSESubscription<T = unknown> {\n unsubscribe: () => void\n readonly connected: boolean\n}\n\n// ============= 基础类型工具 =============\n\n/** 从 TypeBox Schema 提取静态类型 */\ntype InferStatic<T> = T extends { static: infer S } ? S : T\n\n/** 检查是否是 SSE Handler */\ntype IsSSEHandler<T> = T extends { __sse: { readonly __brand: 'SSE' } } ? true : false\n\n/** 从 Schema 对象提取各部分类型 */\ntype GetSchemaQuery<S> = S extends { query: infer Q } ? InferStatic<Q> : undefined\ntype GetSchemaBody<S> = S extends { body: infer B } ? InferStatic<B> : undefined\ntype GetSchemaParams<S> = S extends { params: infer P } ? InferStatic<P> : undefined\n\n/** \n * 从 handler 函数推断返回类型\n * handler: (ctx) => TReturn | Promise<TReturn>\n */\ntype InferHandlerReturn<H> = H extends (...args: never[]) => infer R\n ? R extends Promise<infer T> ? T : R\n : unknown\n\n// ============= 路径处理 =============\n\n/** 移除开头斜杠:/users → users */\ntype TrimSlash<P extends string> = P extends `/${infer R}` ? R : P\n\n/** 获取第一段:users/posts → users */\ntype Head<P extends string> = P extends `${infer H}/${string}` ? H : P\n\n/** 获取剩余段:users/posts → posts */\ntype Tail<P extends string> = P extends `${string}/${infer T}` ? T : never\n\n/** 检查是否是动态参数段::id → true */\ntype IsDynamicSegment<S extends string> = S extends `:${string}` ? true : false\n\n// ============= 清理 undefined 字段 =============\n\ntype Clean<T> = { [K in keyof T as T[K] extends undefined ? never : K]: T[K] }\n\n// ============= SSE 标记类型 =============\n\ntype SSEBrand = { readonly __brand: 'SSE' }\n\n// ============= 核心类型推断(适配新的 defineRoute) =============\n\n/**\n * 从 defineRoute 返回的路由配置构建方法定义\n * \n * defineRoute 返回的 LeafRouteConfig 结构:\n * {\n * method: TMethod,\n * path: TPath,\n * schema?: TSchema,\n * handler: (ctx) => TReturn | Promise<TReturn>\n * }\n */\ntype BuildMethodDef<R> = R extends {\n readonly schema?: infer TSchema\n readonly handler: infer THandler\n}\n ? Clean<{\n query: GetSchemaQuery<TSchema>\n body: GetSchemaBody<TSchema>\n params: GetSchemaParams<TSchema>\n return: InferHandlerReturn<THandler>\n }>\n : Clean<{\n return: R extends { readonly handler: infer H } ? InferHandlerReturn<H> : unknown\n }>\n\n/**\n * 递归构建嵌套路径结构\n * \n * 处理动态参数:/users/:id → { users: { ':id': { ... } } }\n */\ntype BuildPath<Path extends string, Method extends string, Def> =\n Path extends `${infer First}/${infer Rest}`\n ? IsDynamicSegment<First> extends true\n ? { ':id': BuildPath<Rest, Method, Def> }\n : { [K in First]: BuildPath<Rest, Method, Def> }\n : IsDynamicSegment<Path> extends true\n ? { ':id': { [M in Method]: Def } }\n : Path extends ''\n ? { [M in Method]: Def }\n : { [K in Path]: { [M in Method]: Def } }\n\n/**\n * 从单个路由生成嵌套类型结构\n */\ntype RouteToTree<R> = R extends {\n readonly method: infer M extends string\n readonly path: infer P extends string\n}\n ? BuildPath<TrimSlash<P>, Lowercase<M>, BuildMethodDef<R>>\n : {}\n\n// ============= 深度合并多个路由 =============\n\ntype DeepMerge<A, B> = {\n [K in keyof A | keyof B]: \n K extends keyof A & keyof B\n ? A[K] extends object\n ? B[K] extends object\n ? DeepMerge<A[K], B[K]>\n : A[K] & B[K]\n : A[K] & B[K]\n : K extends keyof A\n ? A[K]\n : K extends keyof B\n ? B[K]\n : never\n}\n\n/** 递归合并路由数组为单一类型结构 */\ntype MergeRoutes<T extends readonly unknown[]> = \n T extends readonly [infer First]\n ? RouteToTree<First>\n : T extends readonly [infer First, ...infer Rest]\n ? DeepMerge<RouteToTree<First>, MergeRoutes<Rest>>\n : {}\n\n/**\n * 从 defineRoute 数组自动推断 Eden 契约\n * \n * @example\n * ```typescript\n * import { defineRoute, defineRoutes, Type } from 'vafast'\n * import { eden, InferEden } from '@vafast/api-client'\n * \n * // 定义路由(使用 as const 保留字面量类型)\n * const routeDefinitions = [\n * defineRoute({\n * method: 'GET',\n * path: '/users',\n * schema: { query: Type.Object({ page: Type.Number() }) },\n * handler: ({ query }) => ({ users: [], total: 0 })\n * }),\n * defineRoute({\n * method: 'POST',\n * path: '/users',\n * schema: { body: Type.Object({ name: Type.String() }) },\n * handler: ({ body }) => ({ id: '1', name: body.name })\n * })\n * ] as const\n * \n * // 服务端\n * const routes = defineRoutes(routeDefinitions)\n * const server = new Server(routes)\n * \n * // 客户端类型推断\n * type Api = InferEden<typeof routeDefinitions>\n * const api = eden<Api>('http://localhost:3000')\n * \n * // 类型安全的调用\n * const { data } = await api.users.get({ page: 1 }) // ✅ query 类型推断\n * const { data: user } = await api.users.post({ name: 'John' }) // ✅ body 类型推断\n * ```\n */\nexport type InferEden<T extends readonly unknown[]> = MergeRoutes<T>\n\n// ============= 契约类型(手动定义时使用) =============\n\ninterface MethodDef {\n query?: unknown\n body?: unknown\n params?: unknown\n return: unknown\n sse?: SSEBrand\n}\n\ntype RouteNode = {\n get?: MethodDef\n post?: MethodDef\n put?: MethodDef\n patch?: MethodDef\n delete?: MethodDef\n ':id'?: RouteNode\n [key: string]: MethodDef | RouteNode | undefined\n}\n\n// ============= 客户端类型 =============\n\ninterface SSECallbacks<T> {\n onMessage: (data: T) => void\n onError?: (error: ApiError) => void\n onOpen?: () => void\n onClose?: () => void\n onReconnect?: (attempt: number, maxAttempts: number) => void\n onMaxReconnects?: () => void\n}\n\ntype MethodCall<M extends MethodDef, HasParams extends boolean = false> = \n M extends { sse: SSEBrand }\n ? M extends { query: infer Q }\n ? (query: Q, callbacks: SSECallbacks<M['return']>, options?: SSESubscribeOptions) => SSESubscription<M['return']>\n : (callbacks: SSECallbacks<M['return']>, options?: SSESubscribeOptions) => SSESubscription<M['return']>\n : HasParams extends true\n ? M extends { body: infer B }\n ? (body: B, config?: RequestConfig) => Promise<ApiResponse<M['return']>>\n : (config?: RequestConfig) => Promise<ApiResponse<M['return']>>\n : M extends { query: infer Q }\n ? (query?: Q, config?: RequestConfig) => Promise<ApiResponse<M['return']>>\n : M extends { body: infer B }\n ? (body: B, config?: RequestConfig) => Promise<ApiResponse<M['return']>>\n : (config?: RequestConfig) => Promise<ApiResponse<M['return']>>\n\ntype IsSSEEndpoint<M> = M extends { sse: { readonly __brand: 'SSE' } } ? true : false\n\ntype Endpoint<T, HasParams extends boolean = false> = \n {\n [K in 'get' | 'post' | 'put' | 'patch' | 'delete' as T extends { [P in K]: MethodDef } ? K : never]: \n T extends { [P in K]: infer M extends MethodDef } ? MethodCall<M, HasParams> : never\n } \n & (T extends { get: infer M extends MethodDef }\n ? IsSSEEndpoint<M> extends true \n ? { subscribe: MethodCall<M, HasParams> }\n : {}\n : {})\n\ntype HTTPMethods = 'get' | 'post' | 'put' | 'patch' | 'delete'\n\nexport type EdenClient<T, HasParams extends boolean = false> = {\n [K in keyof T as K extends HTTPMethods | `:${string}` ? never : K]: \n T[K] extends { ':id': infer Child }\n ? ((params: Record<string, string>) => EdenClient<Child, true>) & EdenClient<T[K], false>\n : EdenClient<T[K], false>\n} & Endpoint<T, HasParams>\n\n// ============= SSE 解析器 =============\n\nasync function* parseSSEStream(\n reader: ReadableStreamDefaultReader<Uint8Array>\n): AsyncGenerator<SSEEvent, void, unknown> {\n const decoder = new TextDecoder();\n let buffer = '';\n \n while (true) {\n const { done, value } = await reader.read();\n \n if (done) break;\n \n buffer += decoder.decode(value, { stream: true });\n \n const events = buffer.split('\\n\\n');\n buffer = events.pop() || '';\n \n for (const eventStr of events) {\n if (!eventStr.trim()) continue;\n \n const event: SSEEvent = { data: '' };\n const lines = eventStr.split('\\n');\n let dataLines: string[] = [];\n \n for (const line of lines) {\n if (line.startsWith('event:')) {\n event.event = line.slice(6).trim();\n } else if (line.startsWith('data:')) {\n dataLines.push(line.slice(5).trim());\n } else if (line.startsWith('id:')) {\n event.id = line.slice(3).trim();\n } else if (line.startsWith('retry:')) {\n event.retry = parseInt(line.slice(6).trim(), 10);\n }\n }\n \n const dataStr = dataLines.join('\\n');\n \n try {\n event.data = JSON.parse(dataStr);\n } catch {\n event.data = dataStr;\n }\n \n yield event;\n }\n }\n}\n\n// ============= 实现 =============\n\n/**\n * 创建 Eden 风格的类型安全 API 客户端\n */\nexport function eden<T>(\n baseURL: string,\n config?: EdenConfig\n): EdenClient<T> {\n const { headers: defaultHeaders, onRequest, onResponse, onError, timeout } = config ?? {}\n\n async function request<TReturn>(\n method: string,\n path: string,\n data?: unknown,\n requestConfig?: RequestConfig\n ): Promise<ApiResponse<TReturn>> {\n const url = new URL(path, baseURL)\n \n if (method === 'GET' && data && typeof data === 'object') {\n for (const [key, value] of Object.entries(data as Record<string, unknown>)) {\n if (value !== undefined && value !== null) {\n url.searchParams.set(key, String(value))\n }\n }\n }\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n ...defaultHeaders,\n ...requestConfig?.headers,\n }\n\n const controller = new AbortController()\n let timeoutId: ReturnType<typeof setTimeout> | undefined\n \n const userSignal = requestConfig?.signal\n const requestTimeout = requestConfig?.timeout ?? timeout\n \n if (userSignal) {\n if (userSignal.aborted) {\n controller.abort()\n } else {\n userSignal.addEventListener('abort', () => controller.abort())\n }\n }\n \n if (requestTimeout) {\n timeoutId = setTimeout(() => controller.abort(), requestTimeout)\n }\n\n const fetchOptions: RequestInit = { \n method, \n headers,\n signal: controller.signal \n }\n\n if (method !== 'GET' && method !== 'HEAD' && data) {\n fetchOptions.body = JSON.stringify(data)\n }\n\n let req = new Request(url.toString(), fetchOptions)\n \n if (onRequest) {\n req = await onRequest(req)\n }\n\n try {\n const response = await fetch(req)\n \n if (timeoutId) clearTimeout(timeoutId)\n \n const contentType = response.headers.get('content-type')\n let responseData: TReturn | null = null\n \n if (contentType?.includes('application/json')) {\n responseData = await response.json()\n } else if (contentType?.includes('text/')) {\n responseData = await response.text() as unknown as TReturn\n }\n\n let result: ApiResponse<TReturn>\n \n if (response.ok) {\n result = { data: responseData, error: null }\n } else {\n const errorBody = responseData as { code?: number; message?: string } | null\n result = {\n data: null,\n error: {\n code: errorBody?.code ?? response.status,\n message: errorBody?.message ?? `HTTP ${response.status}`\n }\n }\n }\n\n if (onResponse) {\n result = await onResponse(result)\n }\n\n if (result.error && onError) {\n onError(result.error)\n }\n\n return result\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error))\n const apiError: ApiError = { code: 0, message: err.message || '网络错误' }\n if (onError) onError(apiError)\n return {\n data: null,\n error: apiError,\n }\n }\n }\n\n function subscribe<TData>(\n path: string,\n query: Record<string, unknown> | undefined,\n callbacks: SSECallbacks<TData>,\n options?: SSESubscribeOptions\n ): SSESubscription<TData> {\n const url = new URL(path, baseURL)\n \n if (query) {\n for (const [key, value] of Object.entries(query)) {\n if (value !== undefined && value !== null) {\n url.searchParams.set(key, String(value))\n }\n }\n }\n\n let abortController: AbortController | null = new AbortController()\n let connected = false\n let reconnectCount = 0\n let isUnsubscribed = false\n let lastEventId: string | undefined\n \n const reconnectInterval = options?.reconnectInterval ?? 3000\n const maxReconnects = options?.maxReconnects ?? 5\n\n const connect = async () => {\n if (isUnsubscribed) return\n \n try {\n abortController = new AbortController()\n \n const headers: Record<string, string> = {\n 'Accept': 'text/event-stream',\n ...defaultHeaders,\n ...options?.headers,\n }\n \n if (lastEventId) {\n headers['Last-Event-ID'] = lastEventId\n }\n \n const response = await fetch(url.toString(), {\n method: 'GET',\n headers,\n signal: abortController.signal,\n })\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`)\n }\n\n if (!response.body) {\n throw new Error('No response body')\n }\n\n connected = true\n reconnectCount = 0\n callbacks.onOpen?.()\n\n const reader = response.body.getReader()\n \n for await (const event of parseSSEStream(reader)) {\n if (event.id) {\n lastEventId = event.id\n }\n \n if (event.event === 'error') {\n callbacks.onError?.({ code: -1, message: String(event.data) })\n } else {\n callbacks.onMessage(event.data as TData)\n }\n }\n\n connected = false\n callbacks.onClose?.()\n \n } catch (error) {\n connected = false\n \n if ((error as Error).name === 'AbortError' || isUnsubscribed) {\n return\n }\n \n const err = error instanceof Error ? error : new Error(String(error))\n callbacks.onError?.({ code: 0, message: err.message || 'SSE 连接错误' })\n \n if (reconnectCount < maxReconnects) {\n reconnectCount++\n callbacks.onReconnect?.(reconnectCount, maxReconnects)\n \n setTimeout(() => {\n if (!isUnsubscribed) {\n connect()\n }\n }, reconnectInterval)\n } else {\n callbacks.onMaxReconnects?.()\n }\n }\n }\n\n connect()\n\n return {\n unsubscribe: () => {\n isUnsubscribed = true\n abortController?.abort()\n abortController = null\n connected = false\n },\n get connected() {\n return connected\n }\n }\n }\n\n function createEndpoint(basePath: string): unknown {\n const methods = ['get', 'post', 'put', 'patch', 'delete']\n \n const handler = (params: Record<string, string>) => {\n const paramValue = Object.values(params)[0]\n const newPath = `${basePath}/${encodeURIComponent(paramValue)}`\n return createEndpoint(newPath)\n }\n\n return new Proxy(handler as unknown as object, {\n get(_, prop: string) {\n if (methods.includes(prop)) {\n const httpMethod = prop.toUpperCase()\n return (data?: unknown, cfg?: RequestConfig) => {\n return request(httpMethod, basePath, data, cfg)\n }\n }\n \n if (prop === 'subscribe') {\n return <TData>(\n queryOrCallbacks: Record<string, unknown> | SSECallbacks<TData>,\n callbacksOrOptions?: SSECallbacks<TData> | SSESubscribeOptions,\n options?: SSESubscribeOptions\n ) => {\n if (typeof queryOrCallbacks === 'object' && 'onMessage' in queryOrCallbacks) {\n return subscribe<TData>(\n basePath, \n undefined, \n queryOrCallbacks as SSECallbacks<TData>,\n callbacksOrOptions as SSESubscribeOptions\n )\n } else {\n return subscribe<TData>(\n basePath,\n queryOrCallbacks as Record<string, unknown>,\n callbacksOrOptions as SSECallbacks<TData>,\n options\n )\n }\n }\n }\n \n const childPath = `${basePath}/${prop}`\n return createEndpoint(childPath)\n },\n apply(_, __, args) {\n const params = args[0] as Record<string, string>\n const paramValue = Object.values(params)[0]\n const newPath = `${basePath}/${encodeURIComponent(paramValue)}`\n return createEndpoint(newPath)\n }\n })\n }\n\n return new Proxy({} as EdenClient<T>, {\n get(_, prop: string) {\n return createEndpoint(`/${prop}`)\n }\n })\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vafast/api-client",
3
- "version": "0.1.5",
3
+ "version": "0.2.0",
4
4
  "description": "Type-safe API client for Vafast framework",
5
5
  "license": "MIT",
6
6
  "files": [
@@ -14,10 +14,10 @@
14
14
  "release": "npm run build && npm run test && npx bumpp && npm publish --access=public"
15
15
  },
16
16
  "peerDependencies": {
17
- "vafast": ">=0.4.23"
17
+ "vafast": "^0.5.1"
18
18
  },
19
19
  "devDependencies": {
20
- "vafast": ">=0.4.23",
20
+ "vafast": "^0.5.1",
21
21
  "tsdown": "^0.19.0-beta.4",
22
22
  "typescript": "^5.5.3",
23
23
  "vitest": "^2.1.8"