Compare commits
27 Commits
e1a0e11052
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ce92c8ffc | ||
|
|
e0fe1bb476 | ||
|
|
bd787c55a2 | ||
|
|
648fe7ad33 | ||
|
|
151586f98c | ||
| e18233fdcb | |||
| 5bed882795 | |||
| e4c2c0e1e6 | |||
| 3c56849cfa | |||
| b375e5ac23 | |||
| 7345940f18 | |||
| a9cf1505ff | |||
| 9ed6e63d9e | |||
| e58c99c285 | |||
|
|
2628eb3693 | ||
| 46499bedc7 | |||
|
|
ab61be4875 | ||
| 52b10f2f03 | |||
| f8337a1216 | |||
| ad95dc0081 | |||
| 9859c2f673 | |||
| 0152d15824 | |||
| 5c24e197e6 | |||
| af7d809270 | |||
| a8f409f900 | |||
| 132aa3a888 | |||
| 2e59e318bf |
@@ -1,14 +1,20 @@
|
||||
import { app } from '../app.ts'
|
||||
import './route-create.ts'
|
||||
|
||||
if (!app.hasRoute('auth', '')) {
|
||||
app.route({
|
||||
app.route({
|
||||
path: 'auth',
|
||||
key: '',
|
||||
id: 'auth',
|
||||
description: '身份验证路由',
|
||||
}).define(async (ctx) => {
|
||||
}).define(async (ctx) => {
|
||||
//
|
||||
}).addTo(app);
|
||||
}
|
||||
}).addTo(app, { overwrite: false });
|
||||
|
||||
app.route({
|
||||
path: 'auth-admin',
|
||||
key: '',
|
||||
id: 'auth-admin',
|
||||
description: '管理员身份验证路由',
|
||||
}).define(async (ctx) => {
|
||||
//
|
||||
}).addTo(app, { overwrite: false });
|
||||
@@ -6,7 +6,7 @@ app.route({
|
||||
path: 'router-skill',
|
||||
key: 'create-route',
|
||||
description: '创建路由技能',
|
||||
middleware: ['auth'],
|
||||
middleware: ['auth-admin'],
|
||||
metadata: {
|
||||
tags: ['opencode'],
|
||||
...createSkill({
|
||||
@@ -38,7 +38,7 @@ app.route({
|
||||
path: 'router-skill',
|
||||
key: 'version',
|
||||
description: '获取最新router版本号',
|
||||
middleware: ['auth'],
|
||||
middleware: ['auth-admin'],
|
||||
metadata: {
|
||||
tags: ['opencode'],
|
||||
...createSkill({
|
||||
@@ -59,7 +59,7 @@ app.route({
|
||||
path: 'route-skill',
|
||||
key: 'test',
|
||||
description: '测试路由技能',
|
||||
middleware: ['auth'],
|
||||
middleware: ['auth-admin'],
|
||||
metadata: {
|
||||
tags: ['opencode'],
|
||||
...createSkill({
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
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
277
bun.lock
@@ -5,25 +5,28 @@
|
||||
"": {
|
||||
"name": "@kevisual/router",
|
||||
"dependencies": {
|
||||
"es-toolkit": "^1.44.0",
|
||||
"es-toolkit": "^1.45.1",
|
||||
"md5-typescript": "^1.0.5",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@kevisual/code-builder": "^0.0.6",
|
||||
"@kevisual/context": "^0.0.6",
|
||||
"@kevisual/context": "^0.0.8",
|
||||
"@kevisual/dts": "^0.0.4",
|
||||
"@kevisual/js-filter": "^0.0.5",
|
||||
"@kevisual/js-filter": "^0.0.6",
|
||||
"@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",
|
||||
"@opencode-ai/plugin": "^1.2.6",
|
||||
"@types/bun": "^1.3.9",
|
||||
"@types/node": "^25.2.3",
|
||||
"@opencode-ai/plugin": "^1.2.26",
|
||||
"@types/bun": "^1.3.10",
|
||||
"@types/node": "^25.5.0",
|
||||
"@types/send": "^1.2.1",
|
||||
"@types/ws": "^8.18.1",
|
||||
"@types/xml2js": "^0.4.14",
|
||||
"commander": "^14.0.3",
|
||||
"eventemitter3": "^5.0.4",
|
||||
"fast-glob": "^3.3.3",
|
||||
"hono": "^4.11.9",
|
||||
"hono": "^4.12.8",
|
||||
"nanoid": "^5.1.6",
|
||||
"path-to-regexp": "^8.3.0",
|
||||
"send": "^1.2.1",
|
||||
@@ -35,250 +38,262 @@
|
||||
},
|
||||
},
|
||||
"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", {}, ""],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ app.route({
|
||||
},
|
||||
}).define(async (ctx) => {
|
||||
ctx.body = '03';
|
||||
ctx.args.test
|
||||
return ctx;
|
||||
}).addTo(app);
|
||||
// app.server.on({
|
||||
|
||||
@@ -18,15 +18,6 @@ route01.run = async (ctx) => {
|
||||
ctx.body = '01';
|
||||
return ctx;
|
||||
};
|
||||
app.use(
|
||||
'demo',
|
||||
async (ctx) => {
|
||||
ctx.body = '01';
|
||||
return ctx;
|
||||
},
|
||||
{ key: '01' },
|
||||
);
|
||||
|
||||
const route02 = new Route('demo', '02');
|
||||
route02.run = async (ctx) => {
|
||||
ctx.body = '02';
|
||||
|
||||
@@ -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);
|
||||
@@ -20,7 +20,7 @@ router
|
||||
.define(async (ctx) => {
|
||||
ctx.body = 'Hello, world!';
|
||||
// throw new CustomError('error');
|
||||
throw new CustomError(5000, 'error');
|
||||
ctx.throw(5000, 'error');
|
||||
})
|
||||
.addTo(router);
|
||||
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
);
|
||||
@@ -16,10 +16,16 @@ const queryApp = new QueryRouterServer();
|
||||
app
|
||||
.route({
|
||||
path: 'hello',
|
||||
metadata: {
|
||||
args: {
|
||||
name: 'string',
|
||||
},
|
||||
}
|
||||
})
|
||||
.define(async (ctx) => {
|
||||
// console.log('hello', ctx);
|
||||
// console.log('hello', ctx.res);
|
||||
ctx.query.name;
|
||||
console.log('hello', ctx.query.cookies);
|
||||
// ctx.res?.cookie?.('token', 'abc', {
|
||||
// domain: '*', // 设置为顶级域名,允许跨子域共享
|
||||
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -65,8 +65,6 @@ app.importRoutes(app1.exportRoutes());
|
||||
|
||||
app.importRoutes(app2.exportRoutes());
|
||||
|
||||
app.importApp(app3);
|
||||
|
||||
app.listen(4003, () => {
|
||||
console.log(`http://localhost:4003/api/router?path=app1&key=02`);
|
||||
console.log(`http://localhost:4003/api/router?path=app1&key=01`);
|
||||
|
||||
@@ -26,41 +26,6 @@ qr.add(
|
||||
description: 'get project detail2',
|
||||
run: async (ctx: RouteContext) => {
|
||||
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,
|
||||
data: {
|
||||
name: 'john',
|
||||
age: 's'+13,
|
||||
age: 's' + 13,
|
||||
friends: {
|
||||
hair: 'black',
|
||||
messages: 'hello',
|
||||
|
||||
31
package.json
31
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package",
|
||||
"name": "@kevisual/router",
|
||||
"version": "0.0.80",
|
||||
"version": "0.1.6",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"main": "./dist/router.js",
|
||||
@@ -23,35 +23,41 @@
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@kevisual/code-builder": "^0.0.6",
|
||||
"@kevisual/context": "^0.0.6",
|
||||
"@kevisual/context": "^0.0.8",
|
||||
"@kevisual/dts": "^0.0.4",
|
||||
"@kevisual/js-filter": "^0.0.5",
|
||||
"@kevisual/js-filter": "^0.0.6",
|
||||
"@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",
|
||||
"@opencode-ai/plugin": "^1.2.6",
|
||||
"@types/bun": "^1.3.9",
|
||||
"@types/node": "^25.2.3",
|
||||
"@opencode-ai/plugin": "^1.2.27",
|
||||
"@types/bun": "^1.3.10",
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/node": "^25.5.0",
|
||||
"@types/send": "^1.2.1",
|
||||
"@types/ws": "^8.18.1",
|
||||
"@types/xml2js": "^0.4.14",
|
||||
"commander": "^14.0.3",
|
||||
"crypto-js": "^4.2.0",
|
||||
"es-toolkit": "^1.45.1",
|
||||
"eventemitter3": "^5.0.4",
|
||||
"fast-glob": "^3.3.3",
|
||||
"hono": "^4.11.9",
|
||||
"nanoid": "^5.1.6",
|
||||
"hono": "^4.12.8",
|
||||
"nanoid": "^5.1.7",
|
||||
"path-to-regexp": "^8.3.0",
|
||||
"send": "^1.2.1",
|
||||
"typescript": "^5.9.3",
|
||||
"ws": "npm:@kevisual/ws",
|
||||
"xml2js": "^0.6.2",
|
||||
"zod": "^4.3.6"
|
||||
"xml2js": "^0.6.2"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/abearxiong/kevisual-router.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"es-toolkit": "^1.44.0"
|
||||
"crypto-js": "^4.2.0",
|
||||
"es-toolkit": "^1.45.1",
|
||||
"zod": "^4.3.6"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
@@ -59,6 +65,7 @@
|
||||
"exports": {
|
||||
".": "./dist/router.js",
|
||||
"./browser": "./dist/router-browser.js",
|
||||
"./commander": "./dist/commander.js",
|
||||
"./simple": "./dist/router-simple.js",
|
||||
"./opencode": "./dist/opencode.js",
|
||||
"./skill": "./dist/app.js",
|
||||
|
||||
1212
pnpm-lock.yaml
generated
Normal file
1212
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
83
readme.md
83
readme.md
@@ -32,13 +32,14 @@ app
|
||||
在 route handler 中,你可以通过 `ctx` 访问以下属性:
|
||||
|
||||
| 属性 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| --------------- | ---------------------------- | ---------------------------- |
|
||||
| `query` | `object` | 请求参数,会自动合并 payload |
|
||||
| `body` | `number \| string \| Object` | 响应内容 |
|
||||
| `code` | `number` | 响应状态码,默认为 200 |
|
||||
| `message` | `string` | 响应消息 |
|
||||
| `state` | `any` | 状态数据,可在路由间传递 |
|
||||
| `appId` | `string` | 应用标识 |
|
||||
| `currentId` | `string` | 当前路由ID |
|
||||
| `currentPath` | `string` | 当前路由路径 |
|
||||
| `currentKey` | `string` | 当前路由 key |
|
||||
| `currentRoute` | `Route` | 当前 Route 实例 |
|
||||
@@ -53,7 +54,7 @@ app
|
||||
### 上下文方法
|
||||
|
||||
| 方法 | 参数 | 说明 |
|
||||
|------|------|------|
|
||||
| ----------------------------------- | ----------------------------------------- | -------------------------------------------- |
|
||||
| `ctx.call(msg, ctx?)` | `{ path, key?, payload?, ... } \| { id }` | 调用其他路由,返回完整 context |
|
||||
| `ctx.run(msg, ctx?)` | `{ path, key?, payload? }` | 调用其他路由,返回 `{ code, data, message }` |
|
||||
| `ctx.forward(res)` | `{ code, data?, message? }` | 设置响应结果 |
|
||||
@@ -63,31 +64,25 @@ app
|
||||
|
||||
```ts
|
||||
import { App } from '@kevisual/router';
|
||||
|
||||
import z from 'zod';
|
||||
const app = new App();
|
||||
app.listen(4002);
|
||||
|
||||
// 基本路由
|
||||
app
|
||||
.route({ path: 'user', key: 'info' })
|
||||
.route({ path: 'user', key: 'info', id: 'user-info' })
|
||||
.define(async (ctx) => {
|
||||
// ctx.query 包含请求参数
|
||||
const { id } = ctx.query;
|
||||
// 使用 state 在路由间传递数据
|
||||
ctx.state.orderId = '12345';
|
||||
ctx.body = { id, name: '张三' };
|
||||
ctx.code = 200;
|
||||
})
|
||||
.addTo(app);
|
||||
|
||||
// 使用 state 在路由间传递数据
|
||||
app
|
||||
.route({ path: 'order', key: 'create' })
|
||||
.define(async (ctx) => {
|
||||
ctx.state = { orderId: '12345' };
|
||||
})
|
||||
.addTo(app);
|
||||
|
||||
app
|
||||
.route({ path: 'order', key: 'pay' })
|
||||
.route({ path: 'order', key: 'pay', middleware: ['user-info'] })
|
||||
.define(async (ctx) => {
|
||||
// 可以获取前一个路由设置的 state
|
||||
const { orderId } = ctx.state;
|
||||
@@ -95,14 +90,6 @@ app
|
||||
})
|
||||
.addTo(app);
|
||||
|
||||
// 链式调用
|
||||
app
|
||||
.route({ path: 'product', key: 'list' })
|
||||
.define(async (ctx) => {
|
||||
ctx.body = [{ id: 1, name: '商品A' }];
|
||||
})
|
||||
.addTo(app);
|
||||
|
||||
// 调用其他路由
|
||||
app
|
||||
.route({ path: 'dashboard', key: 'stats' })
|
||||
@@ -114,7 +101,7 @@ app
|
||||
|
||||
ctx.body = {
|
||||
user: userRes.data,
|
||||
products: productRes.data
|
||||
products: productRes.data,
|
||||
};
|
||||
})
|
||||
.addTo(app);
|
||||
@@ -140,17 +127,20 @@ import { App, Route } from '@kevisual/router';
|
||||
const app = new App();
|
||||
|
||||
// 定义中间件
|
||||
app.route({
|
||||
app
|
||||
.route({
|
||||
id: 'auth-example',
|
||||
description: '权限校验中间件'
|
||||
}).define(async(ctx) => {
|
||||
description: '权限校验中间件',
|
||||
})
|
||||
.define(async (ctx) => {
|
||||
const token = ctx.query.token;
|
||||
if (!token) {
|
||||
ctx.throw(401, '未登录', '需要 token');
|
||||
}
|
||||
// 验证通过,设置用户信息到 state
|
||||
ctx.state.tokenUser = { id: 1, name: '用户A' };
|
||||
}).addTo(app);
|
||||
})
|
||||
.addTo(app);
|
||||
|
||||
// 使用中间件(通过 id 引用)
|
||||
app
|
||||
@@ -163,6 +153,33 @@ 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 只能添加一个路由,后添加的会覆盖之前的。
|
||||
@@ -173,16 +190,14 @@ app
|
||||
|
||||
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 记录执行路径**,可用于调试和追踪路由调用链。
|
||||
|
||||
10. **中间件找不到会返回 404**,错误信息中会包含找不到的中间件列表。
|
||||
9. **中间件找不到会返回 404**,错误信息中会包含找不到的中间件列表。
|
||||
|
||||
57
src/app.ts
57
src/app.ts
@@ -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 { HandleCtx } from './server/server-base.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 AppOptions<T = {}> = {
|
||||
router?: QueryRouter;
|
||||
router?: QueryRouterServer;
|
||||
server?: ServerType;
|
||||
/** handle msg 关联 */
|
||||
routerHandle?: RouterHandle;
|
||||
@@ -19,18 +19,19 @@ type AppOptions<T = {}> = {
|
||||
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 的处理
|
||||
* U - Route Context的扩展类型
|
||||
*/
|
||||
export class App<U = {}> extends QueryRouter {
|
||||
export class App<U = {}> extends QueryRouterServer<AppRouteContext<U>> {
|
||||
declare appId: string;
|
||||
router: QueryRouter;
|
||||
router: QueryRouterServer;
|
||||
server: ServerType;
|
||||
declare context: AppRouteContext<U>;
|
||||
constructor(opts?: AppOptions<U>) {
|
||||
super();
|
||||
super({ initHandle: false, context: { needSerialize: true, ...opts?.routerContext } as any });
|
||||
const router = this;
|
||||
let server = opts?.server;
|
||||
if (!server) {
|
||||
@@ -42,7 +43,6 @@ export class App<U = {}> extends QueryRouter {
|
||||
}
|
||||
}
|
||||
server.setHandle(router.getHandle(router, opts?.routerHandle, opts?.routerContext));
|
||||
router.setContext({ needSerialize: true, ...opts?.routerContext });
|
||||
this.router = router;
|
||||
this.server = server;
|
||||
if (opts?.appId) {
|
||||
@@ -64,50 +64,7 @@ export class App<U = {}> extends QueryRouter {
|
||||
// @ts-ignore
|
||||
this.server.listen(...args);
|
||||
}
|
||||
addRoute(route: Route, opts?: AddOpts) {
|
||||
super.add(route, opts);
|
||||
}
|
||||
|
||||
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) {
|
||||
return handleServer(req, res);
|
||||
}
|
||||
|
||||
255
src/commander.ts
Normal file
255
src/commander.ts
Normal 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 })
|
||||
}
|
||||
@@ -10,7 +10,7 @@ export const addCallFn = (app: App) => {
|
||||
path: 'call',
|
||||
key: '',
|
||||
description: '调用',
|
||||
middleware: ['auth'],
|
||||
middleware: ['auth-admin'],
|
||||
metadata: {
|
||||
tags: ['opencode'],
|
||||
...createSkill({
|
||||
@@ -24,7 +24,7 @@ export const addCallFn = (app: App) => {
|
||||
args: {
|
||||
path: tool.schema.string().describe('应用路径,例如 cnb'),
|
||||
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', '')) {
|
||||
addCallFn(router as App)
|
||||
}
|
||||
if (!router.hasRoute('auth', '')) {
|
||||
router.route({ path: 'auth', key: '', id: 'auth', description: '认证' }).define(async (ctx) => { }).addTo(router as App)
|
||||
if (router) {
|
||||
(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 = _routes.filter(r => {
|
||||
const metadata = r.metadata as Skill
|
||||
@@ -75,7 +82,7 @@ export const createRouterAgentPluginFn = (opts?: {
|
||||
});
|
||||
// opencode run "使用技能查看系统信息"
|
||||
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) : {}
|
||||
return {
|
||||
...hooks,
|
||||
|
||||
@@ -1,20 +1,25 @@
|
||||
export type CustomErrorOptions = {
|
||||
cause?: Error | string;
|
||||
code?: number;
|
||||
message?: string;
|
||||
}
|
||||
/** 自定义错误 */
|
||||
export class CustomError extends Error {
|
||||
code?: number;
|
||||
data?: any;
|
||||
message: string;
|
||||
tips?: string;
|
||||
constructor(code?: number | string, message?: string, tips?: string) {
|
||||
super(message || String(code));
|
||||
this.name = 'CustomError';
|
||||
if (typeof code === 'number') {
|
||||
this.code = code;
|
||||
this.message = message!;
|
||||
} else {
|
||||
this.code = 500;
|
||||
this.message = code!;
|
||||
constructor(code?: number | string | CustomErrorOptions, opts?: CustomErrorOptions) {
|
||||
if (typeof code === 'object' && code !== null) {
|
||||
opts = code;
|
||||
code = opts.code || 500;
|
||||
}
|
||||
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, this.constructor);
|
||||
@@ -31,8 +36,7 @@ export class CustomError extends Error {
|
||||
return {
|
||||
code: e?.code,
|
||||
data: e?.data,
|
||||
message: e?.message,
|
||||
tips: e?.tips,
|
||||
message: e?.message
|
||||
};
|
||||
}
|
||||
/**
|
||||
@@ -43,6 +47,22 @@ export class CustomError extends Error {
|
||||
static isError(error: unknown): error is CustomError {
|
||||
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) {
|
||||
if (e) {
|
||||
return CustomError.parseError(e);
|
||||
@@ -52,12 +72,17 @@ export class CustomError extends Error {
|
||||
code: e?.code,
|
||||
data: e?.data,
|
||||
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 {
|
||||
//
|
||||
|
||||
249
src/route.ts
249
src/route.ts
@@ -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 { listenProcess, MockProcess } from './utils/listen-process.ts';
|
||||
import { z } from 'zod';
|
||||
import { randomId } from './utils/random.ts';
|
||||
import { hashIdMd5Sync, randomId } from './utils/random.ts';
|
||||
import * as schema from './validator/schema.ts';
|
||||
|
||||
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 就不许重复的校验
|
||||
* 或者不需要登录的,直接调用
|
||||
@@ -23,9 +32,15 @@ export type RouteContext<T = { code?: number }, S = any> = {
|
||||
code?: number;
|
||||
/** return msg */
|
||||
message?: string;
|
||||
// 传递状态
|
||||
/**
|
||||
* 传递状态
|
||||
*/
|
||||
state?: S;
|
||||
// transfer data
|
||||
/**
|
||||
* 当前routerId
|
||||
*/
|
||||
currentId?: string;
|
||||
/**
|
||||
* 当前路径
|
||||
*/
|
||||
@@ -54,19 +69,19 @@ export type RouteContext<T = { code?: number }, S = any> = {
|
||||
ctx?: RouteContext & { [key: string]: any },
|
||||
) => Promise<any>;
|
||||
/** 请求 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;
|
||||
throw?: (code?: number | string, message?: string, tips?: string) => void;
|
||||
throw?: throwError['throw'];
|
||||
/** 是否需要序列化, 使用JSON.stringify和JSON.parse */
|
||||
needSerialize?: boolean;
|
||||
} & T;
|
||||
} & T & U;
|
||||
export type SimpleObject = Record<string, any>;
|
||||
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 NextRoute = Pick<Route, 'id' | 'path' | 'key'>;
|
||||
export type RouteMiddleware =
|
||||
| {
|
||||
path: string;
|
||||
path?: string;
|
||||
key?: string;
|
||||
id?: string;
|
||||
}
|
||||
@@ -80,15 +95,7 @@ export type RouteOpts<U = {}, T = SimpleObject> = {
|
||||
description?: string;
|
||||
metadata?: T;
|
||||
middleware?: RouteMiddleware[]; // middleware
|
||||
type?: 'route' | 'middleware';
|
||||
/**
|
||||
* $#$ will be used to split path and key
|
||||
*/
|
||||
idUsePath?: boolean;
|
||||
/**
|
||||
* id 合并的分隔符,默认为 $#$
|
||||
*/
|
||||
delimiter?: string;
|
||||
type?: 'route' | 'middleware' | 'compound'; // compound表示这个 route 作为一个聚合体,没有实际的 run,而是一个 router 的聚合列表
|
||||
isDebug?: boolean;
|
||||
};
|
||||
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 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;
|
||||
id?: string;
|
||||
run?: Run;
|
||||
run?: Run<BuildRouteContext<M, U>>;
|
||||
nextRoute?: NextRoute; // route to run after this route
|
||||
description?: string;
|
||||
metadata?: T;
|
||||
metadata?: M;
|
||||
middleware?: RouteMiddleware[]; // middleware
|
||||
type? = 'route';
|
||||
/**
|
||||
@@ -151,23 +162,22 @@ export class Route<U = { [key: string]: any }, T extends SimpleObject = SimpleOb
|
||||
key = key.trim();
|
||||
this.path = path;
|
||||
this.key = key;
|
||||
const pathKey = `${path}$$${key}`;
|
||||
if (opts) {
|
||||
this.id = opts.id || randomId(12, 'rand-');
|
||||
if (!opts.id && opts.idUsePath) {
|
||||
const delimiter = opts.delimiter ?? '$#$';
|
||||
this.id = path + delimiter + key;
|
||||
}
|
||||
this.run = opts.run;
|
||||
this.id = opts.id || hashIdMd5Sync(pathKey);
|
||||
this.run = opts.run as Run<BuildRouteContext<M, U>>;
|
||||
this.nextRoute = opts.nextRoute;
|
||||
this.description = opts.description;
|
||||
this.metadata = opts.metadata as T;
|
||||
this.metadata = opts.metadata as M;
|
||||
this.type = opts.type || 'route';
|
||||
this.middleware = opts.middleware || [];
|
||||
this.key = opts.key || key;
|
||||
this.path = opts.path || path;
|
||||
} else {
|
||||
this.middleware = [];
|
||||
this.id = randomId(12, 'rand-');
|
||||
}
|
||||
if (!this.id) {
|
||||
this.id = hashIdMd5Sync(pathKey);
|
||||
}
|
||||
this.isDebug = opts?.isDebug ?? false;
|
||||
}
|
||||
@@ -184,9 +194,9 @@ export class Route<U = { [key: string]: any }, T extends SimpleObject = SimpleOb
|
||||
return 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>(key: string, fn: Run<T & 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>(fn: Run<T & BuildRouteContext<M, 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 & BuildRouteContext<M, U>>): this;
|
||||
define(...args: any[]) {
|
||||
const [path, key, opts] = args;
|
||||
// 全覆盖,所以opts需要准确,不能由idUsePath 需要check的变量
|
||||
@@ -209,7 +219,7 @@ export class Route<U = { [key: string]: any }, T extends SimpleObject = SimpleOb
|
||||
return this;
|
||||
}
|
||||
if (typeof path === 'function') {
|
||||
this.run = path;
|
||||
this.run = path as Run<BuildRouteContext<M, U>>;
|
||||
return this;
|
||||
}
|
||||
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) {
|
||||
router.add(this, opts);
|
||||
}
|
||||
throw(code?: number | string, message?: string, tips?: string): void;
|
||||
throw(...args: any[]) {
|
||||
throw new CustomError(...args);
|
||||
CustomError.throw(...args);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,6 +262,9 @@ const toJSONSchemaRoute = (route: RouteInfo) => {
|
||||
if (pickValues?.metadata?.args) {
|
||||
pickValues.metadata.args = toJSONSchema(pickValues?.metadata?.args, { mergeObject: false });
|
||||
}
|
||||
if (pickValues?.metadata?.returns) {
|
||||
pickValues.metadata.returns = toJSONSchema(pickValues?.metadata?.returns, { mergeObject: false });
|
||||
}
|
||||
return pickValues;
|
||||
}
|
||||
|
||||
@@ -263,11 +275,11 @@ export const fromJSONSchema = schema.fromJSONSchema;
|
||||
* @parmas overwrite 是否覆盖已存在的route,默认true
|
||||
*/
|
||||
export type AddOpts = { overwrite?: boolean };
|
||||
export class QueryRouter {
|
||||
export class QueryRouter<T extends SimpleObject = SimpleObject> implements throwError {
|
||||
appId: string = '';
|
||||
routes: Route[];
|
||||
maxNextRoute = 40;
|
||||
context?: RouteContext = {}; // default context for call
|
||||
context?: RouteContext<T> = {} as RouteContext<T>; // default context for call
|
||||
constructor() {
|
||||
this.routes = [];
|
||||
}
|
||||
@@ -310,11 +322,12 @@ export class QueryRouter {
|
||||
* @param ctx
|
||||
* @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 maxNextRoute = this.maxNextRoute;
|
||||
ctx = (ctx || {}) as RouteContext;
|
||||
ctx = (ctx || {}) as RouteContext<T>;
|
||||
ctx.currentPath = path;
|
||||
ctx.currentId = route?.id;
|
||||
ctx.currentKey = key;
|
||||
ctx.currentRoute = route;
|
||||
ctx.index = (ctx.index || 0) + 1;
|
||||
@@ -328,7 +341,7 @@ export class QueryRouter {
|
||||
ctx.code = 500;
|
||||
ctx.message = 'Too many nextRoute';
|
||||
ctx.body = null;
|
||||
return;
|
||||
return ctx;
|
||||
}
|
||||
// run middleware
|
||||
if (route && route.middleware && route.middleware.length > 0) {
|
||||
@@ -383,7 +396,7 @@ export class QueryRouter {
|
||||
const middleware = routeMiddleware[i];
|
||||
if (middleware) {
|
||||
try {
|
||||
await middleware.run(ctx as Required<RouteContext>);
|
||||
await middleware.run(ctx as Required<RouteContext<T>>);
|
||||
} catch (e) {
|
||||
if (route?.isDebug) {
|
||||
console.error('=====debug====:middlerware error');
|
||||
@@ -405,6 +418,7 @@ export class QueryRouter {
|
||||
return ctx;
|
||||
}
|
||||
if (ctx.end) {
|
||||
return ctx;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -413,14 +427,14 @@ export class QueryRouter {
|
||||
if (route) {
|
||||
if (route.run) {
|
||||
try {
|
||||
await route.run(ctx as Required<RouteContext>);
|
||||
await route.run(ctx as Required<RouteContext<T>>);
|
||||
} catch (e) {
|
||||
if (route?.isDebug) {
|
||||
console.error('=====debug====:route error');
|
||||
console.error('=====debug====:', e);
|
||||
console.error('=====debug====:[path:key]:', `${route.path}-${route.key}`);
|
||||
}
|
||||
if (e instanceof CustomError) {
|
||||
if (e instanceof CustomError || e?.code) {
|
||||
ctx.code = e.code;
|
||||
ctx.message = e.message;
|
||||
} else {
|
||||
@@ -468,7 +482,7 @@ export class QueryRouter {
|
||||
}
|
||||
}
|
||||
// 如果没有找到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
|
||||
* @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) {
|
||||
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;
|
||||
ctx = ctx || {};
|
||||
ctx = ctx || {} as RouteContext<T>;
|
||||
ctx.query = { ...ctx.query, ...query, ...payload };
|
||||
ctx.args = ctx.query;
|
||||
ctx.state = { ...ctx?.state };
|
||||
@@ -515,7 +529,7 @@ export class QueryRouter {
|
||||
* @param ctx
|
||||
* @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 key = message.key;
|
||||
// 优先 path + key
|
||||
@@ -556,7 +570,7 @@ export class QueryRouter {
|
||||
* @param ctx
|
||||
* @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 });
|
||||
return {
|
||||
code: res.code,
|
||||
@@ -570,7 +584,7 @@ export class QueryRouter {
|
||||
* @param ctx
|
||||
*/
|
||||
setContext(ctx: RouteContext) {
|
||||
this.context = ctx;
|
||||
this.context = ctx as RouteContext<T>;
|
||||
}
|
||||
getList(filter?: (route: Route) => boolean): RouteInfo[] {
|
||||
return this.routes.filter(filter || (() => true)).map((r) => {
|
||||
@@ -581,11 +595,11 @@ export class QueryRouter {
|
||||
/**
|
||||
* 获取handle函数, 这里会去执行parse函数
|
||||
*/
|
||||
getHandle<T = any>(router: QueryRouter, wrapperFn?: HandleFn<T>, ctx?: RouteContext) {
|
||||
return async (msg: { id?: string; path?: string; key?: string;[key: string]: any }, handleContext?: RouteContext) => {
|
||||
getHandle<T = any>(router: QueryRouter, wrapperFn?: HandleFn, ctx?: RouteContext) {
|
||||
return async (msg: { id?: string; path?: string; key?: string;[key: string]: any }, handleContext?: RouteContext<T>) => {
|
||||
try {
|
||||
const context = { ...ctx, ...handleContext };
|
||||
const res = await router.call(msg, context);
|
||||
const res = await router.call(msg, context) as any;
|
||||
if (wrapperFn) {
|
||||
res.data = res.body;
|
||||
return wrapperFn(res, context);
|
||||
@@ -610,9 +624,8 @@ export class QueryRouter {
|
||||
importRouter(router: QueryRouter) {
|
||||
this.importRoutes(router.routes);
|
||||
}
|
||||
throw(code?: number | string, message?: string, tips?: string): void;
|
||||
throw(...args: any[]) {
|
||||
throw new CustomError(...args);
|
||||
CustomError.throw(...args);
|
||||
}
|
||||
hasRoute(path: string, key: string = '') {
|
||||
return this.routes.find((r) => r.path === path && r.key === key);
|
||||
@@ -639,7 +652,7 @@ export class QueryRouter {
|
||||
description: '列出当前应用下的所有的路由信息',
|
||||
middleware: opts?.middleware || [],
|
||||
run: async (ctx: RouteContext) => {
|
||||
const tokenUser = ctx.state.tokenUser;
|
||||
const tokenUser = ctx.state as unknown as { tokenUser?: any };
|
||||
let isUser = !!tokenUser;
|
||||
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') {
|
||||
@@ -685,10 +698,11 @@ export class QueryRouter {
|
||||
fromJSONSchema = fromJSONSchema;
|
||||
}
|
||||
|
||||
type QueryRouterServerOpts = {
|
||||
type QueryRouterServerOpts<C extends SimpleObject = SimpleObject> = {
|
||||
handleFn?: HandleFn;
|
||||
context?: RouteContext;
|
||||
context?: RouteContext<C>;
|
||||
appId?: string;
|
||||
initHandle?: boolean;
|
||||
};
|
||||
interface HandleFn<T = 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
|
||||
* @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;
|
||||
handle: any;
|
||||
constructor(opts?: QueryRouterServerOpts) {
|
||||
declare context: RouteContext<C>;
|
||||
constructor(opts?: QueryRouterServerOpts<C>) {
|
||||
super();
|
||||
const initHandle = opts?.initHandle ?? true;
|
||||
if (initHandle || opts?.handleFn) {
|
||||
this.handle = this.getHandle(this, opts?.handleFn, opts?.context);
|
||||
}
|
||||
this.setContext({ needSerialize: false, ...opts?.context });
|
||||
if (opts?.appId) {
|
||||
this.appId = opts.appId;
|
||||
@@ -718,37 +737,28 @@ export class QueryRouterServer extends QueryRouter {
|
||||
this.add(route, opts);
|
||||
}
|
||||
Route = Route;
|
||||
route(opts: RouteOpts): Route<Required<RouteContext>>;
|
||||
route(path: string, key?: string): Route<Required<RouteContext>>;
|
||||
route(path: string, opts?: RouteOpts): Route<Required<RouteContext>>;
|
||||
route(path: string, key?: string, opts?: RouteOpts): Route<Required<RouteContext>>;
|
||||
route(...args: any[]) {
|
||||
route<M extends SimpleObject = SimpleObject>(opts: RouteOpts & { metadata?: M }): Route<M, Required<RouteContext<C>>>;
|
||||
route<M extends SimpleObject = SimpleObject>(path: string, opts?: RouteOpts & { metadata?: M }): Route<M, Required<RouteContext<C>>>;
|
||||
route<M extends SimpleObject = SimpleObject>(path: string, key?: string): Route<M, Required<RouteContext<C>>>;
|
||||
route<M extends SimpleObject = SimpleObject>(path: string, key?: string, opts?: RouteOpts & { metadata?: M }): Route<M, Required<RouteContext<C>>>;
|
||||
route<M extends SimpleObject = SimpleObject>(...args: any[]) {
|
||||
const [path, key, opts] = args;
|
||||
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 (opts) {
|
||||
return new Route(path, key, opts);
|
||||
return new Route<M, Required<RouteContext<C>>>(path, key, opts);
|
||||
}
|
||||
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: Function): Route<Required<RouteContext>>;
|
||||
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 });
|
||||
prompt(description: string) {
|
||||
return new Route(undefined, undefined, { description });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -756,15 +766,90 @@ export class QueryRouterServer extends QueryRouter {
|
||||
* @param param0
|
||||
* @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;
|
||||
if (handle) {
|
||||
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 { }
|
||||
|
||||
/** 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 shape(key -> 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>>;
|
||||
@@ -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 { parseIfJson } from '../utils/parse.ts';
|
||||
import { EventEmitter } from 'eventemitter3';
|
||||
import { CustomError } from '../result/error.ts';
|
||||
type CookieFn = (name: string, value: string, options?: cookie.SerializeOptions, end?: boolean) => void;
|
||||
|
||||
export type HandleCtx = {
|
||||
@@ -165,8 +166,13 @@ export class ServerBase implements ServerType {
|
||||
res.end(JSON.stringify(end));
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
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') {
|
||||
res.end(resultError(e.message || `Router Server error`, e.code));
|
||||
} else {
|
||||
@@ -278,7 +284,7 @@ export class ServerBase implements ServerType {
|
||||
* @param ws
|
||||
*/
|
||||
async onWsClose(ws: WS) {
|
||||
const id = ws?.data?.id || '';
|
||||
const id = ws?.wsId || '';
|
||||
if (id) {
|
||||
this.emitter.emit('close--' + id, { type: 'close', ws, id });
|
||||
setTimeout(() => {
|
||||
@@ -292,4 +298,7 @@ export class ServerBase implements ServerType {
|
||||
if (this.showConnected)
|
||||
ws.send(JSON.stringify({ type: 'connected' }));
|
||||
}
|
||||
createId() {
|
||||
return Math.random().toString(36).substring(2, 15);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* @tags bun, server, websocket, http
|
||||
* @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';
|
||||
|
||||
export class BunServer extends ServerBase implements ServerType {
|
||||
@@ -264,10 +264,14 @@ export class BunServer extends ServerBase implements ServerType {
|
||||
open: (ws: any) => {
|
||||
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 token = ws.data.token || '';
|
||||
const id = ws.data.id || '';
|
||||
if (!ws.wsId) {
|
||||
ws.wsId = this.createId();
|
||||
}
|
||||
await this.onWebSocket({ ws, message, pathname, token, id });
|
||||
},
|
||||
close: (ws: any) => {
|
||||
|
||||
@@ -49,16 +49,33 @@ export type OnWebSocketOptions<T = {}> = {
|
||||
message: string | Buffer;
|
||||
pathname: string,
|
||||
token?: string,
|
||||
/** data 的id提取出来 */
|
||||
id?: string,
|
||||
}
|
||||
export type OnWebSocketFn = (options: OnWebSocketOptions) => Promise<void> | void;
|
||||
export type WS<T = {}> = {
|
||||
send: (data: any) => void;
|
||||
close: (code?: number, reason?: string) => void;
|
||||
/**
|
||||
* ws 自己生成的一个id,主要是为了区分不同的ws连接,方便在onWebSocket中使用
|
||||
*/
|
||||
wsId?: string;
|
||||
data?: {
|
||||
/**
|
||||
* ws连接时的url,包含pathname和searchParams
|
||||
*/
|
||||
url: URL;
|
||||
/**
|
||||
* ws连接时的pathname
|
||||
*/
|
||||
pathname: string;
|
||||
/**
|
||||
* ws连接时的url中的token参数
|
||||
*/
|
||||
token?: string;
|
||||
/**
|
||||
* ws连接时的url中的id参数.
|
||||
*/
|
||||
id?: string;
|
||||
/**
|
||||
* 鉴权后的获取的信息
|
||||
|
||||
@@ -56,6 +56,11 @@ export class WsServerBase {
|
||||
token,
|
||||
id,
|
||||
}
|
||||
// @ts-ignore
|
||||
if (!ws.wsId) {
|
||||
// @ts-ignore
|
||||
ws.wsId = this.createId();
|
||||
}
|
||||
ws.on('message', async (message: string | Buffer) => {
|
||||
await this.server.onWebSocket({ ws, message, pathname, token, id });
|
||||
});
|
||||
@@ -66,7 +71,9 @@ export class WsServerBase {
|
||||
this.server.onWsClose(ws);
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
createId() {
|
||||
return Math.random().toString(36).substring(2, 15);
|
||||
}
|
||||
}
|
||||
// TODO: ws handle and path and routerContext
|
||||
|
||||
@@ -1,13 +1,69 @@
|
||||
import { App } from '../app.ts'
|
||||
|
||||
const app = new App<{ f: string }>();
|
||||
|
||||
import { App, AppRouteContext } from "@/app.ts";
|
||||
import { QueryRouterServer, RouteContext } from "@/app.ts";
|
||||
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({
|
||||
path: 't',
|
||||
run: async (ctx) => {
|
||||
// ctx.r
|
||||
ctx.app;
|
||||
path: 'test1',
|
||||
metadata: {
|
||||
args: {
|
||||
name: z.string(),
|
||||
}
|
||||
},
|
||||
}).define(async (ctx) => {
|
||||
ctx.f = 'hello';
|
||||
}).addTo(app);
|
||||
// ctx.app 是 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 };
|
||||
@@ -14,4 +14,4 @@ app.prompt('获取天气的工具。\n参数是 city 为对应的城市').define
|
||||
|
||||
export const chat = new RouterChat({ router: app.router });
|
||||
|
||||
console.log(chat.chat());
|
||||
console.log(chat.getChatPrompt());
|
||||
15
src/test/route-ts.ts
Normal file
15
src/test/route-ts.ts
Normal 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
87
src/test/run-schema.ts
Normal 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 })
|
||||
@@ -1,8 +1,17 @@
|
||||
import { customAlphabet } from 'nanoid';
|
||||
|
||||
import Md5 from 'crypto-js/md5.js';
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz', 16);
|
||||
|
||||
export const randomId = (length: number = 8, affix: string = '') => {
|
||||
return affix + nanoid(length);
|
||||
}
|
||||
export { nanoid };
|
||||
|
||||
/**
|
||||
* 基于 MD5 的确定性 ID 生成 (同步版本)
|
||||
* 浏览器和 Node.js 都支持
|
||||
* 相同的 pathKey 永远返回相同的 16 位 ID
|
||||
*/
|
||||
export const hashIdMd5Sync = (pathKey: string): string => {
|
||||
return Md5(pathKey).toString().substring(0, 16);
|
||||
}
|
||||
|
||||
452
system_design/https-server.md
Normal file
452
system_design/https-server.md
Normal 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
378
system_design/router.md
Normal 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"
|
||||
}
|
||||
```
|
||||
5
system_design/ws-server.md
Normal file
5
system_design/ws-server.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# WebSocket Server 设计文档
|
||||
|
||||
## 概述
|
||||
|
||||
WebSocket 服务器支持实时双向通信,可与 HTTP 服务器共享同一端口。所有 WebSocket 连接统一入口为 `/api/router`,通过 `type` 参数区分业务类型。
|
||||
Reference in New Issue
Block a user