关于 data-client-rest-setup
用于在 REST API 项目中配置 `@data-client/rest` 的设置与迁移技能。它识别现有 HTTP 库、引导迁移,并协助设计项目专用的 endpoint 基类。
主要功能
- 01识别现有 HTTP 库
- 02引导从 Axios、fetch、Ky、SuperAgent 和 Got 迁移
- 03配置自定义 RestEndpoint 基类
- 04提供 resource、endpoint 和 schema 建议
适用场景
- 01将应用从 Axios 或 fetch 迁移
- 02为新项目添加 REST 支持
- 03统一 endpoint 的认证与响应处理
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ reactive/ data-client --skill data-client-rest-setup安装后在 Claude Code 中输入/data-client-rest-setup确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/axios-migration.mdfetch-migration.mdgot-migration.mdky-migration.mdsuperagent-migration.md详细介绍
REST 协议设置与迁移
此技能为项目配置 @data-client/rest,可用于全新设置,也可用于从现有 HTTP 库迁移。应在技能「data-client-setup」检测到 REST API 模式后应用。
请先应用技能「data-client-rest」,以确保实现模式准确。
第 1 步:安装
将 REST 包与核心包一同安装:
# npm
npm install @data-client/rest
# yarn
yarn add @data-client/rest
# pnpm
pnpm add @data-client/rest
第 2 步:检测现有 HTTP 模式
扫描代码库,确定当前使用的库。多种模式可能并存——针对相关文件,分别运行每个适用的迁移子流程。
检测清单
检查 package.json 中的依赖项,并扫描源文件:
| 检查 | 模式 | 操作 |
|---|---|---|
依赖项中有 "axios",或源文件中有 import.*from ['"]axios['"] | Axios | 按照 references/axios-migration.md 操作 |
有使用 REST 风格 URL 的 fetch( 调用,或有封装 fetch 的函数 | 原生 fetch | 按照 references/fetch-migration.md 操作 |
依赖项中有 "ky",或有 import.*from ['"]ky['"] | Ky | 按照 references/ky-migration.md 操作 |
依赖项中有 "superagent" | SuperAgent | 按照 references/superagent-migration.md 操作 |
依赖项中有 "got"(在浏览器代码中较少见) | Got | 按照 references/got-migration.md 操作 |
| 未检测到现有 HTTP 库 | 全新项目 | 跳至第 3 步:自定义 RestEndpoint 基类 |
检测结果不明确
如果无法确定使用了哪些模式(例如,没有明确的导入语句,但存在 HTTP 调用),请询问用户:
我在你的代码库中发现了 HTTP 调用,但无法确定所用的库。你是要从以下哪种方案迁移?
- axios
- 原生 fetch / 自定义 fetch 封装
- ky
- superagent
- 其他方案(请说明)
- 从头开始(无需迁移)
混合代码库
检测到多个 HTTP 库时,针对相关文件分别运行各个子流程。这些子流程彼此独立,不会冲突:
- 确定哪些文件使用了哪些库(按导入语句分组)
- 对每组文件运行对应的迁移子流程
- 完成所有迁移后,继续设置基类
迁移参考
每项迁移都有独立的参考文档。根据上面的检测结果,只阅读相关文档。完成迁移后,返回此处设置基类。
- Axios → references/axios-migration.md — codemod、拦截器、错误处理、超时、cancelToken、responseType、paramsSerializer、身份验证、validateStatus、CSRF、上传进度
- 原生 fetch → references/fetch-migration.md — fetch 封装、请求头、状态检查、POST 模式、错误处理
- Ky → references/ky-migration.md — prefixUrl、钩子、HTTPError、实例配置
- SuperAgent → references/superagent-migration.md — 链式 API、插件、Agent、文件上传
- Got → references/got-migration.md — Node.js 模式、钩子、分页、重试
第 3 步:自定义 RestEndpoint 基类
完成安装和所有迁移后,请主动提出为项目创建自定义 RestEndpoint 类。
检测清单
扫描现有代码库,查找常见的 REST 模式,包括:
- 基础 URL / API 前缀:查找硬编码的 URL,例如
https://api.example.com,或环境变量,例如process.env.API_URL - 身份验证:查找
Authorization标头、localStorage/cookies 中的令牌、身份验证拦截器 - Content-Type 处理:检查 API 是否使用 JSON、form-data 或自定义内容类型
- 错误处理:查找错误响应模式、状态码处理
- 请求/响应转换:数据转换、日期解析、大小写转换
- 查询字符串格式:简单参数与嵌套对象(可能需要 qs 库)
基类模板
在 src/api/BaseEndpoint.ts 创建文件(或根据项目结构选择类似位置):
import { RestEndpoint, RestGenerics } from '@data-client/rest';
/**
* Base RestEndpoint with project-specific defaults.
* Extend this for all REST API endpoints.
*/
export class BaseEndpoint<O extends RestGenerics = any> extends RestEndpoint<O> {
// API base URL - adjust based on detected patterns
urlPrefix = process.env.REACT_APP_API_URL ?? 'https://api.example.com';
// Add authentication headers
getHeaders(headers: HeadersInit): HeadersInit {
const token = localStorage.getItem('authToken');
return {
...headers,
...(token && { Authorization: `Bearer ${token}` }),
};
}
}
常见生命周期重写
根据代码库中检测到的情况添加这些内容。完整 API 文档请参阅 RestEndpoint。
身份验证(异步刷新令牌)
async getHeaders(headers: HeadersInit): Promise<HeadersInit> {
const token = await getValidToken(); // handles refresh
return {
...headers,
Authorization: `Bearer ${token}`,
};
}
来自 React context 的身份验证(Okta、Auth0)
当身份验证令牌存储在 React context 中(而非 localStorage)时,基类上的 getHeaders() 无法访问这些令牌。使用 hookifyResource() 将从 context 获取的标头注入每个端点:
import { hookifyResource, resource } from '@data-client/rest';
const ArticleResourceBase = resource({
path: '/articles/:id',
schema: Article,
Endpoint: BaseEndpoint,
});
export const ArticleResource = hookifyResource(
ArticleResourceBase,
function useInit() {
const accessToken = useContext(AuthContext);
return {
headers: { Authorization: `Bearer ${accessToken}` },
};
},
);
用法:useSuspense(ArticleResource.useGet(), { id }) — 该 Hook 会在每次渲染时调用 useInit(),因此令牌始终是从 context 获取的最新令牌。
自定义请求初始化(CSRF、凭据)
getRequestInit(body?: RequestInit['body'] | Record<string, unknown>): RequestInit {
return {
...super.getRequestInit(body),
credentials: 'include', // for cookies
headers: {
'X-CSRF-Token': getCsrfToken(),
},
};
}
自定义响应解析(解包数据信封)
process(value: any, ...args: any[]) {
// If API wraps responses in { data: ... }
return value.data ?? value;
}
自定义错误处理
async fetchResponse(input: RequestInfo, init: RequestInit): Promise<Response> {
const response = await super.fetchResponse(input, init);
if (response.status === 401) {
window.dispatchEvent(new CustomEvent('auth:expired'));
}
return response;
}
自定义搜索参数(使用 qs 库)
searchToString(searchParams: Record<string, any>): string {
return qs.stringify(searchParams, { arrayFormat: 'brackets' });
}
自定义 parseResponse(处理非 JSON 响应)
async parseResponse(response: Response): Promise<any> {
const contentType = response.headers.get('content-type');
if (contentType?.includes('text/csv')) {
return parseCSV(await response.text());
}
return super.parseResponse(response);
}
包含多个重写项的完整示例
import { RestEndpoint, RestGenerics } from '@data-client/rest';
import qs from 'qs';
export class BaseEndpoint<O extends RestGenerics = any> extends RestEndpoint<O> {
urlPrefix = process.env.API_URL ?? 'http://localhost:3001/api';
async getHeaders(headers: HeadersInit): Promise<HeadersInit> {
const token = await getAuthToken();
return {
...headers,
'Content-Type': 'application/json',
...(token && { Authorization: `Bearer ${token}` }),
};
}
getRequestInit(body?: RequestInit['body'] | Record<string, unknown>): RequestInit {
return {
...super.getRequestInit(body),
credentials: 'include',
};
}
searchToString(searchParams: Record<string, any>): string {
return qs.stringify(searchParams, { arrayFormat: 'brackets' });
}
process(value: any, ...args: any[]) {
return value?.data ?? value;
}
}
async function getAuthToken(): Promise<string | null> {
return localStorage.getItem('token');
}
设置后的用法
创建基类后,使用它,而不是直接使用 RestEndpoint。
选择 resource() 还是单独的端点
当 API 模块在单个路径上提供标准 CRUD 操作(列表、获取、创建、更新、删除)时,使用 resource()。这是常见情况:
import { resource } from '@data-client/rest';
import { BaseEndpoint } from './BaseEndpoint';
import { Todo } from '../schemas/Todo';
export const TodoResource = resource({
path: '/todos/:id',
schema: Todo,
Endpoint: BaseEndpoint,
});
// Provides: TodoResource.get, .getList, .create, .update, .delete, .partialUpdate
对于非 CRUD 操作(搜索、身份验证、自定义操作),或者路径不符合 resource() 约定时,使用独立的 new BaseEndpoint():
export const loginEndpoint = new BaseEndpoint({
path: '/auth/login',
method: 'POST' as const,
body: {} as { email: string; password: string },
schema: undefined,
});
请求体类型:使用 body: {} as BodyType(真值)——不要使用 undefined as unknown as BodyType。需要提供真值,端点才能正确为 POST/PUT/PATCH 发送请求体。
与现有验证共存(Zod、Yup)
如果代码库已使用 Zod/Yup 验证响应,优先将 Entity 作为事实来源,用于受益于缓存/规范化的类型。仅对不需要规范化的类型保留 Zod(身份验证令牌、表单验证类型、一次性响应)。详细选项请参阅迁移参考文件。
后续步骤
- 定义 Entity 类(技能“data-client-schema”),并通过
schema:将其连接到端点——这一步至关重要,并非可选项。设置了schema: undefined的端点会绕过规范化和缓存。 - 应用技能“data-client-rest”,了解资源和端点模式
- 应用技能“data-client-react”或“data-client-vue”,了解基于 Hook 的用法
参考资料
- RestEndpoint - RestEndpoint 完整 API
- resource - Resource 工厂函数
- 身份验证指南 - 身份验证模式和示例
- Django 集成 - Django REST Framework 模式
- Axios 迁移指南 - Axios 完整迁移文档