Skip to content

fix(query-persist-client-core): restore undefined persisted query payloads#10842

Open
raashish1601 wants to merge 2 commits into
TanStack:mainfrom
raashish1601:fix/persister-restore-undefined-data
Open

fix(query-persist-client-core): restore undefined persisted query payloads#10842
raashish1601 wants to merge 2 commits into
TanStack:mainfrom
raashish1601:fix/persister-restore-undefined-data

Conversation

@raashish1601
Copy link
Copy Markdown
Contributor

@raashish1601 raashish1601 commented May 30, 2026

Summary

  • Return restored query payload in experimental_createQueryPersister as a presence-preserving result so persisted undefined data is treated as a real restore hit.
  • Add regression test in createPersister.test.ts to ensure persisted undefined payloads are reused without refetching when
    efetchOnRestore: false.

Validation

px -y pnpm@11.0.0 vitest run packages/query-persist-client-core/src/tests/createPersister.test.ts

Summary by CodeRabbit

  • Bug Fixes

    • Improved persister restoration to correctly handle queries with undefined data.
  • Improvements

    • Enhanced server-environment detection in the VueQuery plugin for better server-side rendering support.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 30, 2026

📝 Walkthrough

Walkthrough

This PR refactors internal persistence data shapes by wrapping restored query payloads in a PersistedResult<T> container object. It also migrates Vue plugin server-environment detection from a static isServer import to a dynamic environmentManager.isServer() method call, with corresponding test coverage for undefined-data restoration and server-mode behavior.

Changes

Query Persistence and Environment Detection Updates

Layer / File(s) Summary
Persisted Result Type Wrapper and Restore Flow
packages/query-persist-client-core/src/createPersister.ts
Introduces PersistedResult<T> type definition, updates retrieveQuery return signature to Promise<PersistedResult<T> | undefined>, wraps restored data as { data: ... }, and adjusts persisterFn restore-path to unwrap via restoredData.data.
Persister Restoration Test for Undefined Data
packages/query-persist-client-core/src/__tests__/createPersister.test.ts
Test verifies that persister correctly restores and maintains undefined data values without triggering refetches.
Environment Detection Migration in Vue Plugin
packages/vue-query/src/vueQueryPlugin.ts
Replaces isServer static import with environmentManager and updates mount conditional to call environmentManager.isServer().
Server Environment Test Coverage
packages/vue-query/src/__tests__/vueQueryPlugin.test.ts
Test confirms plugin does not mount the query client when environmentManager reports server environment mode.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related issues

Poem

🐰 A wrapper hugs each persisted dream,
Undefined becomes supreme,
The environment knows where to run,
Server or browser, choose as one!
Type-safe persistence, tests so bright—
The query restore, now done right! 🌟

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main fix: restoring undefined persisted query payloads in the query-persist-client-core package.
Description check ✅ Passed The description provides a clear summary of changes and includes validation details, though it lacks the full checklist from the template (Contributing guide and changeset items are incomplete).
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/query-persist-client-core/src/createPersister.ts (1)

156-156: JSON serialization silently drops undefined values.

The default serializer JSON.stringify omits object properties with undefined values, so { state: { data: undefined } } becomes '{"state":{}}'. On deserialization, persistedQuery.state.data is undefined (missing property access), which coincidentally produces the correct result { data: undefined }. This works but is fragile: custom serializers that explicitly encode undefined differently (e.g., as null or a sentinel) may break this assumption.

