Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/solid-beta-33-upgrade.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@tanstack/solid-query': patch
'@tanstack/solid-query-devtools': patch
'@tanstack/solid-query-persist-client': patch
---

chore: upgrade to solid v2 beta 33. `@tanstack/solid-query` now requires solid-js >= 2.0.0-beta.33 (peer range floor raised): the provider-owned hydration channel depends on beta.33's `normalizeIterator` buffered-replay conflation fix — on beta.32 and earlier, hydration that begins after multiple stream chunks have arrived silently drops later channel yields, leaving queries never primed and their components frozen.
5 changes: 5 additions & 0 deletions .changeset/solid-hydration-data-consumption.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/solid-query': patch
---

fix: prime the query cache during hydration through a provider-owned dehydration channel, so SSR-fetched queries come up warm instead of refetching on mount. `QueryClientProvider` streams dehydrated cache entries (query-core `dehydrate()` shapes) as each query settles during SSR — entries ride the same flush as the content that awaited them — and applies them on the client via query-core `hydrate()` (newer-wins) as they arrive, attaching each hydrated component's observer as soon as its entry is primed. The vestigial per-observer-result `hydrationData` copy is no longer serialized.
4 changes: 2 additions & 2 deletions examples/solid/astro/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
"@tanstack/solid-query": "^6.0.0-beta.7",
"@tanstack/solid-query-devtools": "^6.0.0-beta.7",
"astro": "^5.5.6",
"@solidjs/web": "2.0.0-beta.29",
"solid-js": "2.0.0-beta.29",
"@solidjs/web": "2.0.0-beta.33",
"solid-js": "2.0.0-beta.33",
"tailwindcss": "^3.4.7",
"typescript": "5.8.3"
}
Expand Down
4 changes: 2 additions & 2 deletions examples/solid/basic-graphql-request/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
"@tanstack/solid-query-devtools": "^6.0.0-beta.7",
"graphql": "^16.9.0",
"graphql-request": "^7.1.2",
"@solidjs/web": "2.0.0-beta.29",
"solid-js": "2.0.0-beta.29"
"@solidjs/web": "2.0.0-beta.33",
"solid-js": "2.0.0-beta.33"
},
"devDependencies": {
"typescript": "5.8.3",
Expand Down
4 changes: 2 additions & 2 deletions examples/solid/basic/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
"dependencies": {
"@tanstack/solid-query": "^6.0.0-beta.7",
"@tanstack/solid-query-devtools": "^6.0.0-beta.7",
"@solidjs/web": "2.0.0-beta.29",
"solid-js": "2.0.0-beta.29"
"@solidjs/web": "2.0.0-beta.33",
"solid-js": "2.0.0-beta.33"
},
"devDependencies": {
"typescript": "5.8.3",
Expand Down
4 changes: 2 additions & 2 deletions examples/solid/default-query-function/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
"dependencies": {
"@tanstack/solid-query": "^6.0.0-beta.7",
"@tanstack/solid-query-devtools": "^6.0.0-beta.7",
"@solidjs/web": "2.0.0-beta.29",
"solid-js": "2.0.0-beta.29"
"@solidjs/web": "2.0.0-beta.33",
"solid-js": "2.0.0-beta.33"
},
"devDependencies": {
"typescript": "5.8.3",
Expand Down
4 changes: 2 additions & 2 deletions examples/solid/offline/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
"@tanstack/solid-query-devtools": "^6.0.0-beta.7",
"@tanstack/solid-query-persist-client": "^6.0.0-beta.7",
"msw": "^2.6.6",
"@solidjs/web": "2.0.0-beta.29",
"solid-js": "2.0.0-beta.29"
"@solidjs/web": "2.0.0-beta.33",
"solid-js": "2.0.0-beta.33"
},
"devDependencies": {
"typescript": "5.8.3",
Expand Down
4 changes: 2 additions & 2 deletions examples/solid/simple/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
"dependencies": {
"@tanstack/solid-query": "^6.0.0-beta.7",
"@tanstack/solid-query-devtools": "^6.0.0-beta.7",
"@solidjs/web": "2.0.0-beta.29",
"solid-js": "2.0.0-beta.29"
"@solidjs/web": "2.0.0-beta.33",
"solid-js": "2.0.0-beta.33"
},
"devDependencies": {
"@tanstack/eslint-plugin-query": "^5.101.0",
Expand Down
4 changes: 2 additions & 2 deletions examples/solid/solid-start-streaming/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
"@solidjs/start": "^1.1.3",
"@tanstack/solid-query": "^6.0.0-beta.7",
"@tanstack/solid-query-devtools": "^6.0.0-beta.7",
"@solidjs/web": "2.0.0-beta.29",
"solid-js": "2.0.0-beta.29",
"@solidjs/web": "2.0.0-beta.33",
"solid-js": "2.0.0-beta.33",
"vinxi": "^0.5.3"
},
"engines": {
Expand Down
4 changes: 2 additions & 2 deletions integrations/solid-vite/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
"dependencies": {
"@tanstack/solid-query": "workspace:*",
"@tanstack/solid-query-devtools": "workspace:*",
"solid-js": "2.0.0-beta.29",
"solid-js": "2.0.0-beta.33",
"vite": "^6.4.1",
"vite-plugin-solid": "3.0.0-next.21",
"@solidjs/web": "2.0.0-beta.29"
"@solidjs/web": "2.0.0-beta.33"
}
}
1 change: 1 addition & 0 deletions knip.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"ignore": ["**/__testfixtures__/**"]
},
"packages/solid-query": {
"ignore": ["src/__tests__/fixtures/**"],
"ignoreDependencies": ["esbuild"]
},
"packages/solid-query-devtools": {
Expand Down
4 changes: 4 additions & 0 deletions packages/eslint-plugin-query/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ const config = defineConfig({
dir: './src',
watch: false,
globals: true,
// The first test run by a type-checked RuleTester (project: true) builds
// the full TS program for the ts-fixture, which can exceed vitest's 5s
// default under CI load when other Nx tasks share the runner.
testTimeout: 15_000,
coverage: {
enabled: !!process.env.CI,
provider: 'istanbul',
Expand Down
8 changes: 4 additions & 4 deletions packages/solid-query-devtools/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,13 @@
"devDependencies": {
"@babel/core": "^7.28.0",
"@babel/preset-typescript": "^7.18.6",
"@solidjs/signals": "^2.0.0-beta.29",
"@solidjs/signals": "^2.0.0-beta.33",
"@solidjs/testing-library": "^0.8.10",
"@solidjs/web": "2.0.0-beta.29",
"@solidjs/web": "2.0.0-beta.33",
"@tanstack/solid-query": "workspace:*",
"babel-preset-solid": "2.0.0-beta.29",
"babel-preset-solid": "2.0.0-beta.33",
"npm-run-all2": "^5.0.0",
"solid-js": "2.0.0-beta.29",
"solid-js": "2.0.0-beta.33",
"tsup-preset-solid": "^2.2.0",
"vite-plugin-solid": "3.0.0-next.21"
},
Expand Down
6 changes: 3 additions & 3 deletions packages/solid-query-persist-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,12 @@
"@babel/core": "^7.28.0",
"@babel/preset-typescript": "^7.18.6",
"@solidjs/testing-library": "^0.8.10",
"@solidjs/web": "2.0.0-beta.29",
"@solidjs/web": "2.0.0-beta.33",
"@tanstack/query-test-utils": "workspace:*",
"@tanstack/solid-query": "workspace:*",
"babel-preset-solid": "2.0.0-beta.29",
"babel-preset-solid": "2.0.0-beta.33",
"npm-run-all2": "^5.0.0",
"solid-js": "2.0.0-beta.29",
"solid-js": "2.0.0-beta.33",
"tsup-preset-solid": "^2.2.0",
"vite-plugin-solid": "3.0.0-next.21"
},
Expand Down
8 changes: 4 additions & 4 deletions packages/solid-query/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,15 +71,15 @@
"@babel/core": "^7.28.0",
"@babel/preset-typescript": "^7.18.6",
"@solidjs/testing-library": "^0.8.10",
"@solidjs/web": "2.0.0-beta.29",
"@solidjs/web": "2.0.0-beta.33",
"@tanstack/query-test-utils": "workspace:*",
"babel-preset-solid": "2.0.0-beta.29",
"babel-preset-solid": "2.0.0-beta.33",
"npm-run-all2": "^5.0.0",
"solid-js": "2.0.0-beta.29",
"solid-js": "2.0.0-beta.33",
"tsup-preset-solid": "^2.2.0",
"vite-plugin-solid": "3.0.0-next.21"
},
"peerDependencies": {
"solid-js": ">=2.0.0-beta.0 <3.0.0"
"solid-js": ">=2.0.0-beta.33 <3.0.0"
}
}
62 changes: 60 additions & 2 deletions packages/solid-query/src/QueryClientProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,21 @@
import { createContext, onCleanup, useContext } from 'solid-js'
import {
createContext,
createRenderEffect,
createSignal,
onCleanup,
useContext,
} from 'solid-js'
import {
HydrationCoordinatorContext,
createHydrationCoordinator,
createServerDehydrationChannel,
} from './hydrationChannel'
import type { DehydrationChannelYield } from './hydrationChannel'
import type { QueryClient } from './QueryClient'
import type { JSX } from '@solidjs/web'

const isServer = typeof window === 'undefined'

export const QueryClientContext = createContext<(() => QueryClient) | null>(
null,
)
Expand Down Expand Up @@ -30,9 +44,53 @@ export const QueryClientProvider = (
props.client.mount()
onCleanup(() => props.client.unmount())

// Library-owned serialization channel for SSR dehydration.
//
// Server: the computation's value IS the channel's async iterable, so
// Solid serializes it through its normal per-computation signal path:
// the server runtime tees the iterator into the hydration serializer
// (`ctx.serialize(id, tapped)` in solid-js' `processResult`) and
// seroval streams each cumulative dehydrated-cache snapshot to the
// client as a chunk riding the SSR stream, with the entry objects
// inside deduplicated by reference against everything else in the
// payload. Nothing reads the signal during SSR, so it never suspends
// anything.
//
// Client, hydrating: Solid replays the serialized iterable through the
// per-computation signal path (`hydrateSignalFromAsyncIterable`).
// Yields that were still buffered when hydration began are conflated
// to the LATEST yield (`normalizeIterator`) — lossless here because
// every yield is a cumulative snapshot — and live yields after that
// apply one at a time. Requires a solid-js build with the buffered
// async-iterable replay conflation fix (> 2.0.0-beta.32): before it,
// the replay dropped every buffered yield after the first, including
// the terminal `done` snapshot. The render effect below hands each
// signal value to the coordinator, which primes the QueryClient via
// query-core hydrate() (newer-wins) and unblocks `useBaseQuery`
// subscribers waiting on their query's entry.
//
// Client, fresh mount: the compute returns undefined and the effect
// never fires.
const [channelValue] = createSignal<DehydrationChannelYield | undefined>(
() => (isServer ? createServerDehydrationChannel(props.client) : undefined),
)
const coordinator = isServer
? null
: createHydrationCoordinator(() => props.client)
createRenderEffect(
() => (isServer ? undefined : channelValue()),
(value) => {
if (value && coordinator) {
coordinator.applyYield(value)
}
},
)

return (
<QueryClientContext value={() => props.client}>
{props.children}
<HydrationCoordinatorContext value={coordinator}>
{props.children}
</HydrationCoordinatorContext>
</QueryClientContext>
)
}
66 changes: 66 additions & 0 deletions packages/solid-query/src/__tests__/fixtures/hydration/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/**
* Shared fixture app for the SSR → hydration tests.
*
* This module is compiled twice by the test harness (see
* `src/__tests__/hydration.test.tsx`): once with the Solid SSR transform
* (consumed by `entry-server.tsx`) and once with the hydratable DOM
* transform (consumed by `entry-client.tsx`).
*/
import { Loading } from 'solid-js'
import { QueryClientProvider, useQuery } from '@tanstack/solid-query'
import type { QueryClient } from '@tanstack/solid-query'

export interface FetchCounts {
fresh: number
stale: number
}

export interface AppProps {
client: QueryClient
/** Marker baked into the query data so tests can tell where it was fetched. */
source: 'server' | 'client'
counts: FetchCounts
}

const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))

function Queries(props: AppProps) {
// Fresh for a minute: after hydration this must NOT refetch on mount.
const fresh = useQuery(() => ({
queryKey: ['fresh'],
queryFn: async () => {
props.counts.fresh++
await sleep(5)
return `fresh-${props.source}`
},
staleTime: 60_000,
}))

// Immediately stale: normal staleness rules mean this refetches on mount.
const stale = useQuery(() => ({
queryKey: ['stale'],
queryFn: async () => {
props.counts.stale++
await sleep(5)
return `stale-${props.source}`
},
staleTime: 0,
}))

return (
<div>
<span id="fresh">{fresh.data}</span>
<span id="stale">{stale.data}</span>
</div>
)
}

export function App(props: AppProps) {
return (
<QueryClientProvider client={props.client}>
<Loading fallback={<div>loading</div>}>
<Queries {...props} />
</Loading>
</QueryClientProvider>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/**
* Streaming fixture for the SSR → hydration window tests.
*
* Two Loading boundaries: `header` resolves fast (its boundary content
* streams almost immediately), `feed` holds the stream open for ~250ms.
* This lets tests hydrate the fast section while the stream is still
* in flight and probe the window between cache priming and subscriber
* attach.
*/
import { Loading } from 'solid-js'
import { QueryClientProvider, useQuery } from '@tanstack/solid-query'
import type { QueryClient } from '@tanstack/solid-query'

export interface StreamCounts {
header: number
feed: number
}

export interface StreamAppProps {
client: QueryClient
source: 'server' | 'client'
counts: StreamCounts
}

const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))

function HeaderQuery(props: StreamAppProps) {
const query = useQuery(() => ({
queryKey: ['header'],
queryFn: async () => {
props.counts.header++
await sleep(5)
return `header-${props.source}`
},
staleTime: 60_000,
}))
return <span id="header">{query.data}</span>
}

function FeedQuery(props: StreamAppProps) {
const query = useQuery(() => ({
queryKey: ['feed'],
queryFn: async () => {
props.counts.feed++
await sleep(250)
return `feed-${props.source}`
},
staleTime: 60_000,
}))
return <span id="feed">{query.data}</span>
}

export function StreamApp(props: StreamAppProps) {
return (
<QueryClientProvider client={props.client}>
<div>
<Loading fallback={<div>loading-header</div>}>
<HeaderQuery {...props} />
</Loading>
<Loading fallback={<div>loading-feed</div>}>
<FeedQuery {...props} />
</Loading>
</div>
</QueryClientProvider>
)
}
Loading
Loading