TanStack/query's persistence and devtools layers carry the highest activity risk — 3 functions to address first

experimental_createQueryPersister in createPersister.ts tops TanStack/query's activity-risk rankings with fan-out of 27 and nesting depth of 6, driven by a recent commit activity of 15.79 — making it a live regression risk, not a cleanup backlog item.

Stephen Collins ·
oss typescript refactoring code-health

Antipatterns Detected

exit_heavy5complex_branching4deeply_nested4god_function4long_function4

Key Points

What is a god function and why does it matter in TanStack/query?

A god function is one that takes on too many responsibilities at once, resulting in high complexity, many dependencies, and a wide blast radius when it changes. In TanStack/query, `experimental_createQueryPersister` and `Explorer` both carry this pattern — `Explorer` calls 38 distinct functions, meaning a change to any one of those dependencies can surface unexpected behavior in the devtools component.

How do I reduce fan-out in TypeScript?

Extract cohesive groups of callees into dedicated helper modules or hooks, then have the parent function depend only on those higher-level abstractions rather than their individual implementations. For a component like `Explorer`, splitting rendering responsibilities into focused sub-components naturally reduces the number of direct dependencies.

Is TanStack/query actively maintained?

Yes — the activity scores in this analysis are driven by recent, frequent commits. The top hotspot carries a recent commit activity of 15.79 and even the fifth-ranked function scores 14.53, indicating that multiple subsystems are seeing sustained development activity, not just occasional patches.

How do I reproduce this analysis?

Run the Hotspots CLI against the TanStack/query repository at commit 391db36 to reproduce these exact scores.

What does activity-weighted risk mean?

Complexity × recent commit frequency — functions that are hard to understand AND actively changing are the highest priority for refactoring.

Across 3,031 functions in TanStack/query, 84 have reached critical risk band — and the single highest activity-weighted risk score belongs to experimental_createQueryPersister in packages/query-persist-client-core/src/createPersister.ts, which carries a recent commit activity of 15.79. That score reflects recent, frequent commit activity against a function that already has a cyclomatic complexity of 14, nesting depth of 6, and fan-out of 27 — the combination means every change lands in structurally dense territory with wide coupling surface. The persistence subsystem and the devtools Explorer component dominate the top five hotspots, signaling that these two layers deserve immediate review attention.

The table below ranks functions by activity-weighted risk — a score that multiplies structural complexity by recent commit frequency. A function that is both hard to understand (high cyclomatic complexity) and actively changing is a higher priority than one that is complex but untouched. CC = cyclomatic complexity (independent execution paths); ND = max nesting depth; FO = fan-out (distinct callees).

Top 5 Hotspots

FunctionFileRiskCCNDFO
experimental_createQueryPersisterpackages/query-persist-client-core/src/createPersister.ts16.814627
<anonymous>packages/query-codemods/src/v5/remove-overloads/transformers/filter-aware-usage-transformer.cjs16.323520
<anonymous>packages/query-codemods/src/v5/remove-overloads/transformers/filter-aware-usage-transformer.cjs15.711530
restoreQueriespackages/query-persist-client-core/src/createPersister.ts15.61968
Explorerpackages/query-devtools/src/Explorer.tsx15.426338

Codemod / Tooling Files in Results

Two of the top five hotspots are anonymous functions inside packages/query-codemods/src/v5/remove-overloads/transformers/filter-aware-usage-transformer.cjs. This is a codemod transformer — a migration tool that rewrites user code during the v5 upgrade path, not application runtime code. It scores highly because it contains dense AST-traversal branching (CC 23 and CC 11, ND 5 for both, fan-out of 20 and 30 respectively) and has seen recent commit activity (recent commit activity 15.42 and 14.76). These scores reflect legitimate tooling complexity, not production risk. To exclude codemod files from future hotspot runs, add "exclude": ["packages/query-codemods/**"] to your .hotspotsrc.json.

Hotspot Analysis

experimental_createQueryPersister — packages/query-persist-client-core/src/createPersister.ts

