Files
sesp-site/.cursor/rules/db-remote-functions-drizzle.mdc

143 lines
4.1 KiB
Plaintext

---
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?