complex-request
Advanced tools
+198
| # Complex Request | ||
| 这是一个功能强大且高度可扩展的 TypeScript 请求库,旨在通过分层、可组合的规则引擎,优雅地处理复杂的 Web 请求场景。 | ||
| ## 核心特性 | ||
| - **分层架构**: | ||
| - **`BaseRequest`**: 抽象请求基类,负责驱动整个请求生命周期,包括 Token 处理、请求重试、错误捕获等。 | ||
| - **`Rule`**: 可定制的规则引擎,将所有与具体业务相关的逻辑(如响应解析、登录/刷新流程)集中管理。 | ||
| - **`Token`**: 封装单个 Token 的所有行为,包括值的获取、存储、附加到请求和生命周期管理。 | ||
| - **强大的 Token 管理**: | ||
| - 支持多个具名 `Token` 和一个 `refreshToken`。 | ||
| - 支持从内存、`localStorage` 和 `sessionStorage` 中自动获取和缓存 Token。 | ||
| - 可配置 Token 的有效期、存储位置(`header`, `body`, `params`)和附加键名。 | ||
| - 自动化的登录和刷新流程,当 Token 失效时,可自动触发 `login` 或 `refresh` 操作,并无缝地重试原始请求。 | ||
| - **高度可定制**: | ||
| - **响应解析**: 可通过 `parse` 函数,将任何格式的服务器响应,适配为统一的 `{ status, data, ... }` 格式。 | ||
| - **请求格式化**: 可通过 `format` 函数,在请求发送前,对请求参数进行最后一次修改。 | ||
| - **底层实现无关**: `BaseRequest` 是一个抽象类,您可以继承它,并使用任何底层的请求库(如 `axios`, `fetch`)来实现 `$request` 方法。 | ||
| - **数据格式转换**: | ||
| - 支持在 `json` 和 `form-data` 之间自动转换请求数据。 | ||
| ## 安装 | ||
| ```bash | ||
| npm install complex-request | ||
| ``` | ||
| ## 快速开始 | ||
| 下面是一个使用 `fetch` 作为底层实现的简单示例: | ||
| ```typescript | ||
| import { BaseRequest, Rule, Token } from 'complex-request'; | ||
| // 1. 创建一个继承自 BaseRequest 的具体请求类 | ||
| // 我们在这里为泛型 R (原始响应) 和 L (本地配置) 提供了具体的类型 | ||
| class MyRequest extends BaseRequest<any, { signal?: AbortSignal }> { | ||
| $request(requestConfig) { | ||
| // 使用 fetch 实现底层的请求逻辑 | ||
| const { url, method, headers, data, params, local } = requestConfig; | ||
| const finalUrl = new URL(url); | ||
| Object.keys(params).forEach(key => finalUrl.searchParams.append(key, params[key])); | ||
| return fetch(finalUrl.toString(), { | ||
| method, | ||
| headers, | ||
| body: data, | ||
| signal: local?.signal // 传递 AbortSignal 以便取消请求 | ||
| }).then(res => res.json()); | ||
| } | ||
| $parseError(error) { | ||
| // 解析 fetch 抛出的错误 | ||
| return { type: 'internal', msg: error.message, data: error }; | ||
| } | ||
| } | ||
| // 2. 定义一套规则 | ||
| const myRule = new Rule({ | ||
| prop: 'api', | ||
| token: { | ||
| data: { | ||
| // 定义一个名为 'accessToken' 的 Token | ||
| accessToken: { | ||
| location: 'header', // 存储在 Header 中 | ||
| require: true // 这是一个必需的 Token | ||
| } | ||
| } | ||
| }, | ||
| parse: (response) => { | ||
| // 解析服务器响应 | ||
| if (response.code === 200) { | ||
| return { status: 'success', data: response.data }; | ||
| } else if (response.code === 401) { | ||
| return { status: 'login', data: null, msg: '请先登录' }; | ||
| } else { | ||
| return { status: 'fail', data: null, msg: response.message }; | ||
| } | ||
| }, | ||
| login: () => { | ||
| // 定义登录逻辑 | ||
| return new Promise(resolve => { | ||
| // ... 执行登录操作,例如弹出一个登录框 | ||
| // ... 登录成功后,使用 setToken 设置新的 Token | ||
| myRequest.setToken('accessToken', 'new-token-from-server'); | ||
| resolve(); | ||
| }); | ||
| } | ||
| }); | ||
| // 3. 实例化请求对象 | ||
| const myRequest = new MyRequest({ | ||
| baseUrl: 'https://api.example.com', | ||
| rule: myRule | ||
| }); | ||
| // 4. 发送请求 | ||
| // 4. 发送请求,并传递一个本地配置 | ||
| const controller = new AbortController(); | ||
| myRequest.get({ | ||
| url: '/user/profile', | ||
| local: { signal: controller.signal } | ||
| }) | ||
| .then(response => { | ||
| console.log('用户信息:', response.data); | ||
| }) | ||
| .catch(error => { | ||
| console.error('请求失败:', error); | ||
| }); | ||
| // 可以在需要时取消请求 | ||
| // controller.abort(); | ||
| ``` | ||
| ## 高级用法:泛型类型 | ||
| `complex-request` 大量使用泛型来提供强大的类型安全。理解 `R` 和 `L` 这两个核心泛型,能帮助您更好地利用这个库。 | ||
| ### `R` - 原始响应 (`Raw Response`) | ||
| `R` 代表的是底层请求工具(如 `fetch`, `axios`)直接返回的、**未经 `rule.parse` 解析**的原始响应数据的类型。 | ||
| 在 `Rule` 的 `parse` 函数中,`response` 参数的类型就是 `R`。 | ||
| ```typescript | ||
| // 假设服务器返回的数据结构是 { code: number, data: T, message: string } | ||
| type ServerResponse<T> = { | ||
| code: number; | ||
| data: T; | ||
| message: string; | ||
| } | ||
| // 在定义 Rule 时,我们可以将 R 指定为 ServerResponse<any> | ||
| const myRule = new Rule<ServerResponse<any>>({ | ||
| // ... | ||
| parse: (response) => { // 在这里,`response` 的类型就是 ServerResponse<any> | ||
| if (response.code === 200) { | ||
| return { status: 'success', data: response.data }; | ||
| } | ||
| // ... | ||
| } | ||
| }); | ||
| ``` | ||
| ### `L` - 本地配置 (`Local Config`) | ||
| `L` 代表的是一个可选的、用于传递给底层请求工具的、**特定于实现的**配置对象的类型。 | ||
| 这非常有用,因为不同的底层请求库(`fetch`, `axios`)有它们自己独特的配置选项(例如,`axios` 的 `cancelToken`,`fetch` 的 `signal`)。`L` 允许您以类型安全的方式,将这些特定于实现的配置,一路传递到您自己实现的 `$request` 方法中。 | ||
| 在上面的“快速开始”示例中,我们通过 `L` 泛型,为 `fetch` 实现了一个请求取消的功能。 | ||
| ### `parse` 函数:请求的“交通警察” | ||
| `parse` 函数是 `complex-request` 库的**核心**。它就像一个交通警察,负责检查每一个从服务器返回的原始响应 (`R`),并根据您定义的业务逻辑,告诉 `BaseRequest` 下一步应该做什么。 | ||
| `parse` 函数必须返回一个 `responseType` 对象,其 `status` 字段决定了整个请求的走向: | ||
| ```typescript | ||
| interface responseType<D = any> { | ||
| status: 'success' | 'fail' | 'login' | 'refresh' | ||
| data: D | ||
| code?: number | string | ||
| msg?: string | ||
| } | ||
| ``` | ||
| 下面是 `BaseRequest` 对每种 `status` 的处理方式: | ||
| - **`'success'`**: **请求成功** | ||
| - **含义**: 服务器成功处理了请求,且响应数据是有效的。 | ||
| - **`BaseRequest` 的行为**: `request` 方法返回的 Promise 将被 **resolve**,并将 `data` 字段作为结果传递下去。 | ||
| - **`'fail'`**: **请求失败** | ||
| - **含义**: 服务器返回了一个业务逻辑上的错误(例如,表单验证失败、权限不足等)。 | ||
| - **`BaseRequest` 的行为**: `request` 方法返回的 Promise 将被 **reject**,并将整个 `responseType` 对象作为错误信息传递下去。 | ||
| - **`'login'`**: **需要登录** | ||
| - **含义**: 用户的会话已完全失效,必须重新登录。 | ||
| - **`BaseRequest` 的行为**: | ||
| 1. 自动触发您在 `Rule` 中定义的 `login` 函数。 | ||
| 2. 在 `login` 函数成功执行后(即其返回的 Promise 被 resolve),`BaseRequest` 会**自动重新发送**之前失败的那个请求。 | ||
| 3. 整个过程对最终的调用者是**完全透明的**。 | ||
| - **`'refresh'`**: **需要刷新 Token** | ||
| - **含义**: 用户的访问令牌(Access Token)已过期,但刷新令牌(Refresh Token)仍然有效。 | ||
| - **`BaseRequest` 的行为**: | ||
| 1. 自动触发您在 `Rule` 中定义的 `refresh` 函数。 | ||
| 2. 在 `refresh` 函数成功执行后,`BaseRequest` 会**自动重新发送**之前失败的那个请求。 | ||
| 3. 如果 `refresh` 本身也失败了,或者刷新后重试的请求依然返回 `'refresh'`,`BaseRequest` 会自动将流程降级为 `'login'`。 | ||
| 通过精心设计您的 `parse` 函数,您可以以一种非常优雅和集中的方式,实现极其复杂的认证和重试逻辑。 | ||
| ## 依赖 | ||
| - [complex-utils](https://github.com/MarAngle/complex-utils): 提供核心的工具函数和基类。 | ||
| - [complex-plugin](https://github.com/MarAngle/complex-plugin): 提供 `notice` 通知插件。 |
| import { describe, it, expect } from 'vitest'; | ||
| import Token from './Token'; | ||
| describe('Token', () => { | ||
| it('should be instantiated correctly with default values', () => { | ||
| const token = new Token({}, 'testToken', 'testRule'); | ||
| expect(token).toBeInstanceOf(Token); | ||
| expect(token.location).toBe('body'); | ||
| expect(token.require).toBeUndefined(); | ||
| }); | ||
| it('should store and retrieve a value from memory', () => { | ||
| const token = new Token({ value: 'my-secret-token' }, 'testToken', 'testRule'); | ||
| expect(token.getValue()).toBe('my-secret-token'); | ||
| }); | ||
| it('should respect the require flag', () => { | ||
| const requiredToken = new Token({ require: true }, 'requiredToken', 'testRule'); | ||
| const optionalToken = new Token({ require: false }, 'optionalToken', 'testRule'); | ||
| expect(requiredToken.$checkValue(undefined)).toBe(false); | ||
| expect(requiredToken.$checkValue(null)).toBe(false); | ||
| expect(requiredToken.$checkValue('some-value')).toBe(true); | ||
| expect(optionalToken.$checkValue(undefined)).toBe(true); | ||
| }); | ||
| it('should append value to the correct location', () => { | ||
| const headerToken = new Token({ value: 'header-val', location: 'header' }, 'hToken', 'testRule'); | ||
| const bodyToken = new Token({ value: 'body-val', location: 'body' }, 'bToken', 'testRule'); | ||
| const paramsToken = new Token({ value: 'params-val', location: 'params' }, 'pToken', 'testRule'); | ||
| const config = { | ||
| headers: {}, | ||
| data: {}, | ||
| params: {} | ||
| }; | ||
| headerToken.$appendValue(config as any, 'Authorization'); | ||
| bodyToken.$appendValue(config as any, 'token'); | ||
| paramsToken.$appendValue(config as any, 'api_key'); | ||
| expect(config.headers).toEqual({ 'Authorization': 'header-val' }); | ||
| expect(config.data).toEqual({ 'token': 'body-val' }); | ||
| expect(config.params).toEqual({ 'api_key': 'params-val' }); | ||
| }); | ||
| }); |
| import { defineConfig } from 'vitest/config' | ||
| export default defineConfig({ | ||
| test: { | ||
| globals: true, | ||
| environment: 'jsdom', | ||
| include: ['src/**/*.test.ts'], | ||
| }, | ||
| }) |
+45
-47
@@ -1,87 +0,85 @@ | ||
| ### ToDo | ||
| - 刷新Token实现 | ||
| ### 1.1.1 | ||
| - refactor(build): 移除项目的所有构建配置,回归到纯源码模式。 | ||
| - feat(test): 集成 `Vitest` 单元测试框架。 | ||
| - docs(readme): 创建详细的 `README.md` 文件。 | ||
| - chore(history): 全面优化和重构 `history.md` 的格式。 | ||
| ### 1.0.1 | ||
| - feat:修改模块加载逻辑为ES2020 | ||
| - feat(module): 修改模块加载逻辑为 ES2020。 | ||
| ### 0.6.2 | ||
| - 基于AI优化代码 | ||
| - refactor(code): 基于 AI 进行全局代码优化。 | ||
| ### 0.6.1 | ||
| - 稳定版 | ||
| - chore: 稳定版升级。 | ||
| ### 0.5.10 | ||
| - complex-plugin版本升级,逻辑优化 | ||
| - chore(deps): 升级 `complex-plugin` 依赖并优化逻辑。 | ||
| ### 0.5.8/9 | ||
| - complex-plugin版本升级 | ||
| ### 0.5.7 | ||
| - refactor(request)!: **[非兼容性更新]** `failNotice` 重命名为 `fail`,`failNotice.local` 重命名为 `fail.intercept`。 | ||
| - refactor(request): 优化 `$parseError` 返回的 `type` 类型。 | ||
| - refactor(request): 将 `config` 配置项迁移到类的静态数据中。 | ||
| ### 0.5.6/7 | ||
| - failNotice => fail | ||
| - failNotice.local => fail.intercept | ||
| - $parseError返回的type类型优化 | ||
| - config配置项迁移到类静态数据中 | ||
| ### 0.5.5 | ||
| - 优化类型传递 | ||
| - refactor(types): 优化泛型类型传递。 | ||
| ### 0.5.4 | ||
| - refreshToken的数据保存 | ||
| - feat(token): 实现 `refreshToken` 的数据持久化保存。 | ||
| ### 0.5.2/3 | ||
| - rule.login添加trigger参数,确认登录触发来源 | ||
| - _request替换isRefresh为trigger参数,实现判断请求是否来自于refresh/login成功后的回调 | ||
| - responseType.status添加refresh状态 | ||
| - responseType.status === 'refresh'时认为需要刷新token,调用refresh进行token刷新,刷新成功后重新请求再次触发refresh则直接调用login进行登录 | ||
| - responseType.status === 'login'时直接调用login进行登录 | ||
| ### 0.5.3 | ||
| - feat(request): 实现完整的 `login` -> `refresh` -> `retry` 流程。 | ||
| - `rule.login` 添加 `trigger` 参数,用于判断登录的触发来源。 | ||
| - `responseType.status` 添加 `refresh` 状态,用于触发 `rule.refresh`。 | ||
| - 当 `refresh` 失败或再次触发 `refresh` 时,自动降级为 `login`。 | ||
| ### 0.5.1 | ||
| - complex-plugin版本升级 | ||
| - chore(deps): 升级 `complex-plugin` 依赖。 | ||
| ### 0.4.4/5/6/7 | ||
| - complex-plugin版本升级 | ||
| ### 0.4.7 | ||
| - chore(deps): 升级 `complex-plugin` 依赖。 | ||
| ### 0.4.1/2/3 | ||
| - BaseRequest的rule简化为单选,简化判断逻辑,需要多个rule可生成多个BaseRequest实例单独实现 | ||
| ### 0.4.3 | ||
| - refactor(rule)!: **[非兼容性更新]** `BaseRequest` 的 `rule` 简化为单选,以简化逻辑。 | ||
| ### 0.3.6 | ||
| - 优化undefined校验 | ||
| - refactor(code): 优化全局的 `undefined` 校验逻辑。 | ||
| ### 0.3.5 | ||
| - responseFormat=>responseParse | ||
| - Rule的原format函数更改为parse函数,添加新format函数在请求前实现规则的格式化 | ||
| - refactor(rule)!: **[非兼容性更新]** `responseFormat` 重命名为 `responseParse`。 | ||
| - refactor(rule)!: **[非兼容性更新]** `Rule` 的 `format` 函数重命名为 `parse`,并新增 `format` 函数用于请求前的格式化。 | ||
| ### 0.3.0/1/2/3/4 | ||
| - 依赖大版本升级 | ||
| ### 0.3.4 | ||
| - chore(deps): 依赖大版本升级。 | ||
| ### 0.2.1 | ||
| - local传值类型实现泛型 | ||
| - refactor(types): 为 `local` 参数实现泛型类型。 | ||
| ### 0.2.0 | ||
| - 优化函数命名规则:外部函数以字母开头,内部函数以$开头,私有函数以_开头 | ||
| - refactor(code): 统一函数命名规则 (外部函数、内部函数、私有函数)。 | ||
| ### 0.1.10 | ||
| - 升级依赖,适配formatConfig | ||
| - chore(deps): 升级依赖,适配 `formatConfig`。 | ||
| ### 0.1.9 | ||
| - BUG: 修正requestConfig.data被重置为空数据的BUG | ||
| - 类型: 扩展BaseRequest为泛型类,将返回值基础类型作为泛型传递,可以在后续扩展类中定义最终的返回值基础类型 | ||
| - fix(request): 修正 `requestConfig.data` 被重置为空对象的 BUG。 | ||
| - feat(types): 扩展 `BaseRequest` 为泛型类,允许定义最终的响应类型。 | ||
| ### 0.1.8 | ||
| - 非兼容性更新: Request => BaseRequest | ||
| - 修正post/get/form/json未正确请求的BUG | ||
| - 升级依赖 | ||
| - refactor(request)!: **[非兼容性更新]** `Request` 类重命名为 `BaseRequest`。 | ||
| - fix(request): 修正 `post/get/form/json` 未正确设置 `method` 的 BUG。 | ||
| - chore(deps): 升级依赖。 | ||
| ### 0.1.7 | ||
| - 修正错误的依赖 | ||
| - fix(deps): 修正错误的依赖项。 | ||
| ### 0.1.3/4/5/6 | ||
| - 优化请求参数和失败错误参数 | ||
| - 扩展失败信息提示 | ||
| ### 0.1.6 | ||
| - refactor(request): 优化请求参数和失败错误处理。 | ||
| - feat(request): 扩展失败信息提示。 | ||
| ### 0.1.2 | ||
| - 扩展HTTP请求错误码 | ||
| - 优化函数和类型 | ||
| - feat(request): 扩展支持的 HTTP 状态码错误信息。 | ||
| - refactor(code): 优化函数和类型定义。 | ||
| ### 0.1.1 | ||
| - 实现基础功能 | ||
| - feat: 项目初始化,实现基础的请求功能。 |
+8
-4
| { | ||
| "name": "complex-request", | ||
| "version": "1.0.1", | ||
| "version": "1.1.1", | ||
| "description": "a complex request", | ||
@@ -12,9 +12,13 @@ "type": "module", | ||
| "dependencies": { | ||
| "complex-plugin": "4.9.1 - 4.9.99" | ||
| "complex-plugin": "4.10.1 - 4.10.99" | ||
| }, | ||
| "devDependencies": { | ||
| "typescript": "^5.2.2" | ||
| "jsdom": "^27.1.0", | ||
| "typescript": "^5.2.2", | ||
| "vitest": "^4.0.7" | ||
| }, | ||
| "scripts": { | ||
| "test": "echo \"Error: no test specified\" && exit 1" | ||
| "check": "tsc --noEmit", | ||
| "test": "vitest", | ||
| "coverage": "vitest run --coverage" | ||
| }, | ||
@@ -21,0 +25,0 @@ "keywords": [ |
No README
QualityPackage does not have a README. This may indicate a failed publish or a low quality package.
No tests
QualityPackage does not have any tests. This is a strong signal of a poorly maintained or low quality package.
49562
29.47%10
42.86%809
6.03%1
-50%1
-50%199
Infinity%3
200%+ Added
+ Added
- Removed
- Removed