Skip to content

Order

Order operations control how results are sorted and how many are returned. Use orderBy() for sorting, limit() to cap results, and offset() for simple pagination.

Sort results by one or more fields:

const sorted = await store
.query()
.from("Person", "p")
.select((ctx) => ctx.p)
.orderBy((ctx) => ctx.p.name, "asc")
.execute();
.orderBy(fieldSelector, direction?)
.orderBy(alias, field, direction?)
Parameter Type Description
fieldSelector (ctx) => field Function that selects the field to sort by
alias string Node/edge alias (alternative syntax)
field string Field name (alternative syntax)
direction "asc" | "desc" Sort direction (default: "asc")
// Function syntax
.orderBy((ctx) => ctx.p.name, "asc")
// Alias syntax
.orderBy("p", "name", "asc")

Chain orderBy() for multi-field sorting:

const sorted = await store
.query()
.from("Task", "t")
.select((ctx) => ctx.t)
.orderBy("t", "priority", "desc") // Primary sort
.orderBy("t", "createdAt", "asc") // Secondary sort
.execute();

Or use the array syntax:

.orderBy((ctx) => [
{ field: ctx.t.priority, direction: "desc" },
{ field: ctx.t.createdAt, direction: "asc" },
])

The alias form can order directly by TypeGraph’s physical temporal metadata: valid_from, valid_to, created_at, updated_at, and deleted_at. When the queried node or edge kinds do not declare a property with that name, it resolves to the physical system column:

const oldestFirst = await store
.query()
.from("Task", "t")
.orderBy("t", "valid_from", "asc")
.select((ctx) => ctx.t)
.execute();

The same classification applies whether orderBy() appears before or after select().

A declared property takes precedence over same-named temporal metadata. For example, if Task declares its own created_at property, whereNode("t", (field) => field.created_at...) and orderBy("t", "created_at") both address props.created_at. Rename the property when a query must order by TypeGraph’s physical created_at column.

Control where null values appear:

.orderBy((ctx) => ({
field: ctx.p.email,
direction: "asc",
nulls: "last", // or "first"
}))

Order by properties on traversed edges:

const employees = await store
.query()
.from("Company", "c")
.traverse("worksAt", "e", { direction: "in" })
.to("Person", "p")
.select((ctx) => ({
name: ctx.p.name,
startDate: ctx.e.startDate,
}))
.orderBy("e", "startDate", "desc") // Most recent hires first
.execute();

Aggregate queries have their own orderBy(key, direction?), called after .aggregate({...}). key is any output name from the fields object — a grouped field or an aggregate alias:

import { count, field } from "@nicia-ai/typegraph";
const topDepartments = await store
.query()
.from("Employee", "e")
.groupBy("e", "department")
.aggregate({
department: field("e", "department"),
headcount: count("e"),
})
.orderBy("headcount", "desc")
.execute();

Cap the number of results returned:

const top10 = await store
.query()
.from("Person", "p")
.select((ctx) => ctx.p)
.orderBy("p", "score", "desc")
.limit(10)
.execute();
.limit(n)
Parameter Type Description
n number Maximum number of results to return

Skip a number of results (useful for simple pagination):

const page2 = await store
.query()
.from("Person", "p")
.select((ctx) => ctx.p)
.orderBy("p", "name", "asc")
.limit(10)
.offset(10) // Skip first 10 results
.execute();
.offset(n)
Parameter Type Description
n number Number of results to skip
async function getPage(pageNumber: number, pageSize: number) {
return store
.query()
.from("Person", "p")
.select((ctx) => ctx.p)
.orderBy("p", "name", "asc")
.limit(pageSize)
.offset((pageNumber - 1) * pageSize)
.execute();
}
// Usage
const page1 = await getPage(1, 20); // Results 1-20
const page2 = await getPage(2, 20); // Results 21-40

Note: For large datasets, use cursor pagination instead. Offset-based pagination becomes slower as offset increases.

Both paginate() and stream() require an orderBy() clause:

// Required for pagination
const page = await store
.query()
.from("Person", "p")
.select((ctx) => ctx.p)
.orderBy("p", "name", "asc") // Required
.paginate({ first: 20 });
// Required for streaming
const stream = store
.query()
.from("Event", "e")
.select((ctx) => ctx.e)
.orderBy("e", "createdAt", "desc") // Required
.stream();

Cursor pagination and streaming automatically append missing start-node identity keys: id ASC for a single kind, or kind ASC and id ASC for a multi-kind source. Existing caller-specified identity ordering is preserved. Offset pagination needs an explicit total ordering.

For deterministic offset pagination of one kind, include id in your ordering:

.orderBy("p", "name", "asc")
.orderBy("p", "id", "asc") // Ensures stable ordering when names are equal
const leaderboard = await store
.query()
.from("Player", "p")
.select((ctx) => ({
name: ctx.p.name,
score: ctx.p.score,
}))
.orderBy("p", "score", "desc")
.limit(100)
.execute();
const feed = await store
.query()
.from("Activity", "a")
.whereNode("a", (a) => a.userId.eq(currentUserId))
.select((ctx) => ctx.a)
.orderBy("a", "createdAt", "desc")
.limit(50)
.execute();
async function searchProducts(query: string, page: number) {
const pageSize = 20;
return store
.query()
.from("Product", "p")
.whereNode("p", (p) => p.name.ilike(`%${query}%`))
.select((ctx) => ({
id: ctx.p.id,
name: ctx.p.name,
price: ctx.p.price,
}))
.orderBy("p", "relevance", "desc")
.orderBy("p", "id", "asc")
.limit(pageSize)
.offset((page - 1) * pageSize)
.execute();
}
  • Execute - Cursor pagination and streaming
  • Shape - Output transformation
  • Filter - Reducing results with predicates

When the source node is already available, store.neighbors() is the direct one-statement read:

const [latest] = await store.neighbors(document, {
edges: ["hasVersion"],
orderBy: { by: "node", field: "sequence", direction: "desc" },
limit: 1,
});

Use by: "edge" (or omit by) for edge metadata and by: "node" for an adjacent-node property. Both forms sort nulls last and use the edge ID as the deterministic final tie-breaker.

The equivalent fluent traversal is useful when additional target predicates or projections are needed:

const [newest] = await store.query()
.from("Document", "document")
.whereNode("document", (node) => node.id.eq(documentId))
.traverse("hasVersion", "edge").to("Version", "version")
.orderBy("version", "sequence", "desc")
.orderBy("version", "id", "desc")
.select((ctx) => ctx.version)
.limit(1)
.execute();

The ID order resolves ties deterministically. With no matching target, newest is undefined.