From e60c60dce99c29b0895df24bbc6e133a70371cd4 Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot Date: Fri, 25 Jul 2025 21:59:28 +0000 Subject: [PATCH] sync docs --- .../20-core-concepts/60-remote-functions.md | 471 ++++++++++++++++++ .../content/docs/kit/30-advanced/20-hooks.md | 31 ++ .../docs/kit/98-reference/10-@sveltejs-kit.md | 346 ++++++++++++- .../kit/98-reference/20-$app-navigation.md | 22 + .../docs/kit/98-reference/20-$app-server.md | 251 +++++++++- .../docs/kit/98-reference/50-configuration.md | 34 ++ 6 files changed, 1148 insertions(+), 7 deletions(-) create mode 100644 apps/svelte.dev/content/docs/kit/20-core-concepts/60-remote-functions.md diff --git a/apps/svelte.dev/content/docs/kit/20-core-concepts/60-remote-functions.md b/apps/svelte.dev/content/docs/kit/20-core-concepts/60-remote-functions.md new file mode 100644 index 0000000000..0e744641a7 --- /dev/null +++ b/apps/svelte.dev/content/docs/kit/20-core-concepts/60-remote-functions.md @@ -0,0 +1,471 @@ +--- +NOTE: do not edit this file, it is generated in apps/svelte.dev/scripts/sync-docs/index.ts +title: Remote Functions +--- + +Remote functions are a new concept in SvelteKit since version 2.27 that allow you to declare functions inside a `.remote.ts` file, import them inside Svelte components and call them like regular functions. On the server they work like regular functions (and can access environment variables and database clients and so on), while on the client they become wrappers around `fetch`. Combined with Svelte's [experimental async feature](/docs/svelte/await-expressions) it allows you to load and manipulate date directly inside your components. If you're familiar with RPC and 'server functions', this is basically our take on the concept. + +This feature is currently experimental, and you must opt in by adding the `kit.experimental.remoteFunctions` option in your `svelte.config.js`: + +```js +/// file: svelte.config.js +export default { + kit: { + experimental: { + remoteFunctions: true + } + } +}; +``` + +## Overview + +Remote functions are declared inside a `.remote.ts` file. You can import them inside Svelte components and call them like regular async functions. On the server you import them directly; on the client, the module is transformed into a collection of functions that request data from the server. + +As of now there exist four types of remote function: `query`, `form`, `command` and `prerender`. + +## query + +Queries are for reading dynamic data from the server. They can have zero or one arguments. If they have an argument, you're encouraged to validate the input via a schema which you can create with libraries like `Zod` (more details in the upcoming [Validation](#Validation) section). The argument is serialized with [devalue](https://github.com/rich-harris/devalue), which handles types like `Date` and `Map` in addition to JSON, and takes the [transport hook](https://svelte.dev/docs/kit/hooks#Universal-hooks-transport) into account. + +```ts +/// file: likes.remote.ts +import z from 'zod'; +import { query } from '$app/server'; +import * as db from '$lib/server/db'; + +export const getLikes = query(z.string(), async (id) => { + const [row] = await db.sql`select likes from item where id = ${id}`; + return row.likes; +}); +``` + +When called during server-rendering, the result is serialized into the HTML payload so that the data isn't requested again during hydration. + +```svelte + + + +

likes: {await getLikes(item.id)}

