@newaifree-tool/request-bus
v2.2.0
Published
请求库业务层封装,包含CLI工具
Readme
@newaifree-tool/request-bus
request-bus 是业务 API 契约层。
它负责把接口文档中的路径、方法、参数、响应类型和接口级能力声明成类型安全的 API 方法。
它不负责真实 HTTP 请求,也不负责 token、baseURL、请求/响应拦截器。那些能力应该放在 AxiosRequestor、FetchRequestor 或其他基础 requestor 上。
安装
pnpm add @newaifree-tool/request-bus @newaifree-tool/request-core通常还需要安装一个 requestor 实现包:
pnpm add @newaifree-tool/request-axios-imp基础用法
import { createBus, defineEndpoint } from "@newaifree-tool/request-bus";
import { AxiosRequestor } from "@newaifree-tool/request-axios-imp";
interface User {
id: number;
name: string;
}
interface CreateUserDTO {
name: string;
age: number;
}
const requestor = new AxiosRequestor({
baseURL: "https://api.example.com",
timeout: 10_000,
});
const bus = createBus({
requestor,
});
const userApi = bus.createApiService({
getUser: defineEndpoint<User, { id: number }>({
path: "/users/:id",
method: "GET",
cache: true,
}),
createUser: defineEndpoint<User, void, CreateUserDTO>({
path: "/users",
method: "POST",
idempotent: true,
}),
});
const user = await userApi.getUser({ id: 1 });
const created = await userApi.createUser(undefined, {
name: "Tom",
age: 18,
});类型推导
defineEndpoint<TResponse, TParams, TData>() 用于声明接口类型。
defineEndpoint<User, { id: number }>({
path: "/users/:id",
method: "GET",
});生成后的 API 方法类型为:
(params: { id: number }) => Promise<User>无参数 GET:
const api = bus.createApiService({
listUsers: defineEndpoint<User[]>({
path: "/users",
method: "GET",
}),
});
const users = await api.listUsers();无 params 但有 body 的 POST:
const api = bus.createApiService({
createUser: defineEndpoint<User, void, CreateUserDTO>({
path: "/users",
method: "POST",
}),
});
const user = await api.createUser(undefined, {
name: "Tom",
age: 18,
});路径参数和查询参数
路径中的 :id 会从 params 中取值并替换。
const api = bus.createApiService({
getUserPosts: defineEndpoint<Post[], { id: number; page: number }>({
path: "/users/:id/posts",
method: "GET",
}),
});
await api.getUserPosts({
id: 1,
page: 2,
});最终请求配置大致为:
{
url: "/users/1/posts",
method: "GET",
params: {
page: 2,
},
}如果缺少路径参数,request-bus 会直接抛错,避免发出错误请求。
接口级能力配置
不同接口可以按需开启不同能力。
const api = bus.createApiService({
listUsers: defineEndpoint<User[]>({
path: "/users",
method: "GET",
cache: {
duration: 60_000,
},
retry: {
maxCount: 3,
},
}),
createOrder: defineEndpoint<Order, void, CreateOrderDTO>({
path: "/orders",
method: "POST",
idempotent: {
mode: "concurrent",
},
}),
});推荐使用方式:
- GET 查询接口:适合
cache和retry。 - POST 创建、支付、下单接口:适合
idempotent,重试需要谨慎。 - 高并发场景:可以按接口开启
concurrency。
内部组合顺序:
requestor -> retry -> cache -> concurrency -> idempotent执行时最外层的 idempotent 会先处理重复请求。
拦截器应该放在哪里
拦截器放在基础 requestor 上,不放在 bus 上。
const requestor = new AxiosRequestor({
baseURL: "https://api.example.com",
});
requestor.interceptors.request.use((config) => {
config.headers = {
...config.headers,
Authorization: `Bearer ${accessToken}`,
};
return config;
});
const bus = createBus({ requestor });这样职责更清晰:
requestor负责真实 HTTP 请求、baseURL、token、通用拦截器。request-core负责缓存、重试、并发控制、幂等。request-bus负责 API 契约、路径参数、类型安全 API 生成、接口级能力声明。
多业务线使用
createBus 是实例化 API,不是全局单例。多个业务线可以创建多个 bus。
const userRequestor = new AxiosRequestor({
baseURL: "https://user.example.com",
});
const orderRequestor = new AxiosRequestor({
baseURL: "https://order.example.com",
});
const userBus = createBus({
requestor: userRequestor,
});
const orderBus = createBus({
requestor: orderRequestor,
});CLI
request-bus 包含 CLI 入口:
npx request-bus --helpCLI 可用于根据接口 schema 生成 API 契约代码。生成出来的代码会把 baseURL 放在 requestor 初始化中,而不是放在 createBus 中。
初始化配置文件:
request-bus init默认生成的 apiSchemaUrl 是 ./api-schema.json,表示本地 schema 文件。远程 schema 地址需要显式指定:
request-bus init --schema https://api.example.com/schema --base-url https://api.example.com生成代码:
request-bus generate -s api-schema.json -o ./src/api --base-url https://api.example.com如果希望覆盖已存在的 index.ts,需要显式添加 --force:
request-bus generate -s api-schema.json -o ./src/api --forcegenerate 会优先使用命令行参数;如果没有传 -s/--schema,会读取 request-bus.config.json 中的 apiSchemaUrl。
因为后端 schema 来源可能不可控,CLI 会在生成前校验 schema 结构。至少需要满足:
- 根对象包含
endpoints对象。 - 每个 endpoint 必须包含非空字符串
path。 - 每个 endpoint 的
method必须是GET/POST/PUT/DELETE/PATCH/HEAD/OPTIONS之一。 auth必须是布尔值,cache/retry/idempotent/concurrency必须是布尔值或配置对象。- 不支持的 endpoint 字段会直接报错,避免生成出 TypeScript 无法通过的代码。
如果需要生成类型安全的 defineEndpoint<TResponse, TParams, TData>,可以在 schema 中增加类型字段:
{
"typeImports": [
{
"from": "../types",
"named": ["Order"]
}
],
"endpoints": {
"order": {
"createOrder": {
"path": "/api/orders",
"method": "POST",
"auth": true,
"idempotent": true,
"responseType": "Order",
"paramsType": "void",
"dataType": "{ items: string[] }"
}
}
}
}生成结果:
import type { Order } from "../types";
createOrder: defineEndpoint<Order, void, { items: string[] }>({
path: "/api/orders",
method: "POST",
auth: true,
idempotent: true,
});responseType/paramsType/dataType 只用于代码生成,不会进入最终的运行时 endpoint 配置。命名类型必须通过 typeImports 声明来源;如果不想导入类型,也可以直接把 responseType 写成内联类型,例如 { id: string; items: string[] }。
本地未发布时,可以直接执行构建后的 CLI 文件:
pnpm --filter @newaifree-tool/request-bus build
node packages/request-bus/dist/cli.js --help
node packages/request-bus/dist/cli.js generate -s api-schema.json -o ./src/api --base-url http://localhost:3000 --force也可以在 packages/request-bus 下执行 npm pack,把生成的 .tgz 安装到测试项目中,模拟发布后的真实使用方式。
常见问题
bus 会不会执行请求?
会触发请求调用,但不会实现 HTTP 请求。
request-bus 生成的 API 方法会把接口契约转换成 RequestConfig,然后调用传入的 requestor.request(config)。
真实 HTTP 细节由 AxiosRequestor、FetchRequestor 或自定义 requestor 负责。
bus 为什么没有 baseURL?
baseURL 属于底层请求实现的职责。
如果 bus 也配置 baseURL,会和 axios/fetch requestor 职责重复,也不利于多 requestor 组合。
bus 为什么没有 onRequest/onResponse?
请求、响应、错误拦截器属于 request-core 的基础 requestor 能力。
bus 只做 API 契约和接口级能力编排,不重复实现拦截器。
