An embeddable graph database. Written in Rust.
zega provides a property graph with a Cypher-inspired query language in one engine, one binary, one dependency. It runs in-process in any Rust application, persists through a write-ahead log with snapshots, and compiles to WebAssembly for the browser.
No separate database server to install unless you want one. The server is a single binary that speaks HTTP/JSON. But you can absolutely run it in the browser, just like cqx does for running queries without a server:
-[*1..3]->).road *path -> Junction(name: "B") returns one route
with its nodes, edges and cost: fewest edges, by &km (Dijkstra), or
by &km toward at (A*, with km: Float<km>). See paths.MATCH, CREATE, MERGE,
SET, DELETE/DETACH DELETE, WHERE, WITH, UNWIND, FOREACH,
ORDER BY/SKIP/LIMIT, aggregates (count, sum, avg, min, max,
collect), CASE, and scalar functions.zega is a library first. Open a database with two
lines of Rust and run graph queries against it.zega-wasm exposes the same engine to JavaScript,
in-memory, in the browser.Build the native executable with cargo build --locked --release -p zega-cli.
The resulting binary is .target/release/zega when using the repository's
CARGO_TARGET_DIR=.target convention (.exe on Windows).
zega --help
zega --version
zega start --data ./data
# http://127.0.0.1:9342
zega explorer --data ./data
# http://127.0.0.1:9343
The CLI locks its data directory for the life of the process; starting another
CLI process against that directory fails instead of sharing the WAL. start defaults to port 9342 (ZEGA on a
phone keypad); explorer defaults to 9343. Both accept --port 0 to ask the
OS for an available port and print the actual URL. The default data directory
is ./zega-data. The explorer serves the embedded browser bundle and uses the
native database for every query, import and graph edit; it never opens a browser
automatically or reseeds an existing database. Its editor currently loads Monaco
from the same CDN used by the standalone explorer, so editor startup needs a
network connection even though the application assets and wasm are embedded.
zega start --host 0.0.0.0 --token-file ./token --data ./data enables an
explicit remote bind. The file must contain one nonempty bearer token (a final
newline is fine). With a token file, every database route requires
Authorization: Bearer <token>, including /health. Without one, only the exact
address 127.0.0.1 is accepted. Explorer always binds to 127.0.0.1.
Configuration is through flags; product behavior does not use environment
variables. The old server executable, environment-variable startup and /cql
route have been removed.
POST /zql accepts { "schema": "...", "query": "..." } and returns
{ "ok": true, "result": ... }. Errors return { "ok": false, "error": "..." }
with an error status. GET /health returns { "ok": true }.
curl -s http://127.0.0.1:9342/zql \
-H 'Content-Type: application/json' \
-d '{"schema":"type Player { name: String }","query":"mutation json [\"./players.json\"] { Player(name: $Name) { name } }"}'
curl -s http://127.0.0.1:9342/zql \
-H 'Content-Type: application/json' \
-d '{"schema":"type Player { name: String }","query":"{ Player { name } }"}'
Native load paths resolve against the process cwd. HTTP(S) loads use the library
loader. --allow-private-imports explicitly permits private/loopback URLs for
trusted callers; the default rejects them. Browser file-picker imports supply an
optional sources object mapping literal ZQL locations to raw text. The engine
parses and inserts that text. To execute a full ZQL file, set document: true
and put the document in query (no separate schema needed).
The explorer also uses authenticated /graph read/clear and graph edit routes.
Requests execute on the blocking pool under a shared database gate; slow native
loads do not block the HTTP health worker. See data loading
for format, limits and WAL semantics.
Release manifests contain zega-darwin-arm64, zega-darwin-x64, zega-linux-x64
and zega-windows-x64.exe, with SHA-256 hashes. These are the artifact keys
expected by the site's installer.
Add zega to your Cargo.toml (path or git dependency for now):
use zega::Zega;
// Disk databases replay their WAL on open; in_memory() has the same ZQL API.
let db = Zega::open("./data").build()?;
let schema = "type Person { name: String }";
db.run_lang(schema, r#"mutation { Person(name: "Ada") { name } }"#)?;
let result = db.run_lang(schema, "{ Person { name } }")?;
assert_eq!(result[0]["name"], "Ada");
Durability is configurable on the builder:
let zega = Zega::open("./data")
.wal_flush_every_write() // fsync every append
.wal_flush_interval(10) // or group-commit every 10 ms
.traversal_work_budget(1_000_000)
.build()?;
The zegadb package is prepared for browser bundlers and Node:
import { createDatabase } from 'zegadb';
const db = await createDatabase();
try {
console.log(JSON.parse(db.run(
'type Person { name: String }',
'mutation { Person(name: "Ada") { name } }',
)));
} finally {
db.free();
}
See the package API and bundler notes and
build, consumer checks, and disabled release setup.
Registry publication is pending; local consumers install artifacts/zegadb-0.1.0.tgz.
The repository's own explorer stays in browser/ and uses the WASM crate directly.
zega-wasm wraps an in-memory database for JavaScript via wasm-bindgen:
cd zega-wasm
wasm-pack build --target web
import init, { ZegaWasm } from "./pkg/zega_wasm.js";
await init();
const db = new ZegaWasm();
const schema = 'type Person { name: String }';
db.run(schema, 'mutation { Person(name: "Ada") { name } }');
console.log(JSON.parse(db.run(schema, '{ Person { name } }')));
ZQL has explicit schemas, mutations and graph-shaped queries:
schema {
type Team { name: String players -> Player[] }
type Player { name: String salary: Int }
}
mutation csv ["./players.csv"] {
Team(name: $Team) {
players -> Player(name: $Name && salary: $Salary) { name salary }
}
}
query {
Team {
name
players -> Player(salary > 10000000) { name salary }
}
}
Use run_lang(schema, statement) for one operation or apply_zql(document) for
an entire file. The data-loading guide covers raw sources,
JSON/CSV types and linking. The conformance corpus lives in
zegadb/testsuite.
Every write is appended to a CRC32-framed WAL (wal.bin) — with group
commit by default (5 ms / 64-entry batches) or fsync-per-write if you ask
for it. A statement is all-or-nothing: its writes go to the WAL as one entry,
and until the WAL accepts that entry no reader sees them; if the statement
fails or the WAL refuses it, nothing of it stays in memory or on disk.
Torn writes and bad checksums are detected and truncated on replay.
Zega::snapshot() writes a full snapshot.bin; the next open restores the
snapshot and replays only the WAL after it. Legacy WAL versions are migrated
automatically.
zega-bench runs the same workloads against zega (embedded) and
Neo4j (Bolt) side by side, and commerce-bench runs a commerce-shaped
graph (10k users, 5k products, 50k orders):
cargo run --release -p zega-bench -- zega
cargo run --release -p zega-bench -- neo4j # needs neo4j on localhost:7687
cargo run --release -p zega-bench --bin commerce-bench
zega is the only published crate — the whole engine (query execution,
planner, JWT, policies, WAL, graph storage, ZQL parser and language) lives
inside it as private modules. Everything else in this workspace is a
consumer that depends on zega by path and is never published:
| Crate | What it is |
|---|---|
zega |
the database: the only thing on crates.io |
zega-server |
reusable ZQL HTTP service |
zega-cli |
zega start and the embedded zega explorer |
zega-wasm |
wasm-bindgen wrapper for the browser (in-memory) |
zega-bench |
benchmarks against Neo4j |
zega is early (0.1.0). The engine, query language, WAL, server, and wasm wrapper are functional and tested; the wire protocol is HTTP/JSON only (no Bolt compatibility yet), and there is no REPL.
Apache-2.0. See LICENSE.
ZQL uses @ for language-owned names and & for user edge fields. See
Names in ZQL (APS 6) for the complete syntax inventory and migration rules.
An embeddable graph database. Written in Rust.
zega provides a property graph with a Cypher-inspired query language in one engine, one binary, one dependency. It runs in-process in any Rust application, persists through a write-ahead log with snapshots, and compiles to WebAssembly for the browser.
No separate database server to install unless you want one. The server is a single binary that speaks HTTP/JSON. But you can absolutely run it in the browser, just like cqx does for running queries without a server:
-[*1..3]->).road *path -> Junction(name: "B") returns one route
with its nodes, edges and cost: fewest edges, by &km (Dijkstra), or
by &km toward at (A*, with km: Float<km>). See paths.MATCH, CREATE, MERGE,
SET, DELETE/DETACH DELETE, WHERE, WITH, UNWIND, FOREACH,
ORDER BY/SKIP/LIMIT, aggregates (count, sum, avg, min, max,
collect), CASE, and scalar functions.zega is a library first. Open a database with two
lines of Rust and run graph queries against it.zega-wasm exposes the same engine to JavaScript,
in-memory, in the browser.Build the native executable with cargo build --locked --release -p zega-cli.
The resulting binary is .target/release/zega when using the repository's
CARGO_TARGET_DIR=.target convention (.exe on Windows).
zega --help
zega --version
zega start --data ./data
# http://127.0.0.1:9342
zega explorer --data ./data
# http://127.0.0.1:9343
The CLI locks its data directory for the life of the process; starting another
CLI process against that directory fails instead of sharing the WAL. start defaults to port 9342 (ZEGA on a
phone keypad); explorer defaults to 9343. Both accept --port 0 to ask the
OS for an available port and print the actual URL. The default data directory
is ./zega-data. The explorer serves the embedded browser bundle and uses the
native database for every query, import and graph edit; it never opens a browser
automatically or reseeds an existing database. Its editor currently loads Monaco
from the same CDN used by the standalone explorer, so editor startup needs a
network connection even though the application assets and wasm are embedded.
zega start --host 0.0.0.0 --token-file ./token --data ./data enables an
explicit remote bind. The file must contain one nonempty bearer token (a final
newline is fine). With a token file, every database route requires
Authorization: Bearer <token>, including /health. Without one, only the exact
address 127.0.0.1 is accepted. Explorer always binds to 127.0.0.1.
Configuration is through flags; product behavior does not use environment
variables. The old server executable, environment-variable startup and /cql
route have been removed.
POST /zql accepts { "schema": "...", "query": "..." } and returns
{ "ok": true, "result": ... }. Errors return { "ok": false, "error": "..." }
with an error status. GET /health returns { "ok": true }.
curl -s http://127.0.0.1:9342/zql \
-H 'Content-Type: application/json' \
-d '{"schema":"type Player { name: String }","query":"mutation json [\"./players.json\"] { Player(name: $Name) { name } }"}'
curl -s http://127.0.0.1:9342/zql \
-H 'Content-Type: application/json' \
-d '{"schema":"type Player { name: String }","query":"{ Player { name } }"}'
Native load paths resolve against the process cwd. HTTP(S) loads use the library
loader. --allow-private-imports explicitly permits private/loopback URLs for
trusted callers; the default rejects them. Browser file-picker imports supply an
optional sources object mapping literal ZQL locations to raw text. The engine
parses and inserts that text. To execute a full ZQL file, set document: true
and put the document in query (no separate schema needed).
The explorer also uses authenticated /graph read/clear and graph edit routes.
Requests execute on the blocking pool under a shared database gate; slow native
loads do not block the HTTP health worker. See data loading
for format, limits and WAL semantics.
Release manifests contain zega-darwin-arm64, zega-darwin-x64, zega-linux-x64
and zega-windows-x64.exe, with SHA-256 hashes. These are the artifact keys
expected by the site's installer.
Add zega to your Cargo.toml (path or git dependency for now):
use zega::Zega;
// Disk databases replay their WAL on open; in_memory() has the same ZQL API.
let db = Zega::open("./data").build()?;
let schema = "type Person { name: String }";
db.run_lang(schema, r#"mutation { Person(name: "Ada") { name } }"#)?;
let result = db.run_lang(schema, "{ Person { name } }")?;
assert_eq!(result[0]["name"], "Ada");
Durability is configurable on the builder:
let zega = Zega::open("./data")
.wal_flush_every_write() // fsync every append
.wal_flush_interval(10) // or group-commit every 10 ms
.traversal_work_budget(1_000_000)
.build()?;
The zegadb package is prepared for browser bundlers and Node:
import { createDatabase } from 'zegadb';
const db = await createDatabase();
try {
console.log(JSON.parse(db.run(
'type Person { name: String }',
'mutation { Person(name: "Ada") { name } }',
)));
} finally {
db.free();
}
See the package API and bundler notes and
build, consumer checks, and disabled release setup.
Registry publication is pending; local consumers install artifacts/zegadb-0.1.0.tgz.
The repository's own explorer stays in browser/ and uses the WASM crate directly.
zega-wasm wraps an in-memory database for JavaScript via wasm-bindgen:
cd zega-wasm
wasm-pack build --target web
import init, { ZegaWasm } from "./pkg/zega_wasm.js";
await init();
const db = new ZegaWasm();
const schema = 'type Person { name: String }';
db.run(schema, 'mutation { Person(name: "Ada") { name } }');
console.log(JSON.parse(db.run(schema, '{ Person { name } }')));
ZQL has explicit schemas, mutations and graph-shaped queries:
schema {
type Team { name: String players -> Player[] }
type Player { name: String salary: Int }
}
mutation csv ["./players.csv"] {
Team(name: $Team) {
players -> Player(name: $Name && salary: $Salary) { name salary }
}
}
query {
Team {
name
players -> Player(salary > 10000000) { name salary }
}
}
Use run_lang(schema, statement) for one operation or apply_zql(document) for
an entire file. The data-loading guide covers raw sources,
JSON/CSV types and linking. The conformance corpus lives in
zegadb/testsuite.
Every write is appended to a CRC32-framed WAL (wal.bin) — with group
commit by default (5 ms / 64-entry batches) or fsync-per-write if you ask
for it. A statement is all-or-nothing: its writes go to the WAL as one entry,
and until the WAL accepts that entry no reader sees them; if the statement
fails or the WAL refuses it, nothing of it stays in memory or on disk.
Torn writes and bad checksums are detected and truncated on replay.
Zega::snapshot() writes a full snapshot.bin; the next open restores the
snapshot and replays only the WAL after it. Legacy WAL versions are migrated
automatically.
zega-bench runs the same workloads against zega (embedded) and
Neo4j (Bolt) side by side, and commerce-bench runs a commerce-shaped
graph (10k users, 5k products, 50k orders):
cargo run --release -p zega-bench -- zega
cargo run --release -p zega-bench -- neo4j # needs neo4j on localhost:7687
cargo run --release -p zega-bench --bin commerce-bench
zega is the only published crate — the whole engine (query execution,
planner, JWT, policies, WAL, graph storage, ZQL parser and language) lives
inside it as private modules. Everything else in this workspace is a
consumer that depends on zega by path and is never published:
| Crate | What it is |
|---|---|
zega |
the database: the only thing on crates.io |
zega-server |
reusable ZQL HTTP service |
zega-cli |
zega start and the embedded zega explorer |
zega-wasm |
wasm-bindgen wrapper for the browser (in-memory) |
zega-bench |
benchmarks against Neo4j |
zega is early (0.1.0). The engine, query language, WAL, server, and wasm wrapper are functional and tested; the wire protocol is HTTP/JSON only (no Bolt compatibility yet), and there is no REPL.
Apache-2.0. See LICENSE.
ZQL uses @ for language-owned names and & for user edge fields. See
Names in ZQL (APS 6) for the complete syntax inventory and migration rules.