# createQueryCaller

Creates a direct-mode React Query client that connects straight to the backend, no proxy adapter required.

## Import

```ts
import { createQueryCaller } from "@noverax/courier-react"
```

> **Direct vs Proxy mode**
>
> This is the **direct-mode** query caller — it accepts the API definition object and connects straight to `api.options.baseURL`. For proxy mode (browser → server adapter → backend), use [`createProxyQueryCaller`](/docs/api/create-proxy-query-caller).

## When to use direct mode

Use `createQueryCaller(api)` when:

- You're building an **SPA** (Vite, Create React App) that talks to a separate backend
- You're building a **React Native** app
- You're building an **internal tool** where bundle size doesn't matter
- The API definition can safely ship to the client (no secrets, no auth tokens hard-coded)

Use [`createProxyQueryCaller`](/docs/api/create-proxy-query-caller) instead when:

- You need to **hide secrets** (auth headers, API keys) from the browser
- You need **CORS bypass** (browser → your server → third-party API)
- You want to keep the API definition **server-only** to reduce client bundle size
- You want **request logging / observability** through a single proxy endpoint

## Signature

```ts
declare function createQueryCaller<TApi extends DefinedApi>(
  api: TApi,
  defaults?: QueryCallerDefaults,
): QueryCallerClient<TApi>
```

The first argument is the **API definition object** itself (returned by `defineApi()`). No string source name, no generic parameter — TypeScript infers everything from the api object.

## Defaults

```ts
type QueryCallerDefaults = {
  timeout?: number
  headers?: Record<string, string>
  context?: RequestContext
  onError?: (error: HTTPError) => void | Promise<void>
  onSuccess?: (data: unknown) => void | Promise<void>
  ws?: CallerWSOptions  // as createCaller's
}
```

`ws` sets up the sockets `useChannel` opens: the token, the reconnect backoff, the heartbeat and the queue limits — see [Connection options](/docs/advanced/realtime#connection-options).

No `proxyPath` or `credentials` — all transports use the `baseURL` from the API definition directly.

## Setup

```ts title="lib/query.ts"
import { createQueryCaller, createQueryKeys } from "@noverax/courier-react"
import { api } from "@/shared/api"

export const q = createQueryCaller(api, {
  timeout: 10000,
  headers: { "X-Client": "web-spa" },
})

export const queryKeys = createQueryKeys(api)
```

## Hooks

All hooks are identical to [`createProxyQueryCaller`](/docs/api/create-proxy-query-caller). The only difference is that requests bypass the proxy adapter and connect directly to `api.options.baseURL`.

```tsx title="components/user-list.tsx"
"use client"

import { q } from "@/lib/query"

export function UserList() {
  const { data, isLoading } = q.users.useQuery("getAll")

  if (isLoading) return <p>Loading...</p>

  return <ul>{data?.map((u) => <li key={u.id}>{u.name}</li>)}</ul>
}
```

### Variables, mutations, infinite queries

```ts
// Query with variables
const { data: user } = q.users.useQuery("getById", { variables: { id: "1" } })

// Mutation
const { mutate } = q.users.useMutation("create")
mutate({ name: "Alice" })

// Infinite query
const { data: pages, fetchNextPage } = q.posts.useInfiniteQuery("list", {
  variables: (cursor) => ({ cursor }),
  initialPageParam: 0,
  getNextPageParam: (page) => page.nextCursor,
})
```

### SSE subscriptions

```tsx
const { data, status, events } = q.notifications.useSubscription("feed", {
  variables: { userId: "user-1" },
})
```

The SSE connection goes directly to `${api.options.baseURL}/${groupPrefix}/${path}` — no proxy hop. The path is the one the endpoint addressed, worked out from each call's variables: a `:param` in it is filled from the variable of that name, a `.via()` closure is run with the variables, and an endpoint that named no path is reached by its name. The variables the path did not take are sent in the query string.

### WS channels

```tsx
const { send, lastMessage, status } = q.chat.useChannel("room", {
  variables: { roomId: "general" },
})

send({ text: "Hello" })
```

The WebSocket connection goes directly to `${baseURL with ws:// scheme}/${groupPrefix}/${path}` — no proxy hop. The path is worked out from the call's variables as it is for SSE; the variables themselves are all sent in the subscribe message.

A stream or channel served by this process with `.implement()` has no address at `baseURL`, which is the upstream's. `useSubscription` and `useChannel` report it through `error` and `onError` as `ConfigurationError("DIRECT_CALL_TO_SERVED_ENDPOINT")` and open nothing; reach it with [`createProxyQueryCaller`](/docs/api/create-proxy-query-caller).

## Query Keys

Query keys are auto-generated as `[sourceName, group, method, variables?]` where `sourceName` comes from `defineApi({ name })` (or is derived from the baseURL hostname when omitted).

```ts title="lib/query.ts"
import { createQueryKeys } from "@noverax/courier-react"
import { api } from "@/shared/api"

export const queryKeys = createQueryKeys(api)
```

```ts
queryKeys("users")                          // ["api", "users"]
queryKeys("users", "getAll")                // ["api", "users", "getAll"]
queryKeys("users", "getById", { id: 1 })    // ["api", "users", "getById", { id: 1 }]

queryClient.invalidateQueries({ queryKey: queryKeys("users") })
```

See [`createQueryKeys`](/docs/api/create-query-keys) for details.

## Comparison

| Feature | `createQueryCaller(api)` | `createProxyQueryCaller(apiRoutes)` |
| --- | --- | --- |
| Mode | Direct | Proxy |
| Argument | API object | The API's routes, written by Studio |
| Server adapter required | No | Yes (`apiRoute`, `sseRoute`, WS handler) |
| Ships API definition to client | Yes | No |
| Hides secrets in API definition | No | Yes |
| CORS bypass | No | Yes (proxy lives on your origin) |
| Best for | SPA, React Native, internal tools | Next.js / SSR / multi-tenant apps |

## Related

- [`createCaller`](/docs/api/create-caller) — direct-mode caller without React Query hooks
- [`createProxyQueryCaller`](/docs/api/create-proxy-query-caller) — proxy-mode equivalent
- [`createQueryKeys`](/docs/api/create-query-keys) — type-safe query key factory for direct mode
- [React Query Setup](/docs/react-query/setup) — full setup guide with QueryClientProvider
