query() creates a reactive data-fetching primitive. Pass it a SDK method call and it returns reactive properties (data, loading, error) that auto-update in the DOM. It supports SSR data pre-fetching and integrates with the entity store for cross-component cache invalidation.
query()
Pass a generated SDK method call. The cache key and types are inferred automatically from the SDK:
import { query } from '@vertz/ui/query';
function TaskList() {
const tasks = query(api.tasks.list());
return (
<div>
{tasks.loading && <p>Loading...</p>}
{tasks.error && <p>Error: {tasks.error.message}</p>}
{tasks.data?.items.map((task) => (
<div key={task.id}>{task.title}</div>
))}
</div>
);
}
SDK methods return a QueryDescriptor — an object that carries the fetch function, a deterministic cache key, and entity metadata. query() unwraps the Result for you: if the SDK call succeeds, data gets the value; if it fails, error gets the error. See the Generated SDK reference for details on how SDK methods and the Result type work.
Signature
function query<T, E>(
descriptor: QueryDescriptor<T, E>,
options?: Omit<QueryOptions<T>, 'key'>,
): QueryResult<T, E>;
QueryResult
The returned object has reactive properties that auto-update in the DOM — the compiler handles reactivity for you.
| Property | Type | Description |
|---|
data | T | undefined | Resolved data, or undefined while loading |
loading | boolean | true during the initial fetch |
revalidating | boolean | true during background refetch (stale-while-revalidate) |
error | E | undefined | Error from the last failed fetch |
idle | boolean | true when the query has never fetched (e.g. thunk returned null) |
refetch() | () => void | Re-execute the query (shows loading state) |
revalidate() | () => void | Background refetch (keeps current data visible) |
dispose() | () => void | Cancel the query and clean up subscriptions |
refetch() resets data to undefined and shows loading state. revalidate() keeps the current
data visible while fetching fresh data in the background (stale-while-revalidate pattern).
QueryOptions
| Option | Type | Default | Description |
|---|
key | string | — | Cache key (auto-inferred from SDK descriptors) |
initialData | T | — | Initial data to use before the first fetch completes |
debounce | number | — | Debounce interval in ms before executing |
cache | CacheStore<T> | — | Custom cache store |
ssrTimeout | number | — | Max time to wait for the query during SSR (ms) |
refetchInterval | number | false | (data, iteration) => number | false | false | Polling interval in ms, or a function for dynamic intervals |
invalidate()
Force all queries with a matching key to refetch.
import { invalidate } from '@vertz/ui/query';
// After a mutation succeeds, invalidate the list
const result = await api.tasks.create({ title: 'New task' });
if (result.ok) {
invalidate('task-list');
}
Signature
function invalidate(key: string): void;
SSR support
Queries execute during SSR automatically. The server waits for queries to resolve (up to ssrTimeout), serializes the data, and the client hydrates without re-fetching.
const tasks = query(api.tasks.list(), { ssrTimeout: 500 });
// Server: waits up to 500ms for data, injects into HTML
// Client: hydrates with server data, no loading flash
Types
interface QueryOptions<T> {
initialData?: T;
debounce?: number;
key?: string;
cache?: CacheStore<T>;
ssrTimeout?: number;
refetchInterval?: number | false | ((data: T | undefined, iteration: number) => number | false);
}
interface QueryResult<T, E = unknown> {
readonly data: T | undefined;
readonly loading: boolean;
readonly revalidating: boolean;
readonly error: E | undefined;
readonly idle: boolean;
refetch: () => void;
revalidate: () => void;
dispose: () => void;
}
interface CacheStore<T> {
get(key: string): T | undefined;
set(key: string, value: T): void;
has(key: string): boolean;
clear(): void;
}