与其他 TypeScript 客户端对比
@vafast/api-client 提供 Eden 风格链式调用、{ data, error } 错误模型,以及 Koa 风格洋葱中间件。类型可通过契约或 CLI(vafast sync)获得,不依赖特定后端运行时。
下文先给出总览,再按 调用风格 → 错误处理 → 客户端横切 → SSE → 类型与耦合 逐项展开,最后给出选型建议。
总览
| 维度 | 本库 | Eden | tRPC | Hono hc | OpenAPI | Axios / ky |
|---|---|---|---|---|---|---|
| 调用风格 | 链式 .post(body) | Treaty 链式 | query / mutate | $get(...) | GET('/path') | axios.get |
| 错误处理 | { data, error } | { data, error } | 抛错 | Response | 视生成器 | 抛异常 |
| 客户端横切 | 洋葱 next() | 请求/响应钩子 | Links | headers / fetch | 弱 | 拦截器 |
| SSE | 同调用 .sse() | subscribe | subscription | 视用法 | 通常无 | 通常无 |
| 类型与耦合 | 契约 / CLI,弱耦合 | 同构,绑 Elysia | 同构,绑 tRPC | 同构,绑 Hono | 生成,无绑定 | 无端到端类型 |
一、调用风格
统一场景:POST /users/find,请求体 { current: 1, pageSize: 20 }(分页查用户)。下面只比「怎么写出这次请求」,错误处理见下一节。
本库
const { data, error } = await api.users.find.post({ current: 1, pageSize: 20 })路径段用 . 连接,HTTP 动词落在链末,body 作为第一参数。无 $ 前缀,也无需再包一层 { body } / { query }。
优点:与 URL 结构一一对应,补全直观,读写成本低。
注意:路径段若与动词同名(如 POST /prices/delete),须写成 api.prices.delete.post(...)——中间的 delete 是路径,末尾才是动词。详见 基础用法 · 注意事项。
同一次调用还可改走 SSE(RequestBuilder 懒执行):api.users.find.post(body).sse({ ... }),不必换 API。
Eden Treaty
const { data, error } = await app.users.find.post({ current: 1, pageSize: 20 })这一条 POST 的外形与本库几乎一样(路径即属性、链末动词、body 作第一参)。若只比「写一个带 body 的 POST」,两者刻意同构,谈不上谁更花哨。
真正不同的在相邻能力,而不是这一行点号:
| 点 | 本库 | Eden Treaty |
|---|---|---|
| GET query | api.users.get({ page: 1 }),第一参就是 query | 多为 api.users.get({ query: { page: 1 } }),query 要再包一层 |
| 流式 | 同一调用:.post(body).sse({ onMessage }) | HTTP SSE 常 await 后对 data 做 for await;实时双向是 .subscribe()(WebSocket) |
| 客户端组装 | createClient → .use(中间件) → eden(client) | treaty(url | app, { onRequest, onResponse, headers }) |
| 横切模型 | Koa 洋葱 (ctx, next),与 SSE 共用链 | 请求/响应钩子拆开,不是 next() |
| 错误字段 | error.code / message / details(对齐 Vafast) | error.status / error.value(对齐 Elysia) |
| 类型从哪来 | 契约 / vafast sync,不绑运行时 | typeof app 同构,强绑 Elysia;也可 treaty(app) 同进程调用 |
优点(Eden):后端就是 Elysia 时零生成最省事;同进程 treaty(app) 适合单测。
代价:换非 Elysia 后端即不适用;流式与横切模型和本库不是同一套。
结论:链式「长相」学的是 Eden;差异在 GET 入参扁平化、.sse() 与 JSON 同链、洋葱中间件、以及面向 Vafast 的错误/契约,而不是再发明一种点号语法。
tRPC
// 服务端若写成 query 过程:
const data = await trpc.users.find.query({ current: 1, pageSize: 20 })
// 服务端若写成 mutation 过程,客户端必须改成:
// await trpc.users.find.mutate({ current: 1, pageSize: 20 })tRPC 不写 .get / .post。服务端把接口登记成 query(读) 或 mutation(写) 两种过程之一,客户端只能调用对应的 .query() 或 .mutate()——和 HTTP 动词不是一一映射,即使传输层碰巧是 POST。
优点:全栈同仓时过程级类型与批处理等能力完整。
代价:心智是 RPC 而非 REST;换非 tRPC 后端成本高;调用形态与网关/抓包看到的 URL 不如链式直观。
Hono hc
const res = await client.users.find.$post({
json: { current: 1, pageSize: 20 },
})路径仍可链式,但方法名带 $,入参常按 json / query / param 分区。
优点:与 Hono 路由类型对齐好。
代价:$ 与嵌套字段增加噪音;拿到的是 Response,还要自行 ok / json()(见错误处理)。
OpenAPI 生成客户端
const { data, error } = await api.POST('/users/find', {
body: { current: 1, pageSize: 20 },
})动词 + 字符串路径,资源树感弱于链式属性。
优点:后端无关、多语言一致。
代价:依赖 OpenAPI 流水线;路径字符串易与文档漂移,补全体验通常弱于 Proxy 链式。
Axios
const { data } = await axios.post('/users/find', { current: 1, pageSize: 20 })最直白的 HTTP 调用。
优点:生态大、上手快。
代价:无端到端路径/响应类型(除非再套生成层);风格与类型安全链式客户端不在同一档。
小结
同一 POST /users/find 下:本库与 Eden 外形最像(都是看得见的路径 + 动词);和 Eden 的差别不在这一行点号,而在 GET 入参是否扁平、SSE 是否挂在同一 RequestBuilder、以及中间件 / 错误 / 契约是否面向 Vafast。相对 Hono,少 $ 与嵌套包装;相对 tRPC / OpenAPI / Axios,更强调 HTTP 可读性与类型补全的折中,而不是绑死某一运行时。
路径参数本库与 Eden 同套路:api.users({ id: '123' }).get() → GET /users/123。
二、错误处理
对照
| 库 | 模型 | 业务失败时 |
|---|---|---|
| 本库 | { data, error } | if (error);422 可读 error.details |
| Eden | { data, error } | 同 Result;错误结构随 Elysia 版本略有差异 |
| tRPC | 抛 TRPCClientError | try / catch |
Hono hc | Response | 先判 res.ok,再 json() / text() |
| OpenAPI | 视生成器 | 常见 Result 或抛错两种 |
| Axios | 抛异常 | catch 中读 e.response |
说明
同一场景:分页查用户;失败输出信息,成功使用 list(console 可换成 UI)。
本库 / Eden(Result)
const { data, error } = await api.users.find.post({ current: 1, pageSize: 20 })
if (error) {
console.error(error.message)
return
}
const users = data.listtRPC / Axios(异常)
try {
const data = await trpc.users.list.query({ page: 1, pageSize: 20 })
const users = data.list
} catch (e) {
console.error(e.message)
}Hono hc(Response)
const res = await client.users.$get({ query: { page: '1' } })
if (!res.ok) {
console.error(await res.text())
return
}
const data = await res.json()
const users = data.list本库与 Vafast 服务端约定对齐:业务错误进入 error(含 code / message),校验失败为 HTTP 422 + details,控制流保持线性,无需为业务失败包一层 try / catch。
三、客户端横切
对照
| 库 | 模型 | 说明 |
|---|---|---|
| 本库 | (ctx, next) => ResponseContext | Koa 洋葱;request 与 SSE 用的 requestRaw 共用 compose;内置 retry / timeout / logger 同一接口 |
| Eden | onRequest / onResponse | 请求与响应分钩子,可数组叠加 |
| tRPC | Links | Observable 链,末端须为 terminating link(如 httpLink);面向 RPC operation |
Hono hc | 创建选项 | 主要是默认 headers、自定义 fetch;无一等客户端中间件栈(路由中间件在服务端) |
| Axios | interceptors | request / response 拦截器,生态成熟 |
| OpenAPI | 通常较弱 | 横切多依赖底层 fetch / 另接拦截层 |
说明
各库都能做鉴权头、日志等横切,差异在组合方式:
- 本库:同一中间件内可「修改
ctx→await next()→ 根据{ data, error }分支或再次next()」。 - Eden:请求改动与响应处理拆在两个钩子。
- tRPC:Link 订阅结果流,错误多经 observable / 异常传播,而非 HTTP
ctx+ Result。 - Hono:客户端侧能力有限,复杂横切需自包
fetch。 - Axios:拦截器成熟,但无路径类型与内建 Result。
更复杂的多服务、多租户等组合见 高级用法。
四、SSE
对照
| 库 | 流式入口 | 与普通请求的关系 |
|---|---|---|
| 本库 | .post(body).sse({ onMessage }) | 同一链式调用;走同一中间件链 |
| Eden | 端点上的 subscribe 等 | 与普通 .get / .post 分开 |
| tRPC | subscription + 对应 link | 与 query / mutation 另一套模型 |
Hono hc | 视路由与用法 | 能力不一,常需自行处理流 |
| OpenAPI / Axios | 通常无一等 SSE | 需自建 EventSource / fetch 流 |
说明
api.chat.stream.post({ prompt: 'hi' }).sse({
onMessage: (chunk) => { /* 逐块处理 */ },
onError: (e) => console.error(e.message),
})RequestBuilder 在 await 时发 JSON,在 .sse() 时改走流式;路径、方法、body 与鉴权中间件与普通请求一致,不必为流式再配一套客户端。
五、类型来源与后端耦合
对照
| 库 | 类型从哪来 | 与后端的关系 |
|---|---|---|
| 本库 | 手写契约,或 vafast sync / CLI 生成 | 弱耦合:HTTP + 契约,不强制运行时 |
| Eden | 从 Elysia App 同构推断 | 强绑 Elysia,零生成体验最好 |
| tRPC | 从 AppRouter 同构推断 | 强绑 tRPC(或兼容层) |
Hono hc | 从 AppType 同构推断 | 强绑 Hono |
| OpenAPI | 由 OpenAPI 文档生成 | 不绑运行时;多语言友好,流水线较重 |
| Axios | 无端到端类型(除非另接) | 不绑运行时;路径与响应靠约定 |
说明
| 路线 | 优点 | 代价 |
|---|---|---|
| 同构推断(tRPC / Eden / Hono) | 同仓改接口即可传到客户端 | 换运行时成本高 |
| 契约 / CLI(本库) | 前后端可分仓,保留 HTTP 语义 | 需维护同步步骤 |
| OpenAPI 生成 | 网关、多语言一致 | 生成与文档维护成本高 |
| 无类型(Axios) | 上手快 | 易出现路径 / 字段漂移 |
本库介于同构 RPC 与通用 HTTP 客户端之间:调用体验接近 Eden,部署上不强制 Elysia、tRPC 或 Hono。
选型建议
| 场景 | 建议 |
|---|---|
| 后端为 tRPC,前后端同仓 | tRPC |
| 后端为 Elysia,希望零生成同构 | Eden |
| 后端为 Hono,以类型对齐为主 | Hono hc |
| 多语言客户端,或已有 OpenAPI | OpenAPI 生成客户端 |
| 仅需 HTTP,不依赖端到端类型 | Axios / ky / ofetch |
| 后端为 Vafast,或需要洋葱中间件、Result 与统一 SSE | 本库 |
| 多服务、多租户、较重横切逻辑 | 本库(高级用法) |
已绑定某一同构 RPC 栈时,优先用该栈官方客户端;需要可拆仓的 HTTP 契约,以及统一的链式调用、中间件与 SSE 时,可选用本库。