Paging
Enumerable query proxies expose paging helpers. The backend must implement paging: ordinary automatic pipeline paging uses IQueryable<T>, while observable providers can apply paging themselves (for example MongoDB Observe() uses the query context’s Skip/Limit).
How Paging Works
Section titled “How Paging Works”When the backend returns IQueryable<T>, the query pipeline applies .Skip() and .Take() on the server so only the requested page is fetched. Generated proxies expose useWithPaging and useSuspenseWithPaging for this flow.
For backend implementation details, see Controller-based Paging and Model-bound Paging.
Enabling Paging
Section titled “Enabling Paging”Use useWithPaging instead of use, passing a page size. This example assumes a generated, non-observable AllAccounts query proxy whose items have id, name, and balance; adjust the import to your proxy output path:
import { AllAccounts } from './Accounts/AllAccounts';
export const AccountList = () => { const [result, perform, setSorting, setPage, setPageSize] = AllAccounts.useWithPaging(25);
return ( <> <table> <thead> <tr><th>Name</th><th>Balance</th></tr> </thead> <tbody> {result.data.map(account => ( <tr key={String(account.id)}> <td>{account.name}</td> <td>{account.balance}</td> </tr> ))} </tbody> </table> <p> Page {result.paging.page + 1} of {result.paging.totalPages} ({result.paging.totalItems} total items) </p> <button disabled={result.paging.page === 0} onClick={() => setPage(result.paging.page - 1)}> Previous </button> <button disabled={result.paging.page >= result.paging.totalPages - 1} onClick={() => setPage(result.paging.page + 1)}> Next </button> </> );};Return Tuple for Paged Queries
Section titled “Return Tuple for Paged Queries”For an ordinary query, useWithPaging returns an extended tuple:
| Index | Name | Type | Description |
|---|---|---|---|
| 0 | result | QueryResultWithState<T> | Query result including paging metadata |
| 1 | perform | () => Promise<void> | Re-execute the query |
| 2 | setSorting | (sorting: Sorting) => Promise<void> | Change sort field and direction |
| 3 | setPage | (page: number) => Promise<void> | Navigate to a specific page (zero-based) |
| 4 | setPageSize | (pageSize: number) => Promise<void> | Change number of items per page |
For an observable enumerable query the tuple is [result, setSorting, setPage, setPageSize]; there is no perform delegate. Single-result proxies do not generate paging helpers. Suspense variants preserve these tuple shapes.
Cached observable subscriptions
Section titled “Cached observable subscriptions”In non-Suspense observable hooks, setPage, setPageSize, and setSorting move to a cache entry for the requested paging and sorting. Each new subscription sends those settings to the server. Consumers on different pages or sort orders have independent subscriptions; consumers with matching query arguments, paging and sorting share one.
While the new page is pending, the result shows its cached data if available. Otherwise it shows the query’s default value with isReady: false and isPerforming: true, not the previous page’s rows. Returning to a retained entry reuses its cached result and live subscription without subscribing again.
Ordinary paged queries also reset to the matching entry, but perform a fresh request and mark cached data as isPerforming: true until it settles. Suspense uses separate resource caches. See query instance caching for retention and sharing.
Paging Metadata
Section titled “Paging Metadata”Paging information is available on result.paging:
| Property | Type | Description |
|---|---|---|
page | number | Current zero-based page number |
size | number | Items per page |
totalItems | number | Total items across all pages |
totalPages | number | Total number of pages |
Hook Variants with Paging
Section titled “Hook Variants with Paging”| Hook | Description |
|---|---|
MyQuery.useWithPaging(pageSize) | Standard query with paging |
MyQuery.useSuspenseWithPaging(pageSize) | Suspense-compatible query with paging |
MyObservableQuery.useWithPaging(pageSize) | Observable paging with a subscription for the selected page and sort order |
Parameterized proxies accept (pageSize, args?, sorting?); parameterless enumerable proxies accept (pageSize, sorting?). Match the generated signature rather than inserting an arguments placeholder for a parameterless query.
Sorting with Paging
Section titled “Sorting with Paging”Sorting is independent of paging. Changing sort order fetches the matching ordinary-query result or selects the matching observable subscription. The following ordinary-query fragment shows initial sorting and a dynamic change.
import { Sorting, SortDirection } from '@cratis/arc/queries';
// Initial sortingconst [result] = AllAccounts.use(new Sorting('name', SortDirection.ascending));
// Change sorting dynamicallyconst [paged, perform, setSorting] = AllAccounts.useWithPaging(25);await setSorting(new Sorting('balance', SortDirection.descending));Important Requirement
Section titled “Important Requirement”Ordinary automatic query-pipeline paging requires IQueryable<T>. A materialized IEnumerable<T> or List<T> does not gain automatic server slicing merely because a paging hook exists. An application/provider may explicitly consume paging context, including for observable ISubject results. Verify both returned rows and paging metadata against that provider.