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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@lark-apaas/hey-api-custom-client-axios

v1.0.4

Published

Custom axios client plugin for @hey-api/openapi-ts

Downloads

663

Readme

Custom Axios Client Plugin

基于 @hey-api/client-axios 的自定义 axios 客户端插件,用于 @hey-api/openapi-ts 代码生成。

设计理念

单例模式:一个应用使用一个全局 axios 客户端实例,所有 API 请求基于此实例。通过配置文件统一封装认证、错误处理等必备逻辑,业务代码无需感知这些细节,只需传递业务相关参数。

安装

npm install @lark-apaas/hey-api-custom-client-axios
# or
yarn add @lark-apaas/hey-api-custom-client-axios

快速开始

1. 配置插件(代码生成时)

import { customClientAxiosPlugin } from '@lark-apaas/hey-api-custom-client-axios';
import { createClient } from '@hey-api/openapi-ts';

await createClient({
  input: openapiPath,
  output: {
    path: clientSdkOutPath,
  },
  plugins: [
    '@hey-api/typescript',
    '@hey-api/sdk',
    customClientAxiosPlugin({
      // 指定运行时配置文件路径(推荐)
      runtimeConfigPath: './src/api/client-config.ts',
      // 其他配置
      throwOnError: false,
    }),
  ],
});

2. 创建全局配置文件(运行时)

创建 src/api/client-config.ts,封装应用级的必备逻辑:

import type { CreateClientConfig } from '@lark-apaas/hey-api-custom-client-axios';

/**
 * 创建全局客户端配置
 * 这个函数会在客户端初始化时被调用
 */
export const createClientConfig: CreateClientConfig = () => {
  return {
    // 基础 URL
    baseURL: process.env.API_BASE_URL || 'https://api.example.com',

    // 全局 headers
    headers: {
      'X-App-Version': '1.0.0',
      'X-Platform': 'web',
    },

    // 统一认证逻辑
    auth: async (auth) => {
      // 从 localStorage 或其他地方获取 token
      const token = localStorage.getItem('auth_token');
      if (!token) {
        throw new Error('未登录');
      }
      return token;
    },

    // 响应数据转换(可选)
    responseTransformer: async (data: any) => {
      // 统一转换日期字符串为 Date 对象
      return transformDates(data);
    },

    // 响应数据验证(可选)
    responseValidator: async (data: any) => {
      // 统一验证响应格式
      if (data.code !== 0) {
        throw new Error(data.message || '请求失败');
      }
      return data.data;
    },

    // 其他全局配置
    timeout: 30000,
    withCredentials: true,
  };
};

3. 业务代码使用(无感知)

生成 SDK 后,业务代码只需传递业务参数:

import { getUserById, updateUser, listUsers } from './generated/sdk';

// 示例 1: 简单查询
// 自动应用全局配置(baseURL、认证、headers 等)
const user = await getUserById({
  path: { id: '123' },
});

// 示例 2: 传递请求级额外 header
const users = await listUsers({
  query: { page: 1, limit: 10 },
  headers: {
    'X-Request-ID': generateRequestId(), // 可选的请求级 header
  },
});

// 示例 3: 更新数据
await updateUser({
  path: { id: '123' },
  body: { name: 'New Name' },
});

配置选项

插件配置(代码生成时)

customClientAxiosPlugin() 中可配置:

runtimeConfigPath

  • 类型: string
  • 说明: 运行时配置文件路径(相对于生成的 SDK 目录)
  • 推荐使用: 通过此选项将全局配置逻辑集中管理
customClientAxiosPlugin({
  runtimeConfigPath: './src/api/client-config.ts',
})

throwOnError

  • 类型: boolean
  • 默认值: false
  • 说明: 请求失败时是否抛出异常。设为 false 时,错误会作为返回对象的 error 字段返回
customClientAxiosPlugin({
  throwOnError: true, // 请求失败时抛出异常
})

运行时配置(createClientConfig)

支持所有 axios 和 @hey-api/client-axios 的配置选项:

基础配置

