Fonctions distantes
Disponible depuis la version 2.27
Les fonctions distantes (remote functions) sont un outil pour communiquer de manière typée entre le client et le serveur. Elles peuvent être appelées depuis n’importe où dans votre application, mais sont toujours exécutées sur le serveur, ce qui signifie qu’elles peuvent accéder de manière sécurisée aux modules réservés au serveur contenant des choses comme des variables d’environnement ou des clients de base de données.
Combinées avec le support expérimental de Svelte pour await
,
elles vous permettent de charger et de manipuler les données directement au sein de vos composants.
Cette fonctionnalité est actuellement expérimentale, ce qui signifie qu’elle contient très
certainement des bugs, et peut être modifiée à tout moment. Vous devez l’activer en ajouter l’option
kit.experimental.remoteFunctions
dans votre fichier svelte.config.js
:
export default {
kit: {
experimental: {
remoteFunctions: boolean;
};
}
kit: {
experimental: {
remoteFunctions: boolean;
}
experimental: {
remoteFunctions: boolean
remoteFunctions: true
}
}
};
Aperçu
Les fonctions distantes (remote) sont exportées depuis un fichier .remote.js
ou .remote.ts
, et
sont disponibles en quatre versions : query
, form
, command
, et prerender
. Sur le client, les
fonctions exportées sont transformées en wrappers autour de fetch
qui invoquent leur contrepartie
sur le serveur en utilisant un endpoint HTTP généré. Les fichiers .remote.
doivent être placés
dans les dossiers lib
ou routes
.
query
La fonction query
vous permet de lire des données dynamiques depuis le serveur (pour les données
statiques, envisagez plutôt l’utilisation de prerender
) :
import { function query<Output>(fn: () => MaybePromise<Output>): RemoteQueryFunction<void, Output> (+2 overloads)
Creates a remote query. When called from the browser, the function will be invoked on the server via a fetch
call.
See Remote functions for full documentation.
query } from '$app/server';
import * as module "$lib/server/database"
db from '$lib/server/database';
export const const getPosts: RemoteQueryFunction<void, any[]>
getPosts = query<any[]>(fn: () => MaybePromise<any[]>): RemoteQueryFunction<void, any[]> (+2 overloads)
Creates a remote query. When called from the browser, the function will be invoked on the server via a fetch
call.
See Remote functions for full documentation.
query(async () => {
const const posts: any[]
posts = await module "$lib/server/database"
db.function sql(strings: TemplateStringsArray, ...values: any[]): Promise<any[]>
sql`
SELECT title, slug
FROM post
ORDER BY published_at
DESC
`;
return const posts: any[]
posts;
});
Tout au long de cette page, vous verrez des imports depuis des modules fictifs comme
$lib/server/database
et$lib/server/auth
. Ces imports n’existent que pour illustrer les propos — vous pouvez bien sûr utiliser n’importe quel client de base de données et n’importe quel système d’authentification.La fonction
db.sql
ci-dessus est une fonction de gabarit étiquété qui échappe toute valeur interpolée.
La query renvoyée par getPosts
fonctionne comme une
Promise
qui résout des posts
:
<script>
import { getPosts } from './data.remote';
</script>
<h1>Articles récents</h1>
<ul>
{#each await getPosts() as { title, slug }}
<li><a href="/blog/{slug}">{title}</a></li>
{/each}
</ul>
<script lang="ts">
import { getPosts } from './data.remote';
</script>
<h1>Articles récents</h1>
<ul>
{#each await getPosts() as { title, slug }}
<li><a href="/blog/{slug}">{title}</a></li>
{/each}
</ul>
Tant que la promesse n’est pas résolue — et si elle échoue — la
<svelte:boundary>
la plus proche sera invoquée.
Bien que l’utilisation de await
soit recommandée, vous pouvez également utiliser en alternative
les propriétés loading
, error
et current
de la query :
<script>
import { getPosts } from './data.remote';
const query = getPosts();
</script>
{#if query.error}
<p>oups !</p>
{:else if query.loading}
<p>chargement...</p>
{:else}
<ul>
{#each query.current as { title, slug }}
<li><a href="/blog/{slug}">{title}</a></li>
{/each}
</ul>
{/if}
<script lang="ts">
import { getPosts } from './data.remote';
const query = getPosts();
</script>
{#if query.error}
<p>oups !</p>
{:else if query.loading}
<p>chargement...</p>
{:else}
<ul>
{#each query.current as { title, slug }}
<li><a href="/blog/{slug}">{title}</a></li>
{/each}
</ul>
{/if}
Pour le reste de ce document, nous utiliserons la forme
await
.
Arguments de query
Les fonctions query
peuvent accepter un argument, comme le slug
d’un article individuel :
<script>
import { getPost } from '../data.remote';
let { params } = $props();
const post = $derived(await getPost(params.slug));
</script>
<h1>{post.title}</h1>
<div>{@html post.content}</div>
<script lang="ts">
import { getPost } from '../data.remote';
let { params } = $props();
const post = $derived(await getPost(params.slug));
</script>
<h1>{post.title}</h1>
<div>{@html post.content}</div>
Puisque getPost
expose un endpoint HTTP, il est important de valider cet argument pour garantir
qu’il est du type attendu. Pour cela, nous pouvons utiliser n’importe quelle librairie de validation
de Standard Schema, comme Zod ou
Valibot :
import * as import v
v from 'valibot';
import { function error(status: number, body: App.Error): never (+1 overload)
Throws an error with a HTTP status code and an optional message.
When called during request handling, this will cause SvelteKit to
return an error response without invoking handleError
.
Make sure you’re not catching the thrown error, which would prevent SvelteKit from handling it.
error } from '@sveltejs/kit';
import { function query<Output>(fn: () => MaybePromise<Output>): RemoteQueryFunction<void, Output> (+2 overloads)
Creates a remote query. When called from the browser, the function will be invoked on the server via a fetch
call.
See Remote functions for full documentation.
query } from '$app/server';
import * as module "$lib/server/database"
db from '$lib/server/database';
export const const getPosts: RemoteQueryFunction<void, void>
getPosts = query<void>(fn: () => MaybePromise<void>): RemoteQueryFunction<void, void> (+2 overloads)
Creates a remote query. When called from the browser, the function will be invoked on the server via a fetch
call.
See Remote functions for full documentation.
query(async () => { /* ... */ });
export const const getPost: RemoteQueryFunction<string, any>
getPost = query<v.StringSchema<undefined>, any>(schema: v.StringSchema<undefined>, fn: (arg: string) => any): RemoteQueryFunction<string, any> (+2 overloads)
Creates a remote query. When called from the browser, the function will be invoked on the server via a fetch
call.
See Remote functions for full documentation.
query(import v
v.function string(): v.StringSchema<undefined> (+1 overload)
export string
Creates a string schema.
string(), async (slug: string
slug) => {
const [const post: any
post] = await module "$lib/server/database"
db.function sql(strings: TemplateStringsArray, ...values: any[]): Promise<any[]>
sql`
SELECT * FROM post
WHERE slug = ${slug: string
slug}
`;
if (!const post: any
post) function error(status: number, body?: {
message: string;
} extends App.Error ? App.Error | string | undefined : never): never (+1 overload)
Throws an error with a HTTP status code and an optional message.
When called during request handling, this will cause SvelteKit to
return an error response without invoking handleError
.
Make sure you’re not catching the thrown error, which would prevent SvelteKit from handling it.
error(404, 'Not found');
return const post: any
post;
});
L’argument et la valeur de retour sont tous deux sérialisés avec
devalue, qui gère des types comme Date
et Map
(et
également des types personnalisés que vous pouvez définir dans votre transport
hook) en plus du JSON.
Mettre les queries à jour
Toute query peut être mise à jour via sa méthode refresh
:
<button onclick={() => getPosts().refresh()}>
Vérifier s'il y a des nouveaux articles
</button>
Les queries sont mises en cache tant qu’elles sont sur la page, ce qui veut dire que
getPosts() === getPosts()
. Cela signifie que vous n’avez pas besoin d’une référence commeconst posts = getPosts()
pour mettre à jour la query.
form
La fonction form
facilite l’écriture de données sur le serveur. Elle prend un callback qui reçoit
le FormData
actuel...
import * as import v
v from 'valibot';
import { function error(status: number, body: App.Error): never (+1 overload)
Throws an error with a HTTP status code and an optional message.
When called during request handling, this will cause SvelteKit to
return an error response without invoking handleError
.
Make sure you’re not catching the thrown error, which would prevent SvelteKit from handling it.
error, function redirect(status: 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | ({} & number), location: string | URL): never
Redirect a request. When called during request handling, SvelteKit will return a redirect response.
Make sure you’re not catching the thrown redirect, which would prevent SvelteKit from handling it.
Most common status codes:
303 See Other
: redirect as a GET request (often used after a form POST request)
307 Temporary Redirect
: redirect will keep the request method
308 Permanent Redirect
: redirect will keep the request method, SEO will be transferred to the new page
redirect } from '@sveltejs/kit';
import { function query<Output>(fn: () => MaybePromise<Output>): RemoteQueryFunction<void, Output> (+2 overloads)
Creates a remote query. When called from the browser, the function will be invoked on the server via a fetch
call.
See Remote functions for full documentation.
query, function form<T>(fn: (data: FormData) => MaybePromise<T>): RemoteForm<T>
Creates a form object that can be spread onto a <form>
element.
See Remote functions for full documentation.
form } from '$app/server';
import * as module "$lib/server/database"
db from '$lib/server/database';
import * as module "$lib/server/auth"
auth from '$lib/server/auth';
export const const getPosts: RemoteQueryFunction<void, void>
getPosts = query<void>(fn: () => MaybePromise<void>): RemoteQueryFunction<void, void> (+2 overloads)
Creates a remote query. When called from the browser, the function will be invoked on the server via a fetch
call.
See Remote functions for full documentation.
query(async () => { /* ... */ });
export const const getPost: RemoteQueryFunction<string, void>
getPost = query<v.StringSchema<undefined>, void>(schema: v.StringSchema<undefined>, fn: (arg: string) => MaybePromise<void>): RemoteQueryFunction<string, void> (+2 overloads)
Creates a remote query. When called from the browser, the function will be invoked on the server via a fetch
call.
See Remote functions for full documentation.
query(import v
v.function string(): v.StringSchema<undefined> (+1 overload)
export string
Creates a string schema.
string(), async (slug: string
slug) => { /* ... */ });
export const const createPost: RemoteForm<never>
createPost = form<never>(fn: (data: FormData) => Promise<never>): RemoteForm<never>
Creates a form object that can be spread onto a <form>
element.
See Remote functions for full documentation.
form(async (data: FormData
data) => {
// Vérifie si l'utilisateur est connecté
const const user: auth.User | null
user = await module "$lib/server/auth"
auth.function getUser(): Promise<auth.User | null>
Récupère les informations de l’utilisateur à partir des cookies, en utilisant getRequestEvent
getUser();
if (!const user: auth.User | null
user) function error(status: number, body?: {
message: string;
} extends App.Error ? App.Error | string | undefined : never): never (+1 overload)
Throws an error with a HTTP status code and an optional message.
When called during request handling, this will cause SvelteKit to
return an error response without invoking handleError
.
Make sure you’re not catching the thrown error, which would prevent SvelteKit from handling it.
error(401, 'Unauthorized');
const const title: FormDataEntryValue | null
title = data: FormData
data.FormData.get(name: string): FormDataEntryValue | null
get('title');
const const content: FormDataEntryValue | null
content = data: FormData
data.FormData.get(name: string): FormDataEntryValue | null
get('content');
// Vérifie si les données sont valides
if (typeof const title: FormDataEntryValue | null
title !== 'string' || typeof const content: FormDataEntryValue | null
content !== 'string') {
function error(status: number, body?: {
message: string;
} extends App.Error ? App.Error | string | undefined : never): never (+1 overload)
Throws an error with a HTTP status code and an optional message.
When called during request handling, this will cause SvelteKit to
return an error response without invoking handleError
.
Make sure you’re not catching the thrown error, which would prevent SvelteKit from handling it.
error(400, 'Title and content are required');
}
const const slug: string
slug = const title: string
title.String.toLowerCase(): string
Converts all the alphabetic characters in a string to lowercase.
toLowerCase().String.replace(searchValue: {
[Symbol.replace](string: string, replaceValue: string): string;
}, replaceValue: string): string (+3 overloads)
Passes a string and
{@linkcode
replaceValue
}
to the [Symbol.replace]
method on
{@linkcode
searchValue
}
. This method is expected to implement its own replacement algorithm.
replace(/ /g, '-');
// Insère en base de données
await module "$lib/server/database"
db.function sql(strings: TemplateStringsArray, ...values: any[]): Promise<any[]>
sql`
INSERT INTO post (slug, title, content)
VALUES (${const slug: string
slug}, ${const title: string
title}, ${const content: string
content})
`;
// Redirige vers la page nouvellement créée
function redirect(status: 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | ({} & number), location: string | URL): never
Redirect a request. When called during request handling, SvelteKit will return a redirect response.
Make sure you’re not catching the thrown redirect, which would prevent SvelteKit from handling it.
Most common status codes:
303 See Other
: redirect as a GET request (often used after a form POST request)
307 Temporary Redirect
: redirect will keep the request method
308 Permanent Redirect
: redirect will keep the request method, SEO will be transferred to the new page
redirect(303, `/blog/${const slug: string
slug}`);
});
... et renvoie un objet qui peut être distribué sur un élément <form>
. Le callback est appelée à
chaque fois que le formulaire est soumis.
<script>
import { createPost } from '../data.remote';
</script>
<h1>Créer un nouvel article</h1>
<form {...createPost}>
<label>
<h2>Titre</h2>
<input name="title" />
</label>
<label>
<h2>Écrivez votre article</h2>
<textarea name="content"></textarea>
</label>
<button>Publier !</button>
</form>
<script lang="ts">
import { createPost } from '../data.remote';
</script>
<h1>Créer un nouvel article</h1>
<form {...createPost}>
<label>
<h2>Titre</h2>
<input name="title" />
</label>
<label>
<h2>Écrivez votre article</h2>
<textarea name="content"></textarea>
</label>
<button>Publier !</button>
</form>
L’objet de formulaire contient les propriétés method
et action
qui lui permettent de fonctionner
sans JavaScript (c-à-d qu’il enverra les données puis qu’il rechargera la page). Il a également un
gestionnaire onsubmit
qui améliore progressivement le formulaire lorsque JavaScript est
disponible, en soumettant les données sans recharger entièrement la page.
Mutations single-flight
Par défaut, toutes les queries utilisées sur la page (en plus de toutes fonction load
) sont
automatiquement mises à jour à la suite d’une soumission de formulaire. Ceci assure que tout soit à
jour, mais est également inefficace : beaucoup de queries seront inchangées, et cela requiert un
deuxième aller-retour vers le serveur pour obtenir les données à jour.
À la place, nous pouvons préciser quelles queries devraient être mises à jour en réponse à une soumission particulière de formulaire. On appelle cela une mutation single-flight (mutation sur un seul aller-retour), et il y a deux manières de faire. La première est de rafraîchir la query sur le serveur, dans le gestionnaire de formulaire :
export const const getPosts: RemoteQueryFunction<void, void>
getPosts = query<void>(fn: () => MaybePromise<void>): RemoteQueryFunction<void, void> (+2 overloads)
Creates a remote query. When called from the browser, the function will be invoked on the server via a fetch
call.
See Remote functions for full documentation.
query(async () => { /* ... */ });
export const const getPost: RemoteQueryFunction<string, void>
getPost = query<v.StringSchema<undefined>, void>(schema: v.StringSchema<undefined>, fn: (arg: string) => MaybePromise<void>): RemoteQueryFunction<string, void> (+2 overloads)
Creates a remote query. When called from the browser, the function will be invoked on the server via a fetch
call.
See Remote functions for full documentation.
query(import v
v.function string(): v.StringSchema<undefined> (+1 overload)
export string
Creates a string schema.
string(), async (slug: string
slug) => { /* ... */ });
export const const createPost: RemoteForm<never>
createPost = form<never>(fn: (data: FormData) => Promise<never>): RemoteForm<never>
Creates a form object that can be spread onto a <form>
element.
See Remote functions for full documentation.
form(async (data: FormData
data) => {
// la logique du formulaire se place ici...
// Met à jour `getPosts()` sur le serveur, et
// renvoie les données au client avec le résultat
// de `createPost`
await const getPosts: (arg: void) => RemoteQuery<void>
getPosts().function refresh(): Promise<void>
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
and the refreshed data will accompany the action response back to the client.
This prevents SvelteKit needing to refresh all queries on the page in a second server round-trip.
refresh();
// Redirige vers la page nouvellement créée
function redirect(status: 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | ({} & number), location: string | URL): never
Redirect a request. When called during request handling, SvelteKit will return a redirect response.
Make sure you’re not catching the thrown redirect, which would prevent SvelteKit from handling it.
Most common status codes:
303 See Other
: redirect as a GET request (often used after a form POST request)
307 Temporary Redirect
: redirect will keep the request method
308 Permanent Redirect
: redirect will keep the request method, SEO will be transferred to the new page
redirect(303, `/blog/${const slug: ""
slug}`);
});
La seconde est de conduire la mutation single-flight depuis le client, ce que nous verrons dans la
section sur enhance
.
Renvois et redirections
L’exemple ci-dessus utilise redirect(...)
, qui redirige l’utilisateur ou
l’utilisatrice vers la page nouvellement créée. De manière alternative, le callback pourrait
également renvoyer les données, auquel cas elles seraient disponibles en tant que
createPost.result
:
export const const createPost: RemoteForm<{
success: boolean;
}>
createPost = form<{
success: boolean;
}>(fn: (data: FormData) => MaybePromise<{
success: boolean;
}>): RemoteForm<{
success: boolean;
}>
Creates a form object that can be spread onto a <form>
element.
See Remote functions for full documentation.
form(async (data: FormData
data) => {
// ...
return { success: boolean
success: true };
});
<script>
import { createPost } from '../data.remote';
</script>
<h1>Créer un nouvel article</h1>
<form {...createPost}><!-- ... --></form>
{#if createPost.result?.success}
<p>Publié avec succès !</p>
{/if}
<script lang="ts">
import { createPost } from '../data.remote';
</script>
<h1>Créer un nouvel article</h1>
<form {...createPost}><!-- ... --></form>
{#if createPost.result?.success}
<p>Publié avec succès !</p>
{/if}
Cette valeur est éphémère — elle disparaîtra si vous re-soumettez le formulaire, si vous naviguez sur une autre page, ou si vous rechargez la page.
La valeur de
result
n’indique pas nécessairement un succès — elle peut également contenir des erreurs de validation, ainsi que des données permettant de re-remplir le formulaire en cas de rechargement de la page.
Si une erreur se produit lors de la soumission, la page +error.svelte
la plus proche sera
affichée.
enhance
Nous pouvons personnaliser ce qui se produit lorsque le formulaire est soumis grâce à la méthode
enhance
:
<script>
import { createPost } from '../data.remote';
import { showToast } from '$lib/toast';
</script>
<h1>Créer un nouvel article</h1>
<form {...createPost.enhance(async ({ form, data, submit }) => {
try {
await submit();
form.reset();
showToast('Publié avec succès !');
} catch (error) {
showToast('Oh non ! Quelque chose s\'est mal passé !');
}
})}>
<input name="title" />
<textarea name="content"></textarea>
<button>publier</button>
</form>
<script lang="ts">
import { createPost } from '../data.remote';
import { showToast } from '$lib/toast';
</script>
<h1>Créer un nouvel article</h1>
<form {...createPost.enhance(async ({ form, data, submit }) => {
try {
await submit();
form.reset();
showToast('Publié avec succès !');
} catch (error) {
showToast('Oh non ! Quelque chose s\'est mal passé !');
}
})}>
<input name="title" />
<textarea name="content"></textarea>
<button>publier</button>
</form>
Le callback reçoit l’élément form
, la data
qu’il contient, et une fonction submit
.
Pour activer les mutations single-flight depuis le client, utilisez
submit().updates(...})
. Par exemple, si la query getPosts()
était utilisée sur cette page, nous
pourrions la mettre à jour de cette manière :
await function submit(): Promise<any> & {
updates(...queries: Array<RemoteQuery<any> | RemoteQueryOverride>): Promise<any>;
}
submit().function updates(...queries: Array<RemoteQuery<any> | RemoteQueryOverride>): Promise<any>
updates(function getPosts(): RemoteQuery<Post[]>
getPosts());
Nous pouvons aussi surcharger les données courantes pendant que la soumission est en cours de traitement :
await function submit(): Promise<any> & {
updates(...queries: Array<RemoteQuery<any> | RemoteQueryOverride>): Promise<any>;
}
submit().function updates(...queries: Array<RemoteQuery<any> | RemoteQueryOverride>): Promise<any>
updates(
function getPosts(): RemoteQuery<Post[]>
getPosts().function withOverride(update: (current: Post[]) => Post[]): RemoteQueryOverride
Temporarily override the value of a query. This is used with the updates
method of a command or enhanced form submission to provide optimistic updates.
<script>
import { getTodos, addTodo } from './todos.remote.js';
const todos = getTodos();
</script>
<form {...addTodo.enhance(async ({ data, submit }) => {
await submit().updates(
todos.withOverride((todos) => [...todos, { text: data.get('text') }])
);
}}>
<input type="text" name="text" />
<button type="submit">Add Todo</button>
</form>
withOverride((posts: Post[]
posts) => [const newPost: Post
newPost, ...posts: Post[]
posts])
);
La surcharge sera appliquée immédiatement, et annulée lorsque la soumission se termine (ou échoue).
buttonProps
Par défaut, soumettre un formulaire va envoyer une requête à l’URL indiquée par l’attribut
action
de l’élément <form>
, ce qui dans le cas d’une fonction distante est une propriété sur l’objet de
formulaire généré par SvelteKit.
Il est possible pour un <button>
au sein d’un <form>
d’envoyer la requête à une URL
différente, en utilisant l’attribut
formaction
.
Par exemple, vous pourriez avoir un seul formulaire vous permettant de vous connecter ou de vous
inscrire, selon le bouton sur lequel vous cliquez.
Cet attribut existe dans la propriété buttonProps
d’un objet de formulaire :
<script>
import { login, register } from '$lib/auth';
</script>
<form {...login}>
<label>
Votre nom d'utilisateur
<input name="username" />
</label>
<label>
Votre mot de passe
<input name="password" type="password" />
</label>
<button>se connecter</button>
<button {...register.buttonProps}>s'inscrire</button>
</form>
<script lang="ts">
import { login, register } from '$lib/auth';
</script>
<form {...login}>
<label>
Votre nom d'utilisateur
<input name="username" />
</label>
<label>
Votre mot de passe
<input name="password" type="password" />
</label>
<button>se connecter</button>
<button {...register.buttonProps}>s'inscrire</button>
</form>
Comme l’objet de formulaire lui-même, buttonProps
possède une méthode enhance
permettant de
personnaliser la soumission du formulaire.
command
La fonction command
, comme form
, vous permet d’écrire des données sur le serveur. À la
différence de form
, elle n’est pas spécifique à un élément et peut être appelée depuis n’importe
où.
Privilégiez l’usage de
form
lorsque c’est possible, puisqu’elle continue de fonctionner lorsque JavaScript est désactivé ou a échoué à se charger.
Comme avec query
, si la fonction accepte un argument, celui-ci devrait être
validé en fournissant un Standard Schema en
tant que premier argument à command
.
import * as import v
v from 'valibot';
import { function query<Output>(fn: () => MaybePromise<Output>): RemoteQueryFunction<void, Output> (+2 overloads)
Creates a remote query. When called from the browser, the function will be invoked on the server via a fetch
call.
See Remote functions for full documentation.
query, function command<Output>(fn: () => Output): RemoteCommand<void, Output> (+2 overloads)
Creates a remote command. When called from the browser, the function will be invoked on the server via a fetch
call.
See Remote functions for full documentation.
command } from '$app/server';
import * as module "$lib/server/database"
db from '$lib/server/database';
export const const getLikes: RemoteQueryFunction<string, any>
getLikes = query<v.StringSchema<undefined>, any>(schema: v.StringSchema<undefined>, fn: (arg: string) => any): RemoteQueryFunction<string, any> (+2 overloads)
Creates a remote query. When called from the browser, the function will be invoked on the server via a fetch
call.
See Remote functions for full documentation.
query(import v
v.function string(): v.StringSchema<undefined> (+1 overload)
export string
Creates a string schema.
string(), async (id: string
id) => {
const [const row: any
row] = await module "$lib/server/database"
db.function sql(strings: TemplateStringsArray, ...values: any[]): Promise<any[]>
sql`
SELECT likes
FROM item
WHERE id = ${id: string
id}
`;
return const row: any
row.likes;
});
export const const addLike: RemoteCommand<string, Promise<void>>
addLike = command<v.StringSchema<undefined>, Promise<void>>(validate: v.StringSchema<undefined>, fn: (arg: string) => Promise<void>): RemoteCommand<string, Promise<void>> (+2 overloads)
Creates a remote command. When called from the browser, the function will be invoked on the server via a fetch
call.
See Remote functions for full documentation.
command(import v
v.function string(): v.StringSchema<undefined> (+1 overload)
export string
Creates a string schema.
string(), async (id: string
id) => {
await module "$lib/server/database"
db.function sql(strings: TemplateStringsArray, ...values: any[]): Promise<any[]>
sql`
UPDATE item
SET likes = likes + 1
WHERE id = ${id: string
id}
`;
});
Vous pouvez maintenant simplement appeler addLike
, depuis (par exemple) un gestionnaire
d’évènement :
<script>
import { getLikes, addLike } from './likes.remote';
import { showToast } from '$lib/toast';
let { item } = $props();
</script>
<button
onclick={async () => {
try {
await addLike(item.id);
} catch (error) {
showToast('Quelque chose s\'est mal passé !');
}
}}
>
ajouter un like
</button>
<p>likes : {await getLikes(item.id)}</p>
<script lang="ts">
import { getLikes, addLike } from './likes.remote';
import { showToast } from '$lib/toast';
let { item } = $props();
</script>
<button
onclick={async () => {
try {
await addLike(item.id);
} catch (error) {
showToast('Quelque chose s\'est mal passé !');
}
}}
>
ajouter un like
</button>
<p>likes : {await getLikes(item.id)}</p>
Les commandes ne peuvent pas être appelées lors du rendu.
Mutations single-flight
Comme avec les formulaires, toute query sur la page (comme getLikes(item.id)
dans l’exemple
ci-dessus) sera automatiquement mise à jour à la suite d’une commande qui se passe bien. Mais nous
pouvons rendre les choses plus efficaces en disant à SvelteKit quelles queries seront affectées par
la commande, soit au sein de la commande elle-même...
export const const getLikes: RemoteQueryFunction<string, void>
getLikes = query<v.StringSchema<undefined>, void>(schema: v.StringSchema<undefined>, fn: (arg: string) => MaybePromise<void>): RemoteQueryFunction<string, void> (+2 overloads)
Creates a remote query. When called from the browser, the function will be invoked on the server via a fetch
call.
See Remote functions for full documentation.
query(import v
v.function string(): v.StringSchema<undefined> (+1 overload)
export string
Creates a string schema.
string(), async (id: string
id) => { /* ... */ });
export const const addLike: RemoteCommand<string, Promise<void>>
addLike = command<v.StringSchema<undefined>, Promise<void>>(validate: v.StringSchema<undefined>, fn: (arg: string) => Promise<void>): RemoteCommand<string, Promise<void>> (+2 overloads)
Creates a remote command. When called from the browser, the function will be invoked on the server via a fetch
call.
See Remote functions for full documentation.
command(import v
v.function string(): v.StringSchema<undefined> (+1 overload)
export string
Creates a string schema.
string(), async (id: string
id) => {
await module "$lib/server/database"
db.function sql(strings: TemplateStringsArray, ...values: any[]): Promise<any[]>
sql`
UPDATE item
SET likes = likes + 1
WHERE id = ${id: string
id}
`;
const getLikes: (arg: string) => RemoteQuery<void>
getLikes(id: string
id).function refresh(): Promise<void>
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
and the refreshed data will accompany the action response back to the client.
This prevents SvelteKit needing to refresh all queries on the page in a second server round-trip.
refresh();
});
... soit lorsque nous l’exécutons :
try {
await const addLike: (arg: string) => Promise<void> & {
updates(...queries: Array<RemoteQuery<any> | RemoteQueryOverride>): Promise<...>;
}
addLike(const item: Item
item.Item.id: string
id).function updates(...queries: Array<RemoteQuery<any> | RemoteQueryOverride>): Promise<void>
updates(const getLikes: (arg: string) => RemoteQuery<number>
getLikes(const item: Item
item.Item.id: string
id));
} catch (var error: unknown
error) {
function showToast(message: string): void
showToast('Quelque chose s\'est mal passé !');
}
Comme précédemment, nous pouvons utiliser withOverride
pour faire des mises à jour optimistes :
try {
await const addLike: (arg: string) => Promise<void> & {
updates(...queries: Array<RemoteQuery<any> | RemoteQueryOverride>): Promise<...>;
}
addLike(const item: Item
item.Item.id: string
id).function updates(...queries: Array<RemoteQuery<any> | RemoteQueryOverride>): Promise<void>
updates(
const getLikes: (arg: string) => RemoteQuery<number>
getLikes(const item: Item
item.Item.id: string
id).function withOverride(update: (current: number) => number): RemoteQueryOverride
Temporarily override the value of a query. This is used with the updates
method of a command or enhanced form submission to provide optimistic updates.
<script>
import { getTodos, addTodo } from './todos.remote.js';
const todos = getTodos();
</script>
<form {...addTodo.enhance(async ({ data, submit }) => {
await submit().updates(
todos.withOverride((todos) => [...todos, { text: data.get('text') }])
);
}}>
<input type="text" name="text" />
<button type="submit">Add Todo</button>
</form>
withOverride((n: number
n) => n: number
n + 1)
);
} catch (var error: unknown
error) {
function showToast(message: string): void
showToast('Quelque chose s\'est mal passé !');
}
prerender
La fonction prerender
est similaire à query
, sauf qu’elle sera exécutée lors de la compilation
pour pré-rendre le résultat. Utilisez cette fonction pour des données qui changent au plus une fois
par déploiement.
import { function prerender<Output>(fn: () => MaybePromise<Output>, options?: {
inputs?: RemotePrerenderInputsGenerator<void>;
dynamic?: boolean;
} | undefined): RemotePrerenderFunction<void, Output> (+2 overloads)
Creates a remote prerender function. When called from the browser, the function will be invoked on the server via a fetch
call.
See Remote functions for full documentation.
prerender } from '$app/server';
import * as module "$lib/server/database"
db from '$lib/server/database';
export const const getPosts: RemotePrerenderFunction<void, any[]>
getPosts = prerender<any[]>(fn: () => MaybePromise<any[]>, options?: {
inputs?: RemotePrerenderInputsGenerator<void>;
dynamic?: boolean;
} | undefined): RemotePrerenderFunction<...> (+2 overloads)
Creates a remote prerender function. When called from the browser, the function will be invoked on the server via a fetch
call.
See Remote functions for full documentation.
prerender(async () => {
const const posts: any[]
posts = await module "$lib/server/database"
db.function sql(strings: TemplateStringsArray, ...values: any[]): Promise<any[]>
sql`
SELECT title, slug
FROM post
ORDER BY published_at
DESC
`;
return const posts: any[]
posts;
});
Vous pouvez utiliser des fonctions prerender
sur des pages par ailleurs dynamiques, permettant le
pré-rendu partiel de vos données. Ceci permet une navigation très rapide, puisque les données
prérendues peuvent être stockées sur un CDN à côté de vos autres assets statiques.
Dans le navigateur, les données pré-rendues sont sauvegardées en utilisant l’API
Cache
. Ce cache survit aux rechargements de
page, et sera nettoyé lorsque l’utilisateur ou l’utilisatrice visitera pour la première fois une
nouveau déploiement de votre application.
Lorsque la page entière a
export const prerender = true
, vous ne pouvez pas utiliser de queries, puisqu’elles sont dynamiques.
Arguments de prerender
Comme avec les queries, les fonctions prerender
peuvent accepter un argument, qui devrait être
validé avec un Standard Schema :
import * as import v
v from 'valibot';
import { function error(status: number, body: App.Error): never (+1 overload)
Throws an error with a HTTP status code and an optional message.
When called during request handling, this will cause SvelteKit to
return an error response without invoking handleError
.
Make sure you’re not catching the thrown error, which would prevent SvelteKit from handling it.
error } from '@sveltejs/kit';
import { function prerender<Output>(fn: () => MaybePromise<Output>, options?: {
inputs?: RemotePrerenderInputsGenerator<void>;
dynamic?: boolean;
} | undefined): RemotePrerenderFunction<void, Output> (+2 overloads)
Creates a remote prerender function. When called from the browser, the function will be invoked on the server via a fetch
call.
See Remote functions for full documentation.
prerender } from '$app/server';
import * as module "$lib/server/database"
db from '$lib/server/database';
export const const getPosts: RemotePrerenderFunction<void, void>
getPosts = prerender<void>(fn: () => MaybePromise<void>, options?: {
inputs?: RemotePrerenderInputsGenerator<void>;
dynamic?: boolean;
} | undefined): RemotePrerenderFunction<...> (+2 overloads)
Creates a remote prerender function. When called from the browser, the function will be invoked on the server via a fetch
call.
See Remote functions for full documentation.
prerender(async () => { /* ... */ });
export const const getPost: RemotePrerenderFunction<string, any>
getPost = prerender<v.StringSchema<undefined>, any>(schema: v.StringSchema<undefined>, fn: (arg: string) => any, options?: {
inputs?: RemotePrerenderInputsGenerator<string> | undefined;
dynamic?: boolean;
} | undefined): RemotePrerenderFunction<...> (+2 overloads)
Creates a remote prerender function. When called from the browser, the function will be invoked on the server via a fetch
call.
See Remote functions for full documentation.
prerender(import v
v.function string(): v.StringSchema<undefined> (+1 overload)
export string
Creates a string schema.
string(), async (slug: string
slug) => {
const [const post: any
post] = await module "$lib/server/database"
db.function sql(strings: TemplateStringsArray, ...values: any[]): Promise<any[]>
sql`
SELECT * FROM post
WHERE slug = ${slug: string
slug}
`;
if (!const post: any
post) function error(status: number, body?: {
message: string;
} extends App.Error ? App.Error | string | undefined : never): never (+1 overload)
Throws an error with a HTTP status code and an optional message.
When called during request handling, this will cause SvelteKit to
return an error response without invoking handleError
.
Make sure you’re not catching the thrown error, which would prevent SvelteKit from handling it.
error(404, 'Not found');
return const post: any
post;
});
Tout appel à getPost(...)
identifié par le crawler de SvelteKit lors du pré-rendu des
pages sera automatiquement enregistré, mais vous pouvez également préciser
avec quelles valeurs chaque appel devrait être fait en utilisant l’option inputs
:
export const const getPost: RemotePrerenderFunction<string, void>
getPost = prerender<v.StringSchema<undefined>, void>(schema: v.StringSchema<undefined>, fn: (arg: string) => MaybePromise<void>, options?: {
inputs?: RemotePrerenderInputsGenerator<string> | undefined;
dynamic?: boolean;
} | undefined): RemotePrerenderFunction<...> (+2 overloads)
Creates a remote prerender function. When called from the browser, the function will be invoked on the server via a fetch
call.
See Remote functions for full documentation.
prerender(
import v
v.function string(): v.StringSchema<undefined> (+1 overload)
export string
Creates a string schema.
string(),
async (slug: string
slug) => { /* ... */ },
{
inputs?: RemotePrerenderInputsGenerator<string> | undefined
inputs: () => [
'premier-article',
'deuxième-article',
'troisième-article'
]
}
);
export const const getPost: RemotePrerenderFunction<string, void>
getPost = prerender<v.StringSchema<undefined>, void>(schema: v.StringSchema<undefined>, fn: (arg: string) => MaybePromise<void>, options?: {
inputs?: RemotePrerenderInputsGenerator<string> | undefined;
dynamic?: boolean;
} | undefined): RemotePrerenderFunction<...> (+2 overloads)
Creates a remote prerender function. When called from the browser, the function will be invoked on the server via a fetch
call.
See Remote functions for full documentation.
prerender(
import v
v.function string(): v.StringSchema<undefined> (+1 overload)
export string
Creates a string schema.
string(),
async (slug: string
slug) => { /* ... */ },
{
inputs?: RemotePrerenderInputsGenerator<string> | undefined
inputs: () => [
'premier-article',
'deuxième-article',
'troisième-article'
]
}
);
Svelte ne supporte pas encore le rendu asynchrone côté serveur, et il est donc probable que vous n’appeliez les fonctions distantes que depuis le serveur, plutôt que lors du pré-rendu. À cause de cela, vous aurez besoin d’utiliser
inputs
, pour le moment. Nous travaillons activement sur point bloquant.
Par défaut, les fonctions de pré-rendu sont exclues de votre bundle de compilation, ce qui signifie
que vous ne pouvez pas les exécuter avec des arguments qui n’ont pas été pré-rendus. Vous pouvez
définir dynamic: true
pour changer ce comportement :
export const const getPost: RemotePrerenderFunction<string, void>
getPost = prerender<v.StringSchema<undefined>, void>(schema: v.StringSchema<undefined>, fn: (arg: string) => MaybePromise<void>, options?: {
inputs?: RemotePrerenderInputsGenerator<string> | undefined;
dynamic?: boolean;
} | undefined): RemotePrerenderFunction<...> (+2 overloads)
Creates a remote prerender function. When called from the browser, the function will be invoked on the server via a fetch
call.
See Remote functions for full documentation.
prerender(
import v
v.function string(): v.StringSchema<undefined> (+1 overload)
export string
Creates a string schema.
string(),
async (slug: string
slug) => { /* ... */ },
{
dynamic?: boolean | undefined
dynamic: true,
inputs?: RemotePrerenderInputsGenerator<string> | undefined
inputs: () => [
'premier-article',
'deuxième-article',
'troisième-article'
]
}
);
export const const getPost: RemotePrerenderFunction<string, void>
getPost = prerender<v.StringSchema<undefined>, void>(schema: v.StringSchema<undefined>, fn: (arg: string) => MaybePromise<void>, options?: {
inputs?: RemotePrerenderInputsGenerator<string> | undefined;
dynamic?: boolean;
} | undefined): RemotePrerenderFunction<...> (+2 overloads)
Creates a remote prerender function. When called from the browser, the function will be invoked on the server via a fetch
call.
See Remote functions for full documentation.
prerender(
import v
v.function string(): v.StringSchema<undefined> (+1 overload)
export string
Creates a string schema.
string(),
async (slug: string
slug) => { /* ... */ },
{
dynamic?: boolean | undefined
dynamic: true,
inputs?: RemotePrerenderInputsGenerator<string> | undefined
inputs: () => [
'premier-article',
'deuxième-article',
'troisième-article'
]
}
);
Gestion des erreurs de validation
Tant que vous ne passez pas de données invalides à vos fonctions distantes, il n’y a que deux
raisons pour lesquelles l’argument passé à une command
, une query
ou une prerender
échouerait
à la validation :
- la signature de la fonction a changé entre deux déploiements, et certains utilisateurs ou utilisatrices sont actuellement sur une version ancienne de votre application
- quelqu’un est en train d’essayer d’attaquer votre site en testant les endpoints que vous exposez avec des données malformées
Dans le second cas, nous ne voulons pas donner à l’attaquant d’indices, c’est pourquoi SvelteKit va
générer une réponse générique 400 Bad Request. Vous pouvez contrôler le
message en implémentant le hook serveur
handleValidationError
, qui, comme
handleError
, doit renvoyer un objet
App.Error
(qui est défini par défaut à { message: string }
) :
/** @type {import('@sveltejs/kit').HandleValidationError} */
export function function handleValidationError({ event, issues }: {
event: any;
issues: any;
}): {
message: string;
}
handleValidationError({ event: any
event, issues: any
issues }) {
return {
message: string
message: 'Nice try, hacker!'
};
}
import type { type HandleValidationError<Issue extends StandardSchemaV1.Issue = StandardSchemaV1.Issue> = (input: {
issues: Issue[];
event: RequestEvent;
}) => MaybePromise<App.Error>
The handleValidationError
hook runs when the argument to a remote function fails validation.
It will be called with the validation issues and the event, and must return an object shape that matches App.Error
.
HandleValidationError } from '@sveltejs/kit';
export const const handleValidationError: HandleValidationError
handleValidationError: type HandleValidationError<Issue extends StandardSchemaV1.Issue = StandardSchemaV1.Issue> = (input: {
issues: Issue[];
event: RequestEvent;
}) => MaybePromise<App.Error>
The handleValidationError
hook runs when the argument to a remote function fails validation.
It will be called with the validation issues and the event, and must return an object shape that matches App.Error
.
HandleValidationError = ({ event: RequestEvent<AppLayoutParams<"/">, any>
event, issues: StandardSchemaV1.Issue[]
issues }) => {
return {
App.Error.message: string
message: 'Nice try, hacker!'
};
};
Si vous savez ce que vous faites et souhaitez vous passer de validation, vous pouvez passer la
chaîne de caractères 'unchecked'
à la place du schéma :
import { function query<Output>(fn: () => MaybePromise<Output>): RemoteQueryFunction<void, Output> (+2 overloads)
Creates a remote query. When called from the browser, the function will be invoked on the server via a fetch
call.
See Remote functions for full documentation.
query } from '$app/server';
export const const getStuff: RemoteQueryFunction<{
id: string;
}, void>
getStuff = query<{
id: string;
}, void>(validate: "unchecked", fn: (arg: {
id: string;
}) => MaybePromise<void>): RemoteQueryFunction<{
id: string;
}, void> (+2 overloads)
Creates a remote query. When called from the browser, the function will be invoked on the server via a fetch
call.
See Remote functions for full documentation.
query('unchecked', async ({ id: string
id }: { id: string
id: string }) => {
// la forme peut ne pas être ce que pense TypeScript
// puisque des personnes mal intentionnées pourraient
// appeler cette fonction avec d'autres arguments
});
form
n’accepte pas de schéma puisque vous recevrez toujours un objetFormData
. Vous êtes libres de parser et valider ces données comme bon vous semble.
Utiliser getRequestEvent
Dans une fonction query
, form
ou command
, vous pouvez utiliser
getRequestEvent
pour obtenir l’objet
RequestEvent
courant. Ceci permet de construire simplement des
abstractions pour interagir avec des cookies, par exemple :
import { function getRequestEvent(): RequestEvent<findUser<"/">, any>
Returns the current RequestEvent
. Can be used inside server hooks, server load
functions, actions, and endpoints (and functions called by them).
In environments without AsyncLocalStorage
, this must be called synchronously (i.e. not after an await
).
getRequestEvent, function query<Output>(fn: () => MaybePromise<Output>): RemoteQueryFunction<void, Output> (+2 overloads)
Creates a remote query. When called from the browser, the function will be invoked on the server via a fetch
call.
See Remote functions for full documentation.
query } from '$app/server';
import { import findUser
findUser } from '$lib/server/database';
export const const getProfile: RemoteQueryFunction<void, {
name: any;
avatar: any;
}>
getProfile = query<{
name: any;
avatar: any;
}>(fn: () => MaybePromise<{
name: any;
avatar: any;
}>): RemoteQueryFunction<void, {
name: any;
avatar: any;
}> (+2 overloads)
Creates a remote query. When called from the browser, the function will be invoked on the server via a fetch
call.
See Remote functions for full documentation.
query(async () => {
const const user: any
user = await function getUser(): any
getUser();
return {
name: any
name: const user: any
user.name,
avatar: any
avatar: const user: any
user.avatar
};
});
// cette fonction pourrait être appelée depuis plusieurs endroits
function function getUser(): any
getUser() {
const { const cookies: Cookies
Get or set cookies related to the current request
cookies, const locals: App.Locals
Contains custom data that was added to the request within the server handle hook
.
locals } = function getRequestEvent(): RequestEvent<findUser<"/">, any>
Returns the current RequestEvent
. Can be used inside server hooks, server load
functions, actions, and endpoints (and functions called by them).
In environments without AsyncLocalStorage
, this must be called synchronously (i.e. not after an await
).
getRequestEvent();
const locals: App.Locals
Contains custom data that was added to the request within the server handle hook
.
locals.userPromise ??= import findUser
findUser(const cookies: Cookies
Get or set cookies related to the current request
cookies.Cookies.get: (name: string, opts?: CookieParseOptions) => string | undefined
Gets a cookie that was previously set with cookies.set
, or from the request headers.
get('session_id'));
return await const locals: App.Locals
Contains custom data that was added to the request within the server handle hook
.
locals.userPromise;
}
Notez que certaines propriétés de RequestEvent
sont différentes au sein des fonctions distantes.
Il n’y a pas de params
ou de route.id
, et vous ne pouvez pas définir d’en-têtes (si ce n’est
écrire dans des cookies, et uniquement dans des fonctions form
et command
), et url.pathname
vaut toujours /
(puisque le chemin qui est vraiment requêté par le client est essentiellement un
détail d’implémentation).
Redirections
Au sein d’une fonction query
, form
ou prerender
, il est possible d’utiliser la fonction
redirect(...)
. Ce n’est pas possible au sein d’une fonction
command
, car vous devriez éviter de rediriger à cet endroit. (Si vous avez absolument besoin de le
faire, vous pouvez toujours renvoyer un objet { redirect: location }
et gérer la redirection côté
client.)
Modifier cette page sur Github llms.txt