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](server-only-modules) 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`](/docs/svelte/await-expressions), 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` : ```js /// file: svelte.config.js export default { kit: { experimental: { +++remoteFunctions: true+++ } } }; ``` ## Aperçu [!VO]Overview 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`](#prerender)) : ```js /// file: src/routes/blog/data.remote.js // @filename: ambient.d.ts declare module '$lib/server/database' { export function sql(strings: TemplateStringsArray, ...values: any[]): Promise; } // @filename: index.js // ---cut--- import { query } from '$app/server'; import * as db from '$lib/server/database'; export const getPosts = query(async () => { const posts = await db.sql` SELECT title, slug FROM post ORDER BY published_at DESC `; return posts; }); ``` > [!NOTE] 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é](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Template_literals#gabarits_%C3%A9tiquet%C3%A9s) > qui échappe toute valeur interpolée. La query renvoyée par `getPosts` fonctionne comme une [`Promise`](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Global_Objects/Promise) qui résout des `posts` : ```svelte

Articles récents

``` Tant que la promesse n'est pas résolue — et si elle échoue — la [``](../svelte/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 : ```svelte {#if query.error}

oups !

{:else if query.loading}

chargement...

{:else} {/if} ``` > [!NOTE] Pour le reste de ce document, nous utiliserons la forme `await`. ### Arguments de query [!VO]Query arguments Les fonctions `query` peuvent accepter un argument, comme le `slug` d'un article individuel : ```svelte

{post.title}

{@html post.content}
``` 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](https://standardschema.dev/), comme [Zod](https://zod.dev/) ou [Valibot](https://valibot.dev/) : ```js /// file: src/routes/blog/data.remote.js // @filename: ambient.d.ts declare module '$lib/server/database' { export function sql(strings: TemplateStringsArray, ...values: any[]): Promise; } // @filename: index.js // ---cut--- import * as v from 'valibot'; import { error } from '@sveltejs/kit'; import { query } from '$app/server'; import * as db from '$lib/server/database'; export const getPosts = query(async () => { /* ... */ }); export const getPost = query(v.string(), async (slug) => { const [post] = await db.sql` SELECT * FROM post WHERE slug = ${slug} `; if (!post) error(404, 'Not found'); return post; }); ``` L'argument et la valeur de retour sont tous deux sérialisés avec [devalue](https://github.com/sveltejs/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](hooks#Universal-hooks-transport)) en plus du JSON. ### Mettre les queries à jour [!VO]Refreshing queries Toute query peut être mise à jour via sa méthode `refresh` : ```svelte ``` > [!NOTE] 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 comme `const > 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`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) actuel... ```ts /// file: src/routes/blog/data.remote.js // @filename: ambient.d.ts declare module '$lib/server/database' { export function sql(strings: TemplateStringsArray, ...values: any[]): Promise; } declare module '$lib/server/auth' { interface User { name: string; } /** * Récupère les informations de l'utilisateur à partir des cookies, en utilisant `getRequestEvent` */ export function getUser(): Promise; } // @filename: index.js // ---cut--- import * as v from 'valibot'; import { error, redirect } from '@sveltejs/kit'; import { query, form } from '$app/server'; import * as db from '$lib/server/database'; import * as auth from '$lib/server/auth'; export const getPosts = query(async () => { /* ... */ }); export const getPost = query(v.string(), async (slug) => { /* ... */ }); export const createPost = form(async (data) => { // Vérifie si l'utilisateur est connecté const user = await auth.getUser(); if (!user) error(401, 'Unauthorized'); const title = data.get('title'); const content = data.get('content'); // Vérifie si les données sont valides if (typeof title !== 'string' || typeof content !== 'string') { error(400, 'Title and content are required'); } const slug = title.toLowerCase().replace(/ /g, '-'); // Insère en base de données await db.sql` INSERT INTO post (slug, title, content) VALUES (${slug}, ${title}, ${content}) `; // Redirige vers la page nouvellement créée redirect(303, `/blog/${slug}`); }); ``` ... et renvoie un objet qui peut être distribué sur un élément `
`. Le callback est appelée à chaque fois que le formulaire est soumis. ```svelte

Créer un nouvel article

``` 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 [!VO]Single-flight mutations 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 : ```js import * as v from 'valibot'; import { error, redirect } from '@sveltejs/kit'; import { query, form } from '$app/server'; const slug = ''; // ---cut--- export const getPosts = query(async () => { /* ... */ }); export const getPost = query(v.string(), async (slug) => { /* ... */ }); export const createPost = form(async (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 getPosts().refresh();+++ // Redirige vers la page nouvellement créée redirect(303, `/blog/${slug}`); }); ``` La seconde est de conduire la mutation single-flight depuis le client, ce que nous verrons dans la section sur [`enhance`](#form-enhance). ### Renvois et redirections [!VO]Returns and redirects L'exemple ci-dessus utilise [`redirect(...)`](@sveltejs-kit#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` : ```ts /// file: src/routes/blog/data.remote.js // @filename: ambient.d.ts declare module '$lib/server/database' { export function sql(strings: TemplateStringsArray, ...values: any[]): Promise; } declare module '$lib/server/auth' { interface User { name: string; } /** * Récupère les informations de l'utilisateur à partir des cookies, en utilisant `getRequestEvent` */ export function getUser(): Promise; } // @filename: index.js import * as v from 'valibot'; import { error, redirect } from '@sveltejs/kit'; import { query, form } from '$app/server'; import * as db from '$lib/server/database'; import * as auth from '$lib/server/auth'; export const getPosts = query(async () => { /* ... */ }); export const getPost = query(v.string(), async (slug) => { /* ... */ }); // ---cut--- export const createPost = form(async (data) => { // ... return { success: true }; }); ``` ```svelte

Créer un nouvel article

{#if createPost.result?.success}

Publié avec succès !

{/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. > [!NOTE] 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` : ```svelte

Créer un nouvel article

{ try { await submit(); form.reset(); showToast('Publié avec succès !'); } catch (error) { showToast('Oh non ! Quelque chose s\'est mal passé !'); } })}>
``` Le callback reçoit l'élément `form`, la `data` qu'il contient, et une fonction `submit`. Pour activer les [mutations single-flight](#form-Single-flight-mutations) 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 : ```ts import type { RemoteQuery, RemoteQueryOverride } from '@sveltejs/kit'; interface Post {} declare function submit(): Promise & { updates(...queries: Array | RemoteQueryOverride>): Promise; } declare function getPosts(): RemoteQuery; // ---cut--- await submit().updates(getPosts()); ``` Nous pouvons aussi _surcharger_ les données courantes pendant que la soumission est en cours de traitement : ```ts import type { RemoteQuery, RemoteQueryOverride } from '@sveltejs/kit'; interface Post {} declare function submit(): Promise & { updates(...queries: Array | RemoteQueryOverride>): Promise; } declare function getPosts(): RemoteQuery; declare const newPost: Post; // ---cut--- await submit().updates( getPosts().withOverride((posts) => [newPost, ...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`](https://developer.mozilla.org/fr/docs/Web/HTML/Reference/Elements/form#attributs_pour_lenvoi_de_formulaires) de l'élément `
`, 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 `
``` 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ù. > [!NOTE] 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é](#query-Query-arguments) en fournissant un [Standard Schema](https://standardschema.dev) en tant que premier argument à `command`. ```ts /// file: likes.remote.js // @filename: ambient.d.ts declare module '$lib/server/database' { export function sql(strings: TemplateStringsArray, ...values: any[]): Promise; } // @filename: index.js // ---cut--- import * as v from 'valibot'; import { query, command } from '$app/server'; import * as db from '$lib/server/database'; export const getLikes = query(v.string(), async (id) => { const [row] = await db.sql` SELECT likes FROM item WHERE id = ${id} `; return row.likes; }); export const addLike = command(v.string(), async (id) => { await db.sql` UPDATE item SET likes = likes + 1 WHERE id = ${id} `; }); ``` Vous pouvez maintenant simplement appeler `addLike`, depuis (par exemple) un gestionnaire d'évènement : ```svelte

likes : {await getLikes(item.id)}

``` > [!NOTE] Les commandes ne peuvent pas être appelées lors du rendu. ### Mutations single-flight [!VO]Single-flight mutations 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... ```js /// file: likes.remote.js // @filename: ambient.d.ts declare module '$lib/server/database' { export function sql(strings: TemplateStringsArray, ...values: any[]): Promise; } // @filename: index.js // ---cut--- import * as v from 'valibot'; import { query, command } from '$app/server'; import * as db from '$lib/server/database'; // ---cut--- export const getLikes = query(v.string(), async (id) => { /* ... */ }); export const addLike = command(v.string(), async (id) => { await db.sql` UPDATE item SET likes = likes + 1 WHERE id = ${id} `; +++getLikes(id).refresh();+++ }); ``` ... soit lorsque nous l'exécutons : ```ts import { RemoteCommand, RemoteQueryFunction } from '@sveltejs/kit'; interface Item { id: string } declare const addLike: RemoteCommand; declare const getLikes: RemoteQueryFunction; declare function showToast(message: string): void; declare const item: Item; // ---cut--- try { await addLike(item.id).+++updates(getLikes(item.id))+++; } catch (error) { showToast('Quelque chose s\'est mal passé !'); } ``` Comme précédemment, nous pouvons utiliser `withOverride` pour faire des mises à jour optimistes : ```ts import { RemoteCommand, RemoteQueryFunction } from '@sveltejs/kit'; interface Item { id: string } declare const addLike: RemoteCommand; declare const getLikes: RemoteQueryFunction; declare function showToast(message: string): void; declare const item: Item; // ---cut--- try { await addLike(item.id).updates( getLikes(item.id).+++withOverride((n) => n + 1)+++ ); } catch (error) { 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. ```js /// file: src/routes/blog/data.remote.js // @filename: ambient.d.ts declare module '$lib/server/database' { export function sql(strings: TemplateStringsArray, ...values: any[]): Promise; } // @filename: index.js // ---cut--- import { prerender } from '$app/server'; import * as db from '$lib/server/database'; export const getPosts = prerender(async () => { const posts = await db.sql` SELECT title, slug FROM post ORDER BY published_at DESC `; return 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`](https://developer.mozilla.org/fr/docs/Web/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. > [!NOTE] Lorsque la page entière a `export const prerender = true`, vous ne pouvez pas utiliser de > queries, puisqu'elles sont dynamiques. ### Arguments de prerender [!VO]Prerender arguments Comme avec les queries, les fonctions `prerender` peuvent accepter un argument, qui devrait être [validé](#query-Query-arguments) avec un [Standard Schema](https://standardschema.dev/) : ```js /// file: src/routes/blog/data.remote.js // @filename: ambient.d.ts declare module '$lib/server/database' { export function sql(strings: TemplateStringsArray, ...values: any[]): Promise; } // @filename: index.js // ---cut--- import * as v from 'valibot'; import { error } from '@sveltejs/kit'; import { prerender } from '$app/server'; import * as db from '$lib/server/database'; export const getPosts = prerender(async () => { /* ... */ }); export const getPost = prerender(v.string(), async (slug) => { const [post] = await db.sql` SELECT * FROM post WHERE slug = ${slug} `; if (!post) error(404, 'Not found'); return post; }); ``` Tout appel à `getPost(...)` identifié par le crawler de SvelteKit lors du [pré-rendu des pages](page-options#prerender) sera automatiquement enregistré, mais vous pouvez également préciser avec quelles valeurs chaque appel devrait être fait en utilisant l'option `inputs` : ```js /// file: src/routes/blog/data.remote.js import * as v from 'valibot'; import { prerender } from '$app/server'; // ---cut--- export const getPost = prerender( v.string(), async (slug) => { /* ... */ }, { inputs: () => [ 'premier-article', 'deuxième-article', 'troisième-article' ] } ); ``` > [!NOTE] 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 : ```js /// file: src/routes/blog/data.remote.js import * as v from 'valibot'; import { prerender } from '$app/server'; // ---cut--- export const getPost = prerender( v.string(), async (slug) => { /* ... */ }, { +++dynamic: true+++, inputs: () => [ 'premier-article', 'deuxième-article', 'troisième-article' ] } ); ``` ## Gestion des erreurs de validation [!VO]Handling validation errors 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](https://http.dog/400). Vous pouvez contrôler le message en implémentant le hook serveur [`handleValidationError`](hooks#Server-hooks-handleValidationError), qui, comme [`handleError`](hooks#Shared-hooks-handleError), doit renvoyer un objet [`App.Error`](errors#Type-safety) (qui est défini par défaut à `{ message: string }`) : ```js /// file: src/hooks.server.ts /** @type {import('@sveltejs/kit').HandleValidationError} */ export function handleValidationError({ event, issues }) { return { 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 : ```ts /// file: data.remote.ts import { query } from '$app/server'; export const getStuff = query('unchecked', async ({ id }: { 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 }); ``` > [!NOTE] `form` n'accepte pas de schéma puisque vous recevrez toujours un objet `FormData`. Vous > êtes libres de parser et valider ces données comme bon vous semble. ## Utiliser `getRequestEvent` [!VO]Using `getRequestEvent` Dans une fonction `query`, `form` ou `command`, vous pouvez utiliser [`getRequestEvent`](/docs/kit/$app-server#getRequestEvent) pour obtenir l'objet [`RequestEvent`](@sveltejs-kit#RequestEvent) courant. Ceci permet de construire simplement des abstractions pour interagir avec des cookies, par exemple : ```ts /// file: user.remote.ts import { getRequestEvent, query } from '$app/server'; import { findUser } from '$lib/server/database'; export const getProfile = query(async () => { const user = await getUser(); return { name: user.name, avatar: user.avatar }; }); // cette fonction pourrait être appelée depuis plusieurs endroits function getUser() { const { cookies, locals } = getRequestEvent(); locals.userPromise ??= findUser(cookies.get('session_id')); return await 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 [!VO]Redirects Au sein d'une fonction `query`, `form` ou `prerender`, il est possible d'utiliser la fonction [`redirect(...)`](/docs/kit/@sveltejs-kit#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.)