| 配置项 | 类型 | 说明 | |--------|------|------| | baseURL | string | API 基础 URL | | timeout | number | 请求超时时间(毫秒) | | headers | object | 全局请求头 | | withCredentials | boolean | 是否携带凭证(cookie) |

高级配置

| 配置项 | 类型 | 说明 | |--------|------|------| | auth | function | 统一认证逻辑,返回 token | | responseTransformer | function | 响应数据转换函数 | | responseValidator | function | 响应数据验证函数 | | requestValidator | function | 请求数据验证函数 | | bodySerializer | function | 请求体序列化函数 | | querySerializer | object \| function | 查询参数序列化配置 |

Axios 原生配置

支持所有 axios 原生配置(除了 method):

  • responseType - 响应类型
  • responseEncoding - 响应编码
  • xsrfCookieName / xsrfHeaderName - CSRF 防护
  • maxContentLength / maxBodyLength - 内容限制
  • maxRedirects - 重定向限制
  • validateStatus - 状态码验证
  • paramsSerializer - Axios 原生参数序列化
  • 等等...

最佳实践

1. 统一认证管理

// src/api/client-config.ts
export const createClientConfig = () => {
  return {
    auth: async (auth) => {
      // 根据不同的认证类型返回不同的 token
      if (auth.type === 'http' && auth.scheme === 'bearer') {
        const token = await getAccessToken();
        return token;
      }
      if (auth.type === 'apiKey' && auth.in === 'header') {
        return process.env.API_KEY;
      }
    },
  };
};

2. 统一错误处理

export const createClientConfig = () => {
  return {
    responseValidator: async (data: any) => {
      // 统一的响应格式验证
      if (!data || typeof data !== 'object') {
        throw new Error('Invalid response format');
      }

      // 业务错误码处理
      if (data.code !== 0) {
        const error = new Error(data.message || '请求失败');
        error.code = data.code;
        throw error;
      }

      // 返回实际数据
      return data.data;
    },

    throwOnError: false, // 不抛出 HTTP 错误
  };
};

// 业务使用
const result = await getUserById({ path: { id: '123' } });
if (result.error) {
  // 处理错误
  console.error(result.error);
} else {
  // 使用数据
  console.log(result.data);
}

3. 环境配置

// src/api/client-config.ts
const getBaseURL = () => {
  if (process.env.NODE_ENV === 'production') {
    return 'https://api.production.com';
  }
  if (process.env.NODE_ENV === 'staging') {
    return 'https://api.staging.com';
  }
  return 'http://localhost:3000';
};

export const createClientConfig = () => {
  return {
    baseURL: getBaseURL(),
    headers: {
      'X-Environment': process.env.NODE_ENV,
    },
  };
};

4. 请求追踪

export const createClientConfig = () => {
  return {
    headers: {
      'X-Request-ID': () => generateUUID(), // 每次请求生成新的 ID
      'X-Session-ID': getSessionId(),       // 会话级别的 ID
    },
  };
};

5. 数据转换

// 转换日期字符串
const transformDates = (data: any): any => {
  if (!data || typeof data !== 'object') return data;

  const result = Array.isArray(data) ? [] : {};
  for (const [key, value] of Object.entries(data)) {
    if (typeof value === 'string' && /^\d{4}-\d{2}-\d{2}T/.test(value)) {
      result[key] = new Date(value);
    } else if (typeof value === 'object') {
      result[key] = transformDates(value);
    } else {
      result[key] = value;
    }
  }
  return result;
};

export const createClientConfig = () => {
  return {
    responseTransformer: async (data: any) => {
      return transformDates(data);
    },
  };
};

高级用法

访问客户端实例

生成的 SDK 导出了全局客户端实例,可以访问底层 axios 实例:

import { client } from './generated/sdk';

// 访问 axios 实例,添加拦截器
client.instance.interceptors.request.use(
  (config) => {
    console.log('Request:', config.url);
    return config;
  }
);

client.instance.interceptors.response.use(
  (response) => {
    console.log('Response:', response.status);
    return response;
  },
  (error) => {
    console.error('Error:', error.message);
    return Promise.reject(error);
  }
);

