Skip to content

Shape

Shape operations transform how results are returned. Use select() for the compatibility result mapper, project() for an explicit SQL projection, map() for a final JavaScript transformation, and aggregate() for grouped results.

The select() method defines what data to return:

const results = await store
.query()
.from("Person", "p")
.select((ctx) => ({
name: ctx.p.name,
email: ctx.p.email,
}))
.execute();
.select(selectFunction)
Parameter Type Description
selectFunction (ctx) => T Function that receives a context and returns the output shape

The context provides typed access to all nodes and edges in the query via their aliases.

For new queries that need SQL composition, use project() and the typed expr helpers described in Database Expressions. A projected query can become an asRelation() for derived filtering, aggregation, distinctness, and set operations. Use select() when you specifically need the compatibility JavaScript mapper.

Return all properties as an object:

.select((ctx) => ctx.p)
// Returns: { id, kind, name, email, ... }

Return only the fields you need:

.select((ctx) => ({
id: ctx.p.id,
name: ctx.p.name,
email: ctx.p.email,
}))

Include system metadata fields:

.select((ctx) => ({
id: ctx.p.id,
kind: ctx.p.kind, // "Person"
version: ctx.p.version, // Optimistic concurrency version
createdAt: ctx.p.createdAt,
updatedAt: ctx.p.updatedAt,
}))

Select from multiple nodes in a traversal:

const results = await store
.query()
.from("Person", "p")
.traverse("worksAt", "e")
.to("Company", "c")
.select((ctx) => ({
person: ctx.p.name,
company: ctx.c.name,
role: ctx.e.role, // Edge property
}))
.execute();

Structure output with nested objects:

.select((ctx) => ({
employee: {
id: ctx.p.id,
name: ctx.p.name,
},
company: {
id: ctx.c.id,
name: ctx.c.name,
},
employment: {
role: ctx.e.role,
startDate: ctx.e.startDate,
},
}))

Rename fields in the output:

.select((ctx) => ({
personName: ctx.p.name, // Renamed from 'name'
companyName: ctx.c.name, // Renamed from 'name'
jobTitle: ctx.e.role, // Renamed from 'role'
}))

TypeScript infers the result type from your 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 caught at compile time:
.select((ctx) => ({
invalid: ctx.p.nonexistent, // TypeScript error!
}))

When using optionalTraverse(), accessed nodes and edges may be undefined:

const results = await store
.query()
.from("Person", "p")
.optionalTraverse("worksAt", "e")
.to("Company", "c")
.select((ctx) => ({
person: ctx.p.name,
company: ctx.c?.name, // May be undefined
role: ctx.e?.role, // May be undefined
}))
.execute();

Use aggregate() with aggregate functions for grouped queries:

import { count, sum, avg, field } from "@nicia-ai/typegraph";
const stats = await store
.query()
.from("Person", "p")
.traverse("worksAt", "e")
.to("Company", "c")
.groupBy("c", "name")
.aggregate({
companyName: field("c", "name"),
employeeCount: count("p"),
totalSalary: sum("e", "salary"),
avgSalary: avg("e", "salary"),
})
.execute();

See Aggregate for full aggregate documentation.

With recursive traversals, include path and depth:

const results = await store
.query()
.from("Category", "cat")
.traverse("parentCategory", "e")
.recursive({ path: "pathIds", depth: "depth" })
.to("Category", "ancestor")
.select((ctx) => ({
category: ctx.cat.name,
ancestor: ctx.ancestor.name,
path: ctx.pathIds, // Array of node IDs
depth: ctx.depth, // Number of hops
}))
.execute();

When using temporal queries, access validity information:

const history = await store
.query()
.from("Article", "a")
.temporal("includeEnded")
.select((ctx) => ({
title: ctx.a.title,
validFrom: ctx.a.validFrom, // When this version became valid
validTo: ctx.a.validTo, // When superseded (undefined if current)
version: ctx.a.version, // Version number
}))
.execute();

select() returns an ExecutableQuery that provides:

  • execute() - Run the query and get results
  • paginate() - Cursor-based pagination
  • stream() - Stream results for large datasets
  • first() - Get the first result or undefined
  • count() - Count matching results
  • exists() - Check if any results exist
  • toAst() - Get the query AST
  • compile() - Compile to SQL
  • Expressions - SQL projection and typed expression callbacks
  • Relations - Compose explicit projected results
  • Aggregate - Grouping and aggregate functions
  • Order - Ordering and limiting results
  • Execute - Running queries and pagination