---
trigger: glob
globs: src/**/*.ts
---

# Elysia (Bun)

## Commands
`bun run dev` (`bun --watch src/index.ts`) · `bun test` · `bunx tsc --noEmit` · build with `bun build` only if the repo does.

## Structure
- `src/index.ts` creates the app and `.listen()`s; features are Elysia plugins in `src/modules/<feature>/index.ts` (`new Elysia({ prefix: '/orders', name: 'orders' })`) mounted with `.use()`. Business logic lives in plain service functions, not inside handlers.
- Keep method chaining intact: Elysia infers types through the chain, so register routes on the same instance rather than mutating it in separate statements.
- Export `type App = typeof app` for Eden Treaty clients; frontend code calls the API through `treaty<App>(url)` instead of hand-written fetch wrappers.

## Validation and errors
- Every route declares schemas with `t` (`body`, `query`, `params`, `headers`, and `response` for each status). Don't hand-validate inside handlers.
- Reuse models via `.model({ 'order.create': t.Object({...}) })` and reference them by name.
- Throw with `error(status, body)` (or `status()` in newer versions, per the installed version's docs) for expected failures; handle unexpected ones in a single `.onError` that logs and returns a safe message without stack traces.

## Hooks and plugins
- Auth via a plugin using `.derive()`/`.resolve()` to attach the user, and `beforeHandle` guards on protected groups. Scope hooks deliberately (`{ as: 'scoped' }` or `'global'`); the default is local to the instance.
- Use official plugins (`@elysiajs/cors`, `@elysiajs/swagger`/openapi, `@elysiajs/jwt`) configured explicitly; restrict CORS origins.
- Read config with `Bun.env` validated at startup; secrets never hardcoded.
- Database access through the repo's client created once and provided with `.decorate()`.

## Tests
`bun test` calling `app.handle(new Request('http://localhost/orders', {...}))` or an Eden Treaty client against the app instance, covering validation failures and auth.
