Release RC packages (rc) - #3098
Open
github-actions[bot] wants to merge 1 commit into
Open
Conversation
github-actions
Bot
force-pushed
the
changeset-release/next
branch
30 times, most recently
from
August 31, 2026 08:43
c71fd00 to
39cc6b8
Compare
github-actions
Bot
force-pushed
the
changeset-release/next
branch
22 times, most recently
from
September 1, 2026 07:53
0012857 to
d0da2d7
Compare
github-actions
Bot
force-pushed
the
changeset-release/next
branch
from
September 1, 2026 08:58
d0da2d7 to
88ac477
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR was opened by the Changesets release GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to next, this PR will be updated.
nextis currently in pre mode so this branch has prereleases rather than normal releases. If you want to exit prereleases, runchangeset pre exitonnext.Releases
@solidjs/babel-plugin@2.0.0-rc.5
Patch Changes
<becomes<) and leaves JSX entities undecoded #3127). The DOM and SSR generators splice statictext into an HTML template that a parser later unescapes, so they escape
static values and keep JSX entities as written. The universal generator
hands strings straight to the host —
createTextNode,setProp— with noparser downstream, so the escaping rendered literally (
{"<b>"}showed as<b>) and entities never decoded (<showed as<), leaving nospelling that produced a literal
<in static text under a customrenderer. Universal-rendered element children now pass static values
through unescaped and decode JSX entities in text and string attributes,
matching what component children and fragment text always did. The flag
rides on the element, not the config, so
generate: "dynamic"decides perrenderer. Applied to both compilers; the attribute half closed ten pinned
cross-mode parity divergences between them. Reported with the fix mapped
out by @antoinevanwel.
spread()on the client — nomergeProps, no memo, no hydration id — matching the server's existing pass-through fast path. The runtime resolves a function props source inside its own tracking scopes.validatenow fails the compile instead of warning when a template's markup would be restructured by the browser's HTML parser ([compiler]validatewarns but still emits the broken template walk #3099). Once the validator fires the emitted positional walk is guaranteed not to match the browser-built DOM (crashed or silently misplaced bindings; desynced hydration under SSR), so warn-and-emit shipped certain breakage with the diagnostic buried in server logs. Errors now point at the offending JSX (code frame in Babel, line:col in the native compiler).validate: falseremains the opt-out.@solidjs/compiler@2.0.0-rc.5
Patch Changes
<becomes<) and leaves JSX entities undecoded #3127). The DOM and SSR generators splice statictext into an HTML template that a parser later unescapes, so they escape
static values and keep JSX entities as written. The universal generator
hands strings straight to the host —
createTextNode,setProp— with noparser downstream, so the escaping rendered literally (
{"<b>"}showed as<b>) and entities never decoded (<showed as<), leaving nospelling that produced a literal
<in static text under a customrenderer. Universal-rendered element children now pass static values
through unescaped and decode JSX entities in text and string attributes,
matching what component children and fragment text always did. The flag
rides on the element, not the config, so
generate: "dynamic"decides perrenderer. Applied to both compilers; the attribute half closed ten pinned
cross-mode parity divergences between them. Reported with the fix mapped
out by @antoinevanwel.
<xxhash32(path)>-<ordinal>, so appending a server function to a file renumbered the others and every client holding the old numbering silently dispatched to different code with a 200. Ids are now<name>-<xxhash32(root-relative path)>, with a trailing ordinal only when the same descriptive name recurs within one file — appends, deletes, reorders, and body edits no longer move any address, and a removed or renamed function becomes a clean 404 instead of a wrong call. Development and production now share the exact same id format.spread()on the client — nomergeProps, no memo, no hydration id — matching the server's existing pass-through fast path. The runtime resolves a function props source inside its own tracking scopes.validatenow fails the compile instead of warning when a template's markup would be restructured by the browser's HTML parser ([compiler]validatewarns but still emits the broken template walk #3099). Once the validator fires the emitted positional walk is guaranteed not to match the browser-built DOM (crashed or silently misplaced bindings; desynced hydration under SSR), so warn-and-emit shipped certain breakage with the diagnostic buried in server logs. Errors now point at the offending JSX (code frame in Babel, line:col in the native compiler).validate: falseremains the opt-out.@solidjs/diagnostics@2.0.0-rc.5
Patch Changes
@solidjs/element@2.0.0-rc.5
Patch Changes
@solidjs/h@2.0.0-rc.5
Patch Changes
07471da: Add typed preload links to the server asset pipeline.
Static manifests can attach
preloads: PreloadLink[], resolver results can carry the same shape for framework integrations, and any integration can register a link withregisterAsset("preload", link). The runtime preservesas, MIME type, CORS mode, integrity, referrer policy, fetch priority, and media attributes across string, streaming, embedded-head, custom-sink, and frame renders.lazy()andclientOnly()forward resolver-provided preload links alongside their JS and CSS.JSX.HTMLPreloadAsandJSX.HTMLFetchPriorityare now exported for reuse.Preload links are explicit: manifest
assetsare not preloaded automatically. Existing stylesheet and modulepreload APIs are unchanged.Development builds warn when font or fetch preloads omit
crossorigin, because a different eventual request mode cannot reuse that preload.Frame clients also retain and consume every late root asset record instead of dropping earlier records that reuse the same transport key.
Updated dependencies [bacfb34]
Updated dependencies [51392f3]
Updated dependencies [02e0ebf]
Updated dependencies [ec52360]
Updated dependencies [da50a36]
Updated dependencies [2f18c56]
Updated dependencies [0932c89]
Updated dependencies [817b4d1]
Updated dependencies [929642b]
Updated dependencies [ecfee20]
Updated dependencies [30f9387]
Updated dependencies [af4cfc8]
Updated dependencies [be7bcd2]
Updated dependencies [08b4d1c]
Updated dependencies [93adc02]
Updated dependencies [19fa8b0]
Updated dependencies [1a95943]
Updated dependencies [5be07a8]
Updated dependencies [8963843]
Updated dependencies [8d34af1]
Updated dependencies [fc5d079]
Updated dependencies [1d2d1e5]
Updated dependencies [2f6d8cc]
Updated dependencies [fe4bfa0]
Updated dependencies [02f87fe]
Updated dependencies [5230666]
Updated dependencies [653dd41]
Updated dependencies [8d17083]
Updated dependencies [006a115]
Updated dependencies [0b9d69a]
Updated dependencies [f739ec3]
Updated dependencies [9522945]
Updated dependencies [fe4bfa0]
Updated dependencies [21a5122]
Updated dependencies [f06f7b1]
Updated dependencies [07471da]
@solidjs/html@2.0.0-rc.5
Patch Changes
@solidjs/signals@2.0.0-rc.5
Patch Changes
51ffcb9:
refresh(target)now returns a promise for the target's next QUIESCENT state — the re-ask (and anything that supersedes it) has settled. Accessor targets resolve with the settled value; store targets resolve with the store node passed (nested targets re-ask the whole family but resolve with the node the caller was looking at). A failed re-ask rejects, soyield refresh(x)in an action throws back at the yield point and reverts like any failed step; an ignored promise never surfaces an unhandled rejection, keeping fire-and-forget refresh unchanged. Semantics are quiescence, not flight identity: a superseding refresh folds every waiter onto whatever finally lands. Inside actions, truth landing into the held transaction is staged; the promise settles then (matchingresolve()/until()delivery) and delivers the staged value — never the caller's optimistic override. The re-ask stays verdict-quiet (isPendingunchanged).Implementation is pay-for-use and shares
resolve()/until()'s effect machinery: the waiter is a microtask-delivered, direct-commit, authoritative-read effect over the marked node, deferred one microtask so same-tick refreshes still coalesce into a single re-ask. One new reader bit (CONFIG_FRESH_READ) makes the waiter's read pull a still-dirty source through recompute inline — it then parks on the re-ask's pending window (woken by the settle walk, which runs on every landing including equal-value ones) or serves the sync answer, instead of misreading the pre-re-ask value as settled.updateIfNecessarynow also refuses disposed nodes outright (Dependency-free computations can be resurrected after disposal #2983's bug class), and a disposed or non-derived target resolves immediately with its last value.28a1eaf: Clear a node's transition stamp when its pending value commits.
_transitionwas only ever cleared for optimistic nodes and in one async settle;
everything else relied on
reassignPendingTransition, which the completingbranch runs over
batch._pendingNodes.commitPendingNodesdrains that listwithout clearing anything, so a node committed by an earlier drain kept
pointing at a transition that later finished.
setSignalre-entersel._transitionbefore it discovers a write changes nothing, and a loadingboundary rewrites the same flag on every drain pass, so a finished transition
was re-armed forever and
flush()never ended: dev threw "Potential InfiniteLoop Detected", production has no counter on that loop and hung. The
re-entered transition also aliases the ambient batch's containers, so
initTransition's adoption pass could push into the array it was iteratinguntil
RangeError: Invalid array length.ca16891: Harden the transaction-stamp lifecycle around 2.0.0-rc.4: flush() guard trips from the asyncWrite write-back on a screen with several async memos (no minimal reproduction) #3140 (companion to fix(signals): clear a node's transition stamp when its pending value commits #3143, which clears
_transitionstamps when pending values commit).initTransitionnow refuses a transaction whose_donechain ends intrue— the belt for dead references that survive outside the commit path (merged forwarding chains, async settles racing completion), sincesetSignalre-opens a node's stamped transaction before the value-equal bail and re-activating a corpse spins the flush drain loop (dev threw the loop guard; production hung). The dev loop guard also now reports what kept the loop alive — transition done-state, queue counts, and the last staged node — instead of only that it happened.ed2fb43: Fix
flattenArrayoverwriting itsneedsUnwrapflag with a nested call's result instead of OR-ing it (flattenArray drops needsUnwrap when a fragment follows an accessor, universal renderers receive a raw memo in insertNode #3133). UnderdoNotUnwrap, an accessor child (a<For>/<Repeat>/memo) followed at the same level by a fragment containing no functions reset the flag, soflattenreturned a plain array with the raw accessor still inside instead of the resolving wrapper. Every renderer crashed on the raw function: universal hosts received it ininsertNode(as reported), and the DOM renderer threwinsertBefore … parameter 1 is not of type 'Node'— the protective function branch remembered from 1.x dom-expressions does not exist in 2.0. Reported with the fix by @antoinevanwel; also submitted by @nickshiro.893b8f9: Fix a tracked
latest(() => isPending(...))probe created during an active new-question flight reportingfalsefor that whole flight (A tracked latest(isPending()) probe created mid-flight misses the current async flight #3166). A latest() shadow created lazily mid-flight is born uninitialized, so the probe's uninitialized suspend-throw dropped it from the verdict collection — swallowed by latest()'s committed-value fallback, the probe cached the wrong verdict until the next flight. The suspend now defers to the parent source's initialization state: an initialized parent means latest() has a value to serve, so the shadow is collected and the probe reports the in-flight truth regardless of creation time.2023daa: Fix unrelated async work being captured by a lingering ambient transaction (2.0.0-rc.4 | optimistic store, read in an effect, changes the visible rendering. and has inconsistent value when logged and when rendered. #3141). Parking is flush-driven, but a transaction opened without any writes — an action whose first statements only await — scheduled nothing, so
activeTransitionand the adopted batch stayed armed across the async gap. The next unrelated work to arrive was adopted into a transaction it had nothing to do with: an optimistic store's authoritative landing would not render until the stranger action settled, an unowned optimistic write rode that transaction instead of reverting at the flush, anddeep()/per-key readers disagreed about the committed value in the meantime.initTransitionnow guarantees a flush, so the ambient window closes in one flush regardless of whether the transaction wrote anything — enforcing the A26 containment ruling.3e3676b: The owned-scope write guard's thrown message now names the owning scope (previously only the diagnostics channel carried it); CHEATSHEET no longer claims
untrackexempts owned-scope writes — the guard is owner-based and untrack only stops tracking (CHEATSHEET says untrack exempts owned-scope writes; the guard is owner-based and untrack cannot bypass it #3157)09bbe24: Fix a type error in refresh()'s quiescence waiter that broke declaration emit (
pnpm types): the waiter captured inside the effect's own compute is the effect node, so it is typedComputedrather thanOwner, matching whatdispose()takes. Type-only; no runtime change.88fa9d6: Fix derived optimistic stores permanently corrupting committed state when the source's draft writes ran while a caller's optimistic override was active (2.0.0-rc.5 until breaks optimistic store #3108). The source is the truth author: its draft reads — sync body and post-
await/yieldcontinuations alike — now serve the authoritative view instead of composing the caller's tentative overlay. Before, a generator continuation'sstore.pushreadlengththrough an action's optimistic row and landed truth at the wrong index, committing[null, row]. Values, array length, membership, keys, and descriptors all leave the authoritative view together, gated on the same authoritative-write postureensurePBalready used to seed authoritative drafts from committed truth. User setter drafts are unchanged and keep composing on the optimistic view (2.0.0-beta.26 | optimistic store unexpected pending state for store or dependency, while having an optimistic override #2951). Not anuntil()bug, despite the report's shape — the corruption reproduced with a plain yielded promise.f051db6: Relocate the 2.0.0-rc until doesnt entangle the update resolving it with the action update #3164 held-truth ledger and transition-optimism probe from the scheduler into the optimistic module, and fold the reader-posture exemptions (latest() window, authoritative-read observer) into the
_heldTruthMaskedhook. No behavior change; core-floor and non-optimistic bundles shed the bytes.fa13761: Close superseded async-iterable flights at supersede time (isPending defers superseded async-iterable flight teardown (stale iterator runs to completion) #3122). The iterator close was registered only as an owner cleanup, and a recompute whose disposal rides the zombie-deferred channel drains it at commitPendingNode — which a verdict-held write defers until the SUPERSEDING flight settles, leaving the stale iterator running to completion. Iterator close is the cancellation hook for resource-shaped streams (fibers, sockets, subscriptions), so the flight teardown now also fires at recompute's
_inFlightrelease, keyed to flight identity; the owner cleanup stays as the death backstop.90603c5: Fix latest()-mode isPending probes answering differently depending on read order (2.0.0-rc.4 | internal
latest()in amemovs external latest and inconsistencies #3104). Two probe-mode leaks in the verdict layer: latestRead's mid-tick shadow pull recomputed a stale shadow with the probe still live (collecting the parent and flipping the verdict to the held-write answer only when nothing had pulled the shadow earlier in the tick), and the probe's companion-verdict reads ran with an outer latest() window still active, building a shadow of the pending signal itself that later halted dev with the owned-scope write guard when a flush recompute wrote it.a536e29: Truth landings under live optimism now FOLD instead of consuming (2.0.0-rc.5 optimistic store resets when new data arrive #3123, 2.0.0-beta.14: Optimistic updates tearing fresh data #2719, 2.0.0-rc until doesnt entangle the update resolving it with the action update #3164). While any transaction retains optimism on a store family (or an armed signal), fresh authoritative data — continuation yields, foreign refetches, replacing landings alike — stages into the retaining transaction and reveals atomically when it settles, exactly like a signal landing under an active override. Ordinary readers keep the committed view until that reveal, so overlapping optimistic actions no longer flash each other's pending rows out, partial override coverage can no longer compose override + staged truth into a frame no timeline contains (GabbeV's union tear), and a pending add never ghosts onto the next dataset. Authoritative readers tunnel through the hold:
until()'s predicate andlatest()see the staged truth, and a landing staged under an active override wakes waiting authoritative observers instead of deadlocking. Key-matched rows keep their proxy identity across the fold, and the settle-time revert resyncs structural subscribers (mapArray) with the landed arrangement. This supersedes the interim 2.0.0-rc.5 optimistic store resets when new data arrive #3123 retained-setter replay machinery, which is removed.0c02d42: Add
until(fn, options?)— the acknowledgment primitive for mutations confirmed on a live data channel (sockets, subscriptions, live queries) rather than by the mutation's own response. Resolves the first time the reactive predicate settles truthy (falsy and pending both mean "not yet");yield until(...)from an action holds the transaction — and its optimistic state — open until the world confirms, with{ timeout }(TimeoutError) and{ signal }rejections throwing back at the yield point so failed holds revert like any failed action.The predicate reads the AUTHORITATIVE view, and the carve-out is exactly one layer deep: the caller's own optimistic overrides (values and structure) are invisible, so a tentative write can never satisfy its own ack — including on the single-primitive shape where the optimistic store is the live-fed store. Everything else reads normally, including uncommitted transition-staged data: truth landing into the open transaction (e.g. a
refresh()the action issued) stages and cannot commit until the hold releases, so refusing staged reads would deadlock the hold on its own data plane. The A17-silent "landing equals the override" paths wake authoritative readers only (CONFIG_AUTHORITATIVE_OBSERVED); the wakeup machinery is hook-installed at firstuntil()call so unused apps tree-shake it.Also fixes
resolve()(anduntil()) delivering stale values when their source settles into a held transaction: promise-delivery effects apply on a microtask (2.0.0-beta.21: resolve() created inside action() never settles, deadlocking the action #2930) but their computed value staged with the transition, so the immediate apply read stale mainline state —resolvecould report pre-refresh data anduntilcould deadlock. Such effects now commit their value directly (CONFIG_DIRECT_COMMIT), keeping value and delivery on the same schedule; safe because effects are private leaves (no subscriber reads an effect's value).solid-js@2.0.0-rc.5
Patch Changes
51ffcb9:
refresh(target)now returns a promise for the target's next QUIESCENT state — the re-ask (and anything that supersedes it) has settled. Accessor targets resolve with the settled value; store targets resolve with the store node passed (nested targets re-ask the whole family but resolve with the node the caller was looking at). A failed re-ask rejects, soyield refresh(x)in an action throws back at the yield point and reverts like any failed step; an ignored promise never surfaces an unhandled rejection, keeping fire-and-forget refresh unchanged. Semantics are quiescence, not flight identity: a superseding refresh folds every waiter onto whatever finally lands. Inside actions, truth landing into the held transaction is staged; the promise settles then (matchingresolve()/until()delivery) and delivers the staged value — never the caller's optimistic override. The re-ask stays verdict-quiet (isPendingunchanged).Implementation is pay-for-use and shares
resolve()/until()'s effect machinery: the waiter is a microtask-delivered, direct-commit, authoritative-read effect over the marked node, deferred one microtask so same-tick refreshes still coalesce into a single re-ask. One new reader bit (CONFIG_FRESH_READ) makes the waiter's read pull a still-dirty source through recompute inline — it then parks on the re-ask's pending window (woken by the settle walk, which runs on every landing including equal-value ones) or serves the sync answer, instead of misreading the pre-re-ask value as settled.updateIfNecessarynow also refuses disposed nodes outright (Dependency-free computations can be resurrected after disposal #2983's bug class), and a disposed or non-derived target resolves immediately with its last value.91e300a: Fix
<For>followed by siblings desyncing hydration (<For>followed by siblings desyncs hydration in 2.0.0-rc.4 #3161, rc.4 regression). The patch-mode list seam madeFor'smapArraycreation lazy, but hydration ids mint at creation time — deferring to first read spent the list's id scope after later siblings had already claimed their template keys, shifting every hydration key after the list and leaving the siblings detached (dead buttons). A hydratingFornow creates its map eagerly at source position, restoring rc.3 id parity; outside hydration the lazy creation stands.00d1d5d: Stop exposing generated declaration files through
solid-js/types/*. Public values and types remain available fromsolid-js; keeping implementation declarations private also prevents TypeScript from suggesting invalid runtime imports such assolid-js/types/server/signals.js.07471da: Add typed preload links to the server asset pipeline.
Static manifests can attach
preloads: PreloadLink[], resolver results can carry the same shape for framework integrations, and any integration can register a link withregisterAsset("preload", link). The runtime preservesas, MIME type, CORS mode, integrity, referrer policy, fetch priority, and media attributes across string, streaming, embedded-head, custom-sink, and frame renders.lazy()andclientOnly()forward resolver-provided preload links alongside their JS and CSS.JSX.HTMLPreloadAsandJSX.HTMLFetchPriorityare now exported for reuse.Preload links are explicit: manifest
assetsare not preloaded automatically. Existing stylesheet and modulepreload APIs are unchanged.Development builds warn when font or fetch preloads omit
crossorigin, because a different eventual request mode cannot reuse that preload.Frame clients also retain and consume every late root asset record instead of dropping earlier records that reuse the same transport key.
0c02d42: Add
until(fn, options?)— the acknowledgment primitive for mutations confirmed on a live data channel (sockets, subscriptions, live queries) rather than by the mutation's own response. Resolves the first time the reactive predicate settles truthy (falsy and pending both mean "not yet");yield until(...)from an action holds the transaction — and its optimistic state — open until the world confirms, with{ timeout }(TimeoutError) and{ signal }rejections throwing back at the yield point so failed holds revert like any failed action.The predicate reads the AUTHORITATIVE view, and the carve-out is exactly one layer deep: the caller's own optimistic overrides (values and structure) are invisible, so a tentative write can never satisfy its own ack — including on the single-primitive shape where the optimistic store is the live-fed store. Everything else reads normally, including uncommitted transition-staged data: truth landing into the open transaction (e.g. a
refresh()the action issued) stages and cannot commit until the hold releases, so refusing staged reads would deadlock the hold on its own data plane. The A17-silent "landing equals the override" paths wake authoritative readers only (CONFIG_AUTHORITATIVE_OBSERVED); the wakeup machinery is hook-installed at firstuntil()call so unused apps tree-shake it.Also fixes
resolve()(anduntil()) delivering stale values when their source settles into a held transaction: promise-delivery effects apply on a microtask (2.0.0-beta.21: resolve() created inside action() never settles, deadlocking the action #2930) but their computed value staged with the transition, so the immediate apply read stale mainline state —resolvecould report pre-refresh data anduntilcould deadlock. Such effects now commit their value directly (CONFIG_DIRECT_COMMIT), keeping value and delivery on the same schedule; safe because effects are private leaves (no subscriber reads an effect's value).Updated dependencies [51ffcb9]
Updated dependencies [28a1eaf]
Updated dependencies [ca16891]
Updated dependencies [ed2fb43]
Updated dependencies [893b8f9]
Updated dependencies [2023daa]
Updated dependencies [3e3676b]
Updated dependencies [09bbe24]
Updated dependencies [88fa9d6]
Updated dependencies [f051db6]
Updated dependencies [fa13761]
Updated dependencies [90603c5]
Updated dependencies [a536e29]
Updated dependencies [0c02d42]
@solidjs/universal@2.0.0-rc.5
Patch Changes
@solidjs/web@2.0.0-rc.5
Patch Changes
bacfb34: Add
serializeErrorStacksto the serialization codec options (andcreateSerializer): error-stack disclosure defaulted toNODE_ENV === "development", which describes the process rather than the artifact — a production build run withNODE_ENV=developmentshipped stacks to the wire, including application-code stacks for errors marked withmarkSafeError. Deployments can now pincodec: { serializeErrorStacks: false }regardless of the ambient variable (Error stacks reach the client when a production build runs with NODE_ENV=development #3152)51392f3: Bound what a server-function call may send (A server function accepts a body of any size and any number of arguments #3115). The argument payload is buffered and decoded before dispatch, so its cost was paid before application code could decline it: a 32 MB body was accepted and decoded, and a modest argument list forced a range error out of any function when spread into the call.
bodySizeLimit(default 1 MiB, matching the neighbours' server-action ceilings) now refuses an oversized POST body or?args=encoding with 413 before any decoding — a declared Content-Length is checked up front, a chunked body is buffered under the cap — andmaxArguments(default 1000) refuses an oversized argument list with 400. Both are configurable throughconfigureServerFunctionsServerand per-handler options;Infinityremoves a bound. The decode depth cap also now holds whichever body format the caller selects (The decode depth cap is opt-out: the caller picks the format that skips it #3119): the plain-JSON format walked into a bareJSON.parsewith no ceiling, where the framed codec enforced 64 levels — the same ceiling now applies to both, and a non-array argument encoding in either body format answers 400 instead of surfacing as the function's own failure.02e0ebf: Enforce the
Location/X-Revalidatebounds at the transport edge (A raw Response bypasses the Location and X-Revalidate bounds that redirect() and the revalidate helper enforce #3158).redirect()and the revalidate helpers refuse over-long values, but a hand-builtResponsereached the wire unchecked — a ~1 MBLocationbecame a ~1 MB redirect header, to die at the proxy after the mutation committed. The bound is now a property of the transport, one check where the composed headers leave for every producer; the helpers' authoring-time throws remain the legible fast path. Refused, never trimmed: a cut target is a different address, a dropped revalidate key is a silently stale cache.ec52360: Contain flight-data collector errors per source: a throwing collector no longer fails the mutation response (the client received an error for a mutation that succeeded) or drop the other sources' slices — the failing source is simply omitted and logged.
da50a36: Warn in dev when a scripted server function call is answered with 304 Not Modified (A 304 resolves the call to undefined, so "not modified" arrives as "no data" #3101). The scripted transport sends no conditional headers, so a hand-rolled 304 resolves the call to
undefinedrather than "unchanged" — the warning points at GET-declared reads with ETag/Cache-Control, where the browser owns the conditional exchange and replays its cached answer.2f18c56: Deliver a server-function encode failure as a failure, not an empty success (A result the codec cannot encode reaches the caller as undefined #3117). When the codec could not encode a result, the head was already committed — status spent, no error tag possible — and the body simply stopped; a truncated body decodes to
undefined, the same answer a void function gives, so a mutation that ran and committed its side effects was indistinguishable from one that returned nothing, and a data layer might retry it. The failure now travels in band: a terminal error-trailer frame (a!-prefixed payload on the existing chunk framing, unambiguous because codec frames always open with{) that the decoder throws — as the call's failure when it is the first frame, and into every still-pending async value when a later value's encoding fails mid-stream, with the delivered head keeping its data. The trailer is sanitized like any thrown error (generic in production, cause preserved in dev viaServer function result could not be encoded: …). Version skew degrades safely: an old client reading a trailer fails the call with a decode error rather than resolvingundefined.0932c89: Amortize ChunkReader buffer growth: the framed-stream reader reallocated and copied everything received so far on every network read, making one frame O(reads²) — ~200× the CPU for a payload delivered at slow-client read sizes, on both the server (argument decode) and client (response decode) legs. Growth now appends in place, compacts drained frames, and reallocates at ≥2× only when outgrown (ChunkReader copies the whole buffer per network read, making body decode O(n²) in read count #3154)
817b4d1: Bound the composed redirect and revalidate response headers (The redirect and revalidate headers are unbounded, so a successful mutation can answer with a network error #3131, the
An unbounded error message in the response header makes the response unreadable #3093 class). A 20K-character redirect target or a few hundred
revalidation keys produced a header past receivers' limits — undici's
16 KiB default, nginx's one-page proxy buffer for the whole header block —
so the response died at the socket (HPE_HEADER_OVERFLOW) after the
mutation committed. Truncation is not an option for these values the way
it was for An unbounded error message in the response header makes the response unreadable #3093's error label: a trimmed target is a different address
and a trimmed key list is a silently stale cache. So
redirect()and therevalidateoption now refuse past 4096 characters with a legible errornaming the remedy (carry the state server-side; split the invalidation or
use coarser keys). The bound sits in the producing helpers, which run
inside the function body, so both the returned and thrown spellings land
on the ordinary error path — what leaves dispatch is the error shape,
attributable and parseable. A raw
Responsebuilt by hand with anoversized
Locationremains the author's own; only the helpers arebounded.
929642b: Trust only a conforming (digit-string) Content-Length in the bodySizeLimit guard: a negative declaration (
-1) satisfied neither the over-limit check nor the undeclared-body buffer path and streamed the body into the decoder uncapped; non-conforming declarations now route through the bounded buffer (A negative Content-Length satisfies neither bodySizeLimit guard, so the body streams in uncapped #3153)ecfee20: Two cookie fixes. The no-JS flash cookie now degrades instead of vanishing
when an outcome exceeds the browser's 4 KB cookie ceiling (The no-JS flash cookie has no size bound — an outcome past the browser's ceiling vanishes silently #3137): past it
the whole Set-Cookie was silently discarded — no error anywhere, and the
page after the redirect looked like nothing was submitted, inviting the
retry that writes twice. The encoder drops the input echo first, then
bounds the value itself (a string keeps the longest prefix that fits,
structured results reduce to the outcome flag), and the submission arrives
with
truncatedset so integrations can say "succeeded, result too largeto display". And
serializeCookienow refuses in dev the shapes everybrowser silently rejects on arrival (serializeCookie emits cookie shapes every browser silently rejects #3138):
__Host-/__Secure-prefixrequirements and
SameSite=None/PartitionedwithoutSecure— each oneattribute away from a cookie that never comes back, with login-shaped
consequences. The validation compiles out of production builds. CHIPS
partitionedis also supported now, so partitioned third-party cookies nolonger require hand-building the header string.
30f9387: Direct (SSR-time) server-function calls now run under a per-call shallow copy of the render's
localsinstead of sharing the object: concurrent calls no longer overwrite each other's (and the render's) per-request context. Reads still inherit everything middleware set, and nested objects stay shared by reference;event.responseremains deliberately shared (Concurrent direct server-function calls share the render's locals object and overwrite each other #3156)af4cfc8: Two server-function grant fixes (A
GET()declaration outlives the function it was made about #3129, TheX-Single-Flightrequest header reshapes a cacheable GET body, and nothing names the variance #3128). AGET()declaration nowdies with the binding it was made about:
registerServerFunctionrevokesthe id's declared method when it rebinds the id to a different function,
so a mutation registered onto a once-declared id (an id collision, a
module re-evaluated in a live process after an edit dropped the wrapper)
no longer inherits GET dispatch and the origin-gate exemption — a function
that still declares GET re-runs
GET()right after re-registering, whichre-arms the grant exactly when it is still meant. And the single-flight
request header is now honored on POST only, the server half of the
client's own rule: folding on a GET would put a second body — an envelope
carrying data computed from that caller's request — at a cacheable url
under whatever public Cache-Control the author wrote, with nothing naming
the variance, one curl away from a shared-cache poisoning.
be7bcd2: The bare server-function address no longer decides its answer shape by the
absence of a header (At the bare address the no-JS answer shape is decided by the absence of a header #3139). The no-JS redirect convention (303, outcome
in the flash cookie) engaged on shape alone — form content type, no format
tag — which a page script's
fetch(url, { body: new URLSearchParams(...) })also matches: the script followed the 303 to the referrer's HTML, read
response.ok === true, and its answer disappeared into a cookie it wouldnever look at. Dispatch now reads the browser's own word for the caller
kind:
Sec-Fetch-Mode: navigate(or no fetch metadata, for olderbrowsers) keeps the convention, while a script's form-shaped post is
refused 400 before dispatch — before the mutation runs — pointing at the
data address and the format tag, the two spellings that work. Tagged
direct-HTTP callers keep the plain response as documented.
08b4d1c: Never mutate an application-held Response: the server-function handler takes ownership of the dispatched Response with a copy before any transport stamp lands, and
commitEventResponsefolds cookies/gap-fill headers onto a rebuilt Response instead of writing in place — a module-level cached Response no longer accumulates every caller's Set-Cookie (one user's session cookie served to the next) (commitEventResponse mutates the returned Response in place, so a cached Response accumulates every user's Set-Cookie #3155)93adc02: Three transport-correctness fixes on the server-function HTTP surface. A
POST whose body-format tag names no decoding this runtime has — an unknown
tag, a duplicated format header comma-joined by
Headers, an untaggednon-form body — is refused 400 before dispatch instead of calling the
function with a substituted
undefinedargument that let the mutationcommit and answer 200 (An unusable body-format header drops the body and calls the function with undefined #3130). The transport's defaulted
Cache-Control: no-storeis no longer written onto a 304, which is acache UPDATE rather than a stored response — the default was instructing
caches to evict the very entry the conditional request had just confirmed
(A defaulted Cache-Control: no-store on a 304 evicts the entry the revalidation just confirmed #3134). And
redirect()percent-encodes non-ASCII code points in itstarget before the value touches the latin1
Locationheader: targetsabove U+00FF used to throw (masked as a sanitized 500) and latin1-range
characters rode as raw bytes a client decoded to U+FFFD, redirecting
/caféto/caf%EF%BF%BD(redirect() to a non-ASCII path answers 500, and a latin1 one redirects somewhere else #3135). ASCII passes through untouched, soalready-encoded targets are not double-encoded.
19fa8b0: Fix two server-function transport encoding issues:
respond(undefined, { status: 204 })and raw null-bodyResponses now answer with a real bodiless response at the declared status instead of aTypeErrorfrom theResponseconstructor that dispatch sanitized into a phantom generic error at 200. A value-carrying result on a null-body status is reported as a legible authoring error naming the status, in every build.1a95943: Server-function failure is now signaled by the protocol's error tag alone, and thrown errors answer a real 500 (Failure is classified by HTTP status instead of the protocol's own error tag #3097). The client no longer treats
status >= 500as failure on responses the runtime encoded —respond(value, { status: 500 })resolves with its value like any other returned value, and only a thrown outcome rejects. A peer's own 5xx (proxy, load balancer) carries no body-format header and is still refused before decoding. On the server, a plain thrown error now answers 500 instead of 200-with-tag, so intermediaries — CDN metrics, load-balancer health, log alerts — see what the tag tells the client; thrown envelopes keep the author's status as before.5be07a8: Forward an author's 3xx status consistently (Whether an author's 3xx survives depends on return vs throw and on whether the value is empty #3096). The scripted redirect mask now covers exactly the statuses fetch follows (301, 302, 303, 307, 308) — a 304, the natural answer for a conditional read, forwards untouched for every caller. Returned envelopes keep their status for unscripted callers (the returned path used to hardcode 200 where the thrown path forwarded it), and the no-JS form convention honors a returned redirect envelope's Location the way it already honored a thrown one.
8963843: Fix SSR stream never closing when a fragment rejects terminally while async work in its subtree is still pending (SSR stream never closes when a rejected fragment abandons a pending serialized sibling #3165). Pending promises written to the hydration serializer now join an abandonment ledger keyed by hydration id; a fragment settling with an error releases everything under its key — descendant registry fragments settle so
flushEndcan drain, and abandoned serialized deferreds resolve so seroval's completion fires. Independent live boundaries keep gating the response as before.8d34af1: Answer the labelled version-skew 404 before the CSRF origin gate (The version-skew label is unreachable for a caller that carries no origin proof: 403, not the labelled 404 #3136).
A removed id is no longer in METHODS, so it could not be recognised as a
declared read and the gate fired on it: every caller without origin proof
— a CDN revalidating a GET-declared read, an uptime monitor, a
server-to-server client (Node's fetch sends none of the headers the gate
reads) — got a bare 403 instead of the
X-Server-Function-Unknown404,so a deploy that removed a function read as an auth/WAF failure in the
edge logs and the A stale server-function id is an undistinguishable 404, so version skew cannot be recovered #3110 recovery signal was invisible. Nothing is
registered at an unknown id, so the gate had nothing there to protect,
and the ids were never secret — the compiler ships them in the client
bundle. The hoisted lookup is a side-effect-free Map read, the labelled
404 no longer carries the CSRF
Vary(its answer does not depend onorigin proof, so it must not fragment shared-cache entries on it), and
the meaningless-path 404 stays bare and stays gated. Diagnosed, measured,
and drafted by @frenzzy.
fc5d079: Name the contract a
GET()declaration signs, and add the opt-out of its trade (A GET-declared read is invocable cross-site, and nothing enforces that it is a read #3114). The origin gate is skipped for GET-declared reads by design — same-origin policy already keeps a cross-site caller from reading the response, and the gate'sVaryfragments the shared-cache entries the helper exists to enable — which makes declaring GET a safety assertion, not only a transport choice: the function becomes executable from any origin, with caller-chosen arguments, carrying the user's ambient cookies. That contract is now stated onGET()'s documentation on both entries (declare GET only for reads that are safe in the RFC 9110 §9.2.1 sense), andcsrf: { protectDeclaredReads: true }lets a deployment that does not rely on shared caches apply the origin gate to its reads as well. Both halves are pinned by tests: the default skip, and the opt-in gate.1d2d1e5: Fix a deep-but-legal server function result being reported as a failed call (guardFailures recurses unbounded, so a deep result reports a successful call as a 500 #3160).
guardFailureswalked the result recursively, so ~10k+ nesting overflowed the stack and theRangeErrorescaped into dispatch's catch as a phantom function error — a successful, committed call answered with a generic 500. The container walk now carries an explicit stack (theisJSONSafeprecedent), and any residual synchronous throw on the codec road is renamed to an encode error before rethrow so misattribution cannot recur from another cause.2f6d8cc: Label the unknown-id 404 so version skew is recoverable (A stale server-function id is an undistinguishable 404, so version skew cannot be recovered #3110). A call whose well-formed address is not registered in the answering deployment — a tab holding the previous build's ids across a deploy, or a genuinely removed function — now answers with an
X-Server-Function-Unknownheader, and the client stampsunknownFunction: true(plus a directed message) on the rejection. Integrations can act on it — typically by reloading the document onto the current build — instead of surfacing a generic failed call. A 404 for a path the address scheme gives no meaning to stays unlabelled.fe4bfa0: Type the client
live()reference truthfully: calling it returns the reconnecting iterable itself, synchronously — not aPromiseof one. The declaration previously routed throughServerFunction, whose call signature promisesPromise<T>; the mismatch was masked by the dangling declaration references this release also fixes. Isomorphic consumers are unaffected: theyawaitthe call, and awaiting the client's plain iterable is identity.02f87fe: live() reconnects through the 4xx statuses that say "retry" and honors Retry-After (live() treats every 4xx reconnect as permanent, including 408 and 429 #3100). The reconnect loop treated the whole 4xx band as a definite rejection, so a rate limiter's 429 — or a gateway's 408 — permanently closed a healthy stream. 408 (RFC 9110 §15.5.9), 425 (RFC 8470) and 429 (RFC 6585 §4) now reconnect like a 5xx, as does any failure whose response carries Retry-After — the peer inviting the retry in as many words. A named Retry-After wait (seconds or HTTP-date, stamped on the error as
retryAfterin seconds for policy layers) replaces the exponential backoff guess for that attempt, capped at 60s so a misconfigured header cannot end the stream in all but name.5230666: Fix hydration ids drifting after a reactive lone spread (fix(compiler): align lone spread hydration ids #3105). A lone spread now passes its accessor straight to
spread()on the client — nomergeProps, no memo, no hydration id — matching the server's existing pass-through fast path. The runtime resolves a function props source inside its own tracking scopes.653dd41: Multi-source single-flight: named flight-data sources alongside the unnamed hook
The single-flight channel assumed exactly one data-owning integration — one
collectFlightDatahook on the server, onesubscribeFlightDataconsumer onthe client, later registrations displacing earlier ones. An app running two
caches (a router's route data and a query library's client) had no way to
refresh both from one mutation response: whichever library registered last
silently won.
The channel now multiplexes named sources over the same round trip:
registerFlightDataSource(id, hook)(server) registers a collectoradditively next to the unnamed
collectFlightDataslot, which remains thedata-owning integration's (a router's).
subscribeFlightData(id, consumer)(client) subscribes a consumer to itssource's slice; the bare legacy signature keeps meaning the unnamed source.
X-Single-Flightheader now carries the subscribed sourceids, so the server only runs collectors the client can consume; the
response leg echoes the ids actually folded, making the payload shape
self-describing. With named sources in play,
datais the keyed envelope{ [source]: slice, ... }and each slice is delivered to its consumer,awaited, before the mutation's promise resolves.
Fully wire-compatible in every cross-version pairing: a lone unnamed
registration still sends and echoes the literal
truewith the raw payloadshape, byte-identical to the previous protocol, and unrecognized opt-in
values from hand-tagged requests still reach the unnamed hook. Existing
integrations (Solid Router, TanStack Solid Start) keep working unchanged; the
keyed envelope only materializes when a named source registers on both ends.
8d17083: Server function response streaming now demand-gates and tears down every async-iterable or ReadableStream source in the result graph, not just a top-level one (A stream nested inside the result is neither demand-gated nor torn down on cancel #3125). A stream nested inside the result (
{ items: rows(), total }) no longer produces unbounded ahead of a slow consumer, and a cancelled or aborted request closes it —iterator.return()runs, so generatorfinallyblocks release their resources instead of leaking per abandoned request. The demand gate is shared across concurrently pumped sources (a consumer read wakes all parked pulls; each steps once and re-parks).006a115: Carry masked redirects in a dedicated header and retire the RC transition shims. Scripted callers now receive redirects as
X-Server-Function-Redirect: <status> <url>with the target resolved server-side against the request URL (A masked redirect keeps its target in Location on a 200 #3102) —Locationnever rides a masked 200, so an authoredLocationon a forwarding status (a 201's created-at) stays data, and integrations compare origins on a real URL instead of guessing navigation strategy from the author's spelling (navigate() from flight-data consumer can silently drop a relative-Location redirect (reliable with absolute URLs) #3107).decodeRedirectHeaderValueis exported for readers. Removed the transitional instance-header scripted fallback at the bare address and its forced no-store (A cacheable raw Response is shaped by a request header nothing keys on #3094): the answer shape is now a function of the URL alone, with the data address as the only scripted path.0b9d69a: Fix post-
createEventrefusals silently dropping the event's response stub (Refusals after createEvent drop the response stub's Set-Cookie silently #3159). The scripted-form 400, malformed-arguments 400, and maxArguments 400 returned directly instead of throughcommitEventResponse, so aSet-Cookiean integration wrote increateEvent(a rotated session, a fresh CSRF token) never reached the browser on exactly the requests where something already went wrong. Every exit pastcreateEventnow folds and commits the stub, which also arms the stub's late-write instrumentation on refusals.f739ec3: Sanitize a failure that escapes through a server function's result graph.
sanitizeServerErrorguarded the one road a thrown error takes out ofdispatch; a rejected promise, an async iterable that throws, or a stream
that errors reaches the codec as a value to encode instead, and shipped
its
messageand every own-property to the client verbatim — a drivererror's failing query, connection string and bound params included —
under a 200 carrying no error tag, because the head was already
committed. Those channels are now wrapped before either serializer sees
them: the response encoder and the frames flight sink, which encodes its
outcome with a serializer of its own.
The walk covers plain objects, arrays,
MapandSet. A channel held bya class instance or behind an accessor is left alone — rebuilding one and
invoking the other are not the runtime's to do.
markSafeErrorremainsthe escape hatch, an
Errorthat is a returned value is untouched, andthe wire format is unchanged, cycles and shared references included.
9522945: Scripted server-function calls now go to their own data address,
<endpoint>/data/<id>, leaving the bare<endpoint>/<id>address to plain HTTP (A cacheable raw Response is shaped by a request header nothing keys on #3094). The two caller kinds get differently shaped answers — codec encodings for the client transport, verbatim responses / form-convention handling for everyone else — and shared caches key on the URL, so a header-driven shape meant one caller kind's cached answer could be replayed to the other (aGET-declared function returning a rawResponsewith a public cache policy could serve its codec encoding to a browser navigation, or its raw body to the app's own transport). The answer's shape is now a function of the URL alone. A reference's.urland rendered action urls stay on the bare address; reconstructed callables splice thedatasegment in ahead of the id for their own calls. Transitional: the instance header still summons the scripted shape at the bare address so already-loaded tabs survive a server deploy, with those answers forcedno-store.fe4bfa0: Fix server function references typing as
any: the emittedserver-functionsdeclarations referencedServerFunction/ServerFunctionMetadatawithout importing them (theexport typeblocks only re-export the names), so underskipLibCheckeveryGET/live/createServerReferencereturn type silently collapsed toanyfor consumers.21a5122: Single-flight always folds the keyed envelope — the raw legacy payload shape is gone with the other RC shims. The unnamed registration's slice rides under its reserved id "true" like any named source, so
{ value, data }has one shape, not two; the client always deliversdata[source]to each consumer. The unrecognized-opt-in courtesy (arbitrary truthy header values reaching the unnamed hook) is also removed: only exact source ids run collection.f06f7b1: Pull a streamed server-function result behind a demand gate (A streamed result has no backpressure: one slow consumer buffers it all in server memory #3118). The
response stream was built with no
pulland no queuing strategy, andevery codec node is enqueued the moment it is parsed, so the producer ran
as fast as it could resolve whether or not anyone was reading: one slow
consumer buffered the whole result in server memory, unbounded and
invisible to application code. The consumer's reads now drive
pull,which releases one source pull at a time, so an unread stream stays near
the queue size instead of running away.
Scope: the gate sits on the source the runtime wraps, which is the
result itself. An async iterable nested inside the result —
{ items: rows() }— is pumped by the codec directly and is not yet gated. Endingthe stream releases a parked pull, so an aborted, cancelled or failed
stream still closes its source; a consumer that abandons a stream without
cancelling it now leaves the producer parked rather than running it to
completion.
07471da: Add typed preload links to the server asset pipeline.
Static manifests can attach
preloads: PreloadLink[], resolver results can carry the same shape for framework integrations, and any integration can register a link withregisterAsset("preload", link). The runtime preservesas, MIME type, CORS mode, integrity, referrer policy, fetch priority, and media attributes across string, streaming, embedded-head, custom-sink, and frame renders.lazy()andclientOnly()forward resolver-provided preload links alongside their JS and CSS.JSX.HTMLPreloadAsandJSX.HTMLFetchPriorityare now exported for reuse.Preload links are explicit: manifest
assetsare not preloaded automatically. Existing stylesheet and modulepreload APIs are unchanged.Development builds warn when font or fetch preloads omit
crossorigin, because a different eventual request mode cannot reuse that preload.Frame clients also retain and consume every late root asset record instead of dropping earlier records that reuse the same transport key.
Updated dependencies [51ffcb9]
Updated dependencies [91e300a]
Updated dependencies [00d1d5d]
Updated dependencies [07471da]
Updated dependencies [0c02d42]
test-integration@2.0.0-rc.5
Patch Changes