Skip to content

Query Builder Overview

TypeGraph provides a fluent, type-safe query builder for traversing and filtering your graph. This page introduces the query categories and how they compose together.

Every query builder method falls into one of these categories:

Category Purpose Key Methods
Source Entry point - where to start from()
Filter Reduce the result set whereNode(), whereEdge()
Traverse Navigate relationships traverse(), optionalTraverse(), to()
Recursive Variable-length paths recursive()
Shape Transform output structure select(), aggregate()
Aggregate Summarize data groupBy(), count(), sum(), avg()
Order Control result ordering/size orderBy(), limit(), offset()
Temporal Time-based queries temporal()
Compose Reusable query parts pipe(), createFragment()
Combine Set operations union(), intersect(), except()
Execute Run and retrieve execute(), first(), count(), exists(), paginate(), stream(), batch()

A typical query follows this flow:

Source → Filter → Traverse → Filter → Shape → Order → Execute
↑__________________|
(repeat as needed)

Each step is optional except Source and Execute. You can filter, traverse, and filter again as many times as needed before shaping and executing.

const results = await store
.query()
.from("Person", "p") // Source
.whereNode("p", (p) => p.status.eq("active")) // Filter
.traverse("worksAt", "e") // Traverse
.to("Company", "c") // Traverse (target)
.whereNode("c", (c) => c.industry.eq("Tech")) // Filter
.select((ctx) => ({ // Shape
person: ctx.p.name,
company: ctx.c.name,
role: ctx.e.role,
}))
.orderBy("p", "name", "asc") // Order
.limit(50) // Order
.execute(); // Execute

The query builder is fully typed. TypeScript infers result types based on your schema and selection:

// TypeScript infers: Array<{ name: string; email: string | undefined }>
const results = await store
.query()
.from("Person", "p")
.select((ctx) => ({
name: ctx.p.name, // string (required in schema)
email: ctx.p.email, // string | undefined (optional in schema)
}))
.execute();
// Invalid property access is caught at compile time:
.select((ctx) => ({
invalid: ctx.p.nonexistent, // TypeScript error!
}))

Use the query builder when you need:

  • Filtering based on node properties
  • Traversing relationships between nodes
  • Aggregating data across multiple nodes
  • Complex predicates with AND/OR logic

Use the Store API for simple operations:

  • Get a node by ID
  • Create a new node
  • Update a node’s properties
  • Delete a node

Predicates are the building blocks for filtering. Each data type has its own set of predicates:

Type Documentation
String String Predicates
Number Number Predicates
Date Date Predicates
Array Array Predicates
Object Object Predicates
Embedding Embedding Predicates

Apply predicates as early as possible to reduce the working set:

// Good: Filter at source
store
.query()
.from("Person", "p")
.whereNode("p", (p) => p.active.eq(true))
.traverse("worksAt", "e")
.to("Company", "c");
// Less efficient: Filter after traversal
store
.query()
.from("Person", "p")
.traverse("worksAt", "e")
.to("Company", "c")
.whereNode("p", (p) => p.active.eq(true));

Unless you need subclass expansion, use exact kinds:

// More efficient: Exact kind
.from("Podcast", "p")
// Less efficient: Includes all subclasses
.from("Media", "m", { includeSubClasses: true })
const page = await store
.query()
.from("Event", "e")
.orderBy("e", "date", "desc")
.limit(100)
.execute();

Start with the fundamentals:

  1. Source - Starting queries with from()
  2. Filter - Reducing results with predicates
  3. Traverse - Navigating relationships
  4. Shape - Transforming output with select()