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:
143
.cursor/rules/db-remote-functions-drizzle.mdc
Normal file
143
.cursor/rules/db-remote-functions-drizzle.mdc
Normal 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?
|
||||
28
.cursor/rules/migrations.mdc
Normal file
28
.cursor/rules/migrations.mdc
Normal 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
|
||||
```
|
||||
116
.cursor/rules/svelte-async.mdc
Normal file
116
.cursor/rules/svelte-async.mdc
Normal 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}
|
||||
```
|
||||
28
.cursor/rules/svelte-guide.mdc
Normal file
28
.cursor/rules/svelte-guide.mdc
Normal 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.
|
||||
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();
|
||||
```
|
||||
34
.vscode/settings.json
vendored
34
.vscode/settings.json
vendored
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user