From 7dcf53fb4f65b97051776dee1aa43d0209a3e293 Mon Sep 17 00:00:00 2001 From: abearxiong Date: Wed, 21 Jan 2026 01:44:58 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=E9=9D=99=E6=80=81?= =?UTF-8?q?=E8=B5=84=E6=BA=90=E4=BB=A3=E7=90=86=E6=96=87=E6=A1=A3=EF=BC=8C?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=B7=AF=E7=94=B1=E5=92=8C=E6=8F=92=E4=BB=B6?= =?UTF-8?q?=E9=9B=86=E6=88=90=EF=BC=8C=E6=8F=90=E5=8D=87=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E5=8F=AF=E8=AF=BB=E6=80=A7=E5=92=8C=E5=8A=9F=E8=83=BD=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .opencode/plugin/agent.ts | 3 + STATIC.md | 3 +- agent/app.ts | 5 + agent/gen.ts | 42 +++++++ agent/gen/index.ts | 205 +++++++++++++++++++++++++++++++++++ agent/main.ts | 6 + agent/routes/index.ts | 14 +++ agent/routes/route-create.ts | 45 ++++++++ bun.config.ts | 24 ++++ demo/simple/src/app.ts | 9 -- docs/examples/base.md | 15 +++ package.json | 9 +- pnpm-lock.yaml | 110 +++++++++++++++++-- src/app.ts | 42 ++----- src/opencode.ts | 12 +- src/route.ts | 12 +- 16 files changed, 487 insertions(+), 69 deletions(-) create mode 100644 .opencode/plugin/agent.ts create mode 100644 agent/app.ts create mode 100644 agent/gen.ts create mode 100644 agent/gen/index.ts create mode 100644 agent/main.ts create mode 100644 agent/routes/index.ts create mode 100644 agent/routes/route-create.ts create mode 100644 bun.config.ts create mode 100644 docs/examples/base.md diff --git a/.opencode/plugin/agent.ts b/.opencode/plugin/agent.ts new file mode 100644 index 0000000..e4d4b34 --- /dev/null +++ b/.opencode/plugin/agent.ts @@ -0,0 +1,3 @@ +import { routerAgentPlugin } from "../../agent/main.ts"; + +export { routerAgentPlugin }; \ No newline at end of file diff --git a/STATIC.md b/STATIC.md index 7a2b0b0..ec67296 100644 --- a/STATIC.md +++ b/STATIC.md @@ -1,4 +1,5 @@ -## 兼容服务器 +## 兼容服务器静态资源代理 + ```ts import { App } from '@kevisual/router'; diff --git a/agent/app.ts b/agent/app.ts new file mode 100644 index 0000000..bb4a744 --- /dev/null +++ b/agent/app.ts @@ -0,0 +1,5 @@ +import { App } from '../src/app.ts'; +import { useContextKey } from '@kevisual/context'; +export const app = useContextKey('app', () => new App()); + +export { createSkill, type Skill, tool } from '../src/app.ts'; \ No newline at end of file diff --git a/agent/gen.ts b/agent/gen.ts new file mode 100644 index 0000000..20412e2 --- /dev/null +++ b/agent/gen.ts @@ -0,0 +1,42 @@ +import path from 'path'; +import glob from 'fast-glob'; + +async function inlineMarkdownFiles() { + const files: { path: string; name: string }[] = []; + + // 添加 readme.md + const readmePath = path.join(import.meta.dir, '..', 'readme.md'); + files.push({ path: readmePath, name: 'readme' }); + + // 使用 fast-glob 动态获取 docs 目录下的 md 文件 + const rootDir = path.join(import.meta.dir, '..', 'docs'); + const mdFiles = await glob('**.md', { cwd: rootDir }); + for (const filename of mdFiles) { + // 将路径转为变量名,如 examples/base -> examples_base + const name = filename.replace(/\.md$/, '').replace(/[^a-zA-Z0-9]/g, '_'); + files.push({ path: path.join(rootDir, filename), name }); + } + + let generatedCode = '// Generated by build script\n'; + + for (const file of files) { + try { + const content = await Bun.file(file.path).text(); + // 转义模板字符串中的特殊字符 + const escapedContent = content + .replace(/\\/g, '\\\\') + .replace(/`/g, '\\`') + .replace(/\${/g, '\\${'); + + generatedCode += `export const ${file.name} = \`${escapedContent}\`;\n`; + } catch (error) { + console.warn(`Failed to read ${file.path}:`, error); + generatedCode += `export const ${file.name} = '';\n`; + } + } + + // 写入生成的文件 + await Bun.write(path.join(import.meta.dir, 'gen', 'index.ts'), generatedCode); +} + +await inlineMarkdownFiles(); diff --git a/agent/gen/index.ts b/agent/gen/index.ts new file mode 100644 index 0000000..fdf7a19 --- /dev/null +++ b/agent/gen/index.ts @@ -0,0 +1,205 @@ +// Generated by build script +export const readme = `# router + +一个轻量级的路由框架,支持链式调用、中间件、嵌套路由等功能。 + +## 快速开始 + +\`\`\`ts +import { App } from '@kevisual/router'; + +const app = new App(); +app.listen(4002); + +app + .route({ path: 'demo', key: '02' }) + .define(async (ctx) => { + ctx.body = '02'; + }) + .addTo(app); + +app + .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); + +// 基本路由 +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**,错误信息中会包含找不到的中间件列表。 +`; +export const examples_base = `# 最基本的用法 + +\`\`\`ts +import { App } from '@kevisual/router'; +const app = new App(); +app.listen(4002); + +app + .route({ path: 'demo', key: '02' }) + .define(async (ctx) => { + ctx.body = '02'; + }) + .addTo(app); + +\`\`\``; diff --git a/agent/main.ts b/agent/main.ts new file mode 100644 index 0000000..60215f0 --- /dev/null +++ b/agent/main.ts @@ -0,0 +1,6 @@ +import { app } from './app.ts' +import { createRouterAgentPluginFn } from '../src/opencode.ts' +import './routes/index.ts' + +// 工具列表 +export const routerAgentPlugin = createRouterAgentPluginFn({ router: app }); \ No newline at end of file diff --git a/agent/routes/index.ts b/agent/routes/index.ts new file mode 100644 index 0000000..9644a65 --- /dev/null +++ b/agent/routes/index.ts @@ -0,0 +1,14 @@ +import { app } from '../app.ts' +import './route-create.ts' + +if (!app.hasRoute('auth', '')) { + app.route({ + path: 'auth', + key: '', + id: 'auth', + description: '身份验证路由', + }).define(async (ctx) => { + // + }).addTo(app); +} + diff --git a/agent/routes/route-create.ts b/agent/routes/route-create.ts new file mode 100644 index 0000000..75e5dbf --- /dev/null +++ b/agent/routes/route-create.ts @@ -0,0 +1,45 @@ +import { app, createSkill, tool } from '../app.ts'; +import * as docs from '../gen/index.ts' +import * as pkgs from '../../package.json' assert { type: 'json' }; +app.route({ + path: 'router-skill', + key: 'create-route', + description: '创建路由技能', + middleware: ['auth'], + metadata: { + tags: ['opencode'], + ...createSkill({ + skill: 'create-router-skill', + title: '创建路由技能', + summary: '创建一个新的路由技能,参数包括路径、键、描述、参数等', + args: { + question: tool.schema.string().describe('要实现的功能'), + } + }) + }, +}).define(async (ctx) => { + const { question } = ctx.query || {}; + if (!question) { + ctx.throw('参数 question 不能为空'); + } + let base = '' + base += `根据用户需要实现的功能生成一个route的代码:${question}\n\n`; + base += `资料库:\n` + base += docs.readme + '\n\n'; + + ctx.body = { + body: base + } +}).addTo(app); + +// 调用router应用 path router-skill key version +app.route({ + path: 'router-skill', + key: 'version', + description: '获取路由技能版本', + middleware: ['auth'], +}).define(async (ctx) => { + ctx.body = { + content: pkgs.version || 'unknown' + } +}).addTo(app); \ No newline at end of file diff --git a/bun.config.ts b/bun.config.ts new file mode 100644 index 0000000..a496fdc --- /dev/null +++ b/bun.config.ts @@ -0,0 +1,24 @@ +import path from 'node:path'; +import pkg from './package.json'; +import fs from 'node:fs'; +import { execSync } from 'node:child_process'; +const w = (p: string) => path.resolve(import.meta.dir, p); + +const external: string[] = ["bun"]; +await Bun.build({ + target: 'node', + format: 'esm', + entrypoints: [w('./agent/main.ts')], + outdir: w('./dist'), + naming: { + entry: 'app.js', + }, + define: {}, + external +}); + +const cmd = 'dts -i ./agent/main.ts -o /app.d.ts'; + +execSync(cmd, { stdio: 'inherit' }); + +// Copy package.json to dist \ No newline at end of file diff --git a/demo/simple/src/app.ts b/demo/simple/src/app.ts index ce20f71..a03e542 100644 --- a/demo/simple/src/app.ts +++ b/demo/simple/src/app.ts @@ -10,15 +10,6 @@ route01.run = async (ctx) => { }; app.addRoute(route01); -// app.use( -// 'demo', -// async (ctx) => { -// ctx.body = '01'; -// return ctx; -// }, -// { key: '01' }, -// ); - const route02 = new Route('demo', '02'); route02.run = async (ctx) => { ctx.body = '02'; diff --git a/docs/examples/base.md b/docs/examples/base.md new file mode 100644 index 0000000..7adc8f3 --- /dev/null +++ b/docs/examples/base.md @@ -0,0 +1,15 @@ +# 最基本的用法 + +```ts +import { App } from '@kevisual/router'; +const app = new App(); +app.listen(4002); + +app + .route({ path: 'demo', key: '02' }) + .define(async (ctx) => { + ctx.body = '02'; + }) + .addTo(app); + +``` \ No newline at end of file diff --git a/package.json b/package.json index a1f1718..805250e 100644 --- a/package.json +++ b/package.json @@ -1,20 +1,21 @@ { "$schema": "https://json.schemastore.org/package", "name": "@kevisual/router", - "version": "0.0.58", + "version": "0.0.60", "description": "", "type": "module", "main": "./dist/router.js", "types": "./dist/router.d.ts", "scripts": { "build": "npm run clean && rollup -c", - "build:app": "npm run build && rsync dist/*browser* ../deploy/dist", + "postbuild": "bun run bun.config.ts", "watch": "rollup -c -w", "clean": "rm -rf dist" }, "files": [ "dist", "src", + "agent", "auto.ts", "mod.ts" ], @@ -28,7 +29,7 @@ "@kevisual/local-proxy": "^0.0.8", "@kevisual/query": "^0.0.35", "@kevisual/use-config": "^1.0.28", - "@opencode-ai/plugin": "^1.1.26", + "@opencode-ai/plugin": "^1.1.27", "@rollup/plugin-alias": "^6.0.0", "@rollup/plugin-commonjs": "29.0.0", "@rollup/plugin-node-resolve": "^16.0.3", @@ -39,6 +40,7 @@ "@types/ws": "^8.18.1", "@types/xml2js": "^0.4.14", "eventemitter3": "^5.0.4", + "fast-glob": "^3.3.3", "nanoid": "^5.1.6", "path-to-regexp": "^8.3.0", "rollup": "^4.55.2", @@ -80,6 +82,7 @@ "types": "./dist/router-simple.d.ts" }, "./opencode": "./dist/opencode.js", + "./skill": "./dist/app.js", "./define": { "import": "./dist/router-define.js", "require": "./dist/router-define.js", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5fe0cb7..d7cacc2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -28,8 +28,8 @@ importers: specifier: ^1.0.28 version: 1.0.28(dotenv@17.2.3) '@opencode-ai/plugin': - specifier: ^1.1.26 - version: 1.1.26 + specifier: ^1.1.27 + version: 1.1.27 '@rollup/plugin-alias': specifier: ^6.0.0 version: 6.0.0(rollup@4.55.2) @@ -60,6 +60,9 @@ importers: eventemitter3: specifier: ^5.0.4 version: 5.0.4 + fast-glob: + specifier: ^3.3.3 + version: 3.3.3 nanoid: specifier: ^5.1.6 version: 5.1.6 @@ -329,11 +332,23 @@ packages: resolution: {integrity: sha512-jlFxSlXUEz93cFW+UYT5BXv/rFVgiMQnIfqRYZ0gj1hSP8PMGRqMqUoHSLfKvfRRS4jseLSvTTeEKSQpZJtURg==} engines: {node: '>=10.0.0'} - '@opencode-ai/plugin@1.1.26': - resolution: {integrity: sha512-GnhLZuw9NHDJwY5msUZnFIWiLc0SnoBXWZNfnWF5+hu0fcVLgul8gqB2VK4kUv+KOQlzCVMILYikSSQkMYp7RQ==} + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} - '@opencode-ai/sdk@1.1.26': - resolution: {integrity: sha512-5s+yxNJy7DpWwDq0L//F2sqKERz4640DmDLyrRlFXmckx5prnzFl3liEOOTySOL2WaxgVwjYw8OBiT15v2mAgA==} + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@opencode-ai/plugin@1.1.27': + resolution: {integrity: sha512-EevLVaEhQ1jTLNRbQJj18tFZaVNJcZZcVqvZEbDSe17CfmVRv3FQNKRAjD/QHwb+Kym7sn+LAZxD7aYIPPelvQ==} + + '@opencode-ai/sdk@1.1.27': + resolution: {integrity: sha512-ssRZpET3zUNdk1GuF6HwFkNHhCXSTG0lhuPmw9HjifTwv1EVrn8gz7jAuME2OCvUSBvRTesH6Lb0Xt78Qbhzww==} '@rollup/plugin-alias@6.0.0': resolution: {integrity: sha512-tPCzJOtS7uuVZd+xPhoy5W4vThe6KWXNmsFCNktaAh5RTqcLiSfT4huPQIXkgJ6YCOjJHvecOAzQxLFhPxKr+g==} @@ -777,9 +792,16 @@ packages: fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + fast-uri@3.1.0: resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + fastq@1.20.1: + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -808,6 +830,10 @@ packages: get-tsconfig@4.13.0: resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==} + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + glob-to-regexp@0.4.1: resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} @@ -837,6 +863,14 @@ packages: resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} engines: {node: '>= 0.4'} + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + is-module@1.0.0: resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==} @@ -873,6 +907,10 @@ packages: merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + micromatch@4.0.8: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} @@ -928,6 +966,9 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + randombytes@2.1.0: resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} @@ -947,6 +988,10 @@ packages: engines: {node: '>= 0.4'} hasBin: true + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + rollup-plugin-dts@6.3.0: resolution: {integrity: sha512-d0UrqxYd8KyZ6i3M2Nx7WOMy708qsV/7fTHMHxCMCBOAe3V/U7OMPu5GkX8hC+cmkHhzGnfeYongl1IgiooddA==} engines: {node: '>=16'} @@ -959,6 +1004,9 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} @@ -1265,12 +1313,24 @@ snapshots: '@kevisual/ws@8.0.0': {} - '@opencode-ai/plugin@1.1.26': + '@nodelib/fs.scandir@2.1.5': dependencies: - '@opencode-ai/sdk': 1.1.26 + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.20.1 + + '@opencode-ai/plugin@1.1.27': + dependencies: + '@opencode-ai/sdk': 1.1.27 zod: 4.1.8 - '@opencode-ai/sdk@1.1.26': {} + '@opencode-ai/sdk@1.1.27': {} '@rollup/plugin-alias@6.0.0(rollup@4.55.2)': optionalDependencies: @@ -1670,8 +1730,20 @@ snapshots: fast-deep-equal@3.1.3: {} + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + fast-uri@3.1.0: {} + fastq@1.20.1: + dependencies: + reusify: 1.1.0 + fdir@6.5.0(picomatch@4.0.3): optionalDependencies: picomatch: 4.0.3 @@ -1691,6 +1763,10 @@ snapshots: dependencies: resolve-pkg-maps: 1.0.0 + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + glob-to-regexp@0.4.1: {} graceful-fs@4.2.11: {} @@ -1717,6 +1793,12 @@ snapshots: dependencies: hasown: 2.0.2 + is-extglob@2.1.1: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + is-module@1.0.0: {} is-number@7.0.0: {} @@ -1748,6 +1830,8 @@ snapshots: merge-stream@2.0.0: {} + merge2@1.4.1: {} + micromatch@4.0.8: dependencies: braces: 3.0.3 @@ -1787,6 +1871,8 @@ snapshots: picomatch@4.0.3: {} + queue-microtask@1.2.3: {} + randombytes@2.1.0: dependencies: safe-buffer: 5.2.1 @@ -1803,6 +1889,8 @@ snapshots: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 + reusify@1.1.0: {} + rollup-plugin-dts@6.3.0(rollup@4.55.2)(typescript@5.9.3): dependencies: magic-string: 0.30.21 @@ -1842,6 +1930,10 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.55.2 fsevents: 2.3.3 + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + safe-buffer@5.2.1: {} sax@1.4.3: {} diff --git a/src/app.ts b/src/app.ts index 6a1afa3..7163b22 100644 --- a/src/app.ts +++ b/src/app.ts @@ -2,7 +2,6 @@ import { QueryRouter, Route, RouteContext, RouteOpts } from './route.ts'; import { ServerNode, ServerNodeOpts } from './server/server.ts'; import { HandleCtx } from './server/server-base.ts'; import { ServerType } from './server/server-type.ts'; -import { CustomError } from './result/error.ts'; import { handleServer } from './server/handle-server.ts'; import { IncomingMessage, ServerResponse } from 'http'; import { isBun } from './utils/is-engine.ts'; @@ -26,12 +25,13 @@ export type AppRouteContext = HandleCtx & RouteContext & { app: App { - appId: string; +export class App extends QueryRouter { + declare appId: string; router: QueryRouter; server: ServerType; constructor(opts?: AppOptions) { - const router = opts?.router || new QueryRouter(); + super(); + const router = this; let server = opts?.server; if (!server) { const serverOptions = opts?.serverOptions || {}; @@ -64,15 +64,9 @@ export class App { // @ts-ignore this.server.listen(...args); } - use(path: string, fn: (ctx: any) => any, opts?: RouteOpts) { - const route = new Route(path, '', opts); - route.run = fn; - this.router.add(route); - } addRoute(route: Route) { - this.router.add(route); + super.add(route); } - add = this.addRoute; Route = Route; route(opts: RouteOpts>): Route>; @@ -109,30 +103,10 @@ export class App { } async call(message: { id?: string, path?: string; key?: string; payload?: any }, ctx?: AppRouteContext & { [key: string]: any }) { - const router = this.router; - return await router.call(message, ctx); + return await super.call(message, ctx); } - /** - * @deprecated - */ - async queryRoute(path: string, key?: string, payload?: any, ctx?: AppRouteContext & { [key: string]: any }) { - return await this.router.queryRoute({ path, key, payload }, ctx); - } - async run(msg: { id?: string, path?: string; key?: string; payload?: any }, ctx?: AppRouteContext & { [key: string]: any }) { - return await this.router.run(msg, ctx); - } - exportRoutes() { - return this.router.exportRoutes(); - } - importRoutes(routes: any[]) { - this.router.importRoutes(routes); - } - importApp(app: App) { - this.importRoutes(app.exportRoutes()); - } - throw(code?: number | string, message?: string, tips?: string): void; - throw(...args: any[]) { - throw new CustomError(...args); + async run(msg: { id?: string, path?: string; key?: string; payload?: any }, ctx?: Partial> & { [key: string]: any }) { + return await super.run(msg, ctx); } static handleRequest(req: IncomingMessage, res: ServerResponse) { return handleServer(req, res); diff --git a/src/opencode.ts b/src/opencode.ts index 50307f8..f060211 100644 --- a/src/opencode.ts +++ b/src/opencode.ts @@ -4,7 +4,7 @@ import { type App } from './app.ts' import { type Plugin } from "@opencode-ai/plugin" import { filter } from '@kevisual/js-filter'; -export const addCallFn = (app: QueryRouterServer) => { +export const addCallFn = (app: App) => { app.route({ path: 'call', key: '', @@ -35,20 +35,23 @@ export const addCallFn = (app: QueryRouterServer) => { }).addTo(app) } export const createRouterAgentPluginFn = (opts?: { - router?: QueryRouter, + router?: App | QueryRouterServer, //** 过滤比如,WHERE metadata.tags includes 'opencode' */ query?: string }) => { let router = opts?.router if (!router) { const app = useContextKey('app') - router = app.router + router = app } if (!router) { throw new Error('Router 参数缺失') } if (!router.hasRoute('call', '')) { - addCallFn(router as QueryRouterServer) + addCallFn(router as App) + } + if (!router.hasRoute('auth', '')) { + router.route({ path: 'auth', key: '', id: 'auth', description: '认证' }).define(async (ctx) => { }).addTo(router as App) } const _routes = filter(router.routes, opts?.query || '') const routes = _routes.filter(r => { @@ -88,6 +91,7 @@ export const createRouterAgentPluginFn = (opts?: { } return str; } + console.error('调用出错', res); return `Error: ${res?.message || '无法获取结果'}`; } } diff --git a/src/route.ts b/src/route.ts index 2516696..df9bdcd 100644 --- a/src/route.ts +++ b/src/route.ts @@ -60,7 +60,7 @@ export type RouteContext = { needSerialize?: boolean; } & T; export type SimpleObject = Record; -export type Run = (ctx: RouteContext) => Promise; +export type Run = (ctx: Required>) => Promise; export type NextRoute = Pick; export type RouteMiddleware = @@ -355,7 +355,7 @@ export class QueryRouter { const middleware = routeMiddleware[i]; if (middleware) { try { - await middleware.run(ctx); + await middleware.run(ctx as Required); } catch (e) { if (route?.isDebug) { console.error('=====debug====:middlerware error'); @@ -385,7 +385,7 @@ export class QueryRouter { if (route) { if (route.run) { try { - await route.run(ctx); + await route.run(ctx as Required); } catch (e) { if (route?.isDebug) { console.error('=====debug====:', 'router run error:', e.message); @@ -664,15 +664,9 @@ export class QueryRouterServer extends QueryRouter { setHandle(wrapperFn?: HandleFn, ctx?: RouteContext) { this.handle = this.getHandle(this, wrapperFn, ctx); } - use(path: string, fn: (ctx: any) => any, opts?: RouteOpts) { - const route = new Route(path, '', opts); - route.run = fn; - this.add(route); - } addRoute(route: Route) { this.add(route); } - Route = Route; route(opts: RouteOpts): Route>; route(path: string, key?: string): Route>;