Advanced Queries
This guide covers advanced querying patterns including useObjectSet, derived properties, aggregations, and metadata.
useObjectSet
Experimental - import from @osdk/react/experimental
Advanced querying with set operations, derived properties, and link traversal.
When to Use useObjectSet vs useOsdkObjects
Both hooks support where, orderBy, pagination, withProperties, pivotTo, autoFetchMore, and streamUpdates.
Use useOsdkObjects when:
- Passing an ObjectType or Interface directly (
Todo)
Use useObjectSet when:
- Starting from an ObjectSet instance (
$(Todo)) - Need set operations (
union,intersect,subtract) with other ObjectSets
useOsdkObjects is in active development to reach full OSDK TypeScript client parity as it has more performance enhancements. useObjectSet currently supports everything and should be used if a feature you need isn't present in useOsdkObjects. Check the JSDoc for current feature support.
import { $, Todo } from "@my/osdk";
import { useObjectSet, useOsdkObjects } from "@osdk/react/experimental";
// Simple query - use useOsdkObjects
const { data } = useOsdkObjects(Todo, {
where: { isComplete: false },
orderBy: { createdAt: "desc" },
});
// Set operations - use useObjectSet
const urgentTodos = $(Todo).where({ isUrgent: true });
const completedTodos = $(Todo).where({ isComplete: true });
const { data } = useObjectSet($(Todo), {
union: [urgentTodos],
subtract: [completedTodos],
});
$ functionThe $ function from your generated SDK creates an ObjectSet from an object type. $(Todo) creates an ObjectSet containing all Todo objects that you can then filter, union, intersect, or subtract with other ObjectSets.
Basic Usage
import { $, Todo } from "@my/osdk";
import { useObjectSet } from "@osdk/react/experimental";
function TodosWithSetOperations() {
const allTodos = $(Todo);
const completedTodos = $(Todo).where({ isComplete: true });
const { data, isLoading, fetchMore } = useObjectSet(allTodos, {
subtract: [completedTodos],
where: { priority: "high" },
orderBy: { createdAt: "desc" },
pageSize: 20,
});
return (
<div>
{data?.map(todo => (
<div key={todo.$primaryKey}>
{todo.title}
</div>
))}
</div>
);
}
Set Operations
Union
Combine multiple object sets:
import { $, Todo } from "@my/osdk";
import { useObjectSet } from "@osdk/react/experimental";
function CombinedTodoQuery() {
const highPriorityTodos = $(Todo).where({ priority: "high" });
const urgentTodos = $(Todo).where({ isUrgent: true });
const { data } = useObjectSet(highPriorityTodos, {
union: [urgentTodos], // High priority OR urgent
});
return <div>High priority or urgent: {data?.length}</div>;
}
Intersect
Find objects that exist in all sets:
import { $, Employee } from "@my/osdk";
import { useObjectSet } from "@osdk/react/experimental";
function SharedProjects({ employee1, employee2 }: {
employee1: Employee.OsdkInstance;
employee2: Employee.OsdkInstance;
}) {
const set1 = $(Employee).where({ id: employee1.id });
const set2 = $(Employee).where({ id: employee2.id });
const { data } = useObjectSet(set1, {
pivotTo: "projects",
intersect: [set2.$pivotTo("projects")],
});
return (
<div>
<h3>Shared Projects</h3>
{data?.map(project => (
<div key={project.$primaryKey}>{project.name}</div>
))}
</div>
);
}
Subtract
Remove objects that exist in another set:
import { $, Todo } from "@my/osdk";
import { useObjectSet } from "@osdk/react/experimental";
function ActiveTodos() {
const allTodos = $(Todo);
const completedTodos = $(Todo).where({ isComplete: true });
const { data } = useObjectSet(allTodos, {
subtract: [completedTodos],
});
return <div>Active todos: {data?.length}</div>;
}
Combined Operations
import { $, Todo } from "@my/osdk";
import { useObjectSet } from "@osdk/react/experimental";
function ComplexTodoQuery() {
const highPriorityTodos = $(Todo).where({ priority: "high" });
const urgentTodos = $(Todo).where({ isUrgent: true });
const completedTodos = $(Todo).where({ isComplete: true });
const { data } = useObjectSet(highPriorityTodos, {
union: [urgentTodos], // High priority OR urgent
subtract: [completedTodos], // But not completed
});
return <div>High priority or urgent (but not completed): {data?.length}</div>;
}
Link Traversal with pivotTo
Navigate to linked objects:
import { $, Employee } from "@my/osdk";
import { useObjectSet } from "@osdk/react/experimental";
function EmployeeDepartments({ employee }: { employee: Employee.OsdkInstance }) {
const employeeSet = $(Employee).where({ id: employee.id });
const { data } = useObjectSet(employeeSet, {
pivotTo: "department",
});
return (
<div>
Departments: {data?.map(dept => dept.name).join(", ")}
</div>
);
}
Auto-Fetching and Streaming
import { $, Todo } from "@my/osdk";
import { useObjectSet } from "@osdk/react/experimental";
const { data, isLoading } = useObjectSet($(Todo), {
where: { isComplete: false },
autoFetchMore: 200, // Fetch at least 200 items
streamUpdates: true, // Real-time WebSocket updates
});
All Options
where- Filter objectswithProperties- Add derived/computed propertiesunion- Combine with other ObjectSetsintersect- Find common objects with other ObjectSetssubtract- Remove objects that exist in other ObjectSetspivotTo- Traverse to linked objects (changes result type)pageSize- Number of objects per pageorderBy- Sort orderdedupeIntervalMs- Minimum time between re-fetches (default: 2000ms)streamUpdates- Enable real-time websocket updates (default: false)autoFetchMore- Auto-fetch additional pagesenabled- Enable/disable the query
Return Values
data- Array of objects with derived propertiesisLoading- True while fetchingerror- Error object if fetch failedfetchMore- Function to load next pageobjectSet- The transformed ObjectSet after all operations
Derived Properties
Available in both useOsdkObjects and useObjectSet
Add computed properties calculated server-side using the builder pattern.
Basic Usage
Derived properties use a builder function that receives a DerivedProperty.Builder:
import type { DerivedProperty } from "@osdk/client";
import { Employee } from "@my/osdk";
import { useOsdkObjects } from "@osdk/react/experimental";
const { data } = useOsdkObjects(Employee, {
where: { department: "Engineering" },
withProperties: {
// Get manager's name via link traversal
managerName: (base: DerivedProperty.Builder<Employee, false>) =>
base.pivotTo("manager").selectProperty("fullName"),
// Count direct reports
reportCount: (base: DerivedProperty.Builder<Employee, false>) =>
base.pivotTo("reports").aggregate("$count"),
},
});
Builder Methods
The builder provides these methods:
.pivotTo(linkName)- Navigate to linked objects.selectProperty(propertyName)- Select a property value.aggregate(aggregation)- Aggregate values ("$count","propertyName:$avg", etc.).where(clause)- Filter before aggregating
Advanced Examples
import type { DerivedProperty } from "@osdk/client";
const { data } = useOsdkObjects(Employee, {
where: { department: "Engineering" },
withProperties: {
// Chained traversal
departmentSize: (base: DerivedProperty.Builder<Employee, false>) =>
base.pivotTo("manager")
.pivotTo("reports")
.aggregate("$count"),
// Aggregate a specific property
avgReportSalary: (base: DerivedProperty.Builder<Employee, false>) =>
base.pivotTo("reports")
.selectProperty("salary")
.aggregate("$avg"),
},
});
Filtering on Derived Properties
You can filter on derived properties in your where clause:
import type { DerivedProperty } from "@osdk/client";
const { data } = useOsdkObjects(Employee, {
withProperties: {
reportCount: (base: DerivedProperty.Builder<Employee, false>) =>
base.pivotTo("reports").aggregate("$count"),
},
where: {
department: "Engineering",
reportCount: { $gt: 0 }, // Only managers
},
});
useOsdkAggregation
Experimental - import from @osdk/react/experimental
Server-side grouping and aggregation.
Simple Aggregation
import { Todo } from "@my/osdk";
import { useOsdkAggregation } from "@osdk/react/experimental";
function TodoStats() {
const { data, isLoading, error } = useOsdkAggregation(Todo, {
aggregate: {
$select: {
totalCount: { $count: {} },
avgPriority: { $avg: "priority" },
maxDueDate: { $max: "dueDate" },
},
},
});
if (isLoading) {
return <div>Calculating stats...</div>;
}
if (error) {
return <div>Error: {JSON.stringify(error)}</div>;
}
return (
<div>
<p>Total Todos: {data?.totalCount}</p>
<p>Average Priority: {data?.avgPriority}</p>
<p>Latest Due Date: {data?.maxDueDate}</p>
</div>
);
}
Grouped Aggregations
import { Todo } from "@my/osdk";
import { useOsdkAggregation } from "@osdk/react/experimental";
function TodosByStatus() {
const { data, isLoading } = useOsdkAggregation(Todo, {
aggregate: {
$groupBy: { status: "exact" },
$select: {
count: { $count: {} },
avgPriority: { $avg: "priority" },
},
},
});
if (isLoading) {
return <div>Loading...</div>;
}
return (
<div>
{data?.map((group, idx) => (
<div key={idx}>
<h3>Status: {group.$group.status}</h3>
<p>Count: {group.count}</p>
<p>Avg Priority: {group.avgPriority}</p>
</div>
))}
</div>
);
}
Filtered Aggregations
import { Todo } from "@my/osdk";
import { useOsdkAggregation } from "@osdk/react/experimental";
function HighPriorityStats() {
const { data, isLoading } = useOsdkAggregation(Todo, {
where: { priority: "high", isComplete: false },
aggregate: {
$select: {
count: { $count: {} },
earliestDue: { $min: "dueDate" },
},
},
});
if (isLoading || !data) return <div>Loading...</div>;
return (
<div>
<p>High Priority Incomplete: {data.count}</p>
<p>Earliest Due: {data.earliestDue}</p>
</div>
);
}
Aggregation Operators
$count- Count of objects:{ $count: {} }$sum- Sum of a property:{ $sum: "propertyName" }$avg- Average of a property:{ $avg: "propertyName" }$min- Minimum value:{ $min: "propertyName" }$max- Maximum value:{ $max: "propertyName" }
Options
where- Filter objects before aggregationwithProperties- Add derived properties for computed valuesaggregate- Aggregation configuration:$select(required) - Object mapping metric names to aggregation operators$groupBy(optional) - Object mapping property names to grouping strategy (e.g.,"exact",{ $fixedWidth: 10 })
dedupeIntervalMs- Minimum time between re-fetches (default: 2000ms)
Return Values
data- Aggregation result (single object for non-grouped, array for grouped)isLoading- True while fetchingerror- Error object if fetch failedrefetch- Manual refetch function
useOsdkMetadata
Stable - import from @osdk/react
Fetch metadata about object types or interfaces.
import { Todo } from "@my/osdk";
import { useOsdkMetadata } from "@osdk/react";
function TodoMetadataViewer() {
const { metadata, loading, error } = useOsdkMetadata(Todo);
if (loading) {
return <div>Loading metadata...</div>;
}
if (error) {
return <div>Error: {error}</div>;
}
return (
<div>
<h2>{metadata?.displayName}</h2>
<p>Description: {metadata?.description}</p>
<h3>Properties:</h3>
<ul>
{Object.entries(metadata?.properties || {}).map(([key, prop]) => (
<li key={key}>
{key}: {prop.dataType.type}
{prop.displayName && ` (${prop.displayName})`}
</li>
))}
</ul>
</div>
);
}
Return Values
metadata- ObjectMetadata or InterfaceMetadata with type informationloading- True while fetching metadataerror- Error message string if fetch failed
Performance Considerations
useObjectSet
- Set operations (union, intersect, subtract) are performed on the server
- Each unique combination of options creates a separate cache entry
- Using
pivotTocreates a new query for the linked type - Consider using
pageSizeto limit initial data load
Derived Properties
- Computed server-side, so no client-side overhead
- Complex derived properties with many link traversals may be slower
- Filtering on derived properties happens server-side
Aggregations
- Aggregations are always computed server-side
- Use
whereto reduce the dataset before aggregation - Group by produces array results that may be large