import type { VextApp } from "./app.js"; /** * ParsedFile — 已解析的上传文件 * * 由 multipart 解析插件解析后填充到 req.files。 * 框架本身不包含 multipart 解析逻辑,仅提供类型定义。 * 具体解析由用户通过插件引入 busboy / formidable 等库实现。 * * @example * // 在 multipart 解析插件中: * req.files = parsedFiles // ParsedFile[] * // 在路由 handler 中: * const avatar = req.files?.[0] * console.log(avatar.filename, avatar.size) */ export interface ParsedFile { /** 表单字段名( 中的 "avatar") */ fieldname: string; /** 原始文件名(客户端文件系统中的名称,可能含路径,需自行处理安全) */ filename: string; /** MIME 类型(如 'image/jpeg'、'application/pdf') */ mimetype: string; /** 文件二进制数据 */ buffer: Buffer; /** 文件大小(字节数) */ size: number; } /** * VextRequest — 框架统一请求对象接口 * * 由各 Adapter 负责将底层框架的请求对象转换为此接口。 * 路由 handler 和中间件通过此接口访问请求数据, * 与底层框架解耦,确保切换 Adapter 时用户代码无需改动。 */ export interface VextRequest { /** URL 查询参数(已解析为键值对) */ query: Record; /** * 请求体(由 body-parser 中间件负责填充) * 中间件执行前为 undefined,执行后为解析后的对象 */ body: unknown; /** 路径动态参数(如 /users/:id 中的 { id: '123' }) */ params: Record; /** 请求头(全部小写 key) */ headers: Record; /** HTTP 方法(大写,如 'GET'、'POST') */ method: string; /** 完整请求 URL */ url: string; /** 路径部分(不含 query string) */ path: string; /** * 当前请求匹配的路由模板(如 `/users/:id`) * * 由各 Adapter 在路由匹配完成后注入,值为路由注册时使用的模板字符串。 * 与 `path`(实际请求路径,如 `/users/abc-123`)不同,`route` 是低基数字段, * 适合作为 Prometheus / OTEL 指标的维度标签,可避免高基数问题。 * * 未匹配到路由时(如 404 场景),值为空字符串 `''`。 * * @example * // 注册:app.get('/users/:id', ...) * // 请求:GET /users/abc-123 * req.path // '/users/abc-123'(实际路径,高基数) * req.route // '/users/:id'(路由模板,低基数)✅ * * @example * // 404 场景(无匹配路由) * req.route // '' */ route: string; /** * 当前请求所属的 app 实例 * * 设计说明:路由 handler 通过 defineRoutes(app => ...) 闭包访问 app, * 不需要 req.app。但路由级中间件没有闭包,必须通过 req.app 访问 * throw/logger 等能力。选择挂在 req.app 而非 req.throw/req.logger, * 是为了保持 VextRequest 接口职责清晰——业务扩展字段(req.user 等) * 和框架核心能力(app)通过命名空间隔离。 */ app: VextApp; /** * 请求唯一标识 * * - 默认:从 x-request-id 请求头透传,不存在则框架自动生成 UUID v4 * - 可选:插件通过覆盖 config.requestId.generate 替换生成算法 * * 由 requestId 中间件负责填充,创建时为空字符串。 */ requestId: string; /** * 客户端 IP * * 当 config.trustProxy = true 时从 X-Forwarded-For 请求头读取第一个 IP, * 否则从底层 socket 的 remoteAddress 读取。 */ ip: string; /** * 请求协议 * * 当 config.trustProxy = true 时从 X-Forwarded-Proto 请求头读取, * 否则默认为 'http'。 */ protocol: "http" | "https"; /** * 注册请求关闭钩子(连接断开时触发) * * 主要用于 SSE / WebSocket:客户端断开时清理资源。 * 内存安全:框架在 hooks 执行完毕后自动清空 hooks 数组, * 无需手动移除,不会因闭包引用造成内存泄漏。 * * @param handler 关闭时执行的回调 * @example req.onClose(() => sseStream.close()) */ onClose(handler: () => void): void; /** * 获取经过 validate 校验并类型转换后的数据 * * 必须在 options.validate 配置了对应位置后调用, * 未配置对应位置时返回 undefined。 * schema-dsl 会自动做类型转换(如 query 中的数字字符串 → number)。 * * location 与数据源映射: * 'query' → req.query (URL 查询参数) * 'body' → req.body (请求体) * 'param' → req.params (路径动态参数,如 /:id) * 'header' → req.headers (请求头) * * 注意:location 使用单数 'param'(与 validate 配置的 key 一致), * 但底层数据源是复数 req.params。框架内部已正确映射。 * * @param location 校验数据位置 * @returns 校验后的数据对象;默认返回 Record * * @example * // 直接使用(推荐,validator 已保证数据正确性) * const { page, limit } = req.valid('query') * * @example * // 加泛型获取更精确的 IDE 提示(可选) * const param = req.valid<{ id: string }>('param') * param.id // IDE 知道是 string */ valid>(location: "query" | "body" | "param" | "header"): T; /** * 翻译函数(i18n 插件注入) * @example req.t('user.not_found') → '用户不存在' * @example req.t('welcome', { name: 'Alice' }) → '欢迎, Alice' */ t?: (key: string, params?: Record) => string; /** * 已解析的上传文件列表(由 multipart 解析插件填充) * * 需在路由或全局注册 multipart 解析插件后方可访问。 * 插件解析 multipart/form-data 请求体,将文件写入此字段。 * 非文件上传请求此字段为 undefined。 * * @example * app.post('/upload', {}, async (req, res) => { * const file = req.files?.[0] * if (!file) return res.json({ error: '未收到文件' }) * // 处理 file.buffer ... * }) */ files?: ParsedFile[]; /** * 获取原始请求体字符串(框架内部使用) * * 供 body-parser 中间件读取,不建议用户代码直接调用。 * 多次调用返回相同字符串(带缓存,流只消费一次)。 * GET / HEAD / OPTIONS 等无 body 方法返回空字符串。 * * @internal 由各 adapter 注入实现 */ _getRawBody(maxBytes?: number): Promise; /** * 获取原始请求体 Buffer(插件使用) * * 返回未经任何编码转换的原始字节,是实现 multipart 解析的前提。 * 多次调用返回相同 Buffer 实例(带缓存,流只消费一次)。 * GET / HEAD / OPTIONS 等无 body 方法返回空 Buffer(length = 0)。 * * @example * // 在 multipart 解析插件中: * const buffer = await req._getRawBodyBuffer() * // 将 buffer 传递给 busboy / formidable 解析 * * @internal 由各 adapter 注入实现 */ _getRawBodyBuffer(maxBytes?: number): Promise; /** * 允许中间件或插件在 req 上挂载自定义字段。 * * 通过 declare module 'vextjs' 扩展 VextRequest 接口可获得类型提示: * @example * declare module 'vextjs' { * interface VextRequest { * user?: { id: string; role: string } * } * } */ [key: string]: unknown; }