@kevisual/router 0.0.59 → 0.0.60
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/agent/app.ts +5 -0
- package/agent/gen/index.ts +205 -0
- package/agent/gen.ts +42 -0
- package/agent/main.ts +6 -0
- package/agent/routes/index.ts +14 -0
- package/agent/routes/route-create.ts +45 -0
- package/dist/app.d.ts +5 -0
- package/dist/app.js +19320 -0
- package/dist/opencode.d.ts +1 -1
- package/dist/opencode.js +4 -0
- package/dist/router-browser.d.ts +1 -1
- package/dist/router.d.ts +1 -1
- package/package.json +5 -2
- package/src/app.ts +0 -1
- package/src/opencode.ts +4 -0
- package/src/route.ts +3 -3
package/agent/app.ts
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
// Generated by build script
|
|
2
|
+
export const readme = `# router
|
|
3
|
+
|
|
4
|
+
一个轻量级的路由框架,支持链式调用、中间件、嵌套路由等功能。
|
|
5
|
+
|
|
6
|
+
## 快速开始
|
|
7
|
+
|
|
8
|
+
\`\`\`ts
|
|
9
|
+
import { App } from '@kevisual/router';
|
|
10
|
+
|
|
11
|
+
const app = new App();
|
|
12
|
+
app.listen(4002);
|
|
13
|
+
|
|
14
|
+
app
|
|
15
|
+
.route({ path: 'demo', key: '02' })
|
|
16
|
+
.define(async (ctx) => {
|
|
17
|
+
ctx.body = '02';
|
|
18
|
+
})
|
|
19
|
+
.addTo(app);
|
|
20
|
+
|
|
21
|
+
app
|
|
22
|
+
.route({ path: 'demo', key: '03' })
|
|
23
|
+
.define(async (ctx) => {
|
|
24
|
+
ctx.body = '03';
|
|
25
|
+
})
|
|
26
|
+
.addTo(app);
|
|
27
|
+
\`\`\`
|
|
28
|
+
|
|
29
|
+
## 核心概念
|
|
30
|
+
|
|
31
|
+
### RouteContext 属性说明
|
|
32
|
+
|
|
33
|
+
在 route handler 中,你可以通过 \`ctx\` 访问以下属性:
|
|
34
|
+
|
|
35
|
+
| 属性 | 类型 | 说明 |
|
|
36
|
+
|------|------|------|
|
|
37
|
+
| \`query\` | \`object\` | 请求参数,会自动合并 payload |
|
|
38
|
+
| \`body\` | \`number \\| string \\| Object\` | 响应内容 |
|
|
39
|
+
| \`code\` | \`number\` | 响应状态码,默认为 200 |
|
|
40
|
+
| \`message\` | \`string\` | 响应消息 |
|
|
41
|
+
| \`state\` | \`any\` | 状态数据,可在路由间传递 |
|
|
42
|
+
| \`appId\` | \`string\` | 应用标识 |
|
|
43
|
+
| \`currentPath\` | \`string\` | 当前路由路径 |
|
|
44
|
+
| \`currentKey\` | \`string\` | 当前路由 key |
|
|
45
|
+
| \`currentRoute\` | \`Route\` | 当前 Route 实例 |
|
|
46
|
+
| \`progress\` | \`[string, string][]\` | 路由执行路径记录 |
|
|
47
|
+
| \`nextQuery\` | \`object\` | 传递给下一个路由的参数 |
|
|
48
|
+
| \`end\` | \`boolean\` | 是否提前结束路由执行 |
|
|
49
|
+
| \`app\` | \`QueryRouter\` | 路由实例引用 |
|
|
50
|
+
| \`error\` | \`any\` | 错误信息 |
|
|
51
|
+
| \`index\` | \`number\` | 当前路由执行深度 |
|
|
52
|
+
| \`needSerialize\` | \`boolean\` | 是否需要序列化响应数据 |
|
|
53
|
+
|
|
54
|
+
### 上下文方法
|
|
55
|
+
|
|
56
|
+
| 方法 | 参数 | 说明 |
|
|
57
|
+
|------|------|------|
|
|
58
|
+
| \`ctx.call(msg, ctx?)\` | \`{ path, key?, payload?, ... } \\| { id }\` | 调用其他路由,返回完整 context |
|
|
59
|
+
| \`ctx.run(msg, ctx?)\` | \`{ path, key?, payload? }\` | 调用其他路由,返回 \`{ code, data, message }\` |
|
|
60
|
+
| \`ctx.forward(res)\` | \`{ code, data?, message? }\` | 设置响应结果 |
|
|
61
|
+
| \`ctx.throw(code?, message?, tips?)\` | - | 抛出自定义错误 |
|
|
62
|
+
|
|
63
|
+
## 完整示例
|
|
64
|
+
|
|
65
|
+
\`\`\`ts
|
|
66
|
+
import { App } from '@kevisual/router';
|
|
67
|
+
|
|
68
|
+
const app = new App();
|
|
69
|
+
app.listen(4002);
|
|
70
|
+
|
|
71
|
+
// 基本路由
|
|
72
|
+
app
|
|
73
|
+
.route({ path: 'user', key: 'info' })
|
|
74
|
+
.define(async (ctx) => {
|
|
75
|
+
// ctx.query 包含请求参数
|
|
76
|
+
const { id } = ctx.query;
|
|
77
|
+
ctx.body = { id, name: '张三' };
|
|
78
|
+
ctx.code = 200;
|
|
79
|
+
})
|
|
80
|
+
.addTo(app);
|
|
81
|
+
|
|
82
|
+
// 使用 state 在路由间传递数据
|
|
83
|
+
app
|
|
84
|
+
.route({ path: 'order', key: 'create' })
|
|
85
|
+
.define(async (ctx) => {
|
|
86
|
+
ctx.state = { orderId: '12345' };
|
|
87
|
+
})
|
|
88
|
+
.addTo(app);
|
|
89
|
+
|
|
90
|
+
app
|
|
91
|
+
.route({ path: 'order', key: 'pay' })
|
|
92
|
+
.define(async (ctx) => {
|
|
93
|
+
// 可以获取前一个路由设置的 state
|
|
94
|
+
const { orderId } = ctx.state;
|
|
95
|
+
ctx.body = { orderId, status: 'paid' };
|
|
96
|
+
})
|
|
97
|
+
.addTo(app);
|
|
98
|
+
|
|
99
|
+
// 链式调用
|
|
100
|
+
app
|
|
101
|
+
.route({ path: 'product', key: 'list' })
|
|
102
|
+
.define(async (ctx) => {
|
|
103
|
+
ctx.body = [{ id: 1, name: '商品A' }];
|
|
104
|
+
})
|
|
105
|
+
.addTo(app);
|
|
106
|
+
|
|
107
|
+
// 调用其他路由
|
|
108
|
+
app
|
|
109
|
+
.route({ path: 'dashboard', key: 'stats' })
|
|
110
|
+
.define(async (ctx) => {
|
|
111
|
+
// 调用 user/info 路由
|
|
112
|
+
const userRes = await ctx.run({ path: 'user', key: 'info', payload: { id: 1 } });
|
|
113
|
+
// 调用 product/list 路由
|
|
114
|
+
const productRes = await ctx.run({ path: 'product', key: 'list' });
|
|
115
|
+
|
|
116
|
+
ctx.body = {
|
|
117
|
+
user: userRes.data,
|
|
118
|
+
products: productRes.data
|
|
119
|
+
};
|
|
120
|
+
})
|
|
121
|
+
.addTo(app);
|
|
122
|
+
|
|
123
|
+
// 使用 throw 抛出错误
|
|
124
|
+
app
|
|
125
|
+
.route({ path: 'admin', key: 'delete' })
|
|
126
|
+
.define(async (ctx) => {
|
|
127
|
+
const { id } = ctx.query;
|
|
128
|
+
if (!id) {
|
|
129
|
+
ctx.throw(400, '缺少参数', 'id is required');
|
|
130
|
+
}
|
|
131
|
+
ctx.body = { success: true };
|
|
132
|
+
})
|
|
133
|
+
.addTo(app);
|
|
134
|
+
\`\`\`
|
|
135
|
+
|
|
136
|
+
## 中间件
|
|
137
|
+
|
|
138
|
+
\`\`\`ts
|
|
139
|
+
import { App, Route } from '@kevisual/router';
|
|
140
|
+
|
|
141
|
+
const app = new App();
|
|
142
|
+
|
|
143
|
+
// 定义中间件
|
|
144
|
+
app.route({
|
|
145
|
+
id: 'auth-example',
|
|
146
|
+
description: '权限校验中间件'
|
|
147
|
+
}).define(async(ctx) => {
|
|
148
|
+
const token = ctx.query.token;
|
|
149
|
+
if (!token) {
|
|
150
|
+
ctx.throw(401, '未登录', '需要 token');
|
|
151
|
+
}
|
|
152
|
+
// 验证通过,设置用户信息到 state
|
|
153
|
+
ctx.state.tokenUser = { id: 1, name: '用户A' };
|
|
154
|
+
}).addTo(app);
|
|
155
|
+
|
|
156
|
+
// 使用中间件(通过 id 引用)
|
|
157
|
+
app
|
|
158
|
+
.route({ path: 'admin', key: 'panel', middleware: ['auth-example'] })
|
|
159
|
+
.define(async (ctx) => {
|
|
160
|
+
// 可以访问中间件设置的 state
|
|
161
|
+
const { tokenUser } = ctx.state;
|
|
162
|
+
ctx.body = { tokenUser };
|
|
163
|
+
})
|
|
164
|
+
.addTo(app);
|
|
165
|
+
\`\`\`
|
|
166
|
+
|
|
167
|
+
## 注意事项
|
|
168
|
+
|
|
169
|
+
1. **path 和 key 的组合是路由的唯一标识**,同一个 path+key 只能添加一个路由,后添加的会覆盖之前的。
|
|
170
|
+
|
|
171
|
+
2. **ctx.call vs ctx.run**:
|
|
172
|
+
- \`call\` 返回完整 context,包含所有属性
|
|
173
|
+
- \`run\` 返回 \`{ code, data, message }\` 格式,data 即 body
|
|
174
|
+
|
|
175
|
+
3. **ctx.throw 会自动结束执行**,抛出自定义错误。
|
|
176
|
+
|
|
177
|
+
4. **state 不会自动继承**,每个路由的 state 是独立的,除非显式传递或使用 nextRoute。
|
|
178
|
+
|
|
179
|
+
5. **payload 会自动合并到 query**,调用 \`ctx.run({ path, key, payload })\` 时,payload 会合并到 query。
|
|
180
|
+
|
|
181
|
+
6. **nextQuery 用于传递给 nextRoute**,在当前路由中设置 \`ctx.nextQuery\`,会在执行 nextRoute 时合并到 query。
|
|
182
|
+
|
|
183
|
+
7. **避免 nextRoute 循环调用**,默认最大深度为 40 次,超过会返回 500 错误。
|
|
184
|
+
|
|
185
|
+
8. **needSerialize 默认为 true**,会自动对 body 进行 JSON 序列化和反序列化。
|
|
186
|
+
|
|
187
|
+
9. **progress 记录执行路径**,可用于调试和追踪路由调用链。
|
|
188
|
+
|
|
189
|
+
10. **中间件找不到会返回 404**,错误信息中会包含找不到的中间件列表。
|
|
190
|
+
`;
|
|
191
|
+
export const examples_base = `# 最基本的用法
|
|
192
|
+
|
|
193
|
+
\`\`\`ts
|
|
194
|
+
import { App } from '@kevisual/router';
|
|
195
|
+
const app = new App();
|
|
196
|
+
app.listen(4002);
|
|
197
|
+
|
|
198
|
+
app
|
|
199
|
+
.route({ path: 'demo', key: '02' })
|
|
200
|
+
.define(async (ctx) => {
|
|
201
|
+
ctx.body = '02';
|
|
202
|
+
})
|
|
203
|
+
.addTo(app);
|
|
204
|
+
|
|
205
|
+
\`\`\``;
|
package/agent/gen.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import glob from 'fast-glob';
|
|
3
|
+
|
|
4
|
+
async function inlineMarkdownFiles() {
|
|
5
|
+
const files: { path: string; name: string }[] = [];
|
|
6
|
+
|
|
7
|
+
// 添加 readme.md
|
|
8
|
+
const readmePath = path.join(import.meta.dir, '..', 'readme.md');
|
|
9
|
+
files.push({ path: readmePath, name: 'readme' });
|
|
10
|
+
|
|
11
|
+
// 使用 fast-glob 动态获取 docs 目录下的 md 文件
|
|
12
|
+
const rootDir = path.join(import.meta.dir, '..', 'docs');
|
|
13
|
+
const mdFiles = await glob('**.md', { cwd: rootDir });
|
|
14
|
+
for (const filename of mdFiles) {
|
|
15
|
+
// 将路径转为变量名,如 examples/base -> examples_base
|
|
16
|
+
const name = filename.replace(/\.md$/, '').replace(/[^a-zA-Z0-9]/g, '_');
|
|
17
|
+
files.push({ path: path.join(rootDir, filename), name });
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let generatedCode = '// Generated by build script\n';
|
|
21
|
+
|
|
22
|
+
for (const file of files) {
|
|
23
|
+
try {
|
|
24
|
+
const content = await Bun.file(file.path).text();
|
|
25
|
+
// 转义模板字符串中的特殊字符
|
|
26
|
+
const escapedContent = content
|
|
27
|
+
.replace(/\\/g, '\\\\')
|
|
28
|
+
.replace(/`/g, '\\`')
|
|
29
|
+
.replace(/\${/g, '\\${');
|
|
30
|
+
|
|
31
|
+
generatedCode += `export const ${file.name} = \`${escapedContent}\`;\n`;
|
|
32
|
+
} catch (error) {
|
|
33
|
+
console.warn(`Failed to read ${file.path}:`, error);
|
|
34
|
+
generatedCode += `export const ${file.name} = '';\n`;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 写入生成的文件
|
|
39
|
+
await Bun.write(path.join(import.meta.dir, 'gen', 'index.ts'), generatedCode);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
await inlineMarkdownFiles();
|
package/agent/main.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { app, createSkill, tool } from '../app.ts';
|
|
2
|
+
import * as docs from '../gen/index.ts'
|
|
3
|
+
import * as pkgs from '../../package.json' assert { type: 'json' };
|
|
4
|
+
app.route({
|
|
5
|
+
path: 'router-skill',
|
|
6
|
+
key: 'create-route',
|
|
7
|
+
description: '创建路由技能',
|
|
8
|
+
middleware: ['auth'],
|
|
9
|
+
metadata: {
|
|
10
|
+
tags: ['opencode'],
|
|
11
|
+
...createSkill({
|
|
12
|
+
skill: 'create-router-skill',
|
|
13
|
+
title: '创建路由技能',
|
|
14
|
+
summary: '创建一个新的路由技能,参数包括路径、键、描述、参数等',
|
|
15
|
+
args: {
|
|
16
|
+
question: tool.schema.string().describe('要实现的功能'),
|
|
17
|
+
}
|
|
18
|
+
})
|
|
19
|
+
},
|
|
20
|
+
}).define(async (ctx) => {
|
|
21
|
+
const { question } = ctx.query || {};
|
|
22
|
+
if (!question) {
|
|
23
|
+
ctx.throw('参数 question 不能为空');
|
|
24
|
+
}
|
|
25
|
+
let base = ''
|
|
26
|
+
base += `根据用户需要实现的功能生成一个route的代码:${question}\n\n`;
|
|
27
|
+
base += `资料库:\n`
|
|
28
|
+
base += docs.readme + '\n\n';
|
|
29
|
+
|
|
30
|
+
ctx.body = {
|
|
31
|
+
body: base
|
|
32
|
+
}
|
|
33
|
+
}).addTo(app);
|
|
34
|
+
|
|
35
|
+
// 调用router应用 path router-skill key version
|
|
36
|
+
app.route({
|
|
37
|
+
path: 'router-skill',
|
|
38
|
+
key: 'version',
|
|
39
|
+
description: '获取路由技能版本',
|
|
40
|
+
middleware: ['auth'],
|
|
41
|
+
}).define(async (ctx) => {
|
|
42
|
+
ctx.body = {
|
|
43
|
+
content: pkgs.version || 'unknown'
|
|
44
|
+
}
|
|
45
|
+
}).addTo(app);
|