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

@infra-x/fwrap

v0.1.1

Published

Type-safe HTTP client with hooks support

Readme

Fetcher

基于 Hooks 的类型安全 HTTP 客户端。

核心特性

  • 类型安全的 API 设计,完整的 TypeScript 支持
  • 自动 JSON 请求体序列化和响应解析
  • 统一的错误处理模型(不抛出异常)
  • 灵活的 Hook 系统用于请求拦截和处理
  • 内置超时和重试机制
  • 支持客户端扩展,方便创建预配置实例
  • 零依赖,基于原生 Fetch API

快速开始

import { createClient } from 'fetcher'

const client = createClient({
  prefixUrl: 'https://api.example.com',
  timeout: 5000,
})

// GET 请求
const { data, error, response } = await client.get('/users')
if (error) {
  console.error('请求失败:', error)
} else {
  console.log('用户数据:', data)
  console.log('响应状态:', response.status)
}

// POST 请求 - 自动 JSON 序列化
const result = await client.post('/users', {
  body: { name: 'Alice', email: '[email protected]' },
})

客户端扩展

使用 extend() 创建预配置的客户端实例:

// 基础客户端
const client = createClient({
  prefixUrl: 'https://api.example.com',
  timeout: 10000,
})

// 认证客户端
const authClient = client.extend({
  headers: {
    Authorization: `Bearer ${token}`,
  },
})

// 特定 API 客户端
const usersAPI = authClient.extend({
  prefixUrl: 'https://api.example.com/users',
  retry: 2,
})

响应结构

所有请求返回统一的 FetchResult<T> 结构:

interface FetchResult<T = unknown> {
  data: T | null      // 自动解析的 JSON 数据,非 JSON 响应为 null
  response: Response | null  // 原始 Response 对象
  error: Error | null        // 错误对象,成功时为 null
}

自动 JSON 解析规则:

  • 响应 Content-Type 为 application/json 时自动解析
  • 2xx 状态码且响应体为空时,datanull
  • 解析失败或非 JSON 响应时,datanull,可通过 response 访问原始数据

配置参考

常用选项

| 选项 | 类型 | 默认值 | 说明 | |------|------|--------|------| | prefixUrl | string \| URL | - | 基础 URL,自动拼接到请求路径前 | | timeout | number \| false | 10000 | 超时时间(毫秒),false 禁用超时 | | retry | number \| RetryConfig | 0 | 重试配置,数字表示重试次数 | | throwHttpErrors | boolean \| function | true | 是否抛出 HTTP 错误(非 2xx) | | body | object \| string \| ... | - | 请求体,对象自动 JSON 序列化 | | headers | object | - | 请求头 | | searchParams | object \| URLSearchParams | - | URL 查询参数 | | fetch | typeof fetch | globalThis.fetch | 自定义 fetch 实现 |

重试配置

快速配置:

const client = createClient({
  retry: 3,  // 重试 3 次,使用默认策略
})

详细配置对象(RetryConfig):

| 字段 | 类型 | 默认值 | 说明 | |------|------|--------|------| | limit | number | - | 最大重试次数 | | methods | string[] | ['GET', 'PUT', 'HEAD', 'DELETE', 'OPTIONS', 'TRACE'] | 允许重试的 HTTP 方法 | | statusCodes | number[] | [408, 413, 429, 500, 502, 503, 504] | 触发重试的状态码 | | delay | function | 指数退避 | 延迟函数 (attemptCount) => number | | jitter | boolean \| function | false | 是否添加随机抖动 | | backoffLimit | number | Infinity | 最大退避延迟(毫秒) | | retryOnTimeout | boolean | true | 超时是否重试 |

示例:

const client = createClient({
  retry: {
    limit: 3,
    methods: ['GET', 'POST'],
    statusCodes: [429, 503],
    delay: (attemptCount) => 1000 * Math.pow(2, attemptCount),
    retryOnTimeout: true,
  },
})

Hook 系统

Hook 允许在请求生命周期的不同阶段拦截和处理。通过声明式配置实现请求拦截、响应处理、重试控制等功能。

Hook 类型

  • onRequest - 请求发送前修改 Request 对象
  • onResponse - 响应接收后处理或强制重试
  • onRetry - 重试前修改 Request 或记录日志
  • onError - 错误发生时转换或增强错误信息

声明式配置

在创建客户端时配置 hooks:

import { createClient, RetrySignal } from 'fetcher'

const client = createClient({
  prefixUrl: 'https://api.example.com',
  onRequest: [
    (request) => {
      request.headers.set('X-Request-ID', crypto.randomUUID())
      return request
    },
  ],
  onResponse: [
    async (request, options, response) => {
      if (response.status === 401) {
        await refreshToken()
        return RetrySignal  // 强制重试
      }
    },
  ],
  onRetry: [
    ({ request, error, retryCount }) => {
      console.log(`重试第 ${retryCount} 次:`, error.message)
    },
  ],
  onError: [
    (error, state) => {
      console.error('请求失败:', error)
    },
  ],
})

使用 extend() 组合 Hooks

通过 extend() 创建具有不同 hook 配置的客户端:

// 基础客户端(带全局日志)
const baseClient = createClient({
  prefixUrl: 'https://api.example.com',
  onRequest: [
    (request) => {
      console.log('Request:', request.url)
      return request
    },
  ],
})

// 认证客户端 - 继承父 hooks + 添加认证
const authClient = baseClient.extend({
  onRequest: [
    (request) => {
      request.headers.set('Authorization', `Bearer ${getToken()}`)
      return request
    },
  ],
  onResponse: [
    async (request, options, response) => {
      if (response.status === 401) {
        await refreshToken()
        return RetrySignal
      }
    },
  ],
})

// authClient 会先执行 baseClient 的日志 hook,再执行认证 hook

Hook 执行顺序

  • hooks 按数组顺序依次执行
  • extend() 创建的客户端先执行父 hooks,再执行自己的 hooks
  • 多个 hooks 可以协同工作,分别处理不同关注点

错误处理

错误类型

  • HTTPError - HTTP 错误(非 2xx 状态码)
  • TimeoutError - 请求超时

基础用法

import { HTTPError, TimeoutError } from 'fetcher'

const { data, error } = await client.get('/api/data')

if (error) {
  if (error instanceof HTTPError) {
    console.error(`HTTP ${error.response.status}:`, error.message)
  } else if (error instanceof TimeoutError) {
    console.error('请求超时')
  } else {
    console.error('网络错误:', error)
  }
}

throwHttpErrors 选项

默认情况下,非 2xx 响应会抛出 HTTPError。可以通过 throwHttpErrors 配置:

// 禁用所有 HTTP 错误抛出
const client = createClient({ throwHttpErrors: false })

// 条件抛出(例如忽略 404)
const client = createClient({
  throwHttpErrors: (status) => status !== 404,
})

测试

pnpm test          # 运行测试
pnpm test:ui       # UI 界面
pnpm test:run      # 单次运行
pnpm test:coverage # 覆盖率报告