API index and search · Build metadata
Source snapshot
packages/devtools/src/ast.ts
1// Read-only AST helpers for the queries inspector (DEBUG-TOOLS-BROWSER-DESIGN §4.2): the table2// footprint (for the pending-axis intersection) and a one-line human summary. Pure functions over3// the `@rindle/client` `Ast` — no engine, no side effects.45import type { Ast, Condition, ValuePosition } from "@rindle/client";67/** Every base table this query can read: the root, each `related` subquery, and any correlated8 * subquery in the `where` tree (recursively). Mirrors the optimistic backend's `queryTables`9 * derivation (DEBUG-TOOLS-BROWSER-DESIGN §4.2 — a `count(comments)` subquery names `comment`, so a10 * comment mutation flips this query's pending axis). */11export function collectTables(ast: Ast, into: Set<string> = new Set()): Set<string> {12 into.add(ast.table);13 for (const r of ast.related ?? []) collectTables(r.subquery, into);14 if (ast.where) collectTablesFromCondition(ast.where, into);15 return into;16}1718function collectTablesFromCondition(cond: Condition, into: Set<string>): void {19 if (cond.type === "and" || cond.type === "or") {20 for (const c of cond.conditions) collectTablesFromCondition(c, into);21 } else if (cond.type === "correlatedSubquery") {22 collectTables(cond.related.subquery, into);23 }24}2526/** A compact, one-line description of a query AST for the inspector header — best-effort and27 * truncation-friendly (the full AST is available for a collapsible pretty-print alongside). */28export function summarizeAst(ast: Ast): string {29 const parts: string[] = [ast.alias ? `${ast.table} as ${ast.alias}` : ast.table];30 if (ast.aggregate) parts.push(ast.aggregate === "count" ? "count(*)" : ast.aggregate);31 if (ast.select?.length) parts.push(`select(${ast.select.join(", ")})`);32 if (ast.where) parts.push(`where(${summarizeCondition(ast.where)})`);33 if (ast.orderBy?.length) parts.push(`order(${ast.orderBy.map(([f, d]) => `${f} ${d}`).join(", ")})`);34 if (ast.limit !== undefined) parts.push(`limit ${ast.limit}`);35 if (ast.one) parts.push("one");36 if (ast.related?.length) {37 const rels = ast.related.map((r) => {38 const sub = r.subquery;39 const tag = sub.aggregate ? `${sub.alias ?? sub.table}(count)` : (sub.alias ?? sub.table);40 return tag;41 });42 parts.push(`{ ${rels.join(", ")} }`);43 }44 return parts.join(" ");45}4647function summarizeCondition(cond: Condition): string {48 switch (cond.type) {49 case "simple":50 return `${valueStr(cond.left)} ${cond.op} ${valueStr(cond.right)}`;51 case "and":52 return cond.conditions.map(summarizeCondition).join(" AND ");53 case "or":54 return `(${cond.conditions.map(summarizeCondition).join(" OR ")})`;55 case "correlatedSubquery":56 return `${cond.op} ${cond.related.subquery.table}`;57 }58}5960function valueStr(v: ValuePosition): string {61 if (v.type === "column") return v.name;62 const lit = v.value;63 if (typeof lit === "string") return JSON.stringify(lit);64 if (Array.isArray(lit)) return `[${lit.length}]`;65 return String(lit);66}67