Consider documenting this behavior in the serialize option's JSDoc, or use a more explicit encoding like { hasData: boolean, data: T } if custom serializers are expected to handle undefined differently.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/query-persist-client-core/src/createPersister.ts` at line 156, The
code assumes missing vs explicit undefined are equivalent when returning
persistedQuery.state.data in createPersister (the line returning { data:
persistedQuery.state.data as T }), which breaks with custom serializers that
encode undefined differently; change the persisted shape to an explicit envelope
like { hasData: boolean, data?: T } when serializing and deserializing so
presence is unambiguous: update the serializer/deserializer usage in
createPersister (and where persistedQuery.state is created/read) to write
hasData = Object.prototype.hasOwnProperty.call(payload, 'data') (or data !==
undefined) and to return { data: hasData ? payload.data : undefined }, and
update related types/JSDoc for the serialize option accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/query-persist-client-core/src/createPersister.ts`:
- Line 132: The public API for retrieveQuery on the object returned by
experimental_createQueryPersister now returns Promise<PersistedResult<T> |
undefined> (an object with a data property) instead of Promise<T | undefined>;
update all consumer-facing docs (docs/framework/react/plugins/createPersister.md
and docs/framework/vue/plugins/createPersister.md) to reflect the new signature
and show unwrapping via result?.data, and either provide a note/example in the
docs demonstrating how to migrate callers of retrieveQuery to use result.data or
restore a thin helper on the persister (e.g., retrieveQueryValue) that returns T
| undefined for backward compatibility while keeping retrieveQuery typed as
PersistedResult<T> | undefined.

---

Nitpick comments:
In `@packages/query-persist-client-core/src/createPersister.ts`:
- Line 156: The code assumes missing vs explicit undefined are equivalent when
returning persistedQuery.state.data in createPersister (the line returning {
data: persistedQuery.state.data as T }), which breaks with custom serializers
that encode undefined differently; change the persisted shape to an explicit
envelope like { hasData: boolean, data?: T } when serializing and deserializing
so presence is unambiguous: update the serializer/deserializer usage in
createPersister (and where persistedQuery.state is created/read) to write
hasData = Object.prototype.hasOwnProperty.call(payload, 'data') (or data !==
undefined) and to return { data: hasData ? payload.data : undefined }, and
update related types/JSDoc for the serialize option accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0269f704-bfb9-4474-b9c0-15b2bb400b9a

📥 Commits

Reviewing files that changed from the base of the PR and between 7fa2781 and 03cb202.

📒 Files selected for processing (4)
  • packages/query-persist-client-core/src/__tests__/createPersister.test.ts
  • packages/query-persist-client-core/src/createPersister.ts
  • packages/vue-query/src/__tests__/vueQueryPlugin.test.ts
  • packages/vue-query/src/vueQueryPlugin.ts

queryHash: string,
afterRestoreMacroTask?: (persistedQuery: PersistedQuery) => void,
) {
): Promise<PersistedResult<T> | undefined> {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Search for direct usages of retrieveQuery outside this file to assess impact

# Search for retrieveQuery calls that may be affected by the return type change
rg -nP --type=ts --type=tsx -C3 'retrieveQuery\s*\(' \
  -g '!**/createPersister.ts' \
  -g '!**/__tests__/**' \
  -g '!**/node_modules/**'

Repository: TanStack/query

Length of output: 86


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Inspect createPersister.ts around retrieveQuery export + its return type
FILE="packages/query-persist-client-core/src/createPersister.ts"
if [ -f "$FILE" ]; then
  echo "== retrieveQuery export context =="
  rg -n "export\s+(async\s+)?function\s+retrieveQuery|export\s*\{\s*retrieveQuery|retrieveQuery\s*=" "$FILE"
  echo
  # Print a small window around the first match
  LINE=$(rg -n "export\s+(async\s+)?function\s+retrieveQuery|export\s*\{\s*retrieveQuery|retrieveQuery\s*=" "$FILE" | head -n1 | cut -d: -f1)
  START=$((LINE-20))
  END=$((LINE+60))
  sed -n "${START},${END}p" "$FILE" | nl -ba
fi

# 2) Search for direct usages of retrieveQuery outside createPersister.ts
echo
echo "== retrieveQuery call sites outside createPersister.ts =="
rg -n "retrieveQuery\s*\(" \
  --glob "!**/createPersister.ts" \
  --glob "!**/__tests__/**" \
  --glob "!**/node_modules/**" \
  --glob "**/*.ts" \
  --glob "**/*.tsx" \
  -S

Repository: TanStack/query

Length of output: 93


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate retrieveQuery in the target file
FILE="packages/query-persist-client-core/src/createPersister.ts"
echo "== locate retrieveQuery in $FILE =="
if [ -f "$FILE" ]; then
  rg -n "retrieveQuery" "$FILE" || true
  echo
  # show around the most likely export area (lines near 371 as mentioned in review comment)
  sed -n '340,420p' "$FILE" | nl -ba | sed -n '1,120p'
  echo
  # show around line 120-160 for the type snippet mentioned
  sed -n '100,170p' "$FILE" | nl -ba
else
  echo "File not found: $FILE"
fi

echo
echo "== locate retrieveQuery across repository (excluding node_modules) =="
rg -n "retrieveQuery" \
  --glob "!**/node_modules/**" \
  --glob "!**/dist/**" \
  --glob "!**/build/**" \
  -S

Repository: TanStack/query

Length of output: 326


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="packages/query-persist-client-core/src/createPersister.ts"
echo "== show retrieveQuery definition around line 129 =="
sed -n '110,170p' "$FILE" | sed -n '1,120p'

echo
echo "== show internal call around line 216 (restoredData) =="
sed -n '190,260p' "$FILE" | sed -n '1,120p'

echo
echo "== show export block around line 371 =="
sed -n '350,410p' "$FILE" | sed -n '1,120p'

echo
echo "== locate retrieveQuery references across repository (excluding node_modules/dist/build) =="
rg -n "retrieveQuery\s*<|retrieveQuery\s*\(" \
  --glob "!**/node_modules/**" \
  --glob "!**/dist/**" \
  --glob "!**/build/**" \
  -S

Repository: TanStack/query

Length of output: 5586


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n "retrieveQuery<T>\(queryHash" docs packages -S || true

echo
echo "== docs/framework/react/plugins/createPersister.md excerpt =="
sed -n '80,130p' docs/framework/react/plugins/createPersister.md | nl -ba | head -n 80 || true

echo
echo "== docs/framework/vue/plugins/createPersister.md excerpt =="
sed -n '70,120p' docs/framework/vue/plugins/createPersister.md | nl -ba | head -n 70 || true

echo
echo "== search for PersistedResult in docs/framework/** createPersister =="
rg -n "PersistedResult" docs/framework/react/plugins/createPersister.md docs/framework/vue/plugins/createPersister.md -S || true

echo
echo "== search for retrieveQuery return type hints in typescript files =="
rg -n "retrieveQuery.*Promise<" packages docs -S || true

Repository: TanStack/query

Length of output: 890


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="packages/query-persist-client-core/src/createPersister.ts"
echo "== find PersistedResult definition in createPersister.ts =="
rg -n "type\s+PersistedResult|interface\s+PersistedResult|export\s+type\s+PersistedResult|PersistedResult\s*<" "$FILE" -S || true

echo
echo "== find retrieveQuery signature types near its declaration =="
sed -n '120,155p' "$FILE" | sed -n '1,120p'

echo
echo "== find where createPersister returns exported API shape (persister object) =="
rg -n "return\s*\{\s*persisterFn|return\s*\{" "$FILE" -n -S || true

echo
echo "== look for exported type(s) for the persister returned by createPersister =="
rg -n "export (type|interface) .*Persister|createPersister.*export|PersisterOptions" packages/query-persist-client-core/src -S || true

Repository: TanStack/query

Length of output: 2447


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="packages/query-persist-client-core/src/createPersister.ts"
echo "== show top of createPersister.ts (types + exports) =="
sed -n '1,120p' "$FILE"

echo
echo "== show createPersister function signature and return statement =="
sed -n '120,220p' "$FILE"
sed -n '320,410p' "$FILE"

echo
echo "== check for public return type/interface mentioning retrieveQuery =="
rg -n "createPersister|retrieveQuery|Persister" "$FILE"

echo
echo "== inspect docs for retrieveQuery return type formatting around those lines =="
sed -n '85,120p' docs/framework/react/plugins/createPersister.md
sed -n '80,115p' docs/framework/vue/plugins/createPersister.md

Repository: TanStack/query

Length of output: 11743


Fix/communicate the public retrieveQuery return type change

retrieveQuery is exposed on the persister object returned by experimental_createQueryPersister, and it now returns Promise<PersistedResult<T> | undefined> (i.e., { data: T }) instead of Promise<T | undefined>. Consumers must unwrap result.data.

Docs are still written for the old signature in docs/framework/react/plugins/createPersister.md and docs/framework/vue/plugins/createPersister.md.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/query-persist-client-core/src/createPersister.ts` at line 132, The
public API for retrieveQuery on the object returned by
experimental_createQueryPersister now returns Promise<PersistedResult<T> |
undefined> (an object with a data property) instead of Promise<T | undefined>;
update all consumer-facing docs (docs/framework/react/plugins/createPersister.md
and docs/framework/vue/plugins/createPersister.md) to reflect the new signature
and show unwrapping via result?.data, and either provide a note/example in the
docs demonstrating how to migrate callers of retrieveQuery to use result.data or
restore a thin helper on the persister (e.g., retrieveQueryValue) that returns T
| undefined for backward compatibility while keeping retrieveQuery typed as
PersistedResult<T> | undefined.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant