diff --git a/.cnb.yml b/.cnb.yml index ee748a3..621e020 100644 --- a/.cnb.yml +++ b/.cnb.yml @@ -17,10 +17,7 @@ $: - vscode - docker imports: !reference [.common_env, imports] - # 开发环境启动后会执行的任务 - # stages: - # - name: pnpm install - # script: pnpm install + stages: !reference [.dev_tempalte, stages] .common_sync_to_gitea: &common_sync_to_gitea - <<: *common_env diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..3119bba --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,25 @@ +# @kevisual/router + +## 是什么 + +`@kevisual/router` 是一个基于自定义的的轻量级路由框架,支持 TypeScript,适用于构建 API 服务。 + +## 安装 + +```bash +npm install @kevisual/router +``` + +## 快速开始 + +```ts +import { App } from '@kevisual/router'; +const app = new App(); +app.listen(4002); + +app.route({path:'demo', key: '01'}) + .define(async (ctx) => { + ctx.body = '01'; + }) + .addTo(app); +``` \ No newline at end of file diff --git a/STATIC.md b/STATIC.md new file mode 100644 index 0000000..7a2b0b0 --- /dev/null +++ b/STATIC.md @@ -0,0 +1,14 @@ +## 兼容服务器 +```ts +import { App } from '@kevisual/router'; + +const app = new App(); +app.listen(4002); +import { proxyRoute, initProxy } from '@kevisual/local-proxy/proxy.ts'; +initProxy({ + pagesDir: './demo', + watch: true, +}); + +app.onServerRequest(proxyRoute); +``` \ No newline at end of file diff --git a/package.json b/package.json index 740d811..c8201f7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package", "name": "@kevisual/router", - "version": "0.0.55", + "version": "0.0.56", "description": "", "type": "module", "main": "./dist/router.js", diff --git a/readme.md b/readme.md index e760057..4a33ada 100644 --- a/readme.md +++ b/readme.md @@ -1,5 +1,9 @@ # router +一个轻量级的路由框架,支持链式调用、中间件、嵌套路由等功能。 + +## 快速开始 + ```ts import { App } from '@kevisual/router'; @@ -7,30 +11,178 @@ const app = new App(); app.listen(4002); app - .route({path:'demo', key: '02}) + .route({ path: 'demo', key: '02' }) .define(async (ctx) => { ctx.body = '02'; }) .addTo(app); app - .route('demo', '03') + .route({ path: 'demo', key: '03' }) .define(async (ctx) => { ctx.body = '03'; }) .addTo(app); ``` -## 兼容服务器 -``` + +## 核心概念 + +### RouteContext 属性说明 + +在 route handler 中,你可以通过 `ctx` 访问以下属性: + +| 属性 | 类型 | 说明 | +|------|------|------| +| `query` | `object` | 请求参数,会自动合并 payload | +| `body` | `number \| string \| Object` | 响应内容 | +| `code` | `number` | 响应状态码,默认为 200 | +| `message` | `string` | 响应消息 | +| `state` | `any` | 状态数据,可在路由间传递 | +| `appId` | `string` | 应用标识 | +| `currentPath` | `string` | 当前路由路径 | +| `currentKey` | `string` | 当前路由 key | +| `currentRoute` | `Route` | 当前 Route 实例 | +| `progress` | `[string, string][]` | 路由执行路径记录 | +| `nextQuery` | `object` | 传递给下一个路由的参数 | +| `end` | `boolean` | 是否提前结束路由执行 | +| `app` | `QueryRouter` | 路由实例引用 | +| `error` | `any` | 错误信息 | +| `index` | `number` | 当前路由执行深度 | +| `needSerialize` | `boolean` | 是否需要序列化响应数据 | + +### 上下文方法 + +| 方法 | 参数 | 说明 | +|------|------|------| +| `ctx.call(msg, ctx?)` | `{ path, key?, payload?, ... } \| { id }` | 调用其他路由,返回完整 context | +| `ctx.run(msg, ctx?)` | `{ path, key?, payload? }` | 调用其他路由,返回 `{ code, data, message }` | +| `ctx.forward(res)` | `{ code, data?, message? }` | 设置响应结果 | +| `ctx.throw(code?, message?, tips?)` | - | 抛出自定义错误 | + +## 完整示例 + +```ts import { App } from '@kevisual/router'; const app = new App(); app.listen(4002); -import { proxyRoute, initProxy } from '@kevisual/local-proxy/proxy.ts'; -initProxy({ - pagesDir: './demo', - watch: true, -}); -app.onServerRequest(proxyRoute); -``` \ No newline at end of file +// 基本路由 +app + .route({ path: 'user', key: 'info' }) + .define(async (ctx) => { + // ctx.query 包含请求参数 + const { id } = ctx.query; + ctx.body = { id, name: '张三' }; + ctx.code = 200; + }) + .addTo(app); + +// 使用 state 在路由间传递数据 +app + .route({ path: 'order', key: 'create' }) + .define(async (ctx) => { + ctx.state = { orderId: '12345' }; + }) + .addTo(app); + +app + .route({ path: 'order', key: 'pay' }) + .define(async (ctx) => { + // 可以获取前一个路由设置的 state + const { orderId } = ctx.state; + ctx.body = { orderId, status: 'paid' }; + }) + .addTo(app); + +// 链式调用 +app + .route({ path: 'product', key: 'list' }) + .define(async (ctx) => { + ctx.body = [{ id: 1, name: '商品A' }]; + }) + .addTo(app); + +// 调用其他路由 +app + .route({ path: 'dashboard', key: 'stats' }) + .define(async (ctx) => { + // 调用 user/info 路由 + const userRes = await ctx.run({ path: 'user', key: 'info', payload: { id: 1 } }); + // 调用 product/list 路由 + const productRes = await ctx.run({ path: 'product', key: 'list' }); + + ctx.body = { + user: userRes.data, + products: productRes.data + }; + }) + .addTo(app); + +// 使用 throw 抛出错误 +app + .route({ path: 'admin', key: 'delete' }) + .define(async (ctx) => { + const { id } = ctx.query; + if (!id) { + ctx.throw(400, '缺少参数', 'id is required'); + } + ctx.body = { success: true }; + }) + .addTo(app); +``` + +## 中间件 + +```ts +import { App, Route } from '@kevisual/router'; + +const app = new App(); + +// 定义中间件 +app.route({ + id: 'auth-example', + description: '权限校验中间件' +}).define(async(ctx) => { + const token = ctx.query.token; + if (!token) { + ctx.throw(401, '未登录', '需要 token'); + } + // 验证通过,设置用户信息到 state + ctx.state.tokenUser = { id: 1, name: '用户A' }; +}).addTo(app); + +// 使用中间件(通过 id 引用) +app + .route({ path: 'admin', key: 'panel', middleware: ['auth-example'] }) + .define(async (ctx) => { + // 可以访问中间件设置的 state + const { tokenUser } = ctx.state; + ctx.body = { tokenUser }; + }) + .addTo(app); +``` + +## 注意事项 + +1. **path 和 key 的组合是路由的唯一标识**,同一个 path+key 只能添加一个路由,后添加的会覆盖之前的。 + +2. **ctx.call vs ctx.run**: + - `call` 返回完整 context,包含所有属性 + - `run` 返回 `{ code, data, message }` 格式,data 即 body + +3. **ctx.throw 会自动结束执行**,抛出自定义错误。 + +4. **state 不会自动继承**,每个路由的 state 是独立的,除非显式传递或使用 nextRoute。 + +5. **payload 会自动合并到 query**,调用 `ctx.run({ path, key, payload })` 时,payload 会合并到 query。 + +6. **nextQuery 用于传递给 nextRoute**,在当前路由中设置 `ctx.nextQuery`,会在执行 nextRoute 时合并到 query。 + +7. **避免 nextRoute 循环调用**,默认最大深度为 40 次,超过会返回 500 错误。 + +8. **needSerialize 默认为 true**,会自动对 body 进行 JSON 序列化和反序列化。 + +9. **progress 记录执行路径**,可用于调试和追踪路由调用链。 + +10. **中间件找不到会返回 404**,错误信息中会包含找不到的中间件列表。 diff --git a/src/browser.ts b/src/browser.ts index d3ad86c..87a35cc 100644 --- a/src/browser.ts +++ b/src/browser.ts @@ -1,15 +1,16 @@ export { Route, QueryRouter, QueryRouterServer, Mini } from './route.ts'; -export type { Rule, Schema } from './validator/index.ts'; +export type { Rule, Schema, } from './validator/index.ts'; export { createSchema } from './validator/index.ts'; -export type { RouteContext, RouteOpts } from './route.ts'; +export type { RouteContext, RouteOpts, RouteMiddleware } from './route.ts'; export type { Run, Skill } from './route.ts'; -export { createSkill } from './route.ts'; +export { createSkill, tool } from './route.ts'; export { CustomError } from './result/error.ts'; export * from './router-define.ts'; +// --- 以上同步更新至 browser.ts --- \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 3b6f8e9..a3708d7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,11 +8,12 @@ export type { RouteContext, RouteOpts, RouteMiddleware } from './route.ts'; export type { Run, Skill } from './route.ts'; -export { createSkill } from './route.ts'; +export { createSkill, tool } from './route.ts'; export { CustomError } from './result/error.ts'; export * from './router-define.ts'; +// --- 以上同步更新至 browser.ts --- export { ServerNode, handleServer } from './server/index.ts'; diff --git a/src/route.ts b/src/route.ts index 292b346..2516696 100644 --- a/src/route.ts +++ b/src/route.ts @@ -102,6 +102,9 @@ export type Skill = { [key: string]: any }; } & T +export const tool = { + schema: z +} /** */ export const createSkill = (skill: Skill): Skill => { return {