Relationships are first-class
Edges are typed, directed, and property-bearing. Traversal is O(degree), not a stack of self-joins.
In-memory · Cypher · Rust
A complete map of the engine — Cypher coverage, surfaces, architecture, and the lines we won’t pretend to cross. Pick what you came here to verify.
Design principles
Edges are typed, directed, and property-bearing. Traversal is O(degree), not a stack of self-joins.
A pragmatic subset of Cypher — MATCH, WITH, WHERE, CREATE, RETURN. Short queries, readable intent.
Add a label, an edge type, or a property by writing it. No ALTER, no migration, no restart.
A compiler-style pipeline of focused crates from parser to executor. If the database matters to your product, you should be able to read it.
Cypher coverage
Pattern matching, writes, aggregation pipelines, paths, temporal and spatial predicates — composed top-to-bottom with WITH.
MATCH (a:Person)-[:KNOWS]->(b:Person)
WHERE a.city = 'Berlin'
RETURN a.name, collect(b.name) AS friendsMERGE (u:User {email: $email})
ON CREATE SET u.created = temporal.now()
ON MATCH SET u.last_seen = temporal.now()MATCH p = shortestPath(
(a:Stop {code: $from})-[:CONNECTS*..6]->(b:Stop {code: $to})
)
RETURN path.length(p) AS hops, [n IN path.nodes(p) | n.code] AS viaMATCH (u:User)-[:PLACED]->(o:Order {status: 'paid'})
WITH u, count(o) AS orders, sum(o.total) AS spend
WHERE orders >= 3
RETURN u.email, orders, spend ORDER BY spend DESCMATCH (e:Event)
WHERE e.at >= temporal.now() - 'P7D'::DURATION
RETURN temporal.truncate('day', e.at) AS day, count(*) AS events
ORDER BY dayWITH {latitude: 52.52, longitude: 13.405}::POINT AS origin
MATCH (s:Store)
WHERE geo.distance(s.loc, origin) < 5000
RETURN s.name, geo.distance(s.loc, origin) AS metres
ORDER BY metresFunctions & types
String, math, list, aggregation, temporal, spatial, vector — shipped with the engine. No procedure plugins to install.
Every value — stored as a property, projected in a RETURN, or bound as a parameter — has one of these types.
Architecture
Every query walks a real pipeline — parser, analyzer, compiler, executor — written from scratch in Rust. Each stage is one crate; the names line up with the source tree.
lora-parserA PEG grammar lifts Cypher text into a typed AST with source spans.
lora-analyzerVariable scoping, label and type validation against live graph state, function resolution.
lora-compilerLower the resolved IR into a logical plan, optimize (filter push-down), then a physical plan.
lora-executorInterpret the physical plan against the in-memory store and project results in the requested shape.
Ways to use it
Pick the surface that fits the host process. Cypher, parameters, and result shapes are identical across all of them.
use lora_database::Database;
let db = Database::in_memory();
db.execute("CREATE (:User {name: 'Ada'})", None)?;
let result = db.execute(
"MATCH (u:User) RETURN u.name",
None,
)?;
Operations
Know the boundary
LoraDB is an in-memory engine, not a clustered production database. Below is what to expect — stated up front so you can decide whether the trade-offs match your workload.
The graph lives in process memory. Durability is optional via snapshots and WAL, not a separate persistent storage backend.
RANGE, TEXT, POINT, LOOKUP, VECTOR, and FULLTEXT indexes exist for declared hot paths. The planner is still intentionally small: no global join optimizer or ANN vector structure.
Read-only queries can overlap on snapshots. Write commits and explicit read-write transactions serialize, so this is not built for high-fan-out write concurrency.
lora-server is meant to live behind your own ingress. The crate has no auth surface — you control the host process.
FAQ
Short answers to what evaluators usually ask before installing. The full reference lives in the docs.
No. LoraDB ships a pragmatic subset of Cypher — read clauses (MATCH, OPTIONAL MATCH, WHERE, WITH, RETURN), write clauses (CREATE, MERGE, SET, DELETE, REMOVE, FOREACH), iteration (UNWIND), variable-length paths, aggregation, and most built-in functions. The Limitations page lists everything that is not supported, including general-purpose CALL, LOAD CSV, quantified path patterns, and BETWEEN.
Yes. lora-server is an Axum-based HTTP service that exposes POST /query, GET /health, and opt-in admin endpoints for snapshots and WAL operations. It is meant to live behind your own ingress — the crate has no auth surface, so you control the host process.
Two layers. Snapshots write the full graph to a single file with an atomic rename and are available on every binding (Rust, Node.js, Python, WASM, Go, Ruby, HTTP). WAL-backed durability is available on every filesystem-backed surface — commits append to a write-ahead log and replay on recover above the snapshot fence. WASM stays snapshot-only because the runtime has no filesystem.
LoraDB is bounded by the RAM of one host process. There is no sharding, no replication, no distributed storage. If the working set fits in memory and you want graph queries close to the code that uses them, LoraDB is the right tool; if the dataset is too large to fit in RAM on one machine, LoraDB is the wrong tool.
Yes, via the WebAssembly binding. The WASM build runs in any browser or modern WASM-capable runtime (Cloudflare Workers, Deno Deploy, browser tabs). The Playground itself is a static site running LoraDB entirely in the browser via WASM. The WASM surface is snapshot-only — there is no filesystem to write a WAL to.
LoraDB is pre-1.0. The release journey is public — see the v0.1 through v0.14 blog posts for what was shipped and what was deferred. Security fixes land on main and ship in the next tagged release; older tags are not patched. Use it where you can roll forward; do not depend on storage format stability across minor versions until 1.0.
Three lines of Rust, a curl call, or a single import. The fastest way to feel LoraDB is to write a query against it.