# TanStack Query — Admin & Architecture Guide

This guide is the deep reference for engineers, architects, library maintainers, and contributors. It documents how TanStack Query is built internally: the framework‑agnostic core engine, the per‑framework adapter layer, the caching and state‑machine internals, the monorepo build pipeline, and the security and operational characteristics of the library. Where the [User Guide](TanStack__query__UserGuide.md) teaches *how to use* the library, this guide explains *how it works* and *how to maintain it*.

---

## 1. Executive summary

TanStack Query is a **headless, framework‑agnostic async‑state management library** distributed as a **27‑package pnpm monorepo**. A single core package (`@tanstack/query-core`) contains all of the caching, retry, network, and observation logic and has **zero runtime framework dependency**. Each supported framework ships a thin *adapter* package that binds the core's imperative observer API to that framework's reactivity primitives (React hooks, Vue refs, Solid signals, Svelte stores, Angular signals, Lit controllers).

| Attribute | Value |
|---|---|
| Repository | `TanStack/query` (analyzed at commit `3270e09`) |
| Package version (all packages) | `5.101.0` |
| Published packages | 27 (core, 7 framework adapters, devtools, persistence, eslint plugin, codemods, utilities) |
| Language | TypeScript (strict), ESM + CJS dual output |
| Core dependencies | **None** at runtime — the core is self‑contained |
| Package manager | pnpm 11 (workspaces) |
| Build orchestration | Nx (`affected` graph) + tsup per package |
| License | MIT |

The architectural thesis is **"one engine, many bindings."** All behavior lives in `query-core`; adapters are deliberately minimal. This is why every concept (query keys, `staleTime`, mutations, invalidation) behaves identically across frameworks, and why a bug fix or feature in the core lands for all frameworks at once.

```
                         ┌──────────────────────────────────────────────┐
                         │            @tanstack/query-core                │
                         │  (framework-agnostic engine — no UI deps)      │
                         │                                                │
   App code  ──────►     │  QueryClient ── QueryCache ── Query            │
   (queryFn,             │      │              │           │ (state       │
    mutationFn)          │      │              │           │  machine)    │
                         │      │          MutationCache ─ Mutation        │
                         │      │                                          │
                         │  Observers: QueryObserver / InfiniteQuery /    │
                         │  QueriesObserver / MutationObserver            │
                         │                                                │
                         │  Singletons: focusManager · onlineManager ·    │
                         │  notifyManager · timeoutManager                │
                         └───────────────▲────────────────────────────────┘
                                         │ subscribe() / getCurrentResult()
            ┌────────────┬───────────────┼───────────────┬──────────────┐
        react-query   vue-query     solid-query     svelte-query   angular-query
        (hooks +      (composables  (createX +      (stores +      (injectX +
        useSync-       + refs)       signals)        $-stores)      signals)
        ExternalStore)
```

---

## 2. Repository architecture

### Layered package inventory

The 27 packages fall into clear responsibility layers. Organizing them by *layer* (not alphabetically) is the only way to navigate the repo sensibly.

#### Layer 0 — The engine

| Package | Responsibility |
|---|---|
| `@tanstack/query-core` | The entire caching/retry/observer engine. Everything else depends on it. |

#### Layer 1 — Framework adapters (one per host framework)

| Package | Host | Binding mechanism |
|---|---|---|
| `@tanstack/react-query` | React 18/19 | `useSyncExternalStore` over observers |
| `@tanstack/vue-query` | Vue 2.6+/3 | Reactive `ref`s, plugin install |
| `@tanstack/solid-query` | Solid | Fine‑grained signals; options passed as a function |
| `@tanstack/svelte-query` | Svelte 5 | Svelte stores / runes |
| `@tanstack/angular-query-experimental` | Angular 16+ | Signals + DI provider |
| `@tanstack/lit-query` | Lit | Reactive controllers on `LitElement` |
| `@tanstack/preact-query` | Preact | Hooks (mirror of react-query) |

#### Layer 2 — Devtools (per framework)

`@tanstack/query-devtools` is the framework‑agnostic devtools UI (a Solid‑built component, the largest single module at ~164 KB). Per‑framework wrappers mount it: `@tanstack/react-query-devtools`, `@tanstack/vue-query-devtools`, `@tanstack/solid-query-devtools`, `@tanstack/svelte-query-devtools`, `@tanstack/preact-query-devtools`.

