fix: enveloper 逃生舱 markSent + leizm/web send 404 + zod peerDep + 测试文档#193
Merged
Conversation
根因:LeizmWebAdapter.createLeiReply.send 调用 ctx.response.send(body), 但 @leizm/web 的 response 没有 send() 方法(仅有 end/json/file)。由于用了 可选链 leiRes.send?.(body),调用静默跳过 → 响应体未写 → 框架 final handler 返回 404。 影响面:在 @leizm/web adapter 下,任何用 ctx.reply.send(文本) 的路由 (register 或 registerTyped)都会 404。此前未暴露是因为现有测试与用例多走 reply.json(json 方法存在、工作正常)。 修复:send 映射到 response.end(body),并补 text/plain; charset=utf-8 content-type,与 json() 的 application/json 对齐。 复现测试(src/test/test-multilevel-route.ts):三框架对齐验证 forceGroup 下 「:param 多层路径 + 同前缀多路由」形态,覆盖 data/export/data:pk/refresh 四类路径(register 与 registerTyped 混合),确保三框架行为一致。
问题:registerTyped handler 若需返回非 JSON 响应(CSV/文件下载/流式)并经 raw
手动写完响应,handler return 后 wrappedHandler 无条件置 __returned=true,wrapWithEnvelope
见 __returned 即调 successEnveloper → reply.json(),可能覆盖已发送的响应或触发
「headers already sent」类错误(三框架行为不一,靠框架兜底是脆弱巧合)。
修复:显式标记法(向后兼容,不破坏现有 __returned 机制)
- Reply 接口新增公开方法 markSent():handler 手动写完响应后调用,告知 enveloper 跳过。
- wrapWithEnvelope 收紧决策:__returned && !reply.__sent 才包装。
- 三 adapter(express/koa/leizmweb)reply 实现统一维护 __sent 标记:
json/send/markSent 均置 __sent=true,三框架行为对齐。
测试(test-envelope.ts,三框架对齐):
- 单元:reply.__sent=true → enveloper 跳过(不调 successEnveloper/json)。
- 三框架集成:registerTyped + enveloper + raw 写 CSV + markSent → 响应体保持原始 CSV,
非 {success:true,data} 信封,content-type 为 text/csv。
非 breaking:markSent 为纯新增方法,不调它的现有 handler 行为不变。
- README 测试章节:显式说明 .input() 对 GET/DELETE 归 query、POST/PUT/PATCH 归 body, 补 DELETE 走 query schema 的示例与警示。 - README raw 逃生舱章节:新增 markSent() 小节,说明 enveloper 模式下手动写非 JSON 响应(CSV/下载/流)后须调 markSent 跳过自动包装,含三框架示例。 - agent.ts .input() JSDoc:补充方法归属约定说明。 - MIGRATION.md:新增 v3.2.1 章节,登记 markSent(新增)、leizmweb send 修复、 DELETE input 文档化三项。
问题:erest 此前把 zod@^4.0.5 列为 hard dependency。任何通过 link/本地路径依赖 erest 的项目会出现两份 zod 副本(erest 自带 + 宿主自带),TS 因 zod 内部版本 字面量标记(_zod.version.minor)不同而报类型不兼容(如 4.4.3 vs 4.0.5)。 修复:zod 从 dependencies 移到 peerDependencies(^4.0.0,required), 由宿主项目统一提供版本——这是 schema 校验库的标准做法。devDependencies 保留 zod@^4.4.3 供本仓库开发/测试用。 Breaking:宿主项目须显式声明 zod 依赖。MIGRATION.md v3.2.1 登记迁移说明。
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #193 +/- ##
=======================================
Coverage 92.47% 92.47%
=======================================
Files 28 28
Lines 2882 2883 +1
Branches 748 747 -1
=======================================
+ Hits 2665 2666 +1
Misses 215 215
Partials 2 2 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
背景
在 one-api 项目接入 erest(@leizm/web adapter + enveloper 模式)时发现三个问题,本 PR 一并修复。三框架(Express / Koa / @leizm/web)行为对齐,向后兼容。
问题与修复
1.
@erest/leizmwebreply.send映射错误导致纯文本响应 404根因:
createLeiReply.send调用ctx.response.send(body),但@leizm/web的 response 没有send方法(仅有end/json/file)。由于用了可选链leiRes.send?.(),调用静默跳过 → 响应体未写 → 框架 final handler 返回 404。影响面:
@leizm/webadapter 下任何用ctx.reply.send(文本)的路由此前都会 404。此前未暴露是因为现有测试/用例多走reply.json(json 方法存在、工作正常)。修复:
send映射到response.end(body)+ 补text/plain; charset=utf-8content-type。验证:新增
src/test/test-multilevel-route.ts,三框架对齐测试 forceGroup 下:param多层路径 + register/registerTyped 混合(复现用例:leizm/web 下.register()的/items/:id/export返回 404,Express/Koa 正常)。2. enveloper 与手动写非 JSON 响应冲突(核心)
根因:
registerTyped的 wrappedHandler 在 handler return 后无条件置ctx.__returned = true(api.ts),wrapWithEnvelope见__returned即调successEnveloper → reply.json(),不检查响应是否已手动发送。当 handler 需返回非 JSON(CSV/文件下载/流式)并经raw手动写完响应后,enveloper 会再包一层 json,可能覆盖已发送响应或触发「headers already sent」(三框架行为不一,靠框架兜底是脆弱巧合)。修复(显式标记法,向后兼容):
Reply接口新增公开方法markSent():handler 手动写完响应后调用,告知 enveloper 跳过自动包装。wrapWithEnvelope收紧决策:__returned && !reply.__sent才包装。__sent标记:json/send/markSent均置位,三框架行为对齐。markSent的存量 handler 行为不变(纯新增)。验证:
test-envelope.ts加单元测试(reply.__sent=true→ enveloper 跳过)+ 三框架集成测试(registerTyped + enveloper + raw 写 CSV + markSent → 响应体保持原始 CSV,非信封)。3. 测试引擎
.input()的方法归属未文档化agent.ts的.input()对 GET/DELETE 归 query、POST/PUT/PATCH 归 body(HTTP DELETE body 有争议,fetch/浏览器/代理普遍不传递)。这是合理的测试设计,但未文档化,用户注册 DELETE 路由用 body schema 时会踩坑(测试侧.input()归 query、schema 从 body 读 → MISSING_PARAM)。修复:纯文档化。README 测试章节 +
agent.tsJSDoc 显式说明,补 DELETE 走 query schema 的示例与警示。4.
zod从 dependencies 改为 peerDependencies(连带修复,Breaking)接入时发现:erest 此前把
zod@^4.0.5列为 hard dependency,任何 link/本地路径依赖 erest 的项目会出现两份 zod 副本(erest 自带 + 宿主自带),TS 因 zod 内部版本字面量标记(_zod.version.minor)不同而报类型不兼容(如 4.4.3 vs 4.0.5)。修复:zod 从 dependencies 移到 peerDependencies(
^4.0.0,required),由宿主统一提供版本——schema 校验库的标准做法。devDependencies 保留zod@^4.4.3供本仓库开发。Breaking:宿主项目须在自身
dependencies显式声明zod。adapter 子包不直接依赖 zod,无需改动。详见 MIGRATION.md v3.2.1。验证
pnpm run check(lint + format):0 errors ✅pnpm run build+build:packages:全绿 ✅pnpm run test:cov:317 测试全过,覆盖率达标(lines 92.33% / branches 82.69% > 80% 门槛 / functions 96.96% > 95% 门槛)✅pnpm --filter erest-example test:13 测试全过(端到端 dist 产物)✅test-raw.ts、test-register-typed.ts、test-group.ts、test-integration-{koa,leizmweb}.ts+ 新增用例 ✅文档
.input()方法归属说明 + DELETE 走 query 示例;raw 逃生舱章节新增markSent()小节。.input()JSDoc 补方法归属约定。提交结构
fix(leizmweb): reply.send 映射到 response.end(修复纯文本响应 404)feat(envelope): Reply.markSent() 逃生舱,手动响应不被 enveloper 二次包装docs: 文档化 DELETE input 归属 + markSent 逃生舱 + leizmweb send 修复说明refactor(deps): zod 改为 peerDependencies(消除 link 场景的 zod 副本冲突)