Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.helix-db.com/llms.txt

Use this file to discover all available pages before exploring further.

For the complete documentation index optimized for AI agents, see llms.txt.
Up to this point every example has ended with .valueMap([...]) to ship a few properties back to the caller. This page covers the full set of terminal projections: how to shape the output, compute derived values, and aggregate the stream. A traversal that ends in a terminal step (.count, .values, .project, …) produces a final result rather than another node or edge stream. The TypeScript and Rust typestate machinery prevents you from chaining traversal steps after a terminal.

Scalar terminals: .count, .exists, .id, .label

The smallest terminals collapse the whole stream into a single value.
import { g, readBatch, SourcePredicate } from "@helixdb/enterprise-ql";

const aliceStats = readBatch()
  .varAs("post_count", g().nWithLabel("Post").count())
  .varAs(
    "alice_exists",
    g().nWhere(SourcePredicate.eq("username", "alice")).exists(),
  )
  .returning(["post_count", "alice_exists"]);
The four scalar terminals:
StepReturns
.count()Number of items in the stream.
.exists()true if the stream is non-empty, false otherwise.
.id()Stream of $id values, one per item.
.label()Stream of $label values, one per item.
.id() and .label() are usually most useful followed by another binding via .varAs(...), or as input to .project(...).

.values(...) and .valueMap(...)

When you want a few properties from each item:
  • .values([prop, ...]) — emits an array of arrays, one inner array per item, with the properties in the requested order. No keys, only positional values.
  • .valueMap([prop, ...]) — emits an array of objects keyed by property name. Pass null (or omit the argument) for “every property”.
valueMap is the default choice for service-facing routes; values is handy when the caller does its own positional decoding.
import { g, readBatch } from "@helixdb/enterprise-ql";

const usersForAdminPanel = readBatch()
  .varAs(
    "rows",
    g()
      .nWithLabel("User")
      .valueMap(["$id", "$label", "username", "tier", "createdAt"]),
  )
  .returning(["rows"]);
$id, $label, $from, $to, and $distance are virtual fields: they are always available even though they are not declared on your schema. $from/$to appear on edge streams; $distance appears on vector / text search hits.

.project(...) with renames and expressions

project([...]) is the most flexible terminal — it accepts a list of items, each one either:
  • PropertyProjection.new(prop) — emit prop under its own name.
  • PropertyProjection.renamed(source, alias) — emit source under a different key.
  • ExprProjection.new(alias, expr) — emit a computed expression under alias.
Use it when you want to flatten ids out from under $id, return a different name than the schema’s, or compute a derived field. A typical project call mixes plain property pulls with one or two renames:
import { g, PropertyProjection, readBatch } from "@helixdb/enterprise-ql";

const userCards = readBatch()
  .varAs(
    "cards",
    g()
      .nWithLabel("User")
      .project([
        PropertyProjection.renamed("$id", "id"),
        PropertyProjection.new("username"),
        PropertyProjection.new("tier"),
      ]),
  )
  .returning(["cards"]);
Each entry in Project is a bare object with source / alias (for PropertyProjection) or alias / expr (for ExprProjection). There is no enum tag around them — the Projection enum is untagged on the wire.

Expressions for ExprProjection

Expr is a small arithmetic-and-conditionals algebra:
  • Sources: Expr.prop(name), Expr.val(value), Expr.id(), Expr.timestamp(), Expr.datetime(), Expr.param(name).
  • Arithmetic: .add(other), .sub(other), .mul(other), .div(other), .modulo(other), .neg().
  • Branching: Expr.case([[predicate, expr], ...], elseExpr).
A small derived field — fall back to "unknown" when the tier property is null:
import {
  Expr,
  ExprProjection,
  g,
  Predicate,
  PropertyProjection,
  readBatch,
} from "@helixdb/enterprise-ql";

const userCardsWithFallback = readBatch()
  .varAs(
    "cards",
    g()
      .nWithLabel("User")
      .project([
        PropertyProjection.renamed("$id", "id"),
        PropertyProjection.new("username"),
        ExprProjection.new(
          "tier",
          Expr.case(
            [[Predicate.isNotNull("tier"), Expr.prop("tier")]],
            Expr.val("unknown"),
          ),
        ),
      ]),
  )
  .returning(["cards"]);
For time-relative comparisons against “now” (e.g. “rows newer than 30 days”), use Expr.timestamp() (server epoch millis) or Expr.datetime() (typed datetime) on one side of a Predicate.compare(left, CompareOp.*, right). Arithmetic on Expr returns another Expr, so you can chain Expr.timestamp().sub(Expr.val(30 * 86_400 * 1000)) to compute a cut-off server-side.

Edge property output

For edge streams, .edgeProperties() emits each edge as an object with all of its properties plus the virtual $id, $label, $from, and $to fields.
import { g, NodeRef, readBatch, SourcePredicate } from "@helixdb/enterprise-ql";

const aliceFollowsEdges = readBatch()
  .varAs("user", g().nWhere(SourcePredicate.eq("username", "alice")))
  .varAs(
    "edges",
    g().n(NodeRef.var("user")).outE("FOLLOWS").edgeProperties(),
  )
  .returning(["edges"]);

Aggregations: .group, .groupCount, .aggregateBy

For grouped counts and aggregate statistics:
  • .group(prop) — bucket the stream by prop, emit {groupValue: [items...]}.
  • .groupCount(prop) — bucket and count, emit {groupValue: count}.
  • .aggregateBy(AggregateFunction.*, prop) — apply a reducer to one property: Count, Sum, Min, Max, Mean.
import { AggregateFunction, g, readBatch } from "@helixdb/enterprise-ql";

const tierBreakdown = readBatch()
  .varAs("by_tier", g().nWithLabel("User").groupCount("tier"))
  .varAs("avg_post_age", g().nWithLabel("Post").aggregateBy(AggregateFunction.Mean, "createdAt"))
  .returning(["by_tier", "avg_post_age"]);
AggregateFunction values are serialized as strings: "Count", "Sum", "Min", "Max", "Mean".

Next Steps

Mutations

writeBatch, addN, addE, setProperty, drop — and using results inside the same batch.

Vector and text search

vectorSearchNodes, textSearchNodes, projecting $distance, follow-on traversal.