Compare commits

..

27 Commits

Author SHA1 Message Date
xiongxiao
3ce92c8ffc chore: 更新版本号至 0.1.6,并添加认证相关中间件功能 2026-03-18 22:52:46 +08:00
xiongxiao
e0fe1bb476 chore: 更新版本号至 0.1.5 2026-03-18 13:37:50 +08:00
xiongxiao
bd787c55a2 Auto commit: 2026-03-18 02:09 2026-03-18 02:09:12 +08:00
xiongxiao
648fe7ad33 chore: 更新版本号至 0.1.3,并升级 @opencode-ai/plugin 和 nanoid 依赖,增强命令行参数解析功能 2026-03-17 20:46:13 +08:00
xiongxiao
151586f98c chore: update package version and dependencies
- Bump version from 0.1.1 to 0.1.2 in package.json
- Update dependencies:
  - @kevisual/js-filter from ^0.0.5 to ^0.0.6
  - @opencode-ai/plugin from ^1.2.24 to ^1.2.26
  - @types/node from ^25.4.0 to ^25.5.0
  - hono from ^4.12.7 to ^4.12.8
  - Add crypto-js and es-toolkit as new dependencies
- Remove unused AddOpts import in src/app.ts
- Enhance commander.ts to support remote application connection
- Modify route.ts to use hashIdMd5Sync for ID generation
- Add hashIdMd5Sync function in utils/random.ts for deterministic ID generation
2026-03-15 04:59:42 +08:00
e18233fdcb chore: 更新版本号至 0.1.1,并增强命令行参数解析功能,支持 JSON 和 key=value 格式 2026-03-11 13:21:16 +08:00
5bed882795 chore: 更新版本号至 0.1.0,升级依赖项并在 run 方法中添加 token 和 data 参数 2026-03-10 21:34:08 +08:00
e4c2c0e1e6 chore: 更新版本号至 0.0.90,并在 toJSONSchemaRoute 函数中添加对返回值的处理 2026-03-09 16:15:49 +08:00
3c56849cfa chore: 更新 @opencode-ai/plugin 版本至 1.2.21,并调整 commander 模块中的类型导入 2026-03-09 01:13:45 +08:00
b375e5ac23 chore: 更新版本号至 0.0.88 并修改类型导入以支持 QueryRouterServer 2026-03-07 01:46:01 +08:00
7345940f18 chore: 添加 commander 模块及相关命令行工具功能 2026-03-07 01:41:47 +08:00
a9cf1505ff chore: 更新版本号并添加 runAction 方法及其类型推断支持 2026-03-06 23:41:31 +08:00
9ed6e63d9e chore: 更新依赖项版本以修复兼容性问题 2026-03-05 01:02:07 +08:00
e58c99c285 ws 2026-03-05 01:01:13 +08:00
xiongxiao
2628eb3693 chore: 移除未使用的类型定义并更新 RouteOpts 类型以支持复合路由 2026-03-02 04:01:17 +08:00
46499bedc7 chore: 更新依赖项版本以保持兼容性 2026-03-01 01:10:16 +08:00
xiongxiao
ab61be4875 Add design documents for HTTP server, router, and WebSocket server
- Created `https-server.md` detailing the HTTP server design, including request normalization, routing, and built-in routes.
- Added `router.md` outlining the router system design, core components, and execution flow.
- Introduced `ws-server.md` for the WebSocket server design, covering connection handling, message protocols, and custom listener registration.
2026-02-28 14:40:20 +08:00
52b10f2f03 chore: 删除不再使用的文件并更新路由上下文以支持自定义字段 2026-02-24 01:01:43 +08:00
f8337a1216 temp 2026-02-23 23:47:59 +08:00
ad95dc0081 chore: 更新 QueryRouterServer 和 App 类以支持自定义 RouteContext 类型 2026-02-23 23:43:12 +08:00
9859c2f673 temp 2026-02-23 23:36:47 +08:00
0152d15824 chore: 在文档和代码中添加 currentId 属性,增强路由上下文信息 2026-02-23 23:11:37 +08:00
5c24e197e6 chore: 更新身份验证中间件为 'auth-admin',并简化身份验证路由定义 2026-02-21 01:04:35 +08:00
af7d809270 chore: 更新版本号至0.0.83,并在 CustomError 类中添加静态 throw 方法以增强错误处理 2026-02-21 00:26:21 +08:00
a8f409f900 chore: 更新版本号至0.0.82,并在 CustomError 类中增强构造函数以支持对象参数 2026-02-20 22:53:03 +08:00
132aa3a888 chore: 在 ServerBase 类中添加自定义错误处理逻辑 2026-02-20 22:34:48 +08:00
2e59e318bf Refactor code structure for improved readability and maintainability 2026-02-20 22:25:44 +08:00
35 changed files with 3032 additions and 575 deletions

View File

