# Request

## Description

Request 模块提供围绕远程资源（remote resource）声明、请求执行、响应分类与失败补丁（patch output）生成的基础建模能力，用于把一类可长期复用的请求语义组织为稳定公共边界。

它关注的不是把宿主环境里的 `fetch` API 再包一层临时工具函数，也不是直接提供某个具体后端服务的 SDK，而是把“某个资源如何被声明”“一次请求成功时输出什么”“失败时如何补出可继续消费的结果”“请求生命周期中哪些事件值得稳定暴露”这些问题收束成一套可复用模型。

因此，这个模块更适合被理解为请求模型（request model）与资源模型（resource model）的组合边界，而不是一个只负责拼 URL、塞 headers、发 HTTP 的杂项请求工具箱。

## For Understanding

理解 Request 模块时，首先要把它与“底层传输实现”分开。这里真正要表达的重点，不是网络库本身，而是围绕某类远程资源建立稳定语义：输入如何约束、输出如何分类、成功与失败如何继续被上层消费，以及请求过程里哪些阶段值得被观察。

从这个角度看，这个模块主要由三层语义组成：

- 资源声明语义：通过 `Resource` 及其派生类型，把某一路径、方法、输入、成功输出、错误输出和补丁输出定义成一份可长期维护的资源契约。
- 执行适配语义：通过 `BaseFetch`、`BrowserFetch`、`NodejsFetch` 与 `generalFetch`，把不同运行时下的请求执行方式收束到统一读取接口，而不是把环境差异直接暴露给上层。
- 请求协调语义：通过 `BaseRequest`，把资源契约、请求选项、回调、事件派发与失败补丁生成组织到一个稳定的公共入口。

它适合放在这样的边界里：

- 你需要围绕某组远程资源建立长期可维护的输入输出契约，而不是在业务里直接散落请求实现。
- 你需要把“请求执行失败时如何回退为可消费结果”建模出来，例如补丁输出、占位输出、错误态输出或可恢复状态。
- 你需要把请求成功、失败、成功数据提取、错误数据提取等生命周期阶段稳定暴露给上层订阅者或组合逻辑。

同时也要守住几个边界：

- Request 模块表达的是请求与资源的公共语义，不负责替代完整的 API SDK 生成器、认证系统、重试策略中心或缓存一致性框架。
- `fetch` 子模块可以承载运行时适配，但它只是这个模块的执行基础，而不是整个模块存在的理由。
- 如果某项能力只是某个业务接口的临时参数拼接技巧、某个项目专属响应格式假设或某个服务端产品的耦合封装，通常不适合直接进入这个模块的顶层公共边界。

## For Using

当你希望把“远程资源”作为一份清楚、稳定、可组合的模型接入，而不是每次在业务代码里临时写一段 URL、请求方法、响应判断和错误兜底逻辑时，可以使用 Request 模块。

从使用角度看，这个模块大致可以分为三类能力：

- 资源声明能力：用于描述某类资源的基础 URL、路径、方法、输入结构、成功输出、错误输出与补丁输出，让请求契约先于具体实现被稳定定义。
- 请求执行能力：用于在浏览器或 Node.js 等运行时中发起请求，并以 `json`、`text`、`blob`、`arrayBuffer` 或 `stream` 等方式读取结果。
- 请求协调能力：用于把资源声明、执行器选择、请求前选项调整、生命周期回调以及事件订阅统一收束到一个请求入口，便于上层复用和继续组合。

更合理的接入方式通常是：先定义你的资源契约，再决定是否需要自定义 fetch 执行器，最后再通过继承 `BaseRequest` 或组合 `BaseRequest` 的方式把请求行为接入到上层流程中。这样做的重点不在于“少写几行发请求代码”，而在于把请求相关语义稳定下来，避免让业务代码直接依赖一次性实现细节。

