UNPKG

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.

151 lines (150 loc) 3.89 kB
//#region src/core/where.ts const unaryComparisonOps = [ "=", ">", ">=", "<", "<=", "like", "ilike" ]; const multiComparisonOps = ["in"]; const setComparisonOps = [ "@>", "<@", "&&" ]; const predicateOps = ["is null"]; const multiLogicalWhereOps = ["and", "or"]; const unaryLogicalWhereOp = "not"; const newComparisonWhere = () => (col, op, value) => buildInputWhere(col, op, value); const buildInputWhere = (col, op, value) => { const field = Array.isArray(col) ? col : [col]; if (op === "in") return { field, op, values: value }; if (op === "is null") return { field, op }; return { field, op, value }; }; const mergeWhere = (existing, next) => { if (!existing) return next; if (existing.op === "and") return { op: "and", conditions: [...existing.conditions || [], next] }; return { op: "and", conditions: [existing, next] }; }; /** * 类型守卫:将 `QueryWhere` 收窄为 `ComparisonWhere`。 * * TS 无法通过 `op === 'and' || op === 'or'` 的否定方向消除 * `MultiLogicalWhere`(其 discriminant `op` 是 `'and' | 'or'` 联合类型), * 导致 `field` / `value` / `values` 在后继代码中不可被类型访问。 * 此守卫通过显式排除逻辑运算符来绕过该限制。 */ const isComparisonWhere = (where) => where.op !== "not" && where.op !== "and" && where.op !== "or"; const createExpr = (q) => { return { _q: q, where(col, op, value) { if (col === null || col === void 0) return createExpr(q); if (col && typeof col === "object" && "op" in col) return createExpr(col); const field = Array.isArray(col) ? col : [col]; return createExpr(op === "in" ? { field, op, values: value } : op === "is null" ? { field, op } : { field, op, value }); }, and(exprs) { return createExpr({ op: "and", conditions: exprs.map((e) => e._q).filter(Boolean) }); }, or(exprs) { return createExpr({ op: "or", conditions: exprs.map((e) => e._q).filter(Boolean) }); }, not(expr) { if (expr._q === null || expr._q === void 0) return createExpr(); return createExpr({ op: "not", condition: expr._q }); } }; }; const newWhere = (state) => { const where = (col, op, value) => { return newWhere(mergeWhere(state, buildInputWhere(col, op, value))); }; return { toJSON: () => state, where: (col, op, value) => { if (col === null || col === void 0) return newWhere(state); if (typeof col === "function") { const cbWhere = col(createExpr())._q; return newWhere(state ? { op: "and", conditions: [state, cbWhere] } : cbWhere); } if (col && typeof col === "object" && "op" in col) { const whereObj = col; return newWhere(state ? { op: "and", conditions: [state, whereObj] } : whereObj); } return where(col, op, value); } }; }; const fieldEqual = (a, b) => a.length === b.length && a.every((v, i) => v === b[i]); const isComparisonNode = (node, field) => "field" in node && fieldEqual(node.field, field); const findWhere = (where) => { const search = (field, op) => { if (!where) return; const fieldPath = Array.isArray(field) ? field : [field]; const walk = (node) => { if (isComparisonNode(node, fieldPath)) { if (!op || node.op === op) return node; } if (node.op === "not") return walk(node.condition); if (node.op === "and" || node.op === "or") for (const sub of node.conditions) { const found = walk(sub); if (found) return found; } }; return walk(where); }; return { eq: (field) => search(field, "="), in: (field) => search(field, "in"), find: search }; }; //#endregion export { buildInputWhere, createExpr, findWhere, isComparisonWhere, mergeWhere, multiComparisonOps, multiLogicalWhereOps, newComparisonWhere, newWhere, predicateOps, setComparisonOps, unaryComparisonOps, unaryLogicalWhereOp };