From 0eb50e54870e77802f1bdeccf85f738944d77840 Mon Sep 17 00:00:00 2001 From: Ryan Carniato Date: Mon, 10 Aug 2026 00:51:05 -0700 Subject: [PATCH 1/8] fix(solid-query): hydrate the query cache through a provider-owned dehydration channel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit During SSR the adapter fetches queries and serializes each observer result, but since the Solid 2 rewrite dropped createResource's onHydrated hook nothing on the client ever primed the QueryClient cache: it came up cold after hydration and every observer refetched on mount, even for data well within staleTime. QueryClientProvider now owns a serialization channel: an async store whose generator emits cumulative snapshots of the dehydrated cache (query-core dehydrate() shapes) as queries settle during SSR. Solid serializes it through the normal per-computation path, so entries stream progressively and flush before the boundary content that awaited them. The channel closes itself on cache quiescence so the SSR stream can complete. On the client the provider applies each yield via query-core hydrate() (newer-wins) and useBaseQuery attaches each hydrated component's observer as soon as its query's entry is primed (or the channel completes), restoring normal mount semantics: fresh data does not refetch, stale data does, and earlier cache writes are reconciled at attach. The channel is store-shaped because Solid's hydration replay of signal-shaped async iterables collapses buffered yields into the latest result (dropping entries whenever hydration starts after their chunks arrived), while the store replay applies every yield in order. Yields are cumulative so collapsing intermediate states is lossless; entry objects keep their identity so seroval emits each entry once. The replay itself is detected without internals — a real Promise runs its executor synchronously, the hydration mock does not — leaving the adapter with zero sharedConfig or hydration-registry usage. The vestigial per-observer-result hydrationData copy is no longer serialized; nothing consumed it. Co-authored-by: Cursor --- .../solid-hydration-data-consumption.md | 5 + .../solid-query/src/QueryClientProvider.tsx | 82 +++++- packages/solid-query/src/hydrationChannel.ts | 254 ++++++++++++++++++ packages/solid-query/src/useBaseQuery.ts | 76 +++++- 4 files changed, 402 insertions(+), 15 deletions(-) create mode 100644 .changeset/solid-hydration-data-consumption.md create mode 100644 packages/solid-query/src/hydrationChannel.ts diff --git a/.changeset/solid-hydration-data-consumption.md b/.changeset/solid-hydration-data-consumption.md new file mode 100644 index 00000000000..2dec0849432 --- /dev/null +++ b/.changeset/solid-hydration-data-consumption.md @@ -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. diff --git a/packages/solid-query/src/QueryClientProvider.tsx b/packages/solid-query/src/QueryClientProvider.tsx index 998c42eed06..ded26c84bb4 100644 --- a/packages/solid-query/src/QueryClientProvider.tsx +++ b/packages/solid-query/src/QueryClientProvider.tsx @@ -1,7 +1,22 @@ -import { createContext, onCleanup, useContext } from 'solid-js' +import { + createContext, + createRenderEffect, + createStore, + onCleanup, + snapshot, + 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, ) @@ -30,9 +45,72 @@ export const QueryClientProvider = ( props.client.mount() onCleanup(() => props.client.unmount()) + // Library-owned serialization channel for SSR dehydration. + // + // Server: the store's initializer returns an async generator that + // writes cumulative dehydrated-query snapshots into the draft as + // queries settle during SSR. It is a plain async store computation, so + // Solid serializes it through its normal per-computation path: each + // yield's draft mutations ride the SSR stream as store patches (the + // same flush as the content that awaited them), and the entry objects + // inside them are deduplicated by reference (seroval) against + // everything else in the payload. + // + // The channel is store-shaped rather than signal-shaped on purpose: + // Solid's hydration replay applies *every* buffered yield of a store's + // async iterable in order (`hydrateStoreFromAsyncIterable`), while a + // signal's replay collapses buffered yields into the latest — and its + // final done-result supersedes them — which would drop entries whenever + // hydration begins after their chunks already arrived. + // + // Client, hydrating: the store replays from the serialized value; the + // render effect below applies each state of the channel to the + // QueryClient via query-core hydrate() (newer-wins) and unblocks + // `useBaseQuery` subscribers waiting on their query's entry. + // + // Client, fresh mount: the initializer returns undefined and the store + // keeps its (empty) initial value. + const [channelState] = createStore( + (draft) => { + if (!isServer) return undefined + const channel = createServerDehydrationChannel(props.client) + return (async function* () { + // Settle the store's serialized first snapshot as the empty + // initial state, so every entry travels as a patch and keeps its + // object identity for seroval's reference deduplication (the + // first snapshot is JSON-cloned by the runtime, which would + // break it). + yield undefined + for await (const value of channel) { + draft.entries = value.entries + draft.done = value.done + yield undefined + } + })() + }, + { entries: [], done: false }, + ) + const coordinator = isServer + ? null + : createHydrationCoordinator(() => props.client) + createRenderEffect( + () => + isServer + ? undefined + : { entries: channelState.entries, done: channelState.done }, + (value) => { + if (value && coordinator && (value.entries.length > 0 || value.done)) { + // Unwrap the store proxies so raw entry objects reach the cache. + coordinator.applyYield(snapshot(value) as DehydrationChannelYield) + } + }, + ) + return ( props.client}> - {props.children} + + {props.children} + ) } diff --git a/packages/solid-query/src/hydrationChannel.ts b/packages/solid-query/src/hydrationChannel.ts new file mode 100644 index 00000000000..e26e66c6d7e --- /dev/null +++ b/packages/solid-query/src/hydrationChannel.ts @@ -0,0 +1,254 @@ +import { hydrate } from '@tanstack/query-core' +import { createContext, runWithOwner } from 'solid-js' +import type { DehydratedState, QueryState } from '@tanstack/query-core' +import type { QueryClient } from './QueryClient' + +type DehydratedQueryEntry = DehydratedState['queries'][number] + +/** + * A single message on the dehydration channel. `entries` is *cumulative* — + * every yield carries all entries settled so far. Two reasons: + * + * - Solid's hydration replay of async-iterable values collapses + * intermediate yields that are already buffered when the client pulls + * (`normalizeIterator` drains synchronously available results and keeps + * only the latest), so each yield must be self-contained. + * - Entry objects keep their identity across yields, so seroval's + * cross-reference serialization emits each entry once and later yields + * only reference it — the cumulative shape costs bytes proportional to + * the number of entries, not its square. + * + * `done: true` marks the final yield. The client uses it to release + * subscribers still waiting for entries that will never arrive (e.g. + * queries that errored during SSR and were not dehydrated). + */ +export interface DehydrationChannelYield { + entries: Array + done: boolean +} + +/** + * Server side of the library-owned serialization channel. + * + * Returns an AsyncIterable that yields a cumulative snapshot of the + * dehydrated query cache (success entries, per query-core `dehydrate()` + * shapes) every time a query settles during SSR. `QueryClientProvider` + * holds it as a signal value, so Solid serializes it through the normal + * per-computation path: the server runtime tees the iterator into the + * hydration serializer (`ctx.serialize(id, tapped)` in solid-js' + * `processResult`) and seroval streams each yield to the client as a + * patch chunk riding the SSR stream. + * + * The iterable must terminate for the SSR stream to complete: the + * hydration serializer's `flush()` only fires its `onDone` once all + * pending streams have closed, and the render root is disposed *after* + * that, so neither `onCleanup` nor the serializer itself can close the + * channel. Instead the channel closes itself on cache quiescence: after + * every cache event (and once at creation) it schedules a timer-task + * check; if no query is fetching by then, no further settle can occur — + * suspense retry passes that start waterfall fetches are scheduled on + * microtasks, so they have begun before the check runs — and the channel + * emits its final cumulative snapshot with `done: true` and completes. + * + * Single-consumer by design: solid-js creates exactly one iterator from + * the value and shares it between the memo and the serializer tap. + */ +export function createServerDehydrationChannel( + client: QueryClient, +): AsyncIterable { + const cache = client.getQueryCache() + // Entry objects are reused across yields while the query's state object + // is unchanged, both so seroval can deduplicate them by reference and + // so the client can cheaply skip already-applied entries. + const entryCache = new Map< + string, + { state: QueryState; entry: DehydratedQueryEntry } + >() + + const snapshot = (): Array => { + const entries: Array = [] + for (const query of cache.getAll()) { + // Mirrors query-core's defaultShouldDehydrateQuery. + if (query.state.status !== 'success') continue + let cached = entryCache.get(query.queryHash) + if (!cached || cached.state !== query.state) { + cached = { + state: query.state, + entry: { + dehydratedAt: Date.now(), + state: query.state, + queryKey: query.queryKey, + queryHash: query.queryHash, + ...(query.meta && { meta: query.meta }), + ...(query.queryType && { queryType: query.queryType }), + }, + } + entryCache.set(query.queryHash, cached) + } + entries.push(cached.entry) + } + return entries + } + + let closed = false + let pull: ((result: IteratorResult) => void) | null = + null + const buffered: Array = [] + + const emit = (value: DehydrationChannelYield) => { + if (closed) return + if (value.done) closed = true + if (pull) { + const resolve = pull + pull = null + resolve({ done: false, value }) + } else { + buffered.push(value) + } + } + + let closeTimer: ReturnType | null = null + const scheduleCloseCheck = () => { + if (closed || closeTimer !== null) return + closeTimer = setTimeout(() => { + closeTimer = null + if (closed) return + if (client.isFetching() === 0) { + unsubscribe() + emit({ entries: snapshot(), done: true }) + } + }, 0) + } + + const unsubscribe = cache.subscribe((event) => { + if (closed) return + if (event.type === 'updated' && event.action.type === 'success') { + emit({ entries: snapshot(), done: false }) + } + scheduleCloseCheck() + }) + scheduleCloseCheck() + + return { + [Symbol.asyncIterator]() { + return { + next() { + if (buffered.length > 0) { + return Promise.resolve({ done: false, value: buffered.shift()! }) + } + if (closed) { + return Promise.resolve({ + done: true as const, + value: undefined, + }) + } + return new Promise>( + (resolve) => { + pull = resolve + }, + ) + }, + return(value?: unknown) { + if (!closed) { + closed = true + unsubscribe() + if (closeTimer !== null) { + clearTimeout(closeTimer) + closeTimer = null + } + const resolve = pull + pull = null + resolve?.({ done: true, value: undefined }) + } + return Promise.resolve({ + done: true as const, + value: value as DehydrationChannelYield, + }) + }, + } + }, + } +} + +export interface HydrationCoordinator { + /** + * Prime the QueryClient from a channel yield. Entries already applied + * (same queryHash and dataUpdatedAt) are skipped; the rest go through + * query-core `hydrate()`, which keeps whichever data is newer. + */ + applyYield: (value: DehydrationChannelYield) => void + /** + * Invoke `callback` (on a microtask) once the entry for `queryHash` has + * been applied — or immediately-on-a-microtask if it already was, or + * when the channel completes without one (SSR-errored queries are not + * dehydrated, so their components must not wait forever). + */ + whenQueryPrimed: (queryHash: string, callback: () => void) => void +} + +/** + * Client side of the channel. Created by `QueryClientProvider` on the + * client and handed to `useBaseQuery` via context so hydrated components + * can attach their observers as soon as their query's entry has been + * primed — per query, not at global hydration end, which keeps + * early-hydrated components live while other boundaries still stream. + */ +export function createHydrationCoordinator( + client: () => QueryClient, +): HydrationCoordinator { + // queryHash -> dataUpdatedAt of the applied entry + const applied = new Map() + const waiters = new Map void>>() + let channelDone = false + + const fireWaiters = (queryHash: string) => { + const callbacks = waiters.get(queryHash) + if (!callbacks) return + waiters.delete(queryHash) + for (const callback of callbacks) queueMicrotask(callback) + } + + return { + applyYield(value) { + const fresh = value.entries.filter( + (entry) => applied.get(entry.queryHash) !== entry.state.dataUpdatedAt, + ) + if (fresh.length > 0) { + // hydrate() synchronously notifies cache subscribers which may + // write to stores/signals; escape the owned scope (this runs + // inside the provider's render effect) so those writes are + // allowed. + runWithOwner(null, () => + hydrate(client(), { queries: fresh, mutations: [] }), + ) + for (const entry of fresh) { + applied.set(entry.queryHash, entry.state.dataUpdatedAt) + fireWaiters(entry.queryHash) + } + } + if (value.done && !channelDone) { + channelDone = true + const remaining = [...waiters.values()] + waiters.clear() + for (const callbacks of remaining) { + for (const callback of callbacks) queueMicrotask(callback) + } + } + }, + whenQueryPrimed(queryHash, callback) { + if (channelDone || applied.has(queryHash)) { + queueMicrotask(callback) + return + } + let list = waiters.get(queryHash) + if (!list) { + list = [] + waiters.set(queryHash, list) + } + list.push(callback) + }, + } +} + +export const HydrationCoordinatorContext = + createContext(null) diff --git a/packages/solid-query/src/useBaseQuery.ts b/packages/solid-query/src/useBaseQuery.ts index ee67ac74d4c..94e3775f4ee 100644 --- a/packages/solid-query/src/useBaseQuery.ts +++ b/packages/solid-query/src/useBaseQuery.ts @@ -13,8 +13,10 @@ import { runWithOwner, snapshot, untrack, + useContext, } from 'solid-js' import { useQueryClient } from './QueryClientProvider' +import { HydrationCoordinatorContext } from './hydrationChannel' import { useIsRestoring } from './isRestoring' import type { UseBaseQueryOptions } from './types' import type { Accessor } from 'solid-js' @@ -97,8 +99,14 @@ function reconcileFn( } /** - * Solid's `onHydrated` functionality will silently "fail" (hydrate with an empty object) - * if the resource data is not serializable. + * Prepare an observer result for SSR serialization: the resolved resource + * value is serialized by seroval, which cannot handle functions, so strip + * `refetch` (and the infinite-query pagers). They come back when the + * observer attaches on the client. + * + * The query's dehydrated cache state does not ride the observer result — + * it travels through the provider-owned dehydration channel (see + * `hydrationChannel.ts`). */ const hydratableObserverResult = < TQueryFnData, @@ -107,7 +115,7 @@ const hydratableObserverResult = < TQueryKey extends QueryKey, TDataHydratable, >( - query: Query, + _query: Query, result: QueryObserverResult, ) => { if (!isServer) return result @@ -124,15 +132,6 @@ const hydratableObserverResult = < obj.fetchPreviousPage = undefined } - // We will also attach the dehydrated state of the query to the result - // This will be removed on client after hydration - obj.hydrationData = { - state: query.state, - queryKey: query.queryKey, - queryHash: query.queryHash, - ...(query.meta && { meta: query.meta }), - } - return obj } @@ -281,6 +280,46 @@ export function useBaseQuery< let unsubscribe: (() => void) | null = null let disposed = false + /** + * Attach the client subscriber for a component that hydrated from SSR + * output. + * + * During hydration Solid replays the `queryResource` memo below from the + * serialized SSR value with `Promise` mocked, so the promise executor + * that normally creates the client subscriber never runs (nor could it: + * a mount refetch started from inside the replay would never settle). + * The replay is detected without touching any internals: a real + * `Promise` runs its executor synchronously, the hydration mock does + * not, so `executorRan` stays false exactly when this compute was + * replayed. + * + * The subscription is coordinated with the provider's dehydration + * channel: it attaches once this query's entry has been primed into the + * cache (or once the channel completes without one), so mount semantics + * see the hydrated cache state — a still-fresh query does not refetch, a + * stale one does, and cache writes that landed earlier are reconciled at + * attach. The wait is per-query, not global-hydration-end: a component + * hydrated from an early flush goes live while later boundaries are + * still streaming, so it is not deaf to cache writes, is seen by + * invalidateQueries' active-query refetch, and cannot be gc'ed while + * visible. Without a provider (manual `queryClient` option) it falls + * back to a plain microtask. + */ + const coordinator = useContext(HydrationCoordinatorContext) + const attachHydratedSubscriber = () => { + if (!unsubscribe && !disposed && !isRestoring()) { + unsubscribe = createClientSubscriber() + } + } + const scheduleHydratedAttach = () => { + const queryHash = untrack(() => observer.getCurrentQuery().queryHash) + if (coordinator) { + coordinator.whenQueryPrimed(queryHash, attachHydratedSubscriber) + } else { + queueMicrotask(attachHydratedSubscriber) + } + } + /* Fixes #7275 In a few cases, the observer could unmount before the resource is loaded. @@ -325,7 +364,9 @@ export function useBaseQuery< } } - return new Promise((resolve, reject) => { + const replayProbe = { executorRan: false } + const resource = new Promise((resolve, reject) => { + replayProbe.executorRan = true resolver = resolve if (isServer) { unsubscribe = createServerSubscriber((data) => { @@ -376,6 +417,15 @@ export function useBaseQuery< ) } }) + + if (!isServer && !replayProbe.executorRan) { + // Hydration replay: `Promise` was mocked and the executor above never + // ran, so no subscriber was created. Schedule the attach through the + // provider's hydration coordinator (see scheduleHydratedAttach). + scheduleHydratedAttach() + } + + return resource }) onCleanup(() => { From 41520435d3aa956657ce6a33cadd7fe02877f185 Mon Sep 17 00:00:00 2001 From: Ryan Carniato Date: Mon, 10 Aug 2026 00:51:18 -0700 Subject: [PATCH 2/8] test(solid-query): add SSR and streaming hydration integration tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Build a small fixture app with vite (string + streaming server bundles rendered in a node subprocess, hydratable client bundle) and hydrate it in jsdom with the real @solidjs/web hydrate() against the server HTML and serialized payload. The streaming fixture captures renderToStream chunks with timestamps and replays them in phases so a slow boundary holds the stream open while tests probe an already-hydrated section. Covers: channel payload in the SSR output (and no hydrationData field), warm cache within microtasks of hydration with the server's dataUpdatedAt (newer-wins), no mount refetch at staleTime 60s, mount refetch at staleTime 0, cache writes landing before the subscriber attach reconciled without a refetch, shell-flush entries primed at shell hydration rather than stream end (with the late entry verifiably absent until its boundary's flush), and hydrated components staying live — setQueryData and invalidateQueries both effective — while the stream is still open, with the late boundary hydrating correctly after. Co-authored-by: Cursor --- .../src/__tests__/fixtures/hydration/App.tsx | 66 +++++ .../fixtures/hydration/StreamApp.tsx | 66 +++++ .../fixtures/hydration/build-and-render.mjs | 91 ++++++ .../fixtures/hydration/entry-client.tsx | 44 +++ .../hydration/entry-server-stream.tsx | 40 +++ .../fixtures/hydration/entry-server.tsx | 28 ++ .../src/__tests__/hydration-utils.ts | 139 +++++++++ .../src/__tests__/hydration.test.tsx | 276 ++++++++++++++++++ 8 files changed, 750 insertions(+) create mode 100644 packages/solid-query/src/__tests__/fixtures/hydration/App.tsx create mode 100644 packages/solid-query/src/__tests__/fixtures/hydration/StreamApp.tsx create mode 100644 packages/solid-query/src/__tests__/fixtures/hydration/build-and-render.mjs create mode 100644 packages/solid-query/src/__tests__/fixtures/hydration/entry-client.tsx create mode 100644 packages/solid-query/src/__tests__/fixtures/hydration/entry-server-stream.tsx create mode 100644 packages/solid-query/src/__tests__/fixtures/hydration/entry-server.tsx create mode 100644 packages/solid-query/src/__tests__/hydration-utils.ts create mode 100644 packages/solid-query/src/__tests__/hydration.test.tsx diff --git a/packages/solid-query/src/__tests__/fixtures/hydration/App.tsx b/packages/solid-query/src/__tests__/fixtures/hydration/App.tsx new file mode 100644 index 00000000000..16f1845f8ed --- /dev/null +++ b/packages/solid-query/src/__tests__/fixtures/hydration/App.tsx @@ -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 ( +
+ {fresh.data} + {stale.data} +
+ ) +} + +export function App(props: AppProps) { + return ( + + loading}> + + + + ) +} diff --git a/packages/solid-query/src/__tests__/fixtures/hydration/StreamApp.tsx b/packages/solid-query/src/__tests__/fixtures/hydration/StreamApp.tsx new file mode 100644 index 00000000000..9835dee4993 --- /dev/null +++ b/packages/solid-query/src/__tests__/fixtures/hydration/StreamApp.tsx @@ -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 {query.data} +} + +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 {query.data} +} + +export function StreamApp(props: StreamAppProps) { + return ( + +
+ loading-header
}> + + + loading-feed}> + + + +
+ ) +} diff --git a/packages/solid-query/src/__tests__/fixtures/hydration/build-and-render.mjs b/packages/solid-query/src/__tests__/fixtures/hydration/build-and-render.mjs new file mode 100644 index 00000000000..8ca257a33ba --- /dev/null +++ b/packages/solid-query/src/__tests__/fixtures/hydration/build-and-render.mjs @@ -0,0 +1,91 @@ +/** + * Helper subprocess for the hydration tests. Runs in plain node (vite/esbuild + * cannot run inside the jsdom test worker): builds the fixture app twice + * (server + hydratable client bundles), executes the SSR entry, and prints a + * JSON report on stdout. + * + * Usage: node build-and-render.mjs + */ +import { execFileSync } from 'node:child_process' +import * as path from 'node:path' +import { fileURLToPath } from 'node:url' +import { build } from 'vite' +import solidPlugin from 'vite-plugin-solid' + +const fixtureDir = fileURLToPath(new URL('.', import.meta.url)) +const packageRoot = path.join(fixtureDir, '..', '..', '..', '..') +const outDir = process.argv[2] +if (!outDir) { + throw new Error('usage: node build-and-render.mjs ') +} + +const alias = { + '@tanstack/solid-query': path.join(packageRoot, 'src', 'index.ts'), + '@tanstack/query-core': path.join( + packageRoot, + '..', + 'query-core', + 'src', + 'index.ts', + ), + 'solid-js/web': '@solidjs/web', +} + +// Server bundles: everything inlined so module resolution inside the temp +// output dir is a non-issue. +for (const entry of ['entry-server', 'entry-server-stream']) { + await build({ + configFile: false, + logLevel: 'error', + plugins: [solidPlugin({ ssr: true })], + resolve: { alias }, + ssr: { noExternal: true }, + build: { + ssr: path.join(fixtureDir, `${entry}.tsx`), + outDir, + emptyOutDir: false, + minify: false, + target: 'node18', + rollupOptions: { + output: { entryFileNames: `${entry}.mjs` }, + }, + }, + }) +} + +// Client bundle: hydratable DOM output as a single self-contained ES module. +await build({ + configFile: false, + logLevel: 'error', + plugins: [solidPlugin({ ssr: true })], + resolve: { alias }, + build: { + outDir, + emptyOutDir: false, + minify: false, + target: 'esnext', + lib: { + entry: path.join(fixtureDir, 'entry-client.tsx'), + formats: ['es'], + fileName: () => 'entry-client.mjs', + }, + }, +}) + +const report = execFileSync( + process.execPath, + [path.join(outDir, 'entry-server.mjs')], + { encoding: 'utf-8' }, +) +const streamReport = execFileSync( + process.execPath, + [path.join(outDir, 'entry-server-stream.mjs')], + { encoding: 'utf-8' }, +) + +// Sanity-check both parse before handing them to the test. +const combined = JSON.stringify({ + string: JSON.parse(report), + stream: JSON.parse(streamReport), +}) +process.stdout.write(combined) diff --git a/packages/solid-query/src/__tests__/fixtures/hydration/entry-client.tsx b/packages/solid-query/src/__tests__/fixtures/hydration/entry-client.tsx new file mode 100644 index 00000000000..f2df928c2b0 --- /dev/null +++ b/packages/solid-query/src/__tests__/fixtures/hydration/entry-client.tsx @@ -0,0 +1,44 @@ +/** + * Client entry for the hydration tests. Bundled with the hydratable DOM + * transform and imported dynamically by the jsdom test, which then calls + * `mount()` against the server-rendered markup. Factories return fresh + * QueryClient/counters per call so multiple tests can share the bundle. + */ +import { hydrate } from '@solidjs/web' +import { QueryClient } from '@tanstack/solid-query' +import { App } from './App' +import { StreamApp } from './StreamApp' +import type { FetchCounts } from './App' +import type { StreamCounts } from './StreamApp' + +export function createApp() { + const queryClient = new QueryClient() + const counts: FetchCounts = { fresh: 0, stale: 0 } + return { + queryClient, + counts, + mount(container: HTMLElement): () => void { + return hydrate( + () => , + container, + ) + }, + } +} + +export function createStreamApp() { + const queryClient = new QueryClient() + const counts: StreamCounts = { header: 0, feed: 0 } + return { + queryClient, + counts, + mount(container: HTMLElement): () => void { + return hydrate( + () => ( + + ), + container, + ) + }, + } +} diff --git a/packages/solid-query/src/__tests__/fixtures/hydration/entry-server-stream.tsx b/packages/solid-query/src/__tests__/fixtures/hydration/entry-server-stream.tsx new file mode 100644 index 00000000000..8dc8d005f7c --- /dev/null +++ b/packages/solid-query/src/__tests__/fixtures/hydration/entry-server-stream.tsx @@ -0,0 +1,40 @@ +/** + * Streaming SSR entry for the hydration window tests. Renders StreamApp with + * renderToStream, capturing each written chunk with a timestamp so the test + * can replay the stream in phases (shell + fast boundary first, slow boundary + * later) and probe the window in between. + */ +import { renderToStream } from '@solidjs/web' +import { QueryClient } from '@tanstack/solid-query' +import { StreamApp } from './StreamApp' +import type { StreamCounts } from './StreamApp' + +const client = new QueryClient() +const counts: StreamCounts = { header: 0, feed: 0 } + +const start = Date.now() +const chunks: Array<{ t: number; payload: string }> = [] + +await new Promise((resolve) => { + renderToStream(() => ( + + )).pipe({ + write(payload: string) { + chunks.push({ t: Date.now() - start, payload }) + }, + end() { + resolve() + }, + }) +}) + +const queries = client + .getQueryCache() + .getAll() + .map((query) => ({ + queryKey: query.queryKey, + queryHash: query.queryHash, + state: query.state, + })) + +console.log(JSON.stringify({ chunks, counts, queries })) diff --git a/packages/solid-query/src/__tests__/fixtures/hydration/entry-server.tsx b/packages/solid-query/src/__tests__/fixtures/hydration/entry-server.tsx new file mode 100644 index 00000000000..ab59be9e1a5 --- /dev/null +++ b/packages/solid-query/src/__tests__/fixtures/hydration/entry-server.tsx @@ -0,0 +1,28 @@ +/** + * SSR entry for the hydration tests. Runs in a plain node subprocess so that + * `solid-js` resolves to its server build. Renders the fixture app and prints + * a JSON report (HTML with embedded hydration scripts, fetch counts, and the + * dehydrated-ish query states) on stdout. + */ +import { renderToStringAsync } from '@solidjs/web' +import { QueryClient } from '@tanstack/solid-query' +import { App } from './App' +import type { FetchCounts } from './App' + +const client = new QueryClient() +const counts: FetchCounts = { fresh: 0, stale: 0 } + +const html = await renderToStringAsync(() => ( + +)) + +const queries = client + .getQueryCache() + .getAll() + .map((query) => ({ + queryKey: query.queryKey, + queryHash: query.queryHash, + state: query.state, + })) + +console.log(JSON.stringify({ html, counts, queries })) diff --git a/packages/solid-query/src/__tests__/hydration-utils.ts b/packages/solid-query/src/__tests__/hydration-utils.ts new file mode 100644 index 00000000000..12d83b83419 --- /dev/null +++ b/packages/solid-query/src/__tests__/hydration-utils.ts @@ -0,0 +1,139 @@ +/** + * Shared harness for the SSR → hydration tests. + * + * The fixture app in `fixtures/hydration/` is built with vite in a plain node + * subprocess (vite/esbuild cannot run inside the jsdom worker): a server + * bundle, a streaming server bundle, and a hydratable client bundle. The + * subprocess also executes both server entries and returns their reports. + */ +import { execFileSync } from 'node:child_process' +import { mkdirSync, rmSync } from 'node:fs' +import * as path from 'node:path' +import { pathToFileURL } from 'node:url' +import type { QueryClient } from '..' + +// vitest runs with the package root as cwd; import.meta.url is not a file URL +// inside the jsdom worker. +const fixtureDir = path.join( + process.cwd(), + 'src', + '__tests__', + 'fixtures', + 'hydration', +) + +interface QuerySnapshot { + queryKey: Array + queryHash: string + state: { data: unknown; dataUpdatedAt: number; status: string } +} + +export interface ServerReport { + string: { + html: string + counts: { fresh: number; stale: number } + queries: Array + } + stream: { + chunks: Array<{ t: number; payload: string }> + counts: { header: number; feed: number } + queries: Array + } +} + +export interface ClientBundle { + createApp: () => { + queryClient: QueryClient + counts: { fresh: number; stale: number } + mount: (container: HTMLElement) => () => void + } + createStreamApp: () => { + queryClient: QueryClient + counts: { header: number; feed: number } + mount: (container: HTMLElement) => () => void + } +} + +export interface Harness { + outDir: string + report: ServerReport + clientBundleUrl: string +} + +export function buildFixture(): Harness { + // Must live inside the package so vitest can import the client bundle. + const outDir = path.join( + process.cwd(), + 'node_modules', + '.tmp', + 'hydration-fixture', + ) + rmSync(outDir, { recursive: true, force: true }) + mkdirSync(outDir, { recursive: true }) + + const report = JSON.parse( + execFileSync( + process.execPath, + [path.join(fixtureDir, 'build-and-render.mjs'), outDir], + { encoding: 'utf-8' }, + ), + ) as ServerReport + return { + outDir, + report, + clientBundleUrl: pathToFileURL(path.join(outDir, 'entry-client.mjs')).href, + } +} + +export function cleanupFixture(harness: Harness | undefined): void { + if (harness) rmSync(harness.outDir, { recursive: true, force: true }) +} + +/** + * Reset the hydration bootstrap the way a fresh document would provide it + * (normally injected via generateHydrationScript). + */ +export function bootstrapHydrationGlobals(): void { + ;(globalThis as any)._$HY = { + events: [], + completed: new WeakSet(), + r: {}, + fe() {}, + } +} + +/** + * Append streamed SSR payloads to the container and execute any scripts they + * carry, in document order, the way a browser's parser would. + */ +export function applyChunks( + container: HTMLElement, + payloads: Array, +): void { + for (const payload of payloads) { + container.insertAdjacentHTML('beforeend', payload) + for (const script of Array.from( + container.querySelectorAll('script:not([data-executed])'), + )) { + script.setAttribute('data-executed', 'true') + if (script.textContent) { + window.eval(script.textContent) + } + } + } +} + +/** Flush microtasks plus one timer turn. */ +export function tick(ms = 0): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)) +} + +/** + * Drain `n` rounds of the microtask queue without yielding to timers. + * Used to assert that hydration work happens at microtask timing (i.e. + * before the task ends — before the browser would paint), not on some + * later timer or stream event. + */ +export async function microtasks(n = 10): Promise { + for (let i = 0; i < n; i++) await Promise.resolve() +} diff --git a/packages/solid-query/src/__tests__/hydration.test.tsx b/packages/solid-query/src/__tests__/hydration.test.tsx new file mode 100644 index 00000000000..88dc0d45772 --- /dev/null +++ b/packages/solid-query/src/__tests__/hydration.test.tsx @@ -0,0 +1,276 @@ +/** + * End-to-end SSR → hydration tests for solid-query. + * + * The fixture app in `fixtures/hydration/` is built with vite (server, + * streaming-server, and hydratable client bundles). SSR runs in a node + * subprocess so `solid-js` resolves to its server build; the resulting HTML + * (including Solid's serialized hydration payload) is then hydrated in this + * jsdom process with the real `hydrate()` from `@solidjs/web`. + */ +import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest' +import { + applyChunks, + bootstrapHydrationGlobals, + buildFixture, + cleanupFixture, + microtasks, + tick, +} from './hydration-utils' +import type { ClientBundle, Harness } from './hydration-utils' + +let harness: Harness +let bundle: ClientBundle + +beforeAll(async () => { + harness = buildFixture() + bundle = (await import( + /* @vite-ignore */ harness.clientBundleUrl + )) as ClientBundle +}, 180_000) + +afterAll(() => { + cleanupFixture(harness) +}) + +describe('SSR hydration', () => { + it('server render produces data and the dehydration channel payload', () => { + const { string } = harness.report + expect(string.counts).toEqual({ fresh: 1, stale: 1 }) + expect(string.html).toContain('fresh-server') + expect(string.html).toContain('stale-server') + // The provider's dehydration channel serializes cumulative snapshots of + // dehydrated cache entries (query-core dehydrate shapes)... + expect(string.html).toContain('dehydratedAt') + expect(string.html).toContain('"[\\"fresh\\"]"') + // ...and the per-observer-result hydrationData copy is gone. + expect(string.html).not.toContain('hydrationData') + }) + + it('hydration primes the query cache and refetches only per staleness rules', async () => { + const { string } = harness.report + const app = bundle.createApp() + const container = document.createElement('div') + document.body.appendChild(container) + bootstrapHydrationGlobals() + container.innerHTML = string.html + + // jsdom does not execute scripts inserted via innerHTML; replay them in + // document order the way a browser would. + for (const script of Array.from(container.querySelectorAll('script'))) { + if (script.textContent) { + window.eval(script.textContent) + } + script.remove() + } + + const dispose = app.mount(container) + + try { + // Cache must be warm within microtasks of hydration (the provider + // consumes the deserialized channel; entries apply before the mount + // task's microtask queue drains, i.e. before a browser would paint): + // same data and the server's dataUpdatedAt (hydrate() newer-wins + // semantics). + await microtasks() + const serverFresh = string.queries.find( + (q) => q.queryHash === '["fresh"]', + )! + const freshState = app.queryClient.getQueryState(['fresh']) + expect(freshState?.data).toBe('fresh-server') + expect(freshState?.dataUpdatedAt).toBe(serverFresh.state.dataUpdatedAt) + + const staleState = app.queryClient.getQueryState(['stale']) + expect(staleState?.data).toBe('stale-server') + + // The DOM keeps showing the server-rendered fresh data. + expect(container.querySelector('#fresh')?.textContent).toBe( + 'fresh-server', + ) + + // The immediately-stale query refetches on mount (normal staleness + // rules) and updates the DOM with client data. + await vi.waitFor(() => { + expect(app.counts.stale).toBe(1) + expect(container.querySelector('#stale')?.textContent).toBe( + 'stale-client', + ) + }) + + // ...while the fresh query was never fetched again. + expect(app.counts.fresh).toBe(0) + expect(container.querySelector('#fresh')?.textContent).toBe( + 'fresh-server', + ) + + // The serialized observer results no longer carry a hydrationData + // copy at all — the channel is the only transport. + const registry = (globalThis as any)._$HY.r as Record + const lingering = Object.values(registry).filter((entry) => { + const value = entry != null && entry.s === 1 ? entry.v : entry + return ( + value != null && typeof value === 'object' && 'hydrationData' in value + ) + }) + expect(lingering).toEqual([]) + } finally { + dispose() + container.remove() + } + }) + + it('applies cache writes that land between priming and subscriber attach', async () => { + const { string } = harness.report + const app = bundle.createApp() + const container = document.createElement('div') + document.body.appendChild(container) + bootstrapHydrationGlobals() + container.innerHTML = string.html + for (const script of Array.from(container.querySelectorAll('script'))) { + if (script.textContent) window.eval(script.textContent) + script.remove() + } + + const dispose = app.mount(container) + try { + // Synchronously after hydrate() returns — before the subscriber attach + // microtask has run — write newer data into the cache, the way an + // already-live component (or a settling mutation) would. + app.queryClient.setQueryData(['fresh'], 'updated-client') + + // The hydrated component must pick the write up when its subscriber + // attaches, without any refetch. + await vi.waitFor(() => { + expect(container.querySelector('#fresh')?.textContent).toBe( + 'updated-client', + ) + }) + expect(app.counts.fresh).toBe(0) + } finally { + dispose() + container.remove() + } + }) +}) + +describe('streaming SSR hydration', () => { + function splitStream() { + const { chunks } = harness.report.stream + const idx = chunks.findIndex((c) => c.payload.includes('feed-server')) + expect(idx).toBeGreaterThan(0) + return { + phase1: chunks.slice(0, idx).map((c) => c.payload), + phase2: chunks.slice(idx).map((c) => c.payload), + } + } + + it('primes shell-flush entries at shell hydration, not at stream end', async () => { + const { phase1, phase2 } = splitStream() + const app = bundle.createStreamApp() + const container = document.createElement('div') + document.body.appendChild(container) + bootstrapHydrationGlobals() + + // Deliver only the first flush; the stream stays open (phase2 is never + // applied until later), so anything observable now provably did not + // wait for stream end. + applyChunks(container, phase1) + const dispose = app.mount(container) + + try { + // The header entry settled before the first flush, so its channel + // yield rides the same chunks — it must be primed within microtasks + // of shell hydration, with the server's dataUpdatedAt intact. This + // also proves the mocked-Promise hydration replay does not wedge the + // provider's stream consumption. + await microtasks() + const serverHeader = harness.report.stream.queries.find( + (q) => q.queryHash === '["header"]', + )! + const headerState = app.queryClient.getQueryState(['header']) + expect(headerState?.data).toBe('header-server') + expect(headerState?.dataUpdatedAt).toBe(serverHeader.state.dataUpdatedAt) + // Fresh (staleTime 60s) hydrated data — no mount refetch. + expect(app.counts.header).toBe(0) + // The observer attached (per-query, not at hydration end): the query + // is active while the stream is still open. + expect( + app.queryClient + .getQueryCache() + .find({ queryKey: ['header'] }) + ?.getObserversCount(), + ).toBe(1) + + // The feed entry rides the second flush; it must not be primed early. + expect(app.queryClient.getQueryState(['feed'])).toBeUndefined() + + // When the boundary's flush arrives, its entry arrives with it. + applyChunks(container, phase2) + await vi.waitFor(() => { + expect(app.queryClient.getQueryState(['feed'])?.data).toBe( + 'feed-server', + ) + expect(container.querySelector('#feed')?.textContent).toBe( + 'feed-server', + ) + }) + expect(app.counts.feed).toBe(0) + await tick(30) + } finally { + dispose() + container.remove() + } + }) + + it('hydrated components are live while the stream is still open', async () => { + const { phase1, phase2 } = splitStream() + const app = bundle.createStreamApp() + const container = document.createElement('div') + document.body.appendChild(container) + bootstrapHydrationGlobals() + + // Deliver the shell and the fast boundary only; the slow boundary keeps + // the page in "hydration in progress" state. + applyChunks(container, phase1) + const dispose = app.mount(container) + + try { + expect(container.querySelector('#header')?.textContent).toBe( + 'header-server', + ) + // The slow section still shows its fallback. + expect(container.querySelector('#feed')).toBeNull() + + // Newer data written while the stream is open must reach the + // already-hydrated component without waiting for the stream to end. + app.queryClient.setQueryData(['header'], 'updated-client') + await vi.waitFor(() => { + expect(container.querySelector('#header')?.textContent).toBe( + 'updated-client', + ) + }) + + // An invalidation while the stream is open refetches the hydrated + // query immediately (it is active — its observer is subscribed). + void app.queryClient.invalidateQueries({ queryKey: ['header'] }) + await vi.waitFor(() => { + expect(app.counts.header).toBe(1) + expect(container.querySelector('#header')?.textContent).toBe( + 'header-client', + ) + }) + + // The late boundary still hydrates correctly afterwards. + applyChunks(container, phase2) + await vi.waitFor(() => { + expect(container.querySelector('#feed')?.textContent).toBe( + 'feed-server', + ) + }) + expect(app.counts.feed).toBe(0) + await tick(30) + } finally { + dispose() + container.remove() + } + }) +}) From e3065bdf3a4e6e2e6cf5e7a9c0dc8097f7866b55 Mon Sep 17 00:00:00 2001 From: Ryan Carniato Date: Mon, 10 Aug 2026 01:48:23 -0700 Subject: [PATCH 3/8] test(solid-query): pin coexistence with an external hydrate() channel Hosts like TanStack Start prime the QueryClient through their own query-core hydrate() call before DOM hydration. Pin that the provider channel's re-priming of the same entries is silent (no cache updates, no observer churn, no refetch) and that its per-query attach coordination still resolves. Co-authored-by: Cursor --- .../src/__tests__/hydration.test.tsx | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/packages/solid-query/src/__tests__/hydration.test.tsx b/packages/solid-query/src/__tests__/hydration.test.tsx index 88dc0d45772..64be6f3b976 100644 --- a/packages/solid-query/src/__tests__/hydration.test.tsx +++ b/packages/solid-query/src/__tests__/hydration.test.tsx @@ -7,6 +7,7 @@ * (including Solid's serialized hydration payload) is then hydrated in this * jsdom process with the real `hydrate()` from `@solidjs/web`. */ +import { hydrate as hydrateQueryClient } from '@tanstack/query-core' import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest' import { applyChunks, @@ -150,6 +151,84 @@ describe('SSR hydration', () => { container.remove() } }) + + it('coexists with a host that primes the cache through its own hydrate() channel', async () => { + // TanStack Start does not use the provider channel: its router + // integration applies a dehydrated QueryClient via query-core hydrate() + // before Solid's DOM hydrate() runs. Under Start both channels are live + // and prime the same entries; pin that the second application is silent + // (hydrate() only writes strictly-newer data, so equal dataUpdatedAt is + // a no-op with no observer notification) and that the provider's attach + // coordination still resolves. + const { string } = harness.report + const app = bundle.createApp() + const container = document.createElement('div') + document.body.appendChild(container) + bootstrapHydrationGlobals() + container.innerHTML = string.html + for (const script of Array.from(container.querySelectorAll('script'))) { + if (script.textContent) window.eval(script.textContent) + script.remove() + } + + // The host's channel primes first, with the exact server states. + hydrateQueryClient(app.queryClient, { + queries: string.queries.map((q) => ({ + queryKey: q.queryKey, + queryHash: q.queryHash, + state: q.state, + dehydratedAt: q.state.dataUpdatedAt, + })), + mutations: [], + }) + + // Record every cache update from here on: the provider channel's + // re-priming must not produce any for the already-primed fresh query. + const updatedHashes: Array = [] + const unsubscribe = app.queryClient.getQueryCache().subscribe((event) => { + if (event.type === 'updated') updatedHashes.push(event.query.queryHash) + }) + + const dispose = app.mount(container) + try { + // Attach coordination is not deadlocked by the pre-primed cache: the + // channel still yields, waiters resolve, the observer subscribes. + await vi.waitFor(() => { + expect( + app.queryClient + .getQueryCache() + .find({ queryKey: ['fresh'] }) + ?.getObserversCount(), + ).toBe(1) + }) + + // Double-priming was silent: no state write, no observer churn, no + // refetch of the fresh query. (The stale query's mount refetch per + // normal staleness rules is the only update source.) + expect( + updatedHashes.filter((hash) => hash === '["fresh"]'), + ).toEqual([]) + expect(app.counts.fresh).toBe(0) + expect(container.querySelector('#fresh')?.textContent).toBe( + 'fresh-server', + ) + await vi.waitFor(() => { + expect(app.counts.stale).toBe(1) + }) + + // And the component is live afterwards. + app.queryClient.setQueryData(['fresh'], 'updated-client') + await vi.waitFor(() => { + expect(container.querySelector('#fresh')?.textContent).toBe( + 'updated-client', + ) + }) + } finally { + unsubscribe() + dispose() + container.remove() + } + }) }) describe('streaming SSR hydration', () => { From 640c4eee3b4223338e85860bfc172cfd3e981879 Mon Sep 17 00:00:00 2001 From: Ryan Carniato Date: Mon, 10 Aug 2026 10:54:41 -0700 Subject: [PATCH 4/8] =?UTF-8?q?refactor(solid-query):=20signal-shape=20the?= =?UTF-8?q?=20dehydration=20channel=20=E2=80=94=20buffered=20replay=20conf?= =?UTF-8?q?lation=20makes=20it=20the=20right=20container?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The store shape was a workaround for solid-js' signal-path hydration replay dropping buffered async-iterable yields (normalizeIterator let the stream's done result clobber the backlog, pinning the value at the first yield). With the conflate-to-latest replay fix (solid 23657d29, shipping in the beta after 2.0.0-beta.32), the natural shape works: the provider holds the channel as a plain async-iterable-valued createSignal(fn) computation, the server serializes the tapped iterator through the normal per-computation path, and the client replay conflates any buffered backlog to the latest yield — lossless exactly because yields are cumulative snapshots. Requires that beta: on stock beta.32, buffered replay leaves post-first-yield entries unprimed and their waiters unresolved (frozen components, verified empirically), so the solid pin must be bumped when the beta publishes. Wins over the store shape, verified on the fixture: no draft mutation or yield-undefined first-snapshot dodge (the JSON-cloned-first-snapshot quirk is store-path-only; the signal path serializes the first yield object directly, so entry identity and seroval reference dedup hold from the first yield — the terminal yield serializes as pure $R references), and 167 B / ~60 B gz smaller on the two-query fixture. Coordinator, whenQueryPrimed, and useBaseQuery semantics unchanged. Tests: new buffered-replay conflation integration test (entire stream delivered before hydrate() — all entries primed from the conflated snapshot, all observers attach, nothing refetches); string fixture now collects renderToStream via pipe() (renderToStringAsync is gone from current solid betas). 22 files / 329 tests green against a local solid build at the fix commit. Co-authored-by: Cursor --- .../solid-query/src/QueryClientProvider.tsx | 78 +++++++------------ .../fixtures/hydration/entry-server.tsx | 21 ++++- .../src/__tests__/hydration.test.tsx | 66 ++++++++++++++++ packages/solid-query/src/hydrationChannel.ts | 16 ++-- 4 files changed, 123 insertions(+), 58 deletions(-) diff --git a/packages/solid-query/src/QueryClientProvider.tsx b/packages/solid-query/src/QueryClientProvider.tsx index ded26c84bb4..9f2beb95e4c 100644 --- a/packages/solid-query/src/QueryClientProvider.tsx +++ b/packages/solid-query/src/QueryClientProvider.tsx @@ -1,9 +1,8 @@ import { createContext, createRenderEffect, - createStore, + createSignal, onCleanup, - snapshot, useContext, } from 'solid-js' import { @@ -47,61 +46,42 @@ export const QueryClientProvider = ( // Library-owned serialization channel for SSR dehydration. // - // Server: the store's initializer returns an async generator that - // writes cumulative dehydrated-query snapshots into the draft as - // queries settle during SSR. It is a plain async store computation, so - // Solid serializes it through its normal per-computation path: each - // yield's draft mutations ride the SSR stream as store patches (the - // same flush as the content that awaited them), and the entry objects - // inside them are deduplicated by reference (seroval) against - // everything else in the payload. + // 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. // - // The channel is store-shaped rather than signal-shaped on purpose: - // Solid's hydration replay applies *every* buffered yield of a store's - // async iterable in order (`hydrateStoreFromAsyncIterable`), while a - // signal's replay collapses buffered yields into the latest — and its - // final done-result supersedes them — which would drop entries whenever - // hydration begins after their chunks already arrived. + // 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, hydrating: the store replays from the serialized value; the - // render effect below applies each state of the channel to the - // QueryClient via query-core hydrate() (newer-wins) and unblocks - // `useBaseQuery` subscribers waiting on their query's entry. - // - // Client, fresh mount: the initializer returns undefined and the store - // keeps its (empty) initial value. - const [channelState] = createStore( - (draft) => { - if (!isServer) return undefined - const channel = createServerDehydrationChannel(props.client) - return (async function* () { - // Settle the store's serialized first snapshot as the empty - // initial state, so every entry travels as a patch and keeps its - // object identity for seroval's reference deduplication (the - // first snapshot is JSON-cloned by the runtime, which would - // break it). - yield undefined - for await (const value of channel) { - draft.entries = value.entries - draft.done = value.done - yield undefined - } - })() - }, - { entries: [], done: false }, + // Client, fresh mount: the compute returns undefined and the effect + // never fires. + const [channelValue] = createSignal( + () => (isServer ? createServerDehydrationChannel(props.client) : undefined), ) const coordinator = isServer ? null : createHydrationCoordinator(() => props.client) createRenderEffect( - () => - isServer - ? undefined - : { entries: channelState.entries, done: channelState.done }, + () => (isServer ? undefined : channelValue()), (value) => { - if (value && coordinator && (value.entries.length > 0 || value.done)) { - // Unwrap the store proxies so raw entry objects reach the cache. - coordinator.applyYield(snapshot(value) as DehydrationChannelYield) + if (value && coordinator) { + coordinator.applyYield(value) } }, ) diff --git a/packages/solid-query/src/__tests__/fixtures/hydration/entry-server.tsx b/packages/solid-query/src/__tests__/fixtures/hydration/entry-server.tsx index ab59be9e1a5..c730da6421a 100644 --- a/packages/solid-query/src/__tests__/fixtures/hydration/entry-server.tsx +++ b/packages/solid-query/src/__tests__/fixtures/hydration/entry-server.tsx @@ -4,7 +4,7 @@ * a JSON report (HTML with embedded hydration scripts, fetch counts, and the * dehydrated-ish query states) on stdout. */ -import { renderToStringAsync } from '@solidjs/web' +import { renderToStream } from '@solidjs/web' import { QueryClient } from '@tanstack/solid-query' import { App } from './App' import type { FetchCounts } from './App' @@ -12,9 +12,22 @@ import type { FetchCounts } from './App' const client = new QueryClient() const counts: FetchCounts = { fresh: 0, stale: 0 } -const html = await renderToStringAsync(() => ( - -)) +// Fully-settled single-string render. Collected through pipe() rather than +// the thenable form so the fixture builds against any solid-js 2 beta +// (renderToStringAsync was removed after beta.29). +const html = await new Promise((resolve) => { + let out = '' + renderToStream(() => ( + + )).pipe({ + write(payload: string) { + out += payload + }, + end() { + resolve(out) + }, + }) +}) const queries = client .getQueryCache() diff --git a/packages/solid-query/src/__tests__/hydration.test.tsx b/packages/solid-query/src/__tests__/hydration.test.tsx index 64be6f3b976..9b2ecf6abca 100644 --- a/packages/solid-query/src/__tests__/hydration.test.tsx +++ b/packages/solid-query/src/__tests__/hydration.test.tsx @@ -300,6 +300,72 @@ describe('streaming SSR hydration', () => { } }) + it('applies the latest cumulative snapshot when hydration starts after the whole stream arrived (buffered-replay conflation)', async () => { + // Hydration long after the stream completed (slow client / late script): + // every channel yield — one per settle plus the terminal done snapshot — + // is already buffered in the deserialized stream when the provider's + // signal replays. Solid's signal-path replay conflates that backlog to + // the LATEST yield (`normalizeIterator`), which is lossless precisely + // because yields are cumulative. All entries must be primed and all + // observers attached from that one snapshot; on solid builds without + // the conflation fix (<= 2.0.0-beta.32) the replay pins at the first + // yield and everything after it (including `done`) is dropped. + const { chunks } = harness.report.stream + const app = bundle.createStreamApp() + const container = document.createElement('div') + document.body.appendChild(container) + bootstrapHydrationGlobals() + + // Deliver the ENTIRE stream before mounting. + applyChunks( + container, + chunks.map((c) => c.payload), + ) + const dispose = app.mount(container) + + try { + await microtasks() + // Both queries primed with the server's exact states, from the single + // conflated snapshot. + for (const key of ['header', 'feed'] as const) { + const server = harness.report.stream.queries.find( + (q) => q.queryHash === `["${key}"]`, + )! + const state = app.queryClient.getQueryState([key]) + expect(state?.data).toBe(`${key}-server`) + expect(state?.dataUpdatedAt).toBe(server.state.dataUpdatedAt) + } + // Both observers attach (the done snapshot was not dropped, waiters + // resolved) and neither fresh query refetches. + await vi.waitFor(() => { + expect( + app.queryClient + .getQueryCache() + .find({ queryKey: ['header'] }) + ?.getObserversCount(), + ).toBe(1) + expect( + app.queryClient + .getQueryCache() + .find({ queryKey: ['feed'] }) + ?.getObserversCount(), + ).toBe(1) + }) + expect(app.counts).toEqual({ header: 0, feed: 0 }) + + // And the late-hydrated components are live. + app.queryClient.setQueryData(['feed'], 'updated-client') + await vi.waitFor(() => { + expect(container.querySelector('#feed')?.textContent).toBe( + 'updated-client', + ) + }) + } finally { + dispose() + container.remove() + } + }) + it('hydrated components are live while the stream is still open', async () => { const { phase1, phase2 } = splitStream() const app = bundle.createStreamApp() diff --git a/packages/solid-query/src/hydrationChannel.ts b/packages/solid-query/src/hydrationChannel.ts index e26e66c6d7e..4a0d9927d05 100644 --- a/packages/solid-query/src/hydrationChannel.ts +++ b/packages/solid-query/src/hydrationChannel.ts @@ -9,10 +9,16 @@ type DehydratedQueryEntry = DehydratedState['queries'][number] * A single message on the dehydration channel. `entries` is *cumulative* — * every yield carries all entries settled so far. Two reasons: * - * - Solid's hydration replay of async-iterable values collapses - * intermediate yields that are already buffered when the client pulls - * (`normalizeIterator` drains synchronously available results and keeps - * only the latest), so each yield must be self-contained. + * - It is what makes Solid's signal-path hydration replay lossless. + * Yields still buffered when hydration begins are conflated to the + * LATEST one (`normalizeIterator` drains synchronously available + * results, keeps the last data yield, and delivers the stream's done + * result on a subsequent pull), so each yield must be self-contained: + * the latest cumulative snapshot alone carries everything the dropped + * intermediates did. Requires the solid-js build with that conflation + * behavior (> 2.0.0-beta.32); earlier betas pinned the replay at the + * FIRST buffered yield, dropping every later entry and the `done` + * marker. * - Entry objects keep their identity across yields, so seroval's * cross-reference serialization emits each entry once and later yields * only reference it — the cumulative shape costs bytes proportional to @@ -37,7 +43,7 @@ export interface DehydrationChannelYield { * per-computation path: the server runtime tees the iterator into the * hydration serializer (`ctx.serialize(id, tapped)` in solid-js' * `processResult`) and seroval streams each yield to the client as a - * patch chunk riding the SSR stream. + * script chunk riding the SSR stream. * * The iterable must terminate for the SSR stream to complete: the * hydration serializer's `flush()` only fires its `onDone` once all From 77b19ec30f29262a1956928a8af87d34796f087b Mon Sep 17 00:00:00 2001 From: Ryan Carniato Date: Tue, 11 Aug 2026 10:52:20 -0700 Subject: [PATCH 5/8] chore: upgrade to solid v2 beta 33 - Bump solid-js 2.0.0-beta.29 -> 2.0.0-beta.33 plus matching @solidjs/web, @solidjs/signals, and babel-preset-solid bumps across the solid packages, solid-vite integration, and solid examples - Raise @tanstack/solid-query's solid-js peer range floor to 2.0.0-beta.33: the hydration channel added in this PR requires beta.33's normalizeIterator buffered-replay conflation fix (solid 23657d29). On <= beta.32, hydration that starts after more than one stream chunk has arrived pins the channel replay at its first buffered yield, so later queries are never primed and their observers never attach - silently frozen components, not graceful degradation. - Suite green against published beta.33: 22 files / 329 tests, vitest typecheck and eslint clean Co-authored-by: Cursor --- .changeset/solid-beta-33-upgrade.md | 7 + examples/solid/astro/package.json | 4 +- .../solid/basic-graphql-request/package.json | 4 +- examples/solid/basic/package.json | 4 +- .../solid/default-query-function/package.json | 4 +- examples/solid/offline/package.json | 4 +- examples/solid/simple/package.json | 4 +- .../solid/solid-start-streaming/package.json | 4 +- integrations/solid-vite/package.json | 4 +- packages/solid-query-devtools/package.json | 8 +- .../solid-query-persist-client/package.json | 6 +- packages/solid-query/package.json | 8 +- pnpm-lock.yaml | 279 ++++++++++-------- 13 files changed, 189 insertions(+), 151 deletions(-) create mode 100644 .changeset/solid-beta-33-upgrade.md diff --git a/.changeset/solid-beta-33-upgrade.md b/.changeset/solid-beta-33-upgrade.md new file mode 100644 index 00000000000..5e533bcb799 --- /dev/null +++ b/.changeset/solid-beta-33-upgrade.md @@ -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. diff --git a/examples/solid/astro/package.json b/examples/solid/astro/package.json index 64f09aefbfb..ed8db3227ef 100644 --- a/examples/solid/astro/package.json +++ b/examples/solid/astro/package.json @@ -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" } diff --git a/examples/solid/basic-graphql-request/package.json b/examples/solid/basic-graphql-request/package.json index 704842bb828..a30327ef3b3 100644 --- a/examples/solid/basic-graphql-request/package.json +++ b/examples/solid/basic-graphql-request/package.json @@ -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", diff --git a/examples/solid/basic/package.json b/examples/solid/basic/package.json index 27c4bdeeacb..4d4dfe05d08 100644 --- a/examples/solid/basic/package.json +++ b/examples/solid/basic/package.json @@ -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", diff --git a/examples/solid/default-query-function/package.json b/examples/solid/default-query-function/package.json index e93b3415180..745a2f59005 100644 --- a/examples/solid/default-query-function/package.json +++ b/examples/solid/default-query-function/package.json @@ -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", diff --git a/examples/solid/offline/package.json b/examples/solid/offline/package.json index efd548f6c62..535059edafd 100644 --- a/examples/solid/offline/package.json +++ b/examples/solid/offline/package.json @@ -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", diff --git a/examples/solid/simple/package.json b/examples/solid/simple/package.json index 0bf73683f4c..abdacd97d5d 100644 --- a/examples/solid/simple/package.json +++ b/examples/solid/simple/package.json @@ -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", diff --git a/examples/solid/solid-start-streaming/package.json b/examples/solid/solid-start-streaming/package.json index 44f87f95893..36ad1ba1fe9 100644 --- a/examples/solid/solid-start-streaming/package.json +++ b/examples/solid/solid-start-streaming/package.json @@ -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": { diff --git a/integrations/solid-vite/package.json b/integrations/solid-vite/package.json index edb4313d4e4..cf7a26d1012 100644 --- a/integrations/solid-vite/package.json +++ b/integrations/solid-vite/package.json @@ -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" } } diff --git a/packages/solid-query-devtools/package.json b/packages/solid-query-devtools/package.json index 1da186ceb23..327da5edb14 100644 --- a/packages/solid-query-devtools/package.json +++ b/packages/solid-query-devtools/package.json @@ -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" }, diff --git a/packages/solid-query-persist-client/package.json b/packages/solid-query-persist-client/package.json index 18fdecbd61e..f1f082332ce 100644 --- a/packages/solid-query-persist-client/package.json +++ b/packages/solid-query-persist-client/package.json @@ -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" }, diff --git a/packages/solid-query/package.json b/packages/solid-query/package.json index 5db10f5a95a..90116a6e8d5 100644 --- a/packages/solid-query/package.json +++ b/packages/solid-query/package.json @@ -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" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7963e8df5c1..84c59fe1df3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1590,7 +1590,7 @@ importers: version: 9.5.5(astro@5.18.1(@types/node@22.19.15)(@vercel/functions@2.2.13)(db0@0.3.4)(idb-keyval@6.2.2)(ioredis@5.10.1)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.60.1)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.8.3)(yaml@2.8.3)) '@astrojs/solid-js': specifier: ^5.0.7 - version: 5.1.3(@testing-library/jest-dom@6.9.1)(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(solid-js@2.0.0-beta.29)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + version: 5.1.3(@testing-library/jest-dom@6.9.1)(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(solid-js@2.0.0-beta.33)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) '@astrojs/tailwind': specifier: ^6.0.2 version: 6.0.2(astro@5.18.1(@types/node@22.19.15)(@vercel/functions@2.2.13)(db0@0.3.4)(idb-keyval@6.2.2)(ioredis@5.10.1)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.60.1)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.8.3)(yaml@2.8.3))(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.3)) @@ -1598,8 +1598,8 @@ importers: specifier: ^8.1.3 version: 8.2.11(@sveltejs/kit@2.57.1(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.1)(typescript@5.8.3)(vite@6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(astro@5.18.1(@types/node@22.19.15)(@vercel/functions@2.2.13)(db0@0.3.4)(idb-keyval@6.2.2)(ioredis@5.10.1)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.60.1)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.8.3)(yaml@2.8.3))(next@16.2.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.90.0))(react@19.2.4)(rollup@4.60.1)(svelte@5.55.1)(vue@3.5.31(typescript@5.8.3)) '@solidjs/web': - specifier: 2.0.0-beta.29 - version: 2.0.0-beta.29(solid-js@2.0.0-beta.29) + specifier: 2.0.0-beta.33 + version: 2.0.0-beta.33(solid-js@2.0.0-beta.33) '@tanstack/solid-query': specifier: ^6.0.0-beta.7 version: link:../../../packages/solid-query @@ -1610,8 +1610,8 @@ importers: specifier: ^5.5.6 version: 5.18.1(@types/node@22.19.15)(@vercel/functions@2.2.13)(db0@0.3.4)(idb-keyval@6.2.2)(ioredis@5.10.1)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.60.1)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.8.3)(yaml@2.8.3) solid-js: - specifier: 2.0.0-beta.29 - version: 2.0.0-beta.29 + specifier: 2.0.0-beta.33 + version: 2.0.0-beta.33 tailwindcss: specifier: ^3.4.7 version: 3.4.19(tsx@4.21.0)(yaml@2.8.3) @@ -1622,8 +1622,8 @@ importers: examples/solid/basic: dependencies: '@solidjs/web': - specifier: 2.0.0-beta.29 - version: 2.0.0-beta.29(solid-js@2.0.0-beta.29) + specifier: 2.0.0-beta.33 + version: 2.0.0-beta.33(solid-js@2.0.0-beta.33) '@tanstack/solid-query': specifier: ^6.0.0-beta.7 version: link:../../../packages/solid-query @@ -1631,8 +1631,8 @@ importers: specifier: ^6.0.0-beta.7 version: link:../../../packages/solid-query-devtools solid-js: - specifier: 2.0.0-beta.29 - version: 2.0.0-beta.29 + specifier: 2.0.0-beta.33 + version: 2.0.0-beta.33 devDependencies: typescript: specifier: 5.8.3 @@ -1642,13 +1642,13 @@ importers: version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) vite-plugin-solid: specifier: 3.0.0-next.21 - version: 3.0.0-next.21(@solidjs/web@2.0.0-beta.29(solid-js@2.0.0-beta.29))(@testing-library/jest-dom@6.9.1)(solid-js@2.0.0-beta.29)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 3.0.0-next.21(@solidjs/web@2.0.0-beta.33(solid-js@2.0.0-beta.33))(@testing-library/jest-dom@6.9.1)(solid-js@2.0.0-beta.33)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) examples/solid/basic-graphql-request: dependencies: '@solidjs/web': - specifier: 2.0.0-beta.29 - version: 2.0.0-beta.29(solid-js@2.0.0-beta.29) + specifier: 2.0.0-beta.33 + version: 2.0.0-beta.33(solid-js@2.0.0-beta.33) '@tanstack/solid-query': specifier: ^6.0.0-beta.7 version: link:../../../packages/solid-query @@ -1662,8 +1662,8 @@ importers: specifier: ^7.1.2 version: 7.4.0(graphql@16.13.2) solid-js: - specifier: 2.0.0-beta.29 - version: 2.0.0-beta.29 + specifier: 2.0.0-beta.33 + version: 2.0.0-beta.33 devDependencies: typescript: specifier: 5.8.3 @@ -1673,13 +1673,13 @@ importers: version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) vite-plugin-solid: specifier: 3.0.0-next.21 - version: 3.0.0-next.21(@solidjs/web@2.0.0-beta.29(solid-js@2.0.0-beta.29))(@testing-library/jest-dom@6.9.1)(solid-js@2.0.0-beta.29)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 3.0.0-next.21(@solidjs/web@2.0.0-beta.33(solid-js@2.0.0-beta.33))(@testing-library/jest-dom@6.9.1)(solid-js@2.0.0-beta.33)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) examples/solid/default-query-function: dependencies: '@solidjs/web': - specifier: 2.0.0-beta.29 - version: 2.0.0-beta.29(solid-js@2.0.0-beta.29) + specifier: 2.0.0-beta.33 + version: 2.0.0-beta.33(solid-js@2.0.0-beta.33) '@tanstack/solid-query': specifier: ^6.0.0-beta.7 version: link:../../../packages/solid-query @@ -1687,8 +1687,8 @@ importers: specifier: ^6.0.0-beta.7 version: link:../../../packages/solid-query-devtools solid-js: - specifier: 2.0.0-beta.29 - version: 2.0.0-beta.29 + specifier: 2.0.0-beta.33 + version: 2.0.0-beta.33 devDependencies: typescript: specifier: 5.8.3 @@ -1698,13 +1698,13 @@ importers: version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) vite-plugin-solid: specifier: 3.0.0-next.21 - version: 3.0.0-next.21(@solidjs/web@2.0.0-beta.29(solid-js@2.0.0-beta.29))(@testing-library/jest-dom@6.9.1)(solid-js@2.0.0-beta.29)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 3.0.0-next.21(@solidjs/web@2.0.0-beta.33(solid-js@2.0.0-beta.33))(@testing-library/jest-dom@6.9.1)(solid-js@2.0.0-beta.33)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) examples/solid/offline: dependencies: '@solidjs/web': - specifier: 2.0.0-beta.29 - version: 2.0.0-beta.29(solid-js@2.0.0-beta.29) + specifier: 2.0.0-beta.33 + version: 2.0.0-beta.33(solid-js@2.0.0-beta.33) '@tanstack/query-async-storage-persister': specifier: ^5.101.0 version: link:../../../packages/query-async-storage-persister @@ -1721,8 +1721,8 @@ importers: specifier: ^2.6.6 version: 2.12.14(@types/node@22.19.15)(typescript@5.8.3) solid-js: - specifier: 2.0.0-beta.29 - version: 2.0.0-beta.29 + specifier: 2.0.0-beta.33 + version: 2.0.0-beta.33 devDependencies: typescript: specifier: 5.8.3 @@ -1732,13 +1732,13 @@ importers: version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) vite-plugin-solid: specifier: 3.0.0-next.21 - version: 3.0.0-next.21(@solidjs/web@2.0.0-beta.29(solid-js@2.0.0-beta.29))(@testing-library/jest-dom@6.9.1)(solid-js@2.0.0-beta.29)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 3.0.0-next.21(@solidjs/web@2.0.0-beta.33(solid-js@2.0.0-beta.33))(@testing-library/jest-dom@6.9.1)(solid-js@2.0.0-beta.33)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) examples/solid/simple: dependencies: '@solidjs/web': - specifier: 2.0.0-beta.29 - version: 2.0.0-beta.29(solid-js@2.0.0-beta.29) + specifier: 2.0.0-beta.33 + version: 2.0.0-beta.33(solid-js@2.0.0-beta.33) '@tanstack/solid-query': specifier: ^6.0.0-beta.7 version: link:../../../packages/solid-query @@ -1746,8 +1746,8 @@ importers: specifier: ^6.0.0-beta.7 version: link:../../../packages/solid-query-devtools solid-js: - specifier: 2.0.0-beta.29 - version: 2.0.0-beta.29 + specifier: 2.0.0-beta.33 + version: 2.0.0-beta.33 devDependencies: '@tanstack/eslint-plugin-query': specifier: ^5.101.0 @@ -1760,22 +1760,22 @@ importers: version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) vite-plugin-solid: specifier: 3.0.0-next.21 - version: 3.0.0-next.21(@solidjs/web@2.0.0-beta.29(solid-js@2.0.0-beta.29))(@testing-library/jest-dom@6.9.1)(solid-js@2.0.0-beta.29)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 3.0.0-next.21(@solidjs/web@2.0.0-beta.33(solid-js@2.0.0-beta.33))(@testing-library/jest-dom@6.9.1)(solid-js@2.0.0-beta.33)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) examples/solid/solid-start-streaming: dependencies: '@solidjs/meta': specifier: ^0.29.4 - version: 0.29.4(solid-js@2.0.0-beta.29) + version: 0.29.4(solid-js@2.0.0-beta.33) '@solidjs/router': specifier: ^0.15.3 - version: 0.15.4(solid-js@2.0.0-beta.29) + version: 0.15.4(solid-js@2.0.0-beta.33) '@solidjs/start': specifier: ^1.1.3 - version: 1.3.2(@testing-library/jest-dom@6.9.1)(solid-js@2.0.0-beta.29)(vinxi@0.5.11(@types/node@22.19.15)(@vercel/functions@2.2.13)(db0@0.3.4)(idb-keyval@6.2.2)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 1.3.2(@testing-library/jest-dom@6.9.1)(solid-js@2.0.0-beta.33)(vinxi@0.5.11(@types/node@22.19.15)(@vercel/functions@2.2.13)(db0@0.3.4)(idb-keyval@6.2.2)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) '@solidjs/web': - specifier: 2.0.0-beta.29 - version: 2.0.0-beta.29(solid-js@2.0.0-beta.29) + specifier: 2.0.0-beta.33 + version: 2.0.0-beta.33(solid-js@2.0.0-beta.33) '@tanstack/solid-query': specifier: ^6.0.0-beta.7 version: link:../../../packages/solid-query @@ -1783,8 +1783,8 @@ importers: specifier: ^6.0.0-beta.7 version: link:../../../packages/solid-query-devtools solid-js: - specifier: 2.0.0-beta.29 - version: 2.0.0-beta.29 + specifier: 2.0.0-beta.33 + version: 2.0.0-beta.33 vinxi: specifier: ^0.5.3 version: 0.5.11(@types/node@22.19.15)(@vercel/functions@2.2.13)(db0@0.3.4)(idb-keyval@6.2.2)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) @@ -2393,8 +2393,8 @@ importers: integrations/solid-vite: dependencies: '@solidjs/web': - specifier: 2.0.0-beta.29 - version: 2.0.0-beta.29(solid-js@2.0.0-beta.29) + specifier: 2.0.0-beta.33 + version: 2.0.0-beta.33(solid-js@2.0.0-beta.33) '@tanstack/solid-query': specifier: workspace:* version: link:../../packages/solid-query @@ -2402,14 +2402,14 @@ importers: specifier: workspace:* version: link:../../packages/solid-query-devtools solid-js: - specifier: 2.0.0-beta.29 - version: 2.0.0-beta.29 + specifier: 2.0.0-beta.33 + version: 2.0.0-beta.33 vite: specifier: ^6.4.1 version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) vite-plugin-solid: specifier: 3.0.0-next.21 - version: 3.0.0-next.21(@solidjs/web@2.0.0-beta.29(solid-js@2.0.0-beta.29))(@testing-library/jest-dom@6.9.1)(solid-js@2.0.0-beta.29)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 3.0.0-next.21(@solidjs/web@2.0.0-beta.33(solid-js@2.0.0-beta.33))(@testing-library/jest-dom@6.9.1)(solid-js@2.0.0-beta.33)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) integrations/svelte-vite: devDependencies: @@ -2955,28 +2955,28 @@ importers: version: 7.28.5(@babel/core@7.29.0) '@solidjs/testing-library': specifier: ^0.8.10 - version: 0.8.10(@solidjs/router@0.15.4(solid-js@2.0.0-beta.29))(solid-js@2.0.0-beta.29) + version: 0.8.10(@solidjs/router@0.15.4(solid-js@2.0.0-beta.33))(solid-js@2.0.0-beta.33) '@solidjs/web': - specifier: 2.0.0-beta.29 - version: 2.0.0-beta.29(solid-js@2.0.0-beta.29) + specifier: 2.0.0-beta.33 + version: 2.0.0-beta.33(solid-js@2.0.0-beta.33) '@tanstack/query-test-utils': specifier: workspace:* version: link:../query-test-utils babel-preset-solid: - specifier: 2.0.0-beta.29 - version: 2.0.0-beta.29(@babel/core@7.29.0)(solid-js@2.0.0-beta.29) + specifier: 2.0.0-beta.33 + version: 2.0.0-beta.33(@babel/core@7.29.0)(solid-js@2.0.0-beta.33) npm-run-all2: specifier: ^5.0.0 version: 5.0.2 solid-js: - specifier: 2.0.0-beta.29 - version: 2.0.0-beta.29 + specifier: 2.0.0-beta.33 + version: 2.0.0-beta.33 tsup-preset-solid: specifier: ^2.2.0 - version: 2.2.0(esbuild@0.27.4)(solid-js@2.0.0-beta.29)(tsup@8.5.1(@microsoft/api-extractor@7.47.7(@types/node@22.19.15))(jiti@2.6.1)(postcss@8.5.8)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)) + version: 2.2.0(esbuild@0.27.4)(solid-js@2.0.0-beta.33)(tsup@8.5.1(@microsoft/api-extractor@7.47.7(@types/node@22.19.15))(jiti@2.6.1)(postcss@8.5.8)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)) vite-plugin-solid: specifier: 3.0.0-next.21 - version: 3.0.0-next.21(@solidjs/web@2.0.0-beta.29(solid-js@2.0.0-beta.29))(@testing-library/jest-dom@6.9.1)(solid-js@2.0.0-beta.29)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 3.0.0-next.21(@solidjs/web@2.0.0-beta.33(solid-js@2.0.0-beta.33))(@testing-library/jest-dom@6.9.1)(solid-js@2.0.0-beta.33)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) packages/solid-query-devtools: dependencies: @@ -2991,32 +2991,32 @@ importers: specifier: ^7.18.6 version: 7.28.5(@babel/core@7.29.0) '@solidjs/signals': - specifier: ^2.0.0-beta.29 - version: 2.0.0-beta.29 + specifier: ^2.0.0-beta.33 + version: 2.0.0-beta.33 '@solidjs/testing-library': specifier: ^0.8.10 - version: 0.8.10(@solidjs/router@0.15.4(solid-js@2.0.0-beta.29))(solid-js@2.0.0-beta.29) + version: 0.8.10(@solidjs/router@0.15.4(solid-js@2.0.0-beta.33))(solid-js@2.0.0-beta.33) '@solidjs/web': - specifier: 2.0.0-beta.29 - version: 2.0.0-beta.29(solid-js@2.0.0-beta.29) + specifier: 2.0.0-beta.33 + version: 2.0.0-beta.33(solid-js@2.0.0-beta.33) '@tanstack/solid-query': specifier: workspace:* version: link:../solid-query babel-preset-solid: - specifier: 2.0.0-beta.29 - version: 2.0.0-beta.29(@babel/core@7.29.0)(solid-js@2.0.0-beta.29) + specifier: 2.0.0-beta.33 + version: 2.0.0-beta.33(@babel/core@7.29.0)(solid-js@2.0.0-beta.33) npm-run-all2: specifier: ^5.0.0 version: 5.0.2 solid-js: - specifier: 2.0.0-beta.29 - version: 2.0.0-beta.29 + specifier: 2.0.0-beta.33 + version: 2.0.0-beta.33 tsup-preset-solid: specifier: ^2.2.0 - version: 2.2.0(esbuild@0.27.4)(solid-js@2.0.0-beta.29)(tsup@8.5.1(@microsoft/api-extractor@7.47.7(@types/node@22.19.15))(jiti@2.6.1)(postcss@8.5.8)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)) + version: 2.2.0(esbuild@0.27.4)(solid-js@2.0.0-beta.33)(tsup@8.5.1(@microsoft/api-extractor@7.47.7(@types/node@22.19.15))(jiti@2.6.1)(postcss@8.5.8)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)) vite-plugin-solid: specifier: 3.0.0-next.21 - version: 3.0.0-next.21(@solidjs/web@2.0.0-beta.29(solid-js@2.0.0-beta.29))(@testing-library/jest-dom@6.9.1)(solid-js@2.0.0-beta.29)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 3.0.0-next.21(@solidjs/web@2.0.0-beta.33(solid-js@2.0.0-beta.33))(@testing-library/jest-dom@6.9.1)(solid-js@2.0.0-beta.33)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) packages/solid-query-persist-client: dependencies: @@ -3032,10 +3032,10 @@ importers: version: 7.28.5(@babel/core@7.29.0) '@solidjs/testing-library': specifier: ^0.8.10 - version: 0.8.10(@solidjs/router@0.15.4(solid-js@2.0.0-beta.29))(solid-js@2.0.0-beta.29) + version: 0.8.10(@solidjs/router@0.15.4(solid-js@2.0.0-beta.33))(solid-js@2.0.0-beta.33) '@solidjs/web': - specifier: 2.0.0-beta.29 - version: 2.0.0-beta.29(solid-js@2.0.0-beta.29) + specifier: 2.0.0-beta.33 + version: 2.0.0-beta.33(solid-js@2.0.0-beta.33) '@tanstack/query-test-utils': specifier: workspace:* version: link:../query-test-utils @@ -3043,20 +3043,20 @@ importers: specifier: workspace:* version: link:../solid-query babel-preset-solid: - specifier: 2.0.0-beta.29 - version: 2.0.0-beta.29(@babel/core@7.29.0)(solid-js@2.0.0-beta.29) + specifier: 2.0.0-beta.33 + version: 2.0.0-beta.33(@babel/core@7.29.0)(solid-js@2.0.0-beta.33) npm-run-all2: specifier: ^5.0.0 version: 5.0.2 solid-js: - specifier: 2.0.0-beta.29 - version: 2.0.0-beta.29 + specifier: 2.0.0-beta.33 + version: 2.0.0-beta.33 tsup-preset-solid: specifier: ^2.2.0 - version: 2.2.0(esbuild@0.27.4)(solid-js@2.0.0-beta.29)(tsup@8.5.1(@microsoft/api-extractor@7.47.7(@types/node@22.19.15))(jiti@2.6.1)(postcss@8.5.8)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)) + version: 2.2.0(esbuild@0.27.4)(solid-js@2.0.0-beta.33)(tsup@8.5.1(@microsoft/api-extractor@7.47.7(@types/node@22.19.15))(jiti@2.6.1)(postcss@8.5.8)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)) vite-plugin-solid: specifier: 3.0.0-next.21 - version: 3.0.0-next.21(@solidjs/web@2.0.0-beta.29(solid-js@2.0.0-beta.29))(@testing-library/jest-dom@6.9.1)(solid-js@2.0.0-beta.29)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 3.0.0-next.21(@solidjs/web@2.0.0-beta.33(solid-js@2.0.0-beta.33))(@testing-library/jest-dom@6.9.1)(solid-js@2.0.0-beta.33)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) packages/svelte-query: dependencies: @@ -4699,6 +4699,11 @@ packages: peerDependencies: '@babel/core': ^7.20.12 + '@dom-expressions/babel-plugin-jsx@0.50.0-next.41': + resolution: {integrity: sha512-JQnjahkQysfo5P5ahsOqtA1yN3GeAIaAke1FA4s/vy+ZEOpUHbI5IpdF43+RrVghoTcgtML9tEC0fINtJ4Cmpg==} + peerDependencies: + '@babel/core': ^7.20.12 + '@dom-expressions/compiler-darwin-arm64@0.50.0-next.34': resolution: {integrity: sha512-MkIi1jWuVPaFoyM6qXlx3EPYXgUyySCDUJ/2zxpqzITW6/fkVvCQ++o2MrumgNFjQ/wxXdg8j0WMLcarnJ2H2Q==} cpu: [arm64] @@ -7220,8 +7225,8 @@ packages: peerDependencies: solid-js: ^1.8.6 - '@solidjs/signals@2.0.0-beta.29': - resolution: {integrity: sha512-/W4goRwA/t9SqA0acIcD008pBJr2p5BMxVpzcdvWMYNswyE88se1ecJKQO/QTa6DjVRZvplOOtKEtDRHpEo3rQ==} + '@solidjs/signals@2.0.0-beta.33': + resolution: {integrity: sha512-W+ftJ7bzIE+wfJnx2oM2fEFTejHTce+vKRP3PcopBFqoEFNLZKgnnXxXyyD65QZVNr/CPv2Z/Naa+NuzGHFvVg==} '@solidjs/start@1.3.2': resolution: {integrity: sha512-tasDl3utVbtP0rr4InB3ntBIFV2upvEiFrOOCkRrAA3yBfjx9elpxnc94sJQXo65PNYdAAAkPIC6h93vLrtwHg==} @@ -7238,10 +7243,10 @@ packages: '@solidjs/router': optional: true - '@solidjs/web@2.0.0-beta.29': - resolution: {integrity: sha512-Bsb1JP1n6OIuiCQiYlE1B3+cNY0UlDPyA1UUYC3trFudUMs0aDCxGmY/B5KXnmw4NNcRIUA2bW46h3FC9qhWXA==} + '@solidjs/web@2.0.0-beta.33': + resolution: {integrity: sha512-4wGUjG9jFGbI1dHHYBa8q/Z7w6ijFCNV99UsifQCDxo25ZH1kkK5qR6b2yU/mrKiKgdBbY0SBPcJGYMVIiSUBQ==} peerDependencies: - solid-js: ^2.0.0-beta.29 + solid-js: ^2.0.0-beta.33 '@speed-highlight/core@1.2.15': resolution: {integrity: sha512-BMq1K3DsElxDWawkX6eLg9+CKJrTVGCBAWVuHXVUV2u0s2711qiChLSId6ikYPfxhdYocLNt3wWwSvDiTvFabw==} @@ -8756,6 +8761,15 @@ packages: solid-js: optional: true + babel-preset-solid@2.0.0-beta.33: + resolution: {integrity: sha512-YB1O8FBbIJ9GOAZ3AZrA5T3w86RjO5lPV/Oge8c5XccQYatjUtostD946JgKK/xO/7W7lwpWBjXhDJkKXnh8cQ==} + peerDependencies: + '@babel/core': ^7.0.0 + solid-js: ^2.0.0-beta.33 + peerDependenciesMeta: + solid-js: + optional: true + bail@2.0.2: resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} @@ -14778,8 +14792,8 @@ packages: solid-js@1.9.12: resolution: {integrity: sha512-QzKaSJq2/iDrWR1As6MHZQ8fQkdOBf8GReYb7L5iKwMGceg7HxDcaOHk0at66tNgn9U2U7dXo8ZZpLIAmGMzgw==} - solid-js@2.0.0-beta.29: - resolution: {integrity: sha512-Jv0jRw7joECw63cRfeqo5lnEVNVhAcH1hWt1nSvyp5npTgk6eyCmP5UDLTNWm6ox5G5LmBJzFkBulm+hp9+vmA==} + solid-js@2.0.0-beta.33: + resolution: {integrity: sha512-nayHb+qIYS1PsyJwzN+zc4mJxdaptLWzWaHMCIU+zS/byrp1xaO5Vsl22meoPciDtafUmxoyO4TxnjwQQobNgQ==} solid-presence@0.1.8: resolution: {integrity: sha512-pWGtXUFWYYUZNbg5YpG5vkQJyOtzn2KXhxYaMx/4I+lylTLYkITOLevaCwMRN+liCVk0pqB6EayLWojNqBFECA==} @@ -17101,11 +17115,11 @@ snapshots: dependencies: prismjs: 1.30.0 - '@astrojs/solid-js@5.1.3(@testing-library/jest-dom@6.9.1)(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(solid-js@2.0.0-beta.29)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)': + '@astrojs/solid-js@5.1.3(@testing-library/jest-dom@6.9.1)(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(solid-js@2.0.0-beta.33)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)': dependencies: - solid-js: 2.0.0-beta.29 + solid-js: 2.0.0-beta.33 vite: 6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) - vite-plugin-solid: 2.11.11(@testing-library/jest-dom@6.9.1)(solid-js@2.0.0-beta.29)(vite@6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + vite-plugin-solid: 2.11.11(@testing-library/jest-dom@6.9.1)(solid-js@2.0.0-beta.33)(vite@6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) transitivePeerDependencies: - '@testing-library/jest-dom' - '@types/node' @@ -18595,6 +18609,16 @@ snapshots: parse5: 7.3.0 validate-html-nesting: 1.2.4 + '@dom-expressions/babel-plugin-jsx@0.50.0-next.41(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.18.6 + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) + '@babel/types': 7.29.0 + html-entities: 2.3.3 + parse5: 7.3.0 + validate-html-nesting: 1.2.4 + '@dom-expressions/compiler-darwin-arm64@0.50.0-next.34': optional: true @@ -21124,22 +21148,22 @@ snapshots: dependencies: solid-js: 1.9.12 - '@solidjs/meta@0.29.4(solid-js@2.0.0-beta.29)': + '@solidjs/meta@0.29.4(solid-js@2.0.0-beta.33)': dependencies: - solid-js: 2.0.0-beta.29 + solid-js: 2.0.0-beta.33 '@solidjs/router@0.15.4(solid-js@1.9.12)': dependencies: solid-js: 1.9.12 optional: true - '@solidjs/router@0.15.4(solid-js@2.0.0-beta.29)': + '@solidjs/router@0.15.4(solid-js@2.0.0-beta.33)': dependencies: - solid-js: 2.0.0-beta.29 + solid-js: 2.0.0-beta.33 - '@solidjs/signals@2.0.0-beta.29': {} + '@solidjs/signals@2.0.0-beta.33': {} - '@solidjs/start@1.3.2(@testing-library/jest-dom@6.9.1)(solid-js@2.0.0-beta.29)(vinxi@0.5.11(@types/node@22.19.15)(@vercel/functions@2.2.13)(db0@0.3.4)(idb-keyval@6.2.2)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': + '@solidjs/start@1.3.2(@testing-library/jest-dom@6.9.1)(solid-js@2.0.0-beta.33)(vinxi@0.5.11(@types/node@22.19.15)(@vercel/functions@2.2.13)(db0@0.3.4)(idb-keyval@6.2.2)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': dependencies: '@tanstack/server-functions-plugin': 1.121.21(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) '@vinxi/plugin-directives': 0.5.1(vinxi@0.5.11(@types/node@22.19.15)(@vercel/functions@2.2.13)(db0@0.3.4)(idb-keyval@6.2.2)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) @@ -21153,10 +21177,10 @@ snapshots: seroval-plugins: 1.5.1(seroval@1.5.1) shiki: 1.29.2 source-map-js: 1.2.1 - terracotta: 1.1.0(solid-js@2.0.0-beta.29) + terracotta: 1.1.0(solid-js@2.0.0-beta.33) tinyglobby: 0.2.15 vinxi: 0.5.11(@types/node@22.19.15)(@vercel/functions@2.2.13)(db0@0.3.4)(idb-keyval@6.2.2)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) - vite-plugin-solid: 2.11.11(@testing-library/jest-dom@6.9.1)(solid-js@2.0.0-beta.29)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + vite-plugin-solid: 2.11.11(@testing-library/jest-dom@6.9.1)(solid-js@2.0.0-beta.33)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) transitivePeerDependencies: - '@testing-library/jest-dom' - solid-js @@ -21170,18 +21194,18 @@ snapshots: optionalDependencies: '@solidjs/router': 0.15.4(solid-js@1.9.12) - '@solidjs/testing-library@0.8.10(@solidjs/router@0.15.4(solid-js@2.0.0-beta.29))(solid-js@2.0.0-beta.29)': + '@solidjs/testing-library@0.8.10(@solidjs/router@0.15.4(solid-js@2.0.0-beta.33))(solid-js@2.0.0-beta.33)': dependencies: '@testing-library/dom': 10.4.1 - solid-js: 2.0.0-beta.29 + solid-js: 2.0.0-beta.33 optionalDependencies: - '@solidjs/router': 0.15.4(solid-js@2.0.0-beta.29) + '@solidjs/router': 0.15.4(solid-js@2.0.0-beta.33) - '@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)': dependencies: seroval: 1.5.6 seroval-plugins: 1.5.6(seroval@1.5.6) - solid-js: 2.0.0-beta.29 + solid-js: 2.0.0-beta.33 '@speed-highlight/core@1.2.15': {} @@ -23337,19 +23361,26 @@ snapshots: optionalDependencies: solid-js: 1.9.12 - babel-preset-solid@1.9.12(@babel/core@7.29.0)(solid-js@2.0.0-beta.29): + babel-preset-solid@1.9.12(@babel/core@7.29.0)(solid-js@2.0.0-beta.33): dependencies: '@babel/core': 7.29.0 babel-plugin-jsx-dom-expressions: 0.40.6(@babel/core@7.29.0) optionalDependencies: - solid-js: 2.0.0-beta.29 + solid-js: 2.0.0-beta.33 - babel-preset-solid@2.0.0-beta.29(@babel/core@7.29.0)(solid-js@2.0.0-beta.29): + babel-preset-solid@2.0.0-beta.29(@babel/core@7.29.0)(solid-js@2.0.0-beta.33): dependencies: '@babel/core': 7.29.0 '@dom-expressions/babel-plugin-jsx': 0.50.0-next.34(@babel/core@7.29.0) optionalDependencies: - solid-js: 2.0.0-beta.29 + solid-js: 2.0.0-beta.33 + + babel-preset-solid@2.0.0-beta.33(@babel/core@7.29.0)(solid-js@2.0.0-beta.33): + dependencies: + '@babel/core': 7.29.0 + '@dom-expressions/babel-plugin-jsx': 0.50.0-next.41(@babel/core@7.29.0) + optionalDependencies: + solid-js: 2.0.0-beta.33 bail@2.0.2: {} @@ -24901,13 +24932,13 @@ snapshots: transitivePeerDependencies: - supports-color - esbuild-plugin-solid@0.5.0(esbuild@0.27.4)(solid-js@2.0.0-beta.29): + esbuild-plugin-solid@0.5.0(esbuild@0.27.4)(solid-js@2.0.0-beta.33): dependencies: '@babel/core': 7.29.0 '@babel/preset-typescript': 7.28.5(@babel/core@7.29.0) - babel-preset-solid: 1.9.12(@babel/core@7.29.0)(solid-js@2.0.0-beta.29) + babel-preset-solid: 1.9.12(@babel/core@7.29.0)(solid-js@2.0.0-beta.33) esbuild: 0.27.4 - solid-js: 2.0.0-beta.29 + solid-js: 2.0.0-beta.33 transitivePeerDependencies: - supports-color @@ -30987,9 +31018,9 @@ snapshots: seroval: 1.5.1 seroval-plugins: 1.5.1(seroval@1.5.1) - solid-js@2.0.0-beta.29: + solid-js@2.0.0-beta.33: dependencies: - '@solidjs/signals': 2.0.0-beta.29 + '@solidjs/signals': 2.0.0-beta.33 csstype: 3.2.3 seroval: 1.5.6 seroval-plugins: 1.5.6(seroval@1.5.6) @@ -31013,12 +31044,12 @@ snapshots: transitivePeerDependencies: - supports-color - solid-refresh@0.6.3(solid-js@2.0.0-beta.29): + solid-refresh@0.6.3(solid-js@2.0.0-beta.33): dependencies: '@babel/generator': 7.29.1 '@babel/helper-module-imports': 7.28.6 '@babel/types': 7.29.0 - solid-js: 2.0.0-beta.29 + solid-js: 2.0.0-beta.33 transitivePeerDependencies: - supports-color @@ -31028,9 +31059,9 @@ snapshots: '@solid-primitives/transition-group': 1.1.2(solid-js@1.9.12) solid-js: 1.9.12 - solid-use@0.9.1(solid-js@2.0.0-beta.29): + solid-use@0.9.1(solid-js@2.0.0-beta.33): dependencies: - solid-js: 2.0.0-beta.29 + solid-js: 2.0.0-beta.33 sort-by@1.2.0: dependencies: @@ -31543,10 +31574,10 @@ snapshots: ansi-escapes: 4.3.2 supports-hyperlinks: 2.3.0 - terracotta@1.1.0(solid-js@2.0.0-beta.29): + terracotta@1.1.0(solid-js@2.0.0-beta.33): dependencies: - solid-js: 2.0.0-beta.29 - solid-use: 0.9.1(solid-js@2.0.0-beta.29) + solid-js: 2.0.0-beta.33 + solid-use: 0.9.1(solid-js@2.0.0-beta.33) terser-webpack-plugin@1.4.6(webpack@4.47.0): dependencies: @@ -31762,9 +31793,9 @@ snapshots: - solid-js - supports-color - tsup-preset-solid@2.2.0(esbuild@0.27.4)(solid-js@2.0.0-beta.29)(tsup@8.5.1(@microsoft/api-extractor@7.47.7(@types/node@22.19.15))(jiti@2.6.1)(postcss@8.5.8)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)): + tsup-preset-solid@2.2.0(esbuild@0.27.4)(solid-js@2.0.0-beta.33)(tsup@8.5.1(@microsoft/api-extractor@7.47.7(@types/node@22.19.15))(jiti@2.6.1)(postcss@8.5.8)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)): dependencies: - esbuild-plugin-solid: 0.5.0(esbuild@0.27.4)(solid-js@2.0.0-beta.29) + esbuild-plugin-solid: 0.5.0(esbuild@0.27.4)(solid-js@2.0.0-beta.33) tsup: 8.5.1(@microsoft/api-extractor@7.47.7(@types/node@22.19.15))(jiti@2.6.1)(postcss@8.5.8)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3) transitivePeerDependencies: - esbuild @@ -32439,14 +32470,14 @@ snapshots: transitivePeerDependencies: - supports-color - vite-plugin-solid@2.11.11(@testing-library/jest-dom@6.9.1)(solid-js@2.0.0-beta.29)(vite@6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): + vite-plugin-solid@2.11.11(@testing-library/jest-dom@6.9.1)(solid-js@2.0.0-beta.33)(vite@6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): dependencies: '@babel/core': 7.29.0 '@types/babel__core': 7.20.5 - babel-preset-solid: 1.9.12(@babel/core@7.29.0)(solid-js@2.0.0-beta.29) + babel-preset-solid: 1.9.12(@babel/core@7.29.0)(solid-js@2.0.0-beta.33) merge-anything: 5.1.7 - solid-js: 2.0.0-beta.29 - solid-refresh: 0.6.3(solid-js@2.0.0-beta.29) + solid-js: 2.0.0-beta.33 + solid-refresh: 0.6.3(solid-js@2.0.0-beta.33) vite: 6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) vitefu: 1.1.2(vite@6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) optionalDependencies: @@ -32454,14 +32485,14 @@ snapshots: transitivePeerDependencies: - supports-color - vite-plugin-solid@2.11.11(@testing-library/jest-dom@6.9.1)(solid-js@2.0.0-beta.29)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): + vite-plugin-solid@2.11.11(@testing-library/jest-dom@6.9.1)(solid-js@2.0.0-beta.33)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): dependencies: '@babel/core': 7.29.0 '@types/babel__core': 7.20.5 - babel-preset-solid: 1.9.12(@babel/core@7.29.0)(solid-js@2.0.0-beta.29) + babel-preset-solid: 1.9.12(@babel/core@7.29.0)(solid-js@2.0.0-beta.33) merge-anything: 5.1.7 - solid-js: 2.0.0-beta.29 - solid-refresh: 0.6.3(solid-js@2.0.0-beta.29) + solid-js: 2.0.0-beta.33 + solid-refresh: 0.6.3(solid-js@2.0.0-beta.33) vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) vitefu: 1.1.2(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) optionalDependencies: @@ -32469,16 +32500,16 @@ snapshots: transitivePeerDependencies: - supports-color - vite-plugin-solid@3.0.0-next.21(@solidjs/web@2.0.0-beta.29(solid-js@2.0.0-beta.29))(@testing-library/jest-dom@6.9.1)(solid-js@2.0.0-beta.29)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): + vite-plugin-solid@3.0.0-next.21(@solidjs/web@2.0.0-beta.33(solid-js@2.0.0-beta.33))(@testing-library/jest-dom@6.9.1)(solid-js@2.0.0-beta.33)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): dependencies: '@ampproject/remapping': 2.3.0 '@babel/core': 7.29.0 '@dom-expressions/compiler': 0.50.0-next.34 - '@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) '@types/babel__core': 7.20.5 - babel-preset-solid: 2.0.0-beta.29(@babel/core@7.29.0)(solid-js@2.0.0-beta.29) + babel-preset-solid: 2.0.0-beta.29(@babel/core@7.29.0)(solid-js@2.0.0-beta.33) merge-anything: 5.1.7 - solid-js: 2.0.0-beta.29 + solid-js: 2.0.0-beta.33 vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) vitefu: 1.1.2(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) optionalDependencies: From 5ac50638060b135f6ccd2fd81c9ffbebdb751e15 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Tue, 11 Aug 2026 18:25:47 +0000 Subject: [PATCH 6/8] ci: apply automated fixes --- packages/solid-query/src/__tests__/hydration.test.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/solid-query/src/__tests__/hydration.test.tsx b/packages/solid-query/src/__tests__/hydration.test.tsx index 9b2ecf6abca..74581ad130b 100644 --- a/packages/solid-query/src/__tests__/hydration.test.tsx +++ b/packages/solid-query/src/__tests__/hydration.test.tsx @@ -205,9 +205,7 @@ describe('SSR hydration', () => { // Double-priming was silent: no state write, no observer churn, no // refetch of the fresh query. (The stale query's mount refetch per // normal staleness rules is the only update source.) - expect( - updatedHashes.filter((hash) => hash === '["fresh"]'), - ).toEqual([]) + expect(updatedHashes.filter((hash) => hash === '["fresh"]')).toEqual([]) expect(app.counts.fresh).toBe(0) expect(container.querySelector('#fresh')?.textContent).toBe( 'fresh-server', From 572c1772366971ebff4e652344eb8fd1bf444175 Mon Sep 17 00:00:00 2001 From: Ryan Carniato Date: Tue, 11 Aug 2026 12:11:52 -0700 Subject: [PATCH 7/8] test(eslint-plugin-query): give type-checked RuleTester suites timeout headroom The first test executed by each type-checked RuleTester (parserOptions.project: true) pays the one-time cost of building the TS program for the ts-fixture. On a shared CI runner - this PR adds a solid-query test task that builds vite fixture bundles in parallel under Nx - that cold build pushed the first type-aware test in no-rest-destructuring.test.ts and no-void-query-fn.test.ts just past vitest's 5s default (5.9s measured), failing the run on timeouts with zero assertion failures. Raise the package's testTimeout to 15s; no test logic changes. Co-authored-by: Cursor --- packages/eslint-plugin-query/vite.config.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/eslint-plugin-query/vite.config.ts b/packages/eslint-plugin-query/vite.config.ts index 2014b4f928b..eacad076546 100644 --- a/packages/eslint-plugin-query/vite.config.ts +++ b/packages/eslint-plugin-query/vite.config.ts @@ -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', From 70b2e10cb05cad8deb42118d25b5ef4a1df885c2 Mon Sep 17 00:00:00 2001 From: Ryan Carniato Date: Tue, 11 Aug 2026 14:10:21 -0700 Subject: [PATCH 8/8] =?UTF-8?q?chore(solid-query):=20satisfy=20knip=20?= =?UTF-8?q?=E2=80=94=20ignore=20child-process=20test=20fixtures,=20unexpor?= =?UTF-8?q?t=20internal=20coordinator=20type?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The hydration fixture app is only reachable dynamically (built and rendered via a spawned build-and-render.mjs), so knip can't trace it; ignore the fixture dir in the solid-query workspace like the existing query-codemods/lit-query fixture ignores. HydrationCoordinator is only used within hydrationChannel.ts, so it doesn't need to be exported. Co-authored-by: Cursor --- knip.json | 1 + packages/solid-query/src/hydrationChannel.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/knip.json b/knip.json index a2293598ff6..e6b4cd8efe1 100644 --- a/knip.json +++ b/knip.json @@ -20,6 +20,7 @@ "ignore": ["**/__testfixtures__/**"] }, "packages/solid-query": { + "ignore": ["src/__tests__/fixtures/**"], "ignoreDependencies": ["esbuild"] }, "packages/solid-query-devtools": { diff --git a/packages/solid-query/src/hydrationChannel.ts b/packages/solid-query/src/hydrationChannel.ts index 4a0d9927d05..75bd4359cc8 100644 --- a/packages/solid-query/src/hydrationChannel.ts +++ b/packages/solid-query/src/hydrationChannel.ts @@ -176,7 +176,7 @@ export function createServerDehydrationChannel( } } -export interface HydrationCoordinator { +interface HydrationCoordinator { /** * Prime the QueryClient from a channel yield. Entries already applied * (same queryHash and dataUpdatedAt) are skipped; the rest go through