Follow one request
This is what happens when an agent cancels a booking. Step through it to see which layer does what. The same path serves every action. Only the command and the service change.Which layer does my change belong to
A new action, or a new rule about who may do it
A new action, or a new rule about who may do it
A command in
lib/app/commands/, plus a one-line route and its entry in the typed client’s ROUTES. See Commands.A change to how prices, bookings or quotes behave
A change to how prices, bookings or quotes behave
The domain service. Commands stay thin and call it.
A supplier sends or expects something different
A supplier sends or expects something different
That supplier’s adapter in
lib/suppliers/<supplier>/. Nothing above the adapter knows a supplier’s format. See Adapters.A new table or column
A new table or column
A new migration. See Data.
Boundaries the tests enforce
These rules are checked on every test run bytests/boundaries.test.ts and tests/app-layer.test.ts, so breaking one fails the build:
- Every command has a route, and that route file exists and serves the right HTTP method.
- Every route that isn’t infrastructure is a one-line command route.
- Code in
lib/never imports fromcomponents/. - A screen never imports server code. Only types cross into the browser.
- A screen never writes a command’s URL by hand; it calls
api().
A few routes sit outside the command layer on purpose because they aren’t actions on travel data: the session switcher, presenter unlock, the search stream and its sentence parser, the access gate, the guided tour, health, and the scheduled jobs.