Skip to main content

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
note

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],
});
The $ function

The $ 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>;
}

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 objects
  • withProperties - Add derived/computed properties
  • union - Combine with other ObjectSets
  • intersect - Find common objects with other ObjectSets
  • subtract - Remove objects that exist in other ObjectSets
  • pivotTo - Traverse to linked objects (changes result type)
  • pageSize - Number of objects per page
  • orderBy - Sort order
  • dedupeIntervalMs - Minimum time between re-fetches (default: 2000ms)
  • streamUpdates - Enable real-time websocket updates (default: false)
  • autoFetchMore - Auto-fetch additional pages
  • enabled - Enable/disable the query

Return Values

  • data - Array of objects with derived properties
  • isLoading - True while fetching
  • error - Error object if fetch failed
  • fetchMore - Function to load next page
  • objectSet - 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 aggregation
  • withProperties - Add derived properties for computed values
  • aggregate - 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 fetching
  • error - Error object if fetch failed
  • refetch - 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 information
  • loading - True while fetching metadata
  • error - 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 pivotTo creates a new query for the linked type
  • Consider using pageSize to 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 where to reduce the dataset before aggregation
  • Group by produces array results that may be large