# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Project Overview MeshiTrack is a self-hosted medicine & nutrition management platform. It is a TypeScript monorepo with three packages: `packages/api` (Fastify backend), `packages/web` (Next.js frontend), and `packages/shared` (types + Zod schemas used by both). Medicine tracking is implemented first (Phases 1-4), followed by food tracking (Phases 5-9). ## Commands ### Root (all packages via Turborepo) ```bash npm run dev # Start all services in dev mode npm run build # Build all packages (shared → api/web) npm run test # Run all tests npm run test:cov # Run all tests with coverage npm run lint # Lint all packages npm run lint-fix # Auto-fix lint issues npm run typecheck # Type-check all packages npm run clean # Remove build artifacts npm run seed # Seed the database (delegates to packages/api) ``` ### Single package ```bash npm run test -w packages/api # Run API tests npm run test:cov -w packages/api # API tests with coverage npm run test -- --watch -w packages/api # Watch mode npm run dev -w packages/api # API dev server only ``` ### API package (packages/api) ```bash npm run dev # tsx watch src/main.ts npm run build # tsc npm run seed # tsx src/scripts/seed.ts ``` ### Docker ```bash docker compose -f docker/docker-compose.yml up -d # Start all services docker compose -f docker/docker-compose.yml down # Stop all services ``` ## Architecture ### Monorepo Structure - **`packages/shared`** — Single source of truth for all domain types, enums, and Zod v4 schemas. Consumed by both `api` and `web`. Must be pure TypeScript with no Node.js, browser, or framework dependencies. - **`packages/api`** — Fastify 5 backend. ESM-only, TypeScript strict. Uses Awilix for DI, Mongoose 9 for MongoDB, jose 6 for JWT verification. - **`packages/web`** — Next.js 16 (React 19) frontend. App Router, Tailwind CSS 4. ### API Layer Architecture (Fastify + Awilix) The API follows a **routes → services → repositories** pattern with Awilix constructor-injection DI: - Each domain feature lives in `src/modules//` with files: `*.routes.ts`, `*.service.ts`, `*.repository.ts` - Route plugins use `fastify-plugin` (`fp()`) to export routes and register Awilix dependencies - Services receive dependencies via destructured constructor: `constructor({ productsRepository }: { productsRepository: ProductsRepository })` - Resolve services per-request via `request.diScope.resolve('serviceName')` - All Mongoose read queries must use `.lean().exec()` Plugin registration order in `main.ts`: security → compression → swagger → DI container → database → auth → household guard → route modules. ### Domain Modules **Medicine domain** (Phases 1-4): `medicines/`, `medicine-products/`, `cabinet/`, `regimens/`, `organizer/`, `medicine-prices/`, `refills/` **Food domain** (Phases 5-9): `products/`, `recipes/`, `pantry/`, `meal-plans/`, `grocery/` **Shared**: `health/`, `users/`, `households/`, `stores/`, `llm/` ### Multi-tenancy Every domain document is scoped to a `householdId`. A Fastify `preHandler` hook validates the `householdId` from the URI against the user's `householdIds[]` JWT claim. **Every data query must filter by `householdId`.** Routes can opt out with `config: { public: true }` (skips auth) or `config: { skipHousehold: true }` (skips household validation). ### Auth Keycloak is the OIDC provider. The API verifies JWTs via `jose`. The custom Keycloak protocol mapper injects `householdIds[]` into the JWT claims. ### Shared Package Rules - All domain types and Zod schemas live here — never duplicate types across packages - Import from `'zod/v4'` (not `'zod'`) - Use `z.enum()` for enums, `z.email()` / `z.url()` as top-level calls - Every directory has a barrel `index.ts` - Use `import type` for type-only imports ### Pagination All list endpoints use cursor-based pagination. **Never use `skip()`** on MongoDB queries. Response shape: ```typescript { data: T[], pagination: { cursor: string | null, hasMore: boolean, total?: number } } ``` ### Error Handling Services throw `AppError` subclasses (`NotFoundError`, `ConflictError`, `ForbiddenError`, etc.). The global Fastify error handler maps them to the standard `ApiError` response shape (`statusCode`, `error`, `message`, `timestamp`, `path`). ## Key Rules 1. **No `any` types** — use `unknown` + Zod validation at boundaries 2. **ESM everywhere** — `"type": "module"`, `.js` extensions on all imports, `import type` for type-only 3. **Cursor-based pagination only** — never `skip()` for large collections 4. **Zod v4** — import from `'zod/v4'` 5. **`.lean().exec()`** on all Mongoose read queries 6. **`householdId` filter** on every domain query — this is the multi-tenancy boundary 7. **No emojis** — never use emoji characters in source code, UI text, console output, or documentation 8. **`npx` is banned** — never run `npx` for any reason. Use `npm run