默认情况下，可以直接使用 `generalFetch` 让模块在浏览器（browser）与 Node.js 运行时之间选择合适执行器；只有当你确实需要接入自定义传输层、测试替身（test double）或额外的请求前处理逻辑时，才更适合通过 `BaseRequestOptions.fetchFactory` 与 `modifyRequestOptions` 注入自己的适配方式。这里的重点仍然是复用稳定的请求语义，而不是把任意网络细节都暴露为公共入口。

当前 `BaseRequest` 的默认实现直接围绕 JSON 响应以及 `{ status, data }` 这类成功/错误分类结构工作：它会按 `status === "success"` 与 `status === "error"` 分流输出，并从 `data` 字段提取成功数据或错误数据。如果你的资源输出不满足这一约定，那么更合理的做法通常不是勉强沿用默认行为，而是通过继承 `BaseRequest` 并覆盖相关分类与提取逻辑，或直接在更外层定义更契合该资源语义的请求模型。

如果你的需求只是直接调用某个宿主环境里的原始 `fetch`，并没有资源契约、输出分类、补丁输出或生命周期事件这些长期语义诉求，那么直接使用宿主能力通常更合适，不一定需要进入这个模块边界。

## For Contributing

贡献 Request 模块时，优先判断新增内容是不是在澄清“远程资源与请求行为如何被建模为稳定公共语义”，而不是只是在补某个当前项目恰好需要的网络工具技巧。这个模块长期要维护的是资源契约、请求选项、响应分类、失败补丁与生命周期事件这些边界，而不是某个具体服务端接口的短期便利封装。

扩展时尤其要避免两类偏差：一类是把模块收缩成只剩 `fetch` 包装，导致资源语义与请求协调语义被忽略；另一类是把认证、缓存失效、重试编排、轮询、领域错误码映射、业务级状态机等所有与请求沾边的内容都混进来，最终让模块失去清楚边界。更稳妥的做法，是先判断新增能力表达的是资源契约、请求执行适配、还是请求生命周期协调；只有这些方向里的稳定语义，才适合成为该模块的长期公共能力。

其中 `resource.ts` 主要承载类型层契约，本身没有可单独执行的运行时代码；因此它通常不需要单独编写运行时单元测试，但它的语义必须通过 `BaseRequest` 以及具体 fetch 执行器的测试间接得到覆盖与约束。

如果后续要扩展 `fetch` 子模块，优先判断新增内容是在补充“标准化请求输入如何映射到宿主请求能力”这一稳定问题域，还是只是在引入某个具体框架或平台的偶发细节。前者可以作为请求执行适配的一部分继续演进，后者通常应留在更外层的集成代码里。

### JSDoc 注释格式要求

- 每个公开导出的目标（类型、函数、变量、类等）都应包含 JSDoc 注释，让人在不跳转实现的情况下就能理解用途。
- JSDoc 注释第一行应为清晰且简洁的描述，该描述优先使用中文（英文也可以）。
- 如果描述后还有其他内容，应在描述后加一个空行。
- 如果有示例，应使用 `@example` 标签，后接三重反引号代码块（不带语言标识）。
- 如果有示例，应包含多个场景，展示不同用法，尤其要覆盖常见组合方式或边界输入。
- 如果有示例，应使用注释格式说明每个场景：`// Expect: <result>`。
- 如果有示例，应将结果赋值给 `example1`、`example2` 之类的变量，以保持示例易读。
- 如果有示例，`// Expect: <result>` 应该位于 `example1`、`example2` 之前，以保持示例的逻辑清晰。
- 如果有示例，应优先使用确定性示例；避免断言精确的随机输出。
- 如果函数返回结构化字符串，应展示其预期格式特征。
- 如果有参考资料，应将 `@see` 放在 `@example` 代码块之后，并用一个空行分隔。

### 实现规范要求

