helix-db
There is good documentation in the crate doc comments, especially in
src/lib.rs. AI agents should read the source code and doc comments to get a feel for the query-building patterns and the full API surface.
The helix-db crate (imported as helix_db) is the Rust SDK for HelixDB. It pairs a query-builder DSL with a small async HTTP client (helix_db::Client) for running those queries against a Helix instance.
The DSL is centered on two entry points:
read_batch()for read-only transactionswrite_batch()for write-capable transactions
Everything in the DSL is designed to be composed inside those batch chains. You write one or more named traversals with .var_as(...) / .var_as_if(...), then choose the final payload with .returning(...).
Install
Add the crate under [dependencies]:
= "2.0.0"
The crate is published under the name helix-db and its library is imported as helix_db. For shorter query code, bring the curated builder API into scope:
use *;
The examples below assume that prelude is in scope.
Core Shape
Read chain:
read_batch() -> var_as / var_as_if -> returning
Write chain:
write_batch() -> var_as / var_as_if -> returning
Each var_as call accepts a traversal expression, usually starting with g(). Traversals can read, traverse, filter, aggregate, or mutate depending on whether they are used in a read or write batch.
Read Batches
read_batch
.var_as
.var_as
.returning;
read_batch
.var_as
.returning;
let statuses = param;
read_batch
.var_as
.returning;
Conditional Queries
Use BatchCondition with var_as_if to run later queries only when earlier variables satisfy runtime conditions.
read_batch
.var_as
.var_as_if
.returning;
Write Batches
write_batch
.var_as
.var_as
.var_as
.returning;
write_batch
.var_as
.var_as_if
.returning;
Executing Queries with helix_db::Client
helix_db::Client is a thin async wrapper over reqwest for running queries against a Helix
instance. Construct it with an optional base URL, then optionally attach a bearer API key:
use Client;
// Defaults to http://localhost:6969 when `url` is None.
let client = new?;
// Or point at a remote cluster and attach an API key:
let client = new?
.with_api_key;
Requests are built with a small fluent builder. Start with client.query::<R>() (where R is
the type you want the response deserialized into), optionally toggle request headers, then choose
a query kind and .send().await:
// Inline / dynamic query: POSTs a `DynamicQueryRequest` (DSL query + parameters) to `/v1/query`.
let response: MyResponse = client
.query
.dynamic_query // `request` is a DynamicQueryRequest (see below)
.send
.await?;
// Stored query: POSTs a serializable payload to a deployed query's route
// (`/v1/query/<name>`, e.g. `/v1/query/add_user`).
let response: MyResponse = client
.query
.body? // optional request body for the route
.stored_query
.send
.await?;
Optional header toggles can be chained before choosing the query kind:
.writer_only()— require the request to be served by a writer node (x-helix-require-writer)..warm_only()— only execute if the query is already warm (x-helix-warm); reads only..should_await_durability(true)— block until the write is durable (x-helix-await-durable).
send() is generic over the deserialized response type R and returns Result<R, HelixError>.
HelixError distinguishes transport errors, non-200 responses from the server (RemoteError),
serialization failures, and invalid URLs.
Registered queries + dynamic_query
Annotate a query builder with #[register] to get a callable helper that builds a
DynamicQueryRequest directly from typed arguments. The generated function returns the request
value itself (not a Result) — parameter coercion that can fail (e.g. DateTime, bytes) panics
with a descriptive message rather than returning an error.
use *;
use Client;
use Deserialize;
async
Notes:
- A
#[register]builder generates a public callable helper only when the function ispub. - The serialized payload includes
request_type,query, and optionalparameters/parameter_types. - Private
#[register]functions are still registered for bundle generation (helix_db::query_generator::generate()), but they do not generate the public callable helper.
Vector Search Operations (End-to-End)
The current Helix interpreter executes vector search as top-k nearest-neighbor lookup with these runtime semantics:
- returns up to
khits (top-k behavior) - hit order is ascending by
$distance(smaller is closer) - hit metadata can be read through virtual fields in projections:
- node hits:
$id,$distance - edge hits:
$id,$from,$to,$distance
- node hits:
Result field contract
| Field | Type | Node hits | Edge hits | Meaning |
|---|---|---|---|---|
$id |
integer | yes | yes* | Node ID (for node hits) or edge ID (for edge hits) |
$distance |
floating-point | yes | yes | Vector distance from query (lower = closer) |
$from |
integer | no | yes | Edge source node ID |
$to |
integer | no | yes | Edge target node ID |
* For edge hits, $id is present when an edge ID is available in storage.
Contract scope in the current Helix interpreter:
- available on direct vector-hit streams and projection terminals
- available in
value_map,values,project, and (for edges)edge_properties - once a traversal step leaves the hit stream (
out,in_,both, etc.), downstream traversers no longer carry distance metadata
1) Create indexes and insert vectors
write_batch
.var_as
.var_as
.var_as
.var_as
.returning;
2) Node vector search: get ranked hits and fetch node properties
read_batch
.var_as
.returning;
doc_hits rows (example shape):
[
{ "$id": 42, "$distance": 0.0031, "title": "A" },
{ "$id": 77, "$distance": 0.0198, "title": "B" }
]
3) Use project(...) on vector hits (including distance)
read_batch
.var_as
.returning;
4) Traverse from hit IDs to related entities
Store hit rows (with $id + $distance) and then use NodeRef::var(...) to continue graph traversal from those hit IDs.
read_batch
.var_as
.var_as
.returning;
5) Edge vector search and endpoint/property extraction
read_batch
.var_as
.var_as
.returning;
edge_hits rows include $from, $to, and $distance (and $id when available), so you can inspect ranking metadata and still traverse from those edges.
6) Optional multitenancy
write_batch
.var_as
.var_as
.returning;
read_batch
.var_as
.returning;
Multitenant behavior in the current Helix interpreter:
- multitenant index + missing
tenant_valueon search => query error - multitenant index + unknown tenant => empty result set
- write with vector present but missing tenant property => write error
Edge-First Reads
read_batch
.var_as
.var_as
.returning;
Branching and Repetition
read_batch
.var_as
.returning;
Traversal Building Inside var_as(...)
Common source steps:
n(...),n_where(...),n_with_label(...)e(...),e_where(...),e_with_label(...)vector_search_nodes(...),vector_search_edges(...)- current Helix runtime exposes vector hit metadata via virtual fields (
$id,$distance,$from,$to) in terminal projections
- current Helix runtime exposes vector hit metadata via virtual fields (
Common navigation and filtering:
out/in_/both,out_e/in_e/both_e,out_n/in_n/other_nhas,has_label,has_key,where_,within,without,deduplimit,skip,range,order_by,order_by_multiple
Common terminal projections:
count,exists,id,labelvalues,value_map,project,edge_properties
Write-only operations (usable in write_batch() traversals):
add_n,add_e,set_property,remove_property,drop,drop_edge,drop_edge_by_idcreate_vector_index_nodes,create_vector_index_edges
For exhaustive catalog-style coverage of every public query-builder function, read the crate docs in src/lib.rs and browse the source directly.
License
Licensed under Apache-2.0.