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:
2026-01-08 11:21:30 -03:00
parent 0fbd56d39e
commit 91925a9dea
6 changed files with 490 additions and 4 deletions

View File

@@ -0,0 +1,143 @@
---
alwaysApply: true
---
# Banco de Dados: Frontend → Remote Functions (SvelteKit) + Drizzle (Server)
Neste projeto, **o frontend NÃO se comunica diretamente com o banco**. Toda leitura/escrita no DB deve acontecer **no servidor**, usando **SvelteKit Remote Functions** (`*.remote.ts`) e **Drizzle ORM**.
## Regras obrigatórias
- **NUNCA** importe/instancie `db` ou tabelas do Drizzle no client:
- Proibido em `.svelte`, `+page.ts`, `+layout.ts`, `src/lib/**` (código compartilhado) etc.
- `db` e o schema (`$lib/server/db/**`) são **server-only**.
- Qualquer acesso ao DB deve ficar em **`*.remote.ts` co-localizado com a rota**.
- Remote functions devem:
- Importar `query`, `command`, `form` de `$app/server`
- Validar input com `z` de `zod/v4`
- Usar Drizzle via `db` de `$lib/server/db` e tabelas de `$lib/server/db/schema`
- O frontend deve consumir **apenas** as remote functions, e usar:
- `$derived(remoteQuery(args))` para promessas reativas
- `.updates(remoteQuery(args))` após `command()`/`form()` para invalidar seletivamente
## Exemplo 1: Query (leitura) com Drizzle
```ts
// src/routes/admin/users/users.remote.ts
import { query } from '$app/server';
import z from 'zod/v4';
import { ilike, or } from 'drizzle-orm';
import { db } from '$lib/server/db';
import { user } from '$lib/server/db/schema';
export const listUsers = query(
z.object({
search: z.string().trim().min(1).optional()
}),
async ({ search }) => {
const where =
search
? or(ilike(user.name, `%${search}%`), ilike(user.email, `%${search}%`))
: undefined;
return await db
.select({
id: user.id,
name: user.name,
email: user.email,
createdAt: user.createdAt
})
.from(user)
.where(where);
}
);
```
## Exemplo 2: Command (mutação) com Drizzle + invalidação
```ts
// src/routes/admin/users/users.remote.ts
import { command } from '$app/server';
import z from 'zod/v4';
import { eq } from 'drizzle-orm';
import { db } from '$lib/server/db';
import { user } from '$lib/server/db/schema';
export const deleteUser = command(z.object({ id: z.string().min(1) }), async ({ id }) => {
await db.delete(user).where(eq(user.id, id));
// Se fizer sentido, você pode forçar refresh global da query para todos os clientes:
// await listUsers({ search: undefined }).refresh();
});
```
## Exemplo 3: Form (submit) com Drizzle
```ts
// src/routes/admin/users/users.remote.ts
import { form } from '$app/server';
import z from 'zod/v4';
import { eq } from 'drizzle-orm';
import { db } from '$lib/server/db';
import { user } from '$lib/server/db/schema';
export const updateUserName = form(
z.object({
id: z.string().min(1),
name: z.string().trim().min(2).max(100)
}),
async ({ id, name }) => {
await db.update(user).set({ name }).where(eq(user.id, id));
return { success: true as const };
}
);
```
## Exemplo 4: Frontend consumindo remote functions (sem acesso ao DB)
```svelte
<!-- src/routes/admin/users/+page.svelte -->
<script lang="ts">
import { listUsers, deleteUser, updateUserName } from './users.remote';
let search = $state('');
let usersPromise = $derived(listUsers({ search: search || undefined }));
</script>
<input bind:value={search} placeholder="Buscar por nome ou email" />
{#await usersPromise then users}
<ul>
{#each users as u}
<li>
<strong>{u.name}</strong> ({u.email})
<button
type="button"
onclick={async () => {
await deleteUser({ id: u.id }).updates(listUsers({ search: search || undefined }));
}}
>
Remover
</button>
</li>
{/each}
</ul>
{/await}
<form
{...updateUserName.enhance(async ({ submit }) => {
await submit().updates(listUsers({ search: search || undefined }));
})}
>
<!-- inputs do updateUserName.fields.* -->
<button type="submit">Salvar</button>
</form>
```
## Checklist rápido (antes de abrir PR)
- A leitura/escrita no DB está **somente** em `*.remote.ts`?
- O `.svelte` importa apenas remote functions (e não `db`/schema)?
- Inputs das remote functions estão validados com `zod/v4`?
- Mutação faz `.updates()` (ou `.refresh()` server-side) quando precisa atualizar listas?

View File

