@ahoo-wang/fetcher-decorator
@ahoo-wang/fetcher-decorator 包使用 TypeScript 装饰器实现声明式 API 服务定义。无需编写命令式的 HTTP 调用,您只需用装饰器注解定义 API 类,框架就会在运行时自动生成实现。
安装
pnpm add @ahoo-wang/fetcher-decorator reflect-metadataWARNING
reflect-metadata 是必需的对等依赖。在使用装饰器之前,必须在应用程序入口点导入一次:
import 'reflect-metadata';架构
graph TB
subgraph sg_1 ["Decorator Layer"]
API["@api() class decorator"]
EP["@get/@post/@put/@delete/@patch<br>endpoint decorators"]
PARAM["@path/@query/@header/@body/@request/@attribute<br>parameter decorators"]
end
subgraph sg_2 ["Metadata Layer"]
AM["ApiMetadata<br>Class-level config"]
EM["EndpointMetadata<br>Method-level config"]
PM["ParameterMetadata<br>Argument bindings"]
FM["FunctionMetadata<br>Merged config"]
end
subgraph sg_3 ["Execution Layer"]
RE["RequestExecutor<br>Builds and runs the request"]
LIFE["ExecuteLifeCycle<br>beforeExecute / afterExecute"]
FETCHER["@ahoo-wang/fetcher<br>Interceptor pipeline"]
end
API --> AM
EP --> EM
PARAM --> PM
AM --> FM
EM --> FM
PM --> FM
FM --> RE
RE --> LIFE
RE --> FETCHER
style API fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
style EP fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
style PARAM fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
style AM fill:#161b22,stroke:#30363d,color:#e6edf3
style EM fill:#161b22,stroke:#30363d,color:#e6edf3
style PM fill:#161b22,stroke:#30363d,color:#e6edf3
style FM fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
style RE fill:#161b22,stroke:#30363d,color:#e6edf3
style LIFE fill:#161b22,stroke:#30363d,color:#e6edf3
style FETCHER fill:#161b22,stroke:#30363d,color:#e6edf3装饰器执行流程
当调用一个被装饰的方法时,框架会执行一个明确定义的管道:
sequenceDiagram
autonumber
participant App as Application
participant Method as Decorated Method
participant RE as RequestExecutor
participant FM as FunctionMetadata
participant Fetcher as Fetcher
participant LC as ExecuteLifeCycle
participant IM as Interceptor Chain
App->>Method: userService.getUser(123)
Method->>RE: execute([123])
RE->>FM: resolveExchangeInit(args)
FM->>FM: Map @path params to path: {id: 123}
FM->>FM: Map @query params to query: {...}
FM->>FM: Map @header params to headers: {...}
FM->>FM: Map @body to body: {...}
FM->>FM: Resolve attributes, timeout, fetcher
FM-->>RE: {request, attributes}
RE->>Fetcher: resolveExchange(request, options)
Fetcher-->>RE: FetchExchange
alt Target implements ExecuteLifeCycle
RE->>LC: beforeExecute(exchange)
LC-->>RE: Modified exchange
end
RE->>IM: interceptors.exchange(exchange)
IM-->>RE: Completed exchange
alt Target implements ExecuteLifeCycle
RE->>LC: afterExecute(exchange)
end
RE->>RE: extractResult()
RE-->>App: Result (Response, JSON, etc.)定义 API 类
@api 装饰器
@api 类装饰器为类中所有方法设置基础路径、请求头、超时时间和 Fetcher 实例。(apiDecorator.ts:232)
import { api, get, post, put, del, path, query, body, header, autoGeneratedError } from '@ahoo-wang/fetcher-decorator';
import 'reflect-metadata';
interface User {
id: number;
name: string;
email: string;
}
@api('/api/v1/users', {
headers: { 'X-Api-Version': '1.0' },
timeout: 10000,
fetcher: 'api', // 使用 'api' 命名的 fetcher
})
class UserService {
@get('/')
getUsers(
@query('page') page: number,
@query('limit') limit: number,
): Promise<User[]> {
throw autoGeneratedError();
}
@get('/:id')
getUser(@path('id') id: number): Promise<User> {
throw autoGeneratedError();
}
@post('/')
createUser(@body() user: Omit<User, 'id'>): Promise<User> {
throw autoGeneratedError();
}
@put('/:id')
updateUser(
@path('id') id: number,
@body() user: Partial<User>,
): Promise<User> {
throw autoGeneratedError();
}
@del('/:id')
deleteUser(@path('id') id: number): Promise<void> {
throw autoGeneratedError();
}
}端点装饰器
| 装饰器 | HTTP 方法 | 源码 |
|---|---|---|
@get(path?, metadata?) | GET | endpointDecorator.ts:101 |
@post(path?, metadata?) | POST | endpointDecorator.ts:126 |
@put(path?, metadata?) | PUT | endpointDecorator.ts:151 |
@del(path?, metadata?) | DELETE | endpointDecorator.ts:176 |
@patch(path?, metadata?) | PATCH | endpointDecorator.ts:201 |
@head(path?, metadata?) | HEAD | endpointDecorator.ts:229 |
@options(path?, metadata?) | OPTIONS | endpointDecorator.ts:254 |
每个端点装饰器都接受可选的 MethodEndpointMetadata 来覆盖类级别的设置:
@get('/special', {
headers: { 'X-Special': 'true' },
timeout: 30000,
resultExtractor: ResultExtractors.Json,
})
specialEndpoint(): Promise<any> {
throw autoGeneratedError();
}参数装饰器
参数装饰器将函数参数映射到 HTTP 请求组件。当未提供显式名称时,参数名会自动从函数签名中提取。(parameterDecorator.ts:199)
| 装饰器 | 类型 | 映射到 | 对象展开 |
|---|---|---|---|
@path(name?) | ParameterType.PATH | URL 路径段 | 是 - 对象键成为路径参数 |
@query(name?) | ParameterType.QUERY | URL 查询字符串 | 是 - 对象键成为查询参数 |
@header(name?) | ParameterType.HEADER | 请求头 | 是 - 对象键成为请求头 |
@body() | ParameterType.BODY | 请求体 | 否 |
@request() | ParameterType.REQUEST | 基础请求对象 | 否 |
@attribute(name?) | ParameterType.ATTRIBUTE | Exchange 属性 | 是 - 对象和 Map |
graph LR
subgraph sg_1 ["Method Arguments"]
A1["id: number"]
A2["filters: object"]
A3["auth: string"]
A4["userData: User"]
A5["request: ParameterRequest"]
A6["attr: Map"]
end
subgraph sg_2 ["Parameter Decorators"]
D1["@path('id')"]
D2["@query()"]
D3["@header('Authorization')"]
D4["@body()"]
D5["@request()"]
D6["@attribute()"]
end
subgraph sg_3 ["Request Components"]
R1["urlParams.path = {id: 123}"]
R2["urlParams.query = {...filters}"]
R3["headers['Authorization'] = auth"]
R4["body = userData"]
R5["Merged into request"]
R6["exchange.attributes"]
end
A1 --> D1 --> R1
A2 --> D2 --> R2
A3 --> D3 --> R3
A4 --> D4 --> R4
A5 --> D5 --> R5
A6 --> D6 --> R6
style A1 fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
style A2 fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
style A3 fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
style A4 fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
style A5 fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
style A6 fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
style D1 fill:#161b22,stroke:#30363d,color:#e6edf3
style D2 fill:#161b22,stroke:#30363d,color:#e6edf3
style D3 fill:#161b22,stroke:#30363d,color:#e6edf3
style D4 fill:#161b22,stroke:#30363d,color:#e6edf3
style D5 fill:#161b22,stroke:#30363d,color:#e6edf3
style D6 fill:#161b22,stroke:#30363d,color:#e6edf3
style R1 fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
style R2 fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
style R3 fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
style R4 fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
style R5 fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
style R6 fill:#2d333b,stroke:#6d5dfc,color:#e6edf3对象参数展开
路径、查询和请求头装饰器支持对象参数。当传入对象时,其键值对会被展开为独立的参数:
@api('/api')
class SearchService {
@get('/search')
search(
@query() filters: { limit: number; offset: number; sort: string },
): Promise<SearchResult[]> {
throw autoGeneratedError();
}
}
// 调用时:
service.search({ limit: 10, offset: 20, sort: 'name' });
// => GET /api/search?limit=10&offset=20&sort=name@request 装饰器
@request() 装饰器允许传入 ParameterRequest 对象以完全控制请求。它会与端点级别的配置合并,参数请求具有更高优先级。(parameterDecorator.ts:372)
@api('/api/users')
class UserService {
@post('/')
createUser(@request() req: ParameterRequest): Promise<User> {
throw autoGeneratedError();
}
}
// 使用:
service.createUser({
path: '/api/users',
headers: { 'X-Idempotency-Key': 'abc123' },
body: { name: 'John' },
timeout: 30000,
});@attribute 装饰器
@attribute() 装饰器将数据传递到 exchange 属性中,可被管道中的任何拦截器读取。(parameterDecorator.ts:408)
@api('/api/orders')
class OrderService {
@post('/')
createOrder(
@body() order: Order,
@attribute('tenantId') tenantId: string,
): Promise<Order> {
throw autoGeneratedError();
}
}生命周期钩子(ExecuteLifeCycle)
类可以实现 ExecuteLifeCycle 接口来挂钩请求执行管道。OpenAI 包 使用此机制根据是否启用流式传输来动态切换结果提取器。(executeLifeCycle.ts:23)
graph TD
START["Method Called"] --> BUILD["Build FetchExchange"]
BUILD --> BEFORE["beforeExecute(exchange)"]
BEFORE --> INTERCEPT["Interceptor Chain"]
INTERCEPT --> AFTER["afterExecute(exchange)"]
AFTER --> RESULT["extractResult()"]
RESULT --> RETURN["Return to caller"]
style START fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
style BUILD fill:#161b22,stroke:#30363d,color:#e6edf3
style BEFORE fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
style INTERCEPT fill:#161b22,stroke:#30363d,color:#e6edf3
style AFTER fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
style RESULT fill:#161b22,stroke:#30363d,color:#e6edf3
style RETURN fill:#2d333b,stroke:#6d5dfc,color:#e6edf3import { api, get, ExecuteLifeCycle, autoGeneratedError } from '@ahoo-wang/fetcher-decorator';
import type { FetchExchange } from '@ahoo-wang/fetcher';
@api('/api/data')
class DataService implements ExecuteLifeCycle {
async beforeExecute(exchange: FetchExchange): Promise<void> {
// 从会话中添加租户 ID
exchange.ensureRequestHeaders()['X-Tenant-Id'] = getTenantId();
// 添加请求跟踪
exchange.attributes.set('requestId', crypto.randomUUID());
}
async afterExecute(exchange: FetchExchange): Promise<void> {
// 记录完成信息
const requestId = exchange.attributes.get('requestId');
console.log(`Request ${requestId} completed: ${exchange.response?.status}`);
}
@get('/items')
getItems(): Promise<Item[]> {
throw autoGeneratedError();
}
}EndpointReturnType
默认情况下,被装饰的方法返回提取的结果(如解析后的 JSON)。您可以将此行为更改为返回整个 FetchExchange 对象。(endpointReturnTypeCapable.ts:14)
| 值 | 描述 |
|---|---|
EndpointReturnType.RESULT | 返回提取的结果(默认) |
EndpointReturnType.EXCHANGE | 返回完整的 FetchExchange 对象 |
import { EndpointReturnType } from '@ahoo-wang/fetcher-decorator';
@api('/api/users', { returnType: EndpointReturnType.EXCHANGE })
class UserService {
@get('/')
getUsers(): Promise<FetchExchange> {
throw autoGeneratedError();
}
}元数据解析
FunctionMetadata 类将 API 级别、端点级别和参数元数据合并为单一的解析配置。端点级别的值覆盖 API 级别的值,参数装饰器的值覆盖两者。(functionMetadata.ts:43)
graph TD
subgraph sg_1 ["Priority (Highest to Lowest)"]
P1["Parameter Decorators<br>@path, @query, @header, @body"]
P2["Endpoint Metadata<br>@get path, metadata arg"]
P3["API Metadata<br>@api basePath, options"]
end
P1 --> MERGE["FunctionMetadata<br>resolveExchangeInit()"]
P2 --> MERGE
P3 --> MERGE
MERGE --> FINAL["Final FetchRequest"]
style P1 fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
style P2 fill:#161b22,stroke:#30363d,color:#e6edf3
style P3 fill:#161b22,stroke:#30363d,color:#e6edf3
style MERGE fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
style FINAL fill:#161b22,stroke:#30363d,color:#e6edf3autoGeneratedError
autoGeneratedError() 函数创建一个占位错误,既能满足 ESLint 的 no-unused-vars 规则,又能表明方法体在运行时会被替换。(generated.ts:41)
import { autoGeneratedError } from '@ahoo-wang/fetcher-decorator';
@get('/users/:id')
getUser(@path('id') id: number): Promise<User> {
throw autoGeneratedError(id); // 参数会被接受但会被忽略
}完整示例
import 'reflect-metadata';
import { api, get, post, del, path, query, body, header, autoGeneratedError } from '@ahoo-wang/fetcher-decorator';
import { NamedFetcher, ResultExtractors } from '@ahoo-wang/fetcher';
// 设置:创建命名 fetcher
new NamedFetcher('myApi', {
baseURL: 'https://api.example.com',
headers: { 'Accept': 'application/json' },
timeout: 5000,
});
interface Product {
id: string;
name: string;
price: number;
}
interface ProductFilters {
category?: string;
minPrice?: number;
maxPrice?: number;
}
@api('/api/v2/products', { fetcher: 'myApi' })
class ProductService {
@get('/')
listProducts(
@query() filters: ProductFilters,
@query('page') page: number = 1,
@query('limit') limit: number = 20,
): Promise<Product[]> {
throw autoGeneratedError();
}
@get('/:id')
getProduct(
@path('id') id: string,
@header('Accept-Language') locale: string = 'en',
): Promise<Product> {
throw autoGeneratedError();
}
@post('/')
createProduct(@body() product: Omit<Product, 'id'>): Promise<Product> {
throw autoGeneratedError();
}
@del('/:id')
deleteProduct(@path('id') id: string): Promise<void> {
throw autoGeneratedError();
}
}
// 使用
const products = new ProductService();
const items = await products.listProducts({ category: 'electronics' }, 1, 10);导出 API 总结
| 导出 | 类型 | 源码 |
|---|---|---|
api | 装饰器 | apiDecorator.ts |
get、post、put、del、patch、head、options | 装饰器 | endpointDecorator.ts |
path、query、header、body、request、attribute | 装饰器 | parameterDecorator.ts |
ApiMetadata | 接口 | apiDecorator.ts |
EndpointMetadata | 接口 | endpointDecorator.ts |
ParameterType | 枚举 | parameterDecorator.ts |
ParameterMetadata | 接口 | parameterDecorator.ts |
FunctionMetadata | 类 | functionMetadata.ts |
RequestExecutor | 类 | requestExecutor.ts |
ExecuteLifeCycle | 接口 | executeLifeCycle.ts |
EndpointReturnType | 枚举 | endpointReturnTypeCapable.ts |
autoGeneratedError | 函数 | generated.ts |
buildRequestExecutor | 函数 | apiDecorator.ts |
getParameterNames | 函数 | reflection.ts |
相关页面
- Fetcher(核心) - 装饰器委托的 HTTP 客户端
- OpenAI - 使用装饰器配合
ExecuteLifeCycle的实际案例 - Generator - 从 OpenAPI 规范自动生成基于装饰器的 API 类
- Wow - DDD/CQRS 基于装饰器的客户端
- 包概览 - 生态系统中的所有包