---
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();
```
