146 lines
3.3 KiB
Plaintext
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();
|
|
```
|