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/.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/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/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/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', 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/packages/solid-query/src/QueryClientProvider.tsx b/packages/solid-query/src/QueryClientProvider.tsx index 998c42eed06..9f2beb95e4c 100644 --- a/packages/solid-query/src/QueryClientProvider.tsx +++ b/packages/solid-query/src/QueryClientProvider.tsx @@ -1,7 +1,21 @@ -import { createContext, onCleanup, useContext } from 'solid-js' +import { + createContext, + createRenderEffect, + createSignal, + onCleanup, + useContext, +} from 'solid-js' +import { + HydrationCoordinatorContext, + createHydrationCoordinator, + createServerDehydrationChannel, +} from './hydrationChannel' +import type { DehydrationChannelYield } from './hydrationChannel' import type { QueryClient } from './QueryClient' import type { JSX } from '@solidjs/web' +const isServer = typeof window === 'undefined' + export const QueryClientContext = createContext<(() => QueryClient) | null>( null, ) @@ -30,9 +44,53 @@ export const QueryClientProvider = ( props.client.mount() onCleanup(() => props.client.unmount()) + // Library-owned serialization channel for SSR dehydration. + // + // Server: the computation's value IS the channel's async iterable, so + // Solid serializes it through its normal per-computation signal path: + // the server runtime tees the iterator into the hydration serializer + // (`ctx.serialize(id, tapped)` in solid-js' `processResult`) and + // seroval streams each cumulative dehydrated-cache snapshot to the + // client as a chunk riding the SSR stream, with the entry objects + // inside deduplicated by reference against everything else in the + // payload. Nothing reads the signal during SSR, so it never suspends + // anything. + // + // Client, hydrating: Solid replays the serialized iterable through the + // per-computation signal path (`hydrateSignalFromAsyncIterable`). + // Yields that were still buffered when hydration began are conflated + // to the LATEST yield (`normalizeIterator`) — lossless here because + // every yield is a cumulative snapshot — and live yields after that + // apply one at a time. Requires a solid-js build with the buffered + // async-iterable replay conflation fix (> 2.0.0-beta.32): before it, + // the replay dropped every buffered yield after the first, including + // the terminal `done` snapshot. The render effect below hands each + // signal value to the coordinator, which primes the QueryClient via + // query-core hydrate() (newer-wins) and unblocks `useBaseQuery` + // subscribers waiting on their query's entry. + // + // Client, fresh mount: the compute returns undefined and the effect + // never fires. + const [channelValue] = createSignal( + () => (isServer ? createServerDehydrationChannel(props.client) : undefined), + ) + const coordinator = isServer + ? null + : createHydrationCoordinator(() => props.client) + createRenderEffect( + () => (isServer ? undefined : channelValue()), + (value) => { + if (value && coordinator) { + coordinator.applyYield(value) + } + }, + ) + return ( props.client}> - {props.children} + + {props.children} + ) } 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..c730da6421a --- /dev/null +++ b/packages/solid-query/src/__tests__/fixtures/hydration/entry-server.tsx @@ -0,0 +1,41 @@ +/** + * 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 { renderToStream } 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 } + +// 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() + .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..74581ad130b --- /dev/null +++ b/packages/solid-query/src/__tests__/hydration.test.tsx @@ -0,0 +1,419 @@ +/** + * 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 { hydrate as hydrateQueryClient } from '@tanstack/query-core' +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() + } + }) + + 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', () => { + 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('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() + 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() + } + }) +}) diff --git a/packages/solid-query/src/hydrationChannel.ts b/packages/solid-query/src/hydrationChannel.ts new file mode 100644 index 00000000000..75bd4359cc8 --- /dev/null +++ b/packages/solid-query/src/hydrationChannel.ts @@ -0,0 +1,260 @@ +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: + * + * - 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 + * 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 + * 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 + * 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, + }) + }, + } + }, + } +} + +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(() => { 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: