npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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.

Readme

@ahoo-wang/fetcher-decorator

npm version Build Status codecov License npm downloads npm bundle size Ask DeepWiki Storybook

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); // 生命周期钩子将被自动调用

执行流程

当调用装饰方法时,执行遵循以下序列:

  1. 参数解析:方法参数被解析为请求配置
  2. 交换创建:创建 FetchExchange 对象
  3. 执行前钩子:如果实现,则调用 beforeExecute
  4. 拦截器处理:交换通过拦截器链处理
  5. 执行后钩子:如果实现,则调用 afterExecute
  6. 结果提取:提取并返回最终结果

🛠️ 高级用法

继承支持

@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 通过以下优先级顺序确定:

  1. 服务实例 Fetcher 属性:服务实例上的 fetcher 属性具有最高优先级
  2. 端点级别 Fetcher:在端点元数据中指定的 fetcher(@get('/path', { fetcher: 'name' })
  3. 类级别 Fetcher:在类元数据中指定的 fetcher(@api('/base', { fetcher: 'name' })
  4. 默认 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