Skip to content

Querying

@granit/querying is the largest frontend package — a framework-agnostic, headless data grid system that mirrors Granit.Querying on the .NET backend. It provides types for filters, sorts, pagination (offset and cursor), grouping, saved views, query serialization, and a SmartFilterBar suggestion engine.

@granit/react-querying wraps everything into React hooks with TanStack Query integration: useQueryEndpoint (full query state machine), usePagination, useInfiniteScroll, useSavedViews, useSmartFilter, and useQueryMeta.

Peer dependencies: axios, @granit/utils, react ^19, @tanstack/react-query ^5

  • Directory@granit/querying/ Types, API functions, serialization, operator utilities (framework-agnostic)
    • @granit/react-querying QueryProvider, pagination hooks, query endpoint, smart filter, saved views
PackageRoleDepends on
@granit/queryingFilter/sort/pagination types, API functions, query serialization, operator utilitiesaxios, @granit/utils
@granit/react-queryingQueryProvider, useQueryEndpoint, usePagination, useInfiniteScroll, useSmartFilter, useSavedViews@granit/querying, @tanstack/react-query, react
import { QueryProvider } from '@granit/react-querying';
import { useQueryEndpoint, useQueryMeta } from '@granit/react-querying';
import { api } from './api-client';
function PatientList() {
return (
<QueryProvider config={{ client: api, basePath: '/api/v1/patients' }}>
<PatientTable />
</QueryProvider>
);
}
interface QueryParams {
readonly page?: number;
readonly pageSize?: number;
readonly cursor?: string; // opaque cursor for keyset pagination
readonly search?: string; // global full-text search
readonly filters?: readonly FilterEntry[];
readonly sort?: readonly SortEntry[];
readonly presets?: Readonly<Record<string, readonly string[]>>;
readonly quickFilters?: readonly string[];
readonly groupBy?: string;
}
type FilterOperator =
| 'Eq' | 'Contains' | 'StartsWith' | 'EndsWith'
| 'Gt' | 'Gte' | 'Lt' | 'Lte'
| 'In' | 'Between';
interface FilterEntry {
readonly field: string;
readonly operator: FilterOperator;
readonly value: string; // comma-separated for In/Between
}
interface SortEntry {
readonly field: string;
readonly direction: 'asc' | 'desc';
}

Operator availability by CLR type:

TypeOperators
StringEq, Contains, StartsWith, EndsWith, In
Int32, Decimal, DoubleEq, Gt, Gte, Lt, Lte, In, Between
DateTime, DateOnlyEq, Gt, Gte, Lt, Lte, Between
BooleanEq
Guid, EnumsEq, In
interface PagedResult<T> {
readonly items: readonly T[];
readonly totalCount: number;
readonly nextCursor?: string;
}
interface GroupedResult<T> {
readonly groups: readonly GroupEntry<T>[];
readonly totalCount: number;
}
interface GroupEntry<T> {
readonly field: string;
readonly value: unknown;
readonly label: string;
readonly count: number;
readonly aggregates?: Readonly<Record<string, unknown>>;
readonly items?: readonly T[];
}
interface QueryMetadata {
readonly columns: readonly ColumnDefinition[];
readonly filterableFields: readonly FilterableField[];
readonly sortableFields: readonly SortableField[];
readonly presetFilterGroups: readonly FilterGroupMeta[];
readonly quickFilters: readonly QuickFilterMeta[];
readonly dateFilters: readonly DateFilterMeta[];
readonly groupByFields: readonly GroupByField[];
readonly pagination: PaginationMeta;
readonly defaultSort?: string;
}
interface ColumnDefinition {
readonly name: string;
readonly label: string;
readonly type: string;
readonly order: number;
readonly isSortable: boolean;
readonly isFilterable: boolean;
readonly isVisible: boolean;
readonly format?: string;
}
interface FilterableField {
readonly name: string;
readonly type: string;
readonly operators: readonly FilterOperator[];
readonly enumValues?: readonly string[];
}
interface SavedViewSummary {
readonly id: string;
readonly name: string;
readonly isShared: boolean;
readonly isDefault: boolean;
}
interface CreateSavedViewRequest {
readonly name: string;
readonly isShared: boolean;
readonly isDefault: boolean;
readonly filterJson?: string;
readonly sortJson?: string;
readonly groupByJson?: string;
readonly visibleColumnsJson?: string;
}
type SmartFilterPhase = 'idle' | 'selectField' | 'selectOperator' | 'enterValue';
interface FilterToken {
readonly id: string;
readonly type: 'filter' | 'preset' | 'quickFilter' | 'search';
readonly label: string;
readonly field?: string;
readonly operator?: FilterOperator;
readonly value?: string;
}
interface FilterSuggestion {
readonly id: string;
readonly type: 'filter' | 'preset' | 'quickFilter' | 'search';
readonly label: string;
readonly description?: string;
readonly field?: string;
readonly operators?: readonly FilterOperator[];
readonly values?: readonly { value: string; label: string }[];
}
function fetchPage<T>(client, basePath, params: QueryParams): Promise<PagedResult<T>>;
function fetchGrouped<T>(client, basePath, params: QueryParams): Promise<GroupedResult<T>>;
function fetchQueryMeta(client, basePath): Promise<QueryMetadata>;
// Saved views CRUD
function fetchSavedViews(client, basePath): Promise<SavedViewSummary[]>;
function createSavedView(client, basePath, request): Promise<SavedViewSummary>;
function updateSavedView(client, basePath, id, request): Promise<SavedViewSummary>;
function deleteSavedView(client, basePath, id): Promise<void>;
function setDefaultSavedView(client, basePath, id): Promise<SavedViewSummary>;

