@kevisual/router 0.0.58 → 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 ADDED
@@ -0,0 +1,5 @@
1
+ import { App } from '../src/app.ts';
2
+ import { useContextKey } from '@kevisual/context';
3
+ export const app = useContextKey<App>('app', () => new App());
4
+
5
+ export { createSkill, type Skill, tool } from '../src/app.ts';
@@ -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,6 @@
1
+ import { app } from './app.ts'
2
+ import { createRouterAgentPluginFn } from '../src/opencode.ts'
3
+ import './routes/index.ts'
4
+
5
+ // 工具列表
6
+ export const routerAgentPlugin = createRouterAgentPluginFn({ router: app });
@@ -0,0 +1,14 @@
1
+ import { app } from '../app.ts'
2
+ import './route-create.ts'
3
+
4
+ if (!app.hasRoute('auth', '')) {
5
+ app.route({
6
+ path: 'auth',
7
+ key: '',
8
+ id: 'auth',
9
+ description: '身份验证路由',
10
+ }).define(async (ctx) => {
11
+ //
12
+ }).addTo(app);
13
+ }
14
+
@@ -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);
package/dist/app.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ import * as _opencode_ai_plugin from '@opencode-ai/plugin';
2
+
3
+ declare const routerAgentPlugin: _opencode_ai_plugin.Plugin;
4
+
5
+ export { routerAgentPlugin };