Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,27 @@ jobs:
- run: bun install --frozen-lockfile
- run: bun run typecheck

test:
name: Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- run: bun install --frozen-lockfile
- name: Run Tests
env:
APP_NAME: ${{ secrets.APP_NAME }}
ENV: ${{ secrets.ENV }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}
BETTER_AUTH_SECRET: ${{ secrets.BETTER_AUTH_SECRET }}
BETTER_AUTH_URL: ${{ secrets.BETTER_AUTH_URL }}
AXIOM_TOKEN: ${{ secrets.AXIOM_TOKEN }}
AXIOM_DATASET: ${{ secrets.AXIOM_DATASET }}
SERVICE_VERSION: ${{ secrets.SERVICE_VERSION }}
run: bun run test

build:
name: Build
runs-on: ubuntu-latest
Expand Down
161 changes: 161 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
# AGENTS.md

Guidelines for AI coding agents working in this repository.

## Tech Stack

- **Runtime:** Bun
- **Frontend:** React 19 + Vite + TailwindCSS v4
- **Backend:** Hono (running on Bun)
- **Database:** PostgreSQL + Drizzle ORM
- **Auth:** Better Auth
- **Validation:** Zod
- **Linting/Formatting:** Biome
- **i18n:** i18next

## Commands

```bash
# Development
bun dev # Start full dev environment (DB, Vite, Drizzle Studio)
bun run build # Production build
bun start # Run production server

# Code Quality
bun run lint # Fix lint issues (Biome + locale check)
bun run lint:check # Check only (CI)
bun run format # Fix formatting
bun run format:check # Check only (CI)
bun run typecheck # Type-check all projects
bun run all # Run format + lint + typecheck + build

# Database
bun run db:start # Start PostgreSQL container
bun run db:push # Sync schema to DB (dev only)
bun run db:generate # Generate migrations
bun run db:migrate # Apply migrations
bun run db:regenerate-auth # Regenerate Better Auth schema
```

## Project Structure

```
web/ # Frontend (React)
components/ui/ # Reusable UI components (shadcn pattern)
lib/api.ts # Type-safe Hono RPC client
lib/auth-client.ts # Better Auth client
i18n/locales/ # Translation files

server/ # Backend (Hono)
server.tsx # Entry point & route assembly
auth.ts # Better Auth config
logger.ts # Pino logger with trace context
middleware/ # Global middleware
features/ # Feature modules (one folder per feature)
{feature}/
index.ts # Re-exports feature routes
routes/ # One file per route handler
database/
schema/auth.ts # Better Auth tables (auto-generated)
schema/app.ts # Custom tables (add your tables here)

env.ts # Environment schema (Zod)
```

## Code Style

### Formatting (Biome)
- 2-space indentation, double quotes, imports auto-organized

### TypeScript
- Strict mode enabled
- Use `type` imports: `import type { Foo } from "bar"`
- Path aliases: `@/*` (root), `~/*` (web/)

### Naming
- Files: `kebab-case.ts`
- Components: `PascalCase`
- Variables/functions: `camelCase`
- Database tables: `snake_case`

### React Components
- Use function declarations, not arrow functions for components
- Props type inline: `function Button({ ...props }: React.ComponentProps<"button">)`
- Use `cn()` from `~/lib/utils` for class merging

### Hono Routes
- One route handler per file in `server/features/{feature}/routes/`
- Use Zod validation with `zValidator`:
```typescript
import { zValidator } from "@hono/zod-validator";
import { Hono } from "hono";
import { z } from "zod";

const schema = z.object({ name: z.string().min(1) });

export const myRoute = new Hono().get(
"/",
zValidator("query", schema),
async (c) => {
const { name } = c.req.valid("query");
return c.json({ message: `Hello, ${name}!` });
}
);
```

### Database (Drizzle)
- Define tables in `server/database/schema/app.ts`
- Use `drizzle-orm/pg-core` for table definitions
- Reference auth tables from `./auth.ts` for foreign keys

### Environment Variables
- Define in `env.ts` with Zod schemas
- Access via `import env from "@/env"`
- Never hardcode secrets

### i18n
- Add translations to `web/i18n/locales/{lang}/common.ts`
- Use `useTranslation("common")` hook
- Run `bun run check-locale` to verify all keys exist in all locales

### Error Handling
- Use Zod for input validation
- Wrap pages in `<ErrorBoundary>`
- Server errors logged via `logger` from `@/server/logger`

### Logging (Server)
```typescript
import { logger } from "@/server/logger";
logger.info({ userId, action }, "User performed action");
```
Logger auto-injects traceId, spanId, userId when available.

### Tracing (OpenTelemetry)
- HTTP requests, DB queries, and fetch calls are auto-traced
- For custom spans: `import { withSpan } from "@/server/tracing"`

## Adding New Features

**Backend Route:** Create `server/features/{feature}/routes/{route-name}.ts`, re-export in `index.ts`, mount in `server/server.tsx`

