Anvil combines the performance of Rust with a complete feature set — from a full Cypher query engine and transactions to built-in graph algorithms and multi-language drivers.
Anvil ships with a complete Cypher implementation built from scratch in Rust. The pipeline includes a hand-written lexer, recursive-descent parser, semantic analyzer, and a full type checker that catches errors before execution.
The executor supports multi-hop BFS pattern matching, quantified paths (*1..5, --+, --{3}), ALL SHORTEST path finding, comma-separated MATCH patterns, negated relationship types, MERGE with ON CREATE SET, ORDER BY/LIMIT/SKIP/DISTINCT, and full aggregation with GROUP BY semantics.
// Find mutual friends with activity scores
MATCH (a:Person {name: "Alice"})
-[:FRIEND]->(mutual)<-[:FRIEND]-(b:Person)
WHERE a <> b
AND mutual.active = true
WITH b, collect(mutual.name) AS shared,
count(mutual) AS strength
ORDER BY strength DESC
RETURN b.name, shared, strength
LIMIT 10// Transfer funds with full ACID guarantees
BEGIN TRANSACTION ISOLATION SERIALIZABLE;
MATCH (src:Account {id: "A-1001"})
MATCH (dst:Account {id: "A-2047"})
WHERE src.balance >= 500.00
SET src.balance = src.balance - 500.00,
dst.balance = dst.balance + 500.00
CREATE (src)-[:TRANSFERRED {
amount: 500.00,
ts: datetime()
}]->(dst)
COMMIT;Every mutation in Anvil runs inside a fully transaction. The engine maintains version chains on each record, enabling readers to proceed without blocking writers across three configurable snapshot isolation levels.
Record-level locking keeps contention narrow, while a wait-for graph detects deadlocks in real time and aborts the youngest transaction. write-ahead logging guarantees crash recovery with both redo and undo phases, so committed data is never lost.
Anvil stores data in fixed-size pages managed by a . The protects every page write with , enabling byte-level corruption detection on recovery.
indexes support unique, composite, full-text, and spatial key types. Pages are transparently compressed with LZ4 for hot data and Zstd for cold tiers, while tiered storage policies automatically migrate infrequently accessed subgraphs to cheaper volumes.
Unique
Single-key B+ tree
Composite
Multi-key compound
Full-Text
Inverted index + BM25
Spatial
R-tree for geo queries
LZ4
Fast hot-tier compression
Zstd
High-ratio cold tier
// Run PageRank on the citation network
CALL algo.pageRank(
"Paper",
"CITES",
{
iterations: 20,
dampingFactor: 0.85,
tolerance: 1e-6,
writeProperty: "rank"
}
)
YIELD nodeId, score
RETURN algo.asNode(nodeId).title, score
ORDER BY score DESC
LIMIT 5Anvil includes a library of production-ready graph algorithms that run in-process against the live graph. Centrality measures like PageRank, betweenness, closeness, and degree centrality help you identify key nodes without any step.
Pathfinding with handles weighted and unweighted traversals, while community detection (, label propagation) and structural algorithms (connected components, triangle counting, minimum spanning tree) round out the toolkit.
Extend Anvil with custom procedures and functions using a hybrid plugin architecture. Native Rust plugins compile to shared libraries for zero-overhead performance, while scripting runtimes for Lua, Python, WASM, JavaScript/TypeScript, and Starlark enable rapid iteration.
Every plugin runs inside a sandbox with configurable memory and CPU limits. Hot-reload lets you deploy new versions without restarting the server, and namespace isolation ensures plugins cannot interfere with one another or the core engine.
-- Custom recommendation plugin
local plugin = require("anvil.plugin")
plugin.register_procedure({
name = "recommend.similar",
mode = "READ",
run = function(ctx, node_id, k)
local emb = ctx:get_property(
node_id, "embedding"
)
return ctx:vector_search(
"embedding_idx", emb, k
)
end
})# Anvil cluster configuration
[cluster]
mode = "leader-follower"
consensus = "raft"
node_id = 1
[cluster.replication]
method = "wal-streaming"
sync_mode = "async"
replicas = 2
[cluster.sharding]
strategy = "hash" # hash | label
partitions = 16
[cluster.upgrades]
rolling = true
drain_timeout = "30s"Scale Anvil horizontally with leader-follower replication powered by streaming. consensus ensures automatic leader election and safe failover, while read replicas provide causal consistency for read-heavy workloads.
Sharding distributes data across nodes using either hash-based or label-based partitioning strategies. Rolling upgrades let you update the cluster node by node with zero downtime, draining connections gracefully before each node restarts.
Connect from Rust, TypeScript, Python, or Go using official client drivers that speak the native anvil:// protocol. Each driver supports connection pooling, automatic retries, and transaction helpers out of the box.
The wire protocol is designed for minimal overhead — binary-encoded results stream directly into language-native types. All drivers share the same connection URI scheme, making it easy to switch languages without changing your infrastructure.
use anvil_client::{Client, Config};
let client = Client::connect(
"anvil://localhost:7687",
Config::default()
).await?;
let rows = client
.query("MATCH (n:User) RETURN n LIMIT 10")
.run()
.await?;Anvil includes a built-in NoSQL document store that eliminates the need to run a separate database like DynamoDB or MongoDB alongside your graph. Collections are organized into schemas (public for app data, auth for system collections) and support schemaless JSON documents with composite keys (partition key + sort key), -based expiry, and both and .
The real power is graph-document sync — define mapping rules and Anvil automatically keeps your graph nodes and documents in lockstep. Changes from either side propagate within the same transaction, with configurable conflict resolution (last-write-wins, graph-wins, or document-wins).
# Upsert a document
PUT /docs/users/alice-123
{
"name": "Alice",
"email": "alice@example.com",
"prefs": { "theme": "dark" }
}-- Sync rule: Person nodes ↔ users collection
SYNC LABEL Person
TO COLLECTION users
KEY user_id
INCLUDE name, email, age-- Enable RLS on the Person label
ENABLE ROW LEVEL SECURITY ON :Person
-- Users can only see their own data
CREATE POLICY own_data ON :Person
FOR SELECT TO reader
USING (n.owner = current_user())
-- Multi-tenant isolation via session vars
CREATE POLICY tenant_iso ON :Person
FOR ALL TO authenticated
USING (n.tenant_id = session('tenant_id'))
-- Set session context on connect
SET SESSION tenant_id = 'acme'Inspired by PostgreSQL's , Anvil lets you define fine-grained access policies on nodes, relationships, and document collections. Policies are predicate-based rules evaluated per row at query time — completely transparent to application code.
Session variables like tenant_id enable multi-tenancy without application-level filtering. Policies support both permissive (OR) and restrictive (AND) modes, graph-aware traversal filtering, sync pairs (a single policy on a label automatically protects its paired document collection), and an admin policy simulator for testing access rules before deployment.
Schema protection reserves labels to schemas via sync rules — :User and :Role are reserved by the auth schema. CREATE, MERGE, SET, and DELETE operations on reserved labels from the wrong schema are rejected with a clear error. Bulk deletes silently skip protected nodes. Non-admin users cannot access auth schema data in the UI or via the API.
Anvil stores users and roles in schema-namespaced auth.* collections (auth.users, auth.roles, auth.user_roles) and syncs them to :User and :Role graph nodes with [:HAS_ROLE] relationships. Authentication uses tokens signed with RS256 (asymmetric keys) with a persistent key pair, and publishes a standard endpoint for external verification.
Passwords are hashed with (the current best-practice algorithm), and short-lived access tokens are paired with refresh tokens for seamless session management. Per-request middleware validates tokens and injects session context for policies — tying auth and authorization together in a single stack. The default admin user is prompted to change their password on first login.
# Login and get tokens
POST /auth/login
{ "username": "alice", "password": "..." }
# Response
{
"idToken": "eyJhbG...",
"accessToken": "eyJhbG...",
"refreshToken": "dGhpcy..."
}[auth]
enabled = true
default_password = "anvil"
access_token_ttl = 3600 # 1 hour
refresh_token_ttl = 604800 # 7 days
# RSA key pair auto-generated → data/jwt_key.pemDefine reusable logic with stored Cypher functions — typed parameters, default values, schema namespacing, and CALL...YIELD invocation. Functions can query the graph and document store, with parsed body caching and cycle detection (max 16 levels).
Triggers fire automatically on data changes — BEFORE or AFTER INSERT, UPDATE, or DELETE on graph labels and document collections. Access OLD/NEW pseudo-variables, abort with RAISE, modify NEW with SET, and cascade across sync rules. Priority ordering and a depth limit of 16 prevent runaway loops.
-- Stored function with typed parameters
CREATE FUNCTION greet(name: STRING)
RETURNS STRING AS {
'Hello, ' + name + '!'
}
-- Call inline or with CALL...YIELD
MATCH (n:Person)
RETURN greet(n.name)-- Auto-create profile on user signup
CREATE TRIGGER create_profile
AFTER INSERT ON COLLECTION auth.users
FOR EACH ROW AS {
CREATE DOCUMENT IN profiles
NEW.username { email: NEW.email }
}-- Query analytics with percentile latency
SHOW QUERY STATS LIMIT 10
-- Dependency analysis for triggers
SHOW DEPENDENCIES FOR TRIGGER create_profile
-- Configurable alert rules
CREATE ALERT slow_spike
WHEN QuerySlow > 5 IN 5m
THEN LOG
-- Browse the event log
SHOW EVENTS WHERE type =
'TriggerError'Every trigger firing, function call, and query execution is logged to an in-memory event log with configurable retention. Slow queries are automatically detected and surfaced with p95/p99 latency analytics.
Dependency analysis maps trigger cascades, function calls, and sync rule interactions — detecting potential infinite loops and sync-trigger overlaps. Configurable alert rules fire when error rates spike, with built-in defaults for trigger errors, slow queries, and auth failures.
Hammer is an open-source (MIT) web UI for managing Anvil DB. Query with Cypher or GraphQL, visualize your graph on an interactive canvas, manage documents and collections, configure row-level security, import data, and administer users and roles.
The graph canvas features force-directed layout with focus mode — click a node to orbit its neighbors in a circle with highlighted edges. Shift+drag for lasso selection, double-click to expand, right-click for context menus. Export as PNG or SVG.
Role-gated access ensures non-admin users only see what they should — Admin, Policies, Functions, Triggers pages are hidden, the auth schema is locked down, and all restrictions are enforced server-side.
Cypher Editor
Table, JSON, graph, plan views
GraphQL Playground
Introspection + data views
Graph Canvas
Focus mode, lasso, minimap, export
Schema Browser
Labels, types, keys, indexes
Documents
CRUD, sync rules, schema-filtered
Data Import
Cypher scripts with counts
Admin Panel
Users, roles, events, alerts
Monitoring
Stats, slow queries, event log
# Import via CLI
anvil import cypher ./movies.cypher
# Import via REST API
curl -X POST http://localhost:7474/db/import/cypher \
-H "Content-Type: text/plain" \
-H "Authorization: Bearer $TOKEN" \
--data-binary @movies.cypher
# Response
# {"total":2, "success":2,
# "nodesCreated":171,
# "relationshipsCreated":253}Import Neo4j-compatible .cypher files via CLI, REST API, or the Hammer UI. Supports semicolon-separated statements, multi-CREATE without semicolons, MERGE with ON CREATE SET, and variable binding across CREATE clauses.
The import engine automatically skips unsupported DDL (CREATE CONSTRAINT, CREATE INDEX) and reports detailed counts of nodes and relationships created. The Hammer import page provides a drag-and-drop interface with live progress.
Run JavaScript or TypeScript functions on the server, triggered via HTTP endpoints. Functions execute in a Deno or Node.js subprocess with on-demand startup and automatic cleanup.
Register functions with Cypher DDL, invoke them at /edge/{name}, and manage them through the Hammer UI or REST API. Ideal for webhooks, data transformations, and custom API endpoints backed by graph queries.
// Register: CREATE EDGE FUNCTION hello
// RUNTIME 'deno'
// ENTRYPOINT 'functions/hello.ts'
Deno.serve((req: Request) => {
const url = new URL(req.url);
const name = url.searchParams.get("name")
?? "World";
return new Response(
JSON.stringify({
message: `Hello, ${name}!`
}),
{ headers: { "Content-Type":
"application/json" } }
);
});Get started in minutes with our quick-start guide and client drivers.