@robot-admin/request-core
v0.6.1
Published
Instance-scoped Axios orchestration and headless CRUD composables for Vue 3
Downloads
288
Maintainers
Readme
@robot-admin/request-core
面向生产环境的实例化请求编排与 Vue 3 Headless CRUD 工具。当前版本:
0.6.1。
它保留 Axios 的完整能力,只收拢应用中最容易重复出错的部分:并发请求、缓存、 取消、重试、Token 刷新、错误标准化以及列表 CRUD 生命周期。
特性
- 每个 Client 独立持有缓存、待处理请求、取消作用域和认证状态,无跨应用污染。
- 简单调用保持一行,复杂调用通过平铺的请求配置按需启用。
- 相同请求支持
join、takeLatest、takeFirst、allow四种并发语义。 - 内存 LRU 缓存支持 TTL、标签/前缀失效、自定义同步 CacheStore 和引用保护。
- 重试支持幂等方法白名单、指数退避、抖动、
Retry-After和总时间预算。 - Token 主动刷新、并发 401 恢复和重新登录均使用 single-flight。
RequestError统一业务、HTTP、网络、超时、取消和配置错误。createTableCrud()以函数式应用预配置消除类继承和页面级分页映射重复。source统一真实端点与自定义数据源,业务侧无需拼装api/query/mutations。- Vue 层不依赖具体 UI;Naive UI 仅作为可选兼容适配层。
- ESM、CJS 和 TypeScript 类型入口均经过发布前验证。
安装与入口
bun add @robot-admin/request-core axios| 入口 | 依赖边界 | 用途 |
| --------------------------------- | ---------------------- | ------------------------------------------------------------------ |
| @robot-admin/request-core/axios | Axios | 推荐的请求 Client、策略及兼容 API |
| @robot-admin/request-core/vue | Axios + Vue | useRequest、Headless useTableCrud/createTableCrud、Client 注入 |
| @robot-admin/request-core/naive | Axios + Vue + Naive UI | useNaiveTableCrud 兼容适配 |
| @robot-admin/request-core | 完整兼容入口 | 旧项目平滑迁移,当前仍会引用 Naive UI |
| @robot-admin/request-core/crud | 兼容入口 | 已废弃,迁移到 /vue 或 /naive |
纯请求项目应使用 /axios,这样不会引入 Vue 或任何 UI 框架。
推荐接入
创建唯一的应用 Client
// src/services/request.ts
import {
createRequestClient,
type ResponseAdapter,
} from "@robot-admin/request-core/axios";
const responseAdapter: ResponseAdapter = {
isSuccess: (data) => [0, 200].includes((data as { code: number }).code),
getData: (data) => (data as { data: unknown }).data,
getError: (data) => ({
code: (data as { code?: number }).code,
message: (data as { message?: string }).message ?? "请求失败",
data,
}),
};
export const request = createRequestClient({
request: {
baseURL: import.meta.env.VITE_API_BASE,
timeout: 10_000,
},
response: responseAdapter,
defaults: {
concurrency: "join",
retry: { enabled: false },
cache: { enabled: false },
},
hooks: {
onError: (error) => {
if (error.kind !== "canceled") window.$message?.error(error.message);
},
},
});所有能力均可省略。默认缓存和重试关闭;新 Client 的相同安全读取请求默认共享结果, 副作用方法默认允许并发。
类型化调用
interface User {
id: number;
name: string;
}
interface CreateUser {
name: string;
}
const users = await request.get<User[]>("/users", {
params: { keyword: "robot" },
});
const user = await request.post<User, CreateUser>("/users", {
name: "Robot",
});
const response = await request.raw<User>({
method: "GET",
url: "/users/1",
});支持 request/get/post/put/patch/delete/head/options/raw。raw() 返回完整
AxiosResponse,其他方法返回响应适配器处理后的数据。
全局配置与按需能力
策略可以在 Client 层配置默认值,也可以被单次请求覆盖:
await request.get("/dashboard", {
cache: { enabled: true, ttl: 60_000, tags: ["dashboard"] },
retry: { enabled: true, count: 2, maxElapsedMs: 8_000 },
concurrency: "takeLatest",
scope: "dashboard-page",
});
request.cache.invalidateTag("dashboard");
request.requests.cancelScope("dashboard-page");并发策略:
join:相同请求共享一次网络调用,适合字典和初始化数据。takeLatest:取消旧请求,只保留最新请求,适合搜索和分页。takeFirst:已有相同请求时拒绝新请求,适合提交按钮。allow:允许全部并发,适合调用方自行管理的任务。
旧 dedupe 配置继续可用。隐式去重只作用于 GET、HEAD、OPTIONS;POST 等
副作用请求不会被默认取消。
缓存
const client = createRequestClient({
cache: { maxSize: 500, clone: true },
});
await client.get("/users", {
cache: {
enabled: true,
ttl: 5 * 60_000,
tags: ["users"],
varyHeaders: ["accept-language"],
},
});
client.cache.clear();
client.cache.invalidateTag("users");
client.cache.invalidatePrefix("GET|");缓存按 Client 隔离,键包含 baseURL、URL、方法、参数、请求体、响应类型和身份
相关请求头摘要。切换用户或租户时仍建议显式 client.cache.clear()。
重试
await request.get("/reports", {
retry: {
enabled: true,
count: 3,
delay: 500,
maxDelay: 10_000,
maxElapsedMs: 20_000,
respectRetryAfter: true,
onRetry: ({ attempt, delay }) => reportRetry(attempt, delay),
},
});默认只允许 GET、HEAD、OPTIONS、PUT、DELETE 重试。POST 不会自动重试;流式或 不可重放的 body 也会被保护性跳过。
认证恢复
const request = createRequestClient({
request: { baseURL: "/api" },
auth: {
getToken: () => userStore.token,
shouldRefresh: () => userStore.isTokenExpiringSoon(),
refresh: async ({ raw, signal }) => {
const response = await raw<{ data: { token: string } }>({
method: "POST",
url: "/auth/refresh-token",
data: { refreshToken: userStore.refreshToken },
signal,
});
const token = response.data.data.token;
userStore.setToken(token);
return token;
},
reauthenticate: async () => {
await reLoginDialog.open();
return userStore.token;
},
isAuthRequest: (config) => config.url?.startsWith("/auth/") === true,
},
});并发请求共享同一次刷新或重新登录。401 请求最多自动重放一次;raw 会自动
设置 skipAuth 并关闭重试、缓存去重和取消跟踪,防止刷新接口递归。
单次请求也可显式使用 skipAuth: true。
Vue 接入
// main.ts
import { createRequestPlugin } from "@robot-admin/request-core/vue";
import { request } from "@/services/request";
app.use(createRequestPlugin(request));这只通过 Vue InjectionKey 提供 Client,不写入 window,也不修改 Vue 全局类型。
组件仍可通过配置显式传入 Client,适合多后端或多租户应用。
useRequest
import { useRequest } from "@robot-admin/request-core/vue";
import { request } from "@/services/request";
const user = useRequest(
({ signal }, id: number) => request.get<User>(`/users/${id}`, { signal }),
{ concurrency: "takeLatest", keepPreviousData: true },
);
await user.run(1);提供 data/error/loading/run/cancel/reset,并在 Vue 作用域销毁时自动取消。
取消或重置后,即使业务执行器没有响应 AbortSignal,过期结果也不会回写;
onSuccess/onError 仅用于观察生命周期,其自身异常不会篡改真实请求结果。
Headless useTableCrud
标准页面优先传一个 source:它既可以是端点配置,也可以是自定义数据源。原有
api/query/mutations 继续兼容,只有需要逐项覆盖底层行为时才使用。
import {
createMemoryTableSource,
defineDetailConfig,
useTableCrud,
} from "@robot-admin/request-core/vue";
const table = useTableCrud({
source: import.meta.env.DEV
? createMemoryTableSource(seedUsers)
: {
list: "/users",
get: "/users/:id",
create: "/users",
update: "/users/:id",
remove: "/users/:id",
},
createNewRow: () => ({ id: 0, name: "" }),
});行类型会从 seedUsers 和 createNewRow 自动推断;内存数据源为每次创建保留隔离
快照并内置查询、详情、新增、更新和删除,适合演示、测试与离线数据。业务代码不需要
声明 UseTableCrudConfig、Pick<> 或重复实现五个异步方法。
详情字段使用 defineDetailConfig() 获得 formatter 参数检查,无需在配置变量上追加
类型标注:
const userDetail = defineDetailConfig({
sections: [
{
title: "用户信息",
columns: 2,
items: [{ label: "姓名", key: "name" }],
},
],
});复杂后端仍可直接接管查询和变更:
import { useTableCrud } from "@robot-admin/request-core/vue";
import { request } from "@/services/request";
const table = useTableCrud<User, UserFilters, UserSort>({
client: request,
autoLoad: "mounted",
initialFilters: { keyword: "" },
query: ({ page, pageSize, filters, sort, signal }) =>
request.get("/users", {
params: { page, pageSize, ...filters, sort },
signal,
concurrency: "takeLatest",
}),
mutations: {
create: (row, { signal }) => request.post("/users", row, { signal }),
update: (row, { signal }) =>
request.put(`/users/${row.id}`, row, { signal }),
remove: (row, { signal }) => request.delete(`/users/${row.id}`, { signal }),
},
createNewRow: () => ({ id: 0, name: "" }),
});
await table.search({ keyword: "robot" });
await table.resetSearch();新增/编辑弹窗不需要在页面重复维护 visible/mode/model/loading。配置一次草稿、标题和提交前转换,任意 UI 组件都可以直接消费结构化的 table.editor:
const table = useTableCrud<Order>({
source: orderApi,
createNewRow: () => ({ id: "", orderNo: "", status: "pending" }),
editor: {
createTitle: "新增订单",
editTitle: (row) => `编辑订单 · ${row.orderNo}`,
prepareSubmit: (row) => ({ ...row, orderNo: row.orderNo.trim() }),
},
});
table.editor.openCreate();
table.editor.openEdit(order);
await table.editor.submit();编辑草稿与原始行隔离;提交成功后自动关闭,校验、预处理或请求失败时保持打开。editor 是无 UI 依赖的结构契约,可交给 Naive UI、Element Plus 或项目自己的表单弹窗呈现。
函数式单表预配置
同一项目中的 Client、加载时机、分页字段名等通常完全相同。使用
createTableCrud() 定义一次应用约定,页面只保留自己的接口、筛选项和变更函数:
// src/composables/useAppTable.ts
import { createTableCrud } from "@robot-admin/request-core/vue";
import { request } from "@/services/request";
export const useAppTable = createTableCrud({
client: request,
autoLoad: "mounted",
defaultPageSize: 20,
listParams: { page: "current", pageSize: "size" },
});
// user-page.ts
const table = useAppTable<User, UserFilters>({
source: { list: "/users" },
initialFilters: { keyword: "" },
mutations: {
create: (row, { signal }) => request.post("/users", row, { signal }),
update: (row, { signal }) =>
request.put(`/users/${row.id}`, row, { signal }),
remove: (row, { signal }) => request.delete(`/users/${row.id}`, { signal }),
},
});工厂只保存配置快照,每次调用仍创建完全隔离的响应式状态。页面配置可以覆盖任意
默认项;listParams 对象用于重命名或省略 page/pageSize/sort,函数形式则可完全
接管参数生成。复杂页面仍直接使用 useTableCrud(),不需要迁移到另一套抽象。
使用侧不需要把整个返回对象逐项转发。控制器代码可保留 table 能力对象,Vue
模板只解构实际使用的字段,避免形成新的“大而全返回值”约定。
主要返回值:
| 状态/方法 | 说明 |
| --------------------------------------------- | ----------------------------------- |
| rows, total, error, lastUpdated | 数据与错误状态 |
| loading, isInitialLoading, isRefreshing | 查询状态 |
| creating, updating, removing | 独立变更状态 |
| filters, sort, page, pagination | 查询条件与分页 |
| refresh/reload/search/resetSearch/setSort | 查询操作 |
| create/save/remove/batchRemove/getDetail | CRUD 操作 |
| editor | 隔离的新增/编辑弹窗状态与提交工作流 |
| createDraft/cancel/dispose | 草稿和生命周期 |
刷新采用 latest-wins,旧响应不会覆盖新数据;批量删除默认最多并发 4 个请求; 关闭删除后刷新时会同步维护本地行和总数;组件作用域销毁会终止未完成任务。 Message 适配器属于呈现观察层,其异常不会改变 CRUD 请求结果。
Naive UI 兼容层
import { useNaiveTableCrud } from "@robot-admin/request-core/naive";
const table = useNaiveTableCrud({
client: request,
query: (context) => userApi.list(context),
columns,
});useNaiveTableCrud 只注入 Naive UI 的 Message/Dialog,数据能力与 /vue 完全
共用。Element Plus 或其他 Vue UI 项目直接使用 /vue 并在应用边界传入可选 ui
适配器;表格、查询表单、列配置和工具栏继续由项目自己的呈现层负责。
兼容 API
createRequestCore()、getData()、postData()、putData()、patchData()、
deleteData() 继续可用。需要让这些全局兼容函数指向新 Client 时:
const request = createRequestClient({ setAsDefault: true });或者调用 setDefaultRequestClient(request)。全局兼容入口只保存“默认 Client”引用;
请求运行状态仍属于具体实例。
从 0.2.x 升级
- 请求代码改从
/axios导入,新代码优先使用createRequestClient()。 - Vue 应用通过
/vue的createRequestPlugin()注入 Client。 - Headless CRUD 从
/vue导入;需要现有 Naive 消息行为时使用/naive的useNaiveTableCrud()。 - 根入口和
/crud暂时兼容,/crud已标记废弃。 dedupe: true仍表示 take-latest;副作用方法不再隐式启用去重。- 缓存、取消和 reLogin 现在按 Axios 实例隔离;多 Client 场景应通过各自控制器 清理状态。
没有删除 0.2.x 的公开请求方法、配置字段或 Naive CRUD 交互能力。
开发与发布验证
bun run type-check
bun run test
bun run build
bun run check:package
bun run verifyprepublishOnly 会执行完整 verify,包含类型、测试、构建、publint、ESM/CJS
入口、跨入口默认实例和依赖边界检查。
