MeshiTrack/CLAUDE.md

6.3 KiB

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)

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

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)

npm run dev      # tsx watch src/main.ts
npm run build    # tsc
npm run seed     # tsx src/scripts/seed.ts

Docker

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/<feature>/ 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<T>('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/, purchases/, 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:

{ 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 <script> for all test, lint, build, and tool invocations. No exceptions.

Testing

  • Unit tests: Vitest, co-located with source files as *.routes.test.ts / *.service.test.ts
  • Integration tests: Vitest + mongodb-memory-server (*.integration.test.ts)
  • Component tests: React Testing Library (__tests__/*.test.tsx)
  • E2E: Playwright (root-level e2e/)
  • Use ClassName.name for describe labels (not string literals)
  • Coverage targets: 100% lines/functions/statements for packages/api and packages/shared; 90% branches
  • Mark untestable lines with /* v8 ignore */

Documentation

Before writing code, consult the relevant docs:

  • docs/instructions/ — coding conventions, Fastify patterns, Next.js patterns, MongoDB, Zod/TypeScript, testing, Docker, Keycloak, Turborepo
  • docs/phases/ — per-phase specs with schemas, endpoints, and business logic
  • docs/architecture.md — ADRs explaining key technology choices
  • docs/cross-cutting.md — API versioning, pagination, security, audit trail

Git Conventions

Branches: feature/MESH-001-description, fix/MESH-042-description, chore/description

Commits follow Conventional Commits: feat(products): add barcode lookup, fix(pantry): correct freshness calc