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:

CategoryPurposeKey Methods
SourceEntry point - where to startfrom()
FilterReduce the result setwhereNode(), whereEdge()
TraverseNavigate relationshipstraverse(), optionalTraverse(), to()
RecursiveVariable-length pathsrecursive()
ShapeTransform output structureselect(), aggregate()
AggregateSummarize datagroupBy(), count(), sum(), avg()
OrderControl result ordering/sizeorderBy(), limit(), offset()
TemporalTime-based queriestemporal()
ComposeReusable query partspipe(), createFragment()
CombineSet operationsunion(), intersect(), except()
ExecuteRun and retrieveexecute(), 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:

TypeDocumentation
StringString Predicates
NumberNumber Predicates
DateDate Predicates
ArrayArray Predicates
ObjectObject Predicates
EmbeddingEmbedding 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()