From 08cb4815babe1430ceb08bafe8015af54ca7481a Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 22 Oct 2026 08:57:14 +0000 Subject: [PATCH] docs: live() implies GET, liveQuery implies live() live(fn) declares GET itself (solid rc.17) and router liveQuery declares a server function live() itself (router next.42): drop live(GET(...)) and liveQuery(live(...)) from the guides and the router reference. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01Fmb4aQtDJWoA8CUktR9QoZ --- .../(1)reads-and-live-data.mdx | 26 +++--- .../(4)metadata-and-transport.mdx | 8 +- .../(7)durable-generation.mdx | 88 +++++++++---------- .../reference/(3)solid-router/(4)data.mdx | 2 +- 4 files changed, 56 insertions(+), 68 deletions(-) diff --git a/src/routes/(3)building-apps/(3)server-functions/(1)reads-and-live-data.mdx b/src/routes/(3)building-apps/(3)server-functions/(1)reads-and-live-data.mdx index 965e86916..7f15f7fe4 100644 --- a/src/routes/(3)building-apps/(3)server-functions/(1)reads-and-live-data.mdx +++ b/src/routes/(3)building-apps/(3)server-functions/(1)reads-and-live-data.mdx @@ -125,22 +125,18 @@ The stock level for a product is one such question. ```ts // src/data/inventory.ts -import { GET, live } from "@solidjs/web/server-functions"; - -export const stockLevel = live( - GET(async function* (productId: string) { - "use server"; - yield await inventory.count(productId); - for await (const change of inventory.subscribe(productId)) { - yield change.count; - } - }) -); +import { live } from "@solidjs/web/server-functions"; + +export const stockLevel = live(async function* (productId: string) { + "use server"; + yield await inventory.count(productId); + for await (const change of inventory.subscribe(productId)) { + yield change.count; + } +}); ``` -Place `live()` outside `GET()`. -`GET()` chooses the read transport, and `live()` wraps the call behavior around it, so it must be the outermost declaration. -`live()` does not imply `GET()`; a live source without it streams over `POST`, which is fine when a cacheable URL is of no use. +A live source is a read, so `live()` declares it `GET` itself; the connection uses the read transport without a separate `GET()`. The source yields the current count first, every time it is invoked. That is the contract a reconnect depends on: when the connection drops and the client calls again, the first yield replaces the stale answer, and no client-side cache is needed to fill the gap. @@ -267,7 +263,7 @@ Check the server log for the status: the client retries only transient failures - A `GET()` read puts its arguments in the URL; keep secrets out of them and read identity from the request event. - Responses leave with `Cache-Control: no-store`; return `respond(value, { headers })` to opt a read into caching. - Return an async iterable to stream events over one connection; ending the iteration in the browser ends the server producer. -- Declare a value that changes over time with `live(GET(fn))`, `live()` outermost, and yield the current value first on every invocation. +- Declare a value that changes over time with `live(fn)`, which declares it `GET` as well, and yield the current value first on every invocation. - Read a live source through one memo and pass the value down; each call site that invokes the source opens its own connection. - Use `onstatus` to show a reconnect; put data freshness in the yielded value. diff --git a/src/routes/(3)building-apps/(3)server-functions/(4)metadata-and-transport.mdx b/src/routes/(3)building-apps/(3)server-functions/(4)metadata-and-transport.mdx index 07296d56f..c59a0981f 100644 --- a/src/routes/(3)building-apps/(3)server-functions/(4)metadata-and-transport.mdx +++ b/src/routes/(3)building-apps/(3)server-functions/(4)metadata-and-transport.mdx @@ -57,15 +57,15 @@ The `Avoid` version places any order for any caller; `requiresAuth` is a value o Later `withMeta()` calls shallow-merge over earlier metadata. `GET()` uses the same channel for its HTTP method, and the two compose in either order. -`live()` wraps the call behavior, so it goes outside both: +`live()` wraps the call behavior and declares `GET` itself, so it goes outside `withMeta()` and needs no `GET()`: ```ts export const stockLevels = live( withMeta( - GET(async function* () { + async function* () { "use server"; yield* inventory.levels(); - }), + }, { channel: "inventory" } ) ); @@ -224,7 +224,7 @@ Check the condition in the hook against the metadata on the reference, and move - The defaults are a `POST` to `/_server/data/` with the argument array as the body and `Cache-Control: no-store` on the response; change them only for a reason on this page. - Attach static facts to a declaration with `withMeta()`; the server does not read them, so enforce policy in the body. -- Compose `withMeta()` and `GET()` in either order, and put `live()` outside both. +- Compose `withMeta()` and `GET()` in either order, and put `live()`, which implies `GET()`, outside `withMeta()`. - Add session-dependent headers in one `prepareRequest` hook, configured once in the client entry before the first call. - Move the endpoint with the plugin's `serverFunctions.endpoint` option so client and server stay matched. - Pass `signal`, `keepalive`, or `priority` to one call through `invoke()`; anything longer-lived is refused with a pointer to its home. diff --git a/src/routes/(3)building-apps/(3)server-functions/(7)durable-generation.mdx b/src/routes/(3)building-apps/(3)server-functions/(7)durable-generation.mdx index d6f772af6..912a5d3f5 100644 --- a/src/routes/(3)building-apps/(3)server-functions/(7)durable-generation.mdx +++ b/src/routes/(3)building-apps/(3)server-functions/(7)durable-generation.mdx @@ -158,16 +158,14 @@ The live source yields the reply so far, whole, on every write: ```ts // src/data/replies.ts import { getRequestEvent } from "@solidjs/web"; -import { GET, live } from "@solidjs/web/server-functions"; +import { live } from "@solidjs/web/server-functions"; import * as replies from "../server/replies"; -export const watchReply = live( - GET(async function* (replyId: string) { - "use server"; - const signal = getRequestEvent()?.request.signal; - for await (const reply of replies.watch(replyId, signal)) yield reply; - }) -); +export const watchReply = live(async function* (replyId: string) { + "use server"; + const signal = getRequestEvent()?.request.signal; + for await (const reply of replies.watch(replyId, signal)) yield reply; +}); ``` The request's signal ends the watcher when the reader leaves; the job does not see it. @@ -221,7 +219,7 @@ A projection rather than a memo keeps readers of `reply.status` out of every tex `refresh(reply)` re-runs the projection, which opens a new connection; it does not touch the job. Every call site that iterates a live source holds its own connection. -When the thread list and the thread both need the same reply, hoist the read, or declare it with Solid Router's [`liveQuery`](/reference/solid-router/data#livequery), which keeps one connection per key for every consumer; the spelling is `liveQuery(watchReply, "reply")`, and a function already declared with `live(GET())` passes through unchanged. +When the thread list and the thread both need the same reply, hoist the read, or declare it with Solid Router's [`liveQuery`](/reference/solid-router/data#livequery), which keeps one connection per key for every consumer; the spelling is `liveQuery(watchReply, "reply")`. `liveQuery` declares a server function `live()` itself, and one already declared `live()` passes through unchanged. ## Hold the bubble until the stream confirms @@ -288,13 +286,12 @@ The `chat` example in the Solid repository is this section's reference implement ::: When the markdown renderer, the highlighter, and the model client should stay on the server, the thread can be server markup that keeps changing. -The server function returns a component instead of data, and `live()` holds the response open while it re-renders: +The server function returns a component instead of data, and `liveQuery`, which declares it `live()`, holds the response open while it re-renders: ```tsx // src/routes/thread.tsx import { createMemo, onCleanup } from "solid-js"; import { getRequestEvent } from "@solidjs/web"; -import { GET, live } from "@solidjs/web/server-functions"; import type { Slot } from "@solidjs/web/frames"; import { liveQuery } from "@solidjs/router"; import { stopReply } from "../data/replies"; @@ -302,41 +299,36 @@ import * as threads from "../server/threads"; type ComposerSlot = Slot<{ threadId: string; seen: string[] }>; -export const getThread = liveQuery( - live( - GET(async (id: string) => { - "use server"; - return (props: { composer: ComposerSlot }) => { - const gone = new AbortController(); - onCleanup(() => gone.abort()); - getRequestEvent()?.request.signal.addEventListener("abort", () => - gone.abort() - ); - const view = createMemo(() => threads.watch(id, gone.signal)); - return ( -
- {view().messages.map((message) => ( -
-
- {message.status === "generating" && ( -
- -
- )} -
- ))} - m.id)} - /> -
- ); - }; - }) - ), - "thread" -); +export const getThread = liveQuery(async (id: string) => { + "use server"; + return (props: { composer: ComposerSlot }) => { + const gone = new AbortController(); + onCleanup(() => gone.abort()); + getRequestEvent()?.request.signal.addEventListener("abort", () => + gone.abort() + ); + const view = createMemo(() => threads.watch(id, gone.signal)); + return ( +
+ {view().messages.map((message) => ( +
+
+ {message.status === "generating" && ( +
+ +
+ )} +
+ ))} + m.id)} + /> +
+ ); + }; +}, "thread"); ``` `threads.watch` is the thread-level watcher: it yields the thread and its messages after every write to any of them. @@ -420,8 +412,8 @@ Await it from an action body or another imperative scope. - Start the generation from a server function that returns at once; the job belongs to a module-level registry and writes the reply into a store as it arrives. - Pass the job its own abort signal; the request's signal ends only the watcher. -- Deliver the reply through `live(GET(async function*))`, yielding the whole reply so far and the current reply first on every connection. -- Render it in the browser with `createProjection`, or on the server as a live server component mounted with `dynamicComponent` through `liveQuery(live(GET(fn)), key)`. +- Deliver the reply through `live(async function*)`, yielding the whole reply so far and the current reply first on every connection. +- Render it in the browser with `createProjection`, or on the server as a live server component mounted with `dynamicComponent` through `liveQuery(fn, key)`. - Hold an optimistic row with `createOptimisticStore` inside an `action`, confirm it with `until` over the live thread's ids, and filter the row by the same ids. - A reconnect sends `Last-Event-ID`; an unchanged first value is skipped, and partial replay needs a log the source reads the header against. diff --git a/src/routes/reference/(3)solid-router/(4)data.mdx b/src/routes/reference/(3)solid-router/(4)data.mdx index ff06d09b7..d3abb75d3 100644 --- a/src/routes/reference/(3)solid-router/(4)data.mdx +++ b/src/routes/reference/(3)solid-router/(4)data.mdx @@ -102,7 +102,7 @@ const messages = createMemo(() => roomMessages(props.room)); - Consumers of one key share one connection. A late consumer receives the latest value first. The connection closes when the last consumer leaves. - A stream that has delivered a value and then fails with a network error or a 5xx status reconnects with exponential backoff. The latest value stays available meanwhile. - A failure before the first value, or a 4xx status, ends the channel and throws to consumers. -- When `fn` is a server function without a declared method, `liveQuery` wraps it as a GET server function. +- When `fn` is a server function, `liveQuery` declares it with `live()`, which implies `GET`, so the connection gets the transport's live lifecycle. A function already declared with `live()` passes through unchanged. - A call under preload intent connects the channel and holds it open for 5 seconds. - Mutations do not revalidate live keys. `revalidate(key)` with `force` reconnects the matching channels. - On the server, the document renders the first value. Hydration adopts it and reconnects.