feat(solid): named single-flight source + loadFlightTarget, the cache-agnostic trigger - #8192
feat(solid): named single-flight source + loadFlightTarget, the cache-agnostic trigger#8192ryansolid wants to merge 2 commits into
Conversation
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. Today Start claims the single unnamed slot on both halves, which means any other cache wanting mutation-response data (e.g. solid-query, whose provider subscribes under "sq" in TanStack/query#11326) displaces the router's — whichever registers last wins, silently. The router's flight data now rides its own source id ("tsr"): the server collector registers additively with registerFlightDataSource and the client subscribes its consumer under the same id, so router loader/match state and other caches' slices coexist on one round trip. A user-supplied collectFlightData hook keeps the unnamed slot to itself, adding data alongside the router's instead of displacing it. Both halves feature-detect the protocol on the installed @solidjs/web (it ships in the release after 2.0.0-rc.4) and fall back to the exact previous unnamed-slot behavior on older versions; since client and server resolve the same install, the halves cannot disagree. Co-authored-by: Cursor <cursoragent@cursor.com>
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: trueThanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…t trigger The router's half of flight collection as a public primitive: derive the flight request for the mutation's target, run the matched routes' data functions, hand the loaded router to the caller's collect() — any cache (the router's own state, a query client) composes its extraction on top. Start's collector now consumes it; errors are contained per Solid Router's collector convention (flight data is an optimization, never a mutation error). Co-authored-by: Cursor <cursoragent@cursor.com>
|
View your CI Pipeline Execution ↗ for commit de72ade
☁️ Nx Cloud last updated this comment at |
There was a problem hiding this comment.
Nx Cloud is proposing a fix for your failed CI:
We updated server-functions-handler.ts (source and pre-built dist) to access registerFlightDataSource via Object.assign({}, solidServerFunctions) rather than directly on the namespace binding. This prevents rspack's static ESM linking validation from rejecting the access against the installed @solidjs/web rc.4 exports list, where registerFlightDataSource has not yet shipped. Runtime behaviour is unchanged — the result is the function on rc.5+ or undefined on rc.4, and the existing hasNamedFlightSources guard handles both paths.
Tip
✅ We verified this fix by re-running tanstack-solid-start-e2e-basic:build:rsbuild:ssr.
diff --git a/packages/solid-start/src/server-functions-handler.ts b/packages/solid-start/src/server-functions-handler.ts
index 502d173b3..477440c55 100644
--- a/packages/solid-start/src/server-functions-handler.ts
+++ b/packages/solid-start/src/server-functions-handler.ts
@@ -36,13 +36,19 @@ configureServerFunctionsServer({
// older versions the collector falls back to claiming the unnamed slot
// per-handler, exactly as before; the client half detects the same
// installed package, so the two halves cannot disagree.
-const registerFlightDataSource = (
- solidServerFunctions as {
+// Object.assign produces a plain object, so rspack's static ESM linking
+// validation doesn't flag the access when registerFlightDataSource hasn't
+// shipped in the installed @solidjs/web (rc.4). At runtime the own-property
+// copy still returns the function when it is present (rc.5+) or undefined
+// when it isn't — identical observable behaviour to a direct namespace read.
+const registerFlightDataSource = Object.assign(
+ {} as {
registerFlightDataSource?: (
source: string,
hook: CollectFlightDataHook,
) => () => void
- }
+ },
+ solidServerFunctions,
).registerFlightDataSource
const hasNamedFlightSources = registerFlightDataSource !== undefined
if (registerFlightDataSource) {
Because this branch comes from a fork, it is not possible for us to apply fixes directly, but you can apply the changes locally using the available options below.
Apply changes locally with:
npx nx-cloud apply-locally SkBW-jhDX
Apply fix locally with your editor ↗ View interactive diff ↗
🎓 Learn more about Self-Healing CI on nx.dev
Hooray! CodSpeed harness just leveled up!The base and head of this comparison were measured with different runner settings, so their benchmark values are not directly comparable. What changed between base and head:
Re-run the base with the same settings to get a valid performance comparison. Comparing Footnotes |
Draft — pairs with the
@solidjs/webmulti-source single-flight protocol (solidjs/solid@653dd41e, shipping in the release after 2.0.0-rc.4). Safe to land before it ships: both halves feature-detect and fall back to current behavior.Summary
Solid's single-flight channel is becoming multi-source: a mutation response carries a keyed envelope of per-cache slices, each routed to the consumer subscribed under its source id. Today Start claims the single unnamed slot on both halves — so any other cache wanting mutation-response data (e.g. solid-query, whose provider subscribes under
"sq"in TanStack/query#11326) displaces the router's flight data: whichever library registers last silently wins.This PR moves the router's flight data onto its own source id (
"tsr"):collectSolidStartFlightDataregisters additively viaregisterFlightDataSource(SOLID_START_FLIGHT_SOURCE, ...)instead of claiming the per-handler unnamed slot. A user-suppliedcollectFlightDataoption now keeps the unnamed slot to itself — its data folds alongside the router's rather than replacing it.Net effect: a Start app using solid-query gets router loader/match refresh and query-cache hydration from one mutation round trip, with no coordination between the libraries.
loadFlightTarget: the trigger as a public primitiveThe factoring behind multi-source flight is: the router owns the trigger (running the matched routes' data functions for the URL the client will show after the mutation), each cache owns its slice (what to extract from the loaded state). This PR makes the trigger public —
loadFlightTargetin@tanstack/solid-router/ssr/server:It derives the flight request (the outcome's pre-digested
targetUrl, the mutation's cookie effects already folded in), points the router at the target through a fresh memory history, runsrouter.load()inside the flight request-event scope, and hands the loaded router tocollect. Errors are contained per Solid Router's own collector convention — flight data is an optimization, so a failure omits the slice rather than surfacing as a mutation error (core now also contains per-source: solidjs/solid@ec523607).Start's
tsrcollector consumes the same primitive, keeping only its bespoke halves: start-context acquisition, its serialized-redirect target resolution, and match-state extraction.Known divergence from Solid Router's collector, deliberate for now:
outcome.revalidateKeysscoping isn't honored — it comes from Solid'sX-Revalidateconvention, which TanStack mutations don't produce.Before landing (once
@solidjs/web2.0.0-rc.5 ships)solid-rpc-flight-client.ts(getFlightDataSourceIdsprobe) — subscribe directly underSOLID_START_FLIGHT_SOURCE.server-functions-handler.ts(the unnamedcollectFlightData-slot assignment) — register viaregisterFlightDataSourceunconditionally.@solidjs/webpeer floor to rc.5.The rc.4 fallback paths defend a window nobody occupies — the multi-cache pairing this enables requires rc.5 on the consumer side anyway — so this PR waits and lands clean rather than shipping dead branches to strip later.
Compatibility
@solidjs/web(via exports the protocol introduced). On 2.0.0-rc.4 and older, behavior is exactly today's: unnamed slot, usercollectFlightDataoverrides the router's. Client and server resolve the same install, so the halves cannot disagree.trueheader/payload shape preserved), so no version pairing breaks.isSolidStartFlightDatashape guard stays — required in the legacy path, redundant-but-harmless under named sources.Verification
nx build @tanstack/solid-routerandnx build @tanstack/solid-startgreen (16-task dependency graph),publint --strict+attwpass.fullstack-tanstack(solid-v2/fullstack-tanstack: multi-source single-flight solidjs/templates#287), which uses exactly theloadFlightTarget+dehydrateSettledcomposition above.e2e/solid-start/server-functions) exercises the legacy path unchanged; the named path activates only alongside the new@solidjs/web, whose protocol suite (request-leg negotiation, keyed envelope routing, per-source error containment, cross-version degradation) lives in that repo.Made with Cursor