diff --git a/package.json b/package.json index 2a41299..c6d36e5 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package", "name": "@kevisual/router", - "version": "0.0.85", + "version": "0.0.86", "description": "", "type": "module", "main": "./dist/router.js", @@ -27,11 +27,11 @@ "@kevisual/dts": "^0.0.4", "@kevisual/js-filter": "^0.0.5", "@kevisual/local-proxy": "^0.0.8", - "@kevisual/query": "^0.0.52", + "@kevisual/query": "^0.0.53", "@kevisual/use-config": "^1.0.30", - "@opencode-ai/plugin": "^1.2.16", + "@opencode-ai/plugin": "^1.2.20", "@types/bun": "^1.3.10", - "@types/node": "^25.3.3", + "@types/node": "^25.3.5", "@types/send": "^1.2.1", "@types/ws": "^8.18.1", "@types/xml2js": "^0.4.14", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 879e6fc..b40c9f4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -28,20 +28,20 @@ importers: specifier: ^0.0.8 version: 0.0.8 '@kevisual/query': - specifier: ^0.0.52 - version: 0.0.52 + specifier: ^0.0.53 + version: 0.0.53 '@kevisual/use-config': specifier: ^1.0.30 version: 1.0.30(dotenv@17.2.3) '@opencode-ai/plugin': - specifier: ^1.2.16 - version: 1.2.16 + specifier: ^1.2.20 + version: 1.2.20 '@types/bun': specifier: ^1.3.10 version: 1.3.10 '@types/node': - specifier: ^25.3.3 - version: 25.3.3 + specifier: ^25.3.5 + version: 25.3.5 '@types/send': specifier: ^1.2.1 version: 1.2.1 @@ -142,8 +142,8 @@ packages: '@kevisual/local-proxy@0.0.8': resolution: {integrity: sha512-VX/P+6/Cc8ruqp34ag6gVX073BchUmf5VNZcTV/6MJtjrNE76G8V6TLpBE8bywLnrqyRtFLIspk4QlH8up9B5Q==} - '@kevisual/query@0.0.52': - resolution: {integrity: sha512-m1UbyDTIxtfAQXM+EqhXA4ytE2V8rV8mXTZVBwzfW9O6+gtvAcRY7K1YYxfewTSXLVh9nwvfHe0KQ8MDL5ukyw==} + '@kevisual/query@0.0.53': + resolution: {integrity: sha512-PAhpCLBr0emz0lGNlTVHMbJiC5wrtGLbInPddRzgKE35fiyNt+SWSsUWABiD0DeNrLN/OxWyAFobt880Z/e5MQ==} '@kevisual/use-config@1.0.30': resolution: {integrity: sha512-kPdna0FW/X7D600aMdiZ5UTjbCo6d8d4jjauSc8RMmBwUU6WliFDSPUNKVpzm2BsDX5Nth1IXFPYMqH+wxqAmw==} @@ -166,11 +166,11 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - '@opencode-ai/plugin@1.2.16': - resolution: {integrity: sha512-9Kb7BQIC2P3oKCvI8K3thP5YP0vE7yLvcmBmgyACUIqc3e5UL6U+4umLpTvgQa2eQdjxtOXznuGTNwgcGMHUHg==} + '@opencode-ai/plugin@1.2.20': + resolution: {integrity: sha512-BE6TOXVxgF24g5QgtlogSY5B+/AmZJ3cYaVjHZhUVuAli9JEg4RblrbrK2rfgbyZBoZDpjBLGTYtIRTVmOccEA==} - '@opencode-ai/sdk@1.2.16': - resolution: {integrity: sha512-y9ae9VnCcuog0GaI4DveX1HB6DBoZgGN3EuJVlRFbBCPwhzkls6fCfHSb5+VnTS6Fy0OWFUL28VBCmixL/D+/Q==} + '@opencode-ai/sdk@1.2.20': + resolution: {integrity: sha512-U5ROpG21D8jg9rkc1IgKAk1g5dn6X/rkOBfveupd0peSDO9n6VM9aikYccVLaMObxVqdjtG08IeQOFTPVS8ySQ==} '@rollup/plugin-commonjs@29.0.0': resolution: {integrity: sha512-U2YHaxR2cU/yAiwKJtJRhnyLk7cifnQw0zUpISsocBDoHDJn+HTV74ABqnwr5bEgWUwFZC9oFL6wLe21lHu5eQ==} @@ -371,8 +371,8 @@ packages: '@types/node@25.2.3': resolution: {integrity: sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ==} - '@types/node@25.3.3': - resolution: {integrity: sha512-DpzbrH7wIcBaJibpKo9nnSQL0MTRdnWttGyE5haGwK86xgMOkFLp7vEyfQPGLOJh5wNYiJ3V9PmUMDhV9u8kkQ==} + '@types/node@25.3.5': + resolution: {integrity: sha512-oX8xrhvpiyRCQkG1MFchB09f+cXftgIXb3a7UUa4Y3wpmZPw5tyZGTLWhlESOLq1Rq6oDlc8npVU2/9xiCuXMA==} '@types/resolve@1.20.2': resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} @@ -744,7 +744,7 @@ snapshots: '@kevisual/local-proxy@0.0.8': {} - '@kevisual/query@0.0.52': {} + '@kevisual/query@0.0.53': {} '@kevisual/use-config@1.0.30(dotenv@17.2.3)': dependencies: @@ -765,12 +765,12 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.20.1 - '@opencode-ai/plugin@1.2.16': + '@opencode-ai/plugin@1.2.20': dependencies: - '@opencode-ai/sdk': 1.2.16 + '@opencode-ai/sdk': 1.2.20 zod: 4.1.8 - '@opencode-ai/sdk@1.2.16': {} + '@opencode-ai/sdk@1.2.20': {} '@rollup/plugin-commonjs@29.0.0(rollup@4.57.1)': dependencies: @@ -904,7 +904,7 @@ snapshots: dependencies: undici-types: 7.16.0 - '@types/node@25.3.3': + '@types/node@25.3.5': dependencies: undici-types: 7.18.2 @@ -912,15 +912,15 @@ snapshots: '@types/send@1.2.1': dependencies: - '@types/node': 25.3.3 + '@types/node': 25.3.5 '@types/ws@8.18.1': dependencies: - '@types/node': 25.3.3 + '@types/node': 25.3.5 '@types/xml2js@0.4.14': dependencies: - '@types/node': 25.3.3 + '@types/node': 25.3.5 acorn-walk@8.3.4: dependencies: @@ -936,7 +936,7 @@ snapshots: bun-types@1.3.10: dependencies: - '@types/node': 25.3.3 + '@types/node': 25.3.5 commondir@1.0.1: {} diff --git a/src/route.ts b/src/route.ts index 56fe20a..7c08dc2 100644 --- a/src/route.ts +++ b/src/route.ts @@ -779,8 +779,44 @@ export class QueryRouterServer extends Qu } return super.run(msg, ctx as RouteContext); } + + async runAction( + api: T, + payload: RunActionPayload, + ctx?: RouteContext + ) { + const { path, key, id } = api as any; + return this.run({ path, key, id, payload }, ctx); + } } export class Mini extends QueryRouterServer { } +/** JSON Schema 基本类型映射到 TypeScript 类型 */ +type JsonSchemaTypeToTS = + 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 = + { [K in keyof T as T[K] extends { optional: true } ? never : K]: JsonSchemaTypeToTS } & + { [K in keyof T as T[K] extends { optional: true } ? K : never]?: JsonSchemaTypeToTS }; + +/** 处理两种 args 格式:完整 JSON Schema(含 properties)或简单 key->type 映射 */ +type ArgsToPayload = + T extends { type: "object"; properties: infer P } + ? ArgsShapeToPayload

+ : ArgsShapeToPayload; + +/** 从 API 定义中提取 metadata.args */ +type ExtractArgs = + T extends { metadata: { args: infer A } } ? A : {}; + +/** runAction 第二个参数的类型,根据第一个参数的 metadata.args 推断 */ +export type RunActionPayload = ArgsToPayload>; \ No newline at end of file diff --git a/src/test/run-schema.ts b/src/test/run-schema.ts new file mode 100644 index 0000000..2b57b84 --- /dev/null +++ b/src/test/run-schema.ts @@ -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 }) \ No newline at end of file