agnostic-query
Version:
Type-safe fluent builder for portable query schemas. Runtime-agnostic, database-agnostic — the same QuerySchema drives Drizzle, Kysely, db0, or raw SQL.
91 lines (82 loc) • 3.01 kB
text/typescript
import z from 'zod';
import type { FieldPathByShape, SchemaShape } from './core/schema.ts';
import {
multiLogicalWhereOps,
predicateOps,
setComparisonOps,
toMultiComparisonOps,
type QueryWhere,
unaryComparisonOps,
} from './core/where.ts';
export const createFieldPathSchema = <TShape extends SchemaShape>() =>
z.preprocess(
(val) => (typeof val === 'string' ? [val] : val),
z.tuple([z.string()]).rest(z.union([z.string(), z.number()])),
) as unknown as z.ZodType<FieldPathByShape<TShape>>;
export const createWhereSchema = <TShape extends SchemaShape>() => {
const unaryComparisonSchema = z.object({
field: createFieldPathSchema<TShape>(),
op: z.enum(unaryComparisonOps),
value: z.any(),
});
const multiComparisonSchema = z.object({
field: createFieldPathSchema<TShape>(),
op: z.literal(toMultiComparisonOps[0]),
values: z.array(z.any()),
});
const predicateSchema = z.object({
field: createFieldPathSchema<TShape>(),
op: z.enum(predicateOps),
});
const setComparisonSchema = z.object({
field: createFieldPathSchema<TShape>(),
op: z.enum(setComparisonOps),
value: z.any(),
});
type Out = QueryWhere<TShape, any>;
const schema: z.ZodType<Out, Out> = z.lazy(() =>
z.union([
unaryComparisonSchema,
predicateSchema,
setComparisonSchema,
multiComparisonSchema,
z.object({
op: z.enum(multiLogicalWhereOps),
conditions: z.array(schema),
}),
z.object({
op: z.literal('not'),
condition: schema,
}),
]),
);
return schema;
};
export const createOrderBySchema = <TShape extends SchemaShape>() => {
// 1. 定义单个排序对象的验证器
const itemSchema = z.object({
// 使用 any 避开复杂的路径推导,但在导出时通过类型断言保证安全
field: createFieldPathSchema<TShape>(),
direction: z.enum(['asc', 'desc']),
});
// 2. 返回数组验证器
return z.array(itemSchema);
};
export const createQuerySchema = <TShape extends SchemaShape>() => {
return z.object({
// 这里的 createWhereSchema 内部已经锁定了 TShape
where: createWhereSchema<TShape>().nullish(),
// 这里的 createOrderBySchema 内部也锁定了 TShape
orderBy: createOrderBySchema<TShape>().optional(),
limit: z.number().optional(),
offset: z.number().default(0),
table: z.string().optional(),
// cursor: z.object({ // 之后再实现 游标查询
// // whereFrom // 定位下一页起始点 // 获取游标之后的行的条件表达式。对于多列 ORDER BY,用 OR + AND 组合成复合条件。示例在对 col1 ASC, col2 DESC 且游标值为 [v1, v2] 时生成:
// // (col1 > v1) OR (col1 = v1 AND col2 < v2)
// // 这样能正确处理多列排序的边界。
// // whereCurrent // 定位到等于当前游标值的行(仅用第一排序列)。用于处理边界上的重复值。对普通值就是 eq(col1, v1),对 Date 等连续类型则用 AND(gte(...), lt(...)) 范围匹配防止精度问题
// lastKey: z.string().or(z.number()).optional(),
// }),
});
};