Working on Vector

Vector is developed in one repository: the two packages, and the demo application (The demo application) that consumes them the way an adopter would. Every command on this page runs from demo/, with the stack up and seeded.

Layout

packages/vector/            kinetis/vector: registry, REST API, jobs, MCP tools, migration stubs
packages/vector-ui/         @kinetis/vector-ui, the Nuxt layer; its Vitest suite in test/nuxt/
  hosted/                   the hosted bundle's build entry: the layer alone
demo/docker-compose.yml     the stack: app, worker, nginx, frankenphp, mysql, postgres, redis, node; e2e and oha on demand
demo/docker/                the PHP images (PHP-FPM and FrankenPHP) and their shared extension recipe, nginx, the Caddyfile, php.ini
demo/bin/                   check-frankenphp and benchmark, for the FrankenPHP worker service
demo/backend/               the demo application, kinetis/vector installed from ../../packages/vector
  src/Auth/                 both sign-ins, their throttling, the vector and mcp group middleware, impersonation
  src/Console/              demo:seed
  src/Http/Diagnostics/     GET /_demo/runtime, the development-only runtime report bin/check-frankenphp reads
  src/Domain/               the shop's entities, on MySQL; Support/ the support desk's, on PostgreSQL
  src/Vector/               the demo's resources, actions, widgets, dashboards and settings groups
  migrations/               the shop, the queue table and Vector's tables for MySQL
  migrations/admin/         Vector's tables for PostgreSQL, on the connection admin
  migrations/support/       the support desk, on the connection support
  bootstrap.php             JWT keys, CORS for the standalone UI, the Vector bindings
  public/vector-extensions/ the demo's runtime UI extensions
  translations/             the demo's labels
  tests/                    PHPUnit (App\Tests\)
demo/frontend/              the demo Nuxt app: extends @kinetis/vector-ui
demo/e2e/                   the end-to-end suite: Playwright, both admin modes
docs/                       this manual (Sphinx, MyST Markdown)
bin/orbitron-mcp            launches the Orbitron MCP server for demo/backend

Controllers, commands, entities and resources anywhere under App\ or Kinetis\Vector\ are discovered; there is no registry to edit. Nothing under packages/ may reference App\.

The demo installs kinetis/vector as a Composer path repository, so vendor/composer/installed.json keeps packages/vector/composer.json as it was at the last install. After changing that file, its extra.kinetis keys especially, reinstall the package so discovery sees the change:

docker compose exec app composer update kinetis/vector

Tests

docker compose exec app vendor/bin/phpunit                                        # the API and the PHP package
docker compose exec -e VECTOR_TEST_DB_CONNECTION=default app vendor/bin/phpunit   # the same, Vector's tables on MySQL
docker compose exec node pnpm test                                                # the UI layer: Vitest, no server
docker compose exec node pnpm typecheck                                           # vue-tsc over the layer and its tests
docker compose run --rm e2e                                                       # the admin in a browser, both modes
bin/check-frankenphp                                                              # the application under FrankenPHP worker mode

PHPUnit runs with Vector’s tables on PostgreSQL, where backend/.env puts them, and again on MySQL. It handles its requests in-process and goes through neither web server. bin/check-frankenphp covers the running worker on :8081: worker mode, the drivers it selected, and request isolation across interleaved principals on the same threads (The demo application). frankenphp keeps the code it booted with, so run docker compose restart frankenphp after a PHP change, before checking it.

The UI layer’s Vitest suite is in packages/vector-ui/test/nuxt/. Each file runs inside a Nuxt app built from the layer (@nuxt/test-utils, happy-dom), so auto-imports, app config, the i18n plugin, Vuetify and Pinia behave as they do in the admin. It covers the formatters and client rules, the API client in both authentication modes, the auth store, the schema and resource clients, and the generated form (VxResourceForm) with its 422 mapping. API calls go to endpoints the test registers (test/nuxt/endpoints.ts); nothing needs a server. pnpm typecheck runs vue-tsc in strict mode over the layer, its tests included.

bin/benchmark replaces the demo data when given --seed=N and needs the stack to itself. Run it alone, then restore the stock data set before PHPUnit:

docker compose exec --user www-data app vendor/bin/kinetis demo:seed --fresh

The end-to-end suite needs the hosted bundle published (The demo application) and writes to the demo databases, so run it on its own, never beside PHPUnit. It signs in as its own admin, e2e@vector.local, and clears that account’s saved preferences before each test, so it and a person using the demo do not disturb each other. See demo/e2e/README.md.

Static analysis and builds

docker compose exec app vendor/bin/phpstan analyse
docker compose exec app vendor/bin/kinetis build
docker compose exec node pnpm lint
docker compose exec node pnpm --filter frontend generate

