# jg-tanstack-query

> TanStack Query with safe defaults, opt-in IndexedDB persistence, and a sign-out clear that waits for the cache to finish restoring.

- **Version:** `0.2.0`
- **Runtimes:** Browser
- **Install:** `npm install jg-tanstack-query`
- **On npm:** [jg-tanstack-query](https://www.npmjs.com/package/jg-tanstack-query)
- **Source:** [packages/jg-tanstack-query](https://github.com/jesusgraterol/jg-packages/tree/main/packages/jg-tanstack-query)
- **Recipes:** [jg-tanstack-query recipes](https://jg-packages.jesusgraterol.dev/packages/jg-tanstack-query/recipes/index.md)

## Usage

Create the client and its persister once, outside any component, and provide them at the root:

```ts
import { createElement } from 'react';
import {
  createIndexedDbPersister,
  createQueryClient,
  PersistedQueryProvider,
} from 'jg-tanstack-query';

declare const App: () => null;

export const queryClient = createQueryClient();
export const persister = createIndexedDbPersister({
  databaseName: 'app-query-cache',
  storeName: 'queries',
  key: 'query-client',
});

export const Root = () =>
  createElement(
    PersistedQueryProvider,
    { client: queryClient, persister, buster: 'v1' },
    createElement(App),
  );
```

- **The client** never retries a query or a mutation, so an error shows at once and a failed write never repeats, and doesn't refetch when the window regains focus. Unused data stays in memory for `maxAgeSeconds`, 10 days by default, as long as it stays persisted.
- **Persistence is opt-in:** only a query with `meta: { persist: true }` is saved, once it succeeds, so private or short-lived data never reaches the disk. Mutations are never saved, since one replayed after a reload could act twice. Change `buster` when a release changes a persisted query's data, so older copies are thrown away.
- **Where IndexedDB is missing**, such as in some private windows, the provider provides the client without persistence, and the persister does nothing.

### Signing out

```ts
import { clearQueryCache } from 'jg-tanstack-query';

declare const queryClient: Parameters<typeof clearQueryCache>[0];
declare const persister: Parameters<typeof clearQueryCache>[1];

export const signOut = async (): Promise<void> => {
  await clearQueryCache(queryClient, persister);
};
```

`clearQueryCache` waits for the client's restoration from its persisted copy, so restored data can't come back after the clear. It then clears the memory and removes the persisted copy, and concurrent calls share one clear.

### Paging after a first page

When one query loads a whole screen, including the first page of a list, `useSeededInfiniteQuery` pages through the rest of the list without fetching that first page again:

```ts
import { useQuery } from '@tanstack/react-query';
import { useSeededInfiniteQuery } from 'jg-tanstack-query';

const screen = useQuery({ queryKey: ['dashboard'], queryFn: fetchDashboard });
const activity = useSeededInfiniteQuery<IActivityPage, string | null>({
  queryKey: ['dashboard', 'activity'],
  firstPage: screen.data?.activity,
  firstPageUpdatedAt: screen.dataUpdatedAt,
  initialPageParam: null,
  getNextPageParam: (page) => page.nextCursor,
  continuationQueryFn: (cursor, signal) => fetchActivity(cursor, signal),
});
// activity.data.pages, activity.fetchNextPage(), activity.hasNextPage
```

Each `firstPageUpdatedAt` and first page form a generation. When the screen's query refetches, the list starts over from its new first page, any page in flight is aborted, and a late page of an older generation is dropped. Until the generation is published, the result is loading, with no data. Components using the same key share one generation, so one unmounting aborts nothing. `fetchNextPage` and `refetch`, which retries only the page that failed, keep their identity. The query never fetches by itself and is never persisted; `gcTimeSeconds`, 0 by default, keeps its pages after the last component unmounts. Something that forces it to fetch before the first page arrives rejects with `FIRST_PAGE_NOT_READY`.

### Query state

The query-state helpers read the flags of any query, or of `useSeededInfiniteQuery`'s result: `isQueryLoading`, `isQueryReadyForInitialDisplay`, which holds back stale cached data until it's refetched after mount, `canQueryLoadMore`, `isQueryLoadingMore`, and `isQueryResultDataAvailable`, for a list of queries.

### Query boundaries

`useQueryBoundary` decides what a screen shows around the queries its content needs, and leaves the drawing to the app:

```ts
import { useQueryBoundary } from 'jg-tanstack-query';

const boundary = useQueryBoundary({
  queries: [project, members],
  resolutionKey: projectId, // a new key hides the content until its queries resolve
  isAccessDenied: (error) => getErrorCode(error) === 'ACCESS_DENIED',
});
// { canRenderContent, isLoaderVisible, hasError, errorPresentation, failedQueries,
//   loadMoreQuery, retryFailed }
```

The content waits until every query has data for the key that's fresh, or fetched since mount, so cached data from an earlier visit never shows as current. Once it shows, refetches, pagination, and background errors keep it on screen, with `errorPresentation: 'retained'`; an error before then, or one that leaves a query without data, is `blocking`. A new key, or an error `isAccessDenied` recognizes, hides the content in the same render, and after an access loss it returns only once every query settles with current data, even when `isInitialResolutionApproved` says the app approved the key's data before. `loadMoreQuery` is the first query when it pages and the content shows, and `retryFailed` refetches every failed query; it and the result keep their identity while nothing changes. RECIPES.md has an unstyled boundary component.

### Errors

| Code                   | When                                                                      |
| ---------------------- | ------------------------------------------------------------------------- |
| `FIRST_PAGE_NOT_READY` | A seeded infinite query was forced to fetch before its first page arrived |

## <a id="api-reference"></a>jg-tanstack-query API reference

Every export of `jg-tanstack-query@0.2.0`, 24 in all, with its signature, its errors, and an example. It's generated from the package's TSDoc.

- Functions: [`canQueryLoadMore`](#api-canQueryLoadMore), [`clearQueryCache`](#api-clearQueryCache), [`createIndexedDbPersister`](#api-createIndexedDbPersister), [`createQueryClient`](#api-createQueryClient), [`isQueryLoading`](#api-isQueryLoading), [`isQueryLoadingMore`](#api-isQueryLoadingMore), [`isQueryReadyForInitialDisplay`](#api-isQueryReadyForInitialDisplay), [`isQueryResultDataAvailable`](#api-isQueryResultDataAvailable), [`PersistedQueryProvider`](#api-PersistedQueryProvider), [`useQueryBoundary`](#api-useQueryBoundary), [`useSeededInfiniteQuery`](#api-useSeededInfiniteQuery)
- Constants: [`DEFAULT_QUERY_MAX_AGE_SECONDS`](#api-DEFAULT_QUERY_MAX_AGE_SECONDS), [`ERRORS`](#api-ERRORS)
- Types: [`IIndexedDbPersisterOptions`](#api-IIndexedDbPersisterOptions), [`IPersistedQueryProviderProps`](#api-IPersistedQueryProviderProps), [`IQueryBoundaryQuery`](#api-IQueryBoundaryQuery), [`IQueryBoundaryState`](#api-IQueryBoundaryState), [`IQueryClientOptions`](#api-IQueryClientOptions), [`IQueryErrorPresentation`](#api-IQueryErrorPresentation), [`IQueryExecutionState`](#api-IQueryExecutionState), [`IQueryInitialResolutionState`](#api-IQueryInitialResolutionState), [`ISeededInfiniteQueryResult`](#api-ISeededInfiniteQueryResult), [`IUseQueryBoundaryOptions`](#api-IUseQueryBoundaryOptions), [`IUseSeededInfiniteQueryOptions`](#api-IUseSeededInfiniteQueryOptions)

### <a id="api-functions"></a>Functions

#### <a id="api-canQueryLoadMore"></a>`canQueryLoadMore`

*Function*

Checks whether an infinite query has another page to load, and isn't failing.

```ts
canQueryLoadMore(query: IQueryExecutionState): boolean
```

**Parameters**

- `query` `IQueryExecutionState`: The query's state, such as a useInfiniteQuery result.

**Returns** `boolean`: Whether another page can be requested.

**Example**

```ts
import { canQueryLoadMore } from 'jg-tanstack-query';

canQueryLoadMore({ isError: false, hasNextPage: true }); // true
```

#### <a id="api-clearQueryCache"></a>`clearQueryCache`

*Function*

Clears a client's cached data, in memory and persisted, such as at sign-out, so the next person on the device sees none of it. It first waits for the client's restoration from its persisted copy, so restored data can't come back after the clear. Concurrent calls share one clear.

```ts
clearQueryCache(client: QueryClient, persister: Persister): Promise<void>
```

**Parameters**

- `client` `QueryClient`: The client.
- `persister` `Persister`: The client's persister.

**Returns** `Promise<void>`: A promise that resolves once the persisted copy is removed too.

**Throws**

- The persister's own error, when it can't remove the persisted copy, after the memory is cleared.

**Example**

```ts
import { clearQueryCache, createIndexedDbPersister, createQueryClient } from 'jg-tanstack-query';

const queryClient = createQueryClient();
const persister = createIndexedDbPersister({
  databaseName: 'app-query-cache',
  storeName: 'queries',
  key: 'query-client',
});

export const signOut = async (): Promise<void> => {
  await clearQueryCache(queryClient, persister);
};
```

#### <a id="api-createIndexedDbPersister"></a>`createIndexedDbPersister`

*Function*

Creates a persister that keeps a TanStack Query client's dehydrated copy in IndexedDB, through idb-keyval. It opens the database on first use, not when it's created, and does nothing where IndexedDB is missing.

```ts
createIndexedDbPersister(options: IIndexedDbPersisterOptions): Persister
```

**Parameters**

- `options` `IIndexedDbPersisterOptions`: The database, the store, and the key.

**Returns** `Persister`: The persister.

**Example**

```ts
import { createIndexedDbPersister } from 'jg-tanstack-query';

export const persister = createIndexedDbPersister({
  databaseName: 'app-query-cache',
  storeName: 'queries',
  key: 'query-client',
});
```

#### <a id="api-createQueryClient"></a>`createQueryClient`

*Function*

Creates a TanStack Query client with safe defaults: queries and mutations never retry, a focused window doesn't refetch, and unused data stays in memory as long as it stays persisted. Create it once, outside any component.

```ts
createQueryClient(options?: IQueryClientOptions): QueryClient
```

**Parameters**

- `options?` `IQueryClientOptions`: How long unused data stays in memory. Defaults to `{}`.

**Returns** `QueryClient`: The client.

**Example**

```ts
import { createQueryClient } from 'jg-tanstack-query';

export const queryClient = createQueryClient();
```

#### <a id="api-isQueryLoading"></a>`isQueryLoading`

*Function*

Checks whether a query is loading: it has no data yet, or is fetching its first.

```ts
isQueryLoading(query: IQueryExecutionState): boolean
```

**Parameters**

- `query` `IQueryExecutionState`: The query's state, such as a useQuery result.

**Returns** `boolean`: Whether it's loading or pending.

**Example**

```ts
import { isQueryLoading } from 'jg-tanstack-query';

isQueryLoading({ isError: false, isPending: true }); // true
```

#### <a id="api-isQueryLoadingMore"></a>`isQueryLoadingMore`

*Function*

Checks whether an infinite query is fetching its next page.

```ts
isQueryLoadingMore(query: IQueryExecutionState): boolean
```

**Parameters**

- `query` `IQueryExecutionState`: The query's state, such as a useInfiniteQuery result.

**Returns** `boolean`: Whether a next page is in flight.

**Example**

```ts
import { isQueryLoadingMore } from 'jg-tanstack-query';

isQueryLoadingMore({ isError: false, isFetchingNextPage: true }); // true
```

#### <a id="api-isQueryReadyForInitialDisplay"></a>`isQueryReadyForInitialDisplay`

*Function*

Checks whether a query's data can be shown when a screen first renders: it didn't fail, and its data is fresh, or was fetched since the component mounted. Stale data from the cache waits for the refetch, so a screen never shows data from a previous visit as current.

```ts
isQueryReadyForInitialDisplay(query: IQueryInitialResolutionState): boolean
```

**Parameters**

- `query` `IQueryInitialResolutionState`: The query's state, such as a useQuery result.

**Returns** `boolean`: Whether its data is fit for the first display.

**Example**

```ts
import { isQueryReadyForInitialDisplay } from 'jg-tanstack-query';

isQueryReadyForInitialDisplay({ isError: false, isStale: true, isFetchedAfterMount: false }); // false
```

#### <a id="api-isQueryResultDataAvailable"></a>`isQueryResultDataAvailable`

*Function*

Checks whether every query has data to show: none failed, and none is loading.

```ts
isQueryResultDataAvailable(queries: readonly IQueryExecutionState[]): boolean
```

**Parameters**

- `queries` `readonly IQueryExecutionState[]`: The queries' states.

**Returns** `boolean`: Whether all of them have data.

**Example**

```ts
import { isQueryResultDataAvailable } from 'jg-tanstack-query';

isQueryResultDataAvailable([{ isError: false }, { isError: false, isPending: true }]); // false
```

#### <a id="api-PersistedQueryProvider"></a>`PersistedQueryProvider`

*Function*

Provides a TanStack Query client that restores its persisted copy on start, and persists the queries that opt in with `meta: { persist: true }`, never mutations. Where IndexedDB is missing, it provides the client without persistence.

```ts
PersistedQueryProvider(props: IPersistedQueryProviderProps): ReactElement
```

**Parameters**

- `props` `IPersistedQueryProviderProps`: The client, its persister, the copy's version, its maximum age, and the app.

**Returns** `ReactElement`: The provider.

**Example**

```ts
import { createElement } from 'react';
import { createIndexedDbPersister, createQueryClient, PersistedQueryProvider } from 'jg-tanstack-query';

declare const App: () => null;

const client = createQueryClient();
const persister = createIndexedDbPersister({
  databaseName: 'app-query-cache',
  storeName: 'queries',
  key: 'query-client',
});

export const Root = () =>
  createElement(PersistedQueryProvider, { client, persister, buster: 'v1' }, createElement(App));
```

#### <a id="api-useQueryBoundary"></a>`useQueryBoundary`

*Function*

Decides what a screen shows around the queries its content needs, without rendering anything, so any design system can draw the loader, the error, and the content.

The content waits until every query has data for the current `resolutionKey` that's fresh, or fetched since mount, so stale cached data never shows as current. Once it shows, refetches, pagination, and background errors keep it on screen, with `errorPresentation: 'retained'`. A new key, or an error `isAccessDenied` recognizes, hides it in the same render; after an access loss, it returns only once every query settles with current data, even with `isInitialResolutionApproved`.

```ts
useQueryBoundary<TQuery extends IQueryBoundaryQuery>(
  options: IUseQueryBoundaryOptions<TQuery>,
): IQueryBoundaryState<TQuery>
```

**Parameters**

- `options` `IUseQueryBoundaryOptions<TQuery>`: The queries, the key of what they show, whether the app already approved its data, and how to recognize a lost access.

**Returns** `IQueryBoundaryState<TQuery>`: Whether to render the content or a loader, the error state and the failed queries, the query for a load-more control, and a stable retryFailed. The result keeps its identity while none of it changes.

**Example**

```ts
import { useQuery } from '@tanstack/react-query';
import { useQueryBoundary } from 'jg-tanstack-query';

declare const fetchProject: (id: string) => Promise<{ name: string }>;

export const useProjectBoundary = (projectId: string) => {
  const project = useQuery({
    queryKey: ['project', projectId],
    queryFn: () => fetchProject(projectId),
  });
  const boundary = useQueryBoundary({ queries: [project], resolutionKey: projectId });
  return { project, boundary };
};
```

#### <a id="api-useSeededInfiniteQuery"></a>`useSeededInfiniteQuery`

*Function*

Pages through a list whose first page another query already fetched, such as one that loads a whole screen, without fetching that page again. The first page is published into an infinite query under `queryKey`, and `continuationQueryFn` fetches only the pages after it.

Each `firstPageUpdatedAt` and first page form a generation. A new generation cancels any page in flight and starts over from the new first page, and an older publication that finishes late never replaces a newer one. Components that use the same key share one generation and its pages, so one unmounting aborts nothing. Until the current generation is published, the result is loading, with no data. The query never fetches by itself and is never persisted.

```ts
useSeededInfiniteQuery<TPage, TPageParam>(
  options: IUseSeededInfiniteQueryOptions<TPage, TPageParam>,
): ISeededInfiniteQueryResult<TPage, TPageParam>
```

**Parameters**

- `options` `IUseSeededInfiniteQueryOptions<TPage, TPageParam>`: The key, the first page and when it was fetched, how to fetch the pages after it, and the cache settings.

**Returns** `ISeededInfiniteQueryResult<TPage, TPageParam>`: A query result for boundaries and load-more controls, whose fetchNextPage and refetch keep their identity.

**Throws**

- `FIRST_PAGE_NOT_READY`: The first page isn't ready yet.

**Example**

```ts
import { useQuery } from '@tanstack/react-query';
import { useSeededInfiniteQuery } from 'jg-tanstack-query';

type IPage = { items: string[]; nextCursor: string | null };
declare const fetchScreen: () => Promise<{ activity: IPage }>;
declare const fetchActivity: (cursor: string, signal: AbortSignal) => Promise<IPage>;

export const useActivity = () => {
  const screen = useQuery({ queryKey: ['dashboard'], queryFn: fetchScreen });
  return useSeededInfiniteQuery<IPage, string | null>({
    queryKey: ['dashboard', 'activity'],
    firstPage: screen.data?.activity,
    firstPageUpdatedAt: screen.dataUpdatedAt,
    initialPageParam: null,
    getNextPageParam: (page) => page.nextCursor,
    continuationQueryFn: (cursor, signal) => fetchActivity(cursor as string, signal),
  });
};
```

### <a id="api-constants"></a>Constants

#### <a id="api-DEFAULT_QUERY_MAX_AGE_SECONDS"></a>`DEFAULT_QUERY_MAX_AGE_SECONDS`

*Constant*

how long a query's data stays cached and persisted by default: 10 days, in seconds

```ts
const DEFAULT_QUERY_MAX_AGE_SECONDS: 864000
```

#### <a id="api-ERRORS"></a>`ERRORS`

*Constant*

the codes for every error thrown by the package

```ts
const ERRORS: { FIRST_PAGE_NOT_READY: "FIRST_PAGE_NOT_READY" }
```

### <a id="api-types"></a>Types

#### <a id="api-IIndexedDbPersisterOptions"></a>`IIndexedDbPersisterOptions`

*Type*

where createIndexedDbPersister keeps a client's persisted copy

```ts
type IIndexedDbPersisterOptions = {
    databaseName: string;
    storeName: string;
    key: string;
};
```

**Members**

- `databaseName: string` (property): the IndexedDB database's name, such as 'app-query-cache'
- `storeName: string` (property): the object store's name in that database
- `key: string` (property): the key the persisted copy is stored under

#### <a id="api-IPersistedQueryProviderProps"></a>`IPersistedQueryProviderProps`

*Type*

what PersistedQueryProvider takes

```ts
type IPersistedQueryProviderProps = {
    client: QueryClient;
    persister: Persister;
    buster: string;
    maxAgeSeconds?: number;
    children?: ReactNode;
};
```

**Members**

- `client: QueryClient` (property): the client, from createQueryClient
- `persister: Persister` (property): where the client's persisted copy lives, from createIndexedDbPersister
- `buster: string` (property): the persisted copy's version: change it to throw away copies an older release wrote
- `maxAgeSeconds: number` (property): how long a persisted copy stays usable, which the client's maxAgeSeconds must be at least. Defaults to DEFAULT_QUERY_MAX_AGE_SECONDS
- `children: ReactNode` (property)

#### <a id="api-IQueryBoundaryQuery"></a>`IQueryBoundaryQuery`

*Type*

a query a boundary watches: a useQuery or useInfiniteQuery result, or useSeededInfiniteQuery's

```ts
type IQueryBoundaryQuery = IQueryExecutionState & IQueryInitialResolutionState & {
    data: unknown;
    error?: unknown;
    isFetching?: boolean | undefined;
    isRefetching?: boolean | undefined;
    refetch?: (() => unknown) | undefined;
    fetchNextPage?: (() => unknown) | undefined;
};
```

#### <a id="api-IQueryBoundaryState"></a>`IQueryBoundaryState`

*Type*

what useQueryBoundary returns

```ts
type IQueryBoundaryState<TQuery extends IQueryBoundaryQuery> = {
    canRenderContent: boolean;
    isLoaderVisible: boolean;
    hasError: boolean;
    errorPresentation: IQueryErrorPresentation;
    failedQueries: TQuery[];
    loadMoreQuery: TQuery | undefined;
    retryFailed: () => Promise<void>;
};
```

**Members**

- `canRenderContent: boolean` (property): whether to render the content: the key resolved, every query has data, and no access is lost
- `isLoaderVisible: boolean` (property): whether to show a loader in place of the content
- `hasError: boolean` (property): whether a query failed
- `errorPresentation: IQueryErrorPresentation` (property): whether the error replaces the content, or shows beside content that's still current
- `failedQueries: TQuery[]` (property): the queries that failed
- `loadMoreQuery: TQuery | undefined` (property): the first query, when it pages and the content renders, for a load-more control
- `retryFailed: () => Promise<void>` (property): refetches every failed query, resolving once all of them settle. Its identity never changes

#### <a id="api-IQueryClientOptions"></a>`IQueryClientOptions`

*Type*

what createQueryClient takes

```ts
type IQueryClientOptions = {
    maxAgeSeconds?: number;
};
```

**Members**

- `maxAgeSeconds: number` (property): how long unused query data stays in memory, which must be at least how long it stays persisted. Defaults to DEFAULT_QUERY_MAX_AGE_SECONDS

#### <a id="api-IQueryErrorPresentation"></a>`IQueryErrorPresentation`

*Type*

how an error shows: in place of the content, or beside content that's still current

```ts
type IQueryErrorPresentation = 'blocking' | 'retained';
```

#### <a id="api-IQueryExecutionState"></a>`IQueryExecutionState`

*Type*

the flags of a query, infinite or not, that the loading and load-more helpers read

```ts
type IQueryExecutionState = {
    isError: boolean;
    isLoading?: boolean | undefined;
    isPending?: boolean | undefined;
    hasNextPage?: boolean | undefined;
    isFetchingNextPage?: boolean | undefined;
};
```

**Members**

- `isError: boolean` (property)
- `isLoading: boolean` (property)
- `isPending: boolean` (property)
- `hasNextPage: boolean` (property)
- `isFetchingNextPage: boolean` (property)

#### <a id="api-IQueryInitialResolutionState"></a>`IQueryInitialResolutionState`

*Type*

the flags that decide whether a query's cached data is fit for the first display after mount

```ts
type IQueryInitialResolutionState = {
    isError: boolean;
    isFetchedAfterMount: boolean;
    isStale: boolean;
};
```

**Members**

- `isError: boolean` (property)
- `isFetchedAfterMount: boolean` (property)
- `isStale: boolean` (property)

#### <a id="api-ISeededInfiniteQueryResult"></a>`ISeededInfiniteQueryResult`

*Type*

What useSeededInfiniteQuery returns: a subset of TanStack's infinite query result, shaped so query boundaries and load-more controls read it like any other query. Until the current generation is published, it's loading, with no data. fetchNextPage and refetch keep their identity across renders.

```ts
type ISeededInfiniteQueryResult<TPage, TPageParam> = {
    data: InfiniteData<TPage, TPageParam> | undefined;
    error: Error | null;
    fetchNextPage: () => Promise<unknown>;
    hasNextPage: boolean;
    isError: boolean;
    isFetchedAfterMount: boolean;
    isFetching: boolean;
    isFetchingNextPage: boolean;
    isLoading: boolean;
    isPending: boolean;
    isReady: boolean;
    isRefetching: boolean;
    isStale: boolean;
    isSuccess: boolean;
    refetch: () => Promise<unknown>;
};
```

**Members**

- `data: InfiniteData<TPage, TPageParam> | undefined` (property): the pages, once the current generation is published
- `error: Error | null` (property): the error of the last continuation that failed, or null
- `fetchNextPage: () => Promise<unknown>` (property): fetches the next page, when there is one and none is being fetched
- `hasNextPage: boolean` (property): whether there's another page
- `isError: boolean` (property): whether the last continuation failed
- `isFetchedAfterMount: boolean` (property): whether the current generation is published, which a boundary reads as fetched after mount
- `isFetching: boolean` (property): whether a continuation is in flight
- `isFetchingNextPage: boolean` (property): whether a continuation is in flight
- `isLoading: boolean` (property): whether the current generation isn't published yet
- `isPending: boolean` (property): whether the current generation isn't published yet
- `isReady: boolean` (property): whether the current generation is published
- `isRefetching: boolean` (property): whether a continuation is in flight
- `isStale: boolean` (property): always false: the first page is as fresh as the query it came from
- `isSuccess: boolean` (property): whether the current generation is published, with its data
- `refetch: () => Promise<unknown>` (property): fetches the failed continuation again, after an error

#### <a id="api-IUseQueryBoundaryOptions"></a>`IUseQueryBoundaryOptions`

*Type*

what useQueryBoundary takes

```ts
type IUseQueryBoundaryOptions<TQuery extends IQueryBoundaryQuery> = {
    queries: readonly TQuery[];
    resolutionKey: string;
    isInitialResolutionApproved?: boolean | undefined;
    isAccessDenied?: ((error: unknown) => boolean) | undefined;
};
```

**Members**

- `queries: readonly TQuery[]` (property): the queries whose data the content shows
- `resolutionKey: string` (property): what the content shows, such as a project's id: a new key hides the content until the queries resolve for it
- `isInitialResolutionApproved: boolean` (property): whether the app already approved this key's data, such as before the boundary remounted, so cached data shows at once. It never brings content back after an access loss
- `isAccessDenied: (error: unknown) => boolean` (property): whether a query's error means the user lost access, such as a 403, which hides the content at once, even approved, until every query succeeds again. Defaults to never

#### <a id="api-IUseSeededInfiniteQueryOptions"></a>`IUseSeededInfiniteQueryOptions`

*Type*

what useSeededInfiniteQuery takes

```ts
type IUseSeededInfiniteQueryOptions<TPage, TPageParam> = {
    queryKey: QueryKey;
    firstPage: TPage | undefined;
    firstPageUpdatedAt: number;
    initialPageParam: TPageParam;
    getNextPageParam: (lastPage: TPage, pages: TPage[]) => TPageParam | undefined | null;
    continuationQueryFn: (pageParam: TPageParam, signal: AbortSignal) => Promise<TPage>;
    gcTimeSeconds?: number | undefined;
    maxPages?: number | undefined;
    meta?: QueryMeta | undefined;
};
```

**Members**

- `queryKey: QueryKey` (property): the key of the query that holds the pages, which no other query may use
- `firstPage: TPage | undefined` (property): the first page, which another query, such as one for a whole screen, already fetched
- `firstPageUpdatedAt: number` (property): when the first page was fetched, such as that query's dataUpdatedAt. A new value, or a new first page, starts a new generation: the pages loaded so far are dropped
- `initialPageParam: TPageParam` (property): the page param the first page was fetched with
- `getNextPageParam: (lastPage: TPage, pages: TPage[]) => TPageParam | undefined | null` (property): gives the next page's param, or undefined or null when there are no more pages
- `continuationQueryFn: (pageParam: TPageParam, signal: AbortSignal) => Promise<TPage>` (property): fetches every page after the first, aborting when the signal does
- `gcTimeSeconds: number` (property): the seconds the pages stay cached once no component uses them. Defaults to 0
- `maxPages: number` (property): the most pages kept, dropping the oldest ones
- `meta: QueryMeta` (property): the query's meta, always with `persist: false`, so the pages are never persisted
