Compose
Compose operations let you create reusable query transformations. Use pipe() to apply
transformations and createFragment() to build typed, composable query parts.
The pipe() Method
Section titled “The pipe() Method”Apply a transformation function to a query builder:
const results = await store .query() .from("User", "u") .pipe((q) => q.whereNode("u", ({ status }) => status.eq("active"))) .pipe((q) => q.orderBy("u", "createdAt", "desc")) .select((ctx) => ctx.u) .execute();Each pipe() receives the current builder and returns a modified builder, enabling chained transformations.
Defining Reusable Fragments
Section titled “Defining Reusable Fragments”Extract common patterns into reusable functions:
// Define reusable fragmentsconst activeOnly = (q) => q.whereNode("u", ({ status }) => status.eq("active"));
const recentFirst = (q) => q.orderBy("u", "createdAt", "desc");
const first10 = (q) => q.limit(10);
// Use in queriesconst results = await store .query() .from("User", "u") .pipe(activeOnly) .pipe(recentFirst) .pipe(first10) .select((ctx) => ctx.u) .execute();Typed Fragments with createFragment()
Section titled “Typed Fragments with createFragment()”For full type safety, use the createFragment() factory:
import { createFragment } from "@nicia-ai/typegraph";
// Create a typed fragment factory for your graphconst fragment = createFragment<typeof graph>();
// Define typed fragmentsconst activeUsers = fragment((q) => q.whereNode("u", ({ status }) => status.eq("active")));
const withRecentPosts = fragment((q) => q.traverse("authored", "a") .to("Post", "p") .whereNode("p", ({ createdAt }) => createdAt.gte("2024-01-01")));
// Compose into queriesconst results = await store .query() .from("User", "u") .pipe(activeUsers) .pipe(withRecentPosts) .select((ctx) => ({ user: ctx.u, post: ctx.p, })) .execute();Composing Fragments
Section titled “Composing Fragments”Use composeFragments() to combine multiple fragments into one:
import { composeFragments, limitFragment, orderByFragment } from "@nicia-ai/typegraph";
// Compose multiple fragments into oneconst paginatedActiveUsers = composeFragments( (q) => q.whereNode("u", ({ status }) => status.eq("active")), (q) => q.orderBy("u", "createdAt", "desc"), (q) => q.limit(20));
// Apply as a single transformationconst results = await store .query() .from("User", "u") .pipe(paginatedActiveUsers) .select((ctx) => ctx.u) .execute();Helper Fragments
Section titled “Helper Fragments”TypeGraph provides pre-built helper fragments:
import { limitFragment, offsetFragment, orderByFragment, composeFragments} from "@nicia-ai/typegraph";
// Pre-built fragmentsconst paginated = composeFragments( orderByFragment("u", "createdAt", "desc"), limitFragment(20), offsetFragment(40));
const results = await store .query() .from("User", "u") .pipe(paginated) .select((ctx) => ctx.u) .execute();Available Helpers
Section titled “Available Helpers”| Helper | Description |
|---|---|
limitFragment(n) | Limits results to n rows |
offsetFragment(n) | Skips the first n rows |
orderByFragment(alias, field, direction) | Orders by a field |
Fragments with Traversals
Section titled “Fragments with Traversals”Fragments can include traversals:
// Fragment that adds a manager traversalconst withManager = fragment((q) => q.traverse("reportsTo", "r").to("User", "manager"));
// Fragment that adds department infoconst withDepartment = fragment((q) => q.traverse("belongsTo", "b").to("Department", "dept"));
// Compose for a complete employee viewconst employeeDetails = composeFragments(withManager, withDepartment);
const results = await store .query() .from("User", "u") .pipe(employeeDetails) .select((ctx) => ({ employee: ctx.u, manager: ctx.manager, department: ctx.dept, })) .execute();Post-Select Fragments
Section titled “Post-Select Fragments”pipe() is also available on ExecutableQuery:
// Define a pagination fragment for executable queriesconst paginate = (q) => q.orderBy("u", "name", "asc").limit(10).offset(20);
const results = await store .query() .from("User", "u") .select((ctx) => ({ name: ctx.u.name, email: ctx.u.email })) .pipe(paginate) .execute();Real-World Patterns
Section titled “Real-World Patterns”Search with Conditional Filters
Section titled “Search with Conditional Filters”function searchUsers(filters: { status?: string; role?: string; search?: string;}) { let query = store.query().from("User", "u");
// Apply filters conditionally using pipe if (filters.status) { query = query.pipe((q) => q.whereNode("u", ({ status }) => status.eq(filters.status)) ); }
if (filters.role) { query = query.pipe((q) => q.whereNode("u", ({ role }) => role.eq(filters.role)) ); }
if (filters.search) { query = query.pipe((q) => q.whereNode("u", ({ name }) => name.ilike(`%${filters.search}%`)) ); }
return query.select((ctx) => ctx.u).execute();}Configurable Pagination
Section titled “Configurable Pagination”function createPaginationFragment(options: { sortField: string; sortDir: "asc" | "desc"; page: number; pageSize: number;}) { return composeFragments( orderByFragment("u", options.sortField, options.sortDir), limitFragment(options.pageSize), offsetFragment((options.page - 1) * options.pageSize) );}
// Use with any queryconst pagination = createPaginationFragment({ sortField: "createdAt", sortDir: "desc", page: 2, pageSize: 25,});
const results = await store .query() .from("User", "u") .pipe(pagination) .select((ctx) => ctx.u) .execute();Domain-Specific Query Helpers
Section titled “Domain-Specific Query Helpers”// Create domain-specific query helpersconst userQueries = { active: (q) => q.whereNode("u", ({ status }) => status.eq("active")),
verified: (q) => q.whereNode("u", ({ emailVerified }) => emailVerified.eq(true)),
withRole: (role: string) => (q) => q.whereNode("u", ({ role: r }) => r.eq(role)),
withPosts: (q) => q.traverse("authored", "a").to("Post", "p"),
recentlyActive: (q) => q.whereNode("u", ({ lastLogin }) => lastLogin.gte(new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString()) ),};
// Compose for specific use casesconst activeAdmins = await store .query() .from("User", "u") .pipe(userQueries.active) .pipe(userQueries.verified) .pipe(userQueries.withRole("admin")) .select((ctx) => ctx.u) .execute();Type Definitions
Section titled “Type Definitions”For advanced use cases, TypeGraph exports fragment type definitions:
import type { QueryFragment, FlexibleQueryFragment, TraversalFragment} from "@nicia-ai/typegraph";QueryFragment<G, InAliases, OutAliases, InEdgeAliases, OutEdgeAliases>- A typed fragment transformationFlexibleQueryFragment<G, RequiredAliases, AddedAliases, ...>- A fragment that works with any compatible builderTraversalFragment<G, EK, EA, ...>- A fragment for transforming TraversalBuilder instances