Describe the problem
Following Rich's comment in #16507, I mapped everything that carries server-side state, since a unification needs the full picture. Line refs are from version-3 at b596610.
There are three carriers now, not two. Besides SSRState and RequestState, the dev rework keeps a context in import.meta.hot.data that feeds state.emulator and state.before_handle on every request. Eight functions take both state and event_state, plus two closures in respond.js that capture both, which is half the state-carrying functions in the server runtime. SSRState.fallback has no readers or writers left and can be deleted whatever else happens.
The overlap isn't just duplication, the two objects have opposite semantics that nothing documents. SSRState is mutated in place and is the only carrier that survives event.fetch; RequestState is readonly, copy-varied, and rebuilt per sub-request, with three fields hand-copied from the other side at respond.js:179. That snapshot is why faking state.prerendering in dev couldn't work in #16507, prerender_default only works because of which side of the fetch boundary it happens to live on, and the hmr: false breakage in #16464 is the same shape again, load-bearing state in a carrier that isn't guaranteed to exist.
Describe the proposed solution
A single object has to keep the behaviours the split currently implies:
- sub-requests inherit
prerendering/error/depth but reset the remote caches (fetch.js:219 vs respond.js:177), so it needs an explicit fork rather than spread-one-rebuild-the-other
- copy-on-write for the request flags, since the webcontainer fallback in event.js:74 serializes requests and is only safe because nothing mutates the stored object
remote_responses is per prerender pass while dependencies is per visit, so lifetimes become explicit instead of implied
Server.respond's public shape stays put
- dev values must be re-derivable after module re-evaluation, so
hot.data can back the injection but can't be the only copy
read exists per request (SSRState.read) and per server (read_implementation), pick one home
What I'd do: a request state created per request with a fork() for event.fetch, and the server-lifetime values (emulator, before_handle, read, the hooks-derived fields) on an object created once in Server.init and referenced, not copied. Dev builds that once from its context module instead of writing SSRState fields per request. The eight dual signatures collapse to one parameter, no public API changes.
Hoping this could be the thread to work this out
Importance
nice to have
Additional Information
If this sounds right I'll implement it in stages, starting with the dead field and the fork semantics.
Describe the problem
Following Rich's comment in #16507, I mapped everything that carries server-side state, since a unification needs the full picture. Line refs are from version-3 at b596610.
There are three carriers now, not two. Besides
SSRStateandRequestState, the dev rework keeps a context inimport.meta.hot.datathat feedsstate.emulatorandstate.before_handleon every request. Eight functions take bothstateandevent_state, plus two closures in respond.js that capture both, which is half the state-carrying functions in the server runtime.SSRState.fallbackhas no readers or writers left and can be deleted whatever else happens.The overlap isn't just duplication, the two objects have opposite semantics that nothing documents.
SSRStateis mutated in place and is the only carrier that survivesevent.fetch;RequestStateis readonly, copy-varied, and rebuilt per sub-request, with three fields hand-copied from the other side at respond.js:179. That snapshot is why fakingstate.prerenderingin dev couldn't work in #16507,prerender_defaultonly works because of which side of the fetch boundary it happens to live on, and thehmr: falsebreakage in #16464 is the same shape again, load-bearing state in a carrier that isn't guaranteed to exist.Describe the proposed solution
A single object has to keep the behaviours the split currently implies:
prerendering/error/depthbut reset the remote caches (fetch.js:219 vs respond.js:177), so it needs an explicit fork rather than spread-one-rebuild-the-otherremote_responsesis per prerender pass whiledependenciesis per visit, so lifetimes become explicit instead of impliedServer.respond's public shape stays puthot.datacan back the injection but can't be the only copyreadexists per request (SSRState.read) and per server (read_implementation), pick one homeWhat I'd do: a request state created per request with a
fork()forevent.fetch, and the server-lifetime values (emulator,before_handle,read, the hooks-derived fields) on an object created once inServer.initand referenced, not copied. Dev builds that once from its context module instead of writingSSRStatefields per request. The eight dual signatures collapse to one parameter, no public API changes.Hoping this could be the thread to work this out
Importance
nice to have
Additional Information
If this sounds right I'll implement it in stages, starting with the dead field and the fork semantics.