// documentation

Introduction

SurrealGuard is static analysis and type inference for SurrealQL. It parses your .surql schema and queries into a typed, span-carrying AST, infers the response type of every statement, and reports violations of each construct's contract — before a query ever reaches SurrealDB.

The same engine powers four surfaces: a CLI for CI and agents, a language server, a Rust proc-macro, and a set of TypeScript packages. You write ordinary SurrealQL; SurrealGuard checks it and hands back a fully typed result.

Why contract-first

SurrealDB is permissive at runtime. Cross-kind comparisons order by kind instead of failing, coercions succeed silently, and a misspelled field just returns NONE. That is exactly where bugs hide — the database accepts them.

SurrealGuard is contract-first: every construct has a contract (what the author must mean for the statement to make sense), and the analyzer reports violations of that contract even when the engine would happily execute the query. Severity is intrinsic to each finding; whether SurrealDB throws, tolerates, or coerces is irrelevant to whether the contract was broken.

Why tree-sitter, not SurrealDB's parser? SurrealDB's own parser discards spans before producing its AST (true in 2.x and 3.0), so it can't power an analyzer or editor tooling. SurrealGuard parses with tree-sitter into a typed, span-carrying AST and runs all analysis on that.

What it analyzes

The compiler is the checker (Rust)

In Rust, the query! macro runs the real analyzer during compilation and generates the result type. A wrong table, unknown field, bad arity, or kind mismatch 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 }>>  ← nameless, inferred

let bad = query!("SELECT name, ssn FROM user");
// error: SurrealGuard rejected this query:
//   [E1002] unknown field `ssn` on table `user`

Typed queries, no wrapper (TypeScript)

SurrealGuardClient extends the SurrealDB SDK, so new SurrealGuardClient() has every SDK method plus typed queries. Pass a string literal to db.query and its result and parameters are inferred from the query text. Missing or wrong params are compile errors; dynamic strings degrade to unknown[] and still run.

// One import: the generated file re-exports a ready client and loads the registry.
import { SurrealGuardClient } from "./surrealguard.generated";

const db = new SurrealGuardClient();
await db.connect("ws://localhost:8000/rpc");

// SurrealDB returns one result per statement, so destructure the first result.
const [users] = await db.query("SELECT name FROM user WHERE team = $team", { team: "red" });
//     ^ Array<{ name: string }> — params required + typed from the text

Install

# CLI — zero install via npm
npx surrealguard check --json

# or install from source (Rust)
cargo install --git https://github.com/DrewRidley/surrealguard surrealguard

# Rust library — git dependency in Cargo.toml (until the crates.io release)
# surrealguard-rs = { git = "https://github.com/DrewRidley/surrealguard" }

# TypeScript packages — published on npm
npm i @surrealguard/client

See Getting started for a full walkthrough — init, surrealguard.toml, and your first check.