Skip to content

@solidjs/web@2.0.0-rc.4

Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 28 Aug 21:08
· 31 commits to main since this release

Minor Changes

  • 475744c: Add invoke(fn, options, ...args) — the per-call server function invocator (#3057). Applies one call with invocation-scoped options: signal (aborting rejects the call and cancels the request; ends a live source's iteration across reconnects), keepalive, and priority. Longer-lived concerns are refused with a redirect to their home (prepareRequest, withMeta/GET, the data layer via signal) — never a RequestInit passthrough. Dispatch rides a registered-symbol invocation channel (SERVER_FUNCTION_INVOKE) that wrappers forward like declaration metadata, so invoke composes through GET, live, and integration wrappers that adapt it. On the server the call runs in-process: signal rejects the caller, transport hints are no-ops.
  • 8d249c7: Patch-mode list hydration: claim + register only. The list driver claims each
    server row positionally through the row's own _hk key (a row-scoped
    explicit-id owner makes the compiled template's getNextElement resolve it),
    and patchDriver skips the initial force-apply while hydrating — server HTML
    stays the truth until the first transition. All driver-side each reads and
    the probe are id-isolated (throwaway/private explicit-id owners), so lazily
    minted prop-getter memos can no longer shift the ambient hydration id chain
    on either the engage or decline path.
  • 8d249c7: Patch-mode list driver: keyed <For> over a store array is offered to the
    runtime's row-ops driver (create/bind at op-apply, LIS moves, node removal —
    no mapArray, no per-row owners, no DOM-side reconcile). For carries $ll
    metadata on a lazy classic accessor so unaware renderers and declined lists
    (non-store subject, impure rows proven by a bind-time owner probe, fallback
    or index usage) fall through to today's mapArray path unchanged. Array
    identity swaps keep keyed semantics by raw-identity matching. Adds
    ownerIsBlank (signals) for the purity probe and driveList (web, rxcore
    seam) for the runtime.
  • 8d249c7: Close two list-driver coverage gaps found by the JFB store scenario: setter-
    channel structural mutation (push/splice/index assignment/permutation) now
    emits identity-keyed row ops at the fold — a driven list stays DOM-correct
    for stores mutated without reconcile — and empty-initial lists engage
    TENTATIVELY, deferring the purity probe to the first created row, with a
    late decline handing the region to the classic mapArray path through the
    runtime's re-entry thunk
  • 8d249c7: Shallow store lists through the compiled driver: slot patches graduate from
    prototype to channel semantics (key-aligned value-replaced slots only —
    structure rides row ops — queued at effect phase under the registration
    owner), and the list driver collects a shallow row's compiled bodies at bind
    (rows are raw; nothing to register on) and dispatches them from the array's
    slot channel, rebasing indices with structural ops. Adds storeIsShallow;
    kind-changing subject swaps (shallow <-> deep) hand off to classic.