+``` + +> Async SSR isn’t yet implemented in Svelte, which means this will only load in the client for now. Once SSR is supported, this will be able to hydrate correctly, not refetching data + +Queries are *thenable*, meaning they can be awaited. But they're not just promises, they also provide properties like `status` and `current` (which contains the most recent value, but is initially `undefined`) and methods like `withOverride(...)` (see the section on [optimistic UI](#Optimistic-updates), below) and `refresh()`, which fetches new data from the server. We’ll see an example of that in a moment. + +Query objects are cached in memory for as long as they are actively used, using the serialized arguments as a key — in other words `myQuery(id) === myQuery(id)`. Refreshing or overriding a query will update every occurrence of it on the page. We use Svelte's reactivity system to intelligently clear the cache to avoid memory leaks. + +## form + +Forms are the preferred way to write data to the server. Use the remote `form` function to achieve this: + +```ts +/// file: likes.remote.ts +import z from 'zod'; +import { query, form } from '$app/server'; +import * as db from '$lib/server/db'; + +export const getLikes = query(z.string(), async (id) => {/*...*/}); + +export const addLike = form(async (data: FormData) => { + const id = data.get('id') as string; + + await sql` + update item + set likes = likes + 1 + where id = ${id} + `; + + // we can return arbitrary data from a form function + return { success: true }; +}); +``` + +A form object such as `addLike` has enumerable properties — `method`, `action` and `onsubmit` — that can be spread onto a `
` element. This allows the form to work without JavaScript (i.e. it submits data and reloads the page), but it will also automatically progressively enhance the form, submitting data *without* reloading the entire page. + +```svelte + + + + + + +
+ +

likes: {await getLikes(item.id)}

