Lock images to gitea.thewrightserver.net/josh/{vector-api,vector-web}
and drop the build: sections. docker compose up now only pulls; source
builds happen exclusively in CI.
Vector
Hardware parts inventory system. Tracks serialized parts across sites → rooms → bins, with a full audit trail, repair/RMA workflow, tag-based organization, manufacturer EOL tracking, and signed webhook delivery for external integrations.
Vector 2.0 is a ground-up TypeScript rewrite of the original JavaScript codebase, delivered as a pnpm + Turbo monorepo with shadcn/ui on the frontend and a service-layered Express API on the backend.
Architecture
vector/
├── apps/
│ ├── api/ Express 5 + Prisma + zod. Controllers → services → tx.
│ ├── web/ React 19 + Vite + TanStack Query/Table + shadcn/ui.
│ └── e2e/ Playwright smoke tests (login, parts, repairs, admin).
├── packages/
│ ├── db/ Prisma schema, migrations, seed, singleton client.
│ ├── shared/ zod schemas + DTOs — single source of truth for the API contract.
│ ├── ui/ shadcn primitives + Vector design tokens.
│ └── config/ Shared tsconfig presets + Tailwind preset.
├── .gitea/workflows/ CI (lint · typecheck · test · build) and gated E2E job.
├── docker-compose.yml Postgres + Redis for local development.
└── turbo.json Pipeline: `build`, `test`, `typecheck`, `lint`.
The API is split along a strict controller → service → transaction boundary. Every service function takes (tx, input, actor) so controllers can compose multiple services inside a single prisma.$transaction, guaranteeing that part mutations and their PartEvent audit rows are atomic.
Tech stack
| Layer | Choice |
|---|---|
| Language | TypeScript strict mode across all workspaces |
| API | Express 5, Prisma 5, zod, JWT + refresh-token rotation, CSRF, helmet |
| Web | React 19, Vite, TanStack Query, TanStack Table, react-hook-form, nuqs |
| UI | shadcn/ui on Radix primitives, Tailwind v4, Sonner toasts, Recharts |
| Database | SQLite for dev (Postgres cutover prepared — schema is portable) |
| Observability | pino (JSON logs in prod, pretty in dev) + request-scoped requestId |
| Testing | Vitest (unit, coverage gate on services/lib), Playwright (E2E) |
| Monorepo tools | pnpm workspaces + Turborepo |
| CI | Gitea Actions + Renovate (self-hosted) |
Getting started
Prerequisites
- Node.js ≥ 20
- pnpm ≥ 10 (
corepack enableornpm i -g pnpm) - Docker (optional; required for Postgres + Redis)
Install
pnpm install
pnpm -C packages/db exec prisma generate
Environment
Copy the API env template and generate a JWT secret:
cp apps/api/.env.example apps/api/.env
node -e "console.log(require('crypto').randomBytes(48).toString('hex'))" # paste into JWT_SECRET
The default DATABASE_URL points at a local SQLite file. To use the containerized Postgres instead, start Docker and update the URL:
docker compose up -d postgres redis
# then edit apps/api/.env:
# DATABASE_URL=postgresql://vector:vector@localhost:5432/vector
Database
pnpm -C packages/db exec prisma migrate dev # apply migrations
pnpm -C packages/db run db:seed # create default admin:admin user
Run
pnpm dev
- API: http://localhost:3001
- Web: http://localhost:5173 (proxies
/api→ API) - Default login:
admin/admin— change immediately.
Common tasks
All tasks run at the workspace root; Turbo fans them out to the right packages with caching.
| Command | What it does |
|---|---|
pnpm dev |
Run apps/api and apps/web concurrently |
pnpm build |
Build every package and app |
pnpm typecheck |
tsc --noEmit across every workspace |
pnpm lint |
ESLint across every workspace |
pnpm test |
Run all Vitest unit test suites |
pnpm -C apps/api test:coverage |
Unit tests + v8 coverage report on services/lib (report only) |
pnpm -C apps/e2e test |
Run Playwright smoke tests (requires stack running + creds) |
pnpm -C packages/db run db:studio |
Open Prisma Studio against the current database |
pnpm -C packages/db run db:reset |
Drop schema, re-migrate, re-seed |
Testing
Unit tests live next to the code they cover (*.test.ts). Coverage is reported on apps/api/src/{services,lib}/** and uploaded by CI as an artifact — no threshold gate today; add one once service-level coverage catches up.
packages/shared— zod schema contracts (parts, webhooks, auth).apps/api— pure helpers (signBody,csvCell,http-error), plus the analytics aggregator tested with an in-memoryTxdouble.
End-to-end tests in apps/e2e/ run against a live stack. Each spec skips itself unless TEST_USERNAME and TEST_PASSWORD are present in the environment:
pnpm dev # in one terminal
TEST_USERNAME=admin TEST_PASSWORD=admin \
pnpm -C apps/e2e exec playwright install --with-deps chromium
TEST_USERNAME=admin TEST_PASSWORD=admin \
pnpm -C apps/e2e test
Reports land in apps/e2e/playwright-report/.
Continuous integration
CI runs on Gitea Actions — .gitea/workflows/ci.yaml. Every push and pull request executes:
pnpm install --frozen-lockfileprisma generatepnpm lintpnpm typecheckpnpm -C packages/shared test+pnpm -C apps/api test:coveragepnpm build- API coverage uploaded as an artifact.
A second Playwright job is gated behind the repository variable ENABLE_E2E=true and requires these secrets:
| Secret | Purpose |
|---|---|
E2E_BASE_URL |
URL of a running Vector stack |
E2E_USERNAME |
Test admin username |
E2E_PASSWORD |
Test admin password |
Dependency updates come from a self-hosted Renovate instance configured via renovate.json — grouped minor/patch PRs weekly, auto-merge for dev-dep patches, prisma + @prisma/client grouped together, Radix/shadcn held for manual review.
Conventions
- API shape: all responses follow
{ code, message, requestId, details? }for errors. Paginated lists use{ data, page, pageSize, total }. - Validation: every request body and query string is parsed through a zod schema from
@vector/sharedbefore it reaches a controller. No ad-hoc validation inside controllers. - Query keys: the web app uses a hierarchical factory at apps/web/src/lib/queryKeys.ts. Invalidate by domain (
queryKeys.parts.all) or by filter (queryKeys.parts.list(filters)). - Commits: Conventional Commits — Renovate already expects them.
- Webhooks: every delivery is signed with HMAC-SHA256 over
${timestamp}.${body}and sent with headersx-vector-signature,x-vector-timestamp,x-vector-event, and the recursion-guardx-vector-webhook: v1.
Roadmap
All nine phases of the 2.0 rewrite are in-tree:
| Phase | Scope |
|---|---|
| 0 | Monorepo foundation (pnpm + Turbo, apps scaffolded) |
| 1 | Strict TypeScript, Prisma schema, env validation, singleton client |
| 2 | Service-layered API, transactional mutations, auth hardening, pagination |
| 3 | Schema extensions (hosts, repairs, tags, categories, webhooks, FTS-ready) |
| 4 | shadcn/ui design system + DataTable<T>, Form pattern, query-keys factory |
| 5 | Page-by-page rewrite (Parts, Locations split, Manufacturers, Users) |
| 6 | Feature slice — Repair/RMA, Tags, Bulk ops, Saved views |
| 7 | Analytics dashboard (Recharts) + EOL + Webhooks + streaming CSV export |
| 8 | Vitest + Playwright + Gitea Actions CI + Renovate |
Deferred follow-ups
- Postgres cutover. Schema is already portable; the data-migration script lives at
packages/db/POSTGRES_FTS.md. - BullMQ worker. Replace the in-process webhook emitter in apps/api/src/lib/webhook-emitter.ts with a Redis-backed worker. Signature is stable — one-line swap.
- PDF audit export via
@react-pdf/rendererin the worker. - CSV import wizard UI to pair with the existing
CsvImportJobstaging table.