动态更新配置

虽然推荐使用 runtimeConfigPath,但也可以在运行时动态更新配置:

import { client } from './generated/sdk';

// 用户登录后更新 token
function onUserLogin(token: string) {
  client.setConfig({
    headers: {
      Authorization: `Bearer ${token}`,
    },
  });
}

// 用户登出后清除 token
function onUserLogout() {
  client.setConfig({
    headers: {
      Authorization: null, // null 值会删除该 header
    },
  });
}

自定义生成的客户端代码

生成的客户端代码会被复制到输出目录,可以直接修改(不推荐,升级时会被覆盖):

// 生成的 client.ts 文件
export const createClient = (config: Config = {}): Client => {
  // 在这里添加自定义逻辑
  // 注意:这些修改会在重新生成时丢失
};

工作原理

  1. 代码生成: @hey-api/openapi-ts 根据 OpenAPI 规范生成客户端 SDK
  2. 客户端复制: 插件将自定义的客户端实现文件复制到生成的 SDK 目录中
  3. 运行时配置: 如果指定了 runtimeConfigPath,客户端初始化时会调用 createClientConfig() 函数
  4. 全局单例: 所有生成的 API 函数共享同一个客户端实例
  5. 配置合并: 每次请求时,会合并全局配置和请求级配置

与 @hey-api/client-axios 的区别

| 特性 | @hey-api/client-axios | @lark-apaas/hey-api-custom-client-axios | |------|----------------------|----------------------------------------| | 设计理念 | 灵活多实例 | 单例模式,统一管理 | | 源码控制 | NPM 包,不可修改 | 源码复制到项目,可修改 | | 配置方式 | 运行时动态配置 | 推荐使用 runtimeConfigPath | | 适用场景 | 需要多个独立客户端 | 单一应用,统一配置 | | 业务感知 | 业务需要关心认证等逻辑 | 业务无感知,只传业务参数 |

FAQ

Q: runtimeConfigPath 的路径是相对于哪里的?

A: 相对于生成的 SDK 输出目录。例如:

// 代码生成配置
createClient({
  output: { path: './src/generated/sdk' },
  plugins: [
    customClientAxiosPlugin({
      runtimeConfigPath: '../api/client-config.ts', // 相对于 ./src/generated/sdk
    }),
  ],
});

// 实际路径:./src/api/client-config.ts

Q: 如何在不同环境使用不同的配置?

A: 在 createClientConfig 中读取环境变量:

export const createClientConfig = () => {
  return {
    baseURL: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000',
    headers: {
      'X-Environment': process.env.NODE_ENV,
    },
  };
};

Q: 生成的代码可以修改吗?

A: 可以但不推荐。生成的代码在重新生成时会被覆盖。推荐通过 runtimeConfigPath 配置逻辑,或者使用 axios 拦截器。

Q: 如何处理多个不同的 API?

A: 推荐为每个 API 生成独立的 SDK,使用不同的配置文件:

// API 1
createClient({
  input: './openapi-1.json',
  output: { path: './src/generated/api1' },
  plugins: [
    customClientAxiosPlugin({
      runtimeConfigPath: '../config/api1-config.ts',
    }),
  ],
});

// API 2
createClient({
  input: './openapi-2.json',
  output: { path: './src/generated/api2' },
  plugins: [
    customClientAxiosPlugin({
      runtimeConfigPath: '../config/api2-config.ts',
    }),
  ],
});

Q: 支持哪些认证方式?

A: 支持所有 OpenAPI 规范定义的认证方式:

  • HTTP 认证(Bearer、Basic)
  • API Key(header、query、cookie)
  • OAuth 2.0
  • OpenID Connect

通过 auth 配置函数统一处理:

export const createClientConfig = () => {
  return {
    auth: async (auth) => {
      if (auth.type === 'http' && auth.scheme === 'bearer') {
        return await getBearerToken();
      }
      if (auth.type === 'apiKey') {
        return process.env.API_KEY;
      }
    },
  };
};

License

MIT