PHPStan runs at level 8 over the demo and packages/vector/src, with two Kinetis rules. NoStaticPropertiesRule flags a static property, which outlives every request a worker handles; NoBlockingIoRule flags a call that blocks the event loop instead of yielding. They catch two specific mistakes; they do not prove a change is correct on a persistent worker.

docker compose exec app composer psalm          # Psalm, plain
docker compose exec app composer psalm:taint    # Psalm, taint analysis; writes backend/psalm.sarif

Psalm covers the demo’s src/ and migrations/ and packages/vector/src, at its level 7 (Psalm’s 1 is its strictest). It runs twice on purpose: --taint-analysis reports only taint issues, so it cannot replace the plain run. The taint run needs zend.assertions=-1, which the script sets. Request input reaches this code through PSR-7 and the framework’s hydration, and SQL runs through Kinetis’s own links. Psalm knows neither as a taint source or sink, so a clean taint run only covers flows from PHP’s own sources to PHP’s own sinks. Overriding and implementing methods carry #[\Override], and the plain run enforces it.

Mutation testing

docker compose exec app composer infection:workers  # the workers' databases, first
docker compose exec app composer infection          # the demo's src/, about 70 minutes
docker compose exec app composer infection:vector   # Vector's Registry and Query

Infection runs the PHPUnit suite against each mutant and fails below the minMsi and minCoveredMsi floors in backend/infection.json5 and backend/infection.vector.json5.

The mutated tests write to the databases, so each Infection worker runs on stores of its own. Infection numbers its worker processes in TEST_TOKEN, and when that is set backend/tests/bootstrap.php points worker N at the MySQL database vector_N, the PostgreSQL databases vector_admin_N and vector_support_N, Redis databases 2N (the cache) and 2N+1 (the jobs queue) and a temporary directory of its own (backend/tests/worker-stores.php). composer infection:workers drops, recreates, migrates and seeds those databases for workers 1 to 4, as root on MySQL; run it before each mutation-testing run. Without TEST_TOKEN (PHPUnit on its own, and Infection’s initial run of the suite) the tests use the base databases.

infection:vector runs four workers. infection runs one, on worker 1’s stores: its whole suite includes tests that share state outside them. Neither run touches the base databases beyond what PHPUnit itself does, but both still need them seeded, so run them one at a time, never beside PHPUnit or the end-to-end suite. A killed mutant stops its test before the test puts its rows back, so afterwards a worker’s databases can hold missing order lines, changed product prices and stock, a leftover hosted-test tag, and, in Vector’s own tables, a store lowStockThreshold of 0 and saved product views; composer infection:workers starts them over.

The reports go to backend/infection.log and backend/infection.vector.log, each with a .summary.log beside it.

pnpm lint runs ESLint over every TypeScript and Vue file in the workspace: the UI layer and its hosted app, the demo frontend and the end-to-end suite. One flat config at the repository root, eslint.config.mjs, uses @nuxt/eslint-config on its own, so linting needs no nuxt prepare. Each package also has its own pnpm lint against that config. TypeScript is pinned to ~6.0.3 because typescript-eslint supports TypeScript only below 6.1.

Continuous integration

The workflows in .github/workflows/ run on pushes to main and on pull requests, each for the paths it covers:

  • ci.yml — on PHP 8.4 and 8.5, in the demo’s own PHP image with MySQL, PostgreSQL and Redis as services: composer validate --strict for both composer.json files, composer audit, the PHPUnit suite (both placements of Vector’s tables, as above), PHPStan and both Psalm runs; pnpm lint, pnpm typecheck, pnpm test and pnpm test:scripts; and the strict build of this manual.

  • monorepo-validate.yml — node scripts/validate-monorepo.mjs: no App\ reference under packages/.

  • versions.yml — node scripts/validate-versions.mjs: a pull request that changes files under packages/vector bumps vector in packages.manifest.json by one step, with node scripts/bump.mjs. One that changes files under packages/vector-ui bumps both vector-ui and vector, since the hosted bundle built from the UI ships inside kinetis/vector; see Releasing.

  • infection.yml — composer infection:workers, then composer infection:vector, when Vector’s Registry or Query or the mutation setup changes; the mutated files are split over three parallel jobs, each passing --filter and floors of its own.

  • sonarqube.yml — the PHPUnit suite under pcov, its coverage of packages/vector/src reported to SonarQube Cloud with its quality gate.

  • semgrep.yml — Semgrep SAST; .semgrepignore lists the reviewed false positives.

deploy-docs.yml publishes this manual to kinetis.dev/vector/docs/ when docs/ changes on main.

See also