@@ -0,0 +1,28 @@
---
globs: src/lib/server/db/schema.ts
description: Fluxo de migrações ao editar o schema do banco de dados
---
## Fluxo obrigatório ao alterar [schema.ts](mdc:src/lib/server/db/schema.ts)
Sempre que realizar edits em `src/lib/server/db/schema.ts`, execute este fluxo de migrações:
1. Empurrar alterações usando uma base de teste local (porta 5433):
```bash
DATABASE_URL=postgres://postgres:postgres@localhost:5433/saas_test_db bun run drizzle-kit push
```
2. Sincronizar a base padrão do projeto:
```bash
bun run db:push
```
3. Aviso para commits na branch `main`:
Antes de abrir PR/commit para `main`, gere os artefatos do Drizzle:
```bash
bun run db:generate
```

View File

@@ -0,0 +1,116 @@
---
description: Guia detalhado de uso do modo experimental async no Svelte 5
alwaysApply: false
---
# Guia: Modo Async Experimental no Svelte 5
Este documento detalha como utilizar o modo `experimental.async` ativado neste projeto.
## 🛠 Como Habilitar
Já está configurado no seu `svelte.config.js`:
```javascript
compilerOptions: {
experimental: {
async: true;
}
}
```
## 📖 Regras de Uso
### 1. Top-Level Await
Agora você pode usar `await` diretamente no seu `<script>`. O componente pausará a renderização até que a promessa seja resolvida.
**✅ Recomendado:**
```svelte
<script>
const data = await fetch('/api/data').then((r) => r.json());
</script>
<p>{data.message}</p>
```
### 2. $derived Assíncrono
Valores derivados podem aguardar promessas. Eles se tornam reativos: se uma dependência dentro do `$derived` mudar, a promessa é disparada novamente e o valor é atualizado automaticamente.
**✅ Exemplo:**
```javascript
let id = $state(1);
let user = $derived(await getUser(id));
```
### 3. Estados de Carregamento (Loading)
Como o componente "suspende", precisamos de uma forma de mostrar que algo está carregando.
- **Para o carregamento inicial:** Use `<svelte:boundary>`.
- **Para atualizações reativas:** Use `$effect.pending()`.
```svelte
<svelte:boundary>
<AsyncComponent />
{#snippet pending()}
<p>Carregando dados iniciais...</p>
{/snippet}
</svelte:boundary>
```
---
## 🚫 O que NÃO fazer
### ❌ Async no $effect
O Svelte não permite `async` diretamente em `$effect`. Se precisar de lógica assíncrona lá, use uma IIFE (Immediately Invoked Function Expression).
**Errado:**
```javascript
$effect(async () => {
await doSomething();
});
```
**Correto:**
```javascript
$effect(() => {
(async () => {
await doSomething();
})();
});
```
### ❌ Evite Loops de Suspensão
Não use `await` em valores que mudam muitas vezes por segundo, pois isso fará o componente entrar e sair do estado de "pending" constantemente, prejudicando a UX.
---
## 🔗 Integração com Remote Functions
Este projeto usa `remoteFunctions`. A melhor forma de usá-las com o modo async é:
```svelte
<script>
import { listItems } from './data.remote';
let filter = $state('');
// A query é chamada e o resultado é 'awaitado' automaticamente
let items = $derived(await listItems({ filter }));
</script>
{#if $effect.pending()}
<progress class="progress"></progress>
{/if}
{#each items as item}
<div>{item.name}</div>
{/each}
```

View File

@@ -0,0 +1,28 @@
---
trigger: glob
globs: *.svelte,*.svelte.ts
---
You are able to use the Svelte MCP server, where you have access to comprehensive Svelte 5 and SvelteKit documentation. Here's how to use the available tools effectively:
## Available MCP Tools:
### 1. list-sections
Use this FIRST to discover all available documentation sections. Returns a structured list with titles, use_cases, and paths.
When asked about Svelte or SvelteKit topics, ALWAYS use this tool at the start of the chat to find relevant sections.
### 2. get-documentation
Retrieves full documentation content for specific sections. Accepts single or multiple sections.
After calling the list-sections tool, you MUST analyze the returned documentation sections (especially the use_cases field) and then use the get-documentation tool to fetch ALL documentation sections that are relevant for the user's task.
### 3. svelte-autofixer
Analyzes Svelte code and returns problems and suggestions.
You MUST use this tool whenever you write Svelte code before submitting it to the user. Keep calling it until no problems or suggestions are returned. Remember that this does not eliminate all lint errors, so still keep checking for lint errors before proceeding.
### 4. playground-link
Generates a Svelte Playground link with the provided code.
After completing the code, ask the user if they want a playground link. Only call this tool after user confirmation and NEVER if code was written to files in their project.

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

34
.vscode/settings.json vendored
View File

@@ -1,5 +1,31 @@
{
"files.associations": {
"*.css": "tailwindcss"
}
}
"eslint.useFlatConfig": true,
"eslint.workingDirectories": [
{
"pattern": "apps/*"
},
{
"pattern": "packages/*"
}
],
"eslint.validate": [
"javascript",
"typescript",
"svelte"
],
"eslint.options": {
"cache": true,
"cacheLocation": ".eslintcache"
},
"editor.formatOnSave": true,
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[jsonc]": {
"editor.defaultFormatter": "vscode.json-language-features"
},
"[svelte]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"editor.tabSize": 2
}