Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
helixdb avatar

Helix Query Rust

  • 125 installs
  • Updated August 3, 2026
  • helixdb/skills

Writes and reviews HelixDB queries in the Rust DSL with traversals, projections, indexes, and vector or BM25 search.

About

Writes and revises HelixDB Rust DSL queries from scratch with traversal builders, projections, indexes, and BM25/vector search. A developer uses it to author or review Helix queries in a Rust codebase.

  • Uses read_batch/write_batch, #[register], and queries.json bundles
  • Preferred way to author Helix queries in a Rust codebase

Helix Query Rust by the numbers

  • 125 all-time installs (skills.sh)
  • +12 installs in the week ending Aug 2, 2026 (Skillselion tracking)
  • Ranked #61 of 121 Rust skills by installs in the Skillselion catalog
  • Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/helixdb/skills --skill helix-query-rust

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs125
Last updatedAugust 3, 2026
Repositoryhelixdb/skills

What it does

Writes and reviews HelixDB queries in the Rust DSL with traversals, projections, indexes, and vector or BM25 search.

Files

SKILL.mdMarkdownGitHub ↗

Helix Query Authoring — Rust

Write Helix Rust DSL queries in a way that is schema-aware, explicit, and easy for agents to reason about. The Rust builder is the helix-db crate (sdks/rust); the TypeScript DSL (helix-query-typescript) emits the same JSON AST.

This is the preferred way to author Helix queries in a Rust codebase. Drop to raw dynamic JSON (helix-query-json-dynamic) only for debugging or dynamically-shaped requests.

When To Use

Use this skill when the task is to:

  • write a new Helix query in Rust
  • revise an existing Helix Rust DSL route
  • bundle queries into a queries.json
  • choose between read_batch() and write_batch()
  • add traversal, projection, pagination, BM25 search, or vector search to an existing query

Do not use this skill as the main guide for inline POST /v1/query payloads — use helix-query-json-dynamic. For the TypeScript DSL, use helix-query-typescript.

First Steps

Before writing any query code:

1. Inspect the local repo for existing labels, edge labels, properties, and route patterns. 2. Find the closest existing query and reuse its naming, projection, and scoping style. 3. Decide whether the route is a read or a write. 4. Identify the narrowest indexed anchor before planning the traversal.

If the local repo is thin on Helix examples, use the companion files in this skill:

1. EXAMPLES.md — working end-to-end Rust queries (reads, writes, search, repeat, branching, upsert, for_each_param). 2. REFERENCE.md — full builder catalog organized by category, with typestate notes.

Open REFERENCE.md whenever you need a builder beyond the common surface (add_e, drop_edge_by_id, create_vector_index_nodes, repeat, choose, coalesce, optional, aggregate_by, group_count, inject, order_by_multiple, expression case, etc.) — do not invent method names from memory.

Core Authoring Rules

1. Start With The Right Batch Type

Use:

  • read_batch() for read-only routes
  • write_batch() for any mutation

If the query adds nodes, adds edges, updates properties, or deletes graph data, it is a write route.

2. Anchor Narrow, Then Traverse

Prefer this anchor order:

1. node ID or edge ID 2. unique property lookup 3. equality-indexed property lookup 4. scoped label scan 5. broad label scan as a last resort

Do not start from a broad label scan when the application already has an indexed identifier like entityId, externalId, userId, tenantId, or a similar key.

3. Reuse Existing Property And Label Casing

Do not normalize names to your own preferred style.

If the application uses entityId, updatedAt, FOLLOWS, or RelatesTo, reuse those exact names.

4. Filter Early

Apply scope and status filters before broad traversal whenever possible.

Common examples:

  • tenant filters like tenantId or userId
  • soft-delete or archived filters such as empty or null deletedAt
  • specific ID filters before both, out, or in_

5. Keep Output Shape Intentional

Use:

  • project(...) for stable service-facing response shapes
  • value_map(...) when returning all or many properties is acceptable
  • edge_properties() for edge streams
  • For edge endpoint properties, prefer edge-stream project(...) with

Projection::from_endpoint(prop, alias) / Projection::to_endpoint(prop, alias) instead of traversing to every endpoint first.

Do not return oversized properties like embeddings unless the caller explicitly needs them.

6. Preserve Search Scope

For BM25 and vector search:

  • keep the chosen text or vector property explicit
  • preserve tenant scope when the index is scoped
  • post-filter only when the search API cannot express the scope directly

7. Use Traversal Controls Deliberately

Apply dedup, limit, range, skip, count, and first because the route needs them, not by habit.

repeat(...) is often used with a deliberate bounded depth. Do not assume arbitrary runtime repeat depth unless the local code already supports it.

8. Prefer Explicit Write Branching Over Invented MERGE Semantics

When you need create-or-update behavior, follow this pattern:

1. load existing nodes 2. branch with var_as_if 3. update when found 4. create when missing

9. Know The Full Builder Surface

The DSL is larger than the canonical examples below suggest. Before reaching for a workaround, check REFERENCE.md — there is likely a direct builder.

