chore: 更新版本号至0.0.80,并在 route.ts 中重构 toJSONSchema 和 fromJSONSchema 函数以使用新的 schema.ts 模块
This commit is contained in:
@@ -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.79",
|
"version": "0.0.80",
|
||||||
"description": "",
|
"description": "",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/router.js",
|
"main": "./dist/router.js",
|
||||||
|
|||||||
107
src/route.ts
107
src/route.ts
@@ -3,6 +3,8 @@ 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 { randomId } from './utils/random.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> = {
|
export type RouteContext<T = { code?: number }, S = any> = {
|
||||||
/**
|
/**
|
||||||
@@ -245,101 +247,17 @@ export class Route<U = { [key: string]: any }, T extends SimpleObject = SimpleOb
|
|||||||
throw new CustomError(...args);
|
throw new CustomError(...args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const extractArgs = (args: any) => {
|
|
||||||
if (args && typeof args === 'object' && typeof args.shape === 'object') {
|
|
||||||
return args.shape as z.ZodRawShape;
|
|
||||||
}
|
|
||||||
return args || {};
|
|
||||||
};
|
|
||||||
|
|
||||||
const toJSONSchemaRoute = (route: RouteInfo) => {
|
const toJSONSchemaRoute = (route: RouteInfo) => {
|
||||||
const pickValues = pick(route, pickValue as any);
|
const pickValues = pick(route, pickValue as any);
|
||||||
if (pickValues?.metadata?.args) {
|
if (pickValues?.metadata?.args) {
|
||||||
let args = pickValues.metadata.args;
|
pickValues.metadata.args = toJSONSchema(pickValues?.metadata?.args, { mergeObject: false });
|
||||||
// 如果 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] as z.ZodAny;
|
|
||||||
if (item && typeof item === 'object' && typeof item.toJSONSchema === 'function') {
|
|
||||||
newArgs[key] = item.toJSONSchema();
|
|
||||||
} else {
|
|
||||||
newArgs[key] = args[key]; // 可能不是schema
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pickValues.metadata.args = newArgs;
|
|
||||||
}
|
}
|
||||||
return pickValues;
|
return pickValues;
|
||||||
}
|
}
|
||||||
const fromJSONSchemaRoute = (route: RouteInfo): RouteInfo => {
|
|
||||||
const args = route?.metadata?.args;
|
|
||||||
if (!args) return route;
|
|
||||||
const newArgs = fromJSONSchema(args);
|
|
||||||
route.metadata.args = newArgs;
|
|
||||||
return route;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
export const toJSONSchema = schema.toJSONSchema;
|
||||||
* 剥离第一层schema,转换为JSON Schema,无论是skill还是其他的infer比纯粹的zod object schema更合适,因为它可能包含其他的字段,而不仅仅是schema
|
export const fromJSONSchema = schema.fromJSONSchema;
|
||||||
* @param args
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export const toJSONSchema = (args: any, opts?: { mergeObject?: boolean }): { [key: string]: any } => {
|
|
||||||
const mergeObject = opts?.mergeObject ?? true;
|
|
||||||
if (!args) return {};
|
|
||||||
if (mergeObject) {
|
|
||||||
if (typeof args === 'object' && typeof args.toJSONSchema === 'function') {
|
|
||||||
return args.toJSONSchema();
|
|
||||||
}
|
|
||||||
const schema = z.object(args);
|
|
||||||
return schema.toJSONSchema();
|
|
||||||
}
|
|
||||||
// 如果 args 本身是一个 zod object schema,先提取 shape
|
|
||||||
args = extractArgs(args);
|
|
||||||
const keys = Object.keys(args);
|
|
||||||
let newArgs: { [key: string]: any } = {};
|
|
||||||
for (let key of keys) {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return newArgs;
|
|
||||||
}
|
|
||||||
export const fromJSONSchema = <Merge extends boolean = true>(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];
|
|
||||||
// fromJSONSchema 可能会失败,所以先 optional,等使用的时候再验证
|
|
||||||
newArgs[key] = z.fromJSONSchema(item).optional();
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @parmas overwrite 是否覆盖已存在的route,默认true
|
* @parmas overwrite 是否覆盖已存在的route,默认true
|
||||||
@@ -657,21 +575,6 @@ export class QueryRouter {
|
|||||||
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) => {
|
||||||
const pickValues = pick(r, pickValue as any);
|
const pickValues = pick(r, pickValue as any);
|
||||||
if (pickValues?.metadata?.args) {
|
|
||||||
// const demoArgs = { k: tool.schema.string().describe('示例参数') };
|
|
||||||
const args = pickValues.metadata.args;
|
|
||||||
const keys = Object.keys(args);
|
|
||||||
const newArgs: { [key: string]: any } = {};
|
|
||||||
for (let key of keys) {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pickValues.metadata.args = newArgs;
|
|
||||||
}
|
|
||||||
return pickValues;
|
return pickValues;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
100
src/validator/schema.ts
Normal file
100
src/validator/schema.ts
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
const extractArgs = (args: any) => {
|
||||||
|
if (args && typeof args === 'object' && typeof args.shape === 'object') {
|
||||||
|
return args.shape as z.ZodRawShape;
|
||||||
|
}
|
||||||
|
return args || {};
|
||||||
|
};
|
||||||
|
|
||||||
|
type ZodOverride = (opts: { jsonSchema: any; path: string[]; zodSchema: z.ZodTypeAny }) => void;
|
||||||
|
/**
|
||||||
|
* 剥离第一层schema,转换为JSON Schema,无论是skill还是其他的infer比纯粹的zod object schema更合适,因为它可能包含其他的字段,而不仅仅是schema
|
||||||
|
* @param args
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const toJSONSchema = (args: any, opts?: { mergeObject?: boolean, override?: ZodOverride }): { [key: string]: any } => {
|
||||||
|
const mergeObject = opts?.mergeObject ?? false;
|
||||||
|
if (!args) return {};
|
||||||
|
const _override = ({ jsonSchema, path, zodSchema }) => {
|
||||||
|
if (Array.isArray(path) && path.length > 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const isOptional = (zodSchema as any).isOptional?.();
|
||||||
|
if (isOptional) {
|
||||||
|
// 添加自定义属性
|
||||||
|
jsonSchema.optional = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const isError = (keys: string[]) => {
|
||||||
|
const errorKeys: string[] = ["toJSONSchema", "def", "type", "parse"]
|
||||||
|
const hasErrorKeys = errorKeys.every(key => keys.includes(key));
|
||||||
|
return hasErrorKeys;
|
||||||
|
}
|
||||||
|
const override: any = opts?.override || _override;
|
||||||
|
if (mergeObject) {
|
||||||
|
if (typeof args === 'object' && typeof args.toJSONSchema === 'function') {
|
||||||
|
return args.toJSONSchema();
|
||||||
|
}
|
||||||
|
if (isError(Object.keys(args))) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
// 如果 mergeObject 为 true,直接将整个对象转换为 JSON Schema
|
||||||
|
// 先检测是否是一个错误的 schema
|
||||||
|
const schema = z.object(args);
|
||||||
|
return schema.toJSONSchema();
|
||||||
|
}
|
||||||
|
// 如果 args 本身是一个 zod object schema,先提取 shape
|
||||||
|
args = extractArgs(args);
|
||||||
|
let keys = Object.keys(args);
|
||||||
|
if (isError(keys)) {
|
||||||
|
console.error(`[toJSONSchema error]: 解析到的 schema 可能不正确,包含了zod默认的value的schema. 请检查输入的 schema 是否正确。`);
|
||||||
|
args = {};
|
||||||
|
keys = [];
|
||||||
|
}
|
||||||
|
if (mergeObject) {
|
||||||
|
|
||||||
|
}
|
||||||
|
let newArgs: { [key: string]: any } = {};
|
||||||
|
for (let key of keys) {
|
||||||
|
const item = args[key] as z.ZodAny;
|
||||||
|
if (item && typeof item === 'object' && typeof item.toJSONSchema === 'function') {
|
||||||
|
newArgs[key] = item.toJSONSchema({ override });
|
||||||
|
} else {
|
||||||
|
newArgs[key] = args[key]; // 可能不是schema
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newArgs;
|
||||||
|
}
|
||||||
|
export const fromJSONSchema = <Merge extends boolean = false>(args: any = {}, opts?: { mergeObject?: boolean }) => {
|
||||||
|
let resultArgs: any = null;
|
||||||
|
const mergeObject = opts?.mergeObject ?? false;
|
||||||
|
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];
|
||||||
|
// fromJSONSchema 可能会失败,所以先 optional,等使用的时候再验证
|
||||||
|
newArgs[key] = z.fromJSONSchema(item)
|
||||||
|
if (item.optional) {
|
||||||
|
newArgs[key] = newArgs[key].optional();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user