@kevisual/router 0.2.11 → 0.2.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -60,10 +60,10 @@ import { App } from '@kevisual/router/browser';
60
60
 
61
61
  | 方法 | 参数 | 说明 |
62
62
  | ----------------------------------- | ----------------------------------------- | -------------------------------------------- |
63
- | `ctx.call(msg, ctx?)` | `{ path, key?, payload?, ... } \| { rid }` | 调用其他路由,返回完整 context |
64
- | `ctx.run(msg, ctx?)` | `{ path, key?, payload? }` | 调用其他路由,返回 `{ code, data, message }` |
63
+ | `ctx.run(msg, ctx?)` | `{ path, key?, payload?, ... } \| { rid }` | 调用其他路由,返回 `{ code, data, message }` |
65
64
  | `ctx.forward(res)` | `{ code, data?, message? }` | 设置响应结果 |
66
65
  | `ctx.throw(code?, message?, tips?)` | - | 抛出自定义错误 |
66
+ | `ctx.safeParseAsync(data?, opts?)` | `{ schema?, zodOptions?, stop? }` | 校验路由参数,失败时返回 issues 或自动抛出 422 错误 |
67
67
 
68
68
  ## 完整示例
69
69
 
@@ -162,18 +162,26 @@ app
162
162
 
163
163
  ```ts
164
164
  import { App } from '@kevisual/router';
165
+ import { z } from 'zod';
165
166
  const app = new App();
166
167
 
167
168
  app
168
- .router({
169
+ .route({
169
170
  path: 'dog',
170
171
  key: 'info',
171
172
  description: '获取小狗的信息',
172
173
  metadata: {
174
+ // args: 定义请求参数的 zod schema,用于参数校验和类型推断
173
175
  args: {
174
176
  name: z.string().describe('小狗的姓名'),
175
177
  age: z.number().describe('小狗的年龄'),
176
178
  },
179
+ // returns: 定义响应数据的 zod schema,用于返回结果类型推断
180
+ returns: {
181
+ content: z.string().describe('小狗的信息描述'),
182
+ },
183
+ // check: true 会在路由执行前自动校验 args,失败时返回 422
184
+ check: true,
177
185
  },
178
186
  })
179
187
  .define(async (ctx) => {
@@ -185,20 +193,169 @@ app
185
193
  .addTo(app);
186
194
  ```
187
195
 
188
- ## 注意事项
196
+ ### metadata 字段说明
189
197
 
190
- 1. **path key 的组合是路由的唯一标识**,同一个 path+key 只能添加一个路由,后添加的会覆盖之前的。
198
+ | 字段 | 类型 | 说明 |
199
+ |------|------|------|
200
+ | `args` | `Record<string, z.ZodTypeAny> \| z.ZodObject` | 请求参数的 zod schema,用于参数校验和类型推断 |
201
+ | `returns` | `Record<string, z.ZodTypeAny> \| z.ZodObject` | 响应数据的 zod schema,用于返回值类型推断 |
202
+ | `check` | `boolean` | 设为 `true` 时,路由执行前自动校验 `args`,校验失败返回 422 |
191
203
 
192
- 2. `ctx.run` 返回 `{ code, data, message }` 格式,data 即 body
204
+ ### metadata.args 参数说明
193
205
 
194
- 3. **ctx.throw 会自动结束执行**,抛出自定义错误。
206
+ `args` 是一个 zod schema 对象,用于定义路由的请求参数结构。每个字段的 key 对应请求时传入的参数名,value 为 zod 的类型定义。
195
207
 
