Skip to content

feat: reply.raw 原生能力逃生舱 + 全自动 Raw 泛型(v3.1.0) - #191

Merged
yourtion merged 14 commits into
mainfrom
feat/adapter-setcookie-error-align
Jun 29, 2026
Merged

feat: reply.raw 原生能力逃生舱 + 全自动 Raw 泛型(v3.1.0)#191
yourtion merged 14 commits into
mainfrom
feat/adapter-setcookie-error-align

Conversation

@yourtion

Copy link
Copy Markdown
Owner

概述

v3.0 的标准化抽象(Reply)只有 status/json/send 三个方法,导致 setCookie、redirect、流式响应等底层能力在 registerTyped handler 里完全无法访问。逐个补 Reply 方法不可行——三框架签名永远对不齐(Express res.cookie() / Koa ctx.cookies.set() / leizmweb 各不同)。

本 PR 引入框架泛型驱动的 reply.raw 逃生舱,让 handler 能拿到强类型的原生对象;同时审计三框架能力差异,提供可选的统一错误格式器。非破坏性(现有 new ERest() 代码零改动)。

设计文档:docs/superpowers/specs/2026-06-29-raw-escape-hatch-and-capability-alignment.md

主要变更

1. reply.raw 逃生舱

  • Reply<Raw> 接口新增 readonly raw: Raw 字段,handler 通过 reply.raw 访问原生对象
  • Context.reply 保持 Reply<unknown> —— before/middleware/hook 不暴露 raw(仅 handler 拿,避免 onError 与 re-throw 双重写入)

2. 全自动 Raw 泛型 + createERest() 工厂

  • ERest<T, Raw> / API<T, Raw> / IGroup<T, Raw> / genSchema<T, Raw> 全链路透传 Raw
  • 三子包各导出 createERest(),构造时锁定 Raw,handler 内 reply.raw 零标注强类型