@@ -1,14 +1,20 @@
import { app } from '../app.ts' import { app } from '../app.ts'
import './route-create.ts' import './route-create.ts'
if (!app.hasRoute('auth', '')) { app.route({
app.route({ path: 'auth',
path: 'auth', key: '',
key: '', id: 'auth',
id: 'auth', description: '身份验证路由',
description: '身份验证路由', }).define(async (ctx) => {
}).define(async (ctx) => { //
// }).addTo(app, { overwrite: false });
}).addTo(app);
}
app.route({
path: 'auth-admin',
key: '',
id: 'auth-admin',
description: '管理员身份验证路由',
}).define(async (ctx) => {
//
}).addTo(app, { overwrite: false });

View File

@@ -6,7 +6,7 @@ app.route({
path: 'router-skill', path: 'router-skill',
key: 'create-route', key: 'create-route',
description: '创建路由技能', description: '创建路由技能',
middleware: ['auth'], middleware: ['auth-admin'],
metadata: { metadata: {
tags: ['opencode'], tags: ['opencode'],
...createSkill({ ...createSkill({
@@ -38,7 +38,7 @@ app.route({
path: 'router-skill', path: 'router-skill',
key: 'version', key: 'version',
description: '获取最新router版本号', description: '获取最新router版本号',
middleware: ['auth'], middleware: ['auth-admin'],
metadata: { metadata: {
tags: ['opencode'], tags: ['opencode'],
...createSkill({ ...createSkill({
@@ -59,7 +59,7 @@ app.route({
path: 'route-skill', path: 'route-skill',
key: 'test', key: 'test',
description: '测试路由技能', description: '测试路由技能',
middleware: ['auth'], middleware: ['auth-admin'],
metadata: { metadata: {
tags: ['opencode'], tags: ['opencode'],
...createSkill({ ...createSkill({

View File

@@ -1,15 +1,18 @@
import { buildWithBun } from '@kevisual/code-builder'; import { buildWithBun } from '@kevisual/code-builder';
await buildWithBun({ naming: 'app', entry: 'agent/main.ts', dts: true }); const external: any[] = []
await buildWithBun({ naming: 'app', entry: 'agent/main.ts', dts: true, external });
await buildWithBun({ naming: 'router', entry: 'src/index.ts', dts: true }); await buildWithBun({ naming: 'router', entry: 'src/index.ts', dts: true, external });
await buildWithBun({ naming: 'router-browser', entry: 'src/app-browser.ts', target: 'browser', dts: true }); await buildWithBun({ naming: 'router-browser', entry: 'src/app-browser.ts', target: 'browser', dts: true, external });
await buildWithBun({ naming: 'router-define', entry: 'src/router-define.ts', target: 'browser', dts: true }); await buildWithBun({ naming: 'router-define', entry: 'src/router-define.ts', target: 'browser', dts: true, external });
await buildWithBun({ naming: 'router-simple', entry: 'src/router-simple.ts', dts: true }); await buildWithBun({ naming: 'router-simple', entry: 'src/router-simple.ts', dts: true, external });
await buildWithBun({ naming: 'opencode', entry: 'src/opencode.ts', dts: true }); await buildWithBun({ naming: 'opencode', entry: 'src/opencode.ts', dts: true, external });
await buildWithBun({ naming: 'ws', entry: 'src/ws.ts', dts: true }); await buildWithBun({ naming: 'ws', entry: 'src/ws.ts', dts: true, external });
await buildWithBun({ naming: 'commander', entry: 'src/commander.ts', dts: true, external });

277
bun.lock
View File

@@ -5,25 +5,28 @@
"": { "": {
"name": "@kevisual/router", "name": "@kevisual/router",
"dependencies": { "dependencies": {
"es-toolkit": "^1.44.0", "es-toolkit": "^1.45.1",
"md5-typescript": "^1.0.5",
}, },
"devDependencies": { "devDependencies": {
"@kevisual/code-builder": "^0.0.6", "@kevisual/code-builder": "^0.0.6",
"@kevisual/context": "^0.0.6", "@kevisual/context": "^0.0.8",
"@kevisual/dts": "^0.0.4", "@kevisual/dts": "^0.0.4",
"@kevisual/js-filter": "^0.0.5", "@kevisual/js-filter": "^0.0.6",
"@kevisual/local-proxy": "^0.0.8", "@kevisual/local-proxy": "^0.0.8",
"@kevisual/query": "^0.0.47", "@kevisual/query": "^0.0.53",
"@kevisual/remote-app": "^0.0.7",
"@kevisual/use-config": "^1.0.30", "@kevisual/use-config": "^1.0.30",
"@opencode-ai/plugin": "^1.2.6", "@opencode-ai/plugin": "^1.2.26",
"@types/bun": "^1.3.9", "@types/bun": "^1.3.10",
"@types/node": "^25.2.3", "@types/node": "^25.5.0",
"@types/send": "^1.2.1", "@types/send": "^1.2.1",
"@types/ws": "^8.18.1", "@types/ws": "^8.18.1",
"@types/xml2js": "^0.4.14", "@types/xml2js": "^0.4.14",
"commander": "^14.0.3",
"eventemitter3": "^5.0.4", "eventemitter3": "^5.0.4",
"fast-glob": "^3.3.3", "fast-glob": "^3.3.3",
"hono": "^4.11.9", "hono": "^4.12.8",
"nanoid": "^5.1.6", "nanoid": "^5.1.6",
"path-to-regexp": "^8.3.0", "path-to-regexp": "^8.3.0",
"send": "^1.2.1", "send": "^1.2.1",
@@ -35,250 +38,262 @@
}, },
}, },
"packages": { "packages": {
"@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, ""], "@babel/code-frame": ["@babel/code-frame@7.27.1", "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.27.1.tgz", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, ""],
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, ""], "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", {}, ""],
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, ""], "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", {}, ""],
"@kevisual/code-builder": ["@kevisual/code-builder@0.0.6", "", { "bin": { "code-builder": "bin/code.js", "builder": "bin/code.js" } }, "sha512-0aqATB31/yw4k4s5/xKnfr4DKbUnx8e3Z3BmKbiXTrc+CqWiWTdlGe9bKI9dZ2Df+xNp6g11W4xM2NICNyyCCw=="], "@kevisual/code-builder": ["@kevisual/code-builder@0.0.6", "https://registry.npmmirror.com/@kevisual/code-builder/-/code-builder-0.0.6.tgz", { "bin": { "code-builder": "bin/code.js", "builder": "bin/code.js" } }, "sha512-0aqATB31/yw4k4s5/xKnfr4DKbUnx8e3Z3BmKbiXTrc+CqWiWTdlGe9bKI9dZ2Df+xNp6g11W4xM2NICNyyCCw=="],
"@kevisual/context": ["@kevisual/context@0.0.6", "", {}, "sha512-w7HBOuO3JH37n6xT6W3FD7ykqHTwtyxOQzTzfEcKDCbsvGB1wVreSxFm2bvoFnnFLuxT/5QMpKlnPrwvmcTGnw=="], "@kevisual/context": ["@kevisual/context@0.0.8", "https://registry.npmmirror.com/@kevisual/context/-/context-0.0.8.tgz", {}, "sha512-DTJpyHI34NE76B7g6f+QlIqiCCyqI2qkBMQE736dzeRDGxOjnbe2iQY9W+Rt2PE6kmymM3qyOmSfNovyWyWrkA=="],
"@kevisual/dts": ["@kevisual/dts@0.0.4", "", { "dependencies": { "@rollup/plugin-commonjs": "^29.0.0", "@rollup/plugin-node-resolve": "^16.0.3", "@rollup/plugin-typescript": "^12.3.0", "rollup": "^4.57.1", "rollup-plugin-dts": "^6.3.0", "tslib": "^2.8.1" }, "bin": { "dts": "bin/dts.mjs" } }, "sha512-FVUaH/0nyhbHWpEVjFTGP54PLMm4Hf06aqWLdHOYHNPIgr1aK1C26kOH7iumklGFGk9w93IGxj8Zxe5fap5N2A=="], "@kevisual/dts": ["@kevisual/dts@0.0.4", "https://registry.npmmirror.com/@kevisual/dts/-/dts-0.0.4.tgz", { "dependencies": { "@rollup/plugin-commonjs": "^29.0.0", "@rollup/plugin-node-resolve": "^16.0.3", "@rollup/plugin-typescript": "^12.3.0", "rollup": "^4.57.1", "rollup-plugin-dts": "^6.3.0", "tslib": "^2.8.1" }, "bin": { "dts": "bin/dts.mjs" } }, "sha512-FVUaH/0nyhbHWpEVjFTGP54PLMm4Hf06aqWLdHOYHNPIgr1aK1C26kOH7iumklGFGk9w93IGxj8Zxe5fap5N2A=="],
"@kevisual/js-filter": ["@kevisual/js-filter@0.0.5", "", {}, "sha512-+S+Sf3K/aP6XtZI2s7TgKOr35UuvUvtpJ9YDW30a+mY0/N8gRuzyKhieBzQN7Ykayzz70uoMavBXut2rUlLgzw=="], "@kevisual/js-filter": ["@kevisual/js-filter@0.0.6", "", {}, "sha512-FcbOsmS1inhwrfgXMM/XLFTGTHUxBCss32JEMYdEFWQDYCar5rN8cxD1W8FuKDTVRlpA+zBpQ/BE6XT4UaeljA=="],
"@kevisual/load": ["@kevisual/load@0.0.6", "", { "dependencies": { "eventemitter3": "^5.0.1" } }, "sha512-+3YTFehRcZ1haGel5DKYMUwmi5i6f2psyaPZlfkKU/cOXgkpwoG9/BEqPCnPjicKqqnksEpixVRkyHJ+5bjLVA=="], "@kevisual/load": ["@kevisual/load@0.0.6", "https://registry.npmmirror.com/@kevisual/load/-/load-0.0.6.tgz", { "dependencies": { "eventemitter3": "^5.0.1" } }, "sha512-+3YTFehRcZ1haGel5DKYMUwmi5i6f2psyaPZlfkKU/cOXgkpwoG9/BEqPCnPjicKqqnksEpixVRkyHJ+5bjLVA=="],
"@kevisual/local-proxy": ["@kevisual/local-proxy@0.0.8", "", {}, "sha512-VX/P+6/Cc8ruqp34ag6gVX073BchUmf5VNZcTV/6MJtjrNE76G8V6TLpBE8bywLnrqyRtFLIspk4QlH8up9B5Q=="], "@kevisual/local-proxy": ["@kevisual/local-proxy@0.0.8", "https://registry.npmmirror.com/@kevisual/local-proxy/-/local-proxy-0.0.8.tgz", {}, "sha512-VX/P+6/Cc8ruqp34ag6gVX073BchUmf5VNZcTV/6MJtjrNE76G8V6TLpBE8bywLnrqyRtFLIspk4QlH8up9B5Q=="],
"@kevisual/query": ["@kevisual/query@0.0.47", "", {}, "sha512-ZR7WXeDDGUSzBtcGVU3J173sA0hCqrGTw5ybGbdNGlM0VyJV/XQIovCcSoZh1YpnciLRRqJvzXUgTnCkam+M3g=="], "@kevisual/query": ["@kevisual/query@0.0.53", "https://registry.npmmirror.com/@kevisual/query/-/query-0.0.53.tgz", {}, "sha512-PAhpCLBr0emz0lGNlTVHMbJiC5wrtGLbInPddRzgKE35fiyNt+SWSsUWABiD0DeNrLN/OxWyAFobt880Z/e5MQ=="],
"@kevisual/use-config": ["@kevisual/use-config@1.0.30", "", { "dependencies": { "@kevisual/load": "^0.0.6" }, "peerDependencies": { "dotenv": "^17" } }, "sha512-kPdna0FW/X7D600aMdiZ5UTjbCo6d8d4jjauSc8RMmBwUU6WliFDSPUNKVpzm2BsDX5Nth1IXFPYMqH+wxqAmw=="], "@kevisual/remote-app": ["@kevisual/remote-app@0.0.7", "", {}, "sha512-d0P8uyxoMnmyT8x1J9XC9ecDBbqW+jOP0ZM5fCgQRDUhWw35V/MnbCD4hNG4b6EmvoiS6a/PBC7RC5JGm3wpCg=="],
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], "@kevisual/use-config": ["@kevisual/use-config@1.0.30", "https://registry.npmmirror.com/@kevisual/use-config/-/use-config-1.0.30.tgz", { "dependencies": { "@kevisual/load": "^0.0.6" }, "peerDependencies": { "dotenv": "^17" } }, "sha512-kPdna0FW/X7D600aMdiZ5UTjbCo6d8d4jjauSc8RMmBwUU6WliFDSPUNKVpzm2BsDX5Nth1IXFPYMqH+wxqAmw=="],
"@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
"@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "https://registry.npmmirror.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="],
"@opencode-ai/plugin": ["@opencode-ai/plugin@1.2.6", "", { "dependencies": { "@opencode-ai/sdk": "1.2.6", "zod": "4.1.8" } }, "sha512-CJEp3k17yWsjyfivm3zQof8L42pdze3a7iTqMOyesHgJplSuLiBYAMndbBYMDuJkyAh0dHYjw8v10vVw7Kfl4Q=="], "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "https://registry.npmmirror.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
"@opencode-ai/sdk": ["@opencode-ai/sdk@1.2.6", "", {}, "sha512-dWMF8Aku4h7fh8sw5tQ2FtbqRLbIFT8FcsukpxTird49ax7oUXP+gzqxM/VdxHjfksQvzLBjLZyMdDStc5g7xA=="], "@opencode-ai/plugin": ["@opencode-ai/plugin@1.2.26", "https://registry.npmmirror.com/@opencode-ai/plugin/-/plugin-1.2.26.tgz", { "dependencies": { "@opencode-ai/sdk": "1.2.26", "zod": "4.1.8" } }, "sha512-pC71KGAI9T0+S84KpbEq9THp5pT7KOq+GmfdXkvQ7KSH5zi+iASWRhqorir73sKmEj2MQfpbe1BxdcU5qbeOwA=="],
"@rollup/plugin-commonjs": ["@rollup/plugin-commonjs@29.0.0", "", { "dependencies": { "@rollup/pluginutils": "^5.0.1", "commondir": "^1.0.1", "estree-walker": "^2.0.2", "fdir": "^6.2.0", "is-reference": "1.2.1", "magic-string": "^0.30.3", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^2.68.0||^3.0.0||^4.0.0" } }, "sha512-U2YHaxR2cU/yAiwKJtJRhnyLk7cifnQw0zUpISsocBDoHDJn+HTV74ABqnwr5bEgWUwFZC9oFL6wLe21lHu5eQ=="], "@opencode-ai/sdk": ["@opencode-ai/sdk@1.2.26", "https://registry.npmmirror.com/@opencode-ai/sdk/-/sdk-1.2.26.tgz", {}, "sha512-HPB+0pfvTMPj2KEjNLF3oqgldKW8koTJ7ssqXwzndazqxS+gUynzvdIKIQP4+QIInNcc5nJMG9JtfLcePGgTLQ=="],
"@rollup/plugin-node-resolve": ["@rollup/plugin-node-resolve@16.0.3", "", { "dependencies": { "@rollup/pluginutils": "^5.0.1", "@types/resolve": "1.20.2", "deepmerge": "^4.2.2", "is-module": "^1.0.0", "resolve": "^1.22.1" }, "peerDependencies": { "rollup": "^2.78.0||^3.0.0||^4.0.0" } }, "sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg=="], "@rollup/plugin-commonjs": ["@rollup/plugin-commonjs@29.0.0", "https://registry.npmmirror.com/@rollup/plugin-commonjs/-/plugin-commonjs-29.0.0.tgz", { "dependencies": { "@rollup/pluginutils": "^5.0.1", "commondir": "^1.0.1", "estree-walker": "^2.0.2", "fdir": "^6.2.0", "is-reference": "1.2.1", "magic-string": "^0.30.3", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^2.68.0||^3.0.0||^4.0.0" } }, "sha512-U2YHaxR2cU/yAiwKJtJRhnyLk7cifnQw0zUpISsocBDoHDJn+HTV74ABqnwr5bEgWUwFZC9oFL6wLe21lHu5eQ=="],
"@rollup/plugin-typescript": ["@rollup/plugin-typescript@12.3.0", "", { "dependencies": { "@rollup/pluginutils": "^5.1.0", "resolve": "^1.22.1" }, "peerDependencies": { "rollup": "^2.14.0||^3.0.0||^4.0.0", "tslib": "*", "typescript": ">=3.7.0" } }, "sha512-7DP0/p7y3t67+NabT9f8oTBFE6gGkto4SA6Np2oudYmZE/m1dt8RB0SjL1msMxFpLo631qjRCcBlAbq1ml/Big=="], "@rollup/plugin-node-resolve": ["@rollup/plugin-node-resolve@16.0.3", "https://registry.npmmirror.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.3.tgz", { "dependencies": { "@rollup/pluginutils": "^5.0.1", "@types/resolve": "1.20.2", "deepmerge": "^4.2.2", "is-module": "^1.0.0", "resolve": "^1.22.1" }, "peerDependencies": { "rollup": "^2.78.0||^3.0.0||^4.0.0" } }, "sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg=="],
"@rollup/pluginutils": ["@rollup/pluginutils@5.3.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" } }, ""], "@rollup/plugin-typescript": ["@rollup/plugin-typescript@12.3.0", "https://registry.npmmirror.com/@rollup/plugin-typescript/-/plugin-typescript-12.3.0.tgz", { "dependencies": { "@rollup/pluginutils": "^5.1.0", "resolve": "^1.22.1" }, "peerDependencies": { "rollup": "^2.14.0||^3.0.0||^4.0.0", "tslib": "*", "typescript": ">=3.7.0" } }, "sha512-7DP0/p7y3t67+NabT9f8oTBFE6gGkto4SA6Np2oudYmZE/m1dt8RB0SjL1msMxFpLo631qjRCcBlAbq1ml/Big=="],
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.57.1", "", { "os": "android", "cpu": "arm" }, "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg=="], "@rollup/pluginutils": ["@rollup/pluginutils@5.3.0", "https://registry.npmmirror.com/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" } }, ""],
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.57.1", "", { "os": "android", "cpu": "arm64" }, "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w=="], "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", { "os": "android", "cpu": "arm" }, "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg=="],
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.57.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg=="], "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", { "os": "android", "cpu": "arm64" }, "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w=="],
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.57.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w=="], "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg=="],
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.57.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug=="], "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", { "os": "darwin", "cpu": "x64" }, "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w=="],
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.57.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q=="], "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", { "os": "freebsd", "cpu": "arm64" }, "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug=="],
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.57.1", "", { "os": "linux", "cpu": "arm" }, "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw=="], "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", { "os": "freebsd", "cpu": "x64" }, "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q=="],
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.57.1", "", { "os": "linux", "cpu": "arm" }, "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw=="], "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", { "os": "linux", "cpu": "arm" }, "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw=="],
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.57.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g=="], "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", { "os": "linux", "cpu": "arm" }, "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw=="],
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.57.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q=="], "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g=="],
"@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA=="], "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q=="],
"@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw=="], "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", { "os": "linux", "cpu": "none" }, "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA=="],
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.57.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w=="], "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", { "os": "linux", "cpu": "none" }, "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw=="],
"@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.57.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw=="], "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", { "os": "linux", "cpu": "ppc64" }, "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w=="],
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A=="], "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", { "os": "linux", "cpu": "ppc64" }, "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw=="],
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw=="], "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", { "os": "linux", "cpu": "none" }, "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A=="],
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.57.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg=="], "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", { "os": "linux", "cpu": "none" }, "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw=="],
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.57.1", "", { "os": "linux", "cpu": "x64" }, "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg=="], "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", { "os": "linux", "cpu": "s390x" }, "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg=="],
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.57.1", "", { "os": "linux", "cpu": "x64" }, "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw=="], "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", { "os": "linux", "cpu": "x64" }, "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg=="],
"@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.57.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw=="], "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", { "os": "linux", "cpu": "x64" }, "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw=="],
"@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.57.1", "", { "os": "none", "cpu": "arm64" }, "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ=="], "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", { "os": "openbsd", "cpu": "x64" }, "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw=="],
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.57.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ=="], "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", { "os": "none", "cpu": "arm64" }, "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ=="],
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.57.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew=="], "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", { "os": "win32", "cpu": "arm64" }, "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ=="],
"@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.57.1", "", { "os": "win32", "cpu": "x64" }, "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ=="], "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", { "os": "win32", "cpu": "ia32" }, "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew=="],
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.57.1", "", { "os": "win32", "cpu": "x64" }, "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA=="], "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", { "os": "win32", "cpu": "x64" }, "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ=="],
"@types/bun": ["@types/bun@1.3.9", "", { "dependencies": { "bun-types": "1.3.9" } }, "sha512-KQ571yULOdWJiMH+RIWIOZ7B2RXQGpL1YQrBtLIV3FqDcCu6FsbFUBwhdKUlCKUpS3PJDsHlJ1QKlpxoVR+xtw=="], "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", { "os": "win32", "cpu": "x64" }, "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA=="],
"@types/estree": ["@types/estree@1.0.8", "", {}, ""], "@types/bun": ["@types/bun@1.3.10", "https://registry.npmmirror.com/@types/bun/-/bun-1.3.10.tgz", { "dependencies": { "bun-types": "1.3.10" } }, "sha512-0+rlrUrOrTSskibryHbvQkDOWRJwJZqZlxrUs1u4oOoTln8+WIXBPmAuCF35SWB2z4Zl3E84Nl/D0P7803nigQ=="],
"@types/node": ["@types/node@25.2.3", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ=="], "@types/estree": ["@types/estree@1.0.8", "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz", {}, ""],
"@types/resolve": ["@types/resolve@1.20.2", "", {}, ""], "@types/node": ["@types/node@25.5.0", "https://registry.npmmirror.com/@types/node/-/node-25.5.0.tgz", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="],
"@types/send": ["@types/send@1.2.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ=="], "@types/resolve": ["@types/resolve@1.20.2", "https://registry.npmmirror.com/@types/resolve/-/resolve-1.20.2.tgz", {}, ""],
"@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], "@types/send": ["@types/send@1.2.1", "https://registry.npmmirror.com/@types/send/-/send-1.2.1.tgz", { "dependencies": { "@types/node": "*" } }, "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ=="],
"@types/xml2js": ["@types/xml2js@0.4.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4YnrRemBShWRO2QjvUin8ESA41rH+9nQGLUGZV/1IDhi3SL9OhdpNC/MrulTWuptXKwhx/aDxE7toV0f/ypIXQ=="], "@types/ws": ["@types/ws@8.18.1", "https://registry.npmmirror.com/@types/ws/-/ws-8.18.1.tgz", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="],
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, ""], "@types/xml2js": ["@types/xml2js@0.4.14", "https://registry.npmmirror.com/@types/xml2js/-/xml2js-0.4.14.tgz", { "dependencies": { "@types/node": "*" } }, "sha512-4YnrRemBShWRO2QjvUin8ESA41rH+9nQGLUGZV/1IDhi3SL9OhdpNC/MrulTWuptXKwhx/aDxE7toV0f/ypIXQ=="],
"bun-types": ["bun-types@1.3.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-+UBWWOakIP4Tswh0Bt0QD0alpTY8cb5hvgiYeWCMet9YukHbzuruIEeXC2D7nMJPB12kbh8C7XJykSexEqGKJg=="], "braces": ["braces@3.0.3", "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz", { "dependencies": { "fill-range": "^7.1.1" } }, ""],
"commondir": ["commondir@1.0.1", "", {}, ""], "bun-types": ["bun-types@1.3.10", "https://registry.npmmirror.com/bun-types/-/bun-types-1.3.10.tgz", { "dependencies": { "@types/node": "*" } }, "sha512-tcpfCCl6XWo6nCVnpcVrxQ+9AYN1iqMIzgrSKYMB/fjLtV2eyAVEg7AxQJuCq/26R6HpKWykQXuSOq/21RYcbg=="],
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, ""], "commander": ["commander@14.0.3", "https://registry.npmmirror.com/commander/-/commander-14.0.3.tgz", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="],
"deepmerge": ["deepmerge@4.3.1", "", {}, ""], "commondir": ["commondir@1.0.1", "https://registry.npmmirror.com/commondir/-/commondir-1.0.1.tgz", {}, ""],
"depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="], "debug": ["debug@4.4.3", "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", { "dependencies": { "ms": "^2.1.3" } }, ""],
"dotenv": ["dotenv@17.3.1", "", {}, "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA=="], "deepmerge": ["deepmerge@4.3.1", "https://registry.npmmirror.com/deepmerge/-/deepmerge-4.3.1.tgz", {}, ""],
"ee-first": ["ee-first@1.1.1", "", {}, ""], "depd": ["depd@2.0.0", "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
"encodeurl": ["encodeurl@2.0.0", "", {}, ""], "dotenv": ["dotenv@17.3.1", "https://registry.npmmirror.com/dotenv/-/dotenv-17.3.1.tgz", {}, "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA=="],
"es-toolkit": ["es-toolkit@1.44.0", "", {}, "sha512-6penXeZalaV88MM3cGkFZZfOoLGWshWWfdy0tWw/RlVVyhvMaWSBTOvXNeiW3e5FwdS5ePW0LGEu17zT139ktg=="], "ee-first": ["ee-first@1.1.1", "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz", {}, ""],
"escape-html": ["escape-html@1.0.3", "", {}, ""], "encodeurl": ["encodeurl@2.0.0", "https://registry.npmmirror.com/encodeurl/-/encodeurl-2.0.0.tgz", {}, ""],
"estree-walker": ["estree-walker@2.0.2", "", {}, ""], "es-toolkit": ["es-toolkit@1.45.1", "https://registry.npmmirror.com/es-toolkit/-/es-toolkit-1.45.1.tgz", {}, "sha512-/jhoOj/Fx+A+IIyDNOvO3TItGmlMKhtX8ISAHKE90c4b/k1tqaqEZ+uUqfpU8DMnW5cgNJv606zS55jGvza0Xw=="],
"etag": ["etag@1.8.1", "", {}, ""], "escape-html": ["escape-html@1.0.3", "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz", {}, ""],
"eventemitter3": ["eventemitter3@5.0.4", "", {}, "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="], "estree-walker": ["estree-walker@2.0.2", "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz", {}, ""],
"fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], "etag": ["etag@1.8.1", "https://registry.npmmirror.com/etag/-/etag-1.8.1.tgz", {}, ""],
"fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="], "eventemitter3": ["eventemitter3@5.0.4", "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-5.0.4.tgz", {}, "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="],
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" } }, ""], "fast-glob": ["fast-glob@3.3.3", "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.3.3.tgz", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, ""], "fastq": ["fastq@1.20.1", "https://registry.npmmirror.com/fastq/-/fastq-1.20.1.tgz", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="],
"fresh": ["fresh@2.0.0", "", {}, ""], "fdir": ["fdir@6.5.0", "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz", { "peerDependencies": { "picomatch": "^3 || ^4" } }, ""],
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], "fill-range": ["fill-range@7.1.1", "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz", { "dependencies": { "to-regex-range": "^5.0.1" } }, ""],
"function-bind": ["function-bind@1.1.2", "", {}, ""], "fresh": ["fresh@2.0.0", "https://registry.npmmirror.com/fresh/-/fresh-2.0.0.tgz", {}, ""],
"glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], "fsevents": ["fsevents@2.3.3", "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, ""], "function-bind": ["function-bind@1.1.2", "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", {}, ""],
"hono": ["hono@4.11.9", "", {}, "sha512-Eaw2YTGM6WOxA6CXbckaEvslr2Ne4NFsKrvc0v97JD5awbmeBLO5w9Ho9L9kmKonrwF9RJlW6BxT1PVv/agBHQ=="], "glob-parent": ["glob-parent@5.1.2", "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
"http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="], "hasown": ["hasown@2.0.2", "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz", { "dependencies": { "function-bind": "^1.1.2" } }, ""],
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], "hono": ["hono@4.12.8", "https://registry.npmmirror.com/hono/-/hono-4.12.8.tgz", {}, "sha512-VJCEvtrezO1IAR+kqEYnxUOoStaQPGrCmX3j4wDTNOcD1uRPFpGlwQUIW8niPuvHXaTUxeOUl5MMDGrl+tmO9A=="],
"is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, ""], "http-errors": ["http-errors@2.0.1", "https://registry.npmmirror.com/http-errors/-/http-errors-2.0.1.tgz", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="],
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], "inherits": ["inherits@2.0.4", "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], "is-core-module": ["is-core-module@2.16.1", "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.16.1.tgz", { "dependencies": { "hasown": "^2.0.2" } }, ""],
"is-module": ["is-module@1.0.0", "", {}, ""], "is-extglob": ["is-extglob@2.1.1", "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
"is-number": ["is-number@7.0.0", "", {}, ""], "is-glob": ["is-glob@4.0.3", "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
"is-reference": ["is-reference@1.2.1", "", { "dependencies": { "@types/estree": "*" } }, ""], "is-module": ["is-module@1.0.0", "https://registry.npmmirror.com/is-module/-/is-module-1.0.0.tgz", {}, ""],
"js-tokens": ["js-tokens@4.0.0", "", {}, ""], "is-number": ["is-number@7.0.0", "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz", {}, ""],
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, ""], "is-reference": ["is-reference@1.2.1", "https://registry.npmmirror.com/is-reference/-/is-reference-1.2.1.tgz", { "dependencies": { "@types/estree": "*" } }, ""],
"merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], "js-tokens": ["js-tokens@4.0.0", "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz", {}, ""],
"micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, ""], "magic-string": ["magic-string@0.30.21", "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, ""],
"mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], "md5-typescript": ["md5-typescript@1.0.5", "", {}, "sha512-ovAc4EtiNt2dY8JPhPr/wkC9h4U5k/nuClNVcG0Ga3V1rMlYpAY24ZaaymFXJlz+ccJ6UMPo3FSaVKe7czBsXw=="],
"mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], "merge2": ["merge2@1.4.1", "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
"ms": ["ms@2.1.3", "", {}, ""], "micromatch": ["micromatch@4.0.8", "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.8.tgz", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, ""],
"nanoid": ["nanoid@5.1.6", "", { "bin": "bin/nanoid.js" }, "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg=="], "mime-db": ["mime-db@1.54.0", "https://registry.npmmirror.com/mime-db/-/mime-db-1.54.0.tgz", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
"on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, ""], "mime-types": ["mime-types@3.0.2", "https://registry.npmmirror.com/mime-types/-/mime-types-3.0.2.tgz", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="],
"path-parse": ["path-parse@1.0.7", "", {}, ""], "ms": ["ms@2.1.3", "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", {}, ""],
"path-to-regexp": ["path-to-regexp@8.3.0", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="], "nanoid": ["nanoid@5.1.6", "https://registry.npmmirror.com/nanoid/-/nanoid-5.1.6.tgz", { "bin": "bin/nanoid.js" }, "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg=="],
"picocolors": ["picocolors@1.1.1", "", {}, ""], "on-finished": ["on-finished@2.4.1", "https://registry.npmmirror.com/on-finished/-/on-finished-2.4.1.tgz", { "dependencies": { "ee-first": "1.1.1" } }, ""],
"picomatch": ["picomatch@4.0.3", "", {}, ""], "path-parse": ["path-parse@1.0.7", "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz", {}, ""],
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], "path-to-regexp": ["path-to-regexp@8.3.0", "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-8.3.0.tgz", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="],
"range-parser": ["range-parser@1.2.1", "", {}, ""], "picocolors": ["picocolors@1.1.1", "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", {}, ""],
"resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": "bin/resolve" }, ""], "picomatch": ["picomatch@4.0.3", "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.3.tgz", {}, ""],
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], "queue-microtask": ["queue-microtask@1.2.3", "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
"rollup": ["rollup@4.57.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.57.1", "@rollup/rollup-android-arm64": "4.57.1", "@rollup/rollup-darwin-arm64": "4.57.1", "@rollup/rollup-darwin-x64": "4.57.1", "@rollup/rollup-freebsd-arm64": "4.57.1", "@rollup/rollup-freebsd-x64": "4.57.1", "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", "@rollup/rollup-linux-arm-musleabihf": "4.57.1", "@rollup/rollup-linux-arm64-gnu": "4.57.1", "@rollup/rollup-linux-arm64-musl": "4.57.1", "@rollup/rollup-linux-loong64-gnu": "4.57.1", "@rollup/rollup-linux-loong64-musl": "4.57.1", "@rollup/rollup-linux-ppc64-gnu": "4.57.1", "@rollup/rollup-linux-ppc64-musl": "4.57.1", "@rollup/rollup-linux-riscv64-gnu": "4.57.1", "@rollup/rollup-linux-riscv64-musl": "4.57.1", "@rollup/rollup-linux-s390x-gnu": "4.57.1", "@rollup/rollup-linux-x64-gnu": "4.57.1", "@rollup/rollup-linux-x64-musl": "4.57.1", "@rollup/rollup-openbsd-x64": "4.57.1", "@rollup/rollup-openharmony-arm64": "4.57.1", "@rollup/rollup-win32-arm64-msvc": "4.57.1", "@rollup/rollup-win32-ia32-msvc": "4.57.1", "@rollup/rollup-win32-x64-gnu": "4.57.1", "@rollup/rollup-win32-x64-msvc": "4.57.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A=="], "range-parser": ["range-parser@1.2.1", "https://registry.npmmirror.com/range-parser/-/range-parser-1.2.1.tgz", {}, ""],
"rollup-plugin-dts": ["rollup-plugin-dts@6.3.0", "", { "dependencies": { "magic-string": "^0.30.21" }, "optionalDependencies": { "@babel/code-frame": "^7.27.1" }, "peerDependencies": { "rollup": "^3.29.4 || ^4", "typescript": "^4.5 || ^5.0" } }, "sha512-d0UrqxYd8KyZ6i3M2Nx7WOMy708qsV/7fTHMHxCMCBOAe3V/U7OMPu5GkX8hC+cmkHhzGnfeYongl1IgiooddA=="], "resolve": ["resolve@1.22.11", "https://registry.npmmirror.com/resolve/-/resolve-1.22.11.tgz", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": "bin/resolve" }, ""],
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], "reusify": ["reusify@1.1.0", "https://registry.npmmirror.com/reusify/-/reusify-1.1.0.tgz", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
"sax": ["sax@1.4.3", "", {}, ""], "rollup": ["rollup@4.57.1", "https://registry.npmmirror.com/rollup/-/rollup-4.57.1.tgz", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.57.1", "@rollup/rollup-android-arm64": "4.57.1", "@rollup/rollup-darwin-arm64": "4.57.1", "@rollup/rollup-darwin-x64": "4.57.1", "@rollup/rollup-freebsd-arm64": "4.57.1", "@rollup/rollup-freebsd-x64": "4.57.1", "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", "@rollup/rollup-linux-arm-musleabihf": "4.57.1", "@rollup/rollup-linux-arm64-gnu": "4.57.1", "@rollup/rollup-linux-arm64-musl": "4.57.1", "@rollup/rollup-linux-loong64-gnu": "4.57.1", "@rollup/rollup-linux-loong64-musl": "4.57.1", "@rollup/rollup-linux-ppc64-gnu": "4.57.1", "@rollup/rollup-linux-ppc64-musl": "4.57.1", "@rollup/rollup-linux-riscv64-gnu": "4.57.1", "@rollup/rollup-linux-riscv64-musl": "4.57.1", "@rollup/rollup-linux-s390x-gnu": "4.57.1", "@rollup/rollup-linux-x64-gnu": "4.57.1", "@rollup/rollup-linux-x64-musl": "4.57.1", "@rollup/rollup-openbsd-x64": "4.57.1", "@rollup/rollup-openharmony-arm64": "4.57.1", "@rollup/rollup-win32-arm64-msvc": "4.57.1", "@rollup/rollup-win32-ia32-msvc": "4.57.1", "@rollup/rollup-win32-x64-gnu": "4.57.1", "@rollup/rollup-win32-x64-msvc": "4.57.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A=="],
"send": ["send@1.2.1", "", { "dependencies": { "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.1", "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.2" } }, "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ=="], "rollup-plugin-dts": ["rollup-plugin-dts@6.3.0", "https://registry.npmmirror.com/rollup-plugin-dts/-/rollup-plugin-dts-6.3.0.tgz", { "dependencies": { "magic-string": "^0.30.21" }, "optionalDependencies": { "@babel/code-frame": "^7.27.1" }, "peerDependencies": { "rollup": "^3.29.4 || ^4", "typescript": "^4.5 || ^5.0" } }, "sha512-d0UrqxYd8KyZ6i3M2Nx7WOMy708qsV/7fTHMHxCMCBOAe3V/U7OMPu5GkX8hC+cmkHhzGnfeYongl1IgiooddA=="],
"setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="], "run-parallel": ["run-parallel@1.2.0", "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
"statuses": ["statuses@2.0.2", "", {}, ""], "sax": ["sax@1.4.3", "https://registry.npmmirror.com/sax/-/sax-1.4.3.tgz", {}, ""],
"supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, ""], "send": ["send@1.2.1", "https://registry.npmmirror.com/send/-/send-1.2.1.tgz", { "dependencies": { "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.1", "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.2" } }, "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ=="],
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, ""], "setprototypeof": ["setprototypeof@1.2.0", "https://registry.npmmirror.com/setprototypeof/-/setprototypeof-1.2.0.tgz", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="],
"toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="], "statuses": ["statuses@2.0.2", "https://registry.npmmirror.com/statuses/-/statuses-2.0.2.tgz", {}, ""],
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", {}, ""],
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], "to-regex-range": ["to-regex-range@5.0.1", "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz", { "dependencies": { "is-number": "^7.0.0" } }, ""],
"undici-types": ["undici-types@7.16.0", "", {}, ""], "toidentifier": ["toidentifier@1.0.1", "https://registry.npmmirror.com/toidentifier/-/toidentifier-1.0.1.tgz", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
"ws": ["@kevisual/ws@8.0.0", "", {}, "sha512-jlFxSlXUEz93cFW+UYT5BXv/rFVgiMQnIfqRYZ0gj1hSP8PMGRqMqUoHSLfKvfRRS4jseLSvTTeEKSQpZJtURg=="], "tslib": ["tslib@2.8.1", "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"xml2js": ["xml2js@0.6.2", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA=="], "typescript": ["typescript@5.9.3", "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"xmlbuilder": ["xmlbuilder@11.0.1", "", {}, ""], "undici-types": ["undici-types@7.18.2", "https://registry.npmmirror.com/undici-types/-/undici-types-7.18.2.tgz", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
"zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], "ws": ["@kevisual/ws@8.0.0", "https://registry.npmmirror.com/@kevisual/ws/-/ws-8.0.0.tgz", {}, "sha512-jlFxSlXUEz93cFW+UYT5BXv/rFVgiMQnIfqRYZ0gj1hSP8PMGRqMqUoHSLfKvfRRS4jseLSvTTeEKSQpZJtURg=="],
"@opencode-ai/plugin/zod": ["zod@4.1.8", "", {}, "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ=="], "xml2js": ["xml2js@0.6.2", "https://registry.npmmirror.com/xml2js/-/xml2js-0.6.2.tgz", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA=="],
"@types/send/@types/node": ["@types/node@25.0.3", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA=="], "xmlbuilder": ["xmlbuilder@11.0.1", "https://registry.npmmirror.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz", {}, ""],
"@types/ws/@types/node": ["@types/node@25.0.3", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA=="], "zod": ["zod@4.3.6", "https://registry.npmmirror.com/zod/-/zod-4.3.6.tgz", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
"@types/xml2js/@types/node": ["@types/node@25.0.3", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA=="], "@opencode-ai/plugin/zod": ["zod@4.1.8", "https://registry.npmmirror.com/zod/-/zod-4.1.8.tgz", {}, "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ=="],
"micromatch/picomatch": ["picomatch@2.3.1", "", {}, ""], "@types/send/@types/node": ["@types/node@25.0.3", "https://registry.npmmirror.com/@types/node/-/node-25.0.3.tgz", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA=="],
"@types/ws/@types/node": ["@types/node@25.0.3", "https://registry.npmmirror.com/@types/node/-/node-25.0.3.tgz", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA=="],
"@types/xml2js/@types/node": ["@types/node@25.0.3", "https://registry.npmmirror.com/@types/node/-/node-25.0.3.tgz", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA=="],
"micromatch/picomatch": ["picomatch@2.3.1", "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz", {}, ""],
"@types/send/@types/node/undici-types": ["undici-types@7.16.0", "https://registry.npmmirror.com/undici-types/-/undici-types-7.16.0.tgz", {}, ""],
"@types/ws/@types/node/undici-types": ["undici-types@7.16.0", "https://registry.npmmirror.com/undici-types/-/undici-types-7.16.0.tgz", {}, ""],
"@types/xml2js/@types/node/undici-types": ["undici-types@7.16.0", "https://registry.npmmirror.com/undici-types/-/undici-types-7.16.0.tgz", {}, ""],
} }
} }

View File

@@ -31,6 +31,7 @@ app.route({
}, },
}).define(async (ctx) => { }).define(async (ctx) => {
ctx.body = '03'; ctx.body = '03';
ctx.args.test
return ctx; return ctx;
}).addTo(app); }).addTo(app);
// app.server.on({ // app.server.on({

View File

@@ -18,15 +18,6 @@ route01.run = async (ctx) => {
ctx.body = '01'; ctx.body = '01';
return ctx; return ctx;
}; };
app.use(
'demo',
async (ctx) => {
ctx.body = '01';
return ctx;
},
{ key: '01' },
);
const route02 = new Route('demo', '02'); const route02 = new Route('demo', '02');
route02.run = async (ctx) => { route02.run = async (ctx) => {
ctx.body = '02'; ctx.body = '02';

View File

@@ -1,6 +0,0 @@
import { createCert } from '@kevisual/router/sign';
import { writeFileSync } from 'fs';
const { key, cert } = createCert();
writeFileSync('https-key.pem', key);
writeFileSync('https-cert.pem', cert);

View File

@@ -20,7 +20,7 @@ router
.define(async (ctx) => { .define(async (ctx) => {
ctx.body = 'Hello, world!'; ctx.body = 'Hello, world!';
// throw new CustomError('error'); // throw new CustomError('error');
throw new CustomError(5000, 'error'); ctx.throw(5000, 'error');
}) })
.addTo(router); .addTo(router);

View File

@@ -1,19 +0,0 @@
import { createCert } from '@kevisual/router/src/sign.ts';
import fs from 'node:fs';
const cert = createCert();
fs.writeFileSync('pem/https-private-key.pem', cert.key);
fs.writeFileSync('pem/https-cert.pem', cert.cert);
fs.writeFileSync(
'pem/https-config.json',
JSON.stringify(
{
createTime: new Date().getTime(),
expireDate: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).getTime(),
expireTime: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toISOString(),
},
null,
2,
),
);

View File

@@ -16,10 +16,16 @@ const queryApp = new QueryRouterServer();
app app
.route({ .route({
path: 'hello', path: 'hello',
metadata: {
args: {
name: 'string',
},
}
}) })
.define(async (ctx) => { .define(async (ctx) => {
// console.log('hello', ctx); // console.log('hello', ctx);
// console.log('hello', ctx.res); // console.log('hello', ctx.res);
ctx.query.name;
console.log('hello', ctx.query.cookies); console.log('hello', ctx.query.cookies);
// ctx.res?.cookie?.('token', 'abc', { // ctx.res?.cookie?.('token', 'abc', {
// domain: '*', // 设置为顶级域名,允许跨子域共享 // domain: '*', // 设置为顶级域名,允许跨子域共享

View File

@@ -1,14 +0,0 @@
import { createSchema } from '@kevisual/router';
const a = createSchema({
type: 'string',
minLength: 1,
maxLength: 10,
regex: '^[a-zA-Z0-9_]+$',
required: false,
});
console.log(a.safeParse('1234567890'));
console.log(a.safeParse('').error);
console.log(a.safeParse(undefined));
console.log(a.safeParse(null).error);

View File

@@ -1,35 +0,0 @@
import { SimpleRouter } from '@kevisual/router/src/router-simple.ts';
export const router = new SimpleRouter();
router.get('/', async (req, res) => {
console.log('get /');
});
router.post('/post', async (req, res) => {
console.log('post /post');
console.log('req body:', req, res);
res.end('post response');
});
router.get('/user/:id', async (req, res) => {
console.log('get /user/:id', req.params);
res.end(`user id is ${req.params.id}`);
});
router.post('/user/:id', async (req, res) => {
console.log('post /user/:id params', req.params);
const body = await router.getBody(req);
console.log('post body:', body);
res.end(`post user id is ${req.params.id}`);
});
router.post('/user/:id/a', async (req, res) => {
console.log('post /user/:id', req.params);
res.end(`post user id is ${req.params.id} a`);
});
// router.parse({ url: 'http://localhost:3000/', method: 'GET' } as any, {} as any);
// router.parse({ url: 'http://localhost:3000/post', method: 'POST' } as any, {} as any);
// router.parse({ url: 'http://localhost:3000/user/1/a', method: 'GET' } as any, {} as any);
// router.parse({ url: 'http://localhost:3000/user/1/a', method: 'POST' } as any, {} as any);

View File

@@ -1,56 +0,0 @@
import { App } from '@kevisual/router/src/app.ts';
import { router } from './a.ts';
export const app = new App();
app.server.on([{
fun: async (req, res) => {
console.log('Received request:', req.method, req.url);
const p = await router.parse(req, res);
if (p) {
console.log('Router parse result:', p);
}
}
}, {
id: 'abc',
path: '/ws',
io: true,
fun: async (data, end) => {
console.log('Custom middleware for /ws');
console.log('Data received:', data);
end({ message: 'Hello from /ws middleware' });
}
}]);
app.server.listen(3004, () => {
console.log('Server is running on http://localhost:3004');
// fetch('http://localhost:3004/', { method: 'GET' }).then(async (res) => {
// const text = await res.text();
// console.log('Response for GET /:', text);
// });
// fetch('http://localhost:3004/post', {
// method: 'POST',
// headers: { 'Content-Type': 'application/json' },
// body: JSON.stringify({ message: 'Hello, server!' }),
// }).then(async (res) => {
// const text = await res.text();
// console.log('Response for POST /post:', text);
// });
// fetch('http://localhost:3004/user/123', { method: 'GET' }).then(async (res) => {
// const text = await res.text();
// console.log('Response for GET /user/123:', text);
// });
fetch('http://localhost:3004/user/456', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'User456' }),
}).then(async (res) => {
const text = await res.text();
console.log('Response for POST /user/456:', text);
});
});

View File

@@ -65,8 +65,6 @@ app.importRoutes(app1.exportRoutes());
app.importRoutes(app2.exportRoutes()); app.importRoutes(app2.exportRoutes());
app.importApp(app3);
app.listen(4003, () => { app.listen(4003, () => {
console.log(`http://localhost:4003/api/router?path=app1&key=02`); console.log(`http://localhost:4003/api/router?path=app1&key=02`);
console.log(`http://localhost:4003/api/router?path=app1&key=01`); console.log(`http://localhost:4003/api/router?path=app1&key=01`);

View File

@@ -26,41 +26,6 @@ qr.add(
description: 'get project detail2', description: 'get project detail2',
run: async (ctx: RouteContext) => { run: async (ctx: RouteContext) => {
ctx!.body = 'project detail2'; ctx!.body = 'project detail2';
return ctx;
},
validator: {
id: {
type: 'number',
required: true,
message: 'id is required',
},
data: {
// @ts-ignore
type: 'object',
message: 'data query is error',
properties: {
name: {
type: 'string',
required: true,
message: 'name is required',
},
age: {
type: 'number',
required: true,
message: 'age is error',
},
friends: {
type: 'object',
properties: {
hair: {
type: 'string',
required: true,
message: 'hair is required',
},
},
},
},
},
}, },
}), }),
); );
@@ -73,7 +38,7 @@ const main = async () => {
id: 4, id: 4,
data: { data: {
name: 'john', name: 'john',
age: 's'+13, age: 's' + 13,
friends: { friends: {
hair: 'black', hair: 'black',
messages: 'hello', messages: 'hello',

View File

@@ -1,7 +1,7 @@
{ {
"$schema": "https://json.schemastore.org/package", "$schema": "https://json.schemastore.org/package",
"name": "@kevisual/router", "name": "@kevisual/router",
"version": "0.0.80", "version": "0.1.6",
"description": "", "description": "",
"type": "module", "type": "module",
"main": "./dist/router.js", "main": "./dist/router.js",
@@ -23,35 +23,41 @@
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"@kevisual/code-builder": "^0.0.6", "@kevisual/code-builder": "^0.0.6",
"@kevisual/context": "^0.0.6", "@kevisual/context": "^0.0.8",
"@kevisual/dts": "^0.0.4", "@kevisual/dts": "^0.0.4",
"@kevisual/js-filter": "^0.0.5", "@kevisual/js-filter": "^0.0.6",
"@kevisual/local-proxy": "^0.0.8", "@kevisual/local-proxy": "^0.0.8",
"@kevisual/query": "^0.0.47", "@kevisual/query": "^0.0.53",
"@kevisual/remote-app": "^0.0.7",
"@kevisual/use-config": "^1.0.30", "@kevisual/use-config": "^1.0.30",
"@opencode-ai/plugin": "^1.2.6", "@opencode-ai/plugin": "^1.2.27",
"@types/bun": "^1.3.9", "@types/bun": "^1.3.10",
"@types/node": "^25.2.3", "@types/crypto-js": "^4.2.2",
"@types/node": "^25.5.0",
"@types/send": "^1.2.1", "@types/send": "^1.2.1",
"@types/ws": "^8.18.1", "@types/ws": "^8.18.1",
"@types/xml2js": "^0.4.14", "@types/xml2js": "^0.4.14",
"commander": "^14.0.3",
"crypto-js": "^4.2.0",
"es-toolkit": "^1.45.1",
"eventemitter3": "^5.0.4", "eventemitter3": "^5.0.4",
"fast-glob": "^3.3.3", "fast-glob": "^3.3.3",
"hono": "^4.11.9", "hono": "^4.12.8",
"nanoid": "^5.1.6", "nanoid": "^5.1.7",
"path-to-regexp": "^8.3.0", "path-to-regexp": "^8.3.0",
"send": "^1.2.1", "send": "^1.2.1",
"typescript": "^5.9.3", "typescript": "^5.9.3",
"ws": "npm:@kevisual/ws", "ws": "npm:@kevisual/ws",
"xml2js": "^0.6.2", "xml2js": "^0.6.2"
"zod": "^4.3.6"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/abearxiong/kevisual-router.git" "url": "git+https://github.com/abearxiong/kevisual-router.git"
}, },
"dependencies": { "dependencies": {
"es-toolkit": "^1.44.0" "crypto-js": "^4.2.0",
"es-toolkit": "^1.45.1",
"zod": "^4.3.6"
}, },
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
@@ -59,6 +65,7 @@
"exports": { "exports": {
".": "./dist/router.js", ".": "./dist/router.js",
"./browser": "./dist/router-browser.js", "./browser": "./dist/router-browser.js",
"./commander": "./dist/commander.js",
"./simple": "./dist/router-simple.js", "./simple": "./dist/router-simple.js",
"./opencode": "./dist/opencode.js", "./opencode": "./dist/opencode.js",
"./skill": "./dist/app.js", "./skill": "./dist/app.js",

1212
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

141
readme.md
View File

@@ -31,63 +31,58 @@ app
在 route handler 中,你可以通过 `ctx` 访问以下属性: 在 route handler 中,你可以通过 `ctx` 访问以下属性:
| 属性 | 类型 | 说明 | | 属性 | 类型 | 说明 |
|------|------|------| | --------------- | ---------------------------- | ---------------------------- |
| `query` | `object` | 请求参数,会自动合并 payload | | `query` | `object` | 请求参数,会自动合并 payload |
| `body` | `number \| string \| Object` | 响应内容 | | `body` | `number \| string \| Object` | 响应内容 |
| `code` | `number` | 响应状态码,默认为 200 | | `code` | `number` | 响应状态码,默认为 200 |
| `message` | `string` | 响应消息 | | `message` | `string` | 响应消息 |
| `state` | `any` | 状态数据,可在路由间传递 | | `state` | `any` | 状态数据,可在路由间传递 |
| `appId` | `string` | 应用标识 | | `appId` | `string` | 应用标识 |
| `currentPath` | `string` | 当前路由路径 | | `currentId` | `string` | 当前路由ID |
| `currentKey` | `string` | 当前路由 key | | `currentPath` | `string` | 当前路由路径 |
| `currentRoute` | `Route` | 当前 Route 实例 | | `currentKey` | `string` | 当前路由 key |
| `progress` | `[string, string][]` | 路由执行路径记录 | | `currentRoute` | `Route` | 当前 Route 实例 |
| `nextQuery` | `object` | 传递给下一个路由的参数 | | `progress` | `[string, string][]` | 路由执行路径记录 |
| `end` | `boolean` | 是否提前结束路由执行 | | `nextQuery` | `object` | 传递给下一个路由的参数 |
| `app` | `QueryRouter` | 路由实例引用 | | `end` | `boolean` | 是否提前结束路由执行 |
| `error` | `any` | 错误信息 | | `app` | `QueryRouter` | 路由实例引用 |
| `index` | `number` | 当前路由执行深度 | | `error` | `any` | 错误信息 |
| `needSerialize` | `boolean` | 是否需要序列化响应数据 | | `index` | `number` | 当前路由执行深度 |
| `needSerialize` | `boolean` | 是否需要序列化响应数据 |
### 上下文方法 ### 上下文方法
| 方法 | 参数 | 说明 | | 方法 | 参数 | 说明 |
|------|------|------| | ----------------------------------- | ----------------------------------------- | -------------------------------------------- |
| `ctx.call(msg, ctx?)` | `{ path, key?, payload?, ... } \| { id }` | 调用其他路由,返回完整 context | | `ctx.call(msg, ctx?)` | `{ path, key?, payload?, ... } \| { id }` | 调用其他路由,返回完整 context |
| `ctx.run(msg, ctx?)` | `{ path, key?, payload? }` | 调用其他路由,返回 `{ code, data, message }` | | `ctx.run(msg, ctx?)` | `{ path, key?, payload? }` | 调用其他路由,返回 `{ code, data, message }` |
| `ctx.forward(res)` | `{ code, data?, message? }` | 设置响应结果 | | `ctx.forward(res)` | `{ code, data?, message? }` | 设置响应结果 |
| `ctx.throw(code?, message?, tips?)` | - | 抛出自定义错误 | | `ctx.throw(code?, message?, tips?)` | - | 抛出自定义错误 |
## 完整示例 ## 完整示例
```ts ```ts
import { App } from '@kevisual/router'; import { App } from '@kevisual/router';
import z from 'zod';
const app = new App(); const app = new App();
app.listen(4002); app.listen(4002);
// 基本路由 // 基本路由
app app
.route({ path: 'user', key: 'info' }) .route({ path: 'user', key: 'info', id: 'user-info' })
.define(async (ctx) => { .define(async (ctx) => {
// ctx.query 包含请求参数 // ctx.query 包含请求参数
const { id } = ctx.query; const { id } = ctx.query;
// 使用 state 在路由间传递数据
ctx.state.orderId = '12345';
ctx.body = { id, name: '张三' }; ctx.body = { id, name: '张三' };
ctx.code = 200; ctx.code = 200;
}) })
.addTo(app); .addTo(app);
// 使用 state 在路由间传递数据
app app
.route({ path: 'order', key: 'create' }) .route({ path: 'order', key: 'pay', middleware: ['user-info'] })
.define(async (ctx) => {
ctx.state = { orderId: '12345' };
})
.addTo(app);
app
.route({ path: 'order', key: 'pay' })
.define(async (ctx) => { .define(async (ctx) => {
// 可以获取前一个路由设置的 state // 可以获取前一个路由设置的 state
const { orderId } = ctx.state; const { orderId } = ctx.state;
@@ -95,14 +90,6 @@ app
}) })
.addTo(app); .addTo(app);
// 链式调用
app
.route({ path: 'product', key: 'list' })
.define(async (ctx) => {
ctx.body = [{ id: 1, name: '商品A' }];
})
.addTo(app);
// 调用其他路由 // 调用其他路由
app app
.route({ path: 'dashboard', key: 'stats' }) .route({ path: 'dashboard', key: 'stats' })
@@ -114,7 +101,7 @@ app
ctx.body = { ctx.body = {
user: userRes.data, user: userRes.data,
products: productRes.data products: productRes.data,
}; };
}) })
.addTo(app); .addTo(app);
@@ -140,17 +127,20 @@ import { App, Route } from '@kevisual/router';
const app = new App(); const app = new App();
// 定义中间件 // 定义中间件
app.route({ app
id: 'auth-example', .route({
description: '权限校验中间件' id: 'auth-example',
}).define(async(ctx) => { description: '权限校验中间件',
const token = ctx.query.token; })
if (!token) { .define(async (ctx) => {
ctx.throw(401, '未登录', '需要 token'); const token = ctx.query.token;
} if (!token) {
// 验证通过,设置用户信息到 state ctx.throw(401, '未登录', '需要 token');
ctx.state.tokenUser = { id: 1, name: '用户A' }; }
}).addTo(app); // 验证通过,设置用户信息到 state
ctx.state.tokenUser = { id: 1, name: '用户A' };
})
.addTo(app);
// 使用中间件(通过 id 引用) // 使用中间件(通过 id 引用)
app app
@@ -163,6 +153,33 @@ app
.addTo(app); .addTo(app);
``` ```
## 一个丰富的router示例
```ts
import { App } from '@kevisual/router';
const app = new App();
app
.router({
path: 'dog',
key: 'info',
description: '获取狗的信息',
metedata: {
args: {
owner: z.string().describe('狗主人姓名'),
age: z.number().describe('狗的年龄'),
},
},
})
.define(async (ctx) => {
const { owner, age } = ctx.query;
ctx.body = {
content: `这是一只${age}岁的狗,主人是${owner}`,
};
})
.addTo(app);
```
## 注意事项 ## 注意事项
1. **path 和 key 的组合是路由的唯一标识**,同一个 path+key 只能添加一个路由,后添加的会覆盖之前的。 1. **path 和 key 的组合是路由的唯一标识**,同一个 path+key 只能添加一个路由,后添加的会覆盖之前的。
@@ -173,16 +190,14 @@ app
3. **ctx.throw 会自动结束执行**,抛出自定义错误。 3. **ctx.throw 会自动结束执行**,抛出自定义错误。
4. **state 不会自动继承**,每个路由的 state 是独立的,除非显式传递或使用 nextRoute 4. **payload 会自动合并到 query**,调用 `ctx.run({ path, key, payload })`payload 会合并到 query
5. **payload 会自动合并到 query**,调用 `ctx.run({ path, key, payload })`payload 会合并到 query。 5. **nextQuery 用于传递给 nextRoute**,在当前路由中设置 `ctx.nextQuery`,会在执行 nextRoute 时合并到 query。
6. **nextQuery 用于传递给 nextRoute**在当前路由中设置 `ctx.nextQuery`,会在执行 nextRoute 时合并到 query 6. **避免 nextRoute 循环调用**默认最大深度为 40 次,超过会返回 500 错误
7. **避免 nextRoute 循环调用**,默认最大深度为 40 次,超过会返回 500 错误 7. **needSerialize 默认为 true**,会自动对 body 进行 JSON 序列化和反序列化
8. **needSerialize 默认为 true**,会自动对 body 进行 JSON 序列化和反序列化 8. **progress 记录执行路径**,可用于调试和追踪路由调用链
9. **progress 记录执行路径**,可用于调试和追踪路由调用链。 9. **中间件找不到会返回 404**,错误信息中会包含找不到的中间件列表。
10. **中间件找不到会返回 404**,错误信息中会包含找不到的中间件列表。

View File

@@ -1,4 +1,4 @@
import { AddOpts, QueryRouter, Route, RouteContext, RouteOpts } from './route.ts'; import { QueryRouterServer, Route, RouteContext, RouteOpts } from './route.ts';
import { ServerNode, ServerNodeOpts } from './server/server.ts'; import { ServerNode, ServerNodeOpts } from './server/server.ts';
import { HandleCtx } from './server/server-base.ts'; import { HandleCtx } from './server/server-base.ts';
import { ServerType } from './server/server-type.ts'; import { ServerType } from './server/server-type.ts';
@@ -10,7 +10,7 @@ import { randomId } from './utils/random.ts';
type RouterHandle = (msg: { path: string;[key: string]: any }) => { code: string; data?: any; message?: string;[key: string]: any }; type RouterHandle = (msg: { path: string;[key: string]: any }) => { code: string; data?: any; message?: string;[key: string]: any };
type AppOptions<T = {}> = { type AppOptions<T = {}> = {
router?: QueryRouter; router?: QueryRouterServer;
server?: ServerType; server?: ServerType;
/** handle msg 关联 */ /** handle msg 关联 */
routerHandle?: RouterHandle; routerHandle?: RouterHandle;
@@ -19,18 +19,19 @@ type AppOptions<T = {}> = {
appId?: string; appId?: string;
}; };
export type AppRouteContext<T = {}> = HandleCtx & RouteContext<T> & { app: App<T> }; export type AppRouteContext<T> = HandleCtx & RouteContext<T> & { app: App<T> };
/** /**
* 封装了 Router 和 Server 的 App 模块处理http的请求和响应内置了 Cookie 和 Token 和 res 的处理 * 封装了 Router 和 Server 的 App 模块处理http的请求和响应内置了 Cookie 和 Token 和 res 的处理
* U - Route Context的扩展类型 * U - Route Context的扩展类型
*/ */
export class App<U = {}> extends QueryRouter { export class App<U = {}> extends QueryRouterServer<AppRouteContext<U>> {
declare appId: string; declare appId: string;
router: QueryRouter; router: QueryRouterServer;
server: ServerType; server: ServerType;
declare context: AppRouteContext<U>;
constructor(opts?: AppOptions<U>) { constructor(opts?: AppOptions<U>) {
super(); super({ initHandle: false, context: { needSerialize: true, ...opts?.routerContext } as any });
const router = this; const router = this;
let server = opts?.server; let server = opts?.server;
if (!server) { if (!server) {
@@ -42,7 +43,6 @@ export class App<U = {}> extends QueryRouter {
} }
} }
server.setHandle(router.getHandle(router, opts?.routerHandle, opts?.routerContext)); server.setHandle(router.getHandle(router, opts?.routerHandle, opts?.routerContext));
router.setContext({ needSerialize: true, ...opts?.routerContext });
this.router = router; this.router = router;
this.server = server; this.server = server;
if (opts?.appId) { if (opts?.appId) {
@@ -64,50 +64,7 @@ export class App<U = {}> extends QueryRouter {
// @ts-ignore // @ts-ignore
this.server.listen(...args); this.server.listen(...args);
} }
addRoute(route: Route, opts?: AddOpts) {
super.add(route, opts);
}
Route = Route; Route = Route;
route(opts: RouteOpts<AppRouteContext<U>>): Route<AppRouteContext<U>>;
route(path: string, key?: string): Route<AppRouteContext<U>>;
route(path: string, opts?: RouteOpts<AppRouteContext<U>>): Route<AppRouteContext<U>>;
route(path: string, key?: string, opts?: RouteOpts<AppRouteContext<U>>): Route<AppRouteContext<U>>;
route(...args: any[]) {
const [path, key, opts] = args;
if (typeof path === 'object') {
return new Route(path.path, path.key, path);
}
if (typeof path === 'string') {
if (opts) {
return new Route(path, key, opts);
}
if (key && typeof key === 'object') {
return new Route(path, key?.key || '', key);
}
return new Route(path, key);
}
return new Route(path, key, opts);
}
prompt(description: string): Route<AppRouteContext<U>>
prompt(description: Function): Route<AppRouteContext<U>>
prompt(...args: any[]) {
const [desc] = args;
let description = ''
if (typeof desc === 'string') {
description = desc;
} else if (typeof desc === 'function') {
description = desc() || ''; // 如果是Promise需要addTo App之前就要获取应有的函数了。
}
return new Route('', '', { description });
}
async call(message: { id?: string, path?: string; key?: string; payload?: any }, ctx?: AppRouteContext<U> & { [key: string]: any }) {
return await super.call(message, ctx);
}
async run(msg: { id?: string, path?: string; key?: string; payload?: any }, ctx?: Partial<AppRouteContext<U>> & { [key: string]: any }) {
return await super.run(msg, ctx);
}
static handleRequest(req: IncomingMessage, res: ServerResponse) { static handleRequest(req: IncomingMessage, res: ServerResponse) {
return handleServer(req, res); return handleServer(req, res);
} }

255
src/commander.ts Normal file
View File

@@ -0,0 +1,255 @@
import { Command, program } from 'commander';
import { App } from './app.ts';
import { RemoteApp } from '@kevisual/remote-app'
import z from 'zod';
export const groupByPath = (routes: App['routes']) => {
return routes.reduce((acc, route) => {
const path = route.path || 'default';
if (!acc[path]) {
acc[path] = [];
}
acc[path].push(route);
return acc;
}, {} as Record<string, typeof routes>);
}
export const parseArgs = (args: string) => {
try {
return JSON.parse(args);
} catch {
// 尝试解析 a=b b=c 格式
const result: Record<string, string> = {};
const pairs = args.match(/(\S+?)=(\S+)/g);
if (pairs && pairs.length > 0) {
for (const pair of pairs) {
const idx = pair.indexOf('=');
const key = pair.slice(0, idx);
const raw = pair.slice(idx + 1);
let value: string | number | boolean = raw;
if (raw === 'true') value = true;
else if (raw === 'false') value = false;
else if (raw !== '' && !isNaN(Number(raw))) value = Number(raw);
result[key] = value as string;
}
return result;
}
throw new Error('Invalid arguments: expected JSON or key=value pairs (e.g. a=b c=d)');
}
}
export const parseDescription = (route: App['routes'][number]) => {
let desc = '';
if (route.metadata?.skill) {
desc += `\n\t=====${route.metadata.skill}=====\n`;
}
let hasSummary = false;
if (route.metadata?.summary) {
desc += `\t${route.metadata.summary}`;
hasSummary = true;
}
if (route.metadata?.args) {
const argsLines = Object.entries(route.metadata.args).map(([key, schema]: [string, any]) => {
const defType: string = schema?._def?.type ?? schema?.type ?? '';
const isOptional = defType === 'optional';
const innerType: string = isOptional
? (schema?._def?.innerType?.type ?? schema?._def?.innerType?._def?.type ?? '')
: defType;
const description: string =
schema?.description ??
schema?._def?.description ??
'';
const optionalMark = isOptional ? '?' : '';
const descPart = description ? ` ${description}` : '';
return `\t - ${key}${optionalMark}: ${innerType}${descPart}`;
});
desc += '\n\targs:\n' + argsLines.join('\n');
}
if (route.description && !hasSummary) {
desc += `\t - ${route.description}`;
}
return desc;
}
export const createCommand = (opts: { app: any, program: Command }) => {
const { program } = opts;
const app = opts.app as App;
const routes = app.routes;
const groupRoutes = groupByPath(routes);
for (const path in groupRoutes) {
const routeList = groupRoutes[path];
const keys = routeList.map(route => route.key).filter(Boolean);
const subProgram = program.command(path).description(`路由[${path}] ${keys.length > 0 ? ': ' + keys.join(', ') : ''}`);
routeList.forEach(route => {
if (!route.key) return;
const description = parseDescription(route);
subProgram.command(route.key)
.description(description || '')
.option('--args <args>', '命令参数,支持 JSON 格式或 key=value 形式,例如: --args \'{"a":1}\' 或 --args \'a=1 b=2\'')
.argument('[args...]', '位置参数(推荐通过 -- 分隔传入),支持 JSON 或 key=value 格式,例如: -- a=1 b=2 或 -- \'{"a":1}\'')
.action(async (passedArgs: string[], options, _command) => {
const output = (data: any) => {
if (typeof data === 'object') {
process.stdout.write(JSON.stringify(data, null, 2) + '\n');
} else {
process.stdout.write(String(data) + '\n');
}
}
try {
let args: Record<string, any> = {};
if (options.args) {
args = parseArgs(options.args);
} else if (passedArgs.length > 0) {
args = parseArgs(passedArgs.join(' '));
}
// 这里可以添加实际的命令执行逻辑,例如调用对应的路由处理函数
const res = await app.run({ path, key: route.key, payload: args }, { appId: app.appId });
if (res.code === 200) {
output(res.data);
} else {
output(`Error: ${res.message}`);
}
} catch (error) {
output(`Execution error: ${error instanceof Error ? error.message : String(error)}`);
}
});
});
}
}
export const parse = async (opts: {
app: any,
description?: string,
parse?: boolean,
version?: string,
program?: Command,
remote?: {
token?: string,
username?: string,
id?: string,
},
exitOnEnd?: boolean,
}) => {
const { description, parse = true, version, exitOnEnd = true } = opts;
const app = opts.app as App;
const _program = opts.program || program;
_program.description(description || 'Router 命令行工具');
if (version) {
_program.version(version);
}
app.createRouteList();
createCliList(app);
createCommand({ app: app as App, program: _program });
if (opts.remote) {
const { token, username, id } = opts.remote;
const remoteApp = new RemoteApp({
token,
username,
id,
});
const isConnect = await remoteApp.isConnect();
if (isConnect) {
remoteApp.listenProxy();
console.log('已连接到远程应用,正在监听命令...');
}
return;
}
if (parse) {
await _program.parseAsync(process.argv);
if (exitOnEnd) {
process.exit(0);
}
}
}
const createCliList = (app: App) => {
app.route({
path: 'cli',
key: 'list',
description: '列出所有可用的命令',
metadata: {
summary: '列出所有可用的命令',
args: {
q: z.string().optional().describe('查询关键词,支持模糊匹配命令'),
path: z.string().optional().describe('按路径前缀过滤,如 user、admin'),
tags: z.string().optional().describe('按标签过滤,多个标签用逗号分隔'),
sort: z.enum(['key', 'path', 'name']).optional().describe('排序方式'),
limit: z.number().optional().describe('限制返回数量'),
offset: z.number().optional().describe('偏移量,用于分页'),
format: z.enum(['table', 'simple', 'json']).optional().describe('输出格式'),
}
}
}).define(async (ctx) => {
const { q, path: pathFilter, tags, sort, limit, offset, format } = ctx.query as any;
let routes = app.routes.map(route => {
return {
path: route.path,
key: route.key,
description: route?.metadata?.summary || route.description || '',
tags: route?.metadata?.tags || [],
};
});
// 路径过滤
if (pathFilter) {
routes = routes.filter(route => route.path.startsWith(pathFilter));
}
// 标签过滤
if (tags) {
const tagList = tags.split(',').map((t: string) => t.trim().toLowerCase()).filter(Boolean);
if (tagList.length > 0) {
routes = routes.filter(route => {
const routeTags = Array.isArray(route.tags) ? route.tags.map((t: unknown) => String(t).toLowerCase()) : [];
return tagList.some((tag: string) => routeTags.includes(tag));
});
}
}
// 关键词过滤
if (q) {
const keyword = q.toLowerCase();
routes = routes.filter(route => {
return route.path.toLowerCase().includes(keyword) ||
route.key.toLowerCase().includes(keyword) ||
route.description.toLowerCase().includes(keyword);
});
}
// 排序
if (sort) {
routes.sort((a, b) => {
if (sort === 'path') return a.path.localeCompare(b.path);
if (sort === 'key') return a.key.localeCompare(b.key);
return a.key.localeCompare(b.key); // name 默认为 key
});
}
// 分页
const total = routes.length;
const start = offset || 0;
const end = limit ? start + limit : undefined;
routes = routes.slice(start, end);
// 输出
const outputFormat = format || 'table';
if (outputFormat === 'json') {
console.log(JSON.stringify({ total, offset: start, limit, routes }, null, 2));
return;
}
if (outputFormat === 'simple') {
routes.forEach(route => {
console.log(`${route.path} ${route.key}`);
});
return;
}
// table 格式
const table = routes.map(route => {
return `${route.path} ${route.key} - ${route.description}`;
}).join('\n');
console.log(table);
}).addTo(app, { overwrite: false })
}

View File

@@ -10,7 +10,7 @@ export const addCallFn = (app: App) => {
path: 'call', path: 'call',
key: '', key: '',
description: '调用', description: '调用',
middleware: ['auth'], middleware: ['auth-admin'],
metadata: { metadata: {
tags: ['opencode'], tags: ['opencode'],
...createSkill({ ...createSkill({
@@ -24,7 +24,7 @@ export const addCallFn = (app: App) => {
args: { args: {
path: tool.schema.string().describe('应用路径,例如 cnb'), path: tool.schema.string().describe('应用路径,例如 cnb'),
key: tool.schema.string().optional().describe('应用key例如 list-repos'), key: tool.schema.string().optional().describe('应用key例如 list-repos'),
payload: tool.schema.object({}).optional().describe('调用参数'), payload: tool.schema.object({}).optional().describe('调用参数, 为对象, 例如 { "query": "javascript" }'),
} }
}) })
}, },
@@ -59,9 +59,16 @@ export const createRouterAgentPluginFn = (opts?: {
if (!router.hasRoute('call', '')) { if (!router.hasRoute('call', '')) {
addCallFn(router as App) addCallFn(router as App)
} }
if (!router.hasRoute('auth', '')) { if (router) {
router.route({ path: 'auth', key: '', id: 'auth', description: '认证' }).define(async (ctx) => { }).addTo(router as App) (router as any).route({ path: 'auth', key: '', id: 'auth', description: '认证' }).define(async (ctx) => { }).addTo(router as App, {
overwrite: false
});
(router as any).route({ path: 'auth-admin', key: '', id: 'auth-admin', description: '认证' }).define(async (ctx) => { }).addTo(router as App, {
overwrite: false
})
} }
const _routes = filter(router.routes, opts?.query || '') const _routes = filter(router.routes, opts?.query || '')
const routes = _routes.filter(r => { const routes = _routes.filter(r => {
const metadata = r.metadata as Skill const metadata = r.metadata as Skill
@@ -75,7 +82,7 @@ export const createRouterAgentPluginFn = (opts?: {
}); });
// opencode run "使用技能查看系统信息" // opencode run "使用技能查看系统信息"
const AgentPlugin: Plugin = async (pluginInput) => { const AgentPlugin: Plugin = async (pluginInput) => {
useContextKey<PluginInput>('plugin-input', () => pluginInput, true) useContextKey<PluginInput>('plugin-input', () => pluginInput, { isNew: true })
const hooks = opts?.hooks ? await opts.hooks(pluginInput) : {} const hooks = opts?.hooks ? await opts.hooks(pluginInput) : {}
return { return {
...hooks, ...hooks,

View File

@@ -1,20 +1,25 @@
export type CustomErrorOptions = {
cause?: Error | string;
code?: number;
message?: string;
}
/** 自定义错误 */ /** 自定义错误 */
export class CustomError extends Error { export class CustomError extends Error {
code?: number; code?: number;
data?: any; data?: any;
message: string; message: string;
tips?: string; constructor(code?: number | string | CustomErrorOptions, opts?: CustomErrorOptions) {
constructor(code?: number | string, message?: string, tips?: string) { if (typeof code === 'object' && code !== null) {
super(message || String(code)); opts = code;
this.name = 'CustomError'; code = opts.code || 500;
if (typeof code === 'number') {
this.code = code;
this.message = message!;
} else {
this.code = 500;
this.message = code!;
} }
this.tips = tips; let message = opts?.message || String(code);
const cause = opts?.cause;
super(message, { cause });
this.name = 'RouterError';
let codeNum = opts?.code || (typeof code === 'number' ? code : undefined);
this.code = codeNum ?? 500;
this.message = message!;
// 这一步可不写,默认会保存堆栈追踪信息到自定义错误构造函数之前, // 这一步可不写,默认会保存堆栈追踪信息到自定义错误构造函数之前,
// 而如果写成 `Error.captureStackTrace(this)` 则自定义错误的构造函数也会被保存到堆栈追踪信息 // 而如果写成 `Error.captureStackTrace(this)` 则自定义错误的构造函数也会被保存到堆栈追踪信息
Error.captureStackTrace(this, this.constructor); Error.captureStackTrace(this, this.constructor);
@@ -31,8 +36,7 @@ export class CustomError extends Error {
return { return {
code: e?.code, code: e?.code,
data: e?.data, data: e?.data,
message: e?.message, message: e?.message
tips: e?.tips,
}; };
} }
/** /**
@@ -43,6 +47,22 @@ export class CustomError extends Error {
static isError(error: unknown): error is CustomError { static isError(error: unknown): error is CustomError {
return error instanceof CustomError || (typeof error === 'object' && error !== null && 'code' in error); return error instanceof CustomError || (typeof error === 'object' && error !== null && 'code' in error);
} }
static throw(code?: number | string, message?: string): void;
static throw(code?: number | string, opts?: CustomErrorOptions): void;
static throw(opts?: CustomErrorOptions): void;
static throw(...args: any[]) {
const [args0, args1] = args;
if (args0 && typeof args0 === 'object') {
throw new CustomError(args0);
}
if (args1 && typeof args1 === 'object') {
throw new CustomError(args0, args1);
} else if (args1) {
throw new CustomError(args0, { message: args1 });
}
// args1 不存在;
throw new CustomError(args0);
}
parse(e?: CustomError) { parse(e?: CustomError) {
if (e) { if (e) {
return CustomError.parseError(e); return CustomError.parseError(e);
@@ -52,12 +72,17 @@ export class CustomError extends Error {
code: e?.code, code: e?.code,
data: e?.data, data: e?.data,
message: e?.message, message: e?.message,
tips: e?.tips,
}; };
} }
} }
} }
export interface throwError {
throw(code?: number | string, message?: string): void;
throw(code?: number | string, opts?: CustomErrorOptions): void;
throw(opts?: CustomErrorOptions): void;
}
/* /*
try { try {
// //

View File

@@ -1,12 +1,21 @@
import { CustomError } from './result/error.ts'; import { CustomError, throwError, CustomErrorOptions } from './result/error.ts';
import { pick } from './utils/pick.ts'; import { pick } from './utils/pick.ts';
import { listenProcess, MockProcess } from './utils/listen-process.ts'; import { listenProcess, MockProcess } from './utils/listen-process.ts';
import { z } from 'zod'; import { z } from 'zod';
import { randomId } from './utils/random.ts'; import { hashIdMd5Sync, randomId } from './utils/random.ts';
import * as schema from './validator/schema.ts'; import * as schema from './validator/schema.ts';
export type RouterContextT = { code?: number;[key: string]: any }; export type RouterContextT = { code?: number;[key: string]: any };
export type RouteContext<T = { code?: number }, S = any> = {
type BuildRouteContext<M, U> = M extends { args?: infer A }
? A extends z.ZodObject<any>
? RouteContext<{ args?: z.infer<A> }, U>
: A extends Record<string, z.ZodTypeAny>
? RouteContext<{ args?: { [K in keyof A]: z.infer<A[K]> } }, U>
: RouteContext<U>
: RouteContext<U>;
export type RouteContext<T = { code?: number }, U extends SimpleObject = {}, S = { [key: string]: any }> = {
/** /**
* 本地自己调用的时候使用,可以标识为当前自调用,那么 auth 就不许重复的校验 * 本地自己调用的时候使用,可以标识为当前自调用,那么 auth 就不许重复的校验
* 或者不需要登录的,直接调用 * 或者不需要登录的,直接调用
@@ -23,9 +32,15 @@ export type RouteContext<T = { code?: number }, S = any> = {
code?: number; code?: number;
/** return msg */ /** return msg */
message?: string; message?: string;
// 传递状态 /**
* 传递状态
*/
state?: S; state?: S;
// transfer data // transfer data
/**
* 当前routerId
*/
currentId?: string;
/** /**
* 当前路径 * 当前路径
*/ */
@@ -54,19 +69,19 @@ export type RouteContext<T = { code?: number }, S = any> = {
ctx?: RouteContext & { [key: string]: any }, ctx?: RouteContext & { [key: string]: any },
) => Promise<any>; ) => Promise<any>;
/** 请求 route的返回结果解析了body为data就类同于 query.post获取的数据*/ /** 请求 route的返回结果解析了body为data就类同于 query.post获取的数据*/
run?: (message: { path: string; key?: string; payload?: any }, ctx?: RouteContext & { [key: string]: any }) => Promise<any>; run?: (message: { path: string; key?: string; payload?: any }, ctx?: RouteContext) => Promise<any>;
index?: number; index?: number;
throw?: (code?: number | string, message?: string, tips?: string) => void; throw?: throwError['throw'];
/** 是否需要序列化, 使用JSON.stringify和JSON.parse */ /** 是否需要序列化, 使用JSON.stringify和JSON.parse */
needSerialize?: boolean; needSerialize?: boolean;
} & T; } & T & U;
export type SimpleObject = Record<string, any>; export type SimpleObject = Record<string, any>;
export type Run<T extends SimpleObject = {}> = (ctx: Required<RouteContext<T>>) => Promise<typeof ctx | null | void>; export type Run<T extends SimpleObject = {}> = (ctx: Required<RouteContext<T>>) => Promise<typeof ctx | null | void>;
export type RunMessage = { path?: string; key?: string; id?: string; payload?: any; }; export type RunMessage = { path?: string; key?: string; id?: string; payload?: any; };
export type NextRoute = Pick<Route, 'id' | 'path' | 'key'>; export type NextRoute = Pick<Route, 'id' | 'path' | 'key'>;
export type RouteMiddleware = export type RouteMiddleware =
| { | {
path: string; path?: string;
key?: string; key?: string;
id?: string; id?: string;
} }
@@ -80,15 +95,7 @@ export type RouteOpts<U = {}, T = SimpleObject> = {
description?: string; description?: string;
metadata?: T; metadata?: T;
middleware?: RouteMiddleware[]; // middleware middleware?: RouteMiddleware[]; // middleware
type?: 'route' | 'middleware'; type?: 'route' | 'middleware' | 'compound'; // compound表示这个 route 作为一个聚合体,没有实际的 run而是一个 router 的聚合列表
/**
* $#$ will be used to split path and key
*/
idUsePath?: boolean;
/**
* id 合并的分隔符,默认为 $#$
*/
delimiter?: string;
isDebug?: boolean; isDebug?: boolean;
}; };
export type DefineRouteOpts = Omit<RouteOpts, 'idUsePath' | 'nextRoute'>; export type DefineRouteOpts = Omit<RouteOpts, 'idUsePath' | 'nextRoute'>;
@@ -123,7 +130,11 @@ export const createSkill = <T = SimpleObject>(skill: Skill<T>): Skill<T> => {
export type RouteInfo = Pick<Route, (typeof pickValue)[number]>; export type RouteInfo = Pick<Route, (typeof pickValue)[number]>;
export class Route<U = { [key: string]: any }, T extends SimpleObject = SimpleObject> { /**
* @M 是 route的 metadate的类型默认是 SimpleObject
* @U 是 RouteContext 里 state的类型
*/
export class Route<M extends SimpleObject = SimpleObject, U extends SimpleObject = SimpleObject> implements throwError {
/** /**
* 一级路径 * 一级路径
*/ */
@@ -133,10 +144,10 @@ export class Route<U = { [key: string]: any }, T extends SimpleObject = SimpleOb
*/ */
key?: string; key?: string;
id?: string; id?: string;
run?: Run; run?: Run<BuildRouteContext<M, U>>;
nextRoute?: NextRoute; // route to run after this route nextRoute?: NextRoute; // route to run after this route
description?: string; description?: string;
metadata?: T; metadata?: M;
middleware?: RouteMiddleware[]; // middleware middleware?: RouteMiddleware[]; // middleware
type? = 'route'; type? = 'route';
/** /**
@@ -151,23 +162,22 @@ export class Route<U = { [key: string]: any }, T extends SimpleObject = SimpleOb
key = key.trim(); key = key.trim();
this.path = path; this.path = path;
this.key = key; this.key = key;
const pathKey = `${path}$$${key}`;
if (opts) { if (opts) {
this.id = opts.id || randomId(12, 'rand-'); this.id = opts.id || hashIdMd5Sync(pathKey);
if (!opts.id && opts.idUsePath) { this.run = opts.run as Run<BuildRouteContext<M, U>>;
const delimiter = opts.delimiter ?? '$#$';
this.id = path + delimiter + key;
}
this.run = opts.run;
this.nextRoute = opts.nextRoute; this.nextRoute = opts.nextRoute;
this.description = opts.description; this.description = opts.description;
this.metadata = opts.metadata as T; this.metadata = opts.metadata as M;
this.type = opts.type || 'route'; this.type = opts.type || 'route';
this.middleware = opts.middleware || []; this.middleware = opts.middleware || [];
this.key = opts.key || key; this.key = opts.key || key;
this.path = opts.path || path; this.path = opts.path || path;
} else { } else {
this.middleware = []; this.middleware = [];
this.id = randomId(12, 'rand-'); }
if (!this.id) {
this.id = hashIdMd5Sync(pathKey);
} }
this.isDebug = opts?.isDebug ?? false; this.isDebug = opts?.isDebug ?? false;
} }
@@ -184,9 +194,9 @@ export class Route<U = { [key: string]: any }, T extends SimpleObject = SimpleOb
return this; return this;
} }
define<T extends { [key: string]: any } = RouterContextT>(opts: DefineRouteOpts): this; define<T extends { [key: string]: any } = RouterContextT>(opts: DefineRouteOpts): this;
define<T extends { [key: string]: any } = RouterContextT>(fn: Run<T & U>): this; define<T extends { [key: string]: any } = RouterContextT>(fn: Run<T & BuildRouteContext<M, U>>): this;
define<T extends { [key: string]: any } = RouterContextT>(key: string, fn: Run<T & U>): this; define<T extends { [key: string]: any } = RouterContextT>(key: string, fn: Run<T & BuildRouteContext<M, U>>): this;
define<T extends { [key: string]: any } = RouterContextT>(path: string, key: string, fn: Run<T & U>): this; define<T extends { [key: string]: any } = RouterContextT>(path: string, key: string, fn: Run<T & BuildRouteContext<M, U>>): this;
define(...args: any[]) { define(...args: any[]) {
const [path, key, opts] = args; const [path, key, opts] = args;
// 全覆盖所以opts需要准确不能由idUsePath 需要check的变量 // 全覆盖所以opts需要准确不能由idUsePath 需要check的变量
@@ -209,7 +219,7 @@ export class Route<U = { [key: string]: any }, T extends SimpleObject = SimpleOb
return this; return this;
} }
if (typeof path === 'function') { if (typeof path === 'function') {
this.run = path; this.run = path as Run<BuildRouteContext<M, U>>;
return this; return this;
} }
if (typeof path === 'string' && typeof key === 'function') { if (typeof path === 'string' && typeof key === 'function') {
@@ -242,9 +252,8 @@ export class Route<U = { [key: string]: any }, T extends SimpleObject = SimpleOb
addTo(router: QueryRouter | { add: (route: Route) => void;[key: string]: any }, opts?: AddOpts) { addTo(router: QueryRouter | { add: (route: Route) => void;[key: string]: any }, opts?: AddOpts) {
router.add(this, opts); router.add(this, opts);
} }
throw(code?: number | string, message?: string, tips?: string): void;
throw(...args: any[]) { throw(...args: any[]) {
throw new CustomError(...args); CustomError.throw(...args);
} }
} }
@@ -253,6 +262,9 @@ const toJSONSchemaRoute = (route: RouteInfo) => {
if (pickValues?.metadata?.args) { if (pickValues?.metadata?.args) {
pickValues.metadata.args = toJSONSchema(pickValues?.metadata?.args, { mergeObject: false }); pickValues.metadata.args = toJSONSchema(pickValues?.metadata?.args, { mergeObject: false });
} }
if (pickValues?.metadata?.returns) {
pickValues.metadata.returns = toJSONSchema(pickValues?.metadata?.returns, { mergeObject: false });
}
return pickValues; return pickValues;
} }
@@ -263,11 +275,11 @@ export const fromJSONSchema = schema.fromJSONSchema;
* @parmas overwrite 是否覆盖已存在的route默认true * @parmas overwrite 是否覆盖已存在的route默认true
*/ */
export type AddOpts = { overwrite?: boolean }; export type AddOpts = { overwrite?: boolean };
export class QueryRouter { export class QueryRouter<T extends SimpleObject = SimpleObject> implements throwError {
appId: string = ''; appId: string = '';
routes: Route[]; routes: Route[];
maxNextRoute = 40; maxNextRoute = 40;
context?: RouteContext = {}; // default context for call context?: RouteContext<T> = {} as RouteContext<T>; // default context for call
constructor() { constructor() {
this.routes = []; this.routes = [];
} }
@@ -310,11 +322,12 @@ export class QueryRouter {
* @param ctx * @param ctx
* @returns * @returns
*/ */
async runRoute(path: string, key: string, ctx?: RouteContext) { async runRoute(path: string, key: string, ctx?: RouteContext<T>): Promise<RouteContext<T>> {
const route = this.routes.find((r) => r.path === path && r.key === key); const route = this.routes.find((r) => r.path === path && r.key === key);
const maxNextRoute = this.maxNextRoute; const maxNextRoute = this.maxNextRoute;
ctx = (ctx || {}) as RouteContext; ctx = (ctx || {}) as RouteContext<T>;
ctx.currentPath = path; ctx.currentPath = path;
ctx.currentId = route?.id;
ctx.currentKey = key; ctx.currentKey = key;
ctx.currentRoute = route; ctx.currentRoute = route;
ctx.index = (ctx.index || 0) + 1; ctx.index = (ctx.index || 0) + 1;
@@ -328,7 +341,7 @@ export class QueryRouter {
ctx.code = 500; ctx.code = 500;
ctx.message = 'Too many nextRoute'; ctx.message = 'Too many nextRoute';
ctx.body = null; ctx.body = null;
return; return ctx;
} }
// run middleware // run middleware
if (route && route.middleware && route.middleware.length > 0) { if (route && route.middleware && route.middleware.length > 0) {
@@ -383,7 +396,7 @@ export class QueryRouter {
const middleware = routeMiddleware[i]; const middleware = routeMiddleware[i];
if (middleware) { if (middleware) {
try { try {
await middleware.run(ctx as Required<RouteContext>); await middleware.run(ctx as Required<RouteContext<T>>);
} catch (e) { } catch (e) {
if (route?.isDebug) { if (route?.isDebug) {
console.error('=====debug====:middlerware error'); console.error('=====debug====:middlerware error');
@@ -405,6 +418,7 @@ export class QueryRouter {
return ctx; return ctx;
} }
if (ctx.end) { if (ctx.end) {
return ctx;
} }
} }
} }
@@ -413,14 +427,14 @@ export class QueryRouter {
if (route) { if (route) {
if (route.run) { if (route.run) {
try { try {
await route.run(ctx as Required<RouteContext>); await route.run(ctx as Required<RouteContext<T>>);
} catch (e) { } catch (e) {
if (route?.isDebug) { if (route?.isDebug) {
console.error('=====debug====:route error'); console.error('=====debug====:route error');
console.error('=====debug====:', e); console.error('=====debug====:', e);
console.error('=====debug====:[path:key]:', `${route.path}-${route.key}`); console.error('=====debug====:[path:key]:', `${route.path}-${route.key}`);
} }
if (e instanceof CustomError) { if (e instanceof CustomError || e?.code) {
ctx.code = e.code; ctx.code = e.code;
ctx.message = e.message; ctx.message = e.message;
} else { } else {
@@ -468,7 +482,7 @@ export class QueryRouter {
} }
} }
// 如果没有找到route返回404这是因为出现了错误 // 如果没有找到route返回404这是因为出现了错误
return Promise.resolve({ code: 404, body: 'Not found' }); return Promise.resolve({ code: 404, body: 'Not found' } as RouteContext<T>);
} }
/** /**
* 第一次执行 * 第一次执行
@@ -476,12 +490,12 @@ export class QueryRouter {
* @param ctx * @param ctx
* @returns * @returns
*/ */
async parse(message: { path: string; key?: string; payload?: any }, ctx?: RouteContext & { [key: string]: any }) { async parse(message: { path: string; key?: string; payload?: any }, ctx?: RouteContext<T> & { [key: string]: any }) {
if (!message?.path) { if (!message?.path) {
return Promise.resolve({ code: 404, body: null, message: 'Not found path' }); return Promise.resolve({ code: 404, body: null, message: 'Not found path' } as RouteContext<T>);
} }
const { path, key = '', payload = {}, ...query } = message; const { path, key = '', payload = {}, ...query } = message;
ctx = ctx || {}; ctx = ctx || {} as RouteContext<T>;
ctx.query = { ...ctx.query, ...query, ...payload }; ctx.query = { ...ctx.query, ...query, ...payload };
ctx.args = ctx.query; ctx.args = ctx.query;
ctx.state = { ...ctx?.state }; ctx.state = { ...ctx?.state };
@@ -515,7 +529,7 @@ export class QueryRouter {
* @param ctx * @param ctx
* @returns * @returns
*/ */
async call(message: { id?: string; path?: string; key?: string; payload?: any }, ctx?: RouteContext & { [key: string]: any }) { async call(message: { id?: string; path?: string; key?: string; payload?: any }, ctx?: RouteContext<T> & { [key: string]: any }) {
let path = message.path; let path = message.path;
let key = message.key; let key = message.key;
// 优先 path + key // 优先 path + key
@@ -556,7 +570,7 @@ export class QueryRouter {
* @param ctx * @param ctx
* @returns * @returns
*/ */
async run(message: { id?: string; path?: string; key?: string; payload?: any }, ctx?: RouteContext & { [key: string]: any }) { async run(message: { id?: string; path?: string; key?: string; payload?: any }, ctx?: RouteContext<T> & { [key: string]: any }) {
const res = await this.call(message, { ...this.context, ...ctx }); const res = await this.call(message, { ...this.context, ...ctx });
return { return {
code: res.code, code: res.code,
@@ -570,7 +584,7 @@ export class QueryRouter {
* @param ctx * @param ctx
*/ */
setContext(ctx: RouteContext) { setContext(ctx: RouteContext) {
this.context = ctx; this.context = ctx as RouteContext<T>;
} }
getList(filter?: (route: Route) => boolean): RouteInfo[] { getList(filter?: (route: Route) => boolean): RouteInfo[] {
return this.routes.filter(filter || (() => true)).map((r) => { return this.routes.filter(filter || (() => true)).map((r) => {
@@ -581,11 +595,11 @@ export class QueryRouter {
/** /**
* 获取handle函数, 这里会去执行parse函数 * 获取handle函数, 这里会去执行parse函数
*/ */
getHandle<T = any>(router: QueryRouter, wrapperFn?: HandleFn<T>, ctx?: RouteContext) { getHandle<T = any>(router: QueryRouter, wrapperFn?: HandleFn, ctx?: RouteContext) {
return async (msg: { id?: string; path?: string; key?: string;[key: string]: any }, handleContext?: RouteContext) => { return async (msg: { id?: string; path?: string; key?: string;[key: string]: any }, handleContext?: RouteContext<T>) => {
try { try {
const context = { ...ctx, ...handleContext }; const context = { ...ctx, ...handleContext };
const res = await router.call(msg, context); const res = await router.call(msg, context) as any;
if (wrapperFn) { if (wrapperFn) {
res.data = res.body; res.data = res.body;
return wrapperFn(res, context); return wrapperFn(res, context);
@@ -610,9 +624,8 @@ export class QueryRouter {
importRouter(router: QueryRouter) { importRouter(router: QueryRouter) {
this.importRoutes(router.routes); this.importRoutes(router.routes);
} }
throw(code?: number | string, message?: string, tips?: string): void;
throw(...args: any[]) { throw(...args: any[]) {
throw new CustomError(...args); CustomError.throw(...args);
} }
hasRoute(path: string, key: string = '') { hasRoute(path: string, key: string = '') {
return this.routes.find((r) => r.path === path && r.key === key); return this.routes.find((r) => r.path === path && r.key === key);
@@ -639,7 +652,7 @@ export class QueryRouter {
description: '列出当前应用下的所有的路由信息', description: '列出当前应用下的所有的路由信息',
middleware: opts?.middleware || [], middleware: opts?.middleware || [],
run: async (ctx: RouteContext) => { run: async (ctx: RouteContext) => {
const tokenUser = ctx.state.tokenUser; const tokenUser = ctx.state as unknown as { tokenUser?: any };
let isUser = !!tokenUser; let isUser = !!tokenUser;
const list = this.getList(opts?.filter).filter((item) => { const list = this.getList(opts?.filter).filter((item) => {
if (item.id === 'auth' || item.id === 'auth-can' || item.id === 'check-auth-admin' || item.id === 'auth-admin') { if (item.id === 'auth' || item.id === 'auth-can' || item.id === 'check-auth-admin' || item.id === 'auth-admin') {
@@ -685,10 +698,11 @@ export class QueryRouter {
fromJSONSchema = fromJSONSchema; fromJSONSchema = fromJSONSchema;
} }
type QueryRouterServerOpts = { type QueryRouterServerOpts<C extends SimpleObject = SimpleObject> = {
handleFn?: HandleFn; handleFn?: HandleFn;
context?: RouteContext; context?: RouteContext<C>;
appId?: string; appId?: string;
initHandle?: boolean;
}; };
interface HandleFn<T = any> { interface HandleFn<T = any> {
(msg: { path: string;[key: string]: any }, ctx?: any): { code: string; data?: any; message?: string;[key: string]: any }; (msg: { path: string;[key: string]: any }, ctx?: any): { code: string; data?: any; message?: string;[key: string]: any };
@@ -697,13 +711,18 @@ interface HandleFn<T = any> {
/** /**
* QueryRouterServer * QueryRouterServer
* @description 移除server相关的功能只保留router相关的功能和http.createServer不相关独立 * @description 移除server相关的功能只保留router相关的功能和http.createServer不相关独立
* @template C 自定义 RouteContext 类型
*/ */
export class QueryRouterServer extends QueryRouter { export class QueryRouterServer<C extends SimpleObject = SimpleObject> extends QueryRouter<C> {
declare appId: string; declare appId: string;
handle: any; handle: any;
constructor(opts?: QueryRouterServerOpts) { declare context: RouteContext<C>;
constructor(opts?: QueryRouterServerOpts<C>) {
super(); super();
this.handle = this.getHandle(this, opts?.handleFn, opts?.context); const initHandle = opts?.initHandle ?? true;
if (initHandle || opts?.handleFn) {
this.handle = this.getHandle(this, opts?.handleFn, opts?.context);
}
this.setContext({ needSerialize: false, ...opts?.context }); this.setContext({ needSerialize: false, ...opts?.context });
if (opts?.appId) { if (opts?.appId) {
this.appId = opts.appId; this.appId = opts.appId;
@@ -718,37 +737,28 @@ export class QueryRouterServer extends QueryRouter {
this.add(route, opts); this.add(route, opts);
} }
Route = Route; Route = Route;
route(opts: RouteOpts): Route<Required<RouteContext>>; route<M extends SimpleObject = SimpleObject>(opts: RouteOpts & { metadata?: M }): Route<M, Required<RouteContext<C>>>;
route(path: string, key?: string): Route<Required<RouteContext>>; route<M extends SimpleObject = SimpleObject>(path: string, opts?: RouteOpts & { metadata?: M }): Route<M, Required<RouteContext<C>>>;
route(path: string, opts?: RouteOpts): Route<Required<RouteContext>>; route<M extends SimpleObject = SimpleObject>(path: string, key?: string): Route<M, Required<RouteContext<C>>>;
route(path: string, key?: string, opts?: RouteOpts): Route<Required<RouteContext>>; route<M extends SimpleObject = SimpleObject>(path: string, key?: string, opts?: RouteOpts & { metadata?: M }): Route<M, Required<RouteContext<C>>>;
route(...args: any[]) { route<M extends SimpleObject = SimpleObject>(...args: any[]) {
const [path, key, opts] = args; const [path, key, opts] = args;
if (typeof path === 'object') { if (typeof path === 'object') {
return new Route(path.path, path.key, path); return new Route<M, Required<RouteContext<C>>>(path.path, path.key, path);
} }
if (typeof path === 'string') { if (typeof path === 'string') {
if (opts) { if (opts) {
return new Route(path, key, opts); return new Route<M, Required<RouteContext<C>>>(path, key, opts);
} }
if (key && typeof key === 'object') { if (key && typeof key === 'object') {
return new Route(path, key?.key || '', key); return new Route<M, Required<RouteContext<C>>>(path, key?.key || '', key);
} }
return new Route(path, key); return new Route<M, Required<RouteContext<C>>>(path, key);
} }
return new Route(path, key, opts); return new Route<M, Required<RouteContext<C>>>(path, key, opts);
} }
prompt(description: string): Route<Required<RouteContext>>; prompt(description: string) {
prompt(description: Function): Route<Required<RouteContext>>; return new Route(undefined, undefined, { description });
prompt(...args: any[]) {
const [desc] = args;
let description = ''
if (typeof desc === 'string') {
description = desc;
} else if (typeof desc === 'function') {
description = desc() || ''; // 如果是Promise需要addTo App之前就要获取应有的函数了。
}
return new Route('', '', { description });
} }
/** /**
@@ -756,15 +766,90 @@ export class QueryRouterServer extends QueryRouter {
* @param param0 * @param param0
* @returns * @returns
*/ */
async run(msg: { id?: string; path?: string; key?: string; payload?: any }, ctx?: RouteContext & { [key: string]: any }) { async run(msg: { id?: string; path?: string; key?: string; payload?: any, token?: string, data?: any }, ctx?: Partial<RouteContext<C>>) {
const handle = this.handle; const handle = this.handle;
if (handle) { if (handle) {
return handle(msg, ctx); return handle(msg, ctx);
} }
return super.run(msg, ctx); return super.run(msg, ctx as RouteContext<C>);
}
async runAction<T extends { id?: string; path?: string; key?: string; metadata?: { args?: any } } = {}>(
api: T,
payload: RunActionPayload<T>,
ctx?: RouteContext<C>
) {
const { path, key, id } = api as any;
return this.run({ path, key, id, payload }, ctx);
}
/**
* 创建认证相关的中间件,默认是 auth, auth-admin, auth-can 三个中间件
* @param fun 认证函数,接收 RouteContext 和认证类型
*/
async createAuth(fun: (ctx: RouteContext<C>, type?: 'auth' | 'auth-admin' | 'auth-can') => any) {
this.route({
path: 'auth',
key: 'auth',
id: 'auth',
description: 'token验证',
}).define(async (ctx) => {
if (fun) {
await fun(ctx, 'auth');
}
}).addTo(this, { overwrite: false });
this.route({
path: 'auth-admin',
key: 'auth-admin',
id: 'auth-admin',
description: 'admin token验证',
middleware: ['auth']
}).define(async (ctx) => {
if (fun) {
await fun(ctx, 'auth-admin');
}
}).addTo(this, { overwrite: false });
this.route({
path: 'auth-can',
key: 'auth-can',
id: 'auth-can',
description: '权限验证'
}).define(async (ctx) => {
if (fun) {
await fun(ctx, 'auth-can');
}
}).addTo(this, { overwrite: false });
} }
} }
export class Mini extends QueryRouterServer { } export class Mini extends QueryRouterServer { }
/** JSON Schema 基本类型映射到 TypeScript 类型 */
type JsonSchemaTypeToTS<T> =
T extends { type: "string" } ? string :
T extends { type: "boolean" } ? boolean :
T extends { type: "number" } ? number :
T extends { type: "integer" } ? number :
T extends { type: "object" } ? object :
T extends { type: "array" } ? any[] :
any;
/** 将 args shapekey -> JSON Schema 类型)转换为 payload 类型,支持 optional: true 的字段为可选 */
type ArgsShapeToPayload<T> =
{ [K in keyof T as T[K] extends { optional: true } ? never : K]: JsonSchemaTypeToTS<T[K]> } &
{ [K in keyof T as T[K] extends { optional: true } ? K : never]?: JsonSchemaTypeToTS<T[K]> };
/** 处理两种 args 格式:完整 JSON Schema含 properties或简单 key->type 映射 */
type ArgsToPayload<T> =
T extends { type: "object"; properties: infer P }
? ArgsShapeToPayload<P>
: ArgsShapeToPayload<T>;
/** 从 API 定义中提取 metadata.args */
type ExtractArgs<T> =
T extends { metadata: { args: infer A } } ? A : {};
/** runAction 第二个参数的类型,根据第一个参数的 metadata.args 推断 */
export type RunActionPayload<T> = ArgsToPayload<ExtractArgs<T>>;

View File

@@ -4,6 +4,7 @@ import * as cookie from './cookie.ts';
import { ServerType, Listener, OnListener, ServerOpts, OnWebSocketOptions, OnWebSocketFn, WebSocketListenerFun, ListenerFun, HttpListenerFun, WS } from './server-type.ts'; import { ServerType, Listener, OnListener, ServerOpts, OnWebSocketOptions, OnWebSocketFn, WebSocketListenerFun, ListenerFun, HttpListenerFun, WS } from './server-type.ts';
import { parseIfJson } from '../utils/parse.ts'; import { parseIfJson } from '../utils/parse.ts';
import { EventEmitter } from 'eventemitter3'; import { EventEmitter } from 'eventemitter3';
import { CustomError } from '../result/error.ts';
type CookieFn = (name: string, value: string, options?: cookie.SerializeOptions, end?: boolean) => void; type CookieFn = (name: string, value: string, options?: cookie.SerializeOptions, end?: boolean) => void;
export type HandleCtx = { export type HandleCtx = {
@@ -165,8 +166,13 @@ export class ServerBase implements ServerType {
res.end(JSON.stringify(end)); res.end(JSON.stringify(end));
} }
} catch (e) { } catch (e) {
console.error(e);
res.setHeader('Content-Type', 'application/json; charset=utf-8'); res.setHeader('Content-Type', 'application/json; charset=utf-8');
if (CustomError.isError(e)) {
const parsedError = CustomError.parseError(e);
res.end(JSON.stringify(parsedError));
return;
}
console.error(e);
if (e.code && typeof e.code === 'number') { if (e.code && typeof e.code === 'number') {
res.end(resultError(e.message || `Router Server error`, e.code)); res.end(resultError(e.message || `Router Server error`, e.code));
} else { } else {
@@ -278,7 +284,7 @@ export class ServerBase implements ServerType {
* @param ws * @param ws
*/ */
async onWsClose(ws: WS) { async onWsClose(ws: WS) {
const id = ws?.data?.id || ''; const id = ws?.wsId || '';
if (id) { if (id) {
this.emitter.emit('close--' + id, { type: 'close', ws, id }); this.emitter.emit('close--' + id, { type: 'close', ws, id });
setTimeout(() => { setTimeout(() => {
@@ -292,4 +298,7 @@ export class ServerBase implements ServerType {
if (this.showConnected) if (this.showConnected)
ws.send(JSON.stringify({ type: 'connected' })); ws.send(JSON.stringify({ type: 'connected' }));
} }
createId() {
return Math.random().toString(36).substring(2, 15);
}
} }

View File

@@ -4,7 +4,7 @@
* @tags bun, server, websocket, http * @tags bun, server, websocket, http
* @createdAt 2025-12-20 * @createdAt 2025-12-20
*/ */
import { ServerType, type ServerOpts, type Cors, RouterRes, RouterReq } from './server-type.ts'; import { ServerType, type ServerOpts, type Cors, RouterRes, RouterReq, WS } from './server-type.ts';
import { ServerBase } from './server-base.ts'; import { ServerBase } from './server-base.ts';
export class BunServer extends ServerBase implements ServerType { export class BunServer extends ServerBase implements ServerType {
@@ -264,10 +264,14 @@ export class BunServer extends ServerBase implements ServerType {
open: (ws: any) => { open: (ws: any) => {
this.sendConnected(ws); this.sendConnected(ws);
}, },
message: async (ws: any, message: string | Buffer) => { message: async (bunWs: any, message: string | Buffer) => {
const ws = bunWs as WS;
const pathname = ws.data.pathname || ''; const pathname = ws.data.pathname || '';
const token = ws.data.token || ''; const token = ws.data.token || '';
const id = ws.data.id || ''; const id = ws.data.id || '';
if (!ws.wsId) {
ws.wsId = this.createId();
}
await this.onWebSocket({ ws, message, pathname, token, id }); await this.onWebSocket({ ws, message, pathname, token, id });
}, },
close: (ws: any) => { close: (ws: any) => {

View File

@@ -49,16 +49,33 @@ export type OnWebSocketOptions<T = {}> = {
message: string | Buffer; message: string | Buffer;
pathname: string, pathname: string,
token?: string, token?: string,
/** data 的id提取出来 */
id?: string, id?: string,
} }
export type OnWebSocketFn = (options: OnWebSocketOptions) => Promise<void> | void; export type OnWebSocketFn = (options: OnWebSocketOptions) => Promise<void> | void;
export type WS<T = {}> = { export type WS<T = {}> = {
send: (data: any) => void; send: (data: any) => void;
close: (code?: number, reason?: string) => void; close: (code?: number, reason?: string) => void;
/**
* ws 自己生成的一个id主要是为了区分不同的ws连接方便在onWebSocket中使用
*/
wsId?: string;
data?: { data?: {
/**
* ws连接时的url包含pathname和searchParams
*/
url: URL; url: URL;
/**
* ws连接时的pathname
*/
pathname: string; pathname: string;
/**
* ws连接时的url中的token参数
*/
token?: string; token?: string;
/**
* ws连接时的url中的id参数.
*/
id?: string; id?: string;
/** /**
* 鉴权后的获取的信息 * 鉴权后的获取的信息

View File

@@ -56,6 +56,11 @@ export class WsServerBase {
token, token,
id, id,
} }
// @ts-ignore
if (!ws.wsId) {
// @ts-ignore
ws.wsId = this.createId();
}
ws.on('message', async (message: string | Buffer) => { ws.on('message', async (message: string | Buffer) => {
await this.server.onWebSocket({ ws, message, pathname, token, id }); await this.server.onWebSocket({ ws, message, pathname, token, id });
}); });
@@ -66,7 +71,9 @@ export class WsServerBase {
this.server.onWsClose(ws); this.server.onWsClose(ws);
}); });
}); });
}
createId() {
return Math.random().toString(36).substring(2, 15);
} }
} }
// TODO: ws handle and path and routerContext // TODO: ws handle and path and routerContext

View File

@@ -1,13 +1,69 @@
import { App } from '../app.ts' import { App, AppRouteContext } from "@/app.ts";
import { QueryRouterServer, RouteContext } from "@/app.ts";
const app = new App<{ f: string }>(); import z from "zod";
const route: RouteContext<{ customField: string }> = {} as any;
route.customField
const appRoute: AppRouteContext<{ customField: string }> = {} as any;
appRoute.customField
// 示例 1: 使用 App它会自动使用 AppRouteContext<U> 作为 ctx 类型
const app = new App<{
customField: string;
}>();
app.context.customField = "customValue"; // 可以在 app.context 中添加自定义字段,这些字段会在 ctx 中可用
app.route({ app.route({
path: 't', path: 'test1',
run: async (ctx) => { metadata: {
// ctx.r args: {
ctx.app; name: z.string(),
} }
},
}).define(async (ctx) => { }).define(async (ctx) => {
ctx.f = 'hello'; // ctx.app 是 App 类型
}).addTo(app); const appName = ctx.app.appId;
// ctx.customField 来自自定义泛型参数
const customField: string | undefined = ctx.customField;
// ctx.req 和 ctx.res 来自 HandleCtx
const req = ctx.req;
const res = ctx.res;
// ctx.args 从 metadata.args 推断
const name: string = ctx.args.name;
const name2: string = ctx.query.name;
ctx.body = `Hello ${name}!`;
});
// 示例 2: 使用 QueryRouterServer它可以传递自定义的 Context 类型
const router = new QueryRouterServer<{
routerContextField: number;
}>();
router.context.routerContextField
router.route({
path: 'router-test',
metadata: {
args: {
value: z.number(),
}
},
}).define(async (ctx) => {
const value: number = ctx.args.value;
const field: number | undefined = ctx.routerContextField;
ctx.body = value;
});
// 示例 3: 不带泛型参数的 QueryRouterServer使用默认的 RouteContext
const defaultRouter = new QueryRouterServer();
defaultRouter.route({
path: 'default-test',
metadata: {
args: {
id: z.string(),
}
},
}).define(async (ctx) => {
const id: string = ctx.args.id;
ctx.body = id;
});
export { app, router, defaultRouter };

View File

@@ -14,4 +14,4 @@ app.prompt('获取天气的工具。\n参数是 city 为对应的城市').define
export const chat = new RouterChat({ router: app.router }); export const chat = new RouterChat({ router: app.router });
console.log(chat.chat()); console.log(chat.getChatPrompt());

15
src/test/route-ts.ts Normal file
View File

@@ -0,0 +1,15 @@
import { QueryRouterServer } from "@/route.ts";
import z from "zod";
const router = new QueryRouterServer()
router.route({
metadata: {
args: {
a: z.string(),
}
},
}).define(async (ctx) => {
const argA: string = ctx.args.a;
ctx.body = '1';
})

87
src/test/run-schema.ts Normal file
View File

@@ -0,0 +1,87 @@
import z from "zod";
import { App } from "../index.ts";
const app = new App();
const api = {
"app_domain_manager": {
/**
* 获取域名信息可以通过id或者domain进行查询
*
* @param data - Request parameters
* @param data.data - {object}
*/
"get": {
"path": "app_domain_manager",
"key": "get",
"description": "获取域名信息可以通过id或者domain进行查询",
"metadata": {
"args": {
"data": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"id": {
"type": "string"
},
"domain": {
"type": "string"
}
},
"additionalProperties": false,
"required": ["id",]
}
},
"viewItem": {
"api": {
"url": "/api/router"
},
"type": "api",
"title": "路由"
},
"url": "/api/router",
"source": "query-proxy-api"
}
},
"delete": {
"path": "app_domain_manager",
"key": "delete",
"description": "删除域名",
"metadata": {
"args": {
"domainId": {
"type": "string",
"optional": true
}
}
}
}
},
"user_manager": {
"getUser": {
"path": "user_manager",
"key": "getUser",
"description": "获取用户信息",
"metadata": {
"args": {
"userId": {
"type": "string"
},
"includeProfile": {
"type": "boolean"
}
}
}
}
}
} as const;
type API = typeof api;
// 类型推断生效payload 根据 metadata.args 自动推断
// get 的 args.data 是 type:"object",所以 payload 需要 { data: object }
app.runAction(api.app_domain_manager.get, { data: { id: "1" } })
// delete 的 args 是 { domainId: { type: "string" } },所以 payload 需要 { domainId: string }
app.runAction(api.app_domain_manager.delete, { domainId: "d1" })
// getUser 的 args 是 { userId: string, includeProfile: boolean }
app.runAction(api.user_manager.getUser, { userId: "u1", includeProfile: true })

View File

@@ -1,8 +1,17 @@
import { customAlphabet } from 'nanoid'; import { customAlphabet } from 'nanoid';
import Md5 from 'crypto-js/md5.js';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz', 16); const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz', 16);
export const randomId = (length: number = 8, affix: string = '') => { export const randomId = (length: number = 8, affix: string = '') => {
return affix + nanoid(length); return affix + nanoid(length);
} }
export { nanoid }; export { nanoid };
/**
* 基于 MD5 的确定性 ID 生成 (同步版本)
* 浏览器和 Node.js 都支持
* 相同的 pathKey 永远返回相同的 16 位 ID
*/
export const hashIdMd5Sync = (pathKey: string): string => {
return Md5(pathKey).toString().substring(0, 16);
}

View File

@@ -0,0 +1,452 @@
# HTTP Server 设计文档
## 概述
HTTP 服务器层负责接收外部 HTTP 请求,将其归一化后传递给 Router 层处理。所有请求统一入口为 `/api/router`
## 请求入口
```
POST /api/router?path=demo&key=01
GET /api/router?path=demo&key=01
```
| 配置项 | 默认值 | 说明 |
|--------|--------|------|
| path | /api/router | 请求入口路径 |
## 请求参数归一化
HTTP 请求的参数来源于两个部分,最终合并为一个 Message 对象:
### 1. URL Query (searchParams)
```typescript
// GET /api/router?path=demo&key=01&token=xxx
{
path: "demo",
key: "01",
token: "xxx"
}
```
### 2. POST Body
```typescript
// POST /api/router
// Body: { "name": "test", "value": 123 }
{
name: "test",
value: 123
}
```
### 3. 合并规则
最终的 Message = Query + Body后者覆盖前者
```typescript
// GET /api/router?path=demo&key=01
// POST Body: { "key": "02", "extra": "data" }
{
path: "demo",
key: "02", // body 覆盖 query
extra: "data"
}
```
### 4. payload 参数
如果 query 或 body 中有 `payload` 字段且为 JSON 字符串,会自动解析为对象:
```typescript
// GET /api/router?path=demo&key=01&payload={"id":123}
{
path: "demo",
key: "01",
payload: { id: 123 } // 自动解析
}
```
### 5. 认证信息
| 来源 | 字段名 | 说明 |
|------|--------|------|
| Authorization header | token | Bearer token |
| Cookie | token | cookie 中的 token |
| Cookie | cookies | 完整 cookie 对象 |
## 路由匹配
### 方式一path + key
```bash
# 访问 path=demo, key=01 的路由
POST /api/router?path=demo&key=01
```
### 方式二id
```bash
# 直接通过路由 ID 访问
POST /api/router?id=abc123
```
## 内置路由
所有内置路由使用统一的访问方式:
| 路由 | 访问方式 | 说明 |
|------|----------|------|
| router.list | POST /api/router?path=router&key=list | 获取路由列表 |
### router.list
获取当前应用所有路由列表。
**请求:**
```bash
POST /api/router?path=router&key=list
```
**响应:**
```json
{
"code": 200,
"data": {
"list": [
{
"id": "router$#$list",
"path": "router",
"key": "list",
"description": "列出当前应用下的所有的路由信息",
"middleware": [],
"metadata": {}
}
],
"isUser": false
},
"message": "success"
}
```
## Go 设计
```go
package server
import (
"encoding/json"
"net/http"
)
// Message HTTP 请求归一化后的消息
type Message struct {
Path string // 路由 path
Key string // 路由 key
ID string // 路由 ID (优先于 path+key)
Token string // 认证 token
Payload map[string]interface{} // payload 参数
Query url.Values // 原始 query
Cookies map[string]string // cookie
// 其他参数
Extra map[string]interface{}
}
// HandleServer 解析 HTTP 请求
func HandleServer(req *http.Request) (*Message, error) {
method := req.Method
if method != "GET" && method != "POST" {
return nil, fmt.Errorf("method not allowed")
}
// 解析 query
query := req.URL.Query()
// 获取 token
token := req.Header.Get("Authorization")
if token != "" {
token = strings.TrimPrefix(token, "Bearer ")
}
if token == "" {
if cookie, err := req.Cookie("token"); err == nil {
token = cookie.Value
}
}
// 解析 body (POST)
var body map[string]interface{}
if method == "POST" {
if err := json.NewDecoder(req.Body).Decode(&body); err != nil {
body = make(map[string]interface{})
}
}
// 合并 query 和 body
msg := &Message{
Token: token,
Query: query,
Cookies: parseCookies(req.Cookies()),
}
// query 参数
if v := query.Get("path"); v != "" {
msg.Path = v
}
if v := query.Get("key"); v != "" {
msg.Key = v
}
if v := query.Get("id"); v != "" {
msg.ID = v
}
if v := query.Get("payload"); v != "" {
if err := json.Unmarshal([]byte(v), &msg.Payload); err == nil {
// payload 解析成功
}
}
// body 参数覆盖 query
for k, v := range body {
msg.Extra[k] = v
switch k {
case "path":
msg.Path = v.(string)
case "key":
msg.Key = v.(string)
case "id":
msg.ID = v.(string)
case "payload":
msg.Payload = v.(map[string]interface{})
}
}
return msg, nil
}
// RouterHandler 创建 HTTP 处理函数
func RouterHandler(router *QueryRouter) http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) {
// CORS
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST")
w.Header().Set("Content-Type", "application/json")
if req.Method == "OPTIONS" {
w.WriteHeader(http.StatusOK)
return
}
msg, err := HandleServer(req)
if err != nil {
json.NewEncoder(w).Encode(Result{Code: 400, Message: err.Error()})
return
}
// 调用 router.run
result, err := router.Run(*msg, nil)
if err != nil {
json.NewEncoder(w).Encode(Result{Code: 500, Message: err.Error()})
return
}
json.NewEncoder(w).Encode(result)
}
}
// Server HTTP 服务器
type Server struct {
Router *QueryRouter
Path string
Handlers []http.HandlerFunc
}
func (s *Server) Listen(addr string) error {
mux := http.NewServeMux()
// 自定义处理器
for _, h := range s.Handlers {
mux.HandleFunc(s.Path, h)
}
// 路由处理器
mux.HandleFunc(s.Path, RouterHandler(s.Router))
return http.ListenAndServe(addr, mux)
}
```
## Rust 设计
```rust
use actix_web::{web, App, HttpServer, HttpRequest, HttpResponse, Responder};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
// Message HTTP 请求归一化后的消息
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Message {
pub path: Option<String>,
pub key: Option<String>,
pub id: Option<String>,
pub token: Option<String>,
pub payload: Option<HashMap<String, Value>>,
#[serde(flatten)]
pub extra: HashMap<String, Value>,
}
// Result 调用结果
#[derive(Debug, Serialize)]
pub struct Result {
pub code: i32,
pub data: Option<Value>,
pub message: Option<String>,
}
// 解析 HTTP 请求
pub async fn handle_server(req: HttpRequest, body: web::Bytes) -> Result<Message, String> {
let method = req.method().as_str();
if method != "GET" && method != "POST" {
return Err("method not allowed".to_string());
}
// 获取 query 参数
let query: web::Query<HashMap<String, String>> = web::Query::clone(&req);
// 获取 token
let mut token = None;
if let Some(auth) = req.headers().get("authorization") {
if let Ok(s) = auth.to_str() {
token = Some(s.trim_start_matches("Bearer ").to_string());
}
}
if token.is_none() {
if let Some(cookie) = req.cookie("token") {
token = Some(cookie.value().to_string());
}
}
// 解析 body (POST)
let mut body_map = HashMap::new();
if method == "POST" {
if let Ok(v) = serde_json::from_slice::<Value>(&body) {
if let Some(obj) = v.as_object() {
for (k, val) in obj {
body_map.insert(k.clone(), val.clone());
}
}
}
}
// 构建 Message
let mut msg = Message {
path: query.get("path").cloned(),
key: query.get("key").cloned(),
id: query.get("id").cloned(),
token,
payload: None,
extra: HashMap::new(),
};
// 处理 payload
if let Some(p) = query.get("payload") {
if let Ok(v) = serde_json::from_str::<Value>(p) {
msg.payload = v.as_object().map(|m| m.clone());
}
}
// body 覆盖 query
for (k, v) in body_map {
match k.as_str() {
"path" => msg.path = v.as_str().map(|s| s.to_string()),
"key" => msg.key = v.as_str().map(|s| s.to_string()),
"id" => msg.id = v.as_str().map(|s| s.to_string()),
"payload" => msg.payload = v.as_object().map(|m| m.clone()),
_ => msg.extra.insert(k, v),
}
}
Ok(msg)
}
// HTTP 处理函数
async fn router_handler(
req: HttpRequest,
body: web::Bytes,
router: web::Data<QueryRouter>,
) -> impl Responder {
let msg = match handle_server(req, body).await {
Ok(m) => m,
Err(e) => return HttpResponse::BadRequest().json(Result {
code: 400,
data: None,
message: Some(e),
}),
};
// 调用 router.run
match router.run(msg, None).await {
Ok(result) => HttpResponse::Ok().json(result),
Err(e) => HttpResponse::InternalServerError().json(Result {
code: 500,
data: None,
message: Some(e.to_string()),
}),
}
}
// Server HTTP 服务器
pub struct Server {
pub router: QueryRouter,
pub path: String,
}
impl Server {
pub async fn listen(self, addr: &str) -> std::io::Result<()> {
let router = web::Data::new(self.router);
HttpServer::new(move || {
App::new()
.app_data(router.clone())
.route(&self.path, web::post().to(router_handler))
.route(&self.path, web::get().to(router_handler))
})
.bind(addr)?
.run()
.await
}
}
```
## 请求示例
### 基础请求
```bash
# 访问 demo/01 路由
curl -X POST "http://localhost:4002/api/router?path=demo&key=01"
# 带 body
curl -X POST "http://localhost:4002/api/router?path=demo&key=01" \
-H "Content-Type: application/json" \
-d '{"name":"test","value":123}'
```
### 获取路由列表
```bash
curl -X POST "http://localhost:4002/api/router?path=router&key=list"
```
### 带认证
```bash
# 通过 Header
curl -X POST "http://localhost:4002/api/router?path=demo&key=01" \
-H "Authorization: Bearer your-token"
# 通过 Cookie
curl -X POST "http://localhost:4002/api/router?path=demo&key=01" \
-b "token=your-token"
```

378
system_design/router.md Normal file
View File

@@ -0,0 +1,378 @@
# Router 系统设计文档
## 概述
轻量级路由框架,支持链式路由、中间件模式、统一上下文。适用于构建 API 服务支持跨语言实现Go、Rust 等)。
## 核心组件
### Route
| 字段 | 类型 | 说明 |
|------|------|------|
| path | string | 一级路径 |
| key | string | 二级路径 |
| id | string | 唯一标识 |
| run | Handler | 业务处理函数 |
| nextRoute | NextRoute? | 下一个路由 |
| middleware | string[] | 中间件 ID 列表 |
| metadata | T | 元数据/参数 schema |
| type | string | 类型route / middleware |
| isDebug | bool | 是否开启调试 |
### NextRoute
| 字段 | 类型 | 说明 |
|------|------|------|
| id | string? | 路由 ID |
| path | string? | 一级路径 |
| key | string? | 二级路径 |
### RouteContext
| 字段 | 类型 | 说明 |
|------|------|------|
| appId | string? | 应用 ID |
| query | object | URL 参数和 payload 合并结果 |
| args | object | 同 query |
| body | any | 响应 body |
| code | number | 响应状态码 |
| message | string | 响应消息 |
| state | object | 状态传递 |
| currentId | string? | 当前路由 ID |
| currentPath | string? | 当前路径 |
| currentKey | string? | 当前 key |
| currentRoute | Route? | 当前路由对象 |
| progress | [string, string][] | 路由执行路径 |
| nextQuery | object | 传递给下一个路由的参数 |
| end | boolean | 是否提前结束 |
| app | QueryRouter? | 路由实例引用 |
| error | any | 错误信息 |
| call | function | 调用其他路由(返回完整上下文) |
| run | function | 调用其他路由(返回简化结果) |
| throw | function | 抛出错误 |
| needSerialize | boolean | 是否需要序列化 |
### QueryRouter
| 方法 | 说明 |
|------|------|
| add(route, opts?) | 添加路由 |
| remove(route) | 按 path/key 移除路由 |
| removeById(id) | 按 ID 移除路由 |
| runRoute(path, key, ctx) | 执行单个路由 |
| parse(message, ctx) | 入口解析,返回完整上下文 |
| call(message, ctx) | 调用路由,返回完整上下文 |
| run(message, ctx) | 调用路由,返回简化结果 {code, data, message} |
| getHandle() | 获取 HTTP 处理函数 |
| setContext(ctx) | 设置默认上下文 |
| getList(filter?) | 获取路由列表 |
| hasRoute(path, key) | 检查路由是否存在 |
| findRoute(opts) | 查找路由 |
| exportRoutes() | 导出所有路由 |
| importRoutes(routes) | 批量导入路由 |
| createRouteList(opts) | 创建内置的路由列表功能 |
### QueryRouterServer
继承 QueryRouter新增
| 字段 | 类型 | 说明 |
|------|------|------|
| appId | string | 应用 ID |
| handle | function | HTTP 处理函数 |
| 方法 | 说明 |
|------|------|
| setHandle(wrapperFn, ctx) | 设置处理函数 |
| route(path, key?, opts?) | 工厂方法创建路由 |
## Go 设计
```go
package router
// Route 路由单元
type Route struct {
Path string
Key string
ID string
Run func(ctx *RouteContext) (*RouteContext, error)
NextRoute *NextRoute
Middleware []string
Metadata map[string]interface{}
Type string
IsDebug bool
}
// NextRoute 下一个路由
type NextRoute struct {
ID string
Path string
Key string
}
// RouteContext 请求上下文
type RouteContext struct {
AppID string
Query map[string]interface{}
Args map[string]interface{}
Body interface{}
Code int
Message string
State map[string]interface{}
CurrentID string
CurrentPath string
CurrentKey string
CurrentRoute *Route
Progress [][2]string
NextQuery map[string]interface{}
End bool
App *QueryRouter
Error error
NeedSerialize bool
// Methods
Call func(msg interface{}, ctx *RouteContext) (*RouteContext, error)
Run func(msg interface{}, ctx *RouteContext) (interface{}, error)
Throw func(err interface{})
}
// Message 调用消息
type Message struct {
ID string
Path string
Key string
Payload map[string]interface{}
}
// Result 调用结果
type Result struct {
Code int
Data interface{}
Message string
}
// AddOpts 添加选项
type AddOpts struct {
Overwrite bool
}
// QueryRouter 路由管理器
type QueryRouter struct {
Routes []*Route
MaxNextRoute int
Context *RouteContext
}
func NewQueryRouter() *QueryRouter
func (r *QueryRouter) Add(route *Route, opts *AddOpts)
func (r *QueryRouter) Remove(path, key string)
func (r *QueryRouter) RemoveByID(id string)
func (r *QueryRouter) RunRoute(path, key string, ctx *RouteContext) (*RouteContext, error)
func (r *QueryRouter) Parse(msg Message, ctx *RouteContext) (*RouteContext, error)
func (r *QueryRouter) Call(msg Message, ctx *RouteContext) (*RouteContext, error)
func (r *QueryRouter) Run(msg Message, ctx *RouteContext) (Result, error)
func (r *QueryRouter) GetHandle() func(msg interface{}) Result
func (r *QueryRouter) SetContext(ctx *RouteContext)
func (r *QueryRouter) GetList() []Route
func (r *QueryRouter) HasRoute(path, key string) bool
func (r *QueryRouter) FindRoute(opts FindOpts) *Route
// QueryRouterServer 服务端
type QueryRouterServer struct {
QueryRouter
AppID string
Handle func(msg interface{}) Result
}
type ServerOpts struct {
HandleFn func(msg interface{}, ctx interface{}) Result
Context *RouteContext
AppID string
}
func NewQueryRouterServer(opts *ServerOpts) *QueryRouterServer
func (s *QueryRouterServer) SetHandle(wrapperFn func(msg interface{}, ctx interface{}) Result, ctx *RouteContext)
func (s *QueryRouterServer) Route(path string, key ...string) *Route
```
## Rust 设计
```rust
use std::collections::HashMap;
use std::future::Future;
use std::pin::Pin;
// Route 路由单元
pub struct Route<M = Value> {
pub path: String,
pub key: String,
pub id: String,
pub run: Option<Box<dyn Fn(RouteContext) -> Pin<Box<dyn Future<Output = Result<RouteContext>>>> + Send>>,
pub next_route: Option<NextRoute>,
pub middleware: Vec<String>,
pub metadata: M,
pub route_type: String,
pub is_debug: bool,
}
// NextRoute 下一个路由
#[derive(Clone)]
pub struct NextRoute {
pub id: Option<String>,
pub path: Option<String>,
pub key: Option<String>,
}
// RouteContext 请求上下文
#[derive(Clone)]
pub struct RouteContext {
pub app_id: Option<String>,
pub query: HashMap<String, Value>,
pub args: HashMap<String, Value>,
pub body: Option<Value>,
pub code: Option<i32>,
pub message: Option<String>,
pub state: HashMap<String, Value>,
pub current_id: Option<String>,
pub current_path: Option<String>,
pub current_key: Option<String>,
pub current_route: Option<Box<Route>>,
pub progress: Vec<(String, String)>,
pub next_query: HashMap<String, Value>,
pub end: bool,
pub app: Option<Box<QueryRouter>>,
pub error: Option<Value>,
pub need_serialize: bool,
}
// Message 调用消息
#[derive(Clone)]
pub struct Message {
pub id: Option<String>,
pub path: Option<String>,
pub key: Option<String>,
pub payload: HashMap<String, Value>,
}
// Result 调用结果
pub struct Result {
pub code: i32,
pub data: Option<Value>,
pub message: Option<String>,
}
// AddOpts 添加选项
pub struct AddOpts {
pub overwrite: bool,
}
// FindOpts 查找选项
pub struct FindOpts {
pub path: Option<String>,
pub key: Option<String>,
pub id: Option<String>,
}
// QueryRouter 路由管理器
pub struct QueryRouter {
pub routes: Vec<Route>,
pub max_next_route: usize,
pub context: RouteContext,
}
impl QueryRouter {
pub fn new() -> Self
pub fn add(&mut self, route: Route, opts: Option<AddOpts>)
pub fn remove(&mut self, path: &str, key: &str)
pub fn remove_by_id(&mut self, id: &str)
pub async fn run_route(&self, path: &str, key: &str, ctx: RouteContext) -> Result<RouteContext>
pub async fn parse(&self, msg: Message, ctx: Option<RouteContext>) -> Result<RouteContext>
pub async fn call(&self, msg: Message, ctx: Option<RouteContext>) -> Result<RouteContext>
pub async fn run(&self, msg: Message, ctx: Option<RouteContext>) -> Result<Result>
pub fn get_handle(&self) -> impl Fn(Message) -> Result + '_
pub fn set_context(&mut self, ctx: RouteContext)
pub fn get_list(&self) -> Vec<Route>
pub fn has_route(&self, path: &str, key: &str) -> bool
pub fn find_route(&self, opts: FindOpts) -> Option<&Route>
}
// ServerOpts 服务端选项
pub struct ServerOpts {
pub handle_fn: Option<Box<dyn Fn(Message, Option<RouteContext>) -> Result + Send>>,
pub context: Option<RouteContext>,
pub app_id: Option<String>,
}
// QueryRouterServer 服务端
pub struct QueryRouterServer {
pub router: QueryRouter,
pub app_id: String,
pub handle: Option<Box<dyn Fn(Message) -> Result + Send>>,
}
impl QueryRouterServer {
pub fn new(opts: Option<ServerOpts>) -> Self
pub fn set_handle(&mut self, wrapperFn: Box<dyn Fn(Message) -> Result + Send>)
pub fn route(&self, path: &str, key: Option<&str>) -> Route
}
```
## 执行流程
```
Message → parse() → runRoute() → [middleware] → run() → [nextRoute] → ...
RouteContext (层层传递)
```
1. `parse()` 接收消息初始化上下文query、args、state
2. `runRoute()` 查找路由,先执行 middleware再执行 run
3. middleware 执行出错立即返回错误
4. 如有 nextRoute递归执行下一个路由最多 40 层)
5. 返回最终 RouteContext
## 特性说明
- **双层路径**: path + key 构成唯一路由
- **链式路由**: nextRoute 支持路由链式执行
- **中间件**: 每个 Route 可挂载多个 middleware
- **统一上下文**: RouteContext 贯穿整个请求生命周期
## 内置路由
框架内置以下路由,通过 HTTP 访问时使用 `path``key` 参数:
| 路由 path | 路由 key | 说明 |
|-----------|----------|------|
| router | list | 获取当前应用所有路由列表 |
### router/list
获取当前应用所有路由列表。
**访问方式:** `POST /api/router?path=router&key=list`
**响应:**
```json
{
"code": 200,
"data": {
"list": [
{
"id": "router$#$list",
"path": "router",
"key": "list",
"description": "列出当前应用下的所有的路由信息",
"middleware": [],
"metadata": {}
}
],
"isUser": false
},
"message": "success"
}
```

View File

@@ -0,0 +1,5 @@
# WebSocket Server 设计文档
## 概述
WebSocket 服务器支持实时双向通信,可与 HTTP 服务器共享同一端口。所有 WebSocket 连接统一入口为 `/api/router`,通过 `type` 参数区分业务类型。