Patch Changes

  • 8d249c7: External-audit fixes on the patch-list driver surface: family (projection/optimistic) arrays now decline the driver — their structural changes emit no row/slot ops and the proxy identity is stable, so an engaged list would freeze on optimistic or projection structure (classic mapArray handles them correctly, including on identity-swap handoff). Shallow slot-patch registration is now multi-consumer — two driven lists over one shallow array previously overwrote each other's channel. Adds storeHasFamily (with server stub) and regression tests for both.

  • 54506e0: Clarify invoke's wrapper contract: declaration wrappers (GET, live) forward the invocation channel mechanically (1:1 call mapping); wrappers that share calls (deduping caches, multicast channels) opt in deliberately or decline, and invoke's error now directs callers to the underlying reference or the wrapper's own idioms.

  • 0043643: Document two boundaries of the client fetch option's contract: a retrying wrapper may re-send a request that got no response but must never replay one whose response ended (mid-body death may have executed a mutation; live-source reconnection is the runtime's job), and the call-to-request mapping is delivery detail, not contract.

  • c9b4f2a: SSR <select value> resolution now handles empty-string bound values (#3013 follow-up). Empty attribute values serialize as bare attributes (<select value>, <option value>), which the flush-time pass didn't recognize — a bound '' never marked the value="" placeholder option selected, so the pre-hydration page showed the first option while app state said ''. The pass now reads the bare form as the empty string on both the select and its options, matching React's SSR output for the single-select placeholder pattern.

  • 8c48a2e: Fix whole-document hydration dying when useHead coexists with shell-authored <head> children (#3081). A charset/base registration is spliced as a prelude immediately after the <head> open tag — a deliberate byte-placement constraint — landing it ahead of every head child the shell authored itself. The compiled head traversal is positional (raw firstChild/nextSibling chains in production), so the prepended tag shifted every read by one and hydration for the whole document died on a null read. hydrate() now moves the registry-inserted leading run (data-dh without the data-dhf in-place-rewrite stash) to the end of head before any claiming: the parser already consumed the byte-placement guarantees, the moved metas are inert in an unrendered element, and the walk sees exactly the shell's authored children. The in-place rewritten static <title> keeps its stash, its position, and its claim.

  • 8d249c7: The list driver's identity matching unwraps store proxies on both sides — draft-authored permutations store row proxies verbatim, and matching them against raw records rebuilt every surviving row (caught by the JFB keyed-reorder identity gate).

  • 2f01f23: Module-level "use server" exports now register by value: the server build registers each export's evaluated terminal initializer whole, so server-side wrappers compose onto every call path — export const getUser = withValidation(schema, fn) applies the wrapper to HTTP dispatch and in-process SSR calls alike, and patterns like withDelay(fn, 400) work for server mocks. The client build always emits bare references, so wrappers, schemas, and helpers stay server-only by construction. The compiler never inspects the initializer's shape; registerServerReference now throws at module eval when handed a non-function, turning stray non-function exports into loud boot errors instead of dead references. Anonymous default expressions (export default withDelay(...), export default async () => ...) get a synthesized binding and register too — previously they were silently dropped from both builds. Supersedes the unreleased wrapped-export compile error.

  • 8d249c7: Optimistic family arrays are drivable by the patch-mode list driver, completing the family channel: structural optimism (push/splice/reorder/replace in optimistic drafts) emits identity-diffed row ops at lane timing from the override channel — visible in flight, bypassing the transition stash like optimistic record patches — and reverts emit an identity RESYNC the driver resolves against the live post-revert view. The driver binds optimistic lists from the optimistic view (classic reads the same view through the proxy), and the identity-swap matcher is shared between swaps and resyncs. Equivalence matrix extended with async optimistic scenarios (mounted → in-flight → settled, revert and land, element-level and parent-key structural writes).

  • 8d249c7: Patch channel is pay-for-use: the list driver and patchDriver moved out of the always-retained web runtime into patch-driver.ts, arming the insert seam lazily from rowProof/patchDriver (which only compiled patch-mode output imports); the store's emitters ride hooks installed at first registration (patch-hooks.ts) instead of static imports. Apps without patch-mode output retain only a ~100 B insert hook; the store write-path seams cost ~490 B on the store floor. Before this, every client app carried the full driver (~2.4 KB brotli).

  • 8d249c7: Second re-audit hardening of the patch channel: adoption seams demote accessor-bearing adoptees to tracked effects in development, with a loud diagnostic (production emits directly — per-adoption accessor scans cost ~12% of dbmon's tick, and getter-bearing adoptees on patched records are a development-caught shape); setter-returned root replacements and chained-store swaps emit their patches and row ops at fold commit; the list driver's ops application builds every new row before any destructive step (a throwing row factory leaves DOM and bookkeeping atomically unchanged); patch errors route to the nearest computed ancestor so Errored.reset() can recompute it (reset also skips non-computed sources), and unhandled patch errors halt like unhandled effect errors; key equality is SameValueZero and occurrence-aware everywhere keys compare — NaN keys stay retained and duplicate keys adopt per occurrence on both channels; same-batch duplicate patch emissions coalesce (one application per batch, effect parity).

  • 8d249c7: Third re-audit hardening of the patch channel: same-batch coalescing updates the queued entry in place (latest next wins — adoption replaces the captured object, so dropping later emissions applied stale state) and the drain clears the channel stamps (no batch retention on quiet records); the adoption remainder window builds from the misalignment point so prefix-consumed rows are never re-offered to duplicate keys; optimistic tentative matching gains SameValueZero + occurrence-aware parity with the plain channel; a failed row-ops application forces an identity resync on the next update (the store committed the failed topology while DOM kept the old one — positional ops would mis-index) and suppresses slot ticks until the baseline is restored; a throwing row factory also severs its own partial registrations.

  • 8d249c7: Fifth-round hardening of the patch channel: no-op adoptions (A→B→A in one batch) clear the adopted flag so later setter row ops never freeze a driven list; transition merges retarget the moved entries' coalescing stamps (post-merge emissions coalesce instead of double-applying at commit); multi-consumer patch dispatch snapshots the registration list (a callback unbinding a sibling no longer skips consumers); the list driver's initial construction severs partial registrations on throw like update-time builds (one failed initial render no longer elevates patchCount globally); a failed apply actively resyncs from the next slot tick instead of waiting for a structural update; and identity swaps register the new subject's channels before applying so a throwing swap stays recoverable.

  • 8d249c7: Patch-channel contract hardening from the stage-2 re-audit: ordinary patchDriver registrations unbind with their owner (entries no longer leak past unmount); merged transitions move their held-patch stash so no patch strands; the optimistic drain shares the normal drain's per-entry error isolation and boundary routing; accessor-bearing records are excluded at admission (scan-before-trust) and records that acquire accessors demote their patches to tracked effect fallbacks; writable projection arrays emit setter row ops at their fold-commit visibility moment; row-ops/slot registrations resolve chained backings to the ultimate owner; duplicate keys match occurrence-aware instead of first-wins; the production dev-token typo (_DX_DEV_) is fixed; patchDriver: true normalizes identically in Babel and the native loader, the option is typed in TransformOptions, and a dom-patch parity tier ratchets patch-mode output across both compilers (currently byte-identical on all fixtures).

  • 8d249c7: Patch-channel semantics completion: a throwing patch now routes through its
    registering owner's queue chain to the enclosing error boundary (render-
    effect parity; sibling isolation preserved, unhandled errors still rethrow),
    and the dual-driver effect fallback splits phases with the same compiled
    body — a next===prev read pass tracks in compute, the force apply writes in
    the effect phase where transitions and batching expect DOM writes

  • 8d249c7: Patch-mode lists now implement the identity semantics the view declares instead of the reconcile key's. Deep lists are unaffected (adoption preserves proxy identity, so key ops and reference semantics coincide). Shallow reference-keyed lists rebuild rows whose records were replaced — matching classic mapArray exactly, where the driver previously patched them in place (a default-on compiler mode must never change observable DOM identity). For forwards its keyed prop on the list metadata; explicit keyed={fn} lists decline the driver until the accessor-row binding contract lands.

  • 8d249c7: Patch-channel arming is two-tier so the default-on cost stays proportional: patchDriver no longer retains the list driver (only rowProof — the compiled marker of a patch-mode list — arms the insert seam), and the store emitters split into value hooks (armed by registerPatch) and row hooks (armed by list registrations), so non-list patch templates never retain row binding, LIS, or reconcile's diff builders. Flip-preview size scenarios pin both tiers.

  • 8d249c7: Patch-mode lists retain per-row unbind handles: a record the app keeps beyond its row's life no longer holds a live patch registration updating detached DOM — registrations are severed on row removal, contract-leave handoffs, and list disposal. Dev builds also warn when a stamped row's build attaches computations or cleanups to the shared list owner (owned work in handler/attribute value position is unsupported in patch-mode rows).

  • 8d249c7: Projection (non-optimistic) family arrays are drivable by the patch-mode list driver: their recomputes walk reconcile, whose row/slot emissions were never family-gated and ride the transition-stamped apply queue. The blanket family decline narrows to optimistic families only (storeHasOptimisticFamily), whose user writes ride node overrides and emit no structural ops. Fixes chained-backing patch registration: a projection wrapper's backing is another store's proxy, so registerPatch/patchableRaw now resolve through the chain to the ultimate owner target — patches registered on wrapped projection rows previously never fired (value transitions fold on the source). Equivalence matrix extended with 13 projection scenarios including recompute-driven structure and retention topology.

  • 8d249c7: Patch-mode list admission moves entirely to compile time: driveList engages only for row functions carrying the compiler's rowProof stamp (exported from @solidjs/web), and the runtime purity probe is deleted — no speculative execution of user row code, no probeMark/probeGate seams, no ownerIsBlank, no tentative empty-list engagement with late decline. Unstamped rows take the classic mapArray path before any DOM work; lateClassic remains only for engaged lists whose subject later leaves the contract (identity swap to a derived array, shallow/deep kind switch).

  • 258c76a: Harden the server function handler's HTTP layer. The method gate is now an allowlist: POST always dispatches, GET and HEAD dispatch only to GET-declared functions, and every other verb answers 405 — previously a HEAD (or PUT/DELETE/PATCH) request bypassed the GET gate entirely and executed any registered function with attacker-chosen query arguments (#3069). HEAD runs the function like GET and strips the body per spec. Responses now default to Cache-Control: no-store unless the function set its own cache policy, and GET/HEAD requests to GET-declared functions skip the CSRF origin gate so their responses no longer carry the Vary: Sec-Fetch-Site, Origin, Referer that fragmented shared-cache entries — declared reads are protected by same-origin policy, and caching becomes opt-in on the wire instead of just in prose (#3071).

  • 79b96cf: Address server function calls by path: <endpoint>/<id>, with arguments staying in the query.

    The id travelled in X-Server-Function-Id, with ?id= as the fallback for requests the client runtime did not make. Both are gone; it moves into the path — what per-function edge rules, cache policies and http.route labels key on — leaving one place in the request that carries it, so a cache in front of the app cannot be made to store one function's response under another's key (#3070). POST addresses move too, and endpoint now gates dispatch on both halves: a request whose path does not start with it is not a call.

    serverFunctionUrl(id, boundArgs?) and parseServerFunctionUrl(url) ship on both entries for integrations composing action urls. A GET call whose url would exceed 2000 characters dispatches over POST instead, marked as a read — a cache miss rather than a 414.

    A read whose query is not an argument encoding hands that query to the function as a lone URLSearchParams, the read-side mirror of a no-JS form post decoding to a lone FormData, so a method="get" submit reaches the function it addresses. Which reading applies is decided by the url alone, never by a header; args stays reserved on the query, and a value under it that is not an argument array answers 400.

  • 82b4e14: Add fetch to configureServerFunctionsClient: the function the transport sends every server-function request with, typed and called as (address, init) — the address relative to the document, as the global one receives it — so an ordinary fetch wrapper drops in, a hand-written one needs no casts, and parseServerFunctionUrl reads the id back out for telemetry. null restores the global.

    An app-shaped url is what makes it worth a seam: the handler takes a web Request, so a route that rewrites into the canonical address dispatches like any other call, and nothing downstream — the router's action-url interception, the plugin's dev middleware, the generated dispatch gate — has to learn a second address format. A wrapper forwards init — the call's signal rides on it — keeps the call same-origin, and hands back what the peer answered, unread. The seam is the client transport's exit only: a server-side call runs in process and never reaches a fetch.

    Also tidies the endpoint documentation on both entries, which the path-addressing change left saying the same thing twice.

  • c07edcb: Fail a server function call on a response the runtime did not write, instead of resolving it to undefined (#3087).

    Only the protocol's error header and a 5xx counted as failure, so every other non-2xx was decoded as a result — and decoding a login page, or an empty 405, yields nothing. A response at 400 or above carrying no body format now fails the call with the status on the error, undecoded, and before the passthrough control flow uses: a refusal can carry a Location of its own, and the passthrough would have handed it back as control flow. Redirects are left alone — fetch follows them, so an interstitial arrives as its page at 200, and a 3xx only reaches the transport where something opted out of following one.

    BodyFormat.Void marks the one response the runtime encodes without a format to carry — a function that returned nothing — so respond(undefined, { status: 400 }) stays a result alongside new Response(null, { status: 404 }) and respond(value, { status: 400 }). A client that predates the tag decodes it the same way; a client that has it, talking to a server that does not, reads an untagged void 4xx as a refusal.

    A 2xx is not judged at all: a login page served at 200 is indistinguishable from a void result by header alone. One runtime-produced shape is caught with the foreign ones — a verbatim X-Content-Raw response at a non-2xx status, which an integration's responseHandler claims before the check.

  • Updated dependencies [8d249c7]

  • Updated dependencies [f0c3692]

  • Updated dependencies [f3da41e]

  • Updated dependencies [a10cf1a]

  • Updated dependencies [8d249c7]

  • Updated dependencies [8d249c7]

  • Updated dependencies [8d249c7]

  • Updated dependencies [8d249c7]

    • solid-js@2.0.0-rc.4