diff --git a/src/routes/(3)building-apps/(3)server-functions/(0)index.mdx b/src/routes/(3)building-apps/(3)server-functions/(0)index.mdx index 4642265bf..39afde79b 100644 --- a/src/routes/(3)building-apps/(3)server-functions/(0)index.mdx +++ b/src/routes/(3)building-apps/(3)server-functions/(0)index.mdx @@ -43,7 +43,7 @@ Search the client bundle for `database` and it is not there. The component did not change; `getProduct` returns a promise on both sides, so the [Async reactivity](/concepts/async-reactivity) rules apply to it unchanged. Server functions work without a router. -Solid Router's `query()` and `action()` add caching, submissions, and revalidation on top of them, and [Data loading and mutations](/routing/solid-router/data) covers that layer. +Solid Router's `query()` and `formAction()` add caching, submissions, and revalidation on top of them, and [Data loading and mutations](/routing/solid-router/data) covers that layer. ## Enable server functions @@ -197,9 +197,9 @@ The `X-Server-Function-Instance` header the client runtime sends identifies the ### The call resolves to a `Response` object instead of data -The function returned `redirect()` or `reload()`, and the caller was plain code rather than a router action. +The function returned `redirect()` or `reload()`, and no router was subscribed to the transport when it was called. The client transport hands responses that carry navigation or revalidation metadata back whole, so the integration that owns navigation can apply them. -Call the function through Solid Router's `action()`, or return a value and let the caller decide where to go; [Mutations and responses](/building-apps/server-functions/mutations-and-responses#redirect-the-caller) covers both. +Call the function with Solid Router mounted, which applies the metadata for a direct call or a form action alike, or return a value and let the caller decide where to go; [Mutations and responses](/building-apps/server-functions/mutations-and-responses#redirect-the-caller) covers both. ### The build fails with `server functions cannot capture non-top-level variables` @@ -239,4 +239,4 @@ Call the function from a render, a middleware, or another server function, or pr - [Reads, streams, and live data](/building-apps/server-functions/reads-and-live-data): declare a read that HTTP caches can store, and keep a stock level connected when another shopper changes it. - [Arguments and security](/building-apps/server-functions/arguments-and-security): what a caller can send, what the transport can encode, and how to validate and authorize every request. - [Mutations and responses](/building-apps/server-functions/mutations-and-responses): return a redirect, a reload, or a 400 from a cart mutation, and control what a thrown error reveals. -- [Data loading and mutations](/routing/solid-router/data): wrap the same functions in `query()` and `action()` for caching, submissions, and revalidation. +- [Data loading and mutations](/routing/solid-router/data): wrap the same functions in `query()` and `formAction()` for caching, submissions, and revalidation. diff --git a/src/routes/(3)building-apps/(3)server-functions/(3)mutations-and-responses.mdx b/src/routes/(3)building-apps/(3)server-functions/(3)mutations-and-responses.mdx index e107017d2..db76ff525 100644 --- a/src/routes/(3)building-apps/(3)server-functions/(3)mutations-and-responses.mdx +++ b/src/routes/(3)building-apps/(3)server-functions/(3)mutations-and-responses.mdx @@ -78,9 +78,9 @@ Return a redirect when it is the successful outcome, and throw it when it is an Both reach the caller the same way. What the browser does next depends on who made the call. -A Solid Router `action()` navigates to `/goodbye` without a page load. +With Solid Router mounted, a form action or a direct `deleteAccount()` call from an event handler navigates to `/goodbye` without a page load. An HTML form post without JavaScript is answered with the redirect itself, 302 by default, and the browser follows it. -Plain code that called `deleteAccount()` directly receives the `Response` object as the resolved value, because the transport hands navigation metadata back whole for the caller to apply. +Code that calls `deleteAccount()` with no router subscribed to the transport receives the `Response` object as the resolved value, because the transport hands navigation metadata back whole for the caller to apply. The target can be a string or an `Href` produced by a router's typed paths helper; a branded `Href` redirects to its logical path. Any other object throws `redirect() expects a string URL or an Href-branded value`. @@ -101,13 +101,32 @@ export async function addToCart(productId: string, quantity: number) { } ``` -Call `addToCart("mug", 1)` through a router action and the cart query refetches when the call settles; nothing else on the page is touched. +Call `addToCart("mug", 1)` with Solid Router mounted, from a form action or straight from an event handler, and the cart query refetches when the call settles; nothing else on the page is touched. Omit `revalidate` to ask for every cached read to refresh. Core carries the keys in an `X-Revalidate` header and does not define a cache. Solid Router reads the header against its query cache; [What revalidates after a mutation](/routing/solid-router/data#what-revalidates-after-a-mutation) explains the matching. `respond()` and `redirect()` accept the same `revalidate` option, for a mutation that also returns a value or changes location. +Solid Router treats every `POST` call as a mutation, and `POST` is the default. +A call that declares no `revalidate` keys revalidates every query on screen, the same default a form action has. +Reads are exempt when they go through the router's `query()`, which declares the function `GET`, or when the function is declared with `GET()`. +A `POST` that does not change anything, such as a log call or a search sent on each keystroke, declares an empty `revalidate` so it touches nothing: + +```ts +// src/data/telemetry.ts +import { respond } from "@solidjs/web"; + +export async function logEvent(name: string) { + "use server"; + + await analytics.record(name); + return respond(null, { revalidate: "" }); +} +``` + +`revalidate: ""` declares no keys, the same as `revalidate: []`; return `respond(value, { revalidate: "" })` when the call also returns a value. + ## Handle thrown errors A returned or thrown `Response` or envelope is control flow, and the handler preserves its status, headers, and value. @@ -168,17 +187,17 @@ return respond({ issues }, { status: 400 }); throw respond({ issues }, { status: 400 }); ``` -With the `Avoid` version the HTTP response is a 400, and the promise in the browser resolves as if the write had worked; a router action records it as a successful submission with `{ issues }` as its result. +With the `Avoid` version the HTTP response is a 400, and the promise in the browser resolves as if the write had worked; a router form action records it as a successful submission with `{ issues }` as its result. The status on a returned envelope is for HTTP callers; the throw is what tells the caller something failed. ::: ## Add router submissions -Solid Router's `action()` turns a server function into something a form can post to and the router can track: +Solid Router's `formAction()` turns a server function into something a form can post to and the router can track: ```ts // src/data/cart.ts -import { action } from "@solidjs/router"; +import { formAction } from "@solidjs/router"; import { reload } from "@solidjs/web"; export async function addToCart(form: FormData) { @@ -197,19 +216,19 @@ export async function addToCart(form: FormData) { return reload({ revalidate: "cart" }); } -export const addToCartAction = action(addToCart); +export const addToCartAction = formAction(addToCart); ``` Render `