#### Layer 3 — Persistence

| Package | Responsibility |
|---|---|
| `@tanstack/query-persist-client-core` | Core persist/restore engine (`persistQueryClient`, `createPersister`, retry strategies). |
| `@tanstack/query-sync-storage-persister` | Persister for sync stores (`localStorage`, `sessionStorage`). |
| `@tanstack/query-async-storage-persister` | Persister for async stores (RN `AsyncStorage`), with `asyncThrottle`. |
| `@tanstack/react-query-persist-client` (+ solid/svelte/angular/preact variants) | `PersistQueryClientProvider` wiring. |

#### Layer 4 — Experimental / integration

| Package | Responsibility |
|---|---|
| `@tanstack/react-query-next-experimental` | Next.js App Router streaming SSR integration. |
| `@tanstack/query-broadcast-client-experimental` | Sync cache across browser tabs via `BroadcastChannel`. |

#### Layer 5 — Tooling

| Package | Responsibility |
|---|---|
| `@tanstack/eslint-plugin-query` | Lint rules (exhaustive‑deps, stable‑client, property‑order, etc.). |
| `@tanstack/query-codemods` | jscodeshift codemods for v4→v5 (and v3→v4) migrations. |
| `@tanstack/query-test-utils` | Internal test helpers. |

> [!NOTE]
> `examples/` (per framework) and `integrations/` (bundler/SSR smoke tests like `react-webpack-5`, `react-next-15`, `angular-cli-20`) are workspace members but are **excluded from the publish/build pipeline** — they exist only to validate the packages against real toolchains.

### The `query-core` module map

Inside `packages/query-core/src/`, the engine decomposes into focused modules:

| Module | Class / export | Role |
|---|---|---|
| `queryClient.ts` | `QueryClient` | Public facade. Owns the two caches and default options; exposes the imperative API. |
| `queryCache.ts` | `QueryCache` | A `Map<queryHash, Query>` with add/remove/find/notify. |
| `query.ts` | `Query` | A single cache entry. Holds state, runs the fetch, reduces actions. |
| `queryObserver.ts` | `QueryObserver` | Bridges a `Query` to a consumer; computes the result object, manages stale/refetch timers. |
| `infiniteQueryObserver.ts` | `InfiniteQueryObserver` | `QueryObserver` subclass adding page navigation. |
| `infiniteQueryBehavior.ts` | `infiniteQueryBehavior` | The page‑fetching algorithm injected into a query's `fetch`. |
| `queriesObserver.ts` | `QueriesObserver` | Manages a dynamic array of `QueryObserver`s and the `combine` reducer. |
| `mutationCache.ts` / `mutation.ts` / `mutationObserver.ts` | `MutationCache` / `Mutation` / `MutationObserver` | The mutation counterparts. |
| `retryer.ts` | `createRetryer`, `CancelledError` | Retry/backoff loop, pause/continue, cancellation. |
| `removable.ts` | `Removable` (abstract) | Garbage‑collection timer base class for `Query` and `Mutation`. |
| `subscribable.ts` | `Subscribable` | Observer‑pattern base class for caches, managers, and observers. |
| `focusManager.ts` / `onlineManager.ts` | singletons | Window‑focus and online/offline detection. |
| `notifyManager.ts` | `notifyManager` | Batches and schedules listener notifications. |
| `timeoutManager.ts` | `timeoutManager` | Pluggable `setTimeout`/`setInterval` backend for stale/gc timers. |
| `environmentManager.ts` | `environmentManager` | Server‑vs‑client detection (overridable). |
| `hydration.ts` | `dehydrate` / `hydrate` | SSR serialization of cache state. |
| `streamedQuery.ts` | `experimental_streamedQuery` | Build a `queryFn` from an `AsyncIterable`. |
| `thenable.ts` | `pendingThenable` | React‑compatible promise with `.status`/`.value`/`.reason`. |
| `utils.ts` | hashing, matching, `skipToken`, `replaceEqualDeep` | Shared utilities. |

---

## 3. Core internals

### The observer pattern foundation

