diff --git a/examples/chat/README.md b/examples/chat/README.md index fa784aa3e..6f0e62166 100644 --- a/examples/chat/README.md +++ b/examples/chat/README.md @@ -80,7 +80,7 @@ fact about the thread, not something left hanging. ```tsx const getThread = liveQuery( - live(async (id: string) => { + async (id: string) => { "use server"; return (props: { composer: ComposerSlot; copyCode: CopySlot }) => { const view = createMemo(() => db.watchThread(id, gone.signal)); @@ -95,13 +95,13 @@ const getThread = liveQuery( ); }; - }), + }, "thread" ); ``` -`live` holds the response open and reconnects it if it dies. `liveQuery` -gives it a key and one connection per thread. +`liveQuery` declares the function `live`, which holds the response open and +reconnects it if it dies, and gives it a key and one connection per thread. Each `{…}` that reads `view()` is a piece of the page the server can send again by itself, and it sends one only when its HTML has changed. The thread diff --git a/examples/chat/package.json b/examples/chat/package.json index ea7ca58e7..0fdeff1ac 100644 --- a/examples/chat/package.json +++ b/examples/chat/package.json @@ -16,7 +16,7 @@ }, "dependencies": { "@anthropic-ai/sdk": "^0.133.0", - "@solidjs/router": "2.0.0-next.41", + "@solidjs/router": "2.0.0-next.42", "@solidjs/web": "workspace:*", "highlight.js": "^11.11.1", "marked": "^12.0.2", diff --git a/examples/chat/src/router.tsx b/examples/chat/src/router.tsx index 47afc4792..160edc100 100644 --- a/examples/chat/src/router.tsx +++ b/examples/chat/src/router.tsx @@ -5,7 +5,6 @@ // it. import { createMemo, onCleanup } from "solid-js"; import { getRequestEvent } from "@solidjs/web"; -import { live } from "@solidjs/web/server-functions"; import { createRouter, defineRoute, liveQuery } from "@solidjs/router"; import Home from "~/routes/home"; import Thread from "~/routes/thread"; @@ -16,34 +15,31 @@ import * as db from "~/server/db"; // changes without this tab doing anything — another tab sends a message, a // reply finishes, a thread is renamed or deleted. The router marks the link // to the page you are on. -export const getThreads = liveQuery( - live(async () => { - "use server"; - return () => { - const gone = new AbortController(); - onCleanup(() => gone.abort()); - getRequestEvent()?.request.signal.addEventListener("abort", () => gone.abort(), { - once: true - }); - const threads = createMemo(() => db.watchThreads(gone.signal)); - return ( - - ); - }; - }), - "threads" -); +export const getThreads = liveQuery(async () => { + "use server"; + return () => { + const gone = new AbortController(); + onCleanup(() => gone.abort()); + getRequestEvent()?.request.signal.addEventListener("abort", () => gone.abort(), { + once: true + }); + const threads = createMemo(() => db.watchThreads(gone.signal)); + return ( + + ); + }; +}, "threads"); export const Router = createRouter({ routes: [ diff --git a/examples/chat/src/routes/thread.tsx b/examples/chat/src/routes/thread.tsx index bef9f0aa3..3f56174ec 100644 --- a/examples/chat/src/routes/thread.tsx +++ b/examples/chat/src/routes/thread.tsx @@ -13,7 +13,6 @@ // code block's Copy button, a client component in a slot in that block. import { createMemo, For, Loading, onCleanup } from "solid-js"; import { dynamicComponent, getRequestEvent, redirect } from "@solidjs/web"; -import { live } from "@solidjs/web/server-functions"; import type { Slot } from "@solidjs/web/frames"; import { formAction, liveQuery, type RouteSectionProps } from "@solidjs/router"; import Composer from "~/components/composer"; @@ -65,53 +64,51 @@ const deleteThread = formAction(async (id: string) => { return redirect("/"); }, "deleteThread"); -// `live` holds the response open and reconnects it if it dies; `liveQuery` -// gives it a key and one connection per thread, and tells the router that -// this address is kept fresh by its own stream. -const getThread = liveQuery( - live(async (id: string) => { - "use server"; - return (props: { composer: ComposerSlot; copyCode: CopySlot }) => { - // This render lives as long as its response. When the tab goes away - // the watcher below is told at once. - const gone = new AbortController(); - onCleanup(() => gone.abort()); - getRequestEvent()?.request.signal.addEventListener("abort", () => gone.abort(), { - once: true - }); +// `liveQuery` declares it `live`, which holds the response open and +// reconnects it if it dies; it also gives it a key and one connection per +// thread, and tells the router that this address is kept fresh by its own +// stream. +const getThread = liveQuery(async (id: string) => { + "use server"; + return (props: { composer: ComposerSlot; copyCode: CopySlot }) => { + // This render lives as long as its response. When the tab goes away + // the watcher below is told at once. + const gone = new AbortController(); + onCleanup(() => gone.abort()); + getRequestEvent()?.request.signal.addEventListener("abort", () => gone.abort(), { + once: true + }); - // The thread as it is stored, again after every write to it. - const view = createMemo(() => db.watchThread(id, gone.signal)); + // The thread as it is stored, again after every write to it. + const view = createMemo(() => db.watchThread(id, gone.signal)); - return ( -
-
- {headMarkup(view().thread)} -

Answered by {model.modelName()}

-
-
    - {/* A row the server has already rendered is kept while its + return ( +
    +
    + {headMarkup(view().thread)} +

    Answered by {model.modelName()}

    +
    +
      + {/* A row the server has already rendered is kept while its message is unchanged, so a streaming reply doesn't render the whole history again on every write (#4056). */} - m.status !== "generating")}> - {message => messageMarkup(message, props.copyCode)} - - {messagesMarkup(view().messages, true, props.copyCode)} -
    - {/* Keyed by thread: going to another conversation is a new + m.status !== "generating")}> + {message => messageMarkup(message, props.copyCode)} + + {messagesMarkup(view().messages, true, props.copyCode)} +
+ {/* Keyed by thread: going to another conversation is a new composer, so a half-typed message stays with its thread. */} - m.id)} - busy={view().messages.some(m => m.status === "generating")} - /> -
- ); - }; - }), - "thread" -); + m.id)} + busy={view().messages.some(m => m.status === "generating")} + /> + + ); + }; +}, "thread"); // The title is its own editor: a form around a field, so renaming is typing // and pressing Enter, with or without JavaScript. diff --git a/examples/chat/src/server/model.ts b/examples/chat/src/server/model.ts index d942ed7a3..a440ee579 100644 --- a/examples/chat/src/server/model.ts +++ b/examples/chat/src/server/model.ts @@ -44,14 +44,16 @@ This thread is one. It renders on the server, from the messages the server has s \`\`\`jsx const getThread = liveQuery( - live(async (id) => { + async (id) => { "use server"; return props => ; - }), + }, "thread" ); \`\`\` +\`liveQuery\` declares it \`live\`, so the response stays open and the server sends the parts of the thread that change. + The markdown parser and the syntax highlighter that produced this reply never reach the browser. Only what they produce does.` } ], diff --git a/examples/hackernews-spa/package.json b/examples/hackernews-spa/package.json index c8d1c4314..14f481f59 100644 --- a/examples/hackernews-spa/package.json +++ b/examples/hackernews-spa/package.json @@ -13,7 +13,7 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@solidjs/router": "2.0.0-next.41", + "@solidjs/router": "2.0.0-next.42", "@solidjs/web": "workspace:*", "solid-js": "workspace:*" }, diff --git a/examples/hackernews/package.json b/examples/hackernews/package.json index 93d201b8f..3fbc47799 100644 --- a/examples/hackernews/package.json +++ b/examples/hackernews/package.json @@ -13,7 +13,7 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@solidjs/router": "2.0.0-next.41", + "@solidjs/router": "2.0.0-next.42", "@solidjs/web": "workspace:*", "solid-js": "workspace:*" }, diff --git a/examples/notes/package.json b/examples/notes/package.json index 3a432ecea..4d53137b3 100644 --- a/examples/notes/package.json +++ b/examples/notes/package.json @@ -13,7 +13,7 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@solidjs/router": "2.0.0-next.41", + "@solidjs/router": "2.0.0-next.42", "@solidjs/web": "workspace:*", "date-fns": "^3.6.0", "marked": "^12.0.2", diff --git a/examples/server-dbmon/package.json b/examples/server-dbmon/package.json index b0507f1ac..a96d22038 100644 --- a/examples/server-dbmon/package.json +++ b/examples/server-dbmon/package.json @@ -13,7 +13,7 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@solidjs/router": "2.0.0-next.41", + "@solidjs/router": "2.0.0-next.42", "@solidjs/web": "workspace:*", "solid-js": "workspace:*" }, diff --git a/examples/todos-server/package.json b/examples/todos-server/package.json index 617e191ed..fb2c4323b 100644 --- a/examples/todos-server/package.json +++ b/examples/todos-server/package.json @@ -13,7 +13,7 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@solidjs/router": "2.0.0-next.41", + "@solidjs/router": "2.0.0-next.42", "@solidjs/web": "workspace:*", "solid-js": "workspace:*", "unstorage": "^1.17.5" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 96821ef51..9e207cb5e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -137,8 +137,8 @@ importers: specifier: ^0.133.0 version: 0.133.0 '@solidjs/router': - specifier: 2.0.0-next.41 - version: 2.0.0-next.41(@solidjs/web@packages+web)(solid-js@packages+solid) + specifier: 2.0.0-next.42 + version: 2.0.0-next.42(@solidjs/web@packages+web)(solid-js@packages+solid) '@solidjs/web': specifier: workspace:* version: link:../../packages/web @@ -218,8 +218,8 @@ importers: examples/hackernews: dependencies: '@solidjs/router': - specifier: 2.0.0-next.41 - version: 2.0.0-next.41(@solidjs/web@packages+web)(solid-js@packages+solid) + specifier: 2.0.0-next.42 + version: 2.0.0-next.42(@solidjs/web@packages+web)(solid-js@packages+solid) '@solidjs/web': specifier: workspace:* version: link:../../packages/web @@ -237,8 +237,8 @@ importers: examples/hackernews-spa: dependencies: '@solidjs/router': - specifier: 2.0.0-next.41 - version: 2.0.0-next.41(@solidjs/web@packages+web)(solid-js@packages+solid) + specifier: 2.0.0-next.42 + version: 2.0.0-next.42(@solidjs/web@packages+web)(solid-js@packages+solid) '@solidjs/web': specifier: workspace:* version: link:../../packages/web @@ -277,8 +277,8 @@ importers: examples/notes: dependencies: '@solidjs/router': - specifier: 2.0.0-next.41 - version: 2.0.0-next.41(@solidjs/web@packages+web)(solid-js@packages+solid) + specifier: 2.0.0-next.42 + version: 2.0.0-next.42(@solidjs/web@packages+web)(solid-js@packages+solid) '@solidjs/web': specifier: workspace:* version: link:../../packages/web @@ -329,8 +329,8 @@ importers: examples/server-dbmon: dependencies: '@solidjs/router': - specifier: 2.0.0-next.41 - version: 2.0.0-next.41(@solidjs/web@packages+web)(solid-js@packages+solid) + specifier: 2.0.0-next.42 + version: 2.0.0-next.42(@solidjs/web@packages+web)(solid-js@packages+solid) '@solidjs/web': specifier: workspace:* version: link:../../packages/web @@ -393,8 +393,8 @@ importers: examples/todos-server: dependencies: '@solidjs/router': - specifier: 2.0.0-next.41 - version: 2.0.0-next.41(@solidjs/web@packages+web)(solid-js@packages+solid) + specifier: 2.0.0-next.42 + version: 2.0.0-next.42(@solidjs/web@packages+web)(solid-js@packages+solid) '@solidjs/web': specifier: workspace:* version: link:../../packages/web @@ -2435,8 +2435,8 @@ packages: cpu: [x64] os: [win32] - '@solidjs/router@2.0.0-next.41': - resolution: {integrity: sha512-wCnhq37p4dpTYuy2Zz8nlf9Si/t4cMXUiMY0r7MxzHSH2c4jUiSrA8LkSdif6swwAIT/dOP7Pbv4hM1qY5ZOuA==} + '@solidjs/router@2.0.0-next.42': + resolution: {integrity: sha512-UMeBFTLk5DzIi4Kj0lmoyB0JmGXDB1gYVPwKXkyaHJccjXX6LbFOJkNoJCRzm7K88/NxR8rh10fjbJO6vzfxXA==} peerDependencies: '@solidjs/web': workspace:* filesystem-routing: '>=0.4.0' @@ -6119,7 +6119,7 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.59.0': optional: true - '@solidjs/router@2.0.0-next.41(@solidjs/web@packages+web)(solid-js@packages+solid)': + '@solidjs/router@2.0.0-next.42(@solidjs/web@packages+web)(solid-js@packages+solid)': dependencies: '@solidjs/web': link:packages/web solid-js: link:packages/solid