Commit 67c8179
fix(solid-query): consume hydration data client-side via a provider-owned streaming channel (#11168)
* fix(solid-query): hydrate the query cache through a provider-owned dehydration channel
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 <cursoragent@cursor.com>
* test(solid-query): add SSR and streaming hydration integration tests
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 <cursoragent@cursor.com>
* 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 <cursoragent@cursor.com>
* refactor(solid-query): signal-shape the dehydration channel — buffered replay conflation makes it the right container
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 <cursoragent@cursor.com>
* 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 <cursoragent@cursor.com>
* ci: apply automated fixes
* 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 <cursoragent@cursor.com>
* chore(solid-query): satisfy knip — ignore child-process test fixtures, unexport internal coordinator type
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 <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>1 parent 1c6a856 commit 67c8179
27 files changed
Lines changed: 1488 additions & 166 deletions
File tree
- .changeset
- examples/solid
- astro
- basic-graphql-request
- basic
- default-query-function
- offline
- simple
- solid-start-streaming
- integrations/solid-vite
- packages
- eslint-plugin-query
- solid-query-devtools
- solid-query-persist-client
- solid-query
- src
- __tests__
- fixtures/hydration
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
18 | 18 | | |
19 | 19 | | |
20 | 20 | | |
21 | | - | |
22 | | - | |
| 21 | + | |
| 22 | + | |
23 | 23 | | |
24 | 24 | | |
25 | 25 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
12 | 12 | | |
13 | 13 | | |
14 | 14 | | |
15 | | - | |
16 | | - | |
| 15 | + | |
| 16 | + | |
17 | 17 | | |
18 | 18 | | |
19 | 19 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
10 | 10 | | |
11 | 11 | | |
12 | 12 | | |
13 | | - | |
14 | | - | |
| 13 | + | |
| 14 | + | |
15 | 15 | | |
16 | 16 | | |
17 | 17 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
10 | 10 | | |
11 | 11 | | |
12 | 12 | | |
13 | | - | |
14 | | - | |
| 13 | + | |
| 14 | + | |
15 | 15 | | |
16 | 16 | | |
17 | 17 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
13 | 13 | | |
14 | 14 | | |
15 | 15 | | |
16 | | - | |
17 | | - | |
| 16 | + | |
| 17 | + | |
18 | 18 | | |
19 | 19 | | |
20 | 20 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
10 | 10 | | |
11 | 11 | | |
12 | 12 | | |
13 | | - | |
14 | | - | |
| 13 | + | |
| 14 | + | |
15 | 15 | | |
16 | 16 | | |
17 | 17 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
14 | 14 | | |
15 | 15 | | |
16 | 16 | | |
17 | | - | |
18 | | - | |
| 17 | + | |
| 18 | + | |
19 | 19 | | |
20 | 20 | | |
21 | 21 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
11 | | - | |
| 11 | + | |
12 | 12 | | |
13 | 13 | | |
14 | | - | |
| 14 | + | |
15 | 15 | | |
16 | 16 | | |
0 commit comments