Static analysis for SurrealQL

Catch SurrealQL bugs
before they run.

SurrealGuard infers the type of every SurrealQL statement and reports contract violations — unknown fields, kind mismatches, bad graph traversals — with fully typed results in Rust and TypeScript.

$ npx surrealguard check

SurrealDB says yes to almost anything.

Cross-kind comparisons order by kind instead of failing. Coercions succeed silently. A misspelled field just returns NONE. Those are exactly the places bugs hide — so SurrealGuard checks each construct's contract, statically.

// try it

See the analyzer think

Real diagnostics from the actual engine. Pick a query — SurrealGuard checks it against the schema and sets out each statement's inferred response type in full, below the query.

presets loading engine…
Diagnostics · live

The real SurrealGuard engine, compiled to WebAssembly, running in your browser — edit anything.

Rust · surrealguard-rs

The compiler is the checker

query! runs the analyzer at compile time and generates the result type. A wrong table or field is a cargo check error — no codegen step, no language server, no runtime schema fetch.

use surrealguard_rs::query; let users = query!("SELECT name, age FROM user"); // users: Query<Vec<{ name: String, age: i64 }>> ← inferred let bad = query!("SELECT name, ssn FROM user"); // error: SurrealGuard rejected this query: // [E1002] unknown field `ssn` on table `user`
TypeScript · @surrealguard/{client, next, svelte}

Typed queries in every framework

One typed db.query that extends the SurrealDB SDK, plus reactive live-query adapters for Next.js and Svelte 5. The row type is always inferred from the query text — dynamic strings degrade to unknown[] and still run.

const db = new SurrealGuardClient(); // extends the SurrealDB SDK const [rows] = await db.query(`SELECT name FROM user`); // ^ Array<{ name: string }> — inferred from the query text const [x] = await db.query(`SELECT name, ssn FROM user`); // ✗ unknown field `ssn` on table `user`
// for AI agents

A compile check for SurrealQL your agent can run

Agents like Claude Code already lean on tsc and cargo check to know their edits compile. SurrealGuard gives them the same signal for SurrealQL — run surrealguard check in the loop and every unknown table, missing field, or type mismatch comes back as a structured, byte-precise finding to fix.

The check-and-fix loop

Point the agent at your project once. From then on it runs the CLI on every change and reads the results — no editor integration required, works in any harness.

  • surrealguard check --json → code, severity, message, help & byte range per finding
  • surrealguard generate → typed query results, so downstream field access is checked too
  • Deterministic non-zero exit on error-severity findings — the loop knows when it's done
  • Context files: llms.txt, llms-full.txt & AGENTS.md
  • Secondary: the editor LSP for humans, and any MCP bridge that surfaces the same diagnostics
Set up SurrealGuard in this project. Run the CLI with `npx surrealguard init`, point surrealguard.toml at my .surql schema and query directories, then run `npx surrealguard check --json` and fix any reported findings. Where I write queries, use the Rust `query!` macro or the typed `@surrealguard/client` db.query so they're checked against my schema.
// what it does

One engine, every surface

The same analyzer powers a CLI, a language server, a Rust proc-macro, and a set of TypeScript packages.

Full statement coverage

SELECT, the six mutations, RELATE, graph traversals, transactions, DEFINE/REMOVE/ALTER, LIVE/KILL — every statement lowers to a typed AST and is analyzed.

Real type inference

Response kinds as upstream surrealdb_types::Kind: object literals, IF/ELSE unions, record links, graph edges, function returns, closures, subqueries.

90 contract diagnostics

One code per contract across eight families — schema refs, types, graph, statement misuse, functions, parameters, lints, version compat. Rust-analyzer-style messages with help: fixes and note: spans.

Parameter constraints

Every $param is exported with the kind and value domain its uses imply — UPDATE user SET age = $ageage: int.

Byte-precise spans

Every finding carries an exact byte range — squiggles land on the right token, even inside a query embedded in a host file.

Editor LSP

Diagnostics for .surql and SurrealQL embedded in TypeScript, Svelte, Vue, and Astro — plus inlay type hints, hover on context params and table fields, greyed-out dead code, and live [lints] levels. Runs in Zed.

Rust query! macro

Compile-time checked, typed SurrealQL. The compiler is the checker — no external step; results as nameless typed structs.

TypeScript packages

A typed db.query, a reactive query core, and Next.js + SvelteKit adapters with live queries and SSR handled.

Agent-friendly

JSON diagnostics, llms.txt, AGENTS.md, and compile-time errors make SurrealGuard a first-class tool for AI agents and CI.

// install

Get it

Run the CLI with npx surrealguard — zero install, no toolchain. The engine, Rust SDK, macros, and language server are published on crates.io; the TypeScript packages on npm; a Zed extension brings live diagnostics to your editor.

CLI npx · zero-install
surrealguard — cli
# zero-install via npm — no toolchain needed
$npx surrealguard init
$npx surrealguard check
$npx surrealguard generate
# or install the binary from crates.io:
$cargo install surrealguard
Rust SDK crates.io
cargo
# query! / surql! macros + typed SDK
$cargo add surrealguard-rs
TypeScript npm
npm
$npm i @surrealguard/client
# + @surrealguard/{query,next,svelte}
Editor zed
zed extension
# syntax highlighting + live diagnostics + hover / inlay
# install as a Zed dev extension from the repo —
# it auto-downloads the language-server binary for you
DrewRidley/zed-surrealZed extension repo
// faq

Questions

Doesn't SurrealDB already validate my queries?

At runtime, and permissively — cross-kind comparisons order by kind, coercions succeed, missing fields return NONE. SurrealGuard checks each construct's contract statically, so those silent behaviors become errors you see before shipping.

Do I have to change how I write SurrealQL?

No. You write normal SurrealQL. In Rust you wrap it in query!("…"); in TypeScript you pass a literal to db.query("…") (or run surrealguard generate for standalone types). Dynamic strings still work — they just aren't typed.

Does it need a running database?

No. It analyzes your .surql schema and queries statically. The Rust macro reads your schema/ or migrations/ directory at compile time and checks against it.

How does it parse SurrealQL if the database's parser can't?

SurrealDB discards spans before producing its AST, so it can't power tooling. SurrealGuard parses with tree-sitter into a typed, span-carrying AST and runs all analysis on that.

Is it production-ready?

The engine, CLI, LSP, Rust macros, and TypeScript packages are built and tested. The first public release is in preparation — follow along on GitHub.