Skip to content

Filter

Filter operations reduce the result set based on property values. TypeGraph provides whereNode() for filtering nodes and whereEdge() for filtering edges during traversals.

Filter nodes based on their properties:

const engineers = await store
.query()
.from("Person", "p")
.whereNode("p", (p) => p.role.eq("Engineer"))
.select((ctx) => ctx.p)
.execute();
.whereNode(alias, predicateFunction)
ParameterTypeDescription
aliasstringThe node alias to filter (must exist in query)
predicateFunction(accessor) => PredicateFunction that returns a predicate

The predicate function receives a typed accessor for the node’s properties.

Filter based on edge properties during traversals:

const highPaying = await store
.query()
.from("Person", "p")
.traverse("worksAt", "e")
.whereEdge("e", (e) => e.salary.gte(100000))
.to("Company", "c")
.select((ctx) => ({
person: ctx.p.name,
company: ctx.c.name,
salary: ctx.e.salary,
}))
.execute();
.whereEdge(alias, predicateFunction)
ParameterTypeDescription
aliasstringThe edge alias to filter (must exist in query)
predicateFunction(accessor) => PredicateFunction that returns a predicate

Both conditions must be true:

.whereNode("p", (p) =>
p.status.eq("active").and(p.role.eq("admin"))
)

Either condition can be true:

.whereNode("p", (p) =>
p.role.eq("admin").or(p.role.eq("moderator"))
)

Negate a condition:

.whereNode("p", (p) =>
p.status.eq("deleted").not()
)

Build complex logic with parenthetical grouping:

.whereNode("p", (p) =>
p.status
.eq("active")
.and(p.role.eq("admin").or(p.role.eq("moderator")))
)

This evaluates as: status = 'active' AND (role = 'admin' OR role = 'moderator')

Chain multiple whereNode() calls for AND logic:

const activeManagers = await store
.query()
.from("Person", "p")
.whereNode("p", (p) => p.status.eq("active"))
.whereNode("p", (p) => p.role.eq("Manager"))
.select((ctx) => ctx.p)
.execute();

This is equivalent to:

.whereNode("p", (p) =>
p.status.eq("active").and(p.role.eq("Manager"))
)

Filter nodes at any point in the query:

const techCompanyEngineers = await store
.query()
.from("Person", "p")
.whereNode("p", (p) => p.role.eq("Engineer"))
.traverse("worksAt", "e")
.to("Company", "c")
.whereNode("c", (c) => c.industry.eq("Technology"))
.select((ctx) => ({
person: ctx.p.name,
company: ctx.c.name,
}))
.execute();

Here are the most commonly used predicates. For complete reference, see Predicates.

p.name.eq("Alice") // equals
p.name.neq("Bob") // not equals
p.age.gt(21) // greater than
p.age.gte(21) // greater than or equal
p.age.lt(65) // less than
p.age.lte(65) // less than or equal
p.age.between(18, 65) // inclusive range
p.name.contains("ali") // substring match
p.name.startsWith("A") // prefix match
p.name.endsWith("ice") // suffix match
p.email.like("%@example.com") // SQL LIKE pattern
p.name.ilike("alice") // case-insensitive LIKE

For nodes with at least one field declared with searchable(), use the node-level $fulltext.matches() for BM25-style ranked fulltext search. See Fulltext Search for the full guide.

d.$fulltext.matches("climate change", 20) // Top 20 by relevance
d.$fulltext.matches("quarterly earnings", 10, {
mode: "websearch", // Google-style syntax
})

Combine with any other predicate — fulltext composes with metadata filters, graph traversal, and vector search:

d.$fulltext.matches("climate", 20)
.and(d.tenantId.eq(tenant))
.and(d.published.eq(true))
p.deletedAt.isNull() // is null/undefined
p.email.isNotNull() // is not null
p.status.in(["active", "pending"])
p.status.notIn(["archived", "deleted"])
p.tags.contains("typescript")
p.tags.containsAll(["typescript", "nodejs"])
p.tags.containsAny(["typescript", "rust", "go"])
p.tags.isEmpty()
p.tags.isNotEmpty()

The available predicates depend on the field type:

Field TypeKey Predicates
Stringeq, contains, startsWith, like, ilike
Nodes with searchable() fields$fulltext.matches() (node-level, not per-field)
Numbereq, gt, gte, lt, lte, between
Dateeq, gt, gte, lt, lte, between
Arraycontains, containsAll, containsAny, isEmpty
Objectget(), hasKey, pathEquals
EmbeddingsimilarTo()

See Predicates for complete documentation.

const count: number = await store
.query()
.from("Person", "p")
.whereNode("p", (p) => p.status.eq("active"))
.count();
const exists: boolean = await store
.query()
.from("Person", "p")
.whereNode("p", (p) => p.email.eq("alice@example.com"))
.exists();
const alice = await store
.query()
.from("Person", "p")
.whereNode("p", (p) => p.email.eq("alice@example.com"))
.select((ctx) => ctx.p)
.first();
if (alice) {
console.log(alice.name);
}
  • Predicates - Complete predicate reference
  • Traverse - Navigate relationships
  • Advanced - Subqueries with exists() and inSubquery()