+``` + +By default, all queries used on the page (along with any `load` functions) are automatically refreshed following a form submission, meaning `getLikes(...)` in the example above will show updated data. + +In addition to the enumerable properties, remote forms (`addLike` in our example) have non-enumerable properties. One of them is `result` which contains the return value. Use it to display something in response to the submission. + +```svelte + + + ++++{#if addLike.result?.success} +

success!

+{/if}+++ + +
+ + +
+ +

likes: {await getLikes(item.id)}

+``` + +### enhance + +The remote form property `enhance` allows us to customize how the form is progressively enhanced. We can use this to indicate that *only* `getLikes(...)` should be refreshed and through that also enable *single-flight mutations* — meaning that the updated data for `getLikes(...)` is sent back from the server along with the form result. Additionally we provide nicer behaviour in the case that the submission fails (by default, an error page will be shown): + +```svelte + + + +{#if addLike.result?.success} +

success!

+{/if} + +
{ + try { + // by passing queries to `.updates(...)` we will prevent a global refresh and the + // refreshed data is sent together with the submission response (single flight mutation) + await submit().updates(getLikes(item.id)); + } catch (error) { + // instead of showing an error page, + // present a demure notification + showToast(error.message); + } +}}> + + +
+ +

likes: {await getLikes(item.id)}

+``` + +> `form.result` need not indicate success — it can also contain validation errors along with any data that should repopulate the form on page reload, [much as happens today with form actions](form-actions). + +Alternatively we can also enable single-flight mutations by adding the `refresh` call to the server, which means _all_ calls to `addLike` will leverage single-flight mutations compared to only those who use `submit.updates(...)`: + +```ts +/// file: likes.remote.ts +import { query, form } from '$app/server'; +import * as db from '$lib/server/db'; + +export const getLikes = query(async (id: string) => { + const [row] = await sql`select likes from item where id = ${id}`; + return row.likes; +}); + +export const addLike = form(async (data: FormData) => { + const id = data.get('id') as string; + + await sql` + update item + set likes = likes + 1 + where id = ${id} + `; + ++++ await getLikes(id).refresh();+++ + + // we can return arbitrary data from a form function + return { success: true }; +}); +``` + +### Forms in a list + +Sometimes you may have form submissions of the same type in a list, and each form action should be independently managed. Use the `for` method to create separate instances of the same form submission. In the following example, each todo item gets a separate `toggleTodo` form instance, which means you can toggle many in quick succession, with the submission results staying independent of each other: + +```svelte + + + +{#each await getTodos() as todo (todo.id)} + {@const toggle = toggleTodo.for(todo.id)} +
+ + + + {#if toggle.result?.error} + Something went wrong + {/if} +
+{/each} +``` + +### formAction + +Forms allow you to have more than one button that kicks up a form submission. The non-primary buttons must have a `formaction` property in that case. The remote form provides a `formAction` property that allows you to do just that: + +```svelte + + + +
+ + + + +
+``` + +## command + +For cases where serving no-JS users via the remote `form` function is impractical or undesirable, `command` offers an alternative way to write data to the server. + +```ts +/// file: likes.remote.ts +import z from 'zod'; +import { query, command } from '$app/server'; +import * as db from '$lib/server/db'; + +export const getLikes = query(z.string(), async (id) => { + const [row] = await sql`select likes from item where id = ${id}`; + return row.likes; +}); + +export const addLike = command(z.string(), async (id) => { + await sql` + update item + set likes = likes + 1 + where id = ${id} + `; + + getLikes(id).refresh(); + + // we can return arbitrary data from a command + return { success: true }; +}); +``` + +Now simply call `addLike`, from (for example) an event handler: + +```svelte + + + + + +

likes: {await getLikes(item.id)}

+``` + +> Commands cannot be called during render. + +As with forms, we can refresh associated queries on the server during the command or via `.updates(...)` on the client for a single-flight mutation, otherwise all queries will automatically be refreshed. + +## prerender + +This function is like `query` except that it will be invoked at build time to prerender the result. Use this for data that changes at most once per redeployment. + +```ts +/// file: blog.remote.ts +import z from 'zod'; +import { prerender } from '$app/server'; + +export const getBlogPost = prerender(z.string(), (slug) => { + // ... +}); +``` + +You can use `prerender` functions on pages that are otherwise dynamic, allowing for partial prerendering of your data. This results in very fast navigation, since prerendered data can live on a CDN along with your other static assets, and will be put into the user's browser cache using the [Cache API](https://developer.mozilla.org/en-US/docs/Web/API/Cache) which even survives page reloads. + +> When the entire page has `export const prerender = true`, you cannot use queries, as they are dynamic. + +Prerendering is automatic, driven by SvelteKit's crawler, but you can also provide an `entries` option to control what gets prerendered, in case some pages cannot be reached by the crawler: + +```ts +/// file: blog.remote.ts +import z from 'zod'; +import { prerender } from '$app/server'; + +export const getBlogPost = prerender( + z.string(), + (slug) => { + // ... + }, + { + entries: () => ['first-post', 'second-post', 'third-post'] + } +); +``` + +If the function is called at runtime with arguments that were not prerendered it will error by default, as the code will not have been included in the server bundle. You can set `dynamic: true` to change this behaviour: + +```ts +/// file: blog.remote.ts +import z from 'zod'; +import { prerender } from '$app/server'; + +export const getBlogPost = prerender( + z.string(), + (slug) => { + // ... + }, + { ++++ dynamic: true,+++ + entries: () => ['first-post', 'second-post', 'third-post'] + } +); +``` + +## Optimistic updates + +Queries have an `withOverride` method, which is useful for optimistic updates. It receives a function that transforms the query, and must be passed to `submit().updates(...)` or `myCommand.updates(...)`: + +```svelte + + + + + +

likes: {await getLikes(item.id)}

+``` + +> You can also do `const likes = $derived(getLikes(item.id))` in your ` + + + +``` +Use the `updates` method to specify which queries to update in response to the command. +```svelte + + + + + + +``` + +
+ +```dts +type RemoteCommand = (arg: Input) => Promise< + Awaited +> & { + updates: ( + ...queries: Array< + | RemoteQuery + | ReturnType['withOverride']> + > + ) => Promise>; +}; +``` + +
+ +## RemoteForm + +The return value of a remote `form` function. +Spread it onto a `
` element to connect the element to the remote function. + +```svelte + + + + + +
+``` +Use the `enhance` method to influence what happens when the form is submitted. +```svelte + + +
{ + // `data` is an instance of FormData (https://developer.mozilla.org/en-US/docs/Web/API/FormData) + const text = data.get('text'); + const todo = { text, done: false }; + + // `updates` and `withOverride` enable optimistic UI updates + await submit().updates( + getTodos().withOverride((todos) => [...todos, todo]) + ); +})}> + + +
+ + +``` + +
+ +```dts +type RemoteForm = { + method: 'POST'; + /** The URL to send the form to. */ + action: string; + /** Event handler that intercepts the form submission on the client to prevent a full page reload */ + onsubmit: (event: SubmitEvent) => void; + /** Use the `enhance` method to influence what happens when the form is submitted. */ + enhance: ( + callback: (opts: { + form: HTMLFormElement; + data: FormData; + submit: () => Promise & { + updates: ( + ...queries: Array< + | RemoteQuery + | ReturnType['withOverride']> + > + ) => Promise; + }; + }) => void + ) => { + method: 'POST'; + action: string; + onsubmit: (event: SubmitEvent) => void; + }; + /** + * Create an instance of the form for the given key. + * The key is stringified and used for deduplication to potentially reuse existing instances. + * Useful when you have multiple forms that use the same remote form action, for example in a loop. + * ```svelte + * {#each todos as todo} + * {@const todoForm = updateTodo.for(todo.id)} + *
+ * {#if todoForm.result?.invalid}

Invalid data

{/if} + * ... + *
+ * {/each} + * ``` + */ + for: ( + key: string | number | boolean + ) => Omit, 'for'>; + /** The result of the form submission */ + get result(): Result | undefined; + /** When there's an error during form submission, it appears on this property */ + get error(): App.Error | undefined; + /** Spread this onto a `
+ +## RemotePrerenderFunction + +The return value of a remote `prerender` function. +Call it with the input arguments to retrieve the value. +On the server, this will directly call the underlying function. +On the client, this will `fetch` data from the server. + +
+ +```dts +type RemotePrerenderFunction = ( + arg: Input +) => RemoteResource; +``` + +
+ +## RemoteQuery + +
+ +```dts +type RemoteQuery = RemoteResource & { + /** + * On the client, this function will re-fetch the query from the server. + * + * On the server, this can be called in the context of a `command` or `form` remote function. It will then + * transport the updated data to the client along with the response, if the action was successful. + */ + refresh: () => Promise; + /** + * Temporarily override the value of a query. Useful for optimistic UI updates. + * `withOverride` expects a function that takes the current value and returns the new value. + * In other words this works like `override`, but is specifically for use as part of the `updates` method of a remote `command` or `form` submit + * in order to coordinate query refreshes and override releases at once, without causing e.g. flickering in the UI. + * + * ```svelte + * + * + *
{ + * await submit().updates(todos.withOverride((todos) => [...todos, { text: data.get('text') }])); + * }}> + * + * + *
+ * ``` + */ + withOverride: ( + update: (current: Awaited) => Awaited + ) => { + _key: string; + release: () => void; + }; +}; +``` + +
+ +## RemoteQueryFunction + +The return value of a remote `query` function. +Call it with the input arguments to retrieve the value. +On the server, this will directly call the underlying function. +On the client, this will `fetch` data from the server. +When the query is called in a reactive context on the client, it will update its dependencies with a new value whenever `refresh()` or `override()` are called. + +
+ +```dts +type RemoteQueryFunction = ( + arg: Input +) => RemoteQuery; +``` + +
+ +## RemoteResource + +
+ +```dts +type RemoteResource = Promise> & { + /** The error in case the query fails. Most often this is a [`HttpError`](https://svelte.dev/docs/kit/@sveltejs-kit#HttpError) but it isn't guaranteed to be. */ + get error(): any; + /** `true` before the first result is available and during refreshes */ + get loading(): boolean; +} & ( + | { + /** The current value of the query. Undefined as long as there's no value yet */ + get current(): undefined; + ready: false; + } + | { + /** The current value of the query. Undefined as long as there's no value yet */ + get current(): Awaited; + ready: true; + } + ); +``` + +
+ ## RequestEvent
@@ -2115,6 +2423,20 @@ isSubRequest: boolean; `true` for `+server.js` calls coming from SvelteKit without the overhead of actually making an HTTP request. This happens when you make same-origin `fetch` requests on the server. +
+ + +
+ +```dts +isRemoteRequest: boolean; +``` + +
+ +`true` if the request comes from the client via a remote function. The `url` property will be stripped of the internal information +related to the data request in this case. Use this property instead if the distinction is important to you. +
@@ -2389,6 +2711,18 @@ nodes: SSRNodeLoader[];
+```dts +remotes: Record Promise>; +``` + +
+ +hashed filename -> import to that file + +
+
+
+ ```dts routes: SSRRoute[]; ``` diff --git a/apps/svelte.dev/content/docs/kit/98-reference/20-$app-navigation.md b/apps/svelte.dev/content/docs/kit/98-reference/20-$app-navigation.md index ac29666d00..57163c4dcc 100644 --- a/apps/svelte.dev/content/docs/kit/98-reference/20-$app-navigation.md +++ b/apps/svelte.dev/content/docs/kit/98-reference/20-$app-navigation.md @@ -18,6 +18,7 @@ import { preloadCode, preloadData, pushState, + refreshAll, replaceState } from '$app/navigation'; ``` @@ -248,6 +249,27 @@ function pushState( +## refreshAll + +Causes all currently active remote functions to refresh, and all `load` functions belonging to the currently active page to re-run (unless disabled via the option argument). +Returns a `Promise` that resolves when the page is subsequently updated. + +
+ +```dts +function refreshAll({ + includeLoadFunctions +}?: + | { + includeLoadFunctions?: boolean; + } + | undefined): Promise; +``` + +
+ + + ## replaceState Programmatically replace the current history entry with the given `page.state`. To use the current URL, you can pass `''` as the first argument. Used for [shallow routing](/docs/kit/shallow-routing). diff --git a/apps/svelte.dev/content/docs/kit/98-reference/20-$app-server.md b/apps/svelte.dev/content/docs/kit/98-reference/20-$app-server.md index 6307f34fe5..12f137007c 100644 --- a/apps/svelte.dev/content/docs/kit/98-reference/20-$app-server.md +++ b/apps/svelte.dev/content/docs/kit/98-reference/20-$app-server.md @@ -7,9 +7,120 @@ title: $app/server ```js // @noErrors -import { getRequestEvent, read } from '$app/server'; +import { + command, + form, + getRequestEvent, + prerender, + query, + read +} from '$app/server'; ``` +## command + +Creates a remote command. The given function is invoked directly on the server and via a fetch call on the client. + +```ts +import { blogPosts } from '$lib/server/db'; + +export interface BlogPost { + id: string; + title: string; + content: string; +} + +export const like = command((postId: string) => { + blogPosts.get(postId).like(); +}); +``` + +```svelte + + +

{post.title}

+

{post.content}

+ +``` + +
+ +```dts +function command( + fn: () => Output +): RemoteCommand; +``` + +
+ +
+ +```dts +function command( + validate: 'unchecked', + fn: (arg: Input) => Output +): RemoteCommand; +``` + +
+ +
+ +```dts +function command( + validate: Schema, + fn: (arg: StandardSchemaV1.InferOutput) => Output +): RemoteCommand< + StandardSchemaV1.InferOutput, + Output +>; +``` + +
+ + + +## form + +Creates a form action. The passed function will be called when the form is submitted. +Returns an object that can be spread onto a form element to connect it to the function. +```ts +import * as db from '$lib/server/db'; + +export const createPost = form((formData) => { + const title = formData.get('title'); + const content = formData.get('content'); + return db.createPost({ title, content }); +}); +``` +```svelte + + +
+ +