import { type BooleanLike, type FilterQuery, type Primitive, exp, isBooleanLike, isPrimitive, makeValidator, } from "core"; import type { DeleteQueryBuilder, ExpressionBuilder, ExpressionWrapper, SelectQueryBuilder, UpdateQueryBuilder, } from "kysely"; type Builder = ExpressionBuilder; type Wrapper = ExpressionWrapper; type WhereQb = | SelectQueryBuilder | UpdateQueryBuilder | DeleteQueryBuilder; function key(e: unknown): string { if (typeof e !== "string") { throw new Error(`Invalid key: ${e}`); } return e as string; } const expressions = [ exp( "$eq", (v: Primitive) => isPrimitive(v), (v, k, eb: Builder) => eb(key(k), "=", v), ), exp( "$ne", (v: Primitive) => isPrimitive(v), (v, k, eb: Builder) => eb(key(k), "!=", v), ), exp( "$gt", (v: Primitive) => isPrimitive(v), (v, k, eb: Builder) => eb(key(k), ">", v), ), exp( "$gte", (v: Primitive) => isPrimitive(v), (v, k, eb: Builder) => eb(key(k), ">=", v), ), exp( "$lt", (v: Primitive) => isPrimitive(v), (v, k, eb: Builder) => eb(key(k), "<", v), ), exp( "$lte", (v: Primitive) => isPrimitive(v), (v, k, eb: Builder) => eb(key(k), "<=", v), ), exp( "$isnull", (v: BooleanLike) => isBooleanLike(v), (v, k, eb: Builder) => eb(key(k), v ? "is" : "is not", null), ), exp( "$in", (v: any[]) => Array.isArray(v), (v, k, eb: Builder) => eb(key(k), "in", v), ), exp( "$notin", (v: any[]) => Array.isArray(v), (v, k, eb: Builder) => eb(key(k), "not in", v), ), exp( "$between", (v: [number, number]) => Array.isArray(v) && v.length === 2, (v, k, eb: Builder) => eb.between(key(k), v[0], v[1]), ), exp( "$like", (v: Primitive) => isPrimitive(v), (v, k, eb: Builder) => eb(key(k), "like", String(v).replace(/\*/g, "%")), ), ]; export type WhereQuery = FilterQuery; const validator = makeValidator(expressions); export class WhereBuilder { static addClause(qb: QB, query: WhereQuery) { if (Object.keys(query).length === 0) { return qb; } // @ts-ignore return qb.where((eb) => { const fns = validator.build(query, { value_is_kv: true, exp_ctx: eb, convert: true, }); if (fns.$or.length > 0 && fns.$and.length > 0) { return eb.and(fns.$and).or(eb.and(fns.$or)); } else if (fns.$or.length > 0) { return eb.or(fns.$or); } return eb.and(fns.$and); }); } static convert(query: WhereQuery): WhereQuery { return validator.convert(query); } static getPropertyNames(query: WhereQuery): string[] { const { keys } = validator.build(query, { value_is_kv: true, exp_ctx: () => null, convert: true, }); return Array.from(keys); } }