import type { VextLogger } from "../types/app.js"; import type { VextInternalHooks } from "../types/hooks.js"; import type { VextRequest } from "../types/request.js"; import type { VextResponse } from "../types/response.js"; /** * fetch.ts — app.fetch 内置 HTTP 客户端 * * 封装 Node.js 20+ 内置 fetch,提供: * 1. 自动传播 requestId(从 requestContext AsyncLocalStorage 读取) * 2. 结构化日志记录(出站请求 method/url/status/duration) * 3. 超时控制(AbortController + setTimeout) * 4. 快捷方法(get/post/put/patch/delete) * 5. create() 工厂(baseURL + 默认配置) * 6. proxy() / proxy.() 请求代理能力(仅根 app.fetch 暴露) * * 挂载位置:app.fetch(与 app.logger / app.throw 同级) * * 与 requestContext 的关系: * requestId 中间件在请求进入时将 requestId 写入 requestContext store。 * app.fetch 在发送出站请求时从 requestContext.getStore() 读取 requestId, * 自动注入到出站请求的 x-request-id 头,实现跨服务请求追踪。 * * 配置项(config.fetch): * - timeout: 全局默认请求超时(毫秒,默认 10000) * - retry: 默认重试次数(仅幂等方法,默认 0) * - retryDelay: 默认重试间隔(毫秒,默认 1000) * - propagateHeaders: 除 x-request-id 外还需自动传播的请求头 * - proxy: 上游代理目标列表(仅根 app.fetch.proxy 使用) * * 超时配置优先级: * 单次请求 init.timeout > create() 的 options.timeout > config.fetch.timeout * * 当前版本未暴露 app.setFetch() 公共 API;自定义实现需在框架内部注入。 * * @module lib/fetch * @see IMPLEMENTATION-PLAN.md 任务 1.8b * @see 06d-fetch.md §1~§4 */ /** * 扩展的 fetch 初始化选项 * * 在标准 RequestInit 基础上增加 vext 特有的配置项: * - timeout: 请求超时(毫秒) * - retry: 重试次数(仅幂等方法) * - retryDelay: 重试间隔(毫秒)或指数退避函数 * - propagateRequestId: 是否自动注入 requestId 头 * - propagateHeaders: 额外需要传播的请求头 */ export interface VextFetchInit extends RequestInit { /** 请求超时(毫秒),默认使用全局配置 config.fetch.timeout */ timeout?: number; /** 重试次数(仅对幂等方法 GET/HEAD/OPTIONS/PUT/DELETE 生效),默认 0 */ retry?: number; /** 重试间隔(毫秒),默认 1000;支持函数形式实现指数退避 */ retryDelay?: number | ((attempt: number) => number); /** * 是否自动注入 requestId 头 * 默认 true;设为 false 可禁用(如调用不支持此头的外部 API) */ propagateRequestId?: boolean; /** * 自定义传播头(除 requestId 外还要传播的请求头) * 例如 ['x-trace-id', 'x-tenant-id'] */ propagateHeaders?: string[]; } /** * create() 工厂选项 */ export interface VextFetchClientOptions { /** 基础 URL,所有请求自动拼接 */ baseURL: string; /** 默认请求头 */ headers?: Record; /** 默认超时 */ timeout?: number; /** 默认重试 */ retry?: number; /** 默认重试间隔(毫秒)或指数退避函数 */ retryDelay?: number | ((attempt: number) => number); } /** * 可写入代理上游的请求头值。 */ type ProxyHeaderValue = string | number | boolean | null | undefined; type ProxyRequestBody = Exclude | Buffer | Uint8Array; /** * 代理动态注入 headers 的上下文。 */ export interface VextFetchProxyHeaderContext { req: VextRequest; target?: VextFetchProxyTargetConfig; options: VextFetchProxyOptions; } /** * 代理 headers 配置。 */ export type VextFetchProxyHeaders = Record | ((ctx: VextFetchProxyHeaderContext) => Record | Promise>); /** * config.fetch.proxy[] 的单个代理目标配置。 */ export interface VextFetchProxyTargetConfig { /** 目标名称,对应 app.fetch.proxy.() */ name: string; /** 上游基础地址,会与调用时 options.path 拼接 */ baseURL: string; /** 目标级固定 headers,优先级最低 */ headers?: Record; /** 从当前 req.headers 白名单透传的 header 名称 */ forwardHeaders?: string[]; /** 目标级动态注入 headers,覆盖 headers / forwardHeaders */ defaultInjectHeaders?: VextFetchProxyHeaders; /** 是否允许从当前请求透传原始 Authorization header */ allowAuthorizationForward?: boolean; /** 目标级超时(毫秒) */ timeout?: number; /** 目标级重试次数,表示额外尝试次数 */ retry?: number; /** 目标级重试间隔(毫秒)或指数退避函数 */ retryDelay?: number | ((attempt: number) => number); } /** * app.fetch.proxy 调用选项。 */ export interface VextFetchProxyOptions { /** 命名目标模式下必传:拼接到 target.baseURL 的路径 */ path?: string; /** 直接 URL 模式下必传:app.fetch.proxy(req, res, { url }) */ url?: string; /** 默认使用当前 req.method */ method?: string; /** 默认透传当前 req.query;同名 key 由 options.query 覆盖 */ query?: Record; /** 显式请求体;未传时非 GET/HEAD 会读取当前 req 原始 body Buffer */ body?: ProxyRequestBody; /** 读取当前 req 原始 body 的最大字节数 */ maxBodySize?: number; /** 调用级固定 headers,覆盖目标级配置和透传 headers */ headers?: Record; /** 调用级追加 header 透传白名单 */ forwardHeaders?: string[]; /** 调用级动态注入 headers,优先级最高 */ injectHeaders?: VextFetchProxyHeaders; /** 调用级是否允许透传原始 Authorization header */ allowAuthorizationForward?: boolean; /** 调用级超时(毫秒) */ timeout?: number; /** 调用级重试次数,表示额外尝试次数 */ retry?: number; /** 调用级重试间隔(毫秒)或指数退避函数 */ retryDelay?: number | ((attempt: number) => number); } /** * 命名目标代理处理函数。 */ export type VextFetchProxyHandler = (req: VextRequest, res: VextResponse, options: VextFetchProxyOptions) => Promise; /** * app.fetch.proxy 接口。 * * - app.fetch.proxy(req, res, { url }):直接 URL 代理 * - app.fetch.proxy.(req, res, { path }):config.fetch.proxy[] 目标代理 */ export type VextFetchProxy = { (req: VextRequest, res: VextResponse, options: VextFetchProxyOptions): Promise; } & Record; /** * app.fetch.create() 返回的纯出站 HTTP 客户端。 * * 子客户端不暴露 proxy,避免 app.fetch.create().proxy 带来额外心智负担。 */ export interface VextFetchClient { (input: string | URL | Request, init?: VextFetchInit): Promise; get(url: string, init?: VextFetchInit): Promise; post(url: string, body?: unknown, init?: VextFetchInit): Promise; put(url: string, body?: unknown, init?: VextFetchInit): Promise; patch(url: string, body?: unknown, init?: VextFetchInit): Promise; delete(url: string, init?: VextFetchInit): Promise; create(options: VextFetchClientOptions): VextFetchClient; } /** * 根 VextFetch 接口 * * 既是可调用的函数(与原生 fetch 签名一致), * 又挂载了快捷方法(get/post/put/patch/delete)、create() 工厂和 proxy。 */ export interface VextFetch extends VextFetchClient { proxy: VextFetchProxy; create(options: VextFetchClientOptions): VextFetchClient; } /** * fetch 模块配置(从 VextConfig 中提取) */ export interface VextFetchConfig { timeout?: number; retry?: number; retryDelay?: number | ((attempt: number) => number); propagateHeaders?: string[]; proxy?: VextFetchProxyTargetConfig[]; } type FetchConfig = VextFetchConfig; /** * createVextFetch — 创建 app.fetch 内置 HTTP 客户端 * * 在 bootstrap 阶段调用,将返回的 VextFetch 挂载到 app.fetch。 * * @param logger app.logger 实例(用于结构化日志) * @param fetchConfig config.fetch 配置 * @param requestIdHeader requestId 传播使用的头名称(默认 'x-request-id') * @returns VextFetch 实例 */ export declare function createVextFetch(logger: VextLogger, fetchConfig?: FetchConfig, requestIdHeader?: string, hooks?: VextInternalHooks): VextFetch; export {};