diff --git a/bun.lock b/bun.lock index 89c24fb..554356d 100644 --- a/bun.lock +++ b/bun.lock @@ -13,7 +13,7 @@ "@kevisual/dts": "^0.0.4", "@kevisual/js-filter": "^0.0.5", "@kevisual/local-proxy": "^0.0.8", - "@kevisual/query": "^0.0.46", + "@kevisual/query": "^0.0.47", "@kevisual/use-config": "^1.0.30", "@opencode-ai/plugin": "^1.2.6", "@types/bun": "^1.3.9", @@ -53,7 +53,7 @@ "@kevisual/local-proxy": ["@kevisual/local-proxy@0.0.8", "", {}, "sha512-VX/P+6/Cc8ruqp34ag6gVX073BchUmf5VNZcTV/6MJtjrNE76G8V6TLpBE8bywLnrqyRtFLIspk4QlH8up9B5Q=="], - "@kevisual/query": ["@kevisual/query@0.0.46", "", {}, "sha512-JwHV16ehk8JWM5wiWW5kz9yTg4HrOmmnci5QvwQYdhXYXDzGpUrOxeoz3wloMs4kX3bkowz97iLLW6uQdgUoTw=="], + "@kevisual/query": ["@kevisual/query@0.0.47", "", {}, "sha512-ZR7WXeDDGUSzBtcGVU3J173sA0hCqrGTw5ybGbdNGlM0VyJV/XQIovCcSoZh1YpnciLRRqJvzXUgTnCkam+M3g=="], "@kevisual/use-config": ["@kevisual/use-config@1.0.30", "", { "dependencies": { "@kevisual/load": "^0.0.6" }, "peerDependencies": { "dotenv": "^17" } }, "sha512-kPdna0FW/X7D600aMdiZ5UTjbCo6d8d4jjauSc8RMmBwUU6WliFDSPUNKVpzm2BsDX5Nth1IXFPYMqH+wxqAmw=="], diff --git a/package.json b/package.json index 252bcbb..4379b6b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package", "name": "@kevisual/router", - "version": "0.0.76", + "version": "0.0.77", "description": "", "type": "module", "main": "./dist/router.js", @@ -27,7 +27,7 @@ "@kevisual/dts": "^0.0.4", "@kevisual/js-filter": "^0.0.5", "@kevisual/local-proxy": "^0.0.8", - "@kevisual/query": "^0.0.46", + "@kevisual/query": "^0.0.47", "@kevisual/use-config": "^1.0.30", "@opencode-ai/plugin": "^1.2.6", "@types/bun": "^1.3.9", diff --git a/src/route.ts b/src/route.ts index 0877ec1..9af27c5 100644 --- a/src/route.ts +++ b/src/route.ts @@ -252,7 +252,7 @@ export const extractArgs = (args: any) => { return args || {}; }; -export const toJSONSchema = (route: RouteInfo) => { +const toJSONSchemaRoute = (route: RouteInfo) => { const pickValues = pick(route, pickValue as any); if (pickValues?.metadata?.args) { let args = pickValues.metadata.args; @@ -273,23 +273,62 @@ export const toJSONSchema = (route: RouteInfo) => { } return pickValues; } - -export const fromJSONSchema = (route: RouteInfo): RouteInfo => { +const fromJSONSchemaRoute = (route: RouteInfo): RouteInfo => { const args = route?.metadata?.args; if (!args) return route; - if (args["$schema"] || (args.type === 'object' && args.properties && typeof args.properties === 'object')) { - // 可能是整个schema - route.metadata.args = z.fromJSONSchema(args); - return route; - } + const newArgs = fromJSONSchema(args); + route.metadata.args = newArgs; + return route; +} + +/** + * 剥离第一层schema,转换为JSON Schema,无论是skill还是其他的infer比纯粹的zod object schema更合适,因为它可能包含其他的字段,而不仅仅是schema + * @param args + * @returns + */ +export const toJSONSchema = (args: any): { [key: string]: any } => { + // 如果 args 本身是一个 zod object schema,先提取 shape + args = extractArgs(args); const keys = Object.keys(args); const newArgs: { [key: string]: any } = {}; for (let key of keys) { - const item = args[key]; - newArgs[key] = z.fromJSONSchema(item); + const item = args[key] as z.ZodAny; + if (item && typeof item === 'object' && typeof item.toJSONSchema === 'function') { + newArgs[key] = item.toJSONSchema(); + } else { + newArgs[key] = args[key]; // 可能不是schema + } } - route.metadata.args = newArgs; - return route; + return newArgs; +} +export const fromJSONSchema = (args: any = {}, opts?: { mergeObject?: boolean }) => { + let resultArgs: any = null; + const mergeObject = opts?.mergeObject ?? true; + if (args["$schema"] || (args.type === 'object' && args.properties && typeof args.properties === 'object')) { + // 可能是整个schema + const objectSchema = z.fromJSONSchema(args); + const extract = extractArgs(objectSchema); + const keys = Object.keys(extract); + const newArgs: { [key: string]: any } = {}; + for (let key of keys) { + newArgs[key] = extract[key]; + } + resultArgs = newArgs; + } + if (!resultArgs) { + const keys = Object.keys(args); + const newArgs: { [key: string]: any } = {}; + for (let key of keys) { + const item = args[key]; + newArgs[key] = z.fromJSONSchema(item); + } + resultArgs = newArgs; + } + if (mergeObject) { + resultArgs = z.object(resultArgs); + } + type ResultArgs = Merge extends true ? z.ZodObject<{ [key: string]: any }> : { [key: string]: z.ZodTypeAny }; + return resultArgs as unknown as ResultArgs; } /** @@ -698,7 +737,7 @@ export class QueryRouter { ctx.body = { list: list.map((item) => { const route = pick(item, ['id', 'path', 'key', 'description', 'middleware', 'metadata'] as const); - return toJSONSchema(route); + return toJSONSchemaRoute(route); }), isUser }; diff --git a/src/test/schema.ts b/src/test/schema.ts new file mode 100644 index 0000000..fa155e6 --- /dev/null +++ b/src/test/schema.ts @@ -0,0 +1,14 @@ +import { toJSONSchema, fromJSONSchema } from "@/route.ts"; +import { z } from "zod"; +const schema = z.object({ + name: z.string(), + age: z.number(), + +}); +// console.log("schema", schema); +const jsonSchema = toJSONSchema(schema); +console.log("jsonSchema", jsonSchema); + +const newSchema = fromJSONSchema(jsonSchema, { mergeObject: true }); +console.log("newSchema shape", Object.keys(newSchema.shape)); +console.log('check', newSchema.safeParse({ name: "Alice", age: "30" })?.success); \ No newline at end of file