Files
query/src/query-api.ts

172 lines
5.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { DataOpts, Query } from "./query.ts";
import { z } from "zod";
import { createQueryByRoutes } from "./create-query/index.ts";
import { pick } from 'es-toolkit'
type Pos = {
path?: string;
key?: string;
id?: string;
metadata?: {
args?: Record<string, any>;
viewItem?: Record<string, any>;
url?: string;
source?: string;
[key: string]: any;
};
}
// JSON Schema 类型推断 - 使用更精确的类型匹配
type InferFromJSONSchema<T> =
// 处理带 enum 的字符串类型
T extends { type: "string"; enum: readonly (infer E)[] } ? E :
T extends { type: "string"; enum: (infer E)[] } ? E :
// 基础类型
T extends { type: "string" } ? string :
T extends { type: "number" } ? number :
T extends { type: "integer" } ? number :
T extends { type: "boolean" } ? boolean :
// 数组类型
T extends { type: "array"; items: infer I }
? Array<InferFromJSONSchema<I>>
: // 对象类型 - 带 required 字段(必需字段 + 可选字段)
// 注意:必须在 additionalProperties 检查之前,因为两者可能同时存在
T extends { type: "object"; properties: infer P; required: infer R extends readonly string[] }
? {
[K in keyof P as K extends R[number] ? K : never]: InferFromJSONSchema<P[K]>
} & {
[K in keyof P as K extends R[number] ? never : K]?: InferFromJSONSchema<P[K]>
}
: // 对象类型 - 不带 required 字段(所有字段可选)
T extends { type: "object"; properties: infer P }
? {
[K in keyof P]?: InferFromJSONSchema<P[K]>
}
: // 对象类型 - 只有 additionalProperties纯动态对象
T extends { type: "object"; additionalProperties: infer A }
? (A extends false ? Record<string, never> : Record<string, any>)
: // 默认情况 - 如果是对象但没有 type 字段,尝试递归推断
T extends Record<string, any>
? T extends { properties: infer P; required: infer R extends readonly string[] }
? {
[K in keyof P as K extends R[number] ? K : never]: InferFromJSONSchema<P[K]>
} & {
[K in keyof P as K extends R[number] ? never : K]?: InferFromJSONSchema<P[K]>
}
: T extends { properties: infer P }
? { [K in keyof P]?: InferFromJSONSchema<P[K]> }
: unknown
: unknown;
// 统一类型推断:支持 Zod schema 和原始 JSON Schema
type InferType<T> =
T extends z.ZodType<infer U> ? U : // Zod schema
T extends { type: infer TType } ? InferFromJSONSchema<T> : // 任何包含 type 字段的 JSON Schema忽略 $schema
T extends { properties: infer P } ? InferFromJSONSchema<T> : // 处理没有 type 但有 properties 的对象
T;
// 提取 args 对象,将每个 Zod schema 或 JSON Schema 转换为实际类型
type ExtractArgsFromMetadata<T> = T extends { metadata?: { args?: infer A } }
? A extends Record<string, any>
? { [K in keyof A]: InferType<A[K]> }
: never
: never;
// 类型映射:将 API 配置转换为方法签名
type ApiMethods<P extends { [path: string]: { [key: string]: Pos } }> = {
[Path in keyof P]: {
[Key in keyof P[Path]]: (
data?: Partial<ExtractArgsFromMetadata<P[Path][Key]>>,
opts?: DataOpts
) => ReturnType<Query['post']>
}
}
type QueryApiOpts<P extends { [path: string]: { [key: string]: Pos } } = {}> = {
query?: Query,
api?: P
}
export class QueryApi<P extends { [path: string]: { [key: string]: Pos } } = {}> {
query: Query;
constructor(opts?: QueryApiOpts<P>) {
this.query = opts?.query ?? new Query();
if (opts?.api) {
this.createApi(opts.api);
}
}
// 使用泛型来推断类型
post<T extends Pos>(
pos: T,
data?: Partial<ExtractArgsFromMetadata<T>>,
opts?: DataOpts
) {
const _pos = pick(pos, ['path', 'key', 'id']);
return this.query.post({
..._pos,
payload: data
}, opts)
}
createApi(api: P): asserts this is this & ApiMethods<P> {
const that = this as any;
const apiEntries = Object.entries(api);
const keepPaths = ['createApi', 'query', 'post'];
for (const [path, methods] of apiEntries) {
if (keepPaths.includes(path)) continue;
// 为每个 path 创建命名空间对象
if (!that[path]) {
that[path] = {};
}
for (const [key, pos] of Object.entries(methods)) {
that[path][key] = (data?: Partial<ExtractArgsFromMetadata<typeof pos>>, opts: DataOpts = {}) => {
const _pos = pick(pos, ['path', 'key', 'id']);
if (pos.metadata?.viewItem?.api?.url && !opts.url) {
opts.url = pos.metadata.viewItem.api.url;
}
return that.query.post({
..._pos,
payload: data
}, opts);
};
}
}
}
}
// 创建工厂函数,提供更好的类型推断
export function createQueryApi<P extends { [path: string]: { [key: string]: Pos } }>(
opts?: QueryApiOpts<P>
): QueryApi<P> & ApiMethods<P> {
return new QueryApi(opts) as QueryApi<P> & ApiMethods<P>;
}
export { createQueryByRoutes };
// const demo = {
// "test_path": {
// "test_key": {
// "path": "demo",
// "key": "test",
// metadata: {
// args: {
// name: z.string(),
// age: z.number(),
// }
// }
// }
// }
// } as const;
// // 方式1: 使用工厂函数创建(推荐)
// const queryApi = createQueryApi({ query: new Query(), api: demo });
// // 现在调用时会有完整的类型推断
// // data 参数会被推断为 { name?: string, age?: number }
// queryApi.test_path.test_key({ name: "test", age: 18 });
// // 也可以不传参数
// queryApi.test_path.test_key();
// // 或者只传递 opts
// queryApi.test_path.test_key(undefined, { timeout: 5000 });