Bookmarkable URLs — serialize query state to URL search params and back.

function serializeQueryParams(params: QueryParams): string;
function parseQueryParams(search: string): QueryParams;
// Example:
serializeQueryParams({
page: 2, pageSize: 20, search: 'dupont',
filters: [{ field: 'Status', operator: 'Eq', value: 'Active' }],
sort: [{ field: 'CreatedAt', direction: 'desc' }],
});
// → "page=2&pageSize=20&search=dupont&filter[Status.Eq]=Active&sort=-CreatedAt"
const STRING_OPERATORS: readonly FilterOperator[];
const NUMBER_OPERATORS: readonly FilterOperator[];
const DATE_OPERATORS: readonly FilterOperator[];
const BOOLEAN_OPERATORS: readonly FilterOperator[];
const ENUM_OPERATORS: readonly FilterOperator[];
const OPERATOR_LABELS: Record<FilterOperator, string>;
function inferOperators(clrType: string): readonly FilterOperator[];
<QueryProvider config={{ client: api, basePath: '/api/v1/patients' }}>
{children}
</QueryProvider>

Main hook — manages the full query state machine (search, filter, sort, pagination, grouping) via useReducer (14 actions) with TanStack Query for data fetching.

interface UseQueryEndpointReturn<T> {
readonly params: QueryParams;
readonly query: UseQueryResult<PagedResult<T>>;
readonly groupedQuery: UseQueryResult<GroupedResult<T>>;
readonly isGrouped: boolean;
// Dispatchers
readonly setPage: (page: number) => void;
readonly setPageSize: (pageSize: number) => void;
readonly setSearch: (search: string) => void;
readonly setFilters: (filters: readonly FilterEntry[]) => void;
readonly addFilter: (filter: FilterEntry) => void;
readonly removeFilter: (field: string, operator?: string) => void;
readonly setSort: (sort: readonly SortEntry[]) => void;
readonly toggleSort: (field: string) => void; // none → asc → desc → none
readonly setPresets: (group: string, names: readonly string[]) => void;
readonly toggleQuickFilter: (name: string) => void;
readonly setGroupBy: (groupBy: string | undefined) => void;
readonly setParams: (params: QueryParams) => void;
readonly reset: () => void;
}

Filter, search, preset, and quick-filter changes automatically reset to page 1.

Classic offset-based pagination with page navigation.

interface UsePaginationReturn<T> {
readonly items: readonly T[];
readonly totalCount: number;
readonly page: number;
readonly totalPages: number;
readonly hasPreviousPage: boolean;
readonly hasNextPage: boolean;
readonly goToPage: (page: number) => void;
readonly nextPage: () => void;
readonly previousPage: () => void;
readonly refresh: () => void;
readonly loading: boolean;
}

Load-more pagination — accumulates items from successive pages.

interface UseInfiniteScrollReturn<T> {
readonly items: readonly T[];
readonly totalCount: number;
readonly hasMore: boolean;
readonly loadMore: () => void;
readonly refresh: () => void;
readonly loading: boolean;
readonly loadingMore: boolean;
}

Fetches and caches query metadata (staleTime: Infinity — stable per deployment).

function useQueryMeta(): UseQueryResult<QueryMetadata>;

Full CRUD for saved views with cache invalidation.

interface UseSavedViewsReturn {
readonly views: UseQueryResult<SavedViewSummary[]>;
readonly create: UseMutationResult<SavedViewSummary, Error, CreateSavedViewRequest>;
readonly update: UseMutationResult<...>;
readonly remove: UseMutationResult<void, Error, string>;
readonly setDefault: UseMutationResult<SavedViewSummary, Error, string>;
}

State machine for the SmartFilterBar omnibox. Manages the flow: field → operator → value, generates contextual suggestions, and outputs FilterEntry[] ready for useQueryEndpoint.

interface UseSmartFilterReturn {
readonly phase: SmartFilterPhase;
readonly tokens: readonly FilterToken[];
readonly suggestions: readonly FilterSuggestion[];
// Extracted from tokens (ready for useQueryEndpoint)
readonly filters: readonly FilterEntry[];
readonly search: string | undefined;
readonly presets: Readonly<Record<string, readonly string[]>>;
readonly quickFilters: readonly string[];
// Actions
readonly setInput: (value: string) => void;
readonly selectField: (field: string) => void;
readonly selectOperator: (operator: FilterOperator) => void;
readonly confirmValue: (value: string) => void;
readonly removeToken: (id: string) => void;
readonly clearAll: () => void;
readonly cancel: () => void;
}
CategoryKey exportsPackage
Query typesQueryParams, FilterEntry, SortEntry, FilterOperator, PagedResult<T>, GroupedResult<T>@granit/querying
MetadataQueryMetadata, ColumnDefinition, FilterableField, SortableField@granit/querying
Saved viewsSavedViewSummary, CreateSavedViewRequest, UpdateSavedViewRequest@granit/querying
SmartFilterFilterToken, FilterSuggestion, SmartFilterPhase@granit/querying
API functionsfetchPage(), fetchGrouped(), fetchQueryMeta(), saved views CRUD@granit/querying
SerializationserializeQueryParams(), parseQueryParams()@granit/querying
OperatorsinferOperators(), STRING_OPERATORS, OPERATOR_LABELS@granit/querying
ProviderQueryProvider, useQueryConfig(), buildQueryKey()@granit/react-querying
Query hookuseQueryEndpoint()@granit/react-querying
PaginationusePagination(), useInfiniteScroll()@granit/react-querying
Metadata hookuseQueryMeta()@granit/react-querying
Saved views hookuseSavedViews()@granit/react-querying
Smart filteruseSmartFilter()@granit/react-querying