## Project - Book/library management app with Kobo device support. - pnpm monorepo: `server/` (NestJS), `client/` (Vue 3), `packages/types/` (shared types). - Requires Node >= 24, pnpm >= 9. **Local dev setup:** 1. `docker compose up -d` - start PostgreSQL 2. `cp server/.env.example server/.env` - first time only 3. `pnpm install` - install all deps 4. `cd server && pnpm db:migrate` - apply migrations 5. `pnpm dev` - start server + client concurrently ## Scale Expectations - BookOrbit is designed for large personal libraries: assume tens of thousands of books per user. - High-volume paths include library browsing, search/filtering, imports, filesystem scans, metadata refresh, cover handling, Kobo sync, and bulk edits. - Do not implement features that only work for small libraries. Avoid unbounded queries, loading entire libraries into memory, returning full collections to the frontend, N+1 query patterns, or expensive client-side processing over full result sets. - Use user-scoped queries, pagination or batching, targeted field selection, appropriate indexes, bounded concurrency, and virtualization for long rendered lists where needed. - Long-running bulk work should have clear progress, useful logs, bounded memory use, and resumable or idempotent behavior where practical. ## Backend Conventions - NestJS 11 with Fastify (`NestFastifyApplication`). Global API prefix: `/api/v1`. - Constructor injection via `constructor(private readonly service: SomeService)`. Use `@Inject(DB)` for non-standard providers (e.g., the Drizzle instance). - Database: PostgreSQL via Drizzle ORM. Use `pgTable()` schema definitions and type inference via `typeof table.$inferSelect` / `$inferInsert` - never write manual type aliases for DB rows. - Config via `@nestjs/config` with named configs (`registerAs()`). Three configs: `appConfig`, `dbConfig`, `authConfig`. Always inject the typed config, never read `process.env` directly in services. - Validation: DTOs use `class-validator` decorators. `ValidationPipe` is global with `whitelist: true`, `forbidNonWhitelisted: true`, `transform: true`. - Error handling: `GlobalExceptionFilter` is registered globally. Throw standard NestJS `HttpException` subclasses (e.g., `NotFoundException`, `BadRequestException`) - never throw raw `Error`. - Module structure: one feature = one module under `server/src/modules/`. Each module has `controller`, `service`, `module`, and `dto/` subfolder. - Shared utilities go in `server/src/common/` (filters, guards, decorators, pipes). - Testing: Vitest + `@nestjs/testing`. All test files use `.test.ts` extension. Use `Test.createTestingModule()` for unit tests. E2E tests go in `server/test/`. Use `vi.fn()`, `vi.mock()`, `vi.spyOn()` - never `jest.*`. ## Logging Conventions - Use this message format for service and integration logs: `[event] [phase] key=value ... - short message` - Valid phases are: - `[start]` - `[end]` - `[fail]` - Do not use dotted phase style like `fetch.start` / `fetch.end` / `fetch.fail`. - Keep one stable `event` per operation (for example `book.refresh_metadata`, `author.bulk_refresh_metadata`). When to add start/end/fail logs: - Non-trivial operations: - batch loops - external API/provider calls - multi-step DB work (fan-out queries, complex writes) - file system operations - orchestration/background jobs - destructive operations (delete/merge/bulk update) When to avoid start/end logs: - Chatty hot paths (simple reads, access checks, tiny helper methods) - very frequent per-item loops unless there is an error - add fail logs in these paths if needed, and use threshold-based logs for slow reads Required fields: - `[start]`: primary IDs + key input flags - `[end]`: primary IDs + `durationMs` + outcome counters/flags - `[fail]`: primary IDs + `durationMs` + `errorClass` + `error=""` Key rules: - Keep key order stable: IDs -> inputs -> outcomes/errors - Keep logs single-line - Do not log secrets, tokens, raw DTO payloads, or large blobs - Keep `error` short and sanitized **Always use `sanitizeLogValue()` from `server/src/common/utils/log-sanitize.utils` for any dynamic value embedded inside a quoted log field (e.g., `error="..."`, `path="..."`, `message="..."`).** Never inline `.replace(/"/g, '\\"')` or similar partial escaping - it misses backslashes and triggers CodeQL `js/incomplete-sanitization` alerts. Examples: - `[book.refresh_metadata] [start] bookId=1287 userId=42 preview=false - refresh metadata started` - `[book.refresh_metadata] [end] bookId=1287 durationMs=914 updatedFields=7 coverDownloaded=true - refresh metadata completed` - `[book.refresh_metadata] [fail] bookId=1287 durationMs=10023 errorClass=AbortError error="provider timeout" - refresh metadata failed` ## Frontend Conventions - Vue 3 Composition API with `