# SvelteKit + PostgreSQL + Drizzle ORM — AI Agent Guidelines & Architecture Rules

> Production guidelines for SvelteKit form actions and load functions, Drizzle ORM over PostgreSQL, and Svelte 5 runes-based reactivity.
> Technologies: SvelteKit, Svelte, PostgreSQL, Supabase, Drizzle, TypeScript, Tailwind CSS

---

## AGENTS.md
```markdown
# Project Architecture & Guidelines (SvelteKit + PostgreSQL + Drizzle)

## 1. System Architecture
- **Framework**: SvelteKit (Svelte 5, runes-based reactivity `$state`/`$derived`/`$effect`).
- **Database & ORM**: PostgreSQL with Drizzle ORM (`drizzle-orm/postgres-js`, `drizzle-kit`) — for solo builders and small teams, point `DATABASE_URL` at a managed provider (Supabase or Neon, both have a usable free tier) instead of operating your own Postgres instance; self-host only once you need full infra control.
- **Rendering**: Server-side rendering by default; `+page.server.ts` for data loading, form actions for mutations.
- **Validation**: TypeScript strict mode + Zod for form and API input validation.

## 2. Load Functions & Data Fetching
- Fetch data exclusively in `+page.server.ts` / `+layout.server.ts` `load()` functions — never in `onMount` for data the page needs to render.
- Return typed data from `load()`; SvelteKit infers `PageData`/`LayoutData` automatically via `./$types`.
- Use `depends()`/`invalidate()` for fine-grained cache invalidation instead of full page reloads after mutations.

## 3. Form Actions & Mutations
- All writes go through named form actions in `+page.server.ts` (`export const actions = { create: async ({ request }) => {...} }`), not client-side `fetch` to API routes, unless the mutation is triggered outside a `<form>`.
- Validate `await request.formData()` with Zod before touching the database; return `fail(400, { errors })` on validation failure so the form can show inline errors without JavaScript.
- Use progressive enhancement (`use:enhance`) so forms work with JS disabled and get optimistic UI when enabled.

## 4. Database Client & Migrations
- Singleton Drizzle client in `src/lib/server/db/index.ts`, imported only from `+page.server.ts` / `+server.ts` files — never from `.svelte` components (build fails loudly if `postgres` leaks into the client bundle, which is the intended guardrail).
  ```typescript
  import { drizzle } from 'drizzle-orm/postgres-js';
  import postgres from 'postgres';
  import { DATABASE_URL } from '$env/static/private';
  import * as schema from './schema';

  // DATABASE_URL: Supabase/Neon pooled connection string for solo/small-team
  // deploys — skips operating a Postgres server entirely.
  const client = postgres(DATABASE_URL, { prepare: false });
  export const db = drizzle(client, { schema });
  ```
- Generate migrations with `drizzle-kit generate`; apply with `drizzle-kit migrate`. Never hand-edit generated SQL.

## 5. Common Pitfalls / Coding Standards
- ❌ Importing `$lib/server/*` modules from client-reachable code — SvelteKit throws at build time, but only if the import chain is direct; re-exporting through a shared barrel file can hide the leak.
- ❌ Fetching in `onMount` for data needed on first paint, causing layout shift and losing SSR benefits.
- ✅ Use `$state`/`$derived` runes for component-local reactivity; avoid the legacy `$:` reactive statement style in new code.

## 6. Testing Conventions
- Vitest for unit tests on `load()` functions and Drizzle query helpers, with a `pg` test container or Supabase local dev instance.
- Playwright for e2e coverage of form actions, including the no-JS progressive-enhancement path.
- Run `svelte-check` and `tsc --noEmit` as required checks — Svelte's type-checking catches prop/slot mismatches that plain `tsc` misses.

## 7. Git Workflow & PR Conventions
- Conventional Commits scoped to the route, e.g. `fix(routes/projects): validate slug uniqueness in create action`.
- Generated Drizzle migrations ship in the same PR as the schema change.
- Require `svelte-check`, `tsc --noEmit`, and `bun run build` green before merge.
- Squash-merge; never `drizzle-kit push` against a shared or production database from a feature branch.
```

---

## CLAUDE.md
```markdown
# CLAUDE.md — SvelteKit + PostgreSQL + Drizzle

Refer to @AGENTS.md for complete database configuration and architectural constraints.

## Common Commands
- `bun run dev` - Start local SvelteKit development server
- `bun run build` - Build production SvelteKit bundle
- `bun run check` - Run `svelte-check` + `tsc --noEmit`
- `bun run db:generate` - Generate Drizzle SQL migration files
- `bun run db:migrate` - Apply pending migrations to PostgreSQL

## Claude Specific Directives
- Only import `$lib/server/db` from `+page.server.ts`, `+layout.server.ts`, or `+server.ts` files.
- Prefer form actions over client-side `fetch` calls for mutations that originate from a `<form>`.
```

---

## .cursor/rules/stack.mdc
```markdown
---
description: SvelteKit + PostgreSQL + Drizzle ORM architecture rules
globs: ["**/*.svelte", "**/*.ts"]
alwaysApply: true
---

# SvelteKit + PostgreSQL + Drizzle

- SvelteKit with Svelte 5 runes (`$state`/`$derived`/`$effect`); SSR by default via `+page.server.ts` load functions.
- Drizzle ORM over `postgres-js`; singleton client in `src/lib/server/db/index.ts`, never imported from `.svelte` components.
- All writes go through named form actions with `use:enhance`; validate `formData()` with Zod, return `fail(400, {...})` on error.
- Fetch only in `load()` functions, never in `onMount`, to preserve SSR and avoid layout shift.
- `drizzle-kit generate` + `drizzle-kit migrate` only — never `drizzle-kit push` against production.
- Run `svelte-check` and `tsc --noEmit` before considering a change complete.
```

---

## Architecture Overview & Best Practices
## Architecture Overview

Standardized production guidelines for **SvelteKit** (Svelte 5), **Drizzle ORM**, and **PostgreSQL**, built around SvelteKit's server-first data loading and form action model.

### Verified Real-World Adoption

The SvelteKit pattern shows up as the frontend layer in production self-hosted tools like **Immich** (photo management, SvelteKit + PostgreSQL + pgvector) and **Open WebUI** (AI interface, SvelteKit + FastAPI), with **PocketBase** built around the lighter-weight Svelte (non-Kit) equivalent.

### Key Architectural Nuances

- **Server-Only Import Boundary**: SvelteKit enforces at build time that `$lib/server/*` modules can't leak into client-reachable code, making credential leaks a build failure instead of a runtime surprise.
- **Progressive Enhancement by Default**: Form actions work without JavaScript and are enhanced with `use:enhance`, unlike SPA patterns that require JS for any interaction to function at all.