# AGENTS.md ## Repo overview Bigcapital is a multi-tenant accounting SaaS. pnpm + Lerna monorepo (independent versioning). | Package | Path | Role | |---|---|---| | `@bigcapital/server` | `packages/server` | NestJS API + CLI. Multi-tenant with system DB + per-tenant DBs (MariaDB). Knex migrations, Objection.js ORM. | | `@bigcapital/webapp` | `packages/webapp` | React SPA. Vite, BlueprintJS, Redux, React Router v5. | | `@bigcapital/utils` | `shared/bigcapital-utils` | Shared utilities. tsup (CJS + ESM). | | `@bigcapital/pdf-templates` | `shared/pdf-templates` | PDF invoice/estimate templates. Webpack, Storybook. | | `@bigcapital/email-components` | `shared/email-components` | Email templates (react-email). Vite, Storybook. | | `@bigcapital/sdk-ts` | `shared/sdk-ts` | TypeScript client generated from OpenAPI spec. | ## Dev setup 1. `cp .env.example .env` (root-level env is the canonical one for local dev) 2. `docker compose up -d` — starts MariaDB (3306), Redis (6379), Gotenberg (9000), Garage/S3 (3900) 3. `pnpm install` 4. `pnpm run build:server` — **must run before migrations** (server CLI depends on built output) 5. `pnpm run system:migrate:latest` — system DB 6. `pnpm run dev:server` — NestJS on `:3000` (watch mode) 7. `pnpm run dev:webapp` — Vite on `:4000`, proxies `/api` and `/socket` to `:3000` ## Common commands Run from repo root unless noted. | What | Command | |---|---| | Install deps | `pnpm install` | | Start server (watch) | `pnpm run dev:server` | | Start webapp (Vite, port 4000) | `pnpm run dev:webapp` | | Build server + shared deps | `pnpm run build:server` | | Build all shared packages | `pnpm run build:shared` | | Build everything | `pnpm run build` | | Typecheck all packages | `pnpm run typecheck` | | Lint all | `pnpm run lint` (fix) / `pnpm run lint:check` (check only) | | Format all | `pnpm run format` (fix) / `pnpm run format:check` (check only) | | System DB migrations | `pnpm run system:migrate:latest` | | Tenant DB migrations | `pnpm run tenants:migrate:latest` | | Sync Stripe billing plans (Cloud) | `pnpm run stripe:sync-plans` | | Make new system migration | `pnpm run system:migrate:make ` | | Make new tenant migration | `pnpm run tenants:migrate:make ` | | Seed system DB | `pnpm run system:seed:latest` | | Seed tenant DB | `pnpm run tenants:seed:latest` | | Regenerate SDK from OpenAPI | `pnpm run generate:sdk-types` | | Playwright e2e | `pnpm run e2e:webapp` | | Server unit tests | `cd packages/server && pnpm run test` | | Server e2e tests | `cd packages/server && pnpm run test:e2e` | ## Key gotchas - **Build before migrate**: `pnpm run build:server` is required before running any migration CLI commands. The server CLI entry (`src/cli.ts`) is compiled separately via `nest-cli.json` projects config. - **Dual migration systems**: System DB (`packages/server/src/database/system/`) and tenant DB (`packages/server/src/database/tenant/`) have separate migration/seeds directories and separate CLI commands. - **Single root `.env`**: The root `.env` (copied from root `.env.example`) is the only env file. The server and its CLI resolve it from the repo root regardless of CWD (see `packages/server/src/common/config/env.ts`). If a `packages/server/.env` exists it takes precedence over the root `.env`. - **Webapp env prefixes**: Only `VITE_`, `REACT_APP_`, and `PUBLIC_URL` env vars are exposed to the client (see `vite.config.ts`). - **Server runs on `:3000`**, webapp dev server on `:4000`. The Vite config proxies `/api` and `/socket` to the server. - **Playwright selectors** use `data-testId` attributes (not `data-testid`). Global setup registers + onboards a test user via API, persists auth to `e2e/.auth/user.json`. - **Server path alias**: `@/*` maps to `packages/server/src/*` (both in `tsconfig.json` and Jest `moduleNameMapper`). - **Webapp path alias**: `@` maps to `packages/webapp/src` (Vite resolve alias). - **Commit linting**: Husky enforces conventional commits via `commitlint` on `commit-msg` hook. - **Node version**: `engines` specifies 16.x/17.x/18.x, but CONTRIBUTING.md says 18.x. Use 18.x. ## Data flow: server → sdk-ts → webapp The full request/response cycle with case conversion at each layer: ``` Webapp (camelCase) → SDK request middleware → API wire (snake_case) → Server SerializeInterceptor.in → DTOs (camelCase) Webapp (camelCase) ← SDK response middleware ← API wire (snake_case) ← Server SerializeInterceptor.out ← Service (camelCase) ``` 1. **Server DB columns**: snake_case (MariaDB/Objection.js). 2. **Server DTOs** (`@ApiProperty`): camelCase — used for internal class-validator validation only. 3. **`SerializeInterceptor`** (global, `packages/server/src/common/interceptors/serialize.interceptor.ts`): - **Inbound**: converts request body/query from **snake_case → camelCase** so DTOs receive camelCase keys. - **Outbound**: converts response from **camelCase → snake_case** before sending to client. - **Net effect**: the API wire format is always **snake_case**. 4. **SDK request middleware** (`shared/sdk-ts/src/middleware/snake-case-request-middleware.ts`): enabled by default. Converts outgoing request body/query from camelCase → snake_case (to match what the server interceptor expects). 5. **SDK response middleware** (`shared/sdk-ts/src/middleware/camel-case-middleware.ts`): **disabled by default** (`disableCamelCaseTransform: true`). Converts snake_case → camelCase when opted in. 6. **Webapp consumption**: - **SDK fetcher path** (`useApiFetcher()` in `packages/webapp/src/hooks/useRequest.tsx`): most queries get **snake_case** data by default. Pass `{ enableCamelCaseTransform: true }` to get automatic camelCase. - **Legacy axios path** (`useApiRequest()`): no automatic transform. Use `transformToCamelCase()` / `transfromToSnakeCase()` from `@/utils` manually. ### Practical impact - **When writing new query hooks**: use `useApiFetcher({ enableCamelCaseTransform: true })` and work with camelCase. Types from `@bigcapital/sdk-ts` are camelCase. - **When using legacy `apiRequest`**: responses are snake_case — call `transformToCamelCase(res.data)` before using. - **Database columns** are always snake_case. When adding migrations, use snake_case. When writing Objection.js models, map to camelCase properties. - **New API endpoints**: define DTOs with camelCase `@ApiProperty` names. The `SerializeInterceptor` handles the rest. ## Architecture notes - **Multi-tenancy**: Each tenant gets its own database. `TenantDBManager` handles per-tenant DB connections. System DB stores tenant registry. - **Server modules**: Feature modules live in `packages/server/src/modules//`. Each module typically has its own NestJS module, service, controller, and model. - **ORM**: Objection.js (built on Knex). Models in `packages/server/src/models/`. - **Auth**: Passport.js with JWT strategy. Multi-tenant auth via `nestjs-cls` (continuation-local storage). - **Background jobs**: Bull/BullMQ with Redis. - **Object storage**: S3-compatible (Garage in dev) for attachments/documents. Configured via `S3_*` env vars with `S3_FORCE_PATH_STYLE=true` for Garage. - **PDF generation**: Gotenberg service (`:9000` in dev) using the server's `GET /public/` static files + pdf-templates. - **SDK generation**: `pnpm run generate:sdk-types` exports OpenAPI spec from server, generates TypeScript types via `openapi-typescript`, builds the SDK package. Run this after API schema changes. - **License**: AGPL for the open-source edition. Some modules under `modules/EE/` may be enterprise-only. ## PR workflow Use `gh` CLI (GitHub CLI). Install: `brew install gh` (macOS), then `gh auth login`. ```bash # Create a draft PR (uses conventional commit format, targets develop) gh pr create --draft --title "type(scope): title" --body-file .github/pull_request_template.md --base develop # List your PRs gh pr list --author "@me" # Convert draft to ready for review gh pr ready # Add reviewers gh pr edit --add-reviewer user1,user2 ``` - **PR template**: `.github/pull_request_template.md` — always use it via `--body-file`. - **Title format**: conventional commits (`feat(scope):`, `fix(scope):`, `chore:`, etc.). - **Target branch**: `develop` (not `main`). - **All PR content must be in English**.