Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pnpm exec lint-staged
55 changes: 55 additions & 0 deletions MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,61 @@ npx erest-gen handler --from ./schemas/user.ts --group user --out ./handlers/use

---

## 3.1.0 — reply.raw 逃生舱 + 框架能力对齐

3.1.0 是 **3.0 的 minor 版本**,引入框架原生能力逃生舱与统一错误格式器,**非破坏性变更**
(现有 `new ERest()` 代码零改动,examples 测试全绿)。

### 1. `reply.raw` 逃生舱

`Reply` 接口新增 `readonly raw: Raw` 字段(`status()` 返回类型从 `Reply` 收紧为 `this`)。
registerTyped 的 handler 通过 `reply.raw` 访问框架原生对象,解决 setCookie / redirect /
stream / 文件下载等底层能力此前完全无法访问的问题。

`Context.reply` 仍保持 `Reply<unknown>`——before/middleware/hook 不暴露 raw,仅 handler 拿到
(避免 onError hook 与 re-throw 语义冲突的双重写入)。

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

`ERest<T, Raw>` / `API<T, Raw>` / `IGroup<T, Raw>` / `genSchema<T, Raw>` 全链路透传 Raw,
registerTyped handler 的 reply 类型随之用 `Reply<Raw>`。子包工厂在**构造时**锁定 Raw:

```diff
- const api = new ERest({ info, groups, forceGroup: true });
+ import { createERest } from "@erest/express"; // 或 @erest/koa / @erest/leizmweb
+ const api = createERest({ info, groups, forceGroup: true }); // reply.raw 零标注强类型
```

- `createERest()` 由三子包分别导出(`@erest/express|koa|leizmweb`),各自返回
`ERest<Middleware, ExpressRaw|KoaRaw|LeizmWebRaw>`,handler 内 `reply.raw` 自动推导。
- 裸 `new ERest()` 已标记 `@deprecated`(过渡期保留,Raw 默认 `unknown`,`reply.raw` 需断言)。
- 三框架 adapter 在 `createXxxReply` 时把闭包已有的原生对象挂到 `raw`(热路径零额外分配,
Koa/leizmweb 仅多挂引用,Express 多一个 `{req,res}` 字面量)。

### 3. 可选统一错误格式器 `defaultErrorFormatter`

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

```typescript
import { defaultErrorFormatter } from "erest";
// Express
app.use((err, _req, res, _next) => {
const { status, body } = defaultErrorFormatter(err); // { status, body: { error, code } }
res.status(status).json(body);
});
```

### 4. 能力对齐:仅 `raw` + 文档速查表

cookie/stream/redirect/headersSent 等原生能力差异不再逐个补 Reply 方法,统一通过 `reply.raw`
暴露,并在 README 提供「三框架原生能力速查表」标注各框架语义差异。错误处理(re-throw 语义)
已在 3.0.x 对齐,本次不动。

> raw 的头部/cookie 操作应在 `reply.json()`/`reply.send()` 之前调用(HTTP 头先于体发送)。

---

## 3.0.1 — 文档与发布修复

3.0.1 是 **3.0 的补丁版本**,无运行时 breaking change,修复发布缺陷与文档错误:
Expand Down
37 changes: 37 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,43 @@ api.api

无论哪种 API,**响应都通过 `reply` 写入**——不要用框架原生的 `res.json()` / `ctx.body =` / `ctx.response.json()`。

### 原生能力逃生舱:`reply.raw`

`reply` 只暴露 `json()/status()/send()` 三个框架无关方法。当需要框架特有能力(setCookie、redirect、流式响应、文件下载等)时,通过 `reply.raw` 访问框架原生对象——它是「逃生舱」,绕开标准抽象直达底层。

`reply.raw` 的类型由 **`ERest<T, Raw>` 的 Raw 泛型**驱动。用子包提供的 `createERest()` 工厂创建实例,会在构造时自动锁定 Raw,handler 内 `reply.raw` 自动强类型、**零标注**:

```typescript
import { createERest } from '@erest/express';
// 用 createERest 代替 new ERest:构造时锁定 Raw = ExpressRaw({ req, res })
const api = createERest({ info, groups, forceGroup: true });

api.api.post('/login').group('auth').title('登录').registerTyped(
{ body: LoginSchema },
(req, reply) => {
const user = authenticate(req.body);
// reply.raw 自动推导为 { req: Request; res: Response },无需断言
reply.raw.res.cookie('token', sign(user), { httpOnly: true });
reply.json({ ok: true });
},
);
```

> 直接 `new ERest()` 仍可用(过渡期保留),但 Raw 默认为 `unknown`,`reply.raw` 需手动断言。新代码推荐用子包 `createERest()`。

**框架原生能力速查表**(`reply.raw` 在三框架下的形态不同,签名/语义各自遵循原生约定):

| 能力 | Express (`reply.raw`) | Koa (`reply.raw`) | @leizm/web (`reply.raw`) |
|------|------|------|------|
| 设置 cookie | `.res.cookie(name, val, opts)` | `.cookies.set(name, val, opts)` | `.response.setHeader('Set-Cookie', ...)` |
| 读取 cookie | `.req.cookies`(需 cookie-parser) | `.cookies.get(name)` | `.request.cookies` |
| 重定向 | `.res.redirect(code, url)` | `.redirect(url)` | `.response.redirect(...)` |
| 流式响应 | `.res.write()` / `.res.end()` | `.body = stream` | `.response.send()` |
| 设置响应头 | `.res.setHeader(k, v)` | `.set(k, v)` | `.response.setHeader(k, v)` |
| 响应头已发送 | `.res.headersSent` | `.res.headersSent` | 查原生 |

> ⚠️ `raw` 的头部/cookie 操作应在 `reply.json()`/`reply.send()` **之前**调用(HTTP 头先于体发送)。`reply.raw` 是逃生舱,不参与框架无关复用——用了 raw 的 handler 即与具体框架耦合。

## 参数读取:`$params` 与分层访问器

adapter 的 checker 把校验后的参数注入到 erest 的标准化 `ctx` 上,handler 通过它们读取。
Expand Down
Loading
Loading