# Vector Serialized hardware inventory for engineering fleets. Track every part from receiving → bin → host → repair → destruction, with a complete per-part audit trail, field-maintenance ticketing, per-technician custody, and manufacturer EOL visibility. Vector is a pnpm + Turborepo monorepo written in strict TypeScript end to end: an Express/Prisma API, a React/Vite web client, shared zod contracts, and a shadcn/ui design system. --- ## What it does **Inventory** - Serialized `Part`s live under `Site → Room → Bin` locations or on a `Host`. - Every state change writes a `PartEvent` in the same transaction — nothing mutates silently. - `PartModel`s are grouped by `Manufacturer` and `Category`, with optional EOL dates that feed dashboard risk widgets. - `Tag`s and `SavedView`s for cross-cutting organization and reusable filters. **Field workflow** - `FM` tickets track active issues per host, with attached repairs and close times. - `Repair` logs pair a broken serial with its replacement and auto-ingest parts from a tech's input when the broken serial isn't yet in Vector. - Per-technician **custody** tracks parts held for repair, pending drop-off, or pending destruction — so broken or pre-staged parts never go missing between events. **Operations dashboard** - Universal KPIs: total parts, total spent, deployed value, open FMs, past-EOL deployments, upcoming-EOL deployments (≤180 d). - Admin-only: 7/30-day repair tempo, FMs opened, average FM close time, repairs trend chart, open-FMs-by-host, and a custody backlog by user. **Integrations** - Outbound webhooks signed with `HMAC-SHA256(timestamp + "." + body)`, headers `x-vector-signature`, `x-vector-timestamp`, `x-vector-event`, plus a `x-vector-webhook: v1` recursion guard. - Streaming CSV export of the audit log for admins. --- ## 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 — the source of truth for the API. │ ├── ui/ shadcn primitives + Vector design tokens. │ └── config/ Shared tsconfig + Tailwind tokens. ├── .gitea/workflows/ci.yaml lint · typecheck · test · build, gated E2E job. ├── docker-compose.yml api + web + redis (production-style stack). └── turbo.json dev · build · test · typecheck · lint. ``` The API is strict about its boundaries. Every service takes `(tx, input, actor)` so controllers can compose multiple services inside a single `prisma.$transaction` — part mutations and their audit rows are always atomic or not at all. Controllers do no validation and no business logic; they parse through a zod schema and dispatch. --- ## Tech stack | Layer | Choice | | -------------- | ---------------------------------------------------------------------- | | Language | TypeScript strict mode across every workspace | | 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 (single-file, shipped in prod via Docker volume) | | Observability | pino (JSON in prod, pretty in dev) with per-request `requestId` | | Testing | Vitest (unit), Playwright (E2E) | | Monorepo | pnpm workspaces + Turborepo | | CI / deps | Gitea Actions + self-hosted Renovate | The schema is intentionally Postgres-portable; SQLite is the default because Vector is deployed as a single-container stack per site. --- ## Quick start Prerequisites: **Node 20+**, **pnpm 10+** (`corepack enable`). ```bash pnpm install cp apps/api/.env.example apps/api/.env node -e "console.log(require('crypto').randomBytes(48).toString('hex'))" # paste as JWT_SECRET pnpm -C packages/db exec prisma migrate dev pnpm -C packages/db run db:seed pnpm dev ``` - API: - Web: (proxies `/api` → API) - Default credentials: **`admin` / `admin`** — change them immediately. --- ## Deployment `docker-compose.yml` runs the full stack from prebuilt images (`vector-api`, `vector-web`, `redis`). The SQLite database lives in the `vector-data` volume. ```bash # 1. authenticate to the registry docker login gitea.thewrightserver.net # 2. create a .env next to docker-compose.yml with at minimum: # JWT_SECRET=<64-char hex> # CLIENT_ORIGIN=https://vector.example.com # WEB_PORT=8080 # COOKIE_SECURE=true # required once you're behind TLS # TAG=latest # or a pinned commit SHA docker compose pull && docker compose up -d ``` Redis is included in anticipation of the BullMQ-backed webhook worker; the in-process emitter currently used by the API doesn't depend on it yet. --- ## Development commands All commands run at the workspace root; Turbo fans them out with caching. | Command | Does | | -------------------------------------- | ------------------------------------------------------ | | `pnpm dev` | API + web concurrently | | `pnpm build` | Build every package and app | | `pnpm typecheck` | `tsc --noEmit` across the graph | | `pnpm lint` | ESLint across the graph | | `pnpm test` | Vitest across packages that define it | | `pnpm -C apps/api test:coverage` | Services/lib coverage report (no threshold gate yet) | | `pnpm -C apps/e2e test` | Playwright, against a live stack (see below) | | `pnpm -C packages/db run db:studio` | Prisma Studio | | `pnpm -C packages/db run db:reset` | Drop schema, migrate, seed | --- ## Testing **Unit tests** live next to the code they cover (`*.test.ts`). The API covers services, `lib/` helpers, and the analytics aggregator (tested with an in-memory `Tx` double). Shared covers every zod contract. **End-to-end tests** in `apps/e2e/` run Playwright against a live stack and skip themselves unless `TEST_USERNAME` and `TEST_PASSWORD` are set: ```bash pnpm dev # terminal 1 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 `.gitea/workflows/ci.yaml` runs on every push and PR: 1. `pnpm install --frozen-lockfile` 2. `prisma generate` 3. `pnpm lint` 4. `pnpm typecheck` 5. `pnpm -C packages/shared test` + `pnpm -C apps/api test:coverage` 6. `pnpm build` 7. API coverage uploaded as a workflow artifact. A second Playwright job is gated by repo variable `ENABLE_E2E=true` and needs secrets `E2E_BASE_URL`, `E2E_USERNAME`, `E2E_PASSWORD`. Dependency updates come from self-hosted Renovate — 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** — errors are `{ code, message, requestId, details? }`; paginated lists are `{ data, page, pageSize, total }`. - **Validation** — every request body and query is parsed through a zod schema from `@vector/shared` before reaching a controller. No ad-hoc validation inside route handlers. - **Query keys** — hierarchical factory at `apps/web/src/lib/queryKeys.ts`. Invalidate by domain (`queryKeys.parts.all`) or by filter set (`queryKeys.parts.list(filters)`). - **Commits** — [Conventional Commits](https://www.conventionalcommits.org/). Renovate already expects them.