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
8 times, most recently
from
August 31, 2026 10:21
dac2814 to
4a8ca4a
Compare
github-actions
Bot
force-pushed
the
changeset-release/next
branch
from
August 31, 2026 10:47
4a8ca4a to
c1f5154
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/compiler@2.0.0-rc.5
Minor Changes
<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.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.@solidjs/web@2.0.0-rc.5
Minor Changes
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.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.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.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.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.<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.{ 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.Patch Changes
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.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.
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.
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.
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.
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.
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.
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.
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.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.
Updated dependencies [51ffcb9]
Updated dependencies [00d1d5d]
Updated dependencies [0c02d42]
@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.@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
@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.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.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.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.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.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 [2023daa]
Updated dependencies [09bbe24]
Updated dependencies [88fa9d6]
Updated dependencies [0c02d42]
@solidjs/universal@2.0.0-rc.5
Patch Changes
test-integration@2.0.0-rc.5
Patch Changes