02 — Route / Handler Mapping
— 02 ROUTE / HANDLER MAPPING — why the production APIs fail
Deployment topology (discovered live):
- nginx (incomex-nginx) terminates TLS for vps.incomexsaigoncorp.vn and proxies location / , /api/registry/ , /api/registries/ , /api/registries-pivot/ to upstream nuxt:3000. Directus paths proxy to directus_backend.
- nuxt (incomex-nuxt, image nuxt-ssr-local:s174) runs Nitro on :3000, mem_limit 512m. Env: NUXT_DIRECTUS_INTERNAL_URL=http://directus:8055, NUXT_DIRECTUS_SERVICE_TOKEN=admin token, plus RP_PG_HOST/PORT/DATABASE/USER/PASSWORD (direct Postgres).
- KEY: the container bind-mounts /opt/incomex/deploys/nuxt-output -> /app/.output. The served Nitro bundle lives on the host filesystem, not baked in the image. Editing an existing handler file there + restart applies it without an image rebuild. Adding a NEW route still needs a rebuild (route registration is compiled into nitro.mjs).
- Compose: /opt/incomex/docker/docker-compose.yml (service "nuxt").
Two helper patterns coexist in handlers:
- Directus REST via ofetch ($fetch to http://directus:8055/items/...). Used by matrix and pivot-query.
- Postgres via rpQuery(sql) (exported from nitro.mjs as
rpQuery as m; importedm as rpQuery). Used by registries-pivot/summary, registries-pivot/rows, counts, etc. These run SQL directly against the RP_PG pool.
Mapping of the 3 breaks:
| route | mounted | handler chunk | backend | failure |
|---|---|---|---|---|
| /api/registry/pivot-query | yes | routes/api/registry/pivot-query.get.mjs | directus-rest | jsonb _neq filter -> Directus 400 -> 500 |
| /api/registry/matrix | yes | routes/api/registry/matrix.get.mjs | directus-rest | 897K entity_labels pull -> 15s timeout -> 500 |
| /api/registries/index | NO | none (string only in client chunk index-DCnbKlud.mjs = /knowledge/registries page) | n/a | route never mounted in s174 -> Nitro 404 |
Evidence that registries/index is a client/server drift: the Nitro route registration table lists /api/registries/system-issues and /api/registries/system-issues/detail (plural) but NOT /api/registries/index; the literal string /api/registries/index appears only in client.precomputed.mjs and the /knowledge/registries page chunk. So the page fetches a route the server build does not expose.
Why matrix is the outlier: every sibling RP data endpoint queries Postgres via rpQuery; matrix alone fans out 152 per-collection Directus REST calls plus the 897K-row entity_labels pull plus taxonomy and edges, all limit=-1, each $fetch capped at 15s, inside a 512 MB container. entity_labels (897,347 rows) is the killer; edges (2,199) and per-collection (managed/log = 152 collections, ~thousands of rows) are minor.
View created: v_rp_production_api_route_mapping_status (route -> handler chunk -> mounted -> backend -> live -> fix).