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.

108 lines (107 loc) 3.35 kB
//#region src/kysely/fromKysely.ts const opReverseMap = { "=": "=", ">": ">", ">=": ">=", "<": "<", "<=": "<=", like: "like", ilike: "ilike", in: "in" }; const parseRawSqlField = (sql) => { const rootMatch = sql.match(/^"((?:[^"]|"")*)"(.*)$/); if (!rootMatch) return ["unknown_path"]; const root = rootMatch[1].replace(/""/g, "\""); const tail = rootMatch[2]; if (!tail) return [root]; if (/^\[\d+\]/.test(tail)) return [root, ...[...tail.matchAll(/\[(\d+)\]/g)].map((m) => Number(m[1]) - 1)]; const segments = []; for (const m of tail.matchAll(/->>?(?:'((?:[^']|'')*)'|(\d+))/g)) if (m[1] !== void 0) segments.push(m[1].replace(/''/g, "'")); else if (m[2] !== void 0) segments.push(Number(m[2])); if (segments.length === 0) return ["unknown_path"]; return [root, ...segments]; }; const parseFieldFromNode = (node) => { if (node?.kind === "ReferenceNode" && node.column?.kind === "ColumnNode") return [node.column.column.name]; if (node?.kind === "RawNode" && node.sqlFragments.length === 1) return parseRawSqlField(node.sqlFragments[0]); return ["unknown_path"]; }; const parseWhere = (node) => { if (!node) return; if (node.kind === "AndNode" || node.kind === "OrNode") { const conditions = [parseWhere(node.left), parseWhere(node.right)].filter((c) => !!c); if (conditions.length === 0) return; if (conditions.length === 1) return conditions[0]; return { op: node.kind === "AndNode" ? "and" : "or", conditions }; } if (node.kind === "ParensNode") return parseWhere(node.node); if (node.kind === "UnaryOperationNode" && node.operator?.operator === "not") { const inner = parseWhere(node.operand); if (!inner) return; return { op: "not", condition: inner }; } if (node.kind === "BinaryOperationNode") { const field = parseFieldFromNode(node.leftOperand); if (field.length === 0) return; if (!node.operator?.operator) return; if (node.operator?.operator === "in") return { field, op: "in", values: node.rightOperand?.values ?? [] }; return { field, op: opReverseMap[node.operator.operator], value: node.rightOperand?.value }; } }; const flattenLogic = (where) => { if (where.op === "and" || where.op === "or") { const flat = []; for (const c of where.conditions) { const flattened = flattenLogic(c); if (flattened.op === where.op) flat.push(...flattened.conditions); else flat.push(flattened); } return { op: where.op, conditions: flat }; } if (where.op === "not") return { op: "not", condition: flattenLogic(where.condition) }; return where; }; const fromKysely = (queryBuilder) => { const node = queryBuilder.toOperationNode(); const result = {}; if (node.limit) { const limitValue = node.limit.limit.value; if (typeof limitValue === "number") result.limit = limitValue; } if (node.offset) { const offsetValue = node.offset.offset.value; if (typeof offsetValue === "number") result.offset = offsetValue; } if (node.orderBy && node.orderBy.items.length > 0) result.orderBy = node.orderBy.items.map((item) => { return { field: parseFieldFromNode(item.orderBy), direction: item.direction?.sqlFragments?.[0] === "desc" ? "desc" : "asc" }; }); const parsed = parseWhere(node.where?.where); if (parsed) result.where = flattenLogic(parsed); return result; }; //#endregion export { fromKysely };