Everything reactive in the core extends a tiny `Subscribable<TListener>` base class. It owns a `Set` of listeners and exposes `subscribe(listener)` returning an unsubscribe function, plus protected `onSubscribe`/`onUnsubscribe` hooks. `QueryCache`, `MutationCache`, `FocusManager`, `OnlineManager`, and every observer extend it. This single abstraction is why the entire system is push‑based: a state change cascades through `notify()` calls to subscribed observers, which recompute and notify *their* listeners (the framework adapter), which trigger a re‑render.

```
Query.#dispatch(action)
   └─► reducer updates Query.state
        └─► notifyManager.batch(...)
             ├─► each QueryObserver.onQueryUpdate() → updateResult() → notify listener
             └─► QueryCache.notify({ type: 'updated', ... }) → cache subscribers (devtools, etc.)
```

### `QueryClient` — the facade

`QueryClient` is what application code instantiates. It owns a `QueryCache`, a `MutationCache`, the global `defaultOptions`, and two maps of per‑key defaults (`queryDefaults`, `mutationDefaults`). Its surface splits into:

- **Imperative cache access:** `getQueryData`, `setQueryData`, `getQueriesData`, `setQueriesData`, `getQueryState`.
- **Cache control:** `invalidateQueries`, `refetchQueries`, `cancelQueries`, `removeQueries`, `resetQueries`.
- **Fetching:** `fetchQuery`, `prefetchQuery`, `ensureQueryData`, and the `*InfiniteQuery` variants.
- **Defaults:** `setDefaultOptions`, `setQueryDefaults`, `setMutationDefaults`, and the `defaultQueryOptions`/`defaultMutationOptions` resolvers that merge global → per‑key → call‑site options.
- **Lifecycle:** `mount`/`unmount` (reference‑counted; the first mount subscribes to focus/online managers, the last unmount tears them down).

`mount()` is reference counted via `#mountCount`: only the first provider subscribes the client to `focusManager` and `onlineManager`; only the last `unmount()` unsubscribes. This is what makes "refetch on focus" and "resume on reconnect" work, and why these listeners are not attached when no provider is mounted.

### The `Query` state machine

A `Query` (one per unique `queryHash`) holds a `QueryState` and mutates it **only** through a reducer driven by dispatched actions — a Redux‑style state machine.

`QueryState` fields: `data`, `dataUpdateCount`, `dataUpdatedAt`, `error`, `errorUpdateCount`, `errorUpdatedAt`, `fetchFailureCount`, `fetchFailureReason`, `fetchMeta`, `isInvalidated`, `status` (`pending`/`error`/`success`), `fetchStatus` (`fetching`/`paused`/`idle`).

Reducer actions:

| Action | Effect |
|---|---|
| `fetch` | Enter `fetching` (or `paused` if offline); set `pending` if no data. |
| `success` | Store `data`, bump `dataUpdateCount`, return to `idle`. |
| `error` | Store `error`, bump counts, set `status: 'error'`, mark `isInvalidated`. |
| `failed` | Record a retry failure (`fetchFailureCount`, `fetchFailureReason`). |
| `pause` / `continue` | Toggle `fetchStatus` between `paused` and `fetching`. |
| `invalidate` | Set `isInvalidated: true` (makes the query stale). |
| `setState` | Merge an arbitrary partial state (used by `setQueryData`, hydration). |

The `fetch()` method builds an `AbortController`, lazily exposes its `signal` to the `queryFn` (reading `signal` flips an internal `#abortSignalConsumed` flag so the core knows whether to abort on unmount), runs the (optional) `behavior.onFetch` hook (this is where infinite‑query page logic is injected), then drives a **retryer**. On success it validates the data is not `undefined` — otherwise it throws `<queryHash> data is undefined`.

### `QueryObserver` — the result engine

A `QueryObserver` is the bridge between a `Query` and one consumer. `useQuery` (and the equivalents) create exactly one observer. The observer:

1. Subscribes to its `Query` and, on `onSubscribe`, decides whether to fetch on mount (`shouldFetchOnMount`).
2. Computes the **result object** in `createResult()` — deriving `isPending`/`isLoading`/`isStale`/etc. from query state, applying `placeholderData`, running `select` (memoized via reference equality + structural sharing), and tracking `select` errors.
3. Manages two timers via `timeoutManager`: the **stale timeout** (re‑evaluates `isStale` when `staleTime` elapses) and the **refetch interval** (polling).
4. Implements **render optimization** through `trackResult()`, which wraps the result in a `Proxy`. Only fields the component actually reads are tracked, and the observer re‑notifies only when a *tracked* field changes (`notifyOnChangeProps`). This is why accessing `data` but not `dataUpdatedAt` avoids re‑renders on timestamp churn.