- 不同程序元素之间使用一个空行分隔，保持结构清楚。这里的程序元素，通常指函数、类型、常量，以及直接服务于它们的辅助元素。
- 某程序元素独占的辅助元素与该程序元素本身视为一个整体，不要在它们之间添加空行。
- 程序元素的辅助元素应该放置在该程序元素的上方，以保持阅读时的逻辑顺序。
- 若辅助元素被多个程序元素共享，则应将其视为独立的程序元素，放在这些程序元素中第一个相关目标的上方，并与后续程序元素之间保留一个空行。
- 辅助元素也应该像其它程序元素一样，保持清晰的命名和适当的注释，以便在需要阅读实现细节时能够快速理解它们的作用和使用方式。
- 辅助元素的命名必须以前缀 `internal` 开头（或 `Internal`，大小写不敏感）。
- 辅助元素永远不要公开导出。
- 被模块内多个不同文件中的程序元素共享的辅助元素，应该放在一个单独的文件中，例如 `./src/request/internal.ts`。
- 模块内可以包含子模块。只有当某个子目录表达一个稳定、可单独理解、且可能被父模块重导出的子问题域时，才应将其视为子模块。
- 子模块包含多个文件时，应该为其单独创建子文件夹，并为其创建单独的 Barrel 文件；父模块的 Barrel 文件再重导出子模块的 Barrel 文件。
- 子模块不需要有自己的 `README.md`。
- 子模块可以有自己的 `internal.ts` 文件，多个子模块共享的辅助元素应该放在父模块的 `internal.ts` 文件中，单个子模块共享的辅助元素应该放在该子模块的 `internal.ts` 文件中。
- 对模块依赖关系的要求（通常是不循环依赖或不反向依赖）与对 DRY 的要求可能产生冲突。此时，若复用的代码数量不大，可以适当牺牲 DRY，复制粘贴并保留必要的注释说明；若复用的代码数量较大，则可以将其抽象到新的文件或子模块中，如 `common.ts`，并在需要的地方导入使用。
- 与请求相关的实现应优先围绕资源契约、请求选项规整、响应分类、补丁输出生成与生命周期事件组织，避免把业务专属认证、缓存策略或服务端接口细节直接提升为通用公共语义。

### 导出策略要求

- 保持内部辅助项和内部符号为私有，不要让外部接入依赖临时性的内部结构。
- 每个模块都应有一个用于重导出所有公共 API 的 Barrel 文件。
- Barrel 文件应命名为 `index.ts`，放在模块目录根部，并且所有公共 API 都应从该文件导出。
- 新增公共能力时，应优先检查它是否表达稳定、清楚且值得长期维护的请求或资源语义，而不是某段实现细节的便捷暴露；仅在确认需要长期对外承诺时再加入 Barrel 导出。

### 测试要求

- 若程序元素是函数，则只为该函数编写一个测试，如果该函数需要测试多个用例，应放在同一个测试中。
- 若程序元素是类，则至少要为该类的每一个方法编写一个测试，如果该方法需要测试多个用例，应放在同一个测试中。
- 若程序元素是类，除了为该类的每一个方法编写至少一个测试之外，还可以为该类编写任意多个测试，以覆盖该类的不同使用场景或边界情况。
- 若编写测试时需要用到辅助元素（Mock 或 Spy 等），可以在测试文件中直接定义这些辅助元素。若辅助元素较为简单，则可以直接放在每一个测试内部，优先保证每个测试的独立性，而不是追求极致 DRY；若辅助元素较为复杂或需要在多个测试中复用，则可以放在测试文件顶部，供该测试文件中的所有测试使用。
- 测试顺序应与源文件中被测试目标的原始顺序保持一致。
- 若该模块不需要测试，必须在说明文件中明确说明该模块不需要测试，并说明理由。一般来说，只有在该模块没有可执行的公共函数、只承载类型层表达，或其语义已被上层模块的测试完整覆盖且重复测试几乎不再带来额外价值时，才适合这样处理。
- 模块的单元测试文件目录是 `./tests/unit/request`，若模块包含子模块，则子模块的单元测试文件目录为 `./tests/unit/request/<sub-module-name>`。
- 对这个模块来说，测试应优先覆盖请求 URL 与 body 的组装、不同响应读取方式、运行时执行器分派、成功与错误输出分流、补丁输出生成、生命周期回调与事件派发，以及超时和外部取消信号的行为。
