Developer Overview
This section is for developers working on Vuoro and for integrators who connect external systems to it. End-user workflows are covered in the other sections of this guide.
The authoritative design documents (TES payroll rules, the payroll engine appendix, functional specs, architecture notes and the disaster-recovery runbook) live in the repository's top-level docs/ directory. Change history and capability specs are tracked with OpenSpec under openspec/ — openspec/changelog.md gives a dated, most-recent-first summary of every completed change.
In This Section
- API Conventions — Base URL, versioning, authentication, refresh tokens, rate limiting
- BI Feed — Outbound read-only data feed for a data warehouse, and the inbound demand-forecast ingest
- Booking Webhooks — Pushing reservations from Planyo, Sessiq or another booking system into the roster grid
- Public Endpoints — Employee calendar (ICS) feed, public usage statistics, health checks, kiosk
- Deployment and Operations — CI pipeline, image-based deploys, backups and disaster recovery
Architecture at a Glance
Vuoro is a pnpm + Turborepo monorepo with three main packages:
| Package | Stack | Purpose |
|---|---|---|
apps/api | NestJS (Fastify adapter), Drizzle ORM, PostgreSQL, BullMQ / Redis | REST API, background jobs, payroll engine |
apps/web | Next.js 14 App Router, TanStack Query, Zustand, Tailwind CSS | Manager and employee web app (installable PWA, offline clock queue) |
packages/shared (@roster/shared) | TypeScript | Shared types, enums and the authoritative TES payroll constants |
This documentation site itself lives in apps/docs (Docusaurus).
API modules
The API is a modular monolith. Each domain lives in apps/api/src/modules/<name>/ with the usual NestJS module / controller / service triple. Besides the core domains (auth, tenants, employees, rostering, time-tracking, timesheets, payroll, netvisor, compliance, audit), the module list now includes shift offers and swaps, availability, leave and sick-leave periods, wage tables, custom and shift allowances, project fees, budgeting, staffing requirements and coverage targets, demand-forecast ingest, AI roster drafting and assignment, bookings, announcements, tasks, notifications and email, shift pulse, satisfaction survey, calendar feed, BI feed, public stats, payroll reconciliation and health.
Multi-tenancy and data access
- Every domain table has a
tenant_idcolumn. PostgreSQL Row-Level Security enforces tenant isolation; domain tables useFORCE ROW LEVEL SECURITY. - The application connects as a non-superuser role. Each HTTP request runs inside one database transaction with the tenant set as a transaction-local setting, so a request either commits completely or not at all.
- A small number of deliberately cross-tenant or session-less paths (public usage stats, the ICS calendar feed) run on a separate admin connection (
DATABASE_ADMIN_URL) and filter by tenant explicitly. They take no free-form input into their queries. - New tables must be wired into the RLS setup script (
apply-rls.ts), the E2E test global setup and the DB test helper at the same time.
Roles
Roles are employee, supervisor, manager, company_admin and accounting. company_admin and accounting are tenant-wide; manager and supervisor are scoped to the locations they are assigned to. accounting logins do not need an employee record and can work with payroll periods and exports, and view employees, leave and reports.
Local Development
Requirements: Node 22 and pnpm 11 (pinned via packageManager in the root package.json), plus Docker for Postgres and Redis.
pnpm install # dependency build scripts are disabled; native deps ship prebuilt binaries
docker compose up -d # Postgres + Redis
pnpm db:setup # migrations + RLS policies
pnpm db:seed # development data
pnpm dev # API on :3001, web on :3000
Notes:
- Dependency
overrideslive inpnpm-workspace.yaml, not inpackage.json(pnpm 10+ no longer reads thepnpmfield). - With pnpm 10+, arguments are forwarded without a
--separator:pnpm --filter @roster/api test --testPathPattern=<pattern>. - Schema changes go through
pnpm --filter @roster/api db:generate; never hand-write numbered migrations. Triggers, functions and policies belong inapply-rls.ts. - Swagger UI is served at
/api/docsin non-production environments.
Validation Before Committing
pnpm check # typecheck + lint + unit tests (fail-fast)
pnpm test alone does not type-check (Vitest and Jest strip types), so always run pnpm check or pnpm typecheck. CI also runs gates that pnpm check does not: the i18n key-parity check (check:i18n), API E2E tests against a real Postgres (pnpm --filter @roster/api test:e2e, run sequentially because each spec truncates all tables), Playwright web E2E, and dependency security scanning.