72f81855 feat(solid-query): built-in single-flight consumer via FLIGHT_DATA_SOURCE (#11326)
* feat(solid-query): built-in single-flight consumer via FLIGHT_DATA_SOURCE
Solid's single-flight channel is becoming multi-source (solidjs/solid
653dd41e): mutation responses carry a keyed envelope of per-cache
slices, each routed to the consumer subscribed under its source id, so
independent caches (Solid Router's route data, the query cache) refresh
from one round trip without competing for the single legacy slot.
QueryClientProvider now subscribes the query cache's consumer under the
exported FLIGHT_DATA_SOURCE id ("sq", matching the sq: hydration-registry
namespace): a mutation response carrying that slice — a DehydratedState
produced by a server collector registered with
registerFlightDataSource(FLIGHT_DATA_SOURCE, hook) — hydrates the
provider's client before the mutation's promise resolves. Mounted
queries on those keys update with no follow-up refetches, and apps
delete the hand-rolled subscribeFlightData/hydrate wiring entirely.
Subscribing is inert when no server collector exists (the server folds
nothing), client-only (the server registry is cross-request module
state), and torn down with the provider.
Requires the @solidjs/web release following 2.0.0-rc.4 for the
named-source protocol; a typed shim bridges the installed declarations
until the peer range bumps.
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(solid-query): dehydrateSettled, SSR teardown, and dehydrate filtering
Fills the gaps the router-ssr-query transport used to cover, natively:
- dehydrateSettled(client): the extraction half of a single-flight
collector — waits for every in-flight fetch (chased to quiescence) so
loaders' fire-and-forget prefetches land before dehydrating.
- SSR teardown: the provider cancels and clears the per-request cache on
render disposal, so user-configured finite gcTime timers cannot pin the
client after the response.
- The registry serializer now respects defaultOptions.dehydrate
.shouldDehydrateQuery, the same knob apps use on any other transport.
Co-authored-by: Cursor <cursoragent@cursor.com>
* chore(solid-query): require solid 2.0.0-rc.6, drop the subscribe type bridge
rc.6 publishes the named flight-data source API with real types, so the
subscribeFlightData cast goes away; the peer floor moves to rc.6 because
rc.5's settle-walk regression breaks query hydration. Also adds the
missing changeset for dehydrateSettled and the SSR teardown work.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com> c809e09e feat(solid-query): rewrite the adapter onto Solid 2.0's native async model (#11308)
* feat(solid-query): rewrite the adapter onto Solid 2's native async model
Reads become a single async memo over the query cache (suspends into
<Loading>, holds previous data through refetches, routes rejections to
<Errored>, serializes settled values for streaming hydration); mutations
ride Solid core's `action` primitive with transactional settle and
optimistic overlays; cross-cache hooks share a dual-write aggregate
with a hydration latch. The observer notification/store layer
(createStore/reconcile, tracking proxies, notifyManager batching) is
deleted — QueryObserver/MutationObserver remain as lifecycle/policy
engines with noop listeners.
Breaking: result `data` is non-optional (reads suspend instead of
returning undefined), mutateAsync is removed (mutate returns a
promise), and the reconcile/suspense/deferStream options are removed.
SSR contracts pinned by the fixture suite: settled-only serialization
for every exposed field, hydration id parity, no fetches inside the
hydration/restore windows, and hydration-latched cross-cache
aggregates. Validated against solid-js 2.0.0-rc.0 and rc.3, including
an end-to-end template run with single-flight mutations.
Co-authored-by: Cursor <cursoragent@cursor.com>
* ci: apply automated fixes
* feat(solid-query): wire deferStream through to Solid's per-computation SSR option
deferStream was declared in the option types on this line but wired to
nothing. Solid 2 exposes it as a creation-time memo option, so pass it
straight through to the data node: the server holds the stream flush
until the query resolves instead of flushing the surrounding boundary's
fallback. Server-only; ignored on the client.
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(solid-query): serve data through an auto-reconciling store projection
Solid 2 projections auto-reconcile their returns by key, so the data face
becomes a store: deep reads are fine-grained and item identity survives
across refetches (keyed by the new `reconcile` option, default 'id').
The async memo remains the SSR/hydration spine underneath; the projection
derive is inert on the server — server projections run eagerly at
creation, so a live derive would fetch at hook creation and serialize a
duplicate payload, and the serialized entry would latch the hydrating
client's projection for the whole stream. With nothing serialized the
client projection computes live through the hydration window.
Rewrites the structural-sharing test to the stronger store contract and
adds leaf-granularity, custom-key, and primitive-data semantics tests.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(solid-query): green up CI for the native rewrite
- align @solidjs/web, babel-preset-solid and solid-js dev ranges across the
solid packages (sherif flagged the split, and devtools/persist-client were
resolving a second solid-js copy)
- drop the unused `export` on the internal BaseQueryLayer seam (knip)
- port the PersistQueryClientProvider suite to the 2.0 read layer: a query
with nothing cached now suspends for the whole restore window, so the
tests assert the visible fallback -> restored -> refreshed transitions
and the fetch/callback bookkeeping instead of observer result snapshots
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* refactor(solid-query): unify the data node into a single hybrid projection
Collapse the three-node data face (serialized async memo + transparent
hydration memo + client-only projection) into one createProjection with
an identical derive on both sides — the Solid Router feed-query-into-
projection shape. The engine owns SSR serialization (deferStream rides
the projection now), suspense, hydration adoption, and keyed
reconciliation. ssrSource 'hybrid' keeps hydrated components live: the
serialized value claims the DOM and the derive re-takes over from the
live cache per hydrated region. A per-hook `primed` signal gates the
pull-model fetch so the hydration takeover recompute cannot race data
the channel is about to deliver (SSR-errored queries fall through and
fetch normally once the channel declines them).
Co-authored-by: Cursor <cursoragent@cursor.com>
* refactor(solid-query): default ('server') hydration semantics for the data node
Drop ssrSource 'hybrid' from the data projection: query data IS
server-serialized truth, so the serialized value owns the DOM for the
whole hydration window. Mid-stream cache writes commit when the stream
closes through Solid's hydration-end divergence takeover (solid-js >
2.0.0-rc.3 — earlier engines lose a latched node's mid-stream divergence
instead of deferring it). Network activity is unchanged: observers
attach per-query as the channel primes, so mid-stream invalidations
still refetch immediately; only the DOM commit defers to hydration end.
Co-authored-by: Cursor <cursoragent@cursor.com>
* refactor(solid-query): single-channel hydration — the node payload is the transport
Delete the provider-owned dehydration channel and its coordinator. The
data projection's serialized root now carries the cache facts hydration
needs ({ value, t: dataUpdatedAt, raw?: pre-select data }); on a hydrated
mount each hook peeks its projection's node id, loads that entry itself,
primes the query cache through query-core hydrate() (staleness intact,
newer-wins, silent under external pre-priming), and attaches its observer
per query as chunks land. One serialization story, engine-owned; ~240
lines deleted. Requires solid-js > 2.0.0-rc.3 (peekNextChildId export).
Co-authored-by: Cursor <cursoragent@cursor.com>
* chore(solid-query): drop internal porting notes from the tree
Co-authored-by: Cursor <cursoragent@cursor.com>
* refactor(solid-query): content-addressed hydration transfer (router query() pattern)
Serialize cache entries under sq:<queryHash> at fetch-dispatch time
(provider-side, promise-valued so streaming settles ride the payload)
instead of tying transfer to a rendered node's positional id. Client
hooks look their own hash up and prime through query-core hydrate();
the registry outlives the hydration window, so prefetched-never-rendered
queries transfer and late mounts (lazy routes, post-hydration navigation)
adopt the server payload instead of refetching — restoring full
cache-level dehydration coverage with less machinery. Priming moved ahead
of the attach render effect (its effect half runs synchronously inside an
active flush, and mount policy against a cold cache would refetch).
Drops the peekNextChildId dependency; the data root reverts to { value }.
Co-authored-by: Cursor <cursoragent@cursor.com>
* chore(solid-query): drop internal porting notes from the tree
Co-authored-by: Cursor <cursoragent@cursor.com>
* refactor(solid-query)!: remove create* aliases, completing the #8950 deprecation
The create*/Create* names were kept as a migration bridge when v5 made
use* canonical (#8950 deprecated them). This major already rewrites every
callsite's semantics, so the bridge retires: use* is the only naming,
matching the docs.
Co-authored-by: Cursor <cursoragent@cursor.com>
* ci: apply automated fixes
* refactor(solid-query)!: drop isInitialLoading (deprecated alias of isLoading)
Core deprecated it in favor of isLoading; the adapter's getter was
byte-identical. Result types now omit it (distributively over the
Defined result unions so status discriminants survive), and useBaseQuery
declares the adapter's own result face instead of the core union.
Co-authored-by: Cursor <cursoragent@cursor.com>
* chore(solid-query): require solid-js >= 2.0.0-rc.4
rc.4 is published and carries the hydration-end divergence takeover the
adapter's streaming hydration relies on. Bump the peer range and dev dep
off the locally-built solid and onto the released bits; full suite (344
adapter + 7 persist-client tests, typecheck) green against published rc.4.
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>
Co-authored-by: Brenley Dueck <brenleydueck@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com> 7b04d619 chore: upgrade to solid v2 rc, rename vite-plugin-solid, move branch to rc (#11173)
- Bump solid-js, @solidjs/web, @solidjs/signals and babel-preset-solid
2.0.0-beta.33 -> ^2.0.0-rc.0 across the solid packages, the solid-vite
integration and the solid examples, switching these from exact pins to
carets so the rc line is picked up as it moves
- Rename vite-plugin-solid -> @solidjs/vite-plugin ^3.0.0-next.27 (the
version published under both names) in every manifest, vite config and
the hydration test fixture; packages/query-devtools stays on
vite-plugin-solid ^2.11.6 since it is still on solid 1.x
- Drop the now-redundant `-i vite-plugin-solid` sherif ignore, since the
dependency no longer resolves to more than one version
- Raise the solid-query peer floor to >=2.0.0-rc.0 <3.0.0; the broader
>=2.0.0-beta.0 floors on solid-query-devtools and
solid-query-persist-client are left as they are
- Switch the changesets prerelease tag from beta to rc, and set the solid
package versions to the 6.0.0-rc sentinel so the next `changeset
version` lands on 6.0.0-rc.0 rather than continuing the beta counter
into 6.0.0-rc.9
- No source changes were needed; solid-query, solid-query-devtools and
solid-query-persist-client pass unchanged (336 tests, no type errors),
as do the solid-vite integration and solid example vite builds
Co-authored-by: Claude Opus 5 <noreply@anthropic.com> 67c81794 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>