> ## Documentation Index
> Fetch the complete documentation index at: https://archie.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# React SDK

> Use @archie/react-sdk to add React hooks, a provider, and components to your app. It handles auth state, GraphQL and REST data fetching, real-time subscriptions, file uploads, and notifications.

`@archie/react-sdk` provides React hooks, a provider, and components for your Archie backend. It builds on [`@archie/js-sdk`](https://www.npmjs.com/package/@archie/js-sdk) and adds reactive auth state, declarative data fetching, real-time subscriptions, file uploads with progress tracking, and route guards.

Use it when you build a React frontend that talks to an Archie project. The package is available on [npm](https://www.npmjs.com/package/@archie/react-sdk).

## Installation

Install the React SDK together with the JavaScript SDK.

```bash theme={null}
npm install @archie/js-sdk @archie/react-sdk
# or
pnpm add @archie/js-sdk @archie/react-sdk
```

<Note>
  Peer dependencies: `react >=18` and `@archie/js-sdk`.
</Note>

## Quick start

Create a client with `createClient`, wrap your app in `ArchieProvider`, and use the hooks in any child component.

```tsx theme={null}
import { createClient } from '@archie/js-sdk';
import { ArchieProvider, useAuth, useQuery, AuthGuard } from '@archie/react-sdk';

const archie = createClient({
  projectId: 'your-project-uuid',
  apiKey: 'anon your-api-key',
  apiUrl: 'https://your-project.archiecore.com',
  environment: 'master',
  retries: 3, // automatic retry with backoff (inherited from js-sdk)
  timeout: 15_000, // request timeout in ms
});

function App() {
  return (
    <ArchieProvider client={archie}>
      <AuthGuard fallback={<LoginPage />} loadingComponent={<Spinner />}>
        <Dashboard />
      </AuthGuard>
    </ArchieProvider>
  );
}

function Dashboard() {
  const { user, signOut } = useAuth();
  const { data, isLoading } = useQuery<{ orders: Order[] }>('{ orders { id total status } }');

  return (
    <div>
      <p>Welcome, {user?.email}</p>
      <button onClick={signOut}>Sign out</button>
      {isLoading ? <Spinner /> : <OrderList orders={data?.orders ?? []} />}
    </div>
  );
}
```

## ArchieProvider

`ArchieProvider` makes the Archie client and the auth session available to every hook through React context.

```tsx theme={null}
import { createClient } from '@archie/js-sdk';
import { ArchieProvider } from '@archie/react-sdk';

const archie = createClient({ projectId: '...', apiKey: 'anon ...' });

function App() {
  return (
    <ArchieProvider client={archie}>
      <MyApp />
    </ArchieProvider>
  );
}
```

| Prop | Type | Description |
| - | - | - |
| `client` | `ArchieClient` | Client instance from `createClient()` |
| `children` | `ReactNode` | Child components |

On mount, the provider calls `client.auth.waitForInit()` to restore a persisted session. It then subscribes to `onAuthStateChange` and keeps the session in context up to date. All child hooks read `{ client, session, isLoading }` from this context.

### Client options

The provider uses the options you pass to `createClient()`. Hooks inherit these options automatically.

| Option | Type | Default | Description |
| - | - | - | - |
| `timeout` | `number` | `30000` | Request timeout in ms (0 = disabled) |
| `retries` | `number` | `0` | Max retry attempts for transient errors |
| `retryDelay` | `number` | `200` | Initial backoff delay in ms |
| `fetch` | `typeof fetch` | `globalThis.fetch` | Custom fetch implementation |

Retries cover transient errors (429, 500, 502, 503, 504, and network failures) with exponential backoff and jitter. You can also pass a `logger`, such as `consoleLogger` from `@archie/js-sdk`.

## Hooks

| Hook | Use it to |
| - | - |
| [`useArchieClient`](#usearchieclient) | Get the raw `ArchieClient` instance |
| [`useAuth`](#useauth) | Read auth state and sign users in, up, and out |
| [`useQuery`](#usequery) | Run GraphQL queries with caching and polling |
| [`useMutation`](#usemutation) | Run GraphQL mutations and invalidate queries |
| [`useSubscription`](#usesubscription) | Receive real-time GraphQL updates |
| [`useFileUpload`](#usefileupload) | Upload files with progress tracking |
| [`useRest`](#userest) | Call REST endpoints imperatively |
| [`useRestQuery`](#userestquery) | Fetch custom REST endpoints with GET |
| [`useRestMutation`](#userestmutation) | Send POST, PUT, PATCH, or DELETE requests |
| [`useNotifications`](#usenotifications) | Show live in-app notifications and manage read state |

### useArchieClient

`useArchieClient` returns the raw `ArchieClient` instance. Use it for advanced cases outside the provided hooks.

```tsx theme={null}
const archie = useArchieClient();

// Direct access to any module
const jwks = await archie.auth.getJWKS();
```

<Warning>
  `useArchieClient` throws if you call it outside `<ArchieProvider>`.
</Warning>

### useAuth

`useAuth` exposes the full authentication state and operations. All async methods return `{ data/session, error }` and never throw.

```tsx theme={null}
const {
  user, // User | null
  session, // Session | null
  isLoading, // boolean, true until the initial session check completes
  isAuthenticated, // boolean, shorthand for session !== null
  signIn, // (params) => Promise<{ session, error }>
  signUp, // (params) => Promise<{ data, error }>
  exchangeCode, // ({ code }) => Promise<{ session, error }>
  signOut, // () => Promise<void>
  confirmSignUp, // (params) => Promise<{ session, error }>
  recoverPassword, // (params) => Promise<{ data, error }>
  resetPassword, // (params) => Promise<{ data, error }>
  refreshSession, // () => Promise<{ session, error }>
} = useAuth();
```

`exchangeCode` takes a single-use code from a trusted server. A session created this way cannot be refreshed.

All methods are memoized with `useCallback`, so you can pass them as props without causing extra re-renders.

<AccordionGroup>
  <Accordion title="Sign in">
    The session updates in context automatically, so you do not need to set state yourself.

    ```tsx theme={null}
    function LoginForm() {
      const { signIn, isLoading } = useAuth();
      const [error, setError] = useState<string | null>(null);

      const handleSubmit = async (e: FormEvent) => {
        e.preventDefault();
        const { session, error } = await signIn({ email, password });
        if (error) {
          setError(error.message);
        }
      };

      return (
        <form onSubmit={handleSubmit}>
          {error && <p className="error">{error}</p>}
          {/* inputs */}
          <button disabled={isLoading}>Sign in</button>
        </form>
      );
    }
    ```
  </Accordion>

  <Accordion title="Sign up and confirm">
    ```tsx theme={null}
    const { signUp, confirmSignUp } = useAuth();

    // Step 1: Register
    const { data, error } = await signUp({ email, password });
    // data = { userId: '...', message: 'Check your email' }

    // Step 2: Confirm with the code from the email
    const { session, error: confirmError } = await confirmSignUp({
      email,
      code: '123456',
    });
    ```
  </Accordion>

  <Accordion title="Password recovery">
    ```tsx theme={null}
    const { recoverPassword, resetPassword } = useAuth();

    // Step 1: Request a reset
    await recoverPassword({ email });

    // Step 2: Reset with the code
    await resetPassword({ email, code: '123456', newPassword: 'newPass!' });
    ```
  </Accordion>
</AccordionGroup>

### useQuery

`useQuery` runs declarative GraphQL queries with caching, stale-while-revalidate, polling, and automatic deduplication.

```tsx theme={null}
const { data, error, isLoading, isRefetching, refetch } = useQuery<T>(
  gql,         // GraphQL query string
  variables?,  // Record<string, unknown>
  options?,    // UseQueryOptions
);
```

**Options**

| Option | Type | Default | Description |
| - | - | - | - |
| `enabled` | `boolean` | `true` | Set `false` to skip the fetch (conditional queries) |
| `refetchInterval` | `number` | none | Poll interval in ms |
| `refetchOnWindowFocus` | `boolean` | `false` | Refetch when the window regains focus |
| `onSuccess` | `(data: T) => void` | none | Called on a successful fetch |
| `onError` | `(error: ArchieError) => void` | none | Called on error |

**Return values**

| Field | Type | Description |
| - | - | - |
| `data` | `T \| null` | Query result |
| `error` | `ArchieError \| null` | Error if the query failed |
| `isLoading` | `boolean` | True on initial load |
| `isRefetching` | `boolean` | True on background refetch |
| `refetch` | `() => Promise<void>` | Manually triggers a refetch |

**Behavior**

* **Stale-while-revalidate:** shows cached data immediately and refetches in the background.
* **Deduplication:** components with the same query and variables share a single network request.
* **Auto-refetch on auth change:** when the user's token changes, all active queries refetch.
* **Auto-refetch on invalidation:** a mounted query re-runs as soon as its key is invalidated, for example by a `useMutation` with `invalidateQueries`. Concurrent consumers of the same key still share one request.
* **Latest response wins:** a response already in flight when its key is invalidated is discarded, so pre-mutation data never repaints the view.
* **Errors never blank the view:** a failed revalidation sets `error` but keeps the data already on screen.
* **Variable tracking:** variables are compared with `JSON.stringify`, and the query refetches when they change.

```tsx theme={null}
// Basic query
const { data, isLoading } = useQuery<{ users: User[] }>('{ users { id email name } }');

// With variables
const { data } = useQuery<{ user: User }>('query($id: ID!) { user(id: $id) { id email } }', {
  id: userId,
});

// Conditional query (wait for userId)
const { data } = useQuery<{ user: User }>(
  'query($id: ID!) { user(id: $id) { id email } }',
  { id: userId },
  { enabled: !!userId },
);

// Polling every 5 seconds
const { data } = useQuery<{ stats: Stats }>('{ stats { activeUsers requests } }', undefined, {
  refetchInterval: 5000,
});
```

### useMutation

`useMutation` runs imperative GraphQL mutations and can invalidate cached queries.

```tsx theme={null}
const { mutate, mutateAsync, data, error, isLoading, reset } = useMutation<T>(
  gql,       // GraphQL mutation string
  options?,  // UseMutationOptions
);
```

**Options**

| Option | Type | Description |
| - | - | - |
| `onSuccess` | `(data: T) => void` | Called after a successful mutation |
| `onError` | `(error: ArchieError) => void` | Called on error |
| `onSettled` | `() => void` | Called after success or error |
| `invalidateQueries` | `string[]` | Query substrings to invalidate after success. Every mounted `useQuery` whose key matches refetches. |

**Return values**

| Field | Type | Description |
| - | - | - |
| `mutate` | `(vars?) => void` | Fire-and-forget mutation |
| `mutateAsync` | `(vars?) => Promise<T \| null>` | Awaits the result |
| `data` | `T \| null` | Last successful result |
| `error` | `ArchieError \| null` | Last error |
| `isLoading` | `boolean` | True while executing |
| `reset` | `() => void` | Clears data, error, and isLoading |

```tsx theme={null}
const { mutate, isLoading } = useMutation<{ createUser: User }>(
  'mutation($input: CreateUserInput!) { createUser(input: $input) { id email } }',
  {
    invalidateQueries: ['users'],
    onSuccess: (data) => toast.success(`Created ${data.createUser.email}`),
  },
);

const handleCreate = () => {
  mutate({ input: { email: 'new@user.com', name: 'John' } });
};
```

<Note>
  `invalidateQueries` matches by substring. A pattern like `'id'` refetches every mounted query whose text contains `id`, and each one is a real network request. Use patterns distinctive enough to name the entity you changed. You can pass several patterns, because the whole array is applied as one batch and a query matched more than once still refetches only once.
</Note>

### useSubscription

`useSubscription` opens a real-time GraphQL subscription over WebSocket. It subscribes on mount and unsubscribes on unmount.

```tsx theme={null}
const { data, error, connectionState } = useSubscription<T>(
  gql,         // GraphQL subscription string
  variables?,  // Record<string, unknown>
  options?,    // UseSubscriptionOptions
);
```

**Options**

| Option | Type | Default | Description |
| - | - | - | - |
| `enabled` | `boolean` | `true` | Set `false` to skip the subscription |
| `onData` | `(data: T) => void` | none | Called on each new value |
| `onError` | `(error: ArchieError) => void` | none | Called on error |

**Return values**

| Field | Type | Description |
| - | - | - |
| `data` | `T \| null` | Latest subscription value |
| `error` | `ArchieError \| null` | Last error |
| `connectionState` | `ConnectionState` | `'DISCONNECTED' \| 'CONNECTING' \| 'CONNECTED' \| 'RECONNECTING'` |

```tsx theme={null}
function LiveOrders() {
  const { data, connectionState } = useSubscription<{ orderUpdated: Order }>(
    'subscription { orderUpdated { id status total } }',
  );

  return (
    <div>
      <Badge>{connectionState}</Badge>
      {data && <OrderCard order={data.orderUpdated} />}
    </div>
  );
}
```

The underlying `@archie/js-sdk` realtime module reconnects automatically with exponential backoff.

### useFileUpload

`useFileUpload` uploads files and tracks progress.

```tsx theme={null}
const { upload, progress, isUploading, data, error, reset } = useFileUpload();
```

| Field | Type | Description |
| - | - | - |
| `upload` | `(file: File \| Blob, opts?: FileUploadOptions) => Promise<void>` | Starts the upload |
| `progress` | `number` | 0 to 100 |
| `isUploading` | `boolean` | True while uploading |
| `data` | `FileUploadResult \| null` | `{ url, fileId }` on success |
| `error` | `ArchieError \| null` | Error if the upload failed |
| `reset` | `() => void` | Clears all state |

`FileUploadOptions` is `{ filename?, contentType?, providerType?, onProgress? }`.

```tsx theme={null}
function FileUploader() {
  const { upload, progress, isUploading, data, error, reset } = useFileUpload();

  return (
    <div>
      <input
        type="file"
        onChange={(e) => {
          const file = e.target.files?.[0];
          if (file) upload(file, { contentType: file.type, filename: file.name });
        }}
      />
      {isUploading && <ProgressBar value={progress} />}
      {data && <p>Uploaded: {data.url}</p>}
      {error && <p className="error">{error.message}</p>}
      {data && <button onClick={reset}>Upload another</button>}
    </div>
  );
}
```

### useRest

`useRest` returns the REST module for imperative calls to your REST API.

```tsx theme={null}
const rest = useRest();

// rest.get<T>(path, options?)
// rest.post<T>(path, body?, options?)
// rest.put<T>(path, body?, options?)
// rest.patch<T>(path, body?, options?)
// rest.delete<T>(path, options?)
```

```tsx theme={null}
function ProductActions() {
  const rest = useRest();

  const handleExport = async () => {
    const csv = await rest.get<string>('/api/products/export');
    downloadCsv(csv);
  };

  return <button onClick={handleExport}>Export CSV</button>;
}
```

### useRestQuery

`useRestQuery` is a declarative hook for GET requests to custom REST endpoints.

```tsx theme={null}
const { data, error, isLoading, refetch } = useRestQuery<T>(path, options?);
```

The options extend `RestRequestOptions` (`{ headers?, params?, signal? }`) with `enabled?: boolean`.

| Field | Type | Description |
| - | - | - |
| `data` | `T \| null` | Response data |
| `error` | `ArchieError \| null` | Error if the request failed |
| `isLoading` | `boolean` | True on initial load |
| `refetch` | `() => Promise<void>` | Manually triggers a refetch |

```tsx theme={null}
const { data, isLoading } = useRestQuery<Product[]>('/api/products');
const { data: product } = useRestQuery<Product>(`/api/products/${id}`, { enabled: !!id });
```

### useRestMutation

`useRestMutation` is an imperative hook for POST, PUT, PATCH, and DELETE requests.

```tsx theme={null}
const { execute, data, error, isLoading, reset } = useRestMutation<T>(path, method?);
```

| Param | Type | Default | Description |
| - | - | - | - |
| `path` | `string` | none | REST endpoint path |
| `method` | `'POST' \| 'PUT' \| 'PATCH' \| 'DELETE'` | `'POST'` | HTTP method |

| Field | Type | Description |
| - | - | - |
| `execute` | `(body?, overrideOptions?: RestRequestOptions) => Promise<T \| null>` | Executes the request |
| `data` | `T \| null` | Last successful result |
| `error` | `ArchieError \| null` | Last error |
| `isLoading` | `boolean` | True while executing |
| `reset` | `() => void` | Clears data, error, and isLoading |

The optional `overrideOptions` argument of `execute` lets you pass custom `headers`, `params`, or `signal` for a single request.

```tsx theme={null}
const { execute, isLoading } = useRestMutation<Product>('/api/products', 'POST');

const handleCreate = async () => {
  const product = await execute({ name: 'Widget', price: 9.99 });
  if (product) router.push(`/products/${product.id}`);
};

// With custom headers per request
await execute(payload, { headers: { 'x-idempotency-key': uuid() } });
```

### useNotifications

`useNotifications` subscribes to live in-app notifications and manages read state. It opens a WebSocket channel on mount and closes it on unmount.

```tsx theme={null}
const { user } = useAuth();
const receiverId = user?.id ?? '';

const {
  notifications,
  latest,
  unreadCount,
  isLoading,
  error,
  connectionState,
  markAsRead,
  markAllAsRead,
  refetch,
} = useNotifications(receiverId, { enabled: !!user, limit: 20 });
```

**Options**

| Option | Type | Default | Description |
| - | - | - | - |
| `enabled` | `boolean` | `true` | Set `false` to skip loading and subscription, for example while the user is null |
| `unreadOnly` | `boolean` | `false` | The initial load returns only unread notifications |
| `limit` | `number` | none | Max items to load in the initial fetch |
| `onNotification` | `(n: Notification) => void` | none | Called for each new live notification, for example for toasts or sounds |

**Return values**

| Field | Type | Description |
| - | - | - |
| `notifications` | `Notification[]` | History plus live arrivals, most recent first, deduplicated by `id` |
| `latest` | `Notification \| null` | Last notification received live (convenient for toasts) |
| `unreadCount` | `number` | Live unread count, updated automatically |
| `isLoading` | `boolean` | True during the initial history load |
| `error` | `ArchieError \| null` | Error from the latest failed operation |
| `connectionState` | `ConnectionState` | `'CONNECTING' \| 'CONNECTED' \| 'DISCONNECTED' \| 'RECONNECTING'` |
| `markAsRead` | `(id: string) => Promise<void>` | Marks one notification as read and updates local state optimistically |
| `markAllAsRead` | `() => Promise<void>` | Marks all loaded notifications as read and reconciles `unreadCount` with the server (normally 0) |
| `refetch` | `() => Promise<void>` | Reloads the notification history |

<Warning>
  Always call the hook unconditionally, to follow the React rules of hooks. When there is no authenticated user, pass `receiverId=''` and `enabled: false`. The hook then does not load or subscribe.
</Warning>

<AccordionGroup>
  <Accordion title="Infinite scroll">
    `useNotifications` pages backward through history without dropping or duplicating live items.

    ```tsx theme={null}
    const { notifications, fetchMore, hasMore, isFetchingMore, error } = useNotifications(user.id, {
      limit: 20, // page size for fetchMore()
    });

    // For example, an IntersectionObserver on a sentinel at the bottom of the list.
    // Gate on `!error` so a failed page does not drive a tight auto-retry loop.
    useEffect(() => {
      if (hasMore && !isFetchingMore && !error) void fetchMore();
    }, [hasMore, isFetchingMore, error, fetchMore]);
    ```

    * `fetchMore()` loads the next older page and appends it (deduplicated, newest first). It does nothing when `!hasMore`, when a fetch is already in flight, when the hook is disabled, or when a previous `fetchMore` failed. Call `refetch()`, or change `receiverId` or `unreadOnly`, to retry.
    * `hasMore` is true when more older records exist for the active filter.
    * `isFetchingMore` is true while a `fetchMore()` call is in flight. Use it to drive a "loading older" indicator.

    Changing `receiverId` or `unreadOnly` resets pagination and re-seeds from the first page.
  </Accordion>

  <Accordion title="Notification bell with unread badge">
    ```tsx theme={null}
    import { useAuth, useNotifications } from '@archie/react-sdk';

    function NotificationBell() {
      const { user } = useAuth();
      const receiverId = user?.id ?? '';
      const { unreadCount, connectionState } = useNotifications(receiverId, { enabled: !!user });

      return (
        <button aria-label="Notifications">
          <BellIcon />
          {unreadCount > 0 && <span className="badge">{unreadCount > 99 ? '99+' : unreadCount}</span>}
          {connectionState !== 'CONNECTED' && <span className="dot reconnecting" />}
        </button>
      );
    }
    ```
  </Accordion>

  <Accordion title="Notification list with mark as read">
    ```tsx theme={null}
    function NotificationList() {
      const { user } = useAuth();
      const receiverId = user?.id ?? '';
      const { notifications, isLoading, unreadCount, markAsRead, markAllAsRead } = useNotifications(
        receiverId,
        { enabled: !!user, limit: 20 },
      );

      if (isLoading) return <Spinner />;

      return (
        <div>
          <header>
            <span>{unreadCount} unread</span>
            <button onClick={() => markAllAsRead()} disabled={unreadCount === 0}>
              Mark all read
            </button>
          </header>
          <ul>
            {notifications.map((n) => (
              <li
                key={n.id}
                className={n.isRead ? 'read' : 'unread'}
                onClick={() => !n.isRead && markAsRead(n.id)}
              >
                <p>{n.actionText ?? n.notificationKey}</p>
                <time>{new Date(n.createdAt).toLocaleString()}</time>
              </li>
            ))}
          </ul>
        </div>
      );
    }
    ```
  </Accordion>
</AccordionGroup>

<Warning>
  Before you render `n.actionUrl` as a link, check that its scheme is `http:` or `https:`. Never allow `javascript:` or `data:` URLs. Treat `n.metadata` and `n.params` as untrusted server data.
</Warning>

## Components

### AuthGuard

`AuthGuard` renders content based on authentication state and, optionally, user roles.

```tsx theme={null}
<AuthGuard fallback={<LoginPage />} loadingComponent={<Spinner />} requiredRoles={['admin']}>
  <ProtectedContent />
</AuthGuard>
```

| Prop | Type | Default | Description |
| - | - | - | - |
| `children` | `ReactNode` | none | Content shown when authorized |
| `fallback` | `ReactNode` | `null` | Content shown when the user is NOT authenticated or unauthorized |
| `loadingComponent` | `ReactNode` | `null` | Content shown while the session loads |
| `requiredRoles` | `string[]` | none | The user must have at least one of these roles |

`AuthGuard` evaluates these rules in order:

<Steps>
  <Step title="Session is loading">
    It renders `loadingComponent`.
  </Step>

  <Step title="User is not authenticated">
    It renders `fallback`.
  </Step>

  <Step title="Roles required and the user has none of them">
    It renders `fallback`.
  </Step>

  <Step title="Otherwise">
    It renders `children`.
  </Step>
</Steps>

You can nest guards, for example to protect an admin area inside an authenticated layout:

```tsx theme={null}
<AuthGuard fallback={<LoginPage />}>
  <AppLayout>
    <AuthGuard requiredRoles={['admin']} fallback={<Unauthorized />}>
      <AdminPanel />
    </AuthGuard>
  </AppLayout>
</AuthGuard>
```

## Error handling

All `useAuth` methods return `{ data/session, error }` and never throw. For `useQuery`, `useMutation`, and the other data hooks, errors appear in the `error` field of the return object.

These error classes come from `@archie/js-sdk`:

| Class | When |
| - | - |
| `AuthError` | Authentication failures (sign in, sign up, token refresh) |
| `GraphQLError` | GraphQL response errors |
| `NetworkError` | Network failures and timeouts |
| `ArchieError` | Base class for all Archie errors |

```tsx theme={null}
const { error } = useQuery<{ users: User[] }>('{ users { id } }');

if (error) {
  if (error instanceof AuthError) {
    // Token expired, redirect to login
  } else if (error instanceof NetworkError) {
    // Show offline indicator
  } else {
    // Show generic error
  }
}
```

## Server-side rendering

All hooks are safe to use with server-side rendering (SSR):

* Hooks do not access `window` or `document` on the initial render.
* `refetchOnWindowFocus` checks `typeof window` before it adds listeners.
* `ArchieProvider` initializes the session asynchronously, so it does not block SSR.
* `AuthGuard` renders `loadingComponent` during server render when the session is unknown.

In frameworks like Next.js, create the client outside the component tree.

```tsx theme={null}
// lib/archie.ts
import { createClient } from '@archie/js-sdk';

export const archie = createClient({
  projectId: process.env.NEXT_PUBLIC_ARCHIE_PROJECT_ID!,
  apiKey: process.env.NEXT_PUBLIC_ARCHIE_ANON_KEY!,
});

// app/providers.tsx
'use client';
import { ArchieProvider } from '@archie/react-sdk';
import { archie } from '@/lib/archie';

export function Providers({ children }: { children: React.ReactNode }) {
  return <ArchieProvider client={archie}>{children}</ArchieProvider>;
}
```

## TypeScript

All hooks are generic, so you can type each response.

```tsx theme={null}
interface User {
  id: string;
  email: string;
  name: string;
}

// Typed query response
const { data } = useQuery<{ users: User[] }>('{ users { id email name } }');
//     ^? { users: User[] } | null

// Typed mutation response
const { mutateAsync } = useMutation<{ createUser: User }>('mutation ...');
const result = await mutateAsync({ input: { email: 'a@b.com' } });
//    ^? { createUser: User } | null

// Typed subscription
const { data } = useSubscription<{ orderUpdated: Order }>('subscription ...');
//     ^? { orderUpdated: Order } | null

// Typed REST
const { data } = useRestQuery<Product[]>('/api/products');
//     ^? Product[] | null
```

### Re-exported types

`@archie/react-sdk` re-exports the essential types from `@archie/js-sdk`, so you can import from one package.

```tsx theme={null}
import {
  // Client
  createClient,
  type ArchieClient,
  type ArchieClientOptions,

  // Auth types
  type Session,
  type User,
  type AuthSignUpParams,
  type AuthSignInParams,
  type AuthConfirmParams,
  type AuthRecoverParams,
  type AuthResetPasswordParams,
  type AuthEvent,
  type AuthEventCallback,

  // GraphQL types
  type GraphQLResponse,
  type GraphQLRequestOptions,
  type GraphQLRawRequest,

  // File types
  type FileUploadOptions,
  type FileUploadResult,
  type CsvUploadOptions,

  // Realtime types
  type Subscription,
  type SubscriptionCallbacks,
  type RealtimeEvent,
  type RealtimeChannel,
  type ConnectionState,
  type ConnectionStateCallback,

  // REST types
  type RestRequestOptions,

  // Error classes
  ArchieError,
  AuthError,
  GraphQLError,
  NetworkError,

  // Storage
  type StorageAdapter,
  BrowserLocalStorage,
  MemoryStorage,

  // Interfaces
  type IAuthModule,
  type IGraphQLModule,
  type IFileModule,
  type IRealtimeModule,
  type IRestModule,
  type IHttpClient,
  type Logger,
  type TokenAccessor,
} from '@archie/react-sdk';

// React-specific prop types
import type { ArchieProviderProps, AuthGuardProps } from '@archie/react-sdk';
```

## Health check

The client exposes `ping()` to check that your project is reachable.

```tsx theme={null}
const archie = useArchieClient();

const isUp = await archie.ping();
```

## Resources

<CardGroup cols={2}>
  <Card title="@archie/react-sdk on npm" href="https://www.npmjs.com/package/@archie/react-sdk">
    Package page, versions, and install instructions.
  </Card>

  <Card title="@archie/js-sdk on npm" href="https://www.npmjs.com/package/@archie/js-sdk">
    The underlying client with retry, timeout, and custom fetch details.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.