import { createERest } from "@erest/express";
const api = createERest({ info, groups, forceGroup: true });
api.api.post("/login").group("auth").registerTyped({ body: S }, (req, reply) => {
  reply.raw.res.cookie("token", jwt, { httpOnly: true });  // 自动推导 { req, res }
});
  • new ERest() 标记 @deprecated(过渡期保留,Raw 默认 unknown

3. 可选 defaultErrorFormatter

工具函数(不改 adapter、不破坏 re-throw 对齐),用户在 app 级错误中间件调用统一三框架错误响应体。

4. 三框架能力速查表

cookie/stream/redirect 差异统一通过 raw + README 速查表暴露,不再逐个补 Reply 方法。

附带

  • 727b2c0:leizmweb 错误处理对齐 express/koa(re-throw 给 app 级错误中间件)
  • 各包 bump 至 3.1.0,MIGRATION.md 新增 3.1.0 changelog

测试

  • 核心库 276 测试全通过(新增:raw 三框架集成 3 + 类型推导 2 + 错误格式器 4)
  • examples 13 测试全通过(验证 new ERest() 向后兼容)
  • tsc --noEmit 全量通过
  • 最终 code review 已处理:测试无效错误码、断言注释、MIGRATION 文档

不在范围

  • ❌ 给 Reply 补 cookie()/redirect() 等方法(三框架签名永远对不齐)
  • ❌ adapter 内置错误格式化(破坏 re-throw 对齐)
  • 📝 测试文件未纳入类型检查(既有基础设施缺口,非本 PR 引入,留后续)

yourtion added 14 commits June 29, 2026 08:35
bindRoute 的 .catch 由吞错写死 {error} 改为 ctx.next(err) re-throw,与 express adapter
(next(err))/ koa adapter(throw err)+ README「app 级错误中间件」约定一致。此前 leizmweb 是
唯一特例:吞错使 app 级错误中间件失效、丢失 ERestError.code、无法自定义信封。

补 test-integration-leizmweb 错误 re-throw 集成测试(实跑验证 app 级中间件收到错并写信封)。
全量 280 测试通过(core 267 + examples 13)。
引入框架泛型驱动的 reply.raw 逃生舱,让 registerTyped handler 能拿到强类型
原生对象(setCookie/redirect/stream 等);审计三框架能力差异并提供可选统一
错误格式器。错误处理 re-throw 语义不动(commit 727b2c0 已对齐)。
8 个 TDD 任务:Reply 泛型(1) → API 透传(2) → ERest/IGroup 透传(3) →
错误格式器(4) → Express/Koa/leizmweb adapter(5/6/7) → 类型测试+文档+deprecated(8)
- Reply<Raw> 接口新增 readonly raw 字段,status 返回类型改为 this
- FrameworkAdapter<T, Raw> 加 Raw 维度(方法签名不变)
- Context.reply 标注 Reply<unknown>(before/middleware 不暴露 raw)

tsc 全量通过:三框架子包 createXxxReply 因结构兼容未报错,
Task 5/6/7 将在 createReply 时真正塞入 raw 值。
- 类级加 Raw 泛型维度(默认 unknown)
- registerTyped handler 的 reply 参数类型用 Reply<Raw>
- wrappedHandler 调用处 ctx.reply 断言为 Reply<Raw>(运行时即 adapter 注入的 reply,
  Context 静态类型为 Reply<unknown>,断言模式同 typedReq)
- 现有 5 个方法级 Zod 泛型不变,Raw 与之正交
- tsc 通过,267 测试全通过
- genSchema/IGroup/IApiInfo 加 Raw 维度(默认 unknown)
- ERest<T, Raw>,registAPI/defineAPI 字段类型返回 API<T, Raw>
- group() 返回 IGroup<T, Raw>,new API<T, Raw>
- API.define<T, Raw> 静态方法加 Raw 维度,defineAPI 调用处显式传 Raw
- 现有 new ERest() 行为不变(Raw 默认 unknown),tsc 通过,267 测试全通过
工具函数,用户在 app 级错误中间件调用,实现三框架统一错误响应体
({error, code} 结构,statusCode 取 ERestError.statusCode 或 status,默认 400)。
不改 adapter、不破坏 re-throw 对齐(commit 727b2c0)。
经 index.ts 的 export * 自动从核心包导出。
- createExpressReply 接收 req/res,reply.raw = { req, res }(热路径零分配,
  仅多挂闭包已有引用)
- 导出 ExpressRaw 类型别名;ExpressAdapter implements FrameworkAdapter<T, ExpressRaw>
- 新增 createERest() 工厂构造时锁定 Raw 泛型(handler reply.raw 零标注强类型)
- test-raw.ts 验证 Express handler 通过 reply.raw.res.cookie 设置 Set-Cookie
- http-req helper 扩展 headers 字段(验证响应头所需,纯增量不破坏现有测试)
- createKoaReply 返回 Reply<KoaRaw>,reply.raw = 原生 ctx(热路径零分配)
- 导出 KoaRaw 类型别名;KoaAdapter implements FrameworkAdapter<T, KoaRaw>
- 新增 createERest() 工厂构造时锁定 Raw 泛型
- test-raw.ts 验证 Koa handler 通过 reply.raw.cookies.set 设置 Set-Cookie
  (Koa cookies 库输出小写 httponly,断言大小写不敏感)
- createLeiReply 返回 Reply<LeizmWebRaw>,reply.raw = 原生 ctx(热路径零分配)
- 导出 LeizmWebRaw 类型别名(@leizm/web Context)
- LeizmWebAdapter implements FrameworkAdapter<T, LeizmWebRaw>
- 新增 createERest() 工厂构造时锁定 Raw 泛型
- test-raw.ts 验证 handler 通过 reply.raw.response.setHeader 设置响应头
  (leizmweb cookie API 随版本变化,用 setHeader 作稳定证明)
- test-builder-types.ts:新增 reply.raw 默认 unknown 类型断言
- test-raw-types.ts:验证 Express createERest 锁定 Raw,reply.raw 零标注强类型
- README:新增「原生能力逃生舱 reply.raw」章节 + 三框架能力速查表
- ERest 构造函数标记 @deprecated,引导到子包 createERest()
- 修复 leizmweb ctx 断言(Record→LeizmWebRaw 需经 unknown,子包独立 build 严格)
- npm test 全绿:核心 276 + examples 13
- test-error-formatter: AUTH_REQUIRED → PERMISSION_DENIED(前者不在 ERestErrorCode 联合类型,
  PERMISSION_DENIED 语义匹配 401)
- leizmweb: raw 双重断言补注释说明为何需经 unknown(结构不充分重叠)
- MIGRATION.md: 补充 reply.raw 逃生舱 + createERest 工厂说明(非破坏性,标记 deprecated)

最终 code review 遗留的 Critical(issue1) 与文档类 issue4/5 已处理。
issue2(测试文件未纳入类型检查)为既有基础设施缺口,非本次引入,留作后续。
本次 feature(reply.raw 逃生舱 + 全自动 Raw 泛型 + createERest 工厂 +
defaultErrorFormatter)为非破坏性 minor,bump 版本:
- erest 核心 3.0.1 → 3.1.0
- @erest/express / @erest/koa / @erest/leizmweb 3.0.1 → 3.1.0
- @erest/gen 0.1.0 → 3.1.0(统一 scope 版本,gen 本次未改动)
- MIGRATION.md 新增 3.1.0 changelog 节(替换之前 append 的临时段)
内部依赖均用 workspace: 协议,无需手改。npm test 全绿(276+13)。
修复 CI 失败的 2 个 oxlint error(本 PR 引入):
- test-integration-leizmweb.ts: ctx 参数未使用 → _ctx
- adapters/types.ts: FrameworkAdapter 的 Raw 泛型未使用 → JSDoc 说明 + 局部
  eslint-disable(Raw 为类型标记,供子类 implements 锁定,不在方法签名出现)

新增 pre-commit hook 防止 CI 再次失败:
- husky 9 + lint-staged 17
- .husky/pre-commit 跑 lint-staged
- lint-staged 对暂存的 *.{ts,js} 跑 oxfmt --write + oxlint --fix
- 已验证:oxlint error(如未使用参数)会阻止 commit
@codecov

codecov Bot commented Jun 29, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.64%. Comparing base (7728444) to head (72b869e).

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #191      +/-   ##
==========================================
+ Coverage   91.59%   91.64%   +0.04%     
==========================================
  Files          28       28              
  Lines        2821     2837      +16     
  Branches      719      722       +3     
==========================================
+ Hits         2584     2600      +16     
  Misses        235      235              
  Partials        2        2              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@yourtion
yourtion merged commit 1c1cc2e into main Jun 29, 2026
7 of 8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant