Files
sesp-site/.cursor/rules/svelte-remote-functions.mdc

146 lines
3.3 KiB
Plaintext

---
alwaysApply: true
---
# Svelte Remote Functions (Experimental)
This project uses SvelteKit's experimental `remoteFunctions` feature. Follow these rules when working with server-side logic and data fetching.
## 1. Definition & Structure
- Logic MUST be defined in `*.remote.ts` files co-located with the route.
- Import `form`, `query`, and `command` from `$app/server`.
- Import `z` from `zod/v4`.
## 2. Creating Remote Functions
### Queries (Data Fetching)
Use `query` for reading data. It behaves like a GET request.
```typescript
import { query } from '$app/server';
import z from 'zod/v4';
export const getData = query(z.object({ id: z.string() }), async ({ id }) => {
return await db.select(...);
});
```
### Commands (Mutations)
Use `command` for direct logic execution (RPC style), typically for actions like delete, toggle, or specific updates.
```typescript
import { command } from '$app/server';
export const deleteItem = command(z.string(), async (id) => {
await db.delete(...);
});
```
### Forms (Form Submissions)
Use `form` for handling HTML form submissions with progressive enhancement and field validation.
```typescript
import { form } from '$app/server';
export const updateItem = form(schema, async (data) => {
await db.update(...);
return { success: true };
});
```
## 3. Frontend Usage
### Reactivity with Queries
Queries can be called inside `$derived` to create reactive promises that update when their arguments change.
```svelte
<script>
import { fetchItems } from './data.remote';
let search = $state('');
// This promise re-runs automatically when 'search' changes
let itemsPromise = $derived(fetchItems({ search }));
</script>
{#await itemsPromise then items}
<ul>
{#each items as item}
...
{/each}
</ul>
{/await}
```
### Executing Commands & Invalidating Data
Commands are called as functions. To refresh relevant data after a command finishes, use the `.updates()` method and pass it the query call that should be refreshed.
```svelte
<script>
import { deleteItem, fetchItems } from './data.remote';
let { search } = $props();
</script>
<button
onclick={async () => {
try {
// Execute command and refresh the items list
await deleteItem(id).updates(fetchItems({ search }));
toast.success('Deleted');
} catch (e) {
toast.error('Error');
}
}}
>
Delete
</button>
```
### Using Forms
Use `.enhance` on the form object. You can also use `.updates()` on the `submit()` result.
```svelte
<form
{...updateItem.enhance(async ({ submit }) => {
await submit().updates(fetchItems({ search }));
})}
>
<input {...updateItem.fields.name.as('text')} />
<button type="submit">Save</button>
</form>
```
## 4. Invalidation Patterns
### Client-Side (Selective)
Use `.updates(queryCall())` on the mutation result in the frontend. This is preferred for fine-grained updates.
### Server-Side (Global for that file)
You can refresh queries directly in the remote function definition in `data.remote.ts`:
```typescript
export const deleteItem = command(z.string(), async (id) => {
await db.delete(...);
// Refresh listItems query for all clients
await listItems().refresh();
});
```
### Global Refresh
If logic requires a full page refresh (all loaders), use `refreshAll()` from `$app/navigation`.
```typescript
import { refreshAll } from '$app/navigation';
// ...
await myCommand();
await refreshAll();
```