196
- 4. **payload 会自动合并到 query**,调用 `ctx.run({ path, key, payload })` 时,payload 会合并到 query。
208
+ | 参数名 | 类型 | 必填 | 说明 |
209
+ |--------|------|------|------|
210
+ | `name` | `z.string()` | 是 | 小狗的姓名,字符串类型 |
211
+ | `age` | `z.number()` | 是 | 小狗的年龄,数字类型 |
212
+
213
+ **调用示例:**
214
+ ```ts
215
+ // 调用 dog/info 路由
216
+ const res = await app.run({
217
+ path: 'dog',
218
+ key: 'info',
219
+ payload: { name: '旺财', age: 3 }
220
+ });
221
+ // res.data.content => "这是一只3岁的小狗,名字是旺财"
222
+ ```
223
+
224
+ ### metadata.returns 返回值说明
225
+
226
+ `returns` 是一个 zod schema 对象,用于定义路由响应的数据结构。主要用于:
227
+ 1. 类型安全:配合 `runAction` 方法进行返回值的类型推断
228
+ 2. 文档化:自动生成 API 文档
229
+
230
+ | 返回字段 | 类型 | 说明 |
231
+ |----------|------|------|
232
+ | `content` | `z.string()` | 小狗的信息描述,字符串类型 |
233
+
234
+ **返回结构:**
235
+ ```ts
236
+ {
237
+ code: 200, // HTTP 状态码
238
+ data: { // 返回的数据,类型由 returns schema 推断
239
+ content: "这是一只3岁的小狗,名字是旺财"
240
+ },
241
+ message: "success" // 响应消息
242
+ }
243
+ ```
244
+
245
+ ### 配合 runAction 使用
246
+
247
+ 当你使用 `runAction` 方法调用路由时,`args` 和 `returns` 会参与类型推断:
248
+
249
+ ```ts
250
+ import { App } from '@kevisual/router';
251
+ import { z } from 'zod';
252
+
253
+ const app = new App();
254
+
255
+ // 定义 API 结构
256
+ const dogAPI = {
257
+ path: 'dog',
258
+ key: 'info',
259
+ metadata: {
260
+ args: {
261
+ name: z.string(),
262
+ age: z.number(),
263
+ },
264
+ returns: {
265
+ content: z.string(),
266
+ }
267
+ }
268
+ } as const;
269
+
270
+ // runAction 会根据 metadata.args 推断 payload 类型
271
+ // 根据 metadata.returns 推断返回数据的类型
272
+ const res = await app.runAction(dogAPI, { name: '旺财', age: 3 });
273
+ // res.data.content 会被正确推断为 string 类型
274
+ ```
275
+
276
+ ## 参数校验
277
+
278
+ 框架集成了 [Zod](https://zod.dev/) v4 进行参数校验,提供两种使用方式。
197
279
 
198
- 5. **nextQuery 用于传递给 nextRoute**,在当前路由中设置 `ctx.nextQuery`,会在执行 nextRoute 时合并到 query。
280
+ ### 自动校验(metadata.check)
199
281
 
200
- 6. **避免 nextRoute 循环调用**,默认最大深度为 40 次,超过会返回 500 错误。
282
+ 在路由定义中设置 `metadata.check: true`,框架会在路由函数执行前自动校验 `metadata.args` 中定义的参数。校验失败时自动返回 HTTP 422,`body` zod issues 数组。
201
283
 
202
- 7. **needSerialize 默认为 true**,会自动对 body 进行 JSON 序列化和反序列化。
284
+ ```ts
285
+ app
286
+ .route({
287
+ path: 'user',
288
+ key: 'create',
289
+ metadata: {
290
+ args: {
291
+ name: z.string(),
292
+ age: z.number(),
293
+ },
294
+ check: true, // 开启自动校验
295
+ },
296
+ })
297
+ .define(async (ctx) => {
298
+ // 走到这里时参数已通过校验
299
+ const { name, age } = ctx.query;
300
+ ctx.body = { name, age };
301
+ })
302
+ .addTo(app);
303
+
304
+ // 校验失败时响应示例:
305
+ // { code: 422, message: 'Validation Error:...', data: [{ code: 'invalid_type', path: ['age'], message: '...' }] }
306
+ ```
307
+
308
+ ### 手动校验(ctx.safeParseAsync)
309
+
310
+ 在路由函数内部手动调用 `ctx.safeParseAsync()` 进行校验,可以更灵活地处理校验结果。
311
+
312
+ ```ts
313
+ app
314
+ .route({
315
+ path: 'user',
316
+ key: 'update',
317
+ metadata: {
318
+ args: {
319
+ id: z.string(),
320
+ name: z.string().optional(),
321
+ },
322
+ },
323
+ })
324
+ .define(async (ctx) => {
325
+ // stop: true(默认)时校验失败会自动 throw 422
326
+ // stop: false 时返回结果由你自行处理
327
+ const res = await ctx.safeParseAsync(null, { stop: false });
328
+ if (!res.success) {
329
+ // res.error.issues 为 zod v4 的错误列表(注意:zod v4 用 issues,不再是 errors)
330
+ const { fieldErrors } = res.error.flatten();
331
+ ctx.code = 422;
332
+ ctx.body = { fieldErrors };
333
+ return;
334
+ }
335
+ ctx.body = { ok: true };
336
+ })
337
+ .addTo(app);
338
+ ```
339
+
340
+ **`ctx.safeParseAsync` 参数说明:**
341
+
342
+ | 参数 | 类型 | 默认值 | 说明 |
343
+ |------|------|--------|------|
344
+ | `data` | `any` | `null` | 额外合并到校验数据中(会与 `ctx.query` 合并) |
345
+ | `opts.schema` | `Record<string, z.ZodTypeAny>` | - | 额外追加的 zod schema 字段 |
346
+ | `opts.zodOptions` | `any` | - | 透传给 zod `safeParseAsync` 的选项 |
347
+ | `opts.stop` | `boolean` | `true` | 校验失败时是否自动 throw 422,`false` 时由调用方自行处理 |
348
+
349
+ > **注意:** 项目使用 Zod v4,错误信息字段为 `res.error.issues`
350
+
351
+ ## 注意事项
352
+
353
+ 1. **path 和 key 的组合是路由的唯一标识**,同一个 path+key 只能添加一个路由,后添加的会覆盖之前的。
354
+
355
+ 2. `ctx.run` 返回 `{ code, data, message }` 格式,data 即 body。
356
+
357
+ 3. **ctx.throw 会自动结束执行**,抛出自定义错误。支持传入 `data` 字段,错误时 `ctx.body` 会被设为该值。
358
+
359
+ 4. **payload 会自动合并到 query**,调用 `ctx.run({ path, key, payload })` 时,payload 会合并到 query。
203
360
 
204
- 8. **progress 记录执行路径**,可用于调试和追踪路由调用链。
361
+ 5. **校验失败响应**:`metadata.check: true` 或 `ctx.safeParseAsync` 默认 stop 模式下,校验失败返回 `code: 422`,`body` 为 zod issues 数组,可直接用于前端表单错误展示。