Update VSCode settings: Configure ESLint with flat config, set working directories for apps and packages, enable format on save, and specify default formatters for TypeScript, JSONC, and Svelte files.
This commit is contained in:
145
.cursor/rules/svelte-remote-functions.mdc
Normal file
145
.cursor/rules/svelte-remote-functions.mdc
Normal file
@@ -0,0 +1,145 @@
|
||||
---
|
||||
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();
|
||||
```
|
||||
Reference in New Issue
Block a user