`InfiniteQueryObserver` extends this, overriding `createResult` to add `fetchNextPage`/`fetchPreviousPage`, `hasNextPage`/`hasPreviousPage`, and the directional `isFetchingNextPage`/`isFetchNextPageError` flags.

### Mutations

Mutations mirror queries but are imperative and short‑lived. A `MutationObserver` (created by `useMutation`) builds a fresh `Mutation` each time `mutate()` is called. The `Mutation.execute()` method orchestrates the full callback waterfall in strict order: `mutationCache.onMutate` → `options.onMutate` → run `mutationFn` via the retryer → `onSuccess` → `onSettled` (or `onError` → `onSettled` on failure), dispatching `pending`/`success`/`error`/`pause`/`continue` actions to its own reducer.

`MutationCache` additionally tracks **scopes**: mutations sharing a `scope.id` are serialized via `canRun()`/`runNext()` so only one executes at a time and paused ones resume in order.

### The manager singletons

Four module‑level singletons coordinate cross‑cutting concerns:

| Singleton | Detects / does | Customizable via |
|---|---|---|
| `focusManager` | Window focus via `visibilitychange`. Drives focus refetch. | `setEventListener`, `setFocused` |
| `onlineManager` | Online/offline via `online`/`offline` events. Drives reconnect refetch + mutation resume. | `setEventListener`, `setOnline` |
| `notifyManager` | Batches listener callbacks into a single tick; integrates with React batching. | `setNotifyFunction`, `setBatchNotifyFunction`, `setScheduler` |
| `timeoutManager` | Backs all `setTimeout`/`setInterval` for stale/gc/refetch timers. | `setTimeoutProvider` |

> [!NOTE]
> The `focusManager` and `onlineManager` guard `typeof window !== 'undefined'` so they no‑op in React Native and SSR. Both are overridable, which is how React Native apps wire focus/online detection to `AppState`/`NetInfo`.

### Garbage collection (`Removable`)

`Query` and `Mutation` both extend the abstract `Removable`, which manages a `gcTime` timer. When a query loses its last observer (or a mutation settles with no observers), `scheduleGc()` starts a timer; if nothing re‑subscribes before it fires, `optionalRemove()` deletes the entry from its cache. `updateGcTime()` always keeps the **longest** requested `gcTime` and defaults to 5 minutes on the client / `Infinity` on the server.

### The retryer

`createRetryer()` is a self‑contained loop used by both queries and mutations. It:

- Resolves the `retry` policy (`boolean | number | (failureCount, error) => boolean`; default 3 on client, 0 on server) and `retryDelay` (default exponential backoff `min(1000 × 2^n, 30000)`).
- **Pauses** between retries when the document is hidden or the device is offline (`canContinue()`), resuming when focus/connectivity returns.
- Supports cancellation via `CancelledError` (with `revert`/`silent` flags) — the public `isCancelledError` helper is deprecated in favor of `instanceof CancelledError`.
- Returns a thenable plus `cancel`/`continue`/`canStart`/`start`/`status` controls.

### Hydration & SSR serialization

`dehydrate(client, options)` walks both caches and serializes successful queries (`defaultShouldDehydrateQuery`) and paused mutations (`defaultShouldDehydrateMutation`) into a plain `{ queries, mutations }` object. `hydrate(client, state)` rebuilds cache entries, carefully **not** overwriting entries that already hold newer data (compares `dataUpdatedAt`/`dehydratedAt`).

---

## 4. Configuration reference

TanStack Query has **no external configuration file** — it is a library, configured entirely in code through the `QueryClient` constructor and per‑query/mutation options. (The only `process.env` read in the published core is `NODE_ENV`, used to gate development‑only warnings and error redaction; it is not user configuration.)

### `QueryClientConfig`

```ts
new QueryClient({
  queryCache?: QueryCache,        // supply a custom cache (rare; for global onError/onSuccess)
  mutationCache?: MutationCache,  // same for mutations
  defaultOptions?: {
    queries?: QueryObserverOptions,    // defaults for every query
    mutations?: MutationObserverOptions,
    dehydrate?: DehydrateOptions,      // SSR serialization defaults
    hydrate?: HydrateOptions,
  },
})
```