**Frontend Page:** Add route to `web/router.tsx`, create component in `web/components/`

**Database Table:** Add to `server/database/schema/app.ts`, run `bun run db:push` (dev) or `db:generate && db:migrate` (prod)

## Better Auth

- Config: `server/auth.ts`
- Client: `web/lib/auth-client.ts`
- After adding plugins: `bun run db:regenerate-auth`
- Session in routes: `await auth.api.getSession({ headers: c.req.raw.headers })`

## API Client (Frontend)

Use type-safe Hono RPC:
```typescript
import { api } from "~/lib/api";
import { useMutation } from "@tanstack/react-query";

const mutation = useMutation(api["my-route"].$get.mutationOptions({}));
mutation.mutate({ query: { name: "World" } });
```
75 changes: 58 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<p align="center">
<img src="title.svg" width="100%" alt="Banner Title">
<img src="docs/assets/title.svg" width="100%" alt="Banner Title">
</p>

# Full Stack Starter
Expand Down Expand Up @@ -71,27 +71,63 @@ This command starts:
| `bun run db:start` | Start PostgreSQL container |
| `bun run db:studio` | Open Drizzle Studio |
| `bun run db:reset` | Reset database (destroys all data) |
| `bun run db:push` | Sync schema to DB (dev only, no migrations) |
| `bun run db:generate` | Generate migration files from schema changes |
| `bun run db:migrate` | Apply pending migrations |
| `bun run db:regenerate-auth` | Regenerate Better Auth schema |
| `bun run import:staging` | Import data from staging |

**Workflow:**
- **Development:** Use `db:push` for fast iteration (no migration files)
- **Staging/Production:** Use `db:generate` + `db:migrate` (tracked, reviewable changes)

Re-run `db:regenerate-auth` when adding Better Auth plugins or upgrading.

**Schema files:**
- `server/database/schema/auth.ts` - Auto-generated (safe to overwrite)
- `server/database/schema/app.ts` - Your custom tables (never overwritten)

## Project Structure

```
├── web/ # Frontend (React)
│ ├── app.tsx # Main app component
│ ├── client.tsx # Client entry point
│ ├── router.tsx # Route definitions
│ ├── components/ # UI components
│ ├── i18n/ # Internationalization
│ └── styles.css # Global styles
├── server/ # Backend (Hono)
│ ├── server.tsx # Server entry point
│ ├── logger.ts # Pino logger with trace context
│ └── db/ # Database schema & config
├── lib/ # Shared utilities
│ └── tracing.ts # OpenTelemetry helpers
├── docs/ # Documentation
├── scripts/ # Utility scripts
└── env.ts # Environment schema (Zod)
├── web/ # Frontend (React)
│ ├── app.tsx # Main app component
│ ├── client.tsx # Client entry point
│ ├── router.tsx # Route definitions
│ ├── components/ # UI components
│ ├── lib/
│ │ ├── api.ts # Hono RPC client (type-safe API calls)
│ │ └── auth-client.ts # Better Auth client
│ ├── i18n/ # Internationalization
│ └── styles.css # Global styles
├── server/ # Backend (Hono)
│ ├── server.ts # Server entry point & route assembly
│ ├── lib/ # Shared utilities
│ │ ├── auth.ts # Better Auth configuration
│ │ ├── logger.ts # Pino logger with trace context
│ │ ├── router.ts # Typed Hono router factory
│ │ ├── tracing.ts # OpenTelemetry tracing helpers
│ │ ├── request-context.ts # AsyncLocalStorage request context
│ │ └── instrumentation.ts # Node SDK setup
│ ├── middleware/ # Hono middleware
│ │ ├── auth.middleware.ts # User context middleware
│ │ └── db.middleware.ts # Database middleware
│ ├── features/ # Feature modules
│ │ ├── auth/ # Auth routes (Better Auth handler)
│ │ ├── health/ # Health check
│ │ └── demo/ # Demo routes
│ │ └── routes/ # Route handlers (one file per route)
│ └── database/
│ ├── index.ts # Drizzle connection
│ └── schema/
│ ├── auth.ts # Better Auth tables (auto-generated)
│ ├── app.ts # Your custom tables
│ └── index.ts # Re-exports all tables
├── shared/ # Shared utilities
│ └── tracing.ts # OpenTelemetry helpers
├── docs/ # Documentation
├── scripts/ # Utility scripts
└── env.ts # Environment schema (Zod)
```

## CI
Expand All @@ -110,3 +146,8 @@ GitHub Actions runs on every push and PR to `master`:
| [OpenTelemetry Guide](./docs/otel-guide.md) | How to add tracing to your code |
| [OpenTelemetry Architecture](./docs/otel-architecture.md) | Why the setup is structured this way |
| [Capacitor Guide](./docs/capacitor.md) | Building native iOS/Android apps |

## TODO

- [ ] DB sync from staging (implement `scripts/import-staging.sh`)
- [ ] Sentry frontend integration
Loading
Loading