Skip to content

Commit 14c454c

Browse files
ryansolidclaude
andauthored
docs: live() implies GET, liveQuery implies live() (#1666)
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. Claude-Session: https://claude.ai/code/session_01Fmb4aQtDJWoA8CUktR9QoZ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 6e9db79 commit 14c454c

4 files changed

Lines changed: 56 additions & 68 deletions

File tree

‎src/routes/(3)building-apps/(3)server-functions/(1)reads-and-live-data.mdx‎

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -125,22 +125,18 @@ The stock level for a product is one such question.
125125

126126
```ts
127127
// src/data/inventory.ts
128-
import { GET, live } from "@solidjs/web/server-functions";
129-
130-
export const stockLevel = live(
131-
GET(async function* (productId: string) {
132-
"use server";
133-
yield await inventory.count(productId);
134-
for await (const change of inventory.subscribe(productId)) {
135-
yield change.count;
136-
}
137-
})
138-
);
128+
import { live } from "@solidjs/web/server-functions";
129+
130+
export const stockLevel = live(async function* (productId: string) {
131+
"use server";
132+
yield await inventory.count(productId);
133+
for await (const change of inventory.subscribe(productId)) {
134+
yield change.count;
135+
}
136+
});
139137
```
140138

141-
Place `live()` outside `GET()`.
142-
`GET()` chooses the read transport, and `live()` wraps the call behavior around it, so it must be the outermost declaration.
143-
`live()` does not imply `GET()`; a live source without it streams over `POST`, which is fine when a cacheable URL is of no use.
139+
A live source is a read, so `live()` declares it `GET` itself; the connection uses the read transport without a separate `GET()`.
144140

145141
The source yields the current count first, every time it is invoked.
146142
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
267263
- A `GET()` read puts its arguments in the URL; keep secrets out of them and read identity from the request event.
268264
- Responses leave with `Cache-Control: no-store`; return `respond(value, { headers })` to opt a read into caching.
269265
- Return an async iterable to stream events over one connection; ending the iteration in the browser ends the server producer.
270-
- Declare a value that changes over time with `live(GET(fn))`, `live()` outermost, and yield the current value first on every invocation.
266+
- 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.
271267
- Read a live source through one memo and pass the value down; each call site that invokes the source opens its own connection.
272268
- Use `onstatus` to show a reconnect; put data freshness in the yielded value.
273269

‎src/routes/(3)building-apps/(3)server-functions/(4)metadata-and-transport.mdx‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,15 +57,15 @@ The `Avoid` version places any order for any caller; `requiresAuth` is a value o
5757

5858
Later `withMeta()` calls shallow-merge over earlier metadata.
5959
`GET()` uses the same channel for its HTTP method, and the two compose in either order.
60-
`live()` wraps the call behavior, so it goes outside both:
60+
`live()` wraps the call behavior and declares `GET` itself, so it goes outside `withMeta()` and needs no `GET()`:
6161

6262
```ts
6363
export const stockLevels = live(
6464
withMeta(
65-
GET(async function* () {
65+
async function* () {
6666
"use server";
6767
yield* inventory.levels();
68-
}),
68+
},
6969
{ channel: "inventory" }
7070
)
7171
);
@@ -224,7 +224,7 @@ Check the condition in the hook against the metadata on the reference, and move
224224

225225
- The defaults are a `POST` to `/_server/data/<id>` with the argument array as the body and `Cache-Control: no-store` on the response; change them only for a reason on this page.
226226
- Attach static facts to a declaration with `withMeta()`; the server does not read them, so enforce policy in the body.
227-
- Compose `withMeta()` and `GET()` in either order, and put `live()` outside both.
227+
- Compose `withMeta()` and `GET()` in either order, and put `live()`, which implies `GET()`, outside `withMeta()`.
228228
- Add session-dependent headers in one `prepareRequest` hook, configured once in the client entry before the first call.
229229
- Move the endpoint with the plugin's `serverFunctions.endpoint` option so client and server stay matched.
230230
- Pass `signal`, `keepalive`, or `priority` to one call through `invoke()`; anything longer-lived is refused with a pointer to its home.

‎src/routes/(3)building-apps/(3)server-functions/(7)durable-generation.mdx‎

Lines changed: 40 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -158,16 +158,14 @@ The live source yields the reply so far, whole, on every write:
158158
```ts
159159
// src/data/replies.ts
160160
import { getRequestEvent } from "@solidjs/web";
161-
import { GET, live } from "@solidjs/web/server-functions";
161+
import { live } from "@solidjs/web/server-functions";
162162
import * as replies from "../server/replies";
163163

164-
export const watchReply = live(
165-
GET(async function* (replyId: string) {
166-
"use server";
167-
const signal = getRequestEvent()?.request.signal;
168-
for await (const reply of replies.watch(replyId, signal)) yield reply;
169-
})
170-
);
164+
export const watchReply = live(async function* (replyId: string) {
165+
"use server";
166+
const signal = getRequestEvent()?.request.signal;
167+
for await (const reply of replies.watch(replyId, signal)) yield reply;
168+
});
171169
```
172170

173171
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
221219
`refresh(reply)` re-runs the projection, which opens a new connection; it does not touch the job.
222220

223221
Every call site that iterates a live source holds its own connection.
224-
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.
222+
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.
225223

226224
## Hold the bubble until the stream confirms
227225

@@ -288,55 +286,49 @@ The `chat` example in the Solid repository is this section's reference implement
288286
:::
289287

290288
When the markdown renderer, the highlighter, and the model client should stay on the server, the thread can be server markup that keeps changing.
291-
The server function returns a component instead of data, and `live()` holds the response open while it re-renders:
289+
The server function returns a component instead of data, and `liveQuery`, which declares it `live()`, holds the response open while it re-renders:
292290

293291
```tsx
294292
// src/routes/thread.tsx
295293
import { createMemo, onCleanup } from "solid-js";
296294
import { getRequestEvent } from "@solidjs/web";
297-
import { GET, live } from "@solidjs/web/server-functions";
298295
import type { Slot } from "@solidjs/web/frames";
299296
import { liveQuery } from "@solidjs/router";
300297
import { stopReply } from "../data/replies";
301298
import * as threads from "../server/threads";
302299

303300
type ComposerSlot = Slot<{ threadId: string; seen: string[] }>;
304301

305-
export const getThread = liveQuery(
306-
live(
307-
GET(async (id: string) => {
308-
"use server";
309-
return (props: { composer: ComposerSlot }) => {
310-
const gone = new AbortController();
311-
onCleanup(() => gone.abort());
312-
getRequestEvent()?.request.signal.addEventListener("abort", () =>
313-
gone.abort()
314-
);
315-
const view = createMemo(() => threads.watch(id, gone.signal));
316-
return (
317-
<article>
318-
{view().messages.map((message) => (
319-
<section data-id={message.id}>
320-
<div innerHTML={renderMarkdown(message.text)} />
321-
{message.status === "generating" && (
322-
<form action={stopReply.with(message.id)} method="post">
323-
<button type="submit">Stop</button>
324-
</form>
325-
)}
326-
</section>
327-
))}
328-
<props.composer
329-
$key={id}
330-
threadId={id}
331-
seen={view().messages.map((m) => m.id)}
332-
/>
333-
</article>
334-
);
335-
};
336-
})
337-
),
338-
"thread"
339-
);
302+
export const getThread = liveQuery(async (id: string) => {
303+
"use server";
304+
return (props: { composer: ComposerSlot }) => {
305+
const gone = new AbortController();
306+
onCleanup(() => gone.abort());
307+
getRequestEvent()?.request.signal.addEventListener("abort", () =>
308+
gone.abort()
309+
);
310+
const view = createMemo(() => threads.watch(id, gone.signal));
311+
return (
312+
<article>
313+
{view().messages.map((message) => (
314+
<section data-id={message.id}>
315+
<div innerHTML={renderMarkdown(message.text)} />
316+
{message.status === "generating" && (
317+
<form action={stopReply.with(message.id)} method="post">
318+
<button type="submit">Stop</button>
319+
</form>
320+
)}
321+
</section>
322+
))}
323+
<props.composer
324+
$key={id}
325+
threadId={id}
326+
seen={view().messages.map((m) => m.id)}
327+
/>
328+
</article>
329+
);
330+
};
331+
}, "thread");
340332
```
341333

342334
`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.
420412

421413
- 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.
422414
- Pass the job its own abort signal; the request's signal ends only the watcher.
423-
- Deliver the reply through `live(GET(async function*))`, yielding the whole reply so far and the current reply first on every connection.
424-
- 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)`.
415+
- Deliver the reply through `live(async function*)`, yielding the whole reply so far and the current reply first on every connection.
416+
- Render it in the browser with `createProjection`, or on the server as a live server component mounted with `dynamicComponent` through `liveQuery(fn, key)`.
425417
- 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.
426418
- A reconnect sends `Last-Event-ID`; an unchanged first value is skipped, and partial replay needs a log the source reads the header against.
427419

‎src/routes/reference/(3)solid-router/(4)data.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ const messages = createMemo(() => roomMessages(props.room));
102102
- Consumers of one key share one connection. A late consumer receives the latest value first. The connection closes when the last consumer leaves.
103103
- 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.
104104
- A failure before the first value, or a 4xx status, ends the channel and throws to consumers.
105-
- When `fn` is a server function without a declared method, `liveQuery` wraps it as a GET server function.
105+
- 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.
106106
- A call under preload intent connects the channel and holds it open for 5 seconds.
107107
- Mutations do not revalidate live keys. `revalidate(key)` with `force` reconnects the matching channels.
108108
- On the server, the document renders the first value. Hydration adopts it and reconnects.

0 commit comments

Comments
 (0)