### Global default options (`defaultOptions.queries`)

These are the cross‑cutting knobs most apps set once. See the [User Guide reference](TanStack__query__UserGuide.md#22-reference) for the full option table; the operationally significant ones:

| Option | Default | Operational note |
|---|---|---|
| `staleTime` | `0` | The single biggest lever on request volume. Raise it app‑wide to cut redundant refetches. |
| `gcTime` | `300000` (5 min) | Memory footprint of the cache. `Infinity` on server to survive SSR render. |
| `retry` | `3` (queries), `0` (mutations) | Set `0` for fail‑fast UIs; a function for status‑code‑aware retries. |
| `networkMode` | `'online'` | Set `'always'` for non‑HTTP sources; auto‑`'offlineFirst'` when a persister is set. |
| `refetchOnWindowFocus` / `refetchOnReconnect` / `refetchOnMount` | `true` | Disable globally for low‑traffic or cost‑sensitive backends. |
| `structuralSharing` | `true` | Keeps stable references across refetches; disable only for very large payloads where the diff cost matters. |

### Per‑key defaults

`setQueryDefaults(queryKey, options)` and `setMutationDefaults(mutationKey, options)` apply defaults to all queries/mutations whose key **prefix‑matches**. This is the idiomatic way to, e.g., give all `['user', …]` queries a longer `staleTime` without touching call sites.

### Customizable internals (advanced)

For specialized environments, the manager singletons accept overrides:

```ts
import { focusManager, onlineManager, notifyManager, timeoutManager } from '@tanstack/query-core'

onlineManager.setEventListener((setOnline) => { /* wire to NetInfo */ return cleanup })
notifyManager.setScheduler(requestAnimationFrame)   // custom flush scheduling
timeoutManager.setTimeoutProvider(myCoalescingProvider) // for apps with thousands of timers
```

---

## 5. Build system & development workflow

### Toolchain

| Concern | Tool |
|---|---|
| Package manager | **pnpm 11** with workspaces (`pnpm-workspace.yaml`) |
| Task orchestration | **Nx** — `nx affected`/`nx run-many` build only what changed in the dependency graph |
| Per‑package bundling | **tsup** (esbuild‑based), emitting ESM + CJS + `.d.ts` |
| Type checking | **tsc**, tested against a **matrix of TypeScript 5.4 → 6.0** (devDeps `typescript54`…`typescript60`) |
| Unit tests | **Vitest** (jsdom env, istanbul coverage) |
| Bundle size budget | **size-limit** (`@size-limit/preset-small-lib`) |
| Dead‑code/dep hygiene | **knip** |
| Monorepo dep consistency | **sherif** (catches version drift across packages) |
| Package publish validation | **publint** + **`@arethetypeswrong/cli`** |
| Linting | **ESLint** (`@tanstack/eslint-config`, react‑hooks, cspell) |
| Formatting | **Prettier** |
| Docs | **TypeDoc** (`@tanstack/typedoc-config`) + a link verifier (`scripts/verify-links.ts`) |
| Releases | **Changesets** |

### Key scripts (root `package.json`)

```bash
pnpm build          # nx affected --target=build (excludes examples/integrations)
pnpm build:all      # build every package
pnpm dev            # watch + rebuild all
pnpm test           # = test:ci
pnpm test:ci        # nx run-many: sherif, knip, docs, eslint, lib, types, build
pnpm test:lib       # vitest unit tests across affected packages
pnpm test:types     # type-level tests
pnpm test:size      # size-limit budget check
pnpm changeset      # author a changeset for the next release
```

> [!TIP]
> The CI gate (`test:ci`) runs eight target types: `sherif` (dep consistency), `knip` (dead code), `docs` (link integrity), `eslint`, `lib` (unit), `types` (type tests), `build`, and a `build` smoke. A contributor's PR runs the `affected` subset, so only packages touched by the change (and their dependents) are exercised.

### Release model

Releases are **changeset‑driven**: each PR that changes published behavior adds a changeset describing the bump. `changeset version` rolls versions and changelogs; `changeset publish` pushes to npm. All `@tanstack/*` packages are versioned **in lockstep** (every package is `5.101.0` at this commit), so consumers can safely mix, e.g., `react-query` and `react-query-devtools` at the same version.

---

## 6. Security analysis

TanStack Query is a **client‑side data‑management library**; it does not open ports, read secrets, or execute remote code. Its security surface is therefore narrow and centers on a few areas worth reviewing in any deployment.

### Threat surface

| Area | Consideration | Mitigation in code |
|---|---|---|
| **SSR error leakage** | Errors from pending queries are serialized into the HTML payload during `dehydrate`, potentially leaking server‑side error details to the client. | `dehydrate` redacts errors by default: `defaultShouldRedactErrors` returns `true`, and pending‑query rejections are replaced with a generic `Error('redacted')` in production (the original is `console.error`‑logged only in non‑production). Override with `shouldRedactErrors`. |
| **Cache persistence** | Persisters write cache data to `localStorage`/IndexedDB/`AsyncStorage` — readable by any script on the origin (XSS) and not encrypted. | Treat persisted data as **non‑sensitive**. Do not persist auth tokens or PII; scope what is dehydrated with `shouldDehydrateQuery`. Persist‑client supports buster keys and `maxAge` to invalidate stale/poisoned caches. |
| **Cross‑tab sync** | `query-broadcast-client-experimental` mirrors cache across tabs via `BroadcastChannel`, same‑origin only. | Same‑origin scoping by the browser; still avoid broadcasting sensitive data. |
| **`queryFn` is your code** | The library never constructs requests; it only runs the function you provide. Injection, auth, and URL‑building are the consumer's responsibility. | N/A — by design. Forward `signal` to enable request cancellation and avoid resource leaks. |
| **Supply chain** | The core has **zero runtime dependencies**, minimizing transitive‑dependency risk. Adapters depend only on `query-core` + their (peer) host framework. | Lockstep versioning + `publint`/`arethetypeswrong` validation of published artifacts. |

### Negative knowledge (what the library does *not* do)

- It does **not** read environment variables for configuration (only `NODE_ENV` for dev warnings).
- It does **not** include an HTTP client, retry on its own network stack, or perform authentication.
- It does **not** encrypt persisted caches.
- It does **not** automatically retry mutations (default `retry: 0`), preventing accidental duplicate writes.

> [!IMPORTANT]
> The most common real‑world misconfiguration is persisting sensitive data. Because persisters serialize to plaintext browser storage, audit `shouldDehydrateQuery` and ensure auth/PII queries are excluded from any `PersistQueryClientProvider`.

---

## 7. Code quality & conventions

- **Encapsulation via native private fields.** Internal state uses ECMAScript `#private` fields (`#queryCache`, `#retryer`, `#observers`), not TypeScript `private`, giving true runtime privacy.
- **State machines over ad‑hoc mutation.** `Query` and `Mutation` mutate state only through reducers fed by typed action unions, making transitions auditable and testable.
- **Single base abstractions.** `Subscribable` (observation) and `Removable` (GC) are reused across all stateful classes, keeping behavior consistent.
- **Framework‑agnostic core, thin adapters.** Adapters contain almost no logic — React's `useBaseQuery` is ~150 lines that mostly wire an observer to `useSyncExternalStore`. New frameworks are cheap to add.
- **Render optimization by default.** The `trackResult` Proxy + `notifyOnChangeProps` mean components re‑render only on fields they read — a correctness‑preserving performance feature baked into the core.
- **Pluggable seams.** `timeoutManager`, `notifyManager`, `focusManager`, `onlineManager`, and `environmentManager` are all swappable, which is how the library adapts to React Native, tests (`React.act` integration), and high‑timer‑count environments without forking.
- **Strict typing.** Heavy use of conditional/inferred generics (e.g. the `useQueries` tuple inference, `DataTag` typed keys) provides end‑to‑end type safety from `queryFn` return through `select` to the component.

---

## 8. Testing strategy

| Test type | Tooling | What it covers |
|---|---|---|
| Unit/behavioral | Vitest (jsdom) | Cache logic, observers, retryer, hydration, each adapter's hooks (`packages/*/src/__tests__`). |
| Type‑level | `tsd`‑style type tests via `test:types` | The complex generic inference (queries tuples, typed keys) across the **TS 5.4–6.0 matrix**. |
| Bundle size | size-limit | Each package stays within its byte budget — a regression gate, since bundle size is a core selling point. |
| Lint/static | ESLint + cspell + knip | Code style, spelling, dead code. |
| Monorepo integrity | sherif | Dependency version consistency across all 27 packages. |
| Publish artifacts | publint + arethetypeswrong | Validates `exports` maps, dual ESM/CJS resolution, and `.d.ts` correctness. |
| Real‑toolchain smoke | `integrations/*` | The library installed into real Next.js/webpack/Vite/Angular‑CLI apps. |

> [!NOTE]
> A few internal tests around retry cooldowns rely on fake timers; the `timeoutManager` abstraction exists partly to make timer‑dependent behavior deterministically testable.

---

## 9. Operations & contributing

### Adding a feature to the core

Because all behavior lives in `query-core`, most features are implemented there and surfaced through adapters only if they need framework‑specific glue (e.g. a new hook). The workflow:

1. Implement in `packages/query-core/src`, with unit + type tests.
2. Add a changeset (`pnpm changeset`).
3. If user‑facing in a framework, thread it through the relevant adapter(s) and the `docs/framework/*` reference.
4. `pnpm test:ci` must pass (the full eight‑target gate).

### Adding a new framework adapter

Mirror an existing adapter's structure: re‑export `@tanstack/query-core`, create framework‑idiomatic wrappers around `QueryObserver`/`MutationObserver`/`QueriesObserver` using `subscribe()` + `getCurrentResult()`, and provide a client‑provider mechanism. Keep logic out of the adapter — push it into the core.

### Versioning & migration

- All packages release **in lockstep**; pin them to the same version.
- Breaking changes ship as major versions with a **codemod** (`@tanstack/query-codemods`) and ESLint rules to ease migration (e.g. the v5 single‑object‑argument enforcement, surfaced at runtime as `Bad argument type. Starting with v5, only the "Object" form is allowed…`).

### Operational tuning checklist

- [ ] Set an app‑wide `staleTime` to control request volume.
- [ ] Decide `refetchOnWindowFocus`/`refetchOnReconnect` policy per backend cost.
- [ ] On SSR, set `gcTime: Infinity` server‑side and verify `dehydrate` error redaction.
- [ ] If persisting, audit `shouldDehydrateQuery` to exclude sensitive data; set a buster key tied to your release.
- [ ] Mount exactly one devtools instance and one `QueryClient`.
- [ ] Enable `@tanstack/eslint-plugin-query` recommended rules in CI.

---

## 10. Quick reference

### Public exports of `@tanstack/query-core`

`QueryClient`, `QueryCache`, `MutationCache`, `Query`, `Mutation`, `QueryObserver`, `InfiniteQueryObserver`, `QueriesObserver`, `MutationObserver`, `focusManager`, `onlineManager`, `notifyManager`, `timeoutManager`, `environmentManager`, `dehydrate`, `hydrate`, `defaultShouldDehydrateQuery`, `defaultShouldDehydrateMutation`, `CancelledError`, `hashKey`, `matchQuery`, `matchMutation`, `partialMatchKey`, `replaceEqualDeep`, `keepPreviousData`, `skipToken`, `shouldThrowError`, `isServer`, `experimental_streamedQuery`, plus all types.

### Status reference

| `status` | `fetchStatus` | Meaning |
|---|---|---|
| `pending` | `fetching` | First load in flight (`isLoading`). |
| `pending` | `paused` | Wants to load but offline. |
| `success` | `idle` | Has data, nothing running. |
| `success` | `fetching` | Has data, background refetch in flight (`isRefetching`). |
| `error` | `idle` | Fetch failed, holds the error. |

### Mutation status reference

`idle` → `pending` → `success` | `error`, surfaced as `isIdle`/`isPending`/`isSuccess`/`isError`, with `reset()` to return to `idle`.

### Environment behavior matrix

| Environment | `gcTime` default | `retry` default | Focus/online listeners |
|---|---|---|---|
| Browser (client) | 5 min | 3 | Active (via `window`) |
| Server (SSR) | `Infinity` | 0 | No‑op (no `window`) |
| React Native | 5 min | 3 | Inactive until wired via `setEventListener` |

---

_Generated June 2026 from TanStack/query at 3270e09. AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it. Unofficial; not affiliated with or reviewed by the project._
