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 { app } from '../app.ts'
|
||||||
import './route-create.ts'
|
import './route-create.ts'
|
||||||
|
|
||||||
if (!app.hasRoute('auth', '')) {
|
app.route({
|
||||||
app.route({
|
|
||||||
path: 'auth',
|
path: 'auth',
|
||||||
key: '',
|
key: '',
|
||||||
id: 'auth',
|
id: 'auth',
|
||||||
description: '身份验证路由',
|
description: '身份验证路由',
|
||||||
}).define(async (ctx) => {
|
}).define(async (ctx) => {
|
||||||
//
|
//
|
||||||
}).addTo(app);
|
}).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',
|
path: 'router-skill',
|
||||||
key: 'create-route',
|
key: 'create-route',
|
||||||
description: '创建路由技能',
|
description: '创建路由技能',
|
||||||
middleware: ['auth'],
|
middleware: ['auth-admin'],
|
||||||
metadata: {
|
metadata: {
|
||||||
tags: ['opencode'],
|
tags: ['opencode'],
|
||||||
...createSkill({
|
...createSkill({
|
||||||
@@ -38,7 +38,7 @@ app.route({
|
|||||||
path: 'router-skill',
|
path: 'router-skill',
|
||||||
key: 'version',
|
key: 'version',
|
||||||
description: '获取最新router版本号',
|
description: '获取最新router版本号',
|
||||||
middleware: ['auth'],
|
middleware: ['auth-admin'],
|
||||||
metadata: {
|
metadata: {
|
||||||
tags: ['opencode'],
|
tags: ['opencode'],
|
||||||
...createSkill({
|
...createSkill({
|
||||||
@@ -59,7 +59,7 @@ app.route({
|
|||||||
path: 'route-skill',
|
path: 'route-skill',
|
||||||
key: 'test',
|
key: 'test',
|
||||||
description: '测试路由技能',
|
description: '测试路由技能',
|
||||||
middleware: ['auth'],
|
middleware: ['auth-admin'],
|
||||||
metadata: {
|
metadata: {
|
||||||
tags: ['opencode'],
|
tags: ['opencode'],
|
||||||
...createSkill({
|
...createSkill({
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
import { buildWithBun } from '@kevisual/code-builder';
|
import { buildWithBun } from '@kevisual/code-builder';
|
||||||
|
|
||||||
await buildWithBun({ naming: 'app', entry: 'agent/main.ts', dts: true });
|
const external: any[] = []
|
||||||
|
await buildWithBun({ naming: 'app', entry: 'agent/main.ts', dts: true, external });
|
||||||
|
|
||||||
await buildWithBun({ naming: 'router', entry: 'src/index.ts', dts: true });
|
await buildWithBun({ naming: 'router', entry: 'src/index.ts', dts: true, external });
|
||||||
|
|
||||||
await buildWithBun({ naming: 'router-browser', entry: 'src/app-browser.ts', target: 'browser', dts: true });
|
await buildWithBun({ naming: 'router-browser', entry: 'src/app-browser.ts', target: 'browser', dts: true, external });
|
||||||
|
|
||||||
await buildWithBun({ naming: 'router-define', entry: 'src/router-define.ts', target: 'browser', dts: true });
|
await buildWithBun({ naming: 'router-define', entry: 'src/router-define.ts', target: 'browser', dts: true, external });
|
||||||
|
|
||||||
await buildWithBun({ naming: 'router-simple', entry: 'src/router-simple.ts', dts: true });
|
await buildWithBun({ naming: 'router-simple', entry: 'src/router-simple.ts', dts: true, external });
|
||||||
|
|
||||||
await buildWithBun({ naming: 'opencode', entry: 'src/opencode.ts', dts: true });
|
await buildWithBun({ naming: 'opencode', entry: 'src/opencode.ts', dts: true, external });
|
||||||
|
|
||||||
await buildWithBun({ naming: 'ws', entry: 'src/ws.ts', dts: true });
|
await buildWithBun({ naming: 'ws', entry: 'src/ws.ts', dts: true, external });
|
||||||
|
|
||||||
|
await buildWithBun({ naming: 'commander', entry: 'src/commander.ts', dts: true, external });
|
||||||
|
|||||||
277
bun.lock
277
bun.lock
@@ -5,25 +5,28 @@
|
|||||||
"": {
|
"": {
|
||||||
"name": "@kevisual/router",
|
"name": "@kevisual/router",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"es-toolkit": "^1.44.0",
|
"es-toolkit": "^1.45.1",
|
||||||
|
"md5-typescript": "^1.0.5",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@kevisual/code-builder": "^0.0.6",
|
"@kevisual/code-builder": "^0.0.6",
|
||||||
"@kevisual/context": "^0.0.6",
|
"@kevisual/context": "^0.0.8",
|
||||||
"@kevisual/dts": "^0.0.4",
|
"@kevisual/dts": "^0.0.4",
|
||||||
"@kevisual/js-filter": "^0.0.5",
|
"@kevisual/js-filter": "^0.0.6",
|
||||||
"@kevisual/local-proxy": "^0.0.8",
|
"@kevisual/local-proxy": "^0.0.8",
|
||||||
"@kevisual/query": "^0.0.47",
|
"@kevisual/query": "^0.0.53",
|
||||||
|
"@kevisual/remote-app": "^0.0.7",
|
||||||
"@kevisual/use-config": "^1.0.30",
|
"@kevisual/use-config": "^1.0.30",
|
||||||
"@opencode-ai/plugin": "^1.2.6",
|
"@opencode-ai/plugin": "^1.2.26",
|
||||||
"@types/bun": "^1.3.9",
|
"@types/bun": "^1.3.10",
|
||||||
"@types/node": "^25.2.3",
|
"@types/node": "^25.5.0",
|
||||||
"@types/send": "^1.2.1",
|
"@types/send": "^1.2.1",
|
||||||
"@types/ws": "^8.18.1",
|
"@types/ws": "^8.18.1",
|
||||||
"@types/xml2js": "^0.4.14",
|
"@types/xml2js": "^0.4.14",
|
||||||
|
"commander": "^14.0.3",
|
||||||
"eventemitter3": "^5.0.4",
|
"eventemitter3": "^5.0.4",
|
||||||
"fast-glob": "^3.3.3",
|
"fast-glob": "^3.3.3",
|
||||||
"hono": "^4.11.9",
|
"hono": "^4.12.8",
|
||||||
"nanoid": "^5.1.6",
|
"nanoid": "^5.1.6",
|
||||||
"path-to-regexp": "^8.3.0",
|
"path-to-regexp": "^8.3.0",
|
||||||
"send": "^1.2.1",
|
"send": "^1.2.1",
|
||||||
@@ -35,250 +38,262 @@
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
"packages": {
|
"packages": {
|
||||||
"@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, ""],
|
"@babel/code-frame": ["@babel/code-frame@7.27.1", "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.27.1.tgz", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, ""],
|
||||||
|
|
||||||
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, ""],
|
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", {}, ""],
|
||||||
|
|
||||||
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, ""],
|
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", {}, ""],
|
||||||
|
|
||||||
"@kevisual/code-builder": ["@kevisual/code-builder@0.0.6", "", { "bin": { "code-builder": "bin/code.js", "builder": "bin/code.js" } }, "sha512-0aqATB31/yw4k4s5/xKnfr4DKbUnx8e3Z3BmKbiXTrc+CqWiWTdlGe9bKI9dZ2Df+xNp6g11W4xM2NICNyyCCw=="],
|
"@kevisual/code-builder": ["@kevisual/code-builder@0.0.6", "https://registry.npmmirror.com/@kevisual/code-builder/-/code-builder-0.0.6.tgz", { "bin": { "code-builder": "bin/code.js", "builder": "bin/code.js" } }, "sha512-0aqATB31/yw4k4s5/xKnfr4DKbUnx8e3Z3BmKbiXTrc+CqWiWTdlGe9bKI9dZ2Df+xNp6g11W4xM2NICNyyCCw=="],
|
||||||
|
|
||||||
"@kevisual/context": ["@kevisual/context@0.0.6", "", {}, "sha512-w7HBOuO3JH37n6xT6W3FD7ykqHTwtyxOQzTzfEcKDCbsvGB1wVreSxFm2bvoFnnFLuxT/5QMpKlnPrwvmcTGnw=="],
|
"@kevisual/context": ["@kevisual/context@0.0.8", "https://registry.npmmirror.com/@kevisual/context/-/context-0.0.8.tgz", {}, "sha512-DTJpyHI34NE76B7g6f+QlIqiCCyqI2qkBMQE736dzeRDGxOjnbe2iQY9W+Rt2PE6kmymM3qyOmSfNovyWyWrkA=="],
|
||||||
|
|
||||||
"@kevisual/dts": ["@kevisual/dts@0.0.4", "", { "dependencies": { "@rollup/plugin-commonjs": "^29.0.0", "@rollup/plugin-node-resolve": "^16.0.3", "@rollup/plugin-typescript": "^12.3.0", "rollup": "^4.57.1", "rollup-plugin-dts": "^6.3.0", "tslib": "^2.8.1" }, "bin": { "dts": "bin/dts.mjs" } }, "sha512-FVUaH/0nyhbHWpEVjFTGP54PLMm4Hf06aqWLdHOYHNPIgr1aK1C26kOH7iumklGFGk9w93IGxj8Zxe5fap5N2A=="],
|
"@kevisual/dts": ["@kevisual/dts@0.0.4", "https://registry.npmmirror.com/@kevisual/dts/-/dts-0.0.4.tgz", { "dependencies": { "@rollup/plugin-commonjs": "^29.0.0", "@rollup/plugin-node-resolve": "^16.0.3", "@rollup/plugin-typescript": "^12.3.0", "rollup": "^4.57.1", "rollup-plugin-dts": "^6.3.0", "tslib": "^2.8.1" }, "bin": { "dts": "bin/dts.mjs" } }, "sha512-FVUaH/0nyhbHWpEVjFTGP54PLMm4Hf06aqWLdHOYHNPIgr1aK1C26kOH7iumklGFGk9w93IGxj8Zxe5fap5N2A=="],
|
||||||
|
|
||||||
"@kevisual/js-filter": ["@kevisual/js-filter@0.0.5", "", {}, "sha512-+S+Sf3K/aP6XtZI2s7TgKOr35UuvUvtpJ9YDW30a+mY0/N8gRuzyKhieBzQN7Ykayzz70uoMavBXut2rUlLgzw=="],
|
"@kevisual/js-filter": ["@kevisual/js-filter@0.0.6", "", {}, "sha512-FcbOsmS1inhwrfgXMM/XLFTGTHUxBCss32JEMYdEFWQDYCar5rN8cxD1W8FuKDTVRlpA+zBpQ/BE6XT4UaeljA=="],
|
||||||
|
|
||||||
"@kevisual/load": ["@kevisual/load@0.0.6", "", { "dependencies": { "eventemitter3": "^5.0.1" } }, "sha512-+3YTFehRcZ1haGel5DKYMUwmi5i6f2psyaPZlfkKU/cOXgkpwoG9/BEqPCnPjicKqqnksEpixVRkyHJ+5bjLVA=="],
|
"@kevisual/load": ["@kevisual/load@0.0.6", "https://registry.npmmirror.com/@kevisual/load/-/load-0.0.6.tgz", { "dependencies": { "eventemitter3": "^5.0.1" } }, "sha512-+3YTFehRcZ1haGel5DKYMUwmi5i6f2psyaPZlfkKU/cOXgkpwoG9/BEqPCnPjicKqqnksEpixVRkyHJ+5bjLVA=="],
|
||||||
|
|
||||||
"@kevisual/local-proxy": ["@kevisual/local-proxy@0.0.8", "", {}, "sha512-VX/P+6/Cc8ruqp34ag6gVX073BchUmf5VNZcTV/6MJtjrNE76G8V6TLpBE8bywLnrqyRtFLIspk4QlH8up9B5Q=="],
|
"@kevisual/local-proxy": ["@kevisual/local-proxy@0.0.8", "https://registry.npmmirror.com/@kevisual/local-proxy/-/local-proxy-0.0.8.tgz", {}, "sha512-VX/P+6/Cc8ruqp34ag6gVX073BchUmf5VNZcTV/6MJtjrNE76G8V6TLpBE8bywLnrqyRtFLIspk4QlH8up9B5Q=="],
|
||||||
|
|
||||||
"@kevisual/query": ["@kevisual/query@0.0.47", "", {}, "sha512-ZR7WXeDDGUSzBtcGVU3J173sA0hCqrGTw5ybGbdNGlM0VyJV/XQIovCcSoZh1YpnciLRRqJvzXUgTnCkam+M3g=="],
|
"@kevisual/query": ["@kevisual/query@0.0.53", "https://registry.npmmirror.com/@kevisual/query/-/query-0.0.53.tgz", {}, "sha512-PAhpCLBr0emz0lGNlTVHMbJiC5wrtGLbInPddRzgKE35fiyNt+SWSsUWABiD0DeNrLN/OxWyAFobt880Z/e5MQ=="],
|
||||||
|
|
||||||
"@kevisual/use-config": ["@kevisual/use-config@1.0.30", "", { "dependencies": { "@kevisual/load": "^0.0.6" }, "peerDependencies": { "dotenv": "^17" } }, "sha512-kPdna0FW/X7D600aMdiZ5UTjbCo6d8d4jjauSc8RMmBwUU6WliFDSPUNKVpzm2BsDX5Nth1IXFPYMqH+wxqAmw=="],
|
"@kevisual/remote-app": ["@kevisual/remote-app@0.0.7", "", {}, "sha512-d0P8uyxoMnmyT8x1J9XC9ecDBbqW+jOP0ZM5fCgQRDUhWw35V/MnbCD4hNG4b6EmvoiS6a/PBC7RC5JGm3wpCg=="],
|
||||||
|
|
||||||
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
|
"@kevisual/use-config": ["@kevisual/use-config@1.0.30", "https://registry.npmmirror.com/@kevisual/use-config/-/use-config-1.0.30.tgz", { "dependencies": { "@kevisual/load": "^0.0.6" }, "peerDependencies": { "dotenv": "^17" } }, "sha512-kPdna0FW/X7D600aMdiZ5UTjbCo6d8d4jjauSc8RMmBwUU6WliFDSPUNKVpzm2BsDX5Nth1IXFPYMqH+wxqAmw=="],
|
||||||
|
|
||||||
"@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="],
|
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
|
||||||
|
|
||||||
"@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
|
"@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "https://registry.npmmirror.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="],
|
||||||
|
|
||||||
"@opencode-ai/plugin": ["@opencode-ai/plugin@1.2.6", "", { "dependencies": { "@opencode-ai/sdk": "1.2.6", "zod": "4.1.8" } }, "sha512-CJEp3k17yWsjyfivm3zQof8L42pdze3a7iTqMOyesHgJplSuLiBYAMndbBYMDuJkyAh0dHYjw8v10vVw7Kfl4Q=="],
|
"@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "https://registry.npmmirror.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
|
||||||
|
|
||||||
"@opencode-ai/sdk": ["@opencode-ai/sdk@1.2.6", "", {}, "sha512-dWMF8Aku4h7fh8sw5tQ2FtbqRLbIFT8FcsukpxTird49ax7oUXP+gzqxM/VdxHjfksQvzLBjLZyMdDStc5g7xA=="],
|
"@opencode-ai/plugin": ["@opencode-ai/plugin@1.2.26", "https://registry.npmmirror.com/@opencode-ai/plugin/-/plugin-1.2.26.tgz", { "dependencies": { "@opencode-ai/sdk": "1.2.26", "zod": "4.1.8" } }, "sha512-pC71KGAI9T0+S84KpbEq9THp5pT7KOq+GmfdXkvQ7KSH5zi+iASWRhqorir73sKmEj2MQfpbe1BxdcU5qbeOwA=="],
|
||||||
|
|
||||||
"@rollup/plugin-commonjs": ["@rollup/plugin-commonjs@29.0.0", "", { "dependencies": { "@rollup/pluginutils": "^5.0.1", "commondir": "^1.0.1", "estree-walker": "^2.0.2", "fdir": "^6.2.0", "is-reference": "1.2.1", "magic-string": "^0.30.3", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^2.68.0||^3.0.0||^4.0.0" } }, "sha512-U2YHaxR2cU/yAiwKJtJRhnyLk7cifnQw0zUpISsocBDoHDJn+HTV74ABqnwr5bEgWUwFZC9oFL6wLe21lHu5eQ=="],
|
"@opencode-ai/sdk": ["@opencode-ai/sdk@1.2.26", "https://registry.npmmirror.com/@opencode-ai/sdk/-/sdk-1.2.26.tgz", {}, "sha512-HPB+0pfvTMPj2KEjNLF3oqgldKW8koTJ7ssqXwzndazqxS+gUynzvdIKIQP4+QIInNcc5nJMG9JtfLcePGgTLQ=="],
|
||||||
|
|
||||||
"@rollup/plugin-node-resolve": ["@rollup/plugin-node-resolve@16.0.3", "", { "dependencies": { "@rollup/pluginutils": "^5.0.1", "@types/resolve": "1.20.2", "deepmerge": "^4.2.2", "is-module": "^1.0.0", "resolve": "^1.22.1" }, "peerDependencies": { "rollup": "^2.78.0||^3.0.0||^4.0.0" } }, "sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg=="],
|
"@rollup/plugin-commonjs": ["@rollup/plugin-commonjs@29.0.0", "https://registry.npmmirror.com/@rollup/plugin-commonjs/-/plugin-commonjs-29.0.0.tgz", { "dependencies": { "@rollup/pluginutils": "^5.0.1", "commondir": "^1.0.1", "estree-walker": "^2.0.2", "fdir": "^6.2.0", "is-reference": "1.2.1", "magic-string": "^0.30.3", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^2.68.0||^3.0.0||^4.0.0" } }, "sha512-U2YHaxR2cU/yAiwKJtJRhnyLk7cifnQw0zUpISsocBDoHDJn+HTV74ABqnwr5bEgWUwFZC9oFL6wLe21lHu5eQ=="],
|
||||||
|
|
||||||
"@rollup/plugin-typescript": ["@rollup/plugin-typescript@12.3.0", "", { "dependencies": { "@rollup/pluginutils": "^5.1.0", "resolve": "^1.22.1" }, "peerDependencies": { "rollup": "^2.14.0||^3.0.0||^4.0.0", "tslib": "*", "typescript": ">=3.7.0" } }, "sha512-7DP0/p7y3t67+NabT9f8oTBFE6gGkto4SA6Np2oudYmZE/m1dt8RB0SjL1msMxFpLo631qjRCcBlAbq1ml/Big=="],
|
"@rollup/plugin-node-resolve": ["@rollup/plugin-node-resolve@16.0.3", "https://registry.npmmirror.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.3.tgz", { "dependencies": { "@rollup/pluginutils": "^5.0.1", "@types/resolve": "1.20.2", "deepmerge": "^4.2.2", "is-module": "^1.0.0", "resolve": "^1.22.1" }, "peerDependencies": { "rollup": "^2.78.0||^3.0.0||^4.0.0" } }, "sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg=="],
|
||||||
|
|
||||||
"@rollup/pluginutils": ["@rollup/pluginutils@5.3.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" } }, ""],
|
"@rollup/plugin-typescript": ["@rollup/plugin-typescript@12.3.0", "https://registry.npmmirror.com/@rollup/plugin-typescript/-/plugin-typescript-12.3.0.tgz", { "dependencies": { "@rollup/pluginutils": "^5.1.0", "resolve": "^1.22.1" }, "peerDependencies": { "rollup": "^2.14.0||^3.0.0||^4.0.0", "tslib": "*", "typescript": ">=3.7.0" } }, "sha512-7DP0/p7y3t67+NabT9f8oTBFE6gGkto4SA6Np2oudYmZE/m1dt8RB0SjL1msMxFpLo631qjRCcBlAbq1ml/Big=="],
|
||||||
|
|
||||||
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.57.1", "", { "os": "android", "cpu": "arm" }, "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg=="],
|
"@rollup/pluginutils": ["@rollup/pluginutils@5.3.0", "https://registry.npmmirror.com/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" } }, ""],
|
||||||
|
|
||||||
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.57.1", "", { "os": "android", "cpu": "arm64" }, "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w=="],
|
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", { "os": "android", "cpu": "arm" }, "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg=="],
|
||||||
|
|
||||||
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.57.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg=="],
|
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", { "os": "android", "cpu": "arm64" }, "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w=="],
|
||||||
|
|
||||||
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.57.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w=="],
|
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg=="],
|
||||||
|
|
||||||
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.57.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug=="],
|
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", { "os": "darwin", "cpu": "x64" }, "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w=="],
|
||||||
|
|
||||||
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.57.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q=="],
|
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", { "os": "freebsd", "cpu": "arm64" }, "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug=="],
|
||||||
|
|
||||||
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.57.1", "", { "os": "linux", "cpu": "arm" }, "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw=="],
|
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", { "os": "freebsd", "cpu": "x64" }, "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q=="],
|
||||||
|
|
||||||
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.57.1", "", { "os": "linux", "cpu": "arm" }, "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw=="],
|
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", { "os": "linux", "cpu": "arm" }, "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw=="],
|
||||||
|
|
||||||
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.57.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g=="],
|
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", { "os": "linux", "cpu": "arm" }, "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw=="],
|
||||||
|
|
||||||
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.57.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q=="],
|
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g=="],
|
||||||
|
|
||||||
"@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA=="],
|
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q=="],
|
||||||
|
|
||||||
"@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw=="],
|
"@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", { "os": "linux", "cpu": "none" }, "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA=="],
|
||||||
|
|
||||||
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.57.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w=="],
|
"@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", { "os": "linux", "cpu": "none" }, "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw=="],
|
||||||
|
|
||||||
"@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.57.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw=="],
|
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", { "os": "linux", "cpu": "ppc64" }, "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w=="],
|
||||||
|
|
||||||
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A=="],
|
"@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", { "os": "linux", "cpu": "ppc64" }, "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw=="],
|
||||||
|
|
||||||
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw=="],
|
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", { "os": "linux", "cpu": "none" }, "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A=="],
|
||||||
|
|
||||||
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.57.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg=="],
|
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", { "os": "linux", "cpu": "none" }, "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw=="],
|
||||||
|
|
||||||
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.57.1", "", { "os": "linux", "cpu": "x64" }, "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg=="],
|
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", { "os": "linux", "cpu": "s390x" }, "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg=="],
|
||||||
|
|
||||||
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.57.1", "", { "os": "linux", "cpu": "x64" }, "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw=="],
|
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", { "os": "linux", "cpu": "x64" }, "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg=="],
|
||||||
|
|
||||||
"@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.57.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw=="],
|
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", { "os": "linux", "cpu": "x64" }, "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw=="],
|
||||||
|
|
||||||
"@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.57.1", "", { "os": "none", "cpu": "arm64" }, "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ=="],
|
"@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", { "os": "openbsd", "cpu": "x64" }, "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw=="],
|
||||||
|
|
||||||
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.57.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ=="],
|
"@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", { "os": "none", "cpu": "arm64" }, "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ=="],
|
||||||
|
|
||||||
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.57.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew=="],
|
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", { "os": "win32", "cpu": "arm64" }, "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ=="],
|
||||||
|
|
||||||
"@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.57.1", "", { "os": "win32", "cpu": "x64" }, "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ=="],
|
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", { "os": "win32", "cpu": "ia32" }, "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew=="],
|
||||||
|
|
||||||
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.57.1", "", { "os": "win32", "cpu": "x64" }, "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA=="],
|
"@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", { "os": "win32", "cpu": "x64" }, "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ=="],
|
||||||
|
|
||||||
"@types/bun": ["@types/bun@1.3.9", "", { "dependencies": { "bun-types": "1.3.9" } }, "sha512-KQ571yULOdWJiMH+RIWIOZ7B2RXQGpL1YQrBtLIV3FqDcCu6FsbFUBwhdKUlCKUpS3PJDsHlJ1QKlpxoVR+xtw=="],
|
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", { "os": "win32", "cpu": "x64" }, "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA=="],
|
||||||
|
|
||||||
"@types/estree": ["@types/estree@1.0.8", "", {}, ""],
|
"@types/bun": ["@types/bun@1.3.10", "https://registry.npmmirror.com/@types/bun/-/bun-1.3.10.tgz", { "dependencies": { "bun-types": "1.3.10" } }, "sha512-0+rlrUrOrTSskibryHbvQkDOWRJwJZqZlxrUs1u4oOoTln8+WIXBPmAuCF35SWB2z4Zl3E84Nl/D0P7803nigQ=="],
|
||||||
|
|
||||||
"@types/node": ["@types/node@25.2.3", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ=="],
|
"@types/estree": ["@types/estree@1.0.8", "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz", {}, ""],
|
||||||
|
|
||||||
"@types/resolve": ["@types/resolve@1.20.2", "", {}, ""],
|
"@types/node": ["@types/node@25.5.0", "https://registry.npmmirror.com/@types/node/-/node-25.5.0.tgz", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="],
|
||||||
|
|
||||||
"@types/send": ["@types/send@1.2.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ=="],
|
"@types/resolve": ["@types/resolve@1.20.2", "https://registry.npmmirror.com/@types/resolve/-/resolve-1.20.2.tgz", {}, ""],
|
||||||
|
|
||||||
"@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="],
|
"@types/send": ["@types/send@1.2.1", "https://registry.npmmirror.com/@types/send/-/send-1.2.1.tgz", { "dependencies": { "@types/node": "*" } }, "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ=="],
|
||||||
|
|
||||||
"@types/xml2js": ["@types/xml2js@0.4.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4YnrRemBShWRO2QjvUin8ESA41rH+9nQGLUGZV/1IDhi3SL9OhdpNC/MrulTWuptXKwhx/aDxE7toV0f/ypIXQ=="],
|
"@types/ws": ["@types/ws@8.18.1", "https://registry.npmmirror.com/@types/ws/-/ws-8.18.1.tgz", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="],
|
||||||
|
|
||||||
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, ""],
|
"@types/xml2js": ["@types/xml2js@0.4.14", "https://registry.npmmirror.com/@types/xml2js/-/xml2js-0.4.14.tgz", { "dependencies": { "@types/node": "*" } }, "sha512-4YnrRemBShWRO2QjvUin8ESA41rH+9nQGLUGZV/1IDhi3SL9OhdpNC/MrulTWuptXKwhx/aDxE7toV0f/ypIXQ=="],
|
||||||
|
|
||||||
"bun-types": ["bun-types@1.3.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-+UBWWOakIP4Tswh0Bt0QD0alpTY8cb5hvgiYeWCMet9YukHbzuruIEeXC2D7nMJPB12kbh8C7XJykSexEqGKJg=="],
|
"braces": ["braces@3.0.3", "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz", { "dependencies": { "fill-range": "^7.1.1" } }, ""],
|
||||||
|
|
||||||
"commondir": ["commondir@1.0.1", "", {}, ""],
|
"bun-types": ["bun-types@1.3.10", "https://registry.npmmirror.com/bun-types/-/bun-types-1.3.10.tgz", { "dependencies": { "@types/node": "*" } }, "sha512-tcpfCCl6XWo6nCVnpcVrxQ+9AYN1iqMIzgrSKYMB/fjLtV2eyAVEg7AxQJuCq/26R6HpKWykQXuSOq/21RYcbg=="],
|
||||||
|
|
||||||
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, ""],
|
"commander": ["commander@14.0.3", "https://registry.npmmirror.com/commander/-/commander-14.0.3.tgz", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="],
|
||||||
|
|
||||||
"deepmerge": ["deepmerge@4.3.1", "", {}, ""],
|
"commondir": ["commondir@1.0.1", "https://registry.npmmirror.com/commondir/-/commondir-1.0.1.tgz", {}, ""],
|
||||||
|
|
||||||
"depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
|
"debug": ["debug@4.4.3", "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", { "dependencies": { "ms": "^2.1.3" } }, ""],
|
||||||
|
|
||||||
"dotenv": ["dotenv@17.3.1", "", {}, "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA=="],
|
"deepmerge": ["deepmerge@4.3.1", "https://registry.npmmirror.com/deepmerge/-/deepmerge-4.3.1.tgz", {}, ""],
|
||||||
|
|
||||||
"ee-first": ["ee-first@1.1.1", "", {}, ""],
|
"depd": ["depd@2.0.0", "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
|
||||||
|
|
||||||
"encodeurl": ["encodeurl@2.0.0", "", {}, ""],
|
"dotenv": ["dotenv@17.3.1", "https://registry.npmmirror.com/dotenv/-/dotenv-17.3.1.tgz", {}, "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA=="],
|
||||||
|
|
||||||
"es-toolkit": ["es-toolkit@1.44.0", "", {}, "sha512-6penXeZalaV88MM3cGkFZZfOoLGWshWWfdy0tWw/RlVVyhvMaWSBTOvXNeiW3e5FwdS5ePW0LGEu17zT139ktg=="],
|
"ee-first": ["ee-first@1.1.1", "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz", {}, ""],
|
||||||
|
|
||||||
"escape-html": ["escape-html@1.0.3", "", {}, ""],
|
"encodeurl": ["encodeurl@2.0.0", "https://registry.npmmirror.com/encodeurl/-/encodeurl-2.0.0.tgz", {}, ""],
|
||||||
|
|
||||||
"estree-walker": ["estree-walker@2.0.2", "", {}, ""],
|
"es-toolkit": ["es-toolkit@1.45.1", "https://registry.npmmirror.com/es-toolkit/-/es-toolkit-1.45.1.tgz", {}, "sha512-/jhoOj/Fx+A+IIyDNOvO3TItGmlMKhtX8ISAHKE90c4b/k1tqaqEZ+uUqfpU8DMnW5cgNJv606zS55jGvza0Xw=="],
|
||||||
|
|
||||||
"etag": ["etag@1.8.1", "", {}, ""],
|
"escape-html": ["escape-html@1.0.3", "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz", {}, ""],
|
||||||
|
|
||||||
"eventemitter3": ["eventemitter3@5.0.4", "", {}, "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="],
|
"estree-walker": ["estree-walker@2.0.2", "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz", {}, ""],
|
||||||
|
|
||||||
"fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
|
"etag": ["etag@1.8.1", "https://registry.npmmirror.com/etag/-/etag-1.8.1.tgz", {}, ""],
|
||||||
|
|
||||||
"fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="],
|
"eventemitter3": ["eventemitter3@5.0.4", "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-5.0.4.tgz", {}, "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="],
|
||||||
|
|
||||||
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" } }, ""],
|
"fast-glob": ["fast-glob@3.3.3", "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.3.3.tgz", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
|
||||||
|
|
||||||
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, ""],
|
"fastq": ["fastq@1.20.1", "https://registry.npmmirror.com/fastq/-/fastq-1.20.1.tgz", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="],
|
||||||
|
|
||||||
"fresh": ["fresh@2.0.0", "", {}, ""],
|
"fdir": ["fdir@6.5.0", "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz", { "peerDependencies": { "picomatch": "^3 || ^4" } }, ""],
|
||||||
|
|
||||||
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
"fill-range": ["fill-range@7.1.1", "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz", { "dependencies": { "to-regex-range": "^5.0.1" } }, ""],
|
||||||
|
|
||||||
"function-bind": ["function-bind@1.1.2", "", {}, ""],
|
"fresh": ["fresh@2.0.0", "https://registry.npmmirror.com/fresh/-/fresh-2.0.0.tgz", {}, ""],
|
||||||
|
|
||||||
"glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
"fsevents": ["fsevents@2.3.3", "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
||||||
|
|
||||||
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, ""],
|
"function-bind": ["function-bind@1.1.2", "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", {}, ""],
|
||||||
|
|
||||||
"hono": ["hono@4.11.9", "", {}, "sha512-Eaw2YTGM6WOxA6CXbckaEvslr2Ne4NFsKrvc0v97JD5awbmeBLO5w9Ho9L9kmKonrwF9RJlW6BxT1PVv/agBHQ=="],
|
"glob-parent": ["glob-parent@5.1.2", "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
||||||
|
|
||||||
"http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="],
|
"hasown": ["hasown@2.0.2", "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz", { "dependencies": { "function-bind": "^1.1.2" } }, ""],
|
||||||
|
|
||||||
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
|
"hono": ["hono@4.12.8", "https://registry.npmmirror.com/hono/-/hono-4.12.8.tgz", {}, "sha512-VJCEvtrezO1IAR+kqEYnxUOoStaQPGrCmX3j4wDTNOcD1uRPFpGlwQUIW8niPuvHXaTUxeOUl5MMDGrl+tmO9A=="],
|
||||||
|
|
||||||
"is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, ""],
|
"http-errors": ["http-errors@2.0.1", "https://registry.npmmirror.com/http-errors/-/http-errors-2.0.1.tgz", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="],
|
||||||
|
|
||||||
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
|
"inherits": ["inherits@2.0.4", "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
|
||||||
|
|
||||||
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
|
"is-core-module": ["is-core-module@2.16.1", "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.16.1.tgz", { "dependencies": { "hasown": "^2.0.2" } }, ""],
|
||||||
|
|
||||||
"is-module": ["is-module@1.0.0", "", {}, ""],
|
"is-extglob": ["is-extglob@2.1.1", "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
|
||||||
|
|
||||||
"is-number": ["is-number@7.0.0", "", {}, ""],
|
"is-glob": ["is-glob@4.0.3", "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
|
||||||
|
|
||||||
"is-reference": ["is-reference@1.2.1", "", { "dependencies": { "@types/estree": "*" } }, ""],
|
"is-module": ["is-module@1.0.0", "https://registry.npmmirror.com/is-module/-/is-module-1.0.0.tgz", {}, ""],
|
||||||
|
|
||||||
"js-tokens": ["js-tokens@4.0.0", "", {}, ""],
|
"is-number": ["is-number@7.0.0", "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz", {}, ""],
|
||||||
|
|
||||||
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, ""],
|
"is-reference": ["is-reference@1.2.1", "https://registry.npmmirror.com/is-reference/-/is-reference-1.2.1.tgz", { "dependencies": { "@types/estree": "*" } }, ""],
|
||||||
|
|
||||||
"merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
|
"js-tokens": ["js-tokens@4.0.0", "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz", {}, ""],
|
||||||
|
|
||||||
"micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, ""],
|
"magic-string": ["magic-string@0.30.21", "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, ""],
|
||||||
|
|
||||||
"mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
|
"md5-typescript": ["md5-typescript@1.0.5", "", {}, "sha512-ovAc4EtiNt2dY8JPhPr/wkC9h4U5k/nuClNVcG0Ga3V1rMlYpAY24ZaaymFXJlz+ccJ6UMPo3FSaVKe7czBsXw=="],
|
||||||
|
|
||||||
"mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="],
|
"merge2": ["merge2@1.4.1", "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
|
||||||
|
|
||||||
"ms": ["ms@2.1.3", "", {}, ""],
|
"micromatch": ["micromatch@4.0.8", "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.8.tgz", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, ""],
|
||||||
|
|
||||||
"nanoid": ["nanoid@5.1.6", "", { "bin": "bin/nanoid.js" }, "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg=="],
|
"mime-db": ["mime-db@1.54.0", "https://registry.npmmirror.com/mime-db/-/mime-db-1.54.0.tgz", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
|
||||||
|
|
||||||
"on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, ""],
|
"mime-types": ["mime-types@3.0.2", "https://registry.npmmirror.com/mime-types/-/mime-types-3.0.2.tgz", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="],
|
||||||
|
|
||||||
"path-parse": ["path-parse@1.0.7", "", {}, ""],
|
"ms": ["ms@2.1.3", "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", {}, ""],
|
||||||
|
|
||||||
"path-to-regexp": ["path-to-regexp@8.3.0", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="],
|
"nanoid": ["nanoid@5.1.6", "https://registry.npmmirror.com/nanoid/-/nanoid-5.1.6.tgz", { "bin": "bin/nanoid.js" }, "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg=="],
|
||||||
|
|
||||||
"picocolors": ["picocolors@1.1.1", "", {}, ""],
|
"on-finished": ["on-finished@2.4.1", "https://registry.npmmirror.com/on-finished/-/on-finished-2.4.1.tgz", { "dependencies": { "ee-first": "1.1.1" } }, ""],
|
||||||
|
|
||||||
"picomatch": ["picomatch@4.0.3", "", {}, ""],
|
"path-parse": ["path-parse@1.0.7", "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz", {}, ""],
|
||||||
|
|
||||||
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
|
"path-to-regexp": ["path-to-regexp@8.3.0", "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-8.3.0.tgz", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="],
|
||||||
|
|
||||||
"range-parser": ["range-parser@1.2.1", "", {}, ""],
|
"picocolors": ["picocolors@1.1.1", "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", {}, ""],
|
||||||
|
|
||||||
"resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": "bin/resolve" }, ""],
|
"picomatch": ["picomatch@4.0.3", "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.3.tgz", {}, ""],
|
||||||
|
|
||||||
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
|
"queue-microtask": ["queue-microtask@1.2.3", "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
|
||||||
|
|
||||||
"rollup": ["rollup@4.57.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.57.1", "@rollup/rollup-android-arm64": "4.57.1", "@rollup/rollup-darwin-arm64": "4.57.1", "@rollup/rollup-darwin-x64": "4.57.1", "@rollup/rollup-freebsd-arm64": "4.57.1", "@rollup/rollup-freebsd-x64": "4.57.1", "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", "@rollup/rollup-linux-arm-musleabihf": "4.57.1", "@rollup/rollup-linux-arm64-gnu": "4.57.1", "@rollup/rollup-linux-arm64-musl": "4.57.1", "@rollup/rollup-linux-loong64-gnu": "4.57.1", "@rollup/rollup-linux-loong64-musl": "4.57.1", "@rollup/rollup-linux-ppc64-gnu": "4.57.1", "@rollup/rollup-linux-ppc64-musl": "4.57.1", "@rollup/rollup-linux-riscv64-gnu": "4.57.1", "@rollup/rollup-linux-riscv64-musl": "4.57.1", "@rollup/rollup-linux-s390x-gnu": "4.57.1", "@rollup/rollup-linux-x64-gnu": "4.57.1", "@rollup/rollup-linux-x64-musl": "4.57.1", "@rollup/rollup-openbsd-x64": "4.57.1", "@rollup/rollup-openharmony-arm64": "4.57.1", "@rollup/rollup-win32-arm64-msvc": "4.57.1", "@rollup/rollup-win32-ia32-msvc": "4.57.1", "@rollup/rollup-win32-x64-gnu": "4.57.1", "@rollup/rollup-win32-x64-msvc": "4.57.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A=="],
|
"range-parser": ["range-parser@1.2.1", "https://registry.npmmirror.com/range-parser/-/range-parser-1.2.1.tgz", {}, ""],
|
||||||
|
|
||||||
"rollup-plugin-dts": ["rollup-plugin-dts@6.3.0", "", { "dependencies": { "magic-string": "^0.30.21" }, "optionalDependencies": { "@babel/code-frame": "^7.27.1" }, "peerDependencies": { "rollup": "^3.29.4 || ^4", "typescript": "^4.5 || ^5.0" } }, "sha512-d0UrqxYd8KyZ6i3M2Nx7WOMy708qsV/7fTHMHxCMCBOAe3V/U7OMPu5GkX8hC+cmkHhzGnfeYongl1IgiooddA=="],
|
"resolve": ["resolve@1.22.11", "https://registry.npmmirror.com/resolve/-/resolve-1.22.11.tgz", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": "bin/resolve" }, ""],
|
||||||
|
|
||||||
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
|
"reusify": ["reusify@1.1.0", "https://registry.npmmirror.com/reusify/-/reusify-1.1.0.tgz", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
|
||||||
|
|
||||||
"sax": ["sax@1.4.3", "", {}, ""],
|
"rollup": ["rollup@4.57.1", "https://registry.npmmirror.com/rollup/-/rollup-4.57.1.tgz", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.57.1", "@rollup/rollup-android-arm64": "4.57.1", "@rollup/rollup-darwin-arm64": "4.57.1", "@rollup/rollup-darwin-x64": "4.57.1", "@rollup/rollup-freebsd-arm64": "4.57.1", "@rollup/rollup-freebsd-x64": "4.57.1", "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", "@rollup/rollup-linux-arm-musleabihf": "4.57.1", "@rollup/rollup-linux-arm64-gnu": "4.57.1", "@rollup/rollup-linux-arm64-musl": "4.57.1", "@rollup/rollup-linux-loong64-gnu": "4.57.1", "@rollup/rollup-linux-loong64-musl": "4.57.1", "@rollup/rollup-linux-ppc64-gnu": "4.57.1", "@rollup/rollup-linux-ppc64-musl": "4.57.1", "@rollup/rollup-linux-riscv64-gnu": "4.57.1", "@rollup/rollup-linux-riscv64-musl": "4.57.1", "@rollup/rollup-linux-s390x-gnu": "4.57.1", "@rollup/rollup-linux-x64-gnu": "4.57.1", "@rollup/rollup-linux-x64-musl": "4.57.1", "@rollup/rollup-openbsd-x64": "4.57.1", "@rollup/rollup-openharmony-arm64": "4.57.1", "@rollup/rollup-win32-arm64-msvc": "4.57.1", "@rollup/rollup-win32-ia32-msvc": "4.57.1", "@rollup/rollup-win32-x64-gnu": "4.57.1", "@rollup/rollup-win32-x64-msvc": "4.57.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A=="],
|
||||||
|
|
||||||
"send": ["send@1.2.1", "", { "dependencies": { "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.1", "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.2" } }, "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ=="],
|
"rollup-plugin-dts": ["rollup-plugin-dts@6.3.0", "https://registry.npmmirror.com/rollup-plugin-dts/-/rollup-plugin-dts-6.3.0.tgz", { "dependencies": { "magic-string": "^0.30.21" }, "optionalDependencies": { "@babel/code-frame": "^7.27.1" }, "peerDependencies": { "rollup": "^3.29.4 || ^4", "typescript": "^4.5 || ^5.0" } }, "sha512-d0UrqxYd8KyZ6i3M2Nx7WOMy708qsV/7fTHMHxCMCBOAe3V/U7OMPu5GkX8hC+cmkHhzGnfeYongl1IgiooddA=="],
|
||||||
|
|
||||||
"setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="],
|
"run-parallel": ["run-parallel@1.2.0", "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
|
||||||
|
|
||||||
"statuses": ["statuses@2.0.2", "", {}, ""],
|
"sax": ["sax@1.4.3", "https://registry.npmmirror.com/sax/-/sax-1.4.3.tgz", {}, ""],
|
||||||
|
|
||||||
"supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, ""],
|
"send": ["send@1.2.1", "https://registry.npmmirror.com/send/-/send-1.2.1.tgz", { "dependencies": { "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.1", "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.2" } }, "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ=="],
|
||||||
|
|
||||||
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, ""],
|
"setprototypeof": ["setprototypeof@1.2.0", "https://registry.npmmirror.com/setprototypeof/-/setprototypeof-1.2.0.tgz", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="],
|
||||||
|
|
||||||
"toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
|
"statuses": ["statuses@2.0.2", "https://registry.npmmirror.com/statuses/-/statuses-2.0.2.tgz", {}, ""],
|
||||||
|
|
||||||
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
"supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", {}, ""],
|
||||||
|
|
||||||
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
"to-regex-range": ["to-regex-range@5.0.1", "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz", { "dependencies": { "is-number": "^7.0.0" } }, ""],
|
||||||
|
|
||||||
"undici-types": ["undici-types@7.16.0", "", {}, ""],
|
"toidentifier": ["toidentifier@1.0.1", "https://registry.npmmirror.com/toidentifier/-/toidentifier-1.0.1.tgz", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
|
||||||
|
|
||||||
"ws": ["@kevisual/ws@8.0.0", "", {}, "sha512-jlFxSlXUEz93cFW+UYT5BXv/rFVgiMQnIfqRYZ0gj1hSP8PMGRqMqUoHSLfKvfRRS4jseLSvTTeEKSQpZJtURg=="],
|
"tslib": ["tslib@2.8.1", "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||||
|
|
||||||
"xml2js": ["xml2js@0.6.2", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA=="],
|
"typescript": ["typescript@5.9.3", "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||||
|
|
||||||
"xmlbuilder": ["xmlbuilder@11.0.1", "", {}, ""],
|
"undici-types": ["undici-types@7.18.2", "https://registry.npmmirror.com/undici-types/-/undici-types-7.18.2.tgz", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
|
||||||
|
|
||||||
"zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
|
"ws": ["@kevisual/ws@8.0.0", "https://registry.npmmirror.com/@kevisual/ws/-/ws-8.0.0.tgz", {}, "sha512-jlFxSlXUEz93cFW+UYT5BXv/rFVgiMQnIfqRYZ0gj1hSP8PMGRqMqUoHSLfKvfRRS4jseLSvTTeEKSQpZJtURg=="],
|
||||||
|
|
||||||
"@opencode-ai/plugin/zod": ["zod@4.1.8", "", {}, "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ=="],
|
"xml2js": ["xml2js@0.6.2", "https://registry.npmmirror.com/xml2js/-/xml2js-0.6.2.tgz", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA=="],
|
||||||
|
|
||||||
"@types/send/@types/node": ["@types/node@25.0.3", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA=="],
|
"xmlbuilder": ["xmlbuilder@11.0.1", "https://registry.npmmirror.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz", {}, ""],
|
||||||
|
|
||||||
"@types/ws/@types/node": ["@types/node@25.0.3", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA=="],
|
"zod": ["zod@4.3.6", "https://registry.npmmirror.com/zod/-/zod-4.3.6.tgz", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
|
||||||
|
|
||||||
"@types/xml2js/@types/node": ["@types/node@25.0.3", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA=="],
|
"@opencode-ai/plugin/zod": ["zod@4.1.8", "https://registry.npmmirror.com/zod/-/zod-4.1.8.tgz", {}, "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ=="],
|
||||||
|
|
||||||
"micromatch/picomatch": ["picomatch@2.3.1", "", {}, ""],
|
"@types/send/@types/node": ["@types/node@25.0.3", "https://registry.npmmirror.com/@types/node/-/node-25.0.3.tgz", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA=="],
|
||||||
|
|
||||||
|
"@types/ws/@types/node": ["@types/node@25.0.3", "https://registry.npmmirror.com/@types/node/-/node-25.0.3.tgz", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA=="],
|
||||||
|
|
||||||
|
"@types/xml2js/@types/node": ["@types/node@25.0.3", "https://registry.npmmirror.com/@types/node/-/node-25.0.3.tgz", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA=="],
|
||||||
|
|
||||||
|
"micromatch/picomatch": ["picomatch@2.3.1", "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz", {}, ""],
|
||||||
|
|
||||||
|
"@types/send/@types/node/undici-types": ["undici-types@7.16.0", "https://registry.npmmirror.com/undici-types/-/undici-types-7.16.0.tgz", {}, ""],
|
||||||
|
|
||||||
|
"@types/ws/@types/node/undici-types": ["undici-types@7.16.0", "https://registry.npmmirror.com/undici-types/-/undici-types-7.16.0.tgz", {}, ""],
|
||||||
|
|
||||||
|
"@types/xml2js/@types/node/undici-types": ["undici-types@7.16.0", "https://registry.npmmirror.com/undici-types/-/undici-types-7.16.0.tgz", {}, ""],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ app.route({
|
|||||||
},
|
},
|
||||||
}).define(async (ctx) => {
|
}).define(async (ctx) => {
|
||||||
ctx.body = '03';
|
ctx.body = '03';
|
||||||
|
ctx.args.test
|
||||||
return ctx;
|
return ctx;
|
||||||
}).addTo(app);
|
}).addTo(app);
|
||||||
// app.server.on({
|
// app.server.on({
|
||||||
|
|||||||
@@ -18,15 +18,6 @@ route01.run = async (ctx) => {
|
|||||||
ctx.body = '01';
|
ctx.body = '01';
|
||||||
return ctx;
|
return ctx;
|
||||||
};
|
};
|
||||||
app.use(
|
|
||||||
'demo',
|
|
||||||
async (ctx) => {
|
|
||||||
ctx.body = '01';
|
|
||||||
return ctx;
|
|
||||||
},
|
|
||||||
{ key: '01' },
|
|
||||||
);
|
|
||||||
|
|
||||||
const route02 = new Route('demo', '02');
|
const route02 = new Route('demo', '02');
|
||||||
route02.run = async (ctx) => {
|
route02.run = async (ctx) => {
|
||||||
ctx.body = '02';
|
ctx.body = '02';
|
||||||
|
|||||||
@@ -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) => {
|
.define(async (ctx) => {
|
||||||
ctx.body = 'Hello, world!';
|
ctx.body = 'Hello, world!';
|
||||||
// throw new CustomError('error');
|
// throw new CustomError('error');
|
||||||
throw new CustomError(5000, 'error');
|
ctx.throw(5000, 'error');
|
||||||
})
|
})
|
||||||
.addTo(router);
|
.addTo(router);
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
app
|
||||||
.route({
|
.route({
|
||||||
path: 'hello',
|
path: 'hello',
|
||||||
|
metadata: {
|
||||||
|
args: {
|
||||||
|
name: 'string',
|
||||||
|
},
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.define(async (ctx) => {
|
.define(async (ctx) => {
|
||||||
// console.log('hello', ctx);
|
// console.log('hello', ctx);
|
||||||
// console.log('hello', ctx.res);
|
// console.log('hello', ctx.res);
|
||||||
|
ctx.query.name;
|
||||||
console.log('hello', ctx.query.cookies);
|
console.log('hello', ctx.query.cookies);
|
||||||
// ctx.res?.cookie?.('token', 'abc', {
|
// ctx.res?.cookie?.('token', 'abc', {
|
||||||
// domain: '*', // 设置为顶级域名,允许跨子域共享
|
// domain: '*', // 设置为顶级域名,允许跨子域共享
|
||||||
|
|||||||
@@ -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.importRoutes(app2.exportRoutes());
|
||||||
|
|
||||||
app.importApp(app3);
|
|
||||||
|
|
||||||
app.listen(4003, () => {
|
app.listen(4003, () => {
|
||||||
console.log(`http://localhost:4003/api/router?path=app1&key=02`);
|
console.log(`http://localhost:4003/api/router?path=app1&key=02`);
|
||||||
console.log(`http://localhost:4003/api/router?path=app1&key=01`);
|
console.log(`http://localhost:4003/api/router?path=app1&key=01`);
|
||||||
|
|||||||
@@ -26,41 +26,6 @@ qr.add(
|
|||||||
description: 'get project detail2',
|
description: 'get project detail2',
|
||||||
run: async (ctx: RouteContext) => {
|
run: async (ctx: RouteContext) => {
|
||||||
ctx!.body = 'project detail2';
|
ctx!.body = 'project detail2';
|
||||||
return ctx;
|
|
||||||
},
|
|
||||||
validator: {
|
|
||||||
id: {
|
|
||||||
type: 'number',
|
|
||||||
required: true,
|
|
||||||
message: 'id is required',
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
// @ts-ignore
|
|
||||||
type: 'object',
|
|
||||||
message: 'data query is error',
|
|
||||||
properties: {
|
|
||||||
name: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
message: 'name is required',
|
|
||||||
},
|
|
||||||
age: {
|
|
||||||
type: 'number',
|
|
||||||
required: true,
|
|
||||||
message: 'age is error',
|
|
||||||
},
|
|
||||||
friends: {
|
|
||||||
type: 'object',
|
|
||||||
properties: {
|
|
||||||
hair: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
message: 'hair is required',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -73,7 +38,7 @@ const main = async () => {
|
|||||||
id: 4,
|
id: 4,
|
||||||
data: {
|
data: {
|
||||||
name: 'john',
|
name: 'john',
|
||||||
age: 's'+13,
|
age: 's' + 13,
|
||||||
friends: {
|
friends: {
|
||||||
hair: 'black',
|
hair: 'black',
|
||||||
messages: 'hello',
|
messages: 'hello',
|
||||||
|
|||||||
31
package.json
31
package.json
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package",
|
"$schema": "https://json.schemastore.org/package",
|
||||||
"name": "@kevisual/router",
|
"name": "@kevisual/router",
|
||||||
"version": "0.0.80",
|
"version": "0.1.6",
|
||||||
"description": "",
|
"description": "",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/router.js",
|
"main": "./dist/router.js",
|
||||||
@@ -23,35 +23,41 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@kevisual/code-builder": "^0.0.6",
|
"@kevisual/code-builder": "^0.0.6",
|
||||||
"@kevisual/context": "^0.0.6",
|
"@kevisual/context": "^0.0.8",
|
||||||
"@kevisual/dts": "^0.0.4",
|
"@kevisual/dts": "^0.0.4",
|
||||||
"@kevisual/js-filter": "^0.0.5",
|
"@kevisual/js-filter": "^0.0.6",
|
||||||
"@kevisual/local-proxy": "^0.0.8",
|
"@kevisual/local-proxy": "^0.0.8",
|
||||||
"@kevisual/query": "^0.0.47",
|
"@kevisual/query": "^0.0.53",
|
||||||
|
"@kevisual/remote-app": "^0.0.7",
|
||||||
"@kevisual/use-config": "^1.0.30",
|
"@kevisual/use-config": "^1.0.30",
|
||||||
"@opencode-ai/plugin": "^1.2.6",
|
"@opencode-ai/plugin": "^1.2.27",
|
||||||
"@types/bun": "^1.3.9",
|
"@types/bun": "^1.3.10",
|
||||||
"@types/node": "^25.2.3",
|
"@types/crypto-js": "^4.2.2",
|
||||||
|
"@types/node": "^25.5.0",
|
||||||
"@types/send": "^1.2.1",
|
"@types/send": "^1.2.1",
|
||||||
"@types/ws": "^8.18.1",
|
"@types/ws": "^8.18.1",
|
||||||
"@types/xml2js": "^0.4.14",
|
"@types/xml2js": "^0.4.14",
|
||||||
|
"commander": "^14.0.3",
|
||||||
|
"crypto-js": "^4.2.0",
|
||||||
|
"es-toolkit": "^1.45.1",
|
||||||
"eventemitter3": "^5.0.4",
|
"eventemitter3": "^5.0.4",
|
||||||
"fast-glob": "^3.3.3",
|
"fast-glob": "^3.3.3",
|
||||||
"hono": "^4.11.9",
|
"hono": "^4.12.8",
|
||||||
"nanoid": "^5.1.6",
|
"nanoid": "^5.1.7",
|
||||||
"path-to-regexp": "^8.3.0",
|
"path-to-regexp": "^8.3.0",
|
||||||
"send": "^1.2.1",
|
"send": "^1.2.1",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
"ws": "npm:@kevisual/ws",
|
"ws": "npm:@kevisual/ws",
|
||||||
"xml2js": "^0.6.2",
|
"xml2js": "^0.6.2"
|
||||||
"zod": "^4.3.6"
|
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/abearxiong/kevisual-router.git"
|
"url": "git+https://github.com/abearxiong/kevisual-router.git"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"es-toolkit": "^1.44.0"
|
"crypto-js": "^4.2.0",
|
||||||
|
"es-toolkit": "^1.45.1",
|
||||||
|
"zod": "^4.3.6"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
@@ -59,6 +65,7 @@
|
|||||||
"exports": {
|
"exports": {
|
||||||
".": "./dist/router.js",
|
".": "./dist/router.js",
|
||||||
"./browser": "./dist/router-browser.js",
|
"./browser": "./dist/router-browser.js",
|
||||||
|
"./commander": "./dist/commander.js",
|
||||||
"./simple": "./dist/router-simple.js",
|
"./simple": "./dist/router-simple.js",
|
||||||
"./opencode": "./dist/opencode.js",
|
"./opencode": "./dist/opencode.js",
|
||||||
"./skill": "./dist/app.js",
|
"./skill": "./dist/app.js",
|
||||||
|
|||||||
1212
pnpm-lock.yaml
generated
Normal file
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` 访问以下属性:
|
在 route handler 中,你可以通过 `ctx` 访问以下属性:
|
||||||
|
|
||||||
| 属性 | 类型 | 说明 |
|
| 属性 | 类型 | 说明 |
|
||||||
|------|------|------|
|
| --------------- | ---------------------------- | ---------------------------- |
|
||||||
| `query` | `object` | 请求参数,会自动合并 payload |
|
| `query` | `object` | 请求参数,会自动合并 payload |
|
||||||
| `body` | `number \| string \| Object` | 响应内容 |
|
| `body` | `number \| string \| Object` | 响应内容 |
|
||||||
| `code` | `number` | 响应状态码,默认为 200 |
|
| `code` | `number` | 响应状态码,默认为 200 |
|
||||||
| `message` | `string` | 响应消息 |
|
| `message` | `string` | 响应消息 |
|
||||||
| `state` | `any` | 状态数据,可在路由间传递 |
|
| `state` | `any` | 状态数据,可在路由间传递 |
|
||||||
| `appId` | `string` | 应用标识 |
|
| `appId` | `string` | 应用标识 |
|
||||||
|
| `currentId` | `string` | 当前路由ID |
|
||||||
| `currentPath` | `string` | 当前路由路径 |
|
| `currentPath` | `string` | 当前路由路径 |
|
||||||
| `currentKey` | `string` | 当前路由 key |
|
| `currentKey` | `string` | 当前路由 key |
|
||||||
| `currentRoute` | `Route` | 当前 Route 实例 |
|
| `currentRoute` | `Route` | 当前 Route 实例 |
|
||||||
@@ -53,7 +54,7 @@ app
|
|||||||
### 上下文方法
|
### 上下文方法
|
||||||
|
|
||||||
| 方法 | 参数 | 说明 |
|
| 方法 | 参数 | 说明 |
|
||||||
|------|------|------|
|
| ----------------------------------- | ----------------------------------------- | -------------------------------------------- |
|
||||||
| `ctx.call(msg, ctx?)` | `{ path, key?, payload?, ... } \| { id }` | 调用其他路由,返回完整 context |
|
| `ctx.call(msg, ctx?)` | `{ path, key?, payload?, ... } \| { id }` | 调用其他路由,返回完整 context |
|
||||||
| `ctx.run(msg, ctx?)` | `{ path, key?, payload? }` | 调用其他路由,返回 `{ code, data, message }` |
|
| `ctx.run(msg, ctx?)` | `{ path, key?, payload? }` | 调用其他路由,返回 `{ code, data, message }` |
|
||||||
| `ctx.forward(res)` | `{ code, data?, message? }` | 设置响应结果 |
|
| `ctx.forward(res)` | `{ code, data?, message? }` | 设置响应结果 |
|
||||||
@@ -63,31 +64,25 @@ app
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { App } from '@kevisual/router';
|
import { App } from '@kevisual/router';
|
||||||
|
import z from 'zod';
|
||||||
const app = new App();
|
const app = new App();
|
||||||
app.listen(4002);
|
app.listen(4002);
|
||||||
|
|
||||||
// 基本路由
|
// 基本路由
|
||||||
app
|
app
|
||||||
.route({ path: 'user', key: 'info' })
|
.route({ path: 'user', key: 'info', id: 'user-info' })
|
||||||
.define(async (ctx) => {
|
.define(async (ctx) => {
|
||||||
// ctx.query 包含请求参数
|
// ctx.query 包含请求参数
|
||||||
const { id } = ctx.query;
|
const { id } = ctx.query;
|
||||||
|
// 使用 state 在路由间传递数据
|
||||||
|
ctx.state.orderId = '12345';
|
||||||
ctx.body = { id, name: '张三' };
|
ctx.body = { id, name: '张三' };
|
||||||
ctx.code = 200;
|
ctx.code = 200;
|
||||||
})
|
})
|
||||||
.addTo(app);
|
.addTo(app);
|
||||||
|
|
||||||
// 使用 state 在路由间传递数据
|
|
||||||
app
|
app
|
||||||
.route({ path: 'order', key: 'create' })
|
.route({ path: 'order', key: 'pay', middleware: ['user-info'] })
|
||||||
.define(async (ctx) => {
|
|
||||||
ctx.state = { orderId: '12345' };
|
|
||||||
})
|
|
||||||
.addTo(app);
|
|
||||||
|
|
||||||
app
|
|
||||||
.route({ path: 'order', key: 'pay' })
|
|
||||||
.define(async (ctx) => {
|
.define(async (ctx) => {
|
||||||
// 可以获取前一个路由设置的 state
|
// 可以获取前一个路由设置的 state
|
||||||
const { orderId } = ctx.state;
|
const { orderId } = ctx.state;
|
||||||
@@ -95,14 +90,6 @@ app
|
|||||||
})
|
})
|
||||||
.addTo(app);
|
.addTo(app);
|
||||||
|
|
||||||
// 链式调用
|
|
||||||
app
|
|
||||||
.route({ path: 'product', key: 'list' })
|
|
||||||
.define(async (ctx) => {
|
|
||||||
ctx.body = [{ id: 1, name: '商品A' }];
|
|
||||||
})
|
|
||||||
.addTo(app);
|
|
||||||
|
|
||||||
// 调用其他路由
|
// 调用其他路由
|
||||||
app
|
app
|
||||||
.route({ path: 'dashboard', key: 'stats' })
|
.route({ path: 'dashboard', key: 'stats' })
|
||||||
@@ -114,7 +101,7 @@ app
|
|||||||
|
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
user: userRes.data,
|
user: userRes.data,
|
||||||
products: productRes.data
|
products: productRes.data,
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.addTo(app);
|
.addTo(app);
|
||||||
@@ -140,17 +127,20 @@ import { App, Route } from '@kevisual/router';
|
|||||||
const app = new App();
|
const app = new App();
|
||||||
|
|
||||||
// 定义中间件
|
// 定义中间件
|
||||||
app.route({
|
app
|
||||||
|
.route({
|
||||||
id: 'auth-example',
|
id: 'auth-example',
|
||||||
description: '权限校验中间件'
|
description: '权限校验中间件',
|
||||||
}).define(async(ctx) => {
|
})
|
||||||
|
.define(async (ctx) => {
|
||||||
const token = ctx.query.token;
|
const token = ctx.query.token;
|
||||||
if (!token) {
|
if (!token) {
|
||||||
ctx.throw(401, '未登录', '需要 token');
|
ctx.throw(401, '未登录', '需要 token');
|
||||||
}
|
}
|
||||||
// 验证通过,设置用户信息到 state
|
// 验证通过,设置用户信息到 state
|
||||||
ctx.state.tokenUser = { id: 1, name: '用户A' };
|
ctx.state.tokenUser = { id: 1, name: '用户A' };
|
||||||
}).addTo(app);
|
})
|
||||||
|
.addTo(app);
|
||||||
|
|
||||||
// 使用中间件(通过 id 引用)
|
// 使用中间件(通过 id 引用)
|
||||||
app
|
app
|
||||||
@@ -163,6 +153,33 @@ app
|
|||||||
.addTo(app);
|
.addTo(app);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 一个丰富的router示例
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { App } from '@kevisual/router';
|
||||||
|
const app = new App();
|
||||||
|
|
||||||
|
app
|
||||||
|
.router({
|
||||||
|
path: 'dog',
|
||||||
|
key: 'info',
|
||||||
|
description: '获取狗的信息',
|
||||||
|
metedata: {
|
||||||
|
args: {
|
||||||
|
owner: z.string().describe('狗主人姓名'),
|
||||||
|
age: z.number().describe('狗的年龄'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.define(async (ctx) => {
|
||||||
|
const { owner, age } = ctx.query;
|
||||||
|
ctx.body = {
|
||||||
|
content: `这是一只${age}岁的狗,主人是${owner}`,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.addTo(app);
|
||||||
|
```
|
||||||
|
|
||||||
## 注意事项
|
## 注意事项
|
||||||
|
|
||||||
1. **path 和 key 的组合是路由的唯一标识**,同一个 path+key 只能添加一个路由,后添加的会覆盖之前的。
|
1. **path 和 key 的组合是路由的唯一标识**,同一个 path+key 只能添加一个路由,后添加的会覆盖之前的。
|
||||||
@@ -173,16 +190,14 @@ app
|
|||||||
|
|
||||||
3. **ctx.throw 会自动结束执行**,抛出自定义错误。
|
3. **ctx.throw 会自动结束执行**,抛出自定义错误。
|
||||||
|
|
||||||
4. **state 不会自动继承**,每个路由的 state 是独立的,除非显式传递或使用 nextRoute。
|
4. **payload 会自动合并到 query**,调用 `ctx.run({ path, key, payload })` 时,payload 会合并到 query。
|
||||||
|
|
||||||
5. **payload 会自动合并到 query**,调用 `ctx.run({ path, key, payload })` 时,payload 会合并到 query。
|
5. **nextQuery 用于传递给 nextRoute**,在当前路由中设置 `ctx.nextQuery`,会在执行 nextRoute 时合并到 query。
|
||||||
|
|
||||||
6. **nextQuery 用于传递给 nextRoute**,在当前路由中设置 `ctx.nextQuery`,会在执行 nextRoute 时合并到 query。
|
6. **避免 nextRoute 循环调用**,默认最大深度为 40 次,超过会返回 500 错误。
|
||||||
|
|
||||||
7. **避免 nextRoute 循环调用**,默认最大深度为 40 次,超过会返回 500 错误。
|
7. **needSerialize 默认为 true**,会自动对 body 进行 JSON 序列化和反序列化。
|
||||||
|
|
||||||
8. **needSerialize 默认为 true**,会自动对 body 进行 JSON 序列化和反序列化。
|
8. **progress 记录执行路径**,可用于调试和追踪路由调用链。
|
||||||
|
|
||||||
9. **progress 记录执行路径**,可用于调试和追踪路由调用链。
|
9. **中间件找不到会返回 404**,错误信息中会包含找不到的中间件列表。
|
||||||
|
|
||||||
10. **中间件找不到会返回 404**,错误信息中会包含找不到的中间件列表。
|
|
||||||
|
|||||||
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 { ServerNode, ServerNodeOpts } from './server/server.ts';
|
||||||
import { HandleCtx } from './server/server-base.ts';
|
import { HandleCtx } from './server/server-base.ts';
|
||||||
import { ServerType } from './server/server-type.ts';
|
import { ServerType } from './server/server-type.ts';
|
||||||
@@ -10,7 +10,7 @@ import { randomId } from './utils/random.ts';
|
|||||||
|
|
||||||
type RouterHandle = (msg: { path: string;[key: string]: any }) => { code: string; data?: any; message?: string;[key: string]: any };
|
type RouterHandle = (msg: { path: string;[key: string]: any }) => { code: string; data?: any; message?: string;[key: string]: any };
|
||||||
type AppOptions<T = {}> = {
|
type AppOptions<T = {}> = {
|
||||||
router?: QueryRouter;
|
router?: QueryRouterServer;
|
||||||
server?: ServerType;
|
server?: ServerType;
|
||||||
/** handle msg 关联 */
|
/** handle msg 关联 */
|
||||||
routerHandle?: RouterHandle;
|
routerHandle?: RouterHandle;
|
||||||
@@ -19,18 +19,19 @@ type AppOptions<T = {}> = {
|
|||||||
appId?: string;
|
appId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AppRouteContext<T = {}> = HandleCtx & RouteContext<T> & { app: App<T> };
|
export type AppRouteContext<T> = HandleCtx & RouteContext<T> & { app: App<T> };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 封装了 Router 和 Server 的 App 模块,处理http的请求和响应,内置了 Cookie 和 Token 和 res 的处理
|
* 封装了 Router 和 Server 的 App 模块,处理http的请求和响应,内置了 Cookie 和 Token 和 res 的处理
|
||||||
* U - Route Context的扩展类型
|
* U - Route Context的扩展类型
|
||||||
*/
|
*/
|
||||||
export class App<U = {}> extends QueryRouter {
|
export class App<U = {}> extends QueryRouterServer<AppRouteContext<U>> {
|
||||||
declare appId: string;
|
declare appId: string;
|
||||||
router: QueryRouter;
|
router: QueryRouterServer;
|
||||||
server: ServerType;
|
server: ServerType;
|
||||||
|
declare context: AppRouteContext<U>;
|
||||||
constructor(opts?: AppOptions<U>) {
|
constructor(opts?: AppOptions<U>) {
|
||||||
super();
|
super({ initHandle: false, context: { needSerialize: true, ...opts?.routerContext } as any });
|
||||||
const router = this;
|
const router = this;
|
||||||
let server = opts?.server;
|
let server = opts?.server;
|
||||||
if (!server) {
|
if (!server) {
|
||||||
@@ -42,7 +43,6 @@ export class App<U = {}> extends QueryRouter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
server.setHandle(router.getHandle(router, opts?.routerHandle, opts?.routerContext));
|
server.setHandle(router.getHandle(router, opts?.routerHandle, opts?.routerContext));
|
||||||
router.setContext({ needSerialize: true, ...opts?.routerContext });
|
|
||||||
this.router = router;
|
this.router = router;
|
||||||
this.server = server;
|
this.server = server;
|
||||||
if (opts?.appId) {
|
if (opts?.appId) {
|
||||||
@@ -64,50 +64,7 @@ export class App<U = {}> extends QueryRouter {
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
this.server.listen(...args);
|
this.server.listen(...args);
|
||||||
}
|
}
|
||||||
addRoute(route: Route, opts?: AddOpts) {
|
|
||||||
super.add(route, opts);
|
|
||||||
}
|
|
||||||
|
|
||||||
Route = Route;
|
Route = Route;
|
||||||
route(opts: RouteOpts<AppRouteContext<U>>): Route<AppRouteContext<U>>;
|
|
||||||
route(path: string, key?: string): Route<AppRouteContext<U>>;
|
|
||||||
route(path: string, opts?: RouteOpts<AppRouteContext<U>>): Route<AppRouteContext<U>>;
|
|
||||||
route(path: string, key?: string, opts?: RouteOpts<AppRouteContext<U>>): Route<AppRouteContext<U>>;
|
|
||||||
route(...args: any[]) {
|
|
||||||
const [path, key, opts] = args;
|
|
||||||
if (typeof path === 'object') {
|
|
||||||
return new Route(path.path, path.key, path);
|
|
||||||
}
|
|
||||||
if (typeof path === 'string') {
|
|
||||||
if (opts) {
|
|
||||||
return new Route(path, key, opts);
|
|
||||||
}
|
|
||||||
if (key && typeof key === 'object') {
|
|
||||||
return new Route(path, key?.key || '', key);
|
|
||||||
}
|
|
||||||
return new Route(path, key);
|
|
||||||
}
|
|
||||||
return new Route(path, key, opts);
|
|
||||||
}
|
|
||||||
prompt(description: string): Route<AppRouteContext<U>>
|
|
||||||
prompt(description: Function): Route<AppRouteContext<U>>
|
|
||||||
prompt(...args: any[]) {
|
|
||||||
const [desc] = args;
|
|
||||||
let description = ''
|
|
||||||
if (typeof desc === 'string') {
|
|
||||||
description = desc;
|
|
||||||
} else if (typeof desc === 'function') {
|
|
||||||
description = desc() || ''; // 如果是Promise,需要addTo App之前就要获取应有的函数了。
|
|
||||||
}
|
|
||||||
return new Route('', '', { description });
|
|
||||||
}
|
|
||||||
|
|
||||||
async call(message: { id?: string, path?: string; key?: string; payload?: any }, ctx?: AppRouteContext<U> & { [key: string]: any }) {
|
|
||||||
return await super.call(message, ctx);
|
|
||||||
}
|
|
||||||
async run(msg: { id?: string, path?: string; key?: string; payload?: any }, ctx?: Partial<AppRouteContext<U>> & { [key: string]: any }) {
|
|
||||||
return await super.run(msg, ctx);
|
|
||||||
}
|
|
||||||
static handleRequest(req: IncomingMessage, res: ServerResponse) {
|
static handleRequest(req: IncomingMessage, res: ServerResponse) {
|
||||||
return handleServer(req, res);
|
return handleServer(req, res);
|
||||||
}
|
}
|
||||||
|
|||||||
255
src/commander.ts
Normal file
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',
|
path: 'call',
|
||||||
key: '',
|
key: '',
|
||||||
description: '调用',
|
description: '调用',
|
||||||
middleware: ['auth'],
|
middleware: ['auth-admin'],
|
||||||
metadata: {
|
metadata: {
|
||||||
tags: ['opencode'],
|
tags: ['opencode'],
|
||||||
...createSkill({
|
...createSkill({
|
||||||
@@ -24,7 +24,7 @@ export const addCallFn = (app: App) => {
|
|||||||
args: {
|
args: {
|
||||||
path: tool.schema.string().describe('应用路径,例如 cnb'),
|
path: tool.schema.string().describe('应用路径,例如 cnb'),
|
||||||
key: tool.schema.string().optional().describe('应用key,例如 list-repos'),
|
key: tool.schema.string().optional().describe('应用key,例如 list-repos'),
|
||||||
payload: tool.schema.object({}).optional().describe('调用参数'),
|
payload: tool.schema.object({}).optional().describe('调用参数, 为对象, 例如 { "query": "javascript" }'),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@@ -59,9 +59,16 @@ export const createRouterAgentPluginFn = (opts?: {
|
|||||||
if (!router.hasRoute('call', '')) {
|
if (!router.hasRoute('call', '')) {
|
||||||
addCallFn(router as App)
|
addCallFn(router as App)
|
||||||
}
|
}
|
||||||
if (!router.hasRoute('auth', '')) {
|
if (router) {
|
||||||
router.route({ path: 'auth', key: '', id: 'auth', description: '认证' }).define(async (ctx) => { }).addTo(router as App)
|
(router as any).route({ path: 'auth', key: '', id: 'auth', description: '认证' }).define(async (ctx) => { }).addTo(router as App, {
|
||||||
|
overwrite: false
|
||||||
|
});
|
||||||
|
|
||||||
|
(router as any).route({ path: 'auth-admin', key: '', id: 'auth-admin', description: '认证' }).define(async (ctx) => { }).addTo(router as App, {
|
||||||
|
overwrite: false
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const _routes = filter(router.routes, opts?.query || '')
|
const _routes = filter(router.routes, opts?.query || '')
|
||||||
const routes = _routes.filter(r => {
|
const routes = _routes.filter(r => {
|
||||||
const metadata = r.metadata as Skill
|
const metadata = r.metadata as Skill
|
||||||
@@ -75,7 +82,7 @@ export const createRouterAgentPluginFn = (opts?: {
|
|||||||
});
|
});
|
||||||
// opencode run "使用技能查看系统信息"
|
// opencode run "使用技能查看系统信息"
|
||||||
const AgentPlugin: Plugin = async (pluginInput) => {
|
const AgentPlugin: Plugin = async (pluginInput) => {
|
||||||
useContextKey<PluginInput>('plugin-input', () => pluginInput, true)
|
useContextKey<PluginInput>('plugin-input', () => pluginInput, { isNew: true })
|
||||||
const hooks = opts?.hooks ? await opts.hooks(pluginInput) : {}
|
const hooks = opts?.hooks ? await opts.hooks(pluginInput) : {}
|
||||||
return {
|
return {
|
||||||
...hooks,
|
...hooks,
|
||||||
|
|||||||
@@ -1,20 +1,25 @@
|
|||||||
|
export type CustomErrorOptions = {
|
||||||
|
cause?: Error | string;
|
||||||
|
code?: number;
|
||||||
|
message?: string;
|
||||||
|
}
|
||||||
/** 自定义错误 */
|
/** 自定义错误 */
|
||||||
export class CustomError extends Error {
|
export class CustomError extends Error {
|
||||||
code?: number;
|
code?: number;
|
||||||
data?: any;
|
data?: any;
|
||||||
message: string;
|
message: string;
|
||||||
tips?: string;
|
constructor(code?: number | string | CustomErrorOptions, opts?: CustomErrorOptions) {
|
||||||
constructor(code?: number | string, message?: string, tips?: string) {
|
if (typeof code === 'object' && code !== null) {
|
||||||
super(message || String(code));
|
opts = code;
|
||||||
this.name = 'CustomError';
|
code = opts.code || 500;
|
||||||
if (typeof code === 'number') {
|
|
||||||
this.code = code;
|
|
||||||
this.message = message!;
|
|
||||||
} else {
|
|
||||||
this.code = 500;
|
|
||||||
this.message = code!;
|
|
||||||
}
|
}
|
||||||
this.tips = tips;
|
let message = opts?.message || String(code);
|
||||||
|
const cause = opts?.cause;
|
||||||
|
super(message, { cause });
|
||||||
|
this.name = 'RouterError';
|
||||||
|
let codeNum = opts?.code || (typeof code === 'number' ? code : undefined);
|
||||||
|
this.code = codeNum ?? 500;
|
||||||
|
this.message = message!;
|
||||||
// 这一步可不写,默认会保存堆栈追踪信息到自定义错误构造函数之前,
|
// 这一步可不写,默认会保存堆栈追踪信息到自定义错误构造函数之前,
|
||||||
// 而如果写成 `Error.captureStackTrace(this)` 则自定义错误的构造函数也会被保存到堆栈追踪信息
|
// 而如果写成 `Error.captureStackTrace(this)` 则自定义错误的构造函数也会被保存到堆栈追踪信息
|
||||||
Error.captureStackTrace(this, this.constructor);
|
Error.captureStackTrace(this, this.constructor);
|
||||||
@@ -31,8 +36,7 @@ export class CustomError extends Error {
|
|||||||
return {
|
return {
|
||||||
code: e?.code,
|
code: e?.code,
|
||||||
data: e?.data,
|
data: e?.data,
|
||||||
message: e?.message,
|
message: e?.message
|
||||||
tips: e?.tips,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@@ -43,6 +47,22 @@ export class CustomError extends Error {
|
|||||||
static isError(error: unknown): error is CustomError {
|
static isError(error: unknown): error is CustomError {
|
||||||
return error instanceof CustomError || (typeof error === 'object' && error !== null && 'code' in error);
|
return error instanceof CustomError || (typeof error === 'object' && error !== null && 'code' in error);
|
||||||
}
|
}
|
||||||
|
static throw(code?: number | string, message?: string): void;
|
||||||
|
static throw(code?: number | string, opts?: CustomErrorOptions): void;
|
||||||
|
static throw(opts?: CustomErrorOptions): void;
|
||||||
|
static throw(...args: any[]) {
|
||||||
|
const [args0, args1] = args;
|
||||||
|
if (args0 && typeof args0 === 'object') {
|
||||||
|
throw new CustomError(args0);
|
||||||
|
}
|
||||||
|
if (args1 && typeof args1 === 'object') {
|
||||||
|
throw new CustomError(args0, args1);
|
||||||
|
} else if (args1) {
|
||||||
|
throw new CustomError(args0, { message: args1 });
|
||||||
|
}
|
||||||
|
// args1 不存在;
|
||||||
|
throw new CustomError(args0);
|
||||||
|
}
|
||||||
parse(e?: CustomError) {
|
parse(e?: CustomError) {
|
||||||
if (e) {
|
if (e) {
|
||||||
return CustomError.parseError(e);
|
return CustomError.parseError(e);
|
||||||
@@ -52,12 +72,17 @@ export class CustomError extends Error {
|
|||||||
code: e?.code,
|
code: e?.code,
|
||||||
data: e?.data,
|
data: e?.data,
|
||||||
message: e?.message,
|
message: e?.message,
|
||||||
tips: e?.tips,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface throwError {
|
||||||
|
throw(code?: number | string, message?: string): void;
|
||||||
|
throw(code?: number | string, opts?: CustomErrorOptions): void;
|
||||||
|
throw(opts?: CustomErrorOptions): void;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
try {
|
try {
|
||||||
//
|
//
|
||||||
|
|||||||
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 { pick } from './utils/pick.ts';
|
||||||
import { listenProcess, MockProcess } from './utils/listen-process.ts';
|
import { listenProcess, MockProcess } from './utils/listen-process.ts';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { randomId } from './utils/random.ts';
|
import { hashIdMd5Sync, randomId } from './utils/random.ts';
|
||||||
import * as schema from './validator/schema.ts';
|
import * as schema from './validator/schema.ts';
|
||||||
|
|
||||||
export type RouterContextT = { code?: number;[key: string]: any };
|
export type RouterContextT = { code?: number;[key: string]: any };
|
||||||
export type RouteContext<T = { code?: number }, S = any> = {
|
|
||||||
|
type BuildRouteContext<M, U> = M extends { args?: infer A }
|
||||||
|
? A extends z.ZodObject<any>
|
||||||
|
? RouteContext<{ args?: z.infer<A> }, U>
|
||||||
|
: A extends Record<string, z.ZodTypeAny>
|
||||||
|
? RouteContext<{ args?: { [K in keyof A]: z.infer<A[K]> } }, U>
|
||||||
|
: RouteContext<U>
|
||||||
|
: RouteContext<U>;
|
||||||
|
|
||||||
|
export type RouteContext<T = { code?: number }, U extends SimpleObject = {}, S = { [key: string]: any }> = {
|
||||||
/**
|
/**
|
||||||
* 本地自己调用的时候使用,可以标识为当前自调用,那么 auth 就不许重复的校验
|
* 本地自己调用的时候使用,可以标识为当前自调用,那么 auth 就不许重复的校验
|
||||||
* 或者不需要登录的,直接调用
|
* 或者不需要登录的,直接调用
|
||||||
@@ -23,9 +32,15 @@ export type RouteContext<T = { code?: number }, S = any> = {
|
|||||||
code?: number;
|
code?: number;
|
||||||
/** return msg */
|
/** return msg */
|
||||||
message?: string;
|
message?: string;
|
||||||
// 传递状态
|
/**
|
||||||
|
* 传递状态
|
||||||
|
*/
|
||||||
state?: S;
|
state?: S;
|
||||||
// transfer data
|
// transfer data
|
||||||
|
/**
|
||||||
|
* 当前routerId
|
||||||
|
*/
|
||||||
|
currentId?: string;
|
||||||
/**
|
/**
|
||||||
* 当前路径
|
* 当前路径
|
||||||
*/
|
*/
|
||||||
@@ -54,19 +69,19 @@ export type RouteContext<T = { code?: number }, S = any> = {
|
|||||||
ctx?: RouteContext & { [key: string]: any },
|
ctx?: RouteContext & { [key: string]: any },
|
||||||
) => Promise<any>;
|
) => Promise<any>;
|
||||||
/** 请求 route的返回结果,解析了body为data,就类同于 query.post获取的数据*/
|
/** 请求 route的返回结果,解析了body为data,就类同于 query.post获取的数据*/
|
||||||
run?: (message: { path: string; key?: string; payload?: any }, ctx?: RouteContext & { [key: string]: any }) => Promise<any>;
|
run?: (message: { path: string; key?: string; payload?: any }, ctx?: RouteContext) => Promise<any>;
|
||||||
index?: number;
|
index?: number;
|
||||||
throw?: (code?: number | string, message?: string, tips?: string) => void;
|
throw?: throwError['throw'];
|
||||||
/** 是否需要序列化, 使用JSON.stringify和JSON.parse */
|
/** 是否需要序列化, 使用JSON.stringify和JSON.parse */
|
||||||
needSerialize?: boolean;
|
needSerialize?: boolean;
|
||||||
} & T;
|
} & T & U;
|
||||||
export type SimpleObject = Record<string, any>;
|
export type SimpleObject = Record<string, any>;
|
||||||
export type Run<T extends SimpleObject = {}> = (ctx: Required<RouteContext<T>>) => Promise<typeof ctx | null | void>;
|
export type Run<T extends SimpleObject = {}> = (ctx: Required<RouteContext<T>>) => Promise<typeof ctx | null | void>;
|
||||||
export type RunMessage = { path?: string; key?: string; id?: string; payload?: any; };
|
export type RunMessage = { path?: string; key?: string; id?: string; payload?: any; };
|
||||||
export type NextRoute = Pick<Route, 'id' | 'path' | 'key'>;
|
export type NextRoute = Pick<Route, 'id' | 'path' | 'key'>;
|
||||||
export type RouteMiddleware =
|
export type RouteMiddleware =
|
||||||
| {
|
| {
|
||||||
path: string;
|
path?: string;
|
||||||
key?: string;
|
key?: string;
|
||||||
id?: string;
|
id?: string;
|
||||||
}
|
}
|
||||||
@@ -80,15 +95,7 @@ export type RouteOpts<U = {}, T = SimpleObject> = {
|
|||||||
description?: string;
|
description?: string;
|
||||||
metadata?: T;
|
metadata?: T;
|
||||||
middleware?: RouteMiddleware[]; // middleware
|
middleware?: RouteMiddleware[]; // middleware
|
||||||
type?: 'route' | 'middleware';
|
type?: 'route' | 'middleware' | 'compound'; // compound表示这个 route 作为一个聚合体,没有实际的 run,而是一个 router 的聚合列表
|
||||||
/**
|
|
||||||
* $#$ will be used to split path and key
|
|
||||||
*/
|
|
||||||
idUsePath?: boolean;
|
|
||||||
/**
|
|
||||||
* id 合并的分隔符,默认为 $#$
|
|
||||||
*/
|
|
||||||
delimiter?: string;
|
|
||||||
isDebug?: boolean;
|
isDebug?: boolean;
|
||||||
};
|
};
|
||||||
export type DefineRouteOpts = Omit<RouteOpts, 'idUsePath' | 'nextRoute'>;
|
export type DefineRouteOpts = Omit<RouteOpts, 'idUsePath' | 'nextRoute'>;
|
||||||
@@ -123,7 +130,11 @@ export const createSkill = <T = SimpleObject>(skill: Skill<T>): Skill<T> => {
|
|||||||
|
|
||||||
export type RouteInfo = Pick<Route, (typeof pickValue)[number]>;
|
export type RouteInfo = Pick<Route, (typeof pickValue)[number]>;
|
||||||
|
|
||||||
export class Route<U = { [key: string]: any }, T extends SimpleObject = SimpleObject> {
|
/**
|
||||||
|
* @M 是 route的 metadate的类型,默认是 SimpleObject
|
||||||
|
* @U 是 RouteContext 里 state的类型
|
||||||
|
*/
|
||||||
|
export class Route<M extends SimpleObject = SimpleObject, U extends SimpleObject = SimpleObject> implements throwError {
|
||||||
/**
|
/**
|
||||||
* 一级路径
|
* 一级路径
|
||||||
*/
|
*/
|
||||||
@@ -133,10 +144,10 @@ export class Route<U = { [key: string]: any }, T extends SimpleObject = SimpleOb
|
|||||||
*/
|
*/
|
||||||
key?: string;
|
key?: string;
|
||||||
id?: string;
|
id?: string;
|
||||||
run?: Run;
|
run?: Run<BuildRouteContext<M, U>>;
|
||||||
nextRoute?: NextRoute; // route to run after this route
|
nextRoute?: NextRoute; // route to run after this route
|
||||||
description?: string;
|
description?: string;
|
||||||
metadata?: T;
|
metadata?: M;
|
||||||
middleware?: RouteMiddleware[]; // middleware
|
middleware?: RouteMiddleware[]; // middleware
|
||||||
type? = 'route';
|
type? = 'route';
|
||||||
/**
|
/**
|
||||||
@@ -151,23 +162,22 @@ export class Route<U = { [key: string]: any }, T extends SimpleObject = SimpleOb
|
|||||||
key = key.trim();
|
key = key.trim();
|
||||||
this.path = path;
|
this.path = path;
|
||||||
this.key = key;
|
this.key = key;
|
||||||
|
const pathKey = `${path}$$${key}`;
|
||||||
if (opts) {
|
if (opts) {
|
||||||
this.id = opts.id || randomId(12, 'rand-');
|
this.id = opts.id || hashIdMd5Sync(pathKey);
|
||||||
if (!opts.id && opts.idUsePath) {
|
this.run = opts.run as Run<BuildRouteContext<M, U>>;
|
||||||
const delimiter = opts.delimiter ?? '$#$';
|
|
||||||
this.id = path + delimiter + key;
|
|
||||||
}
|
|
||||||
this.run = opts.run;
|
|
||||||
this.nextRoute = opts.nextRoute;
|
this.nextRoute = opts.nextRoute;
|
||||||
this.description = opts.description;
|
this.description = opts.description;
|
||||||
this.metadata = opts.metadata as T;
|
this.metadata = opts.metadata as M;
|
||||||
this.type = opts.type || 'route';
|
this.type = opts.type || 'route';
|
||||||
this.middleware = opts.middleware || [];
|
this.middleware = opts.middleware || [];
|
||||||
this.key = opts.key || key;
|
this.key = opts.key || key;
|
||||||
this.path = opts.path || path;
|
this.path = opts.path || path;
|
||||||
} else {
|
} else {
|
||||||
this.middleware = [];
|
this.middleware = [];
|
||||||
this.id = randomId(12, 'rand-');
|
}
|
||||||
|
if (!this.id) {
|
||||||
|
this.id = hashIdMd5Sync(pathKey);
|
||||||
}
|
}
|
||||||
this.isDebug = opts?.isDebug ?? false;
|
this.isDebug = opts?.isDebug ?? false;
|
||||||
}
|
}
|
||||||
@@ -184,9 +194,9 @@ export class Route<U = { [key: string]: any }, T extends SimpleObject = SimpleOb
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
define<T extends { [key: string]: any } = RouterContextT>(opts: DefineRouteOpts): this;
|
define<T extends { [key: string]: any } = RouterContextT>(opts: DefineRouteOpts): this;
|
||||||
define<T extends { [key: string]: any } = RouterContextT>(fn: Run<T & U>): this;
|
define<T extends { [key: string]: any } = RouterContextT>(fn: Run<T & BuildRouteContext<M, U>>): this;
|
||||||
define<T extends { [key: string]: any } = RouterContextT>(key: string, fn: Run<T & U>): this;
|
define<T extends { [key: string]: any } = RouterContextT>(key: string, fn: Run<T & BuildRouteContext<M, U>>): this;
|
||||||
define<T extends { [key: string]: any } = RouterContextT>(path: string, key: string, fn: Run<T & U>): this;
|
define<T extends { [key: string]: any } = RouterContextT>(path: string, key: string, fn: Run<T & BuildRouteContext<M, U>>): this;
|
||||||
define(...args: any[]) {
|
define(...args: any[]) {
|
||||||
const [path, key, opts] = args;
|
const [path, key, opts] = args;
|
||||||
// 全覆盖,所以opts需要准确,不能由idUsePath 需要check的变量
|
// 全覆盖,所以opts需要准确,不能由idUsePath 需要check的变量
|
||||||
@@ -209,7 +219,7 @@ export class Route<U = { [key: string]: any }, T extends SimpleObject = SimpleOb
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
if (typeof path === 'function') {
|
if (typeof path === 'function') {
|
||||||
this.run = path;
|
this.run = path as Run<BuildRouteContext<M, U>>;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
if (typeof path === 'string' && typeof key === 'function') {
|
if (typeof path === 'string' && typeof key === 'function') {
|
||||||
@@ -242,9 +252,8 @@ export class Route<U = { [key: string]: any }, T extends SimpleObject = SimpleOb
|
|||||||
addTo(router: QueryRouter | { add: (route: Route) => void;[key: string]: any }, opts?: AddOpts) {
|
addTo(router: QueryRouter | { add: (route: Route) => void;[key: string]: any }, opts?: AddOpts) {
|
||||||
router.add(this, opts);
|
router.add(this, opts);
|
||||||
}
|
}
|
||||||
throw(code?: number | string, message?: string, tips?: string): void;
|
|
||||||
throw(...args: any[]) {
|
throw(...args: any[]) {
|
||||||
throw new CustomError(...args);
|
CustomError.throw(...args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,6 +262,9 @@ const toJSONSchemaRoute = (route: RouteInfo) => {
|
|||||||
if (pickValues?.metadata?.args) {
|
if (pickValues?.metadata?.args) {
|
||||||
pickValues.metadata.args = toJSONSchema(pickValues?.metadata?.args, { mergeObject: false });
|
pickValues.metadata.args = toJSONSchema(pickValues?.metadata?.args, { mergeObject: false });
|
||||||
}
|
}
|
||||||
|
if (pickValues?.metadata?.returns) {
|
||||||
|
pickValues.metadata.returns = toJSONSchema(pickValues?.metadata?.returns, { mergeObject: false });
|
||||||
|
}
|
||||||
return pickValues;
|
return pickValues;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,11 +275,11 @@ export const fromJSONSchema = schema.fromJSONSchema;
|
|||||||
* @parmas overwrite 是否覆盖已存在的route,默认true
|
* @parmas overwrite 是否覆盖已存在的route,默认true
|
||||||
*/
|
*/
|
||||||
export type AddOpts = { overwrite?: boolean };
|
export type AddOpts = { overwrite?: boolean };
|
||||||
export class QueryRouter {
|
export class QueryRouter<T extends SimpleObject = SimpleObject> implements throwError {
|
||||||
appId: string = '';
|
appId: string = '';
|
||||||
routes: Route[];
|
routes: Route[];
|
||||||
maxNextRoute = 40;
|
maxNextRoute = 40;
|
||||||
context?: RouteContext = {}; // default context for call
|
context?: RouteContext<T> = {} as RouteContext<T>; // default context for call
|
||||||
constructor() {
|
constructor() {
|
||||||
this.routes = [];
|
this.routes = [];
|
||||||
}
|
}
|
||||||
@@ -310,11 +322,12 @@ export class QueryRouter {
|
|||||||
* @param ctx
|
* @param ctx
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
async runRoute(path: string, key: string, ctx?: RouteContext) {
|
async runRoute(path: string, key: string, ctx?: RouteContext<T>): Promise<RouteContext<T>> {
|
||||||
const route = this.routes.find((r) => r.path === path && r.key === key);
|
const route = this.routes.find((r) => r.path === path && r.key === key);
|
||||||
const maxNextRoute = this.maxNextRoute;
|
const maxNextRoute = this.maxNextRoute;
|
||||||
ctx = (ctx || {}) as RouteContext;
|
ctx = (ctx || {}) as RouteContext<T>;
|
||||||
ctx.currentPath = path;
|
ctx.currentPath = path;
|
||||||
|
ctx.currentId = route?.id;
|
||||||
ctx.currentKey = key;
|
ctx.currentKey = key;
|
||||||
ctx.currentRoute = route;
|
ctx.currentRoute = route;
|
||||||
ctx.index = (ctx.index || 0) + 1;
|
ctx.index = (ctx.index || 0) + 1;
|
||||||
@@ -328,7 +341,7 @@ export class QueryRouter {
|
|||||||
ctx.code = 500;
|
ctx.code = 500;
|
||||||
ctx.message = 'Too many nextRoute';
|
ctx.message = 'Too many nextRoute';
|
||||||
ctx.body = null;
|
ctx.body = null;
|
||||||
return;
|
return ctx;
|
||||||
}
|
}
|
||||||
// run middleware
|
// run middleware
|
||||||
if (route && route.middleware && route.middleware.length > 0) {
|
if (route && route.middleware && route.middleware.length > 0) {
|
||||||
@@ -383,7 +396,7 @@ export class QueryRouter {
|
|||||||
const middleware = routeMiddleware[i];
|
const middleware = routeMiddleware[i];
|
||||||
if (middleware) {
|
if (middleware) {
|
||||||
try {
|
try {
|
||||||
await middleware.run(ctx as Required<RouteContext>);
|
await middleware.run(ctx as Required<RouteContext<T>>);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (route?.isDebug) {
|
if (route?.isDebug) {
|
||||||
console.error('=====debug====:middlerware error');
|
console.error('=====debug====:middlerware error');
|
||||||
@@ -405,6 +418,7 @@ export class QueryRouter {
|
|||||||
return ctx;
|
return ctx;
|
||||||
}
|
}
|
||||||
if (ctx.end) {
|
if (ctx.end) {
|
||||||
|
return ctx;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -413,14 +427,14 @@ export class QueryRouter {
|
|||||||
if (route) {
|
if (route) {
|
||||||
if (route.run) {
|
if (route.run) {
|
||||||
try {
|
try {
|
||||||
await route.run(ctx as Required<RouteContext>);
|
await route.run(ctx as Required<RouteContext<T>>);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (route?.isDebug) {
|
if (route?.isDebug) {
|
||||||
console.error('=====debug====:route error');
|
console.error('=====debug====:route error');
|
||||||
console.error('=====debug====:', e);
|
console.error('=====debug====:', e);
|
||||||
console.error('=====debug====:[path:key]:', `${route.path}-${route.key}`);
|
console.error('=====debug====:[path:key]:', `${route.path}-${route.key}`);
|
||||||
}
|
}
|
||||||
if (e instanceof CustomError) {
|
if (e instanceof CustomError || e?.code) {
|
||||||
ctx.code = e.code;
|
ctx.code = e.code;
|
||||||
ctx.message = e.message;
|
ctx.message = e.message;
|
||||||
} else {
|
} else {
|
||||||
@@ -468,7 +482,7 @@ export class QueryRouter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 如果没有找到route,返回404,这是因为出现了错误
|
// 如果没有找到route,返回404,这是因为出现了错误
|
||||||
return Promise.resolve({ code: 404, body: 'Not found' });
|
return Promise.resolve({ code: 404, body: 'Not found' } as RouteContext<T>);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* 第一次执行
|
* 第一次执行
|
||||||
@@ -476,12 +490,12 @@ export class QueryRouter {
|
|||||||
* @param ctx
|
* @param ctx
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
async parse(message: { path: string; key?: string; payload?: any }, ctx?: RouteContext & { [key: string]: any }) {
|
async parse(message: { path: string; key?: string; payload?: any }, ctx?: RouteContext<T> & { [key: string]: any }) {
|
||||||
if (!message?.path) {
|
if (!message?.path) {
|
||||||
return Promise.resolve({ code: 404, body: null, message: 'Not found path' });
|
return Promise.resolve({ code: 404, body: null, message: 'Not found path' } as RouteContext<T>);
|
||||||
}
|
}
|
||||||
const { path, key = '', payload = {}, ...query } = message;
|
const { path, key = '', payload = {}, ...query } = message;
|
||||||
ctx = ctx || {};
|
ctx = ctx || {} as RouteContext<T>;
|
||||||
ctx.query = { ...ctx.query, ...query, ...payload };
|
ctx.query = { ...ctx.query, ...query, ...payload };
|
||||||
ctx.args = ctx.query;
|
ctx.args = ctx.query;
|
||||||
ctx.state = { ...ctx?.state };
|
ctx.state = { ...ctx?.state };
|
||||||
@@ -515,7 +529,7 @@ export class QueryRouter {
|
|||||||
* @param ctx
|
* @param ctx
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
async call(message: { id?: string; path?: string; key?: string; payload?: any }, ctx?: RouteContext & { [key: string]: any }) {
|
async call(message: { id?: string; path?: string; key?: string; payload?: any }, ctx?: RouteContext<T> & { [key: string]: any }) {
|
||||||
let path = message.path;
|
let path = message.path;
|
||||||
let key = message.key;
|
let key = message.key;
|
||||||
// 优先 path + key
|
// 优先 path + key
|
||||||
@@ -556,7 +570,7 @@ export class QueryRouter {
|
|||||||
* @param ctx
|
* @param ctx
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
async run(message: { id?: string; path?: string; key?: string; payload?: any }, ctx?: RouteContext & { [key: string]: any }) {
|
async run(message: { id?: string; path?: string; key?: string; payload?: any }, ctx?: RouteContext<T> & { [key: string]: any }) {
|
||||||
const res = await this.call(message, { ...this.context, ...ctx });
|
const res = await this.call(message, { ...this.context, ...ctx });
|
||||||
return {
|
return {
|
||||||
code: res.code,
|
code: res.code,
|
||||||
@@ -570,7 +584,7 @@ export class QueryRouter {
|
|||||||
* @param ctx
|
* @param ctx
|
||||||
*/
|
*/
|
||||||
setContext(ctx: RouteContext) {
|
setContext(ctx: RouteContext) {
|
||||||
this.context = ctx;
|
this.context = ctx as RouteContext<T>;
|
||||||
}
|
}
|
||||||
getList(filter?: (route: Route) => boolean): RouteInfo[] {
|
getList(filter?: (route: Route) => boolean): RouteInfo[] {
|
||||||
return this.routes.filter(filter || (() => true)).map((r) => {
|
return this.routes.filter(filter || (() => true)).map((r) => {
|
||||||
@@ -581,11 +595,11 @@ export class QueryRouter {
|
|||||||
/**
|
/**
|
||||||
* 获取handle函数, 这里会去执行parse函数
|
* 获取handle函数, 这里会去执行parse函数
|
||||||
*/
|
*/
|
||||||
getHandle<T = any>(router: QueryRouter, wrapperFn?: HandleFn<T>, ctx?: RouteContext) {
|
getHandle<T = any>(router: QueryRouter, wrapperFn?: HandleFn, ctx?: RouteContext) {
|
||||||
return async (msg: { id?: string; path?: string; key?: string;[key: string]: any }, handleContext?: RouteContext) => {
|
return async (msg: { id?: string; path?: string; key?: string;[key: string]: any }, handleContext?: RouteContext<T>) => {
|
||||||
try {
|
try {
|
||||||
const context = { ...ctx, ...handleContext };
|
const context = { ...ctx, ...handleContext };
|
||||||
const res = await router.call(msg, context);
|
const res = await router.call(msg, context) as any;
|
||||||
if (wrapperFn) {
|
if (wrapperFn) {
|
||||||
res.data = res.body;
|
res.data = res.body;
|
||||||
return wrapperFn(res, context);
|
return wrapperFn(res, context);
|
||||||
@@ -610,9 +624,8 @@ export class QueryRouter {
|
|||||||
importRouter(router: QueryRouter) {
|
importRouter(router: QueryRouter) {
|
||||||
this.importRoutes(router.routes);
|
this.importRoutes(router.routes);
|
||||||
}
|
}
|
||||||
throw(code?: number | string, message?: string, tips?: string): void;
|
|
||||||
throw(...args: any[]) {
|
throw(...args: any[]) {
|
||||||
throw new CustomError(...args);
|
CustomError.throw(...args);
|
||||||
}
|
}
|
||||||
hasRoute(path: string, key: string = '') {
|
hasRoute(path: string, key: string = '') {
|
||||||
return this.routes.find((r) => r.path === path && r.key === key);
|
return this.routes.find((r) => r.path === path && r.key === key);
|
||||||
@@ -639,7 +652,7 @@ export class QueryRouter {
|
|||||||
description: '列出当前应用下的所有的路由信息',
|
description: '列出当前应用下的所有的路由信息',
|
||||||
middleware: opts?.middleware || [],
|
middleware: opts?.middleware || [],
|
||||||
run: async (ctx: RouteContext) => {
|
run: async (ctx: RouteContext) => {
|
||||||
const tokenUser = ctx.state.tokenUser;
|
const tokenUser = ctx.state as unknown as { tokenUser?: any };
|
||||||
let isUser = !!tokenUser;
|
let isUser = !!tokenUser;
|
||||||
const list = this.getList(opts?.filter).filter((item) => {
|
const list = this.getList(opts?.filter).filter((item) => {
|
||||||
if (item.id === 'auth' || item.id === 'auth-can' || item.id === 'check-auth-admin' || item.id === 'auth-admin') {
|
if (item.id === 'auth' || item.id === 'auth-can' || item.id === 'check-auth-admin' || item.id === 'auth-admin') {
|
||||||
@@ -685,10 +698,11 @@ export class QueryRouter {
|
|||||||
fromJSONSchema = fromJSONSchema;
|
fromJSONSchema = fromJSONSchema;
|
||||||
}
|
}
|
||||||
|
|
||||||
type QueryRouterServerOpts = {
|
type QueryRouterServerOpts<C extends SimpleObject = SimpleObject> = {
|
||||||
handleFn?: HandleFn;
|
handleFn?: HandleFn;
|
||||||
context?: RouteContext;
|
context?: RouteContext<C>;
|
||||||
appId?: string;
|
appId?: string;
|
||||||
|
initHandle?: boolean;
|
||||||
};
|
};
|
||||||
interface HandleFn<T = any> {
|
interface HandleFn<T = any> {
|
||||||
(msg: { path: string;[key: string]: any }, ctx?: any): { code: string; data?: any; message?: string;[key: string]: any };
|
(msg: { path: string;[key: string]: any }, ctx?: any): { code: string; data?: any; message?: string;[key: string]: any };
|
||||||
@@ -697,13 +711,18 @@ interface HandleFn<T = any> {
|
|||||||
/**
|
/**
|
||||||
* QueryRouterServer
|
* QueryRouterServer
|
||||||
* @description 移除server相关的功能,只保留router相关的功能,和http.createServer不相关,独立
|
* @description 移除server相关的功能,只保留router相关的功能,和http.createServer不相关,独立
|
||||||
|
* @template C 自定义 RouteContext 类型
|
||||||
*/
|
*/
|
||||||
export class QueryRouterServer extends QueryRouter {
|
export class QueryRouterServer<C extends SimpleObject = SimpleObject> extends QueryRouter<C> {
|
||||||
declare appId: string;
|
declare appId: string;
|
||||||
handle: any;
|
handle: any;
|
||||||
constructor(opts?: QueryRouterServerOpts) {
|
declare context: RouteContext<C>;
|
||||||
|
constructor(opts?: QueryRouterServerOpts<C>) {
|
||||||
super();
|
super();
|
||||||
|
const initHandle = opts?.initHandle ?? true;
|
||||||
|
if (initHandle || opts?.handleFn) {
|
||||||
this.handle = this.getHandle(this, opts?.handleFn, opts?.context);
|
this.handle = this.getHandle(this, opts?.handleFn, opts?.context);
|
||||||
|
}
|
||||||
this.setContext({ needSerialize: false, ...opts?.context });
|
this.setContext({ needSerialize: false, ...opts?.context });
|
||||||
if (opts?.appId) {
|
if (opts?.appId) {
|
||||||
this.appId = opts.appId;
|
this.appId = opts.appId;
|
||||||
@@ -718,37 +737,28 @@ export class QueryRouterServer extends QueryRouter {
|
|||||||
this.add(route, opts);
|
this.add(route, opts);
|
||||||
}
|
}
|
||||||
Route = Route;
|
Route = Route;
|
||||||
route(opts: RouteOpts): Route<Required<RouteContext>>;
|
route<M extends SimpleObject = SimpleObject>(opts: RouteOpts & { metadata?: M }): Route<M, Required<RouteContext<C>>>;
|
||||||
route(path: string, key?: string): Route<Required<RouteContext>>;
|
route<M extends SimpleObject = SimpleObject>(path: string, opts?: RouteOpts & { metadata?: M }): Route<M, Required<RouteContext<C>>>;
|
||||||
route(path: string, opts?: RouteOpts): Route<Required<RouteContext>>;
|
route<M extends SimpleObject = SimpleObject>(path: string, key?: string): Route<M, Required<RouteContext<C>>>;
|
||||||
route(path: string, key?: string, opts?: RouteOpts): Route<Required<RouteContext>>;
|
route<M extends SimpleObject = SimpleObject>(path: string, key?: string, opts?: RouteOpts & { metadata?: M }): Route<M, Required<RouteContext<C>>>;
|
||||||
route(...args: any[]) {
|
route<M extends SimpleObject = SimpleObject>(...args: any[]) {
|
||||||
const [path, key, opts] = args;
|
const [path, key, opts] = args;
|
||||||
if (typeof path === 'object') {
|
if (typeof path === 'object') {
|
||||||
return new Route(path.path, path.key, path);
|
return new Route<M, Required<RouteContext<C>>>(path.path, path.key, path);
|
||||||
}
|
}
|
||||||
if (typeof path === 'string') {
|
if (typeof path === 'string') {
|
||||||
if (opts) {
|
if (opts) {
|
||||||
return new Route(path, key, opts);
|
return new Route<M, Required<RouteContext<C>>>(path, key, opts);
|
||||||
}
|
}
|
||||||
if (key && typeof key === 'object') {
|
if (key && typeof key === 'object') {
|
||||||
return new Route(path, key?.key || '', key);
|
return new Route<M, Required<RouteContext<C>>>(path, key?.key || '', key);
|
||||||
}
|
}
|
||||||
return new Route(path, key);
|
return new Route<M, Required<RouteContext<C>>>(path, key);
|
||||||
}
|
}
|
||||||
return new Route(path, key, opts);
|
return new Route<M, Required<RouteContext<C>>>(path, key, opts);
|
||||||
}
|
}
|
||||||
prompt(description: string): Route<Required<RouteContext>>;
|
prompt(description: string) {
|
||||||
prompt(description: Function): Route<Required<RouteContext>>;
|
return new Route(undefined, undefined, { description });
|
||||||
prompt(...args: any[]) {
|
|
||||||
const [desc] = args;
|
|
||||||
let description = ''
|
|
||||||
if (typeof desc === 'string') {
|
|
||||||
description = desc;
|
|
||||||
} else if (typeof desc === 'function') {
|
|
||||||
description = desc() || ''; // 如果是Promise,需要addTo App之前就要获取应有的函数了。
|
|
||||||
}
|
|
||||||
return new Route('', '', { description });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -756,15 +766,90 @@ export class QueryRouterServer extends QueryRouter {
|
|||||||
* @param param0
|
* @param param0
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
async run(msg: { id?: string; path?: string; key?: string; payload?: any }, ctx?: RouteContext & { [key: string]: any }) {
|
async run(msg: { id?: string; path?: string; key?: string; payload?: any, token?: string, data?: any }, ctx?: Partial<RouteContext<C>>) {
|
||||||
const handle = this.handle;
|
const handle = this.handle;
|
||||||
if (handle) {
|
if (handle) {
|
||||||
return handle(msg, ctx);
|
return handle(msg, ctx);
|
||||||
}
|
}
|
||||||
return super.run(msg, ctx);
|
return super.run(msg, ctx as RouteContext<C>);
|
||||||
|
}
|
||||||
|
|
||||||
|
async runAction<T extends { id?: string; path?: string; key?: string; metadata?: { args?: any } } = {}>(
|
||||||
|
api: T,
|
||||||
|
payload: RunActionPayload<T>,
|
||||||
|
ctx?: RouteContext<C>
|
||||||
|
) {
|
||||||
|
const { path, key, id } = api as any;
|
||||||
|
return this.run({ path, key, id, payload }, ctx);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 创建认证相关的中间件,默认是 auth, auth-admin, auth-can 三个中间件
|
||||||
|
* @param fun 认证函数,接收 RouteContext 和认证类型
|
||||||
|
*/
|
||||||
|
async createAuth(fun: (ctx: RouteContext<C>, type?: 'auth' | 'auth-admin' | 'auth-can') => any) {
|
||||||
|
this.route({
|
||||||
|
path: 'auth',
|
||||||
|
key: 'auth',
|
||||||
|
id: 'auth',
|
||||||
|
description: 'token验证',
|
||||||
|
}).define(async (ctx) => {
|
||||||
|
if (fun) {
|
||||||
|
await fun(ctx, 'auth');
|
||||||
|
}
|
||||||
|
}).addTo(this, { overwrite: false });
|
||||||
|
|
||||||
|
this.route({
|
||||||
|
path: 'auth-admin',
|
||||||
|
key: 'auth-admin',
|
||||||
|
id: 'auth-admin',
|
||||||
|
description: 'admin token验证',
|
||||||
|
middleware: ['auth']
|
||||||
|
}).define(async (ctx) => {
|
||||||
|
if (fun) {
|
||||||
|
await fun(ctx, 'auth-admin');
|
||||||
|
}
|
||||||
|
}).addTo(this, { overwrite: false });
|
||||||
|
|
||||||
|
this.route({
|
||||||
|
path: 'auth-can',
|
||||||
|
key: 'auth-can',
|
||||||
|
id: 'auth-can',
|
||||||
|
description: '权限验证'
|
||||||
|
}).define(async (ctx) => {
|
||||||
|
if (fun) {
|
||||||
|
await fun(ctx, 'auth-can');
|
||||||
|
}
|
||||||
|
}).addTo(this, { overwrite: false });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export class Mini extends QueryRouterServer { }
|
export class Mini extends QueryRouterServer { }
|
||||||
|
|
||||||
|
/** JSON Schema 基本类型映射到 TypeScript 类型 */
|
||||||
|
type JsonSchemaTypeToTS<T> =
|
||||||
|
T extends { type: "string" } ? string :
|
||||||
|
T extends { type: "boolean" } ? boolean :
|
||||||
|
T extends { type: "number" } ? number :
|
||||||
|
T extends { type: "integer" } ? number :
|
||||||
|
T extends { type: "object" } ? object :
|
||||||
|
T extends { type: "array" } ? any[] :
|
||||||
|
any;
|
||||||
|
|
||||||
|
/** 将 args 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 { ServerType, Listener, OnListener, ServerOpts, OnWebSocketOptions, OnWebSocketFn, WebSocketListenerFun, ListenerFun, HttpListenerFun, WS } from './server-type.ts';
|
||||||
import { parseIfJson } from '../utils/parse.ts';
|
import { parseIfJson } from '../utils/parse.ts';
|
||||||
import { EventEmitter } from 'eventemitter3';
|
import { EventEmitter } from 'eventemitter3';
|
||||||
|
import { CustomError } from '../result/error.ts';
|
||||||
type CookieFn = (name: string, value: string, options?: cookie.SerializeOptions, end?: boolean) => void;
|
type CookieFn = (name: string, value: string, options?: cookie.SerializeOptions, end?: boolean) => void;
|
||||||
|
|
||||||
export type HandleCtx = {
|
export type HandleCtx = {
|
||||||
@@ -165,8 +166,13 @@ export class ServerBase implements ServerType {
|
|||||||
res.end(JSON.stringify(end));
|
res.end(JSON.stringify(end));
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
|
||||||
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
||||||
|
if (CustomError.isError(e)) {
|
||||||
|
const parsedError = CustomError.parseError(e);
|
||||||
|
res.end(JSON.stringify(parsedError));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.error(e);
|
||||||
if (e.code && typeof e.code === 'number') {
|
if (e.code && typeof e.code === 'number') {
|
||||||
res.end(resultError(e.message || `Router Server error`, e.code));
|
res.end(resultError(e.message || `Router Server error`, e.code));
|
||||||
} else {
|
} else {
|
||||||
@@ -278,7 +284,7 @@ export class ServerBase implements ServerType {
|
|||||||
* @param ws
|
* @param ws
|
||||||
*/
|
*/
|
||||||
async onWsClose(ws: WS) {
|
async onWsClose(ws: WS) {
|
||||||
const id = ws?.data?.id || '';
|
const id = ws?.wsId || '';
|
||||||
if (id) {
|
if (id) {
|
||||||
this.emitter.emit('close--' + id, { type: 'close', ws, id });
|
this.emitter.emit('close--' + id, { type: 'close', ws, id });
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -292,4 +298,7 @@ export class ServerBase implements ServerType {
|
|||||||
if (this.showConnected)
|
if (this.showConnected)
|
||||||
ws.send(JSON.stringify({ type: 'connected' }));
|
ws.send(JSON.stringify({ type: 'connected' }));
|
||||||
}
|
}
|
||||||
|
createId() {
|
||||||
|
return Math.random().toString(36).substring(2, 15);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
* @tags bun, server, websocket, http
|
* @tags bun, server, websocket, http
|
||||||
* @createdAt 2025-12-20
|
* @createdAt 2025-12-20
|
||||||
*/
|
*/
|
||||||
import { ServerType, type ServerOpts, type Cors, RouterRes, RouterReq } from './server-type.ts';
|
import { ServerType, type ServerOpts, type Cors, RouterRes, RouterReq, WS } from './server-type.ts';
|
||||||
import { ServerBase } from './server-base.ts';
|
import { ServerBase } from './server-base.ts';
|
||||||
|
|
||||||
export class BunServer extends ServerBase implements ServerType {
|
export class BunServer extends ServerBase implements ServerType {
|
||||||
@@ -264,10 +264,14 @@ export class BunServer extends ServerBase implements ServerType {
|
|||||||
open: (ws: any) => {
|
open: (ws: any) => {
|
||||||
this.sendConnected(ws);
|
this.sendConnected(ws);
|
||||||
},
|
},
|
||||||
message: async (ws: any, message: string | Buffer) => {
|
message: async (bunWs: any, message: string | Buffer) => {
|
||||||
|
const ws = bunWs as WS;
|
||||||
const pathname = ws.data.pathname || '';
|
const pathname = ws.data.pathname || '';
|
||||||
const token = ws.data.token || '';
|
const token = ws.data.token || '';
|
||||||
const id = ws.data.id || '';
|
const id = ws.data.id || '';
|
||||||
|
if (!ws.wsId) {
|
||||||
|
ws.wsId = this.createId();
|
||||||
|
}
|
||||||
await this.onWebSocket({ ws, message, pathname, token, id });
|
await this.onWebSocket({ ws, message, pathname, token, id });
|
||||||
},
|
},
|
||||||
close: (ws: any) => {
|
close: (ws: any) => {
|
||||||
|
|||||||
@@ -49,16 +49,33 @@ export type OnWebSocketOptions<T = {}> = {
|
|||||||
message: string | Buffer;
|
message: string | Buffer;
|
||||||
pathname: string,
|
pathname: string,
|
||||||
token?: string,
|
token?: string,
|
||||||
|
/** data 的id提取出来 */
|
||||||
id?: string,
|
id?: string,
|
||||||
}
|
}
|
||||||
export type OnWebSocketFn = (options: OnWebSocketOptions) => Promise<void> | void;
|
export type OnWebSocketFn = (options: OnWebSocketOptions) => Promise<void> | void;
|
||||||
export type WS<T = {}> = {
|
export type WS<T = {}> = {
|
||||||
send: (data: any) => void;
|
send: (data: any) => void;
|
||||||
close: (code?: number, reason?: string) => void;
|
close: (code?: number, reason?: string) => void;
|
||||||
|
/**
|
||||||
|
* ws 自己生成的一个id,主要是为了区分不同的ws连接,方便在onWebSocket中使用
|
||||||
|
*/
|
||||||
|
wsId?: string;
|
||||||
data?: {
|
data?: {
|
||||||
|
/**
|
||||||
|
* ws连接时的url,包含pathname和searchParams
|
||||||
|
*/
|
||||||
url: URL;
|
url: URL;
|
||||||
|
/**
|
||||||
|
* ws连接时的pathname
|
||||||
|
*/
|
||||||
pathname: string;
|
pathname: string;
|
||||||
|
/**
|
||||||
|
* ws连接时的url中的token参数
|
||||||
|
*/
|
||||||
token?: string;
|
token?: string;
|
||||||
|
/**
|
||||||
|
* ws连接时的url中的id参数.
|
||||||
|
*/
|
||||||
id?: string;
|
id?: string;
|
||||||
/**
|
/**
|
||||||
* 鉴权后的获取的信息
|
* 鉴权后的获取的信息
|
||||||
|
|||||||
@@ -56,6 +56,11 @@ export class WsServerBase {
|
|||||||
token,
|
token,
|
||||||
id,
|
id,
|
||||||
}
|
}
|
||||||
|
// @ts-ignore
|
||||||
|
if (!ws.wsId) {
|
||||||
|
// @ts-ignore
|
||||||
|
ws.wsId = this.createId();
|
||||||
|
}
|
||||||
ws.on('message', async (message: string | Buffer) => {
|
ws.on('message', async (message: string | Buffer) => {
|
||||||
await this.server.onWebSocket({ ws, message, pathname, token, id });
|
await this.server.onWebSocket({ ws, message, pathname, token, id });
|
||||||
});
|
});
|
||||||
@@ -66,7 +71,9 @@ export class WsServerBase {
|
|||||||
this.server.onWsClose(ws);
|
this.server.onWsClose(ws);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
createId() {
|
||||||
|
return Math.random().toString(36).substring(2, 15);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO: ws handle and path and routerContext
|
// TODO: ws handle and path and routerContext
|
||||||
|
|||||||
@@ -1,13 +1,69 @@
|
|||||||
import { App } from '../app.ts'
|
import { App, AppRouteContext } from "@/app.ts";
|
||||||
|
import { QueryRouterServer, RouteContext } from "@/app.ts";
|
||||||
const app = new App<{ f: string }>();
|
import z from "zod";
|
||||||
|
const route: RouteContext<{ customField: string }> = {} as any;
|
||||||
|
route.customField
|
||||||
|
const appRoute: AppRouteContext<{ customField: string }> = {} as any;
|
||||||
|
appRoute.customField
|
||||||
|
// 示例 1: 使用 App,它会自动使用 AppRouteContext<U> 作为 ctx 类型
|
||||||
|
const app = new App<{
|
||||||
|
customField: string;
|
||||||
|
}>();
|
||||||
|
app.context.customField = "customValue"; // 可以在 app.context 中添加自定义字段,这些字段会在 ctx 中可用
|
||||||
app.route({
|
app.route({
|
||||||
path: 't',
|
path: 'test1',
|
||||||
run: async (ctx) => {
|
metadata: {
|
||||||
// ctx.r
|
args: {
|
||||||
ctx.app;
|
name: z.string(),
|
||||||
}
|
}
|
||||||
|
},
|
||||||
}).define(async (ctx) => {
|
}).define(async (ctx) => {
|
||||||
ctx.f = 'hello';
|
// ctx.app 是 App 类型
|
||||||
}).addTo(app);
|
const appName = ctx.app.appId;
|
||||||
|
// ctx.customField 来自自定义泛型参数
|
||||||
|
const customField: string | undefined = ctx.customField;
|
||||||
|
|
||||||
|
// ctx.req 和 ctx.res 来自 HandleCtx
|
||||||
|
const req = ctx.req;
|
||||||
|
const res = ctx.res;
|
||||||
|
|
||||||
|
// ctx.args 从 metadata.args 推断
|
||||||
|
const name: string = ctx.args.name;
|
||||||
|
const name2: string = ctx.query.name;
|
||||||
|
|
||||||
|
|
||||||
|
ctx.body = `Hello ${name}!`;
|
||||||
|
});
|
||||||
|
// 示例 2: 使用 QueryRouterServer,它可以传递自定义的 Context 类型
|
||||||
|
const router = new QueryRouterServer<{
|
||||||
|
routerContextField: number;
|
||||||
|
}>();
|
||||||
|
router.context.routerContextField
|
||||||
|
router.route({
|
||||||
|
path: 'router-test',
|
||||||
|
metadata: {
|
||||||
|
args: {
|
||||||
|
value: z.number(),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}).define(async (ctx) => {
|
||||||
|
const value: number = ctx.args.value;
|
||||||
|
const field: number | undefined = ctx.routerContextField;
|
||||||
|
|
||||||
|
ctx.body = value;
|
||||||
|
});
|
||||||
|
// 示例 3: 不带泛型参数的 QueryRouterServer,使用默认的 RouteContext
|
||||||
|
const defaultRouter = new QueryRouterServer();
|
||||||
|
defaultRouter.route({
|
||||||
|
path: 'default-test',
|
||||||
|
metadata: {
|
||||||
|
args: {
|
||||||
|
id: z.string(),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}).define(async (ctx) => {
|
||||||
|
const id: string = ctx.args.id;
|
||||||
|
|
||||||
|
ctx.body = id;
|
||||||
|
});
|
||||||
|
export { app, router, defaultRouter };
|
||||||
@@ -14,4 +14,4 @@ app.prompt('获取天气的工具。\n参数是 city 为对应的城市').define
|
|||||||
|
|
||||||
export const chat = new RouterChat({ router: app.router });
|
export const chat = new RouterChat({ router: app.router });
|
||||||
|
|
||||||
console.log(chat.chat());
|
console.log(chat.getChatPrompt());
|
||||||
15
src/test/route-ts.ts
Normal file
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 { customAlphabet } from 'nanoid';
|
||||||
|
import Md5 from 'crypto-js/md5.js';
|
||||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz', 16);
|
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz', 16);
|
||||||
|
|
||||||
export const randomId = (length: number = 8, affix: string = '') => {
|
export const randomId = (length: number = 8, affix: string = '') => {
|
||||||
return affix + nanoid(length);
|
return affix + nanoid(length);
|
||||||
}
|
}
|
||||||
export { nanoid };
|
export { nanoid };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基于 MD5 的确定性 ID 生成 (同步版本)
|
||||||
|
* 浏览器和 Node.js 都支持
|
||||||
|
* 相同的 pathKey 永远返回相同的 16 位 ID
|
||||||
|
*/
|
||||||
|
export const hashIdMd5Sync = (pathKey: string): string => {
|
||||||
|
return Md5(pathKey).toString().substring(0, 16);
|
||||||
|
}
|
||||||
|
|||||||
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