Based on its name and location, this function is the factory entry point for the client-side query persistence feature — the experimental_ prefix is a deliberate signal that this API is not yet stable and is under active iteration, which is confirmed by its recent commit activity of 15.79 (the highest in the repo). A cyclomatic complexity of 14 means at least 14 independent execution paths run through it, a nesting depth of 6 means reasoning about any single path requires tracking six levels of control flow simultaneously, and a fan-out of 27 means it calls 27 distinct functions — making it a god function whose blast radius spans a significant portion of the persistence package. The complex_branching, deeply_nested, and exit_heavy patterns all co-occur here: the exit-heavy classification alone means test coverage must account for multiple early-return paths, each of which could mask a regression when the function changes.

Recommendation: Before the next change lands, write characterization tests that exercise each of the major exit paths to lock in current behavior. Then extract the branching sub-concerns — likely persistence strategy selection, cache validation, and serialization — into named helper functions to reduce both the fan-out coupling and the nesting depth in the factory itself.

restoreQueries — packages/query-persist-client-core/src/createPersister.ts

Living in the same file as experimental_createQueryPersister, restoreQueries likely handles rehydrating persisted query state back into the query client on startup or reconnect. Its cyclomatic complexity of 19 and nesting depth of 6 are both higher than its sibling function’s, suggesting the rehydration logic contains more conditional branching — possibly handling stale-data checks, version mismatches, or partial cache states — and its recent commit activity of 14.52 confirms it is changing at nearly the same pace. The exit_heavy and complex_branching patterns reinforce that there are many paths through this function, each a distinct test obligation.

Recommendation: Map the 19 independent paths before touching this function — a coverage report scoped to restoreQueries will reveal which branches are currently untested and highest risk. Consider decomposing the function along the lines of validation, deserialization, and cache-insertion responsibilities.

Explorer — packages/query-devtools/src/Explorer.tsx

The Explorer component in the devtools package is the UI tree renderer that allows developers to inspect query cache state at runtime. Its cyclomatic complexity of 26 is the second highest among the named top hotspots, and its fan-out of 38 — the highest of any named function in the data — means it depends on 38 distinct callees, making it a hub function with an unusually wide blast radius. Notably its nesting depth is only 3, so the complexity is driven by breadth of branching rather than deep nesting, consistent with a component that must render many different data shapes and states. With a recent commit activity of 14.53 it is actively changing, meaning the devtools UI is a live regression surface despite being a secondary tool.

Recommendation: Audit the 38 callees to identify which rendering concerns can be extracted into standalone sub-components or hooks; reducing fan-out is the highest-leverage action here. The god_function and long_function patterns both indicate this component is doing too much in one place — splitting by data type or rendering context is a natural seam.

Patterns Found

Antipatterns detected across the top functions in this snapshot:

PatternOccurrences
exit_heavy5
complex_branching4
deeply_nested4
god_function4
long_function4

These labels belong to two tiers — Tier 1 (structural): complex_branching, deeply_nested, exit_heavy, long_function, god_function. Tier 2 (relational/temporal): hub_function, cyclic_hub, middle_man, neighbor_risk, stale_complex, churn_magnet, shotgun_target, volatile_god.

Key Takeaways

  • experimental_createQueryPersister has a recent commit activity of 15.79 — the highest in the repo — against a function the experimental_ prefix already marks as unsettled. Any change here is a regression risk until characterization tests cover its exit-heavy branching paths.
  • restoreQueries in the same file has cyclomatic complexity 19 with nesting depth 6 and is changing at recent commit activity 14.52: decomposing it by responsibility (validation, deserialization, cache-insertion) will reduce both the bug surface and the per-change review burden.
  • The Explorer devtools component’s fan-out of 38 is the broadest coupling point among named hotspots — splitting it into focused sub-components by rendering concern will contain the blast radius of future devtools changes.

Reproduce This Analysis

git clone https://github.com/TanStack/query
cd query
git checkout 391db367b3e7adeff9a0dd647474d1575a31a787
hotspots analyze . --mode snapshot --explain-patterns --force

To run the same analysis on your own codebase, run hotspots analyze . --mode snapshot in any local git repo — no configuration required.

Hotspots highlights structural and activity risk — not “bad code.” Findings are a prioritization aid, not a bug predictor. Editorial policy →

Run this on your own codebase

Hotspots runs locally in under a minute — no account, no data leaves your machine.

macOS
$ brew install Stephen-Collins-tech/tap/hotspots
Linux / cargo
$ cargo install hotspots-cli
Run in any repo
$ hotspots analyze .
★ Star on GitHub

Related Analyses