CategoryPrimary buildersNotes
Sourcesg().n(...), n_where, n_with_label, n_with_label_where, e, e_where, e_with_label, e_with_label_where, vector_search_nodes_with, text_search_nodes_with, vector_search_edges_with, text_search_edges_withAnchor narrowly — indexed ID first, then label scope.
Traversalout, in_, both, out_e, in_e, both_e, out_n, in_n, other_nEdge-valued forms (*_e) switch the stream type.
Filtershas, has_label, has_key, where_, dedup, within, without, edge_has, edge_has_labelPredicate::* + Predicate::*_param for parameterized comparisons.
Limitslimit, skip, rangeAll accept usize or Expr.
Variablesas_ / store, select, injectCross-query refs via NodeRef::var, EdgeRef::var, NodeRef::param, EdgeRef::param.
Orderingorder_by, order_by_multipleUse Order::Desc for descending.
Aggregationcount, exists, group, group_count, aggregate_byAggregateFunction::{Count,Sum,Min,Max,Mean}.
Branchingunion, choose, coalesce, optionalEach arm is a sub() sub-traversal.
Repeatrepeat(RepeatConfig::new(sub).times(n).until(pred).emit_all().max_depth(100))Always bound with times or until; default max_depth is 100.
Projectionvalues, value_map, project, edge_propertiesproject mixes PropertyProjection (incl. renames) and ExprProjection; edge streams can project endpoint fields with Projection::from_endpoint / Projection::to_endpoint.
ExpressionsExpr::prop, Expr::val, Expr::id, Expr::timestamp, Expr::datetime, Expr::param, .add/.sub/.mul/.div/.modulo/.neg, Expr::caseExpr::Timestamp writes server UTC millis; Expr::DateTimeNow writes typed datetime.
Mutationsadd_n, add_e, set_property, remove_property, drop, drop_edge, drop_edge_labeled, drop_edge_by_iddrop_edge_by_id is multigraph-safe.
IndexesIndexSpec::node_equality / node_range / node_range_desc / node_range_with_direction / edge_equality / edge_range / edge_range_desc / edge_range_with_direction / node_vector / node_text / edge_vector / edge_text plus create_index / drop_index; convenience: create_vector_index_nodes, create_text_index_nodes, edge variantsUse .create_index(spec) from a write batch. RangeIndexDirection::Desc sets descending physical order.
TransportDynamicQueryRequest::{read,write}(batch).with_query_name("name").with_parameter_value(...).with_parameter_type(...).to_json_string()Bridge from Rust DSL to the JSON payload (helix-query-json-dynamic). Direct unnamed requests serialize query_name: null; #[register] callable helpers set query_name to the Rust function name.
ClientClient::new(Some(url))?.with_api_key(...).query().writer_only()/.warm_only()/.should_await_durability(b).dynamic(req)/.stored(name).send().awaitSends to POST /v1/query; send() yields R on 200, else HelixError. Prefer .should_await_durability(true) on writes — reduces 409 conflicts under concurrency. See REFERENCE.md → "Client".

See REFERENCE.md for signatures and typestate constraints.

Nested object/array property values are supported with PropertyValue::object(...) and PropertyValue::array(...). Read nested object fields with dotted property strings such as metadata.externalID in predicates, Expr::prop, values, value_map, project, and order_by. Dotted paths are exact-first and scan-only in V1; indexes remain top-level only.

Canonical Examples

Read By Indexed Identifier

read_batch()
    .var_as(
        "user",
        g().n_with_label("User")
            .where_(Predicate::eq_param("userId", "userId"))
            .project(vec![
                PropertyProjection::new("$id"),
                PropertyProjection::new("userId"),
                PropertyProjection::new("name"),
            ]),
    )
    .returning(["user"])

Explicit Create Or Update

write_batch()
    .var_as(
        "existing",
        g().n_with_label("User")
            .where_(Predicate::eq_param("userId", "userId")),
    )
    .var_as_if(
        "updated",
        BatchCondition::VarNotEmpty("existing".to_string()),
        g().n(NodeRef::var("existing"))
            .set_property("name", PropertyInput::param("name")),
    )
    .var_as_if(
        "created",
        BatchCondition::VarEmpty("existing".to_string()),
        g().add_n(
            "User",
            vec![
                ("userId", PropertyInput::param("userId")),
                ("name", PropertyInput::param("name")),
            ],
        ),
    )
    .returning(["updated", "created"])

Scoped Search Route

read_batch()
    .var_as(
        "results",
        g().vector_search_nodes_with(
            "Document",
            "embedding",
            PropertyInput::param("queryVector"),
            Expr::param("limit"),
            Some(PropertyInput::param("tenantId")),
        )
        .project(vec![
            PropertyProjection::new("$id"),
            PropertyProjection::new("title"),
            PropertyProjection::renamed("$distance", "distance"),
        ]),
    )
    .returning(["results"])

Anti-Patterns

Do not:

  • invent labels, edge labels, or property names without checking the codebase
  • start from broad scans when an indexed ID or scoped predicate exists
  • return embeddings by default in search results
  • ignore tenant scope on text or vector search
  • add dedup or limit without a reason
  • assume dynamic inline-query rules apply to Rust DSL queries authored with the builder
  • treat BM25 as if it searches every property automatically

Validation Checklist

Before finishing:

  • verify read_batch() versus write_batch() is correct
  • verify labels, edge labels, and properties match the repo exactly
  • verify the first anchor is the narrowest practical indexed set
  • verify scope filters happen before or as early as possible
  • verify the returned variable names and shape match service expectations
  • verify text and vector routes preserve tenant scope when required
  • verify large properties are omitted unless needed
  • verify the query matches surrounding local style more than any generic example

Reference Files

  • REFERENCE.md — full builder catalog (sources, traversal, predicates, expressions, projections, branching, repeat, mutations, indexes, dynamic-request transport).
  • EXAMPLES.md — end-to-end Rust queries mirroring the scenarios in ../helix-query-typescript/EXAMPLES.md and ../helix-query-json-dynamic/EXAMPLES.md 1:1, so you can move fluently between the Rust DSL, TypeScript DSL, and JSON forms.

Related skills

Rustdatabases

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.