@ahoo-wang/fetcher-decorator
v3.10.5
Published
TypeScript decorators for clean, declarative API service definitions with Fetcher HTTP client. Enables automatic parameter binding, method mapping, and type-safe API interactions.
Maintainers
Readme
@ahoo-wang/fetcher-decorator
Fetcher HTTP 客户端的装饰器支持。使用 TypeScript 装饰器实现简洁、声明式的 API 服务定义。
🌟 功能特性
- 🎨 简洁的 API 定义:使用直观的装饰器定义 HTTP 服务
- 🧭 自动参数绑定:路径、查询、头部和请求体参数自动绑定
- ⏱️ 可配置超时:支持每方法和每类的超时设置
- 🔗 Fetcher 集成:与 Fetcher 的命名 fetcher 系统无缝集成
- 🛡️ TypeScript 支持:完整的 TypeScript 类型定义
- ⚡ 自动实现:方法自动实现为 HTTP 调用
- 📦 元数据系统:丰富的元数据支持,用于高级定制
🚀 快速开始
安装
# 使用 npm
npm install @ahoo-wang/fetcher-decorator
# 使用 pnpm
pnpm add @ahoo-wang/fetcher-decorator
# 使用 yarn
yarn add @ahoo-wang/fetcher-decorator集成测试示例:Typicode Post 服务
以下示例展示了如何为 JSONPlaceholder API 定义服务,类似于 Fetcher 项目中的集成测试。您可以在 integration-test/src/decorator/typicodePostService.ts 中找到完整实现。
import {
api,
autoGeneratedError,
body,
del,
get,
post,
patch,
path,
put,
query,
} from '@ahoo-wang/fetcher-decorator';
import { typicodeFetcher } from '../fetcher';
import { Post } from '../types';
@api('/posts', { fetcher: typicodeFetcher })
export class TypicodePostService {
@get('')
getPosts(): Promise<Post[]> {
throw autoGeneratedError();
}
@get('/{postId}')
getPost(@path('postId') postId: string): Promise<Post> {
throw autoGeneratedError();
}
@post('')
createPost(@body() post: Post): Promise<Post> {
throw autoGeneratedError();
}
@put('/{postId}')
updatePost(
@path('postId') postId: string,
@body() post: Post,
): Promise<Post> {
throw autoGeneratedError();
}
@patch('/{postId}')
patchPost(
@path('postId') postId: string,
@body() post: Partial<Post>,
): Promise<Post> {
throw autoGeneratedError();
}
@del('/{postId}')
deletePost(@path('postId') postId: string): Promise<object> {
throw autoGeneratedError();
}
@get('')
filterPosts(
@query('userId') userId?: string,
@query('completed') completed?: boolean,
): Promise<Post[]> {
throw autoGeneratedError();
}
}
export const typicodePostService = new TypicodePostService();基本用法
import { NamedFetcher, fetcherRegistrar } from '@ahoo-wang/fetcher';
import {
api,
get,
post,
path,
query,
body,
} from '@ahoo-wang/fetcher-decorator';
// 创建并注册 fetcher
const userFetcher = new NamedFetcher('user', {
baseURL: 'https://api.user-service.com',
});
// 使用装饰器定义服务类
@api('/users', { fetcher: 'user', timeout: 10000 })
class UserService {
@post('/', { timeout: 5000 })
createUser(@body() user: User): Promise<User> {
throw autoGeneratedError(user);
}
@get('/{id}')
getUser(@path() id: number, @query() include: string): Promise<User> {
throw autoGeneratedError(id, include);
}
}
// 使用服务
const userService = new UserService();
const response = await userService.createUser({ name: 'John' });📚 API 参考
类装饰器
@api(basePath, metadata)
为类定义 API 元数据。
参数:
basePath: 类中所有端点的基础路径metadata: API 的额外元数据headers: 类中所有请求的默认头部timeout: 类中所有请求的默认超时时间fetcher: 要使用的 fetcher 实例名称(默认:'default')
示例:
@api('/api/v1', {
headers: { 'X-API-Version': '1.0' },
timeout: 5000,
fetcher: 'api',
})
class ApiService {
// ...
}方法装饰器
@get(path, metadata)
定义 GET 端点。
@post(path, metadata)
定义 POST 端点。
@put(path, metadata)
定义 PUT 端点。
@del(path, metadata)
定义 DELETE 端点。
@patch(path, metadata)
定义 PATCH 端点。
@head(path, metadata)
定义 HEAD 端点。
@options(path, metadata)
定义 OPTIONS 端点。
通用参数:
path: 端点路径(相对于类基础路径)metadata: 端点的额外元数据headers: 请求的头部timeout: 请求的超时时间fetcher: 要使用的 fetcher 实例名称
示例:
class UserService {
@get('/{id}', { timeout: 3000 })
getUser(@path() id: number): Promise<User> {
throw autoGeneratedError(id);
}
@post('/', { headers: { 'Content-Type': 'application/json' } })
createUser(@body() user: User): Promise<Response> {
throw autoGeneratedError(user);
}
}参数装饰器
@path(name)
定义路径参数。名称是可选的 - 如果未提供,将使用反射从方法参数名自动提取。
参数:
name: 参数名称(在路径模板中使用,可选 - 如果未提供则自动提取)
@query(name)
定义查询参数。名称是可选的 - 如果未提供,将使用反射从方法参数名自动提取。
参数:
name: 参数名称(在查询字符串中使用,可选 - 如果未提供则自动提取)
@body()
定义请求体。请求体参数没有名称,因为每个请求只有一个请求体。
@header(name)
定义头部参数。名称是可选的 - 如果未提供,将使用反射从方法参数名自动提取。
参数:
name: 头部名称(可选 - 如果未提供则自动提取)
@request()
定义请求参数,将用作基础请求对象。这允许您传递一个完整的 FetcherRequest 对象来自定义请求配置。
示例:
class UserService {
@get('/search')
searchUsers(
@query('q') query: string,
@query() limit: number,
@header('Authorization') auth: string,
): Promise<Response> {
throw autoGeneratedError(query, limit, auth);
}
@post('/users')
createUsers(@request() request: FetcherRequest): Promise<Response> {
throw autoGeneratedError(request);
}
@put('/{id}')
updateUser(@path() id: number, @body() user: User): Promise<Response> {
throw autoGeneratedError(id, user);
}
}🪝 生命周期钩子
ExecuteLifeCycle 接口允许您钩入请求执行生命周期,并在拦截器处理 FetchExchange 之前和之后修改它。这提供了一种在执行流程的特定点自定义 API 调用行为的方法。
ExecuteLifeCycle 接口
import { ExecuteLifeCycle } from '@ahoo-wang/fetcher-decorator';
interface ExecuteLifeCycle {
beforeExecute?(exchange: FetchExchange): void | Promise<void>;
afterExecute?(exchange: FetchExchange): void | Promise<void>;
}生命周期方法
beforeExecute(exchange: FetchExchange)
在 FetchExchange 被拦截器处理之前调用。用户可以在处理之前检查或修改交换,例如添加头部、修改请求体或设置日志记录。
参数:
exchange: 表示请求和响应的FetchExchange对象
afterExecute(exchange: FetchExchange)
在 FetchExchange 被拦截器处理之后调用。用户可以在处理之后检查或修改交换,例如处理响应数据、记录结果或执行清理。
参数:
exchange: 表示请求和响应的FetchExchange对象
使用示例
import { api, get, path, ExecuteLifeCycle } from '@ahoo-wang/fetcher-decorator';
import { FetchExchange } from '@ahoo-wang/fetcher';
@api('/users')
class UserService implements ExecuteLifeCycle {
@get('/{id}')
getUser(@path() id: number): Promise<User> {
throw autoGeneratedError(id);
}
async beforeExecute(exchange: FetchExchange): Promise<void> {
// 在请求执行前添加自定义头部
exchange.request.headers.set('X-Custom-Header', 'value');
// 记录请求
console.log('Executing request:', exchange.request.url);
}
async afterExecute(exchange: FetchExchange): Promise<void> {
// 记录响应状态
console.log('Response status:', exchange.response?.status);
// 处理特定错误代码
if (exchange.response?.status === 401) {
// 处理未授权访问
console.warn('Unauthorized access detected');
}
}
}
const userService = new UserService();
const user = await userService.getUser(123); // 生命周期钩子将被自动调用执行流程
当调用装饰方法时,执行遵循以下序列:
- 参数解析:方法参数被解析为请求配置
- 交换创建:创建
FetchExchange对象 - 执行前钩子:如果实现,则调用
beforeExecute - 拦截器处理:交换通过拦截器链处理
- 执行后钩子:如果实现,则调用
afterExecute - 结果提取:提取并返回最终结果
🛠️ 高级用法
继承支持
@api('/base')
class BaseService {
@get('/status')
getStatus(): Promise<Response> {
throw autoGeneratedError();
}
}
@api('/users')
class UserService extends BaseService {
@get('/{id}')
getUser(@path() id: number): Promise<Response> {
throw autoGeneratedError(id);
}
}复杂参数处理
@api('/api')
class ComplexService {
@post('/batch')
batchOperation(
@body() items: Item[],
@header('X-Request-ID') requestId: string,
@query() dryRun: boolean = false,
): Promise<Response> {
throw autoGeneratedError(items, requestId, dryRun);
}
}请求合并
当使用 @request() 装饰器时,提供的 FetcherRequest 对象会使用复杂的合并策略与端点特定配置进行合并:
- 嵌套对象(路径、查询、头部)会被递归合并,参数值优先
- 基本值(方法、请求体、超时、信号)来自参数请求的值会覆盖端点值
- 空对象会被优雅地处理,回退到端点配置
@api('/users')
class UserService {
@post('/')
createUsers(
@body() user: User,
@request() request: FetcherRequest,
): Promise<Response> {
// 最终请求将合并:
// - 端点方法(POST)
// - 请求体参数(user 对象)
// - 来自 request 参数的任何配置
throw autoGeneratedError(user, request);
}
}Fetcher 解析优先级
用于发起 HTTP 请求的 fetcher 通过以下优先级顺序确定:
- 服务实例 Fetcher 属性:服务实例上的
fetcher属性具有最高优先级 - 端点级别 Fetcher:在端点元数据中指定的 fetcher(
@get('/path', { fetcher: 'name' })) - 类级别 Fetcher:在类元数据中指定的 fetcher(
@api('/base', { fetcher: 'name' })) - 默认 Fetcher:如果以上都没有指定,则回退到默认 fetcher
// 展示 fetcher 解析优先级的示例
const customFetcher = new Fetcher({ baseURL: 'https://custom-api.com' });
@api('/users', { fetcher: 'class-level-fetcher' })
class UserService {
@get('/{id}', { fetcher: 'endpoint-level-fetcher' })
getUser(@path() id: number): Promise<User> {
throw autoGeneratedError(id);
}
}
// 带有 fetcher 属性的服务实例(最高优先级)
const userService = new UserService();
userService.fetcher = customFetcher; // 将使用此 fetcher
// 如果实例上没有设置 fetcher 属性,则会使用:
// 1. 'endpoint-level-fetcher'(来自 @get 装饰器)
// 2. 'class-level-fetcher'(来自 @api 装饰器)
// 3. 默认 fetcher(如果以上都没有指定)结果提取器
结果提取器用于处理和提取应用程序中不同类型响应或结果的数据。 它们允许您自定义如何处理和返回 HTTP 请求的响应。
可用的结果提取器
- ExchangeResultExtractor: 返回原始的 FetchExchange 对象
- ResponseResultExtractor: 返回 FetchExchange 中的响应对象
- JsonResultExtractor: 将响应内容解析为 JSON 格式(默认)
- TextResultExtractor: 将响应内容解析为文本格式
- EventStreamResultExtractor: 从 FetchExchange 中提取服务器发送事件流
- JsonEventStreamResultExtractor: 从 FetchExchange 中提取 JSON 服务器发送事件流
使用结果提取器
您可以在类级别或方法级别指定结果提取器:
import { ResultExtractors } from '@ahoo-wang/fetcher-decorator';
@api('/users', { resultExtractor: ResultExtractors.Json })
class UserService {
// 使用类级别的 JSON 结果提取器
@get('/{id}')
getUser(@path() id: number): Promise<User> {
throw autoGeneratedError(id);
}
// 使用 EventStream 结果提取器覆盖
@get('/events', { resultExtractor: ResultExtractors.EventStream })
getUserEvents(): Promise<ServerSentEventStream> {
throw autoGeneratedError();
}
// 使用 JsonEventStream 结果提取器处理 JSON 事件
@get('/json-events', {
resultExtractor: ResultExtractors.JsonEventStream,
})
getJsonEvents(): Promise<JsonServerSentEventStream<MyDataType>> {
throw autoGeneratedError();
}
}🧪 测试
# 运行测试
pnpm test
# 运行带覆盖率的测试
pnpm test --coverage🤝 贡献
欢迎贡献!请查看 贡献指南 了解更多详情。
📄 许可证
Apache-2.0
