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
Package structure
Section titled “Package structure”Directory@granit/querying/ Types, API functions, serialization, operator utilities (framework-agnostic)
- @granit/react-querying QueryProvider, pagination hooks, query endpoint, smart filter, saved views
| Package | Role | Depends on |
|---|---|---|
@granit/querying | Filter/sort/pagination types, API functions, query serialization, operator utilities | axios, @granit/utils |
@granit/react-querying | QueryProvider, 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> );}import { fetchPage, fetchQueryMeta, serializeQueryParams } from '@granit/querying';import type { QueryParams, PagedResult, FilterEntry } from '@granit/querying';TypeScript SDK
Section titled “TypeScript SDK”Query parameters
Section titled “Query parameters”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;}Filter system
Section titled “Filter system”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:
| Type | Operators |
|---|---|
String | Eq, Contains, StartsWith, EndsWith, In |
Int32, Decimal, Double | Eq, Gt, Gte, Lt, Lte, In, Between |
DateTime, DateOnly | Eq, Gt, Gte, Lt, Lte, Between |
Boolean | Eq |
Guid, Enums | Eq, In |
Result types
Section titled “Result types”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[];}Query metadata
Section titled “Query metadata”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[];}Saved views
Section titled “Saved views”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;}SmartFilter types
Section titled “SmartFilter types”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 }[];}API functions
Section titled “API functions”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 CRUDfunction 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>;Query serialization
Section titled “Query serialization”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"Operator utilities
Section titled “Operator utilities”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[];React bindings
Section titled “React bindings”QueryProvider
Section titled “QueryProvider”<QueryProvider config={{ client: api, basePath: '/api/v1/patients' }}> {children}</QueryProvider>useQueryEndpoint<T>(options?)
Section titled “useQueryEndpoint<T>(options?)”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.
usePagination<T>(options)
Section titled “usePagination<T>(options)”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;}useInfiniteScroll<T>(options)
Section titled “useInfiniteScroll<T>(options)”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;}useQueryMeta()
Section titled “useQueryMeta()”Fetches and caches query metadata (staleTime: Infinity — stable per deployment).
function useQueryMeta(): UseQueryResult<QueryMetadata>;useSavedViews()
Section titled “useSavedViews()”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>;}useSmartFilter(options?)
Section titled “useSmartFilter(options?)”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;}Public API summary
Section titled “Public API summary”| Category | Key exports | Package |
|---|---|---|
| Query types | QueryParams, FilterEntry, SortEntry, FilterOperator, PagedResult<T>, GroupedResult<T> | @granit/querying |
| Metadata | QueryMetadata, ColumnDefinition, FilterableField, SortableField | @granit/querying |
| Saved views | SavedViewSummary, CreateSavedViewRequest, UpdateSavedViewRequest | @granit/querying |
| SmartFilter | FilterToken, FilterSuggestion, SmartFilterPhase | @granit/querying |
| API functions | fetchPage(), fetchGrouped(), fetchQueryMeta(), saved views CRUD | @granit/querying |
| Serialization | serializeQueryParams(), parseQueryParams() | @granit/querying |
| Operators | inferOperators(), STRING_OPERATORS, OPERATOR_LABELS | @granit/querying |
| Provider | QueryProvider, useQueryConfig(), buildQueryKey() | @granit/react-querying |
| Query hook | useQueryEndpoint() | @granit/react-querying |
| Pagination | usePagination(), useInfiniteScroll() | @granit/react-querying |
| Metadata hook | useQueryMeta() | @granit/react-querying |
| Saved views hook | useSavedViews() | @granit/react-querying |
| Smart filter | useSmartFilter() | @granit/react-querying |
See also
Section titled “See also”- Granit.Querying module — .NET whitelist-first querying with expression trees
- Notifications — Uses
useInfiniteScrollfrom this package for the notification inbox - Data Exchange — Uses
@granit/utilsfor shared formatting