diff --git a/apps/svelte.dev/content/docs/kit/30-advanced/20-hooks.md b/apps/svelte.dev/content/docs/kit/30-advanced/20-hooks.md index 8b2f95cee..053067274 100644 --- a/apps/svelte.dev/content/docs/kit/30-advanced/20-hooks.md +++ b/apps/svelte.dev/content/docs/kit/30-advanced/20-hooks.md @@ -144,6 +144,37 @@ export async function handleFetch({ event, request, fetch }) { } ``` +### handleValidationError + +This hook is called when a remote function is called with an argument that does not match its schema. It must return an object matching the shape of [`App.Error`](types#Error). + +Say you have a remote function that expects a string as its argument ... + +```js +/// file: todos.remote.js +import z from 'zod'; +import { query } from '$app/server'; + +export getTodo = query(z.string(), (id) => { + // implementation... +}); +``` + +...but it is called with something that doesn't match the schema — such as a number (e.g `await getTodos(1)`) — then validation will fail, the server will respond with a [400 status code](https://http.dog/400), and the function will throw with the message 'Bad Request'. + +To customise this message and add additional properties to the error object, implement `handleValidationError`: + +```js +/// file: src/hooks.server.js +import z from 'zod'; + +/** @type {import('@sveltejs/kit').HandleValidationError} */ +export const handleValidationError = ({ issues }) => { + // @ts-expect-error The types are too strict and disallow this but it's perfectly valid + return { message: 'Schema Error', validationErrors: z.treeifyError({ issues })}; +} +``` + ## Shared hooks The following can be added to `src/hooks.server.js` _and_ `src/hooks.client.js`: diff --git a/apps/svelte.dev/content/docs/kit/98-reference/10-@sveltejs-kit.md b/apps/svelte.dev/content/docs/kit/98-reference/10-@sveltejs-kit.md index 943cc0e40..534e482d5 100644 --- a/apps/svelte.dev/content/docs/kit/98-reference/10-@sveltejs-kit.md +++ b/apps/svelte.dev/content/docs/kit/98-reference/10-@sveltejs-kit.md @@ -118,9 +118,10 @@ function fail(status: number): ActionFailure;
```dts -function fail< - T extends Record | undefined = undefined ->(status: number, data: T): ActionFailure; +function fail( + status: number, + data: T +): ActionFailure; ```
@@ -306,9 +307,7 @@ type Action<
```dts -interface ActionFailure< - T extends Record | undefined = undefined -> {/*…*/} +interface ActionFailure {/*…*/} ```
@@ -1144,6 +1143,27 @@ type HandleServerError = (input: {
+## HandleValidationError + +The server-side [`handleValidationError`](/docs/kit/hooks#Server-hooks-handleValidationError) hook runs when schema validation fails in a remote function. + +If schema validation fails in a remote function, this function will be called with the validation issues and the event. +This function is expected return an object shape that matches `App.Error`. + +
+ +```dts +type HandleValidationError< + Issue extends + StandardSchemaV1.Issue = StandardSchemaV1.Issue +> = (input: { + issues: Issue[]; + event: RequestEvent; +}) => MaybePromise; +``` + +
+ ## HttpError The object returned by the [`error`](/docs/kit/@sveltejs-kit#error) function. @@ -1915,6 +1935,311 @@ The location to redirect to.
+## RemoteCommand + +The return value of a remote `command` function. +Call it with the input arguments to execute the command. + +Note: Prefer remote `form` functions when possible, as they +work without JavaScript enabled. + +```svelte + + + + +``` +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< + | ReturnType> + | ReturnType< + ReturnType>['withOverride'] + > + > + ) => Promise>; +}; +``` + +
+ +## RemoteFormAction + +The return value of a remote `form` function. +Spread it onto a `
` element to connect the form with the remote form action. +```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 RemoteFormAction = (( + data: FormData +) => Promise) & { + 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< + | ReturnType> + | ReturnType< + ReturnType< + RemoteQuery + >['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(): Success | Failure | undefined; + /** When there's an error during form submission, it appears on this property */ + get error(): App.Error | undefined; + /** Spread this onto a button or input of type submit */ + formAction: { + type: 'submit'; + formaction: string; + onclick: (event: Event) => 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< + | ReturnType> + | ReturnType< + ReturnType< + RemoteQuery + >['withOverride'] + > + > + ) => Promise; + }; + }) => void + ) => { + type: 'submit'; + formaction: string; + onclick: (event: Event) => void; + }; + }; +}; +``` + +
+ +## RemoteQuery + +The return value of a remote `query` or `prerender` function. +Call it with the input arguments to retrieve the value. +On the server, this will directly call through to the underlying function. +On the client, this will do a fetch to the server to retrieve the value. +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 RemoteQuery = (arg: Input) => Promise< + Awaited +> & { + /** 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; + /** + * 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 outside of a `command` or `form` remote function (for those, use `withOverride`). + * `override` expects a function that takes the current value and returns the new value. It returns a function that will release the override. + * Overrides are applied on new values, too, until they are released. + * + * ```svelte + * + * + *

Select items to remove

+ * + *
    + * {#each list as item} + *
  • {item.text}
  • + * + * {/each} + *
+ * + * + * + * + * ``` + * + * Can only be called on the client. + */ + override: ( + update: (current: Awaited) => Awaited + ) => () => void; + /** + * 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; + }; +} & ( + | { + /** The current value of the query. Undefined as long as there's no value yet */ + get current(): undefined; + status: 'loading'; + } + | { + /** The current value of the query. Undefined as long as there's no value yet */ + get current(): Awaited; + status: 'success' | 'reloading'; + } + | { + /** The current value of the query. Undefined as long as there's no value yet */ + get current(): Awaited | undefined; + status: 'error'; + } + ); +``` + +
+ ## RequestEvent
@@ -2123,6 +2448,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. +
@@ -2398,6 +2737,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 ac29666d0..57163c4dc 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 13c45a05d..ec129c1d0 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 + + +
+ +