UNPKG

trellis

Version:

Agentic State Engine — event-sourced causal graph with branching, decision traces, and realtime sync for AI-native applications

518 lines (514 loc) 15 kB
import { QueryEngine, literal, variable } from "./chunk-BYTAOXGW.js"; // src/core/query/parser.ts function tokenize(input) { const tokens = []; let i = 0; while (i < input.length) { if (/\s/.test(input[i])) { i++; continue; } if (input[i] === "/" && input[i + 1] === "/") { while (i < input.length && input[i] !== "\n") i++; continue; } const pos = i; if ("[](){},:".includes(input[i])) { tokens.push({ kind: "symbol", value: input[i], pos }); i++; continue; } if (input[i] === "!" && input[i + 1] === "=") { tokens.push({ kind: "symbol", value: "!=", pos }); i += 2; continue; } if (input[i] === "<" && input[i + 1] === "=") { tokens.push({ kind: "symbol", value: "<=", pos }); i += 2; continue; } if (input[i] === ">" && input[i + 1] === "=") { tokens.push({ kind: "symbol", value: ">=", pos }); i += 2; continue; } if ("<>=".includes(input[i])) { tokens.push({ kind: "symbol", value: input[i], pos }); i++; continue; } if (input[i] === '"') { i++; let s = ""; while (i < input.length && input[i] !== '"') { if (input[i] === "\\" && i + 1 < input.length) { s += input[i + 1]; i += 2; } else { s += input[i]; i++; } } if (i < input.length) i++; tokens.push({ kind: "string", value: s, pos }); continue; } if (/[0-9]/.test(input[i]) || input[i] === "-" && i + 1 < input.length && /[0-9]/.test(input[i + 1])) { let n = input[i]; i++; while (i < input.length && /[0-9.]/.test(input[i])) { n += input[i]; i++; } tokens.push({ kind: "number", value: n, pos }); continue; } if (/[?a-zA-Z_]/.test(input[i])) { let w = ""; while (i < input.length && /[?a-zA-Z0-9_.:/-]/.test(input[i])) { w += input[i]; i++; } tokens.push({ kind: "word", value: w, pos }); continue; } i++; } tokens.push({ kind: "eof", value: "", pos: input.length }); return tokens; } var Parser = class { tokens; pos = 0; constructor(tokens) { this.tokens = tokens; } peek() { return this.tokens[this.pos]; } advance() { return this.tokens[this.pos++]; } expect(kind, value) { const t = this.advance(); if (t.kind !== kind || value !== void 0 && t.value !== value) { throw new Error(`Expected ${kind}${value ? ` "${value}"` : ""} at pos ${t.pos}, got ${t.kind} "${t.value}"`); } return t; } match(kind, value) { const t = this.peek(); if (t.kind === kind && (value === void 0 || t.value === value)) { this.pos++; return true; } return false; } isAt(kind, value) { const t = this.peek(); return t.kind === kind && (value === void 0 || t.value === value); } // ----------------------------------------------------------------------- // Terms // ----------------------------------------------------------------------- parseTerm() { const t = this.peek(); if (t.kind === "word" && t.value.startsWith("?")) { this.advance(); return variable(t.value.slice(1)); } if (t.kind === "string") { this.advance(); return literal(t.value); } if (t.kind === "number") { this.advance(); const n = Number(t.value); return literal(n); } if (t.kind === "word") { const v = t.value; this.advance(); if (v === "true") return literal(true); if (v === "false") return literal(false); return literal(v); } throw new Error(`Unexpected token at pos ${t.pos}: ${t.kind} "${t.value}"`); } // ----------------------------------------------------------------------- // Patterns // ----------------------------------------------------------------------- parsePattern() { const t = this.peek(); if (t.kind === "word" && t.value.toUpperCase() === "NOT") { this.advance(); const inner = this.parsePattern(); return { kind: "not", pattern: inner }; } if (t.kind === "word" && t.value.toUpperCase() === "OR") { this.advance(); const branches = []; while (this.isAt("symbol", "{")) { this.advance(); const branch = []; while (!this.isAt("symbol", "}") && !this.isAt("eof", void 0)) { branch.push(this.parsePattern()); } this.expect("symbol", "}"); branches.push(branch); } return { kind: "or", branches }; } if (t.kind === "symbol" && t.value === "[") { this.advance(); const entity = this.parseTerm(); const attribute = this.parseTerm(); const value = this.parseTerm(); this.expect("symbol", "]"); return { kind: "fact", entity, attribute, value }; } if (t.kind === "symbol" && t.value === "(") { this.advance(); const source = this.parseTerm(); const attribute = this.parseTerm(); const target = this.parseTerm(); this.expect("symbol", ")"); return { kind: "link", source, attribute, target }; } if (t.kind === "word" && !t.value.startsWith("?")) { const name = this.advance().value; if (this.isAt("symbol", "(")) { this.advance(); const args = []; while (!this.isAt("symbol", ")") && !this.isAt("eof", void 0)) { args.push(this.parseTerm()); this.match("symbol", ","); } this.expect("symbol", ")"); return { kind: "rule", name, args }; } throw new Error(`Expected '(' after rule name "${name}" at pos ${t.pos}`); } throw new Error(`Cannot parse pattern at pos ${t.pos}: ${t.kind} "${t.value}"`); } // ----------------------------------------------------------------------- // Filter // ----------------------------------------------------------------------- parseFilter() { const left = this.parseTerm(); const op = this.advance().value; const right = this.parseTerm(); return { kind: "filter", left, op, right }; } // ----------------------------------------------------------------------- // Full Query // ----------------------------------------------------------------------- parseQuery() { const query = { select: [], where: [], filters: [], aggregates: [], orderBy: [], limit: 0, offset: 0 }; while (!this.isAt("eof", void 0)) { const kw = this.peek(); if (kw.kind !== "word") { throw new Error(`Expected keyword at pos ${kw.pos}, got ${kw.kind} "${kw.value}"`); } switch (kw.value.toUpperCase()) { case "SELECT": { this.advance(); while (this.peek().kind === "word" && this.peek().value.startsWith("?")) { query.select.push(this.advance().value.slice(1)); } break; } case "WHERE": { this.advance(); this.expect("symbol", "{"); while (!this.isAt("symbol", "}") && !this.isAt("eof", void 0)) { query.where.push(this.parsePattern()); } this.expect("symbol", "}"); break; } case "FILTER": { this.advance(); query.filters.push(this.parseFilter()); break; } case "AGGREGATE": { this.advance(); const op = this.advance().value; this.expect("symbol", "("); const varName = this.advance().value; const varClean = varName.startsWith("?") ? varName.slice(1) : varName; this.expect("symbol", ")"); this.expect("word", "AS"); const asName = this.advance().value; const asClean = asName.startsWith("?") ? asName.slice(1) : asName; query.aggregates.push({ op, variable: varClean, as: asClean }); break; } case "ORDER": { this.advance(); this.expect("word", "BY"); while (this.peek().kind === "word" && this.peek().value.startsWith("?")) { const v = this.advance().value.slice(1); let dir = "asc"; if (this.peek().kind === "word" && ["ASC", "DESC"].includes(this.peek().value.toUpperCase())) { dir = this.advance().value.toLowerCase(); } query.orderBy.push({ variable: v, direction: dir }); } break; } case "LIMIT": { this.advance(); query.limit = Number(this.expect("number").value); break; } case "OFFSET": { this.advance(); query.offset = Number(this.expect("number").value); break; } default: throw new Error(`Unknown keyword "${kw.value}" at pos ${kw.pos}`); } } return query; } // ----------------------------------------------------------------------- // Datalog Rule // ----------------------------------------------------------------------- parseRule() { const name = this.expect("word").value; this.expect("symbol", "("); const params = []; while (!this.isAt("symbol", ")") && !this.isAt("eof", void 0)) { const v = this.expect("word").value; params.push(v.startsWith("?") ? v.slice(1) : v); this.match("symbol", ","); } this.expect("symbol", ")"); if (this.isAt("symbol", ":")) { this.advance(); if (this.peek().kind === "number" && this.peek().value.startsWith("-")) { this.advance(); } } else if (this.isAt("word", ":-")) { this.advance(); } const body = []; const filters = []; while (!this.isAt("eof", void 0)) { if (this.peek().kind === "word" && this.peek().value.toUpperCase() === "FILTER") { this.advance(); filters.push(this.parseFilter()); } else { body.push(this.parsePattern()); } this.match("symbol", ","); } return { name, params, body, filters }; } }; function parseQuery(input) { const tokens = tokenize(input); return new Parser(tokens).parseQuery(); } function parseRule(input) { const tokens = tokenize(input); return new Parser(tokens).parseRule(); } function parseSimple(input) { const trimmed = input.trim(); const upper = trimmed.toUpperCase(); if (upper.startsWith("SELECT") || upper.startsWith("WHERE")) { return parseQuery(trimmed); } const findMatch = trimmed.match(/^find\s+(.+?)\s+where\s+(.+)$/i); if (findMatch) { const vars = findMatch[1].trim().split(/\s+/); const conditions = findMatch[2].trim(); const selectVars = vars.map((v) => v.startsWith("?") ? v : `?${v}`); const entity = selectVars[0]; const parts = conditions.split(/\s+and\s+/i); const patterns = []; const filters = []; for (const part of parts) { const eqMatch = part.match(/^(\S+)\s*(=|!=|<|<=|>|>=|contains|startsWith|endsWith|matches)\s*(.+)$/); if (eqMatch) { const [, attr, op, val] = eqMatch; const valTrimmed = val.trim(); if (op === "=") { patterns.push(`[${entity} "${attr}" ${valTrimmed}]`); } else { const tmpVar = `?_${attr.replace(/[^a-zA-Z0-9]/g, "_")}`; patterns.push(`[${entity} "${attr}" ${tmpVar}]`); filters.push(`FILTER ${tmpVar} ${op} ${valTrimmed}`); } } } const fullQuery = `SELECT ${selectVars.join(" ")} WHERE { ${patterns.join("\n ")} } ${filters.join("\n")}`; return parseQuery(fullQuery); } throw new Error(`Cannot parse query: "${trimmed}". Use full EQL-S syntax or "find ?e where attr = value".`); } // src/core/query/datalog.ts function transitiveClosureRules(ruleName, linkAttribute) { return [ // Base case: direct link { name: ruleName, params: ["x", "y"], body: [ { kind: "link", source: variable("x"), attribute: literal(linkAttribute), target: variable("y") } ], filters: [] }, // Recursive case: indirect via intermediate { name: ruleName, params: ["x", "y"], body: [ { kind: "link", source: variable("x"), attribute: literal(linkAttribute), target: variable("z") }, { kind: "rule", name: ruleName, args: [variable("z"), variable("y")] } ], filters: [] } ]; } function reverseReachabilityRules(ruleName, linkAttribute) { return [ { name: ruleName, params: ["x", "y"], body: [ { kind: "link", source: variable("y"), attribute: literal(linkAttribute), target: variable("x") } ], filters: [] }, { name: ruleName, params: ["x", "y"], body: [ { kind: "link", source: variable("z"), attribute: literal(linkAttribute), target: variable("x") }, { kind: "rule", name: ruleName, args: [variable("z"), variable("y")] } ], filters: [] } ]; } function siblingRules(ruleName, linkAttribute) { return [ { name: ruleName, params: ["a", "b"], body: [ { kind: "link", source: variable("a"), attribute: literal(linkAttribute), target: variable("parent") }, { kind: "link", source: variable("b"), attribute: literal(linkAttribute), target: variable("parent") } ], filters: [ { kind: "filter", left: variable("a"), op: "!=", right: variable("b") } ] } ]; } var DatalogRuntime = class { engine; constructor(store) { this.engine = new QueryEngine(store); } /** Register a Datalog rule (or multiple). */ addRule(rule) { this.engine.addRule(rule); } addRules(rules) { for (const r of rules) this.engine.addRule(r); } removeRule(name) { this.engine.removeRule(name); } /** Register built-in transitive closure for a link attribute. */ registerTransitiveClosure(ruleName, linkAttribute) { this.addRules(transitiveClosureRules(ruleName, linkAttribute)); } /** Register built-in reverse reachability for a link attribute. */ registerReverseReachability(ruleName, linkAttribute) { this.addRules(reverseReachabilityRules(ruleName, linkAttribute)); } /** Register built-in sibling rule for a link attribute. */ registerSiblings(ruleName, linkAttribute) { this.addRules(siblingRules(ruleName, linkAttribute)); } /** Get the underlying QueryEngine for direct query execution. */ getEngine() { return this.engine; } }; export { parseQuery, parseRule, parseSimple, transitiveClosureRules, reverseReachabilityRules, siblingRules, DatalogRuntime };