# TanStack Query

`@newinstance/bugwatch-tanstack` connects BugWatch to [TanStack Query](https://tanstack.com/query). Point it at a `QueryClient` and every failed query and mutation is reported, with the operation that failed, the breadcrumb trail that led there, and a span for the request.

One package covers **React Web** and [React Native](/react-native).

## Install

```bash
npm install @newinstance/bugwatch-tanstack
```

You also need TanStack Query and the BugWatch SDK for your platform:

```bash
# React Web
npm install @tanstack/react-query @newinstance/bugwatch

# React Native
npm install @tanstack/react-query @newinstance/bugwatch-react-native
```

## Wire it up

Initialise BugWatch as usual, then instrument the client once, where you create it. Nothing else in the app changes, and no query or mutation needs to know BugWatch exists.

**React Web**

```ts
import { QueryClient } from '@tanstack/react-query';
import { BugWatch } from '@newinstance/bugwatch';
import { instrumentQueryClientWithBugWatch } from '@newinstance/bugwatch-tanstack/web';

BugWatch.init({ dsn: process.env.BUGWATCH_DSN! });

export const queryClient = new QueryClient();
instrumentQueryClientWithBugWatch(queryClient);
```

[React Native](/react-native)

```ts
import { QueryClient } from '@tanstack/react-query';
import { BugWatch } from '@newinstance/bugwatch-react-native';
import { instrumentQueryClientWithBugWatch } from '@newinstance/bugwatch-tanstack/native';

BugWatch.init({ dsn: BUGWATCH_DSN });

export const queryClient = new QueryClient();
instrumentQueryClientWithBugWatch(queryClient);
```

Instrumenting returns a function that removes it again:

```ts
const stop = instrumentQueryClientWithBugWatch(queryClient);
stop();
```

## What is reported

| | Sent to BugWatch |
|---|---|
| Failed query | The error, tagged with the operation name and retry count, with the query key as context |
| Failed mutation | The same, taken from the mutation key |
| Breadcrumbs | Every query and mutation start, success and failure, in order, so a captured error arrives with the trail that led to it |
| Spans | One span per query and mutation, carrying duration and failure status (web only, see below) |

The operation name is the first element of the query or mutation key, so `['users', 'list']` reports as `users`.

## Platform differences

The two BugWatch SDKs are not identical, and the adapter does not pretend otherwise.

| | Web (`@newinstance/bugwatch`) | React Native (`@newinstance/bugwatch-react-native`) |
|---|---|---|
| Error capture | Yes | Yes |
| Breadcrumbs | Yes | Yes |
| Per-error tags | Yes | No: the native SDK's `captureException` takes a level only |
| Structured context | Yes, an object | Serialized to a string, since the native SDK takes strings |
| Spans / tracing | Yes | **No: the native SDK has no span API** |

Tracing is on by default. On React Native it is unavailable, so pass `onWarning` if you want to hear about that rather than wonder why no spans appear:

```ts
instrumentQueryClientWithBugWatch(queryClient, {
  onWarning: (message) => console.warn(message),
});
```

Errors and breadcrumbs still work on React Native; only spans are missing. The returned function carries `tracingActive` if you need to branch on it.

## Keeping personal data out of BugWatch

Query keys routinely hold user ids, emails and search terms, and they are recorded with every captured error. `redactKey` runs before anything leaves the app:

```ts
instrumentQueryClientWithBugWatch(queryClient, {
  redactKey: (key) =>
    key.map((part) =>
      typeof part === 'string' && part.includes('@') ? '[redacted]' : part,
    ),
});
```

**Mutation variables are never recorded by default**, because that is where card numbers and passwords live. Opt in only where the payload is known to be safe, and `redactKey` is applied to them too:

```ts
instrumentQueryClientWithBugWatch(queryClient, { includeMutationVariables: true });
```

## Not reporting expected failures

A 404 from a lookup is usually not a bug. `shouldCapture` decides:

```ts
instrumentQueryClientWithBugWatch(queryClient, {
  shouldCapture: (error, context) => {
    if (error instanceof HttpError && error.status === 404) return false;
    if (context.kind === 'query' && context.operationName === 'presence') return false;
    return true;
  },
});
```

Filtered errors still leave a breadcrumb, so the trail stays complete even when the error itself is not worth an issue.

## Options

| Option | Default | Notes |
|---|---|---|
| `captureQueryErrors` | `true` | Report failed queries |
| `captureMutationErrors` | `true` | Report failed mutations |
| `breadcrumbs` | `true` | Record the lifecycle trail |
| `tracing` | `true` | Span per operation. Web only |
| `redactKey` | none | `(key, kind) => key`, applied before anything is recorded |
| `shouldCapture` | none | `(error, context) => boolean` |
| `includeMutationVariables` | `false` | Off because payloads carry secrets |
| `maxKeyLength` | `256` | Serialized keys are truncated past this |
| `onWarning` | none | Called when a requested feature is unavailable on this platform |

## Custom targets

`instrumentQueryClient` accepts any object implementing `BugWatchTarget`, which is useful in tests or when routing to your own sink:

```ts
import { instrumentQueryClient } from '@newinstance/bugwatch-tanstack';

instrumentQueryClient(queryClient, {
  platform: 'web',
  capabilities: { captureTags: true, tracing: false, richContext: true },
  captureException: (error) => myReporter.report(error),
  addBreadcrumb: (crumb) => myReporter.trail(crumb),
  setContext: (key, data) => myReporter.context(key, data),
});
```

## Troubleshooting

- **Nothing is reported.** `BugWatch.init()` must run before the first query. Instrumenting a client is not enough on its own.
- **Errors arrive but no spans.** Expected on React Native, which has no span API. On web, check that the DSN is a project key and that spans are not being dropped by `flushInterval: 0` without a `flush()`.
- **A query key is missing from the issue.** `redactKey` removed it, or it exceeded `maxKeyLength` and was truncated.
- **Circular query keys throw.** TanStack Query hashes keys with `JSON.stringify`, so this fails inside TanStack itself before the adapter is involved. Keys holding `Date`, `Map` or `undefined` are handled.

Every call into BugWatch is wrapped: if the SDK is missing, misconfigured or throws, queries and mutations resolve and reject exactly as they would without the adapter.
