Skip to content

feat(extraction): route Next.js App Router handlers (#759) - #2747

Open
petterip wants to merge 6 commits into
DeusData:mainfrom
petterip:feat/nextjs-app-router-routes
Open

petterip wants to merge 6 commits into
DeusData:mainfrom
petterip:feat/nextjs-app-router-routes

Conversation

@petterip

Copy link
Copy Markdown

What does this PR do?

Fixes #759: Next.js App Router app/**/route.ts handlers now produce Route nodes and HANDLES edges, so cross-repo intelligence has something to match against. It follows the approach approved in the issue.

Stacked on #2738, which makes the route pass read and write route properties as valid JSON. Without it, a route directory with a quote in its name gets a second Route cut at the quote and HANDLES properties that are not valid JSON, on the first full index already (pipeline_nextjs_quoted_route_round_trip_issue759 fails at ASSERT(full.read) with the route pass from before #2738). Until #2738 merges, this PR also shows its commits; the change here is the top commit, fa331ed.

  • internal/cbm/extract_nextjs.c (new), called from cbm.c after cbm_extract_openapi, sets route_path/route_method on the existing Function definition. The existing route-node pass then creates the Route nodes and HANDLES edges. It adds no pass, no Variables and no synthetic definitions.
  • Supported: export [async] function GET, export const GET[: Type] = [async] (...) => ..., and export { GET } of such a declaration, in route.ts/route.js under app/ or src/app/.
  • Paths are spelled as SvelteKit's already are (sveltekit_route_path): [id] is :id, [...slug] is *slug, a route group (x) adds no segment, and the root route is /. Route nodes carry the handler's file_path, so search_graph --file-pattern finds them (search_graph: Route nodes have empty file_path, so label=Route + file_pattern always returns 0 — and the hint blames the label #1366).
  • CBM_SEMANTIC_INDEX_VERSION 9 → 10, with a history entry, so a v9 index is rebuilt once.

Not routed by this increment (each covered by a test)

export const GET = function () {} (the TS/JS extractor makes no Function for it today); renamed export { h as GET }; re-exports (export { GET } from, export *); destructured or wrapped handlers (auth(handler), as/satisfies); optional catch-all [[...x]]; private _x; parallel @slot; intercepting (.)x; workspace roots (apps/web/app/...); route.tsx/jsx/mjs. Each file is judged on its own, so src/app/ is routed even where a root app/ makes Next.js ignore it. HEAD is not derived from GET. Pages Router, Convex and cross-repo matching stay out of scope, as agreed in the issue.

Size

+883 / −2: 288 source lines and 595 test lines added. That's over the 500-line guideline because of the lifecycle and upgrade tests; the source alone is under it. I can split the tests out if you'd prefer.

Evidence

Tested on current main (3457b21b) with #2738 (d9766ae8). Machine: x86_64, 16 cores, WSL2 (Linux 6.18) on Ubuntu 22.04, gcc 11.4; clang-format 20.1.8, cppcheck 2.20.0 and clang-tidy 21.1.8, the versions CI uses.

Requirement Test
Supported forms, path spelling, root, groups, dynamic and catch-all segments, JS and TS extract_nextjs_route_metadata_contract_issue759
Non-routes and the exclusions above extract_nextjs_rejects_nonroutes_issue759
Persisted Route name/QN/file/method and HANDLES handler, sequential path pipeline_nextjs_persisted_route_contract_issue759
Same on the parallel path (CBM_WORKERS=4, mode=parallel in the log) pipeline_nextjs_persisted_route_contract_parallel_issue759
full → NOOP → helper edit → remove method → body edit → add PATCH → move directory → delete; an unrelated route is kept at each step pipeline_nextjs_incremental_lifecycle_issue759
v9 index → one forced full rebuild, version 10 stored, then NOOP (fails if the version stays at 9) pipeline_nextjs_semantic_upgrade_issue759
Quoted directory: the Route is stored intact on the full index and after an incremental one pipeline_nextjs_quoted_route_round_trip_issue759 (SKIP_PLATFORM on Windows)

AI assistance

Implementation and tests were written with AI assistance (Claude Code; planning with Codex). I reviewed the change, ran the commands listed above, and take responsibility for it under my DCO sign-off.

Checklist

  • Every commit is signed off (git commit -s)
  • Tests pass locally (make -f Makefile.cbm test)
  • Lint passes (make -f Makefile.cbm lint-ci)
  • New behavior is covered by a test (reproduce-first for bug fixes)

petterip and others added 4 commits October 21, 2026 00:57
An unchanged handler's Route is rebuilt from the route_path and
route_method stored on its Function. They were read with a quote scan:
an escaped quote or backslash cut the path short, and a nested, broken
or non-string value still named a Route. They are now decoded with
yyjson, and anything that is not a top-level string that fits names no
Route.

Every Route and HANDLES properties writer in pass_route_nodes.c now
escapes its values into a buffer that fits them: the rebuilt handler QN
was unescaped, a QN filling the 512-byte escape buffer left the closing
"} off the 512-byte properties, a method is source text where a DRF
@action names it, and the SvelteKit +server writer had both problems
in 256 bytes.

Tests: each of the five new infrascan tests fails on 4cf4414 without
this change.

Signed-off-by: Petteri Ponsimaa <petterip@gmail.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Review follow-up to the previous commit. Properties hold source text
as its bytes, so a Latin-1 docstring stored beside route_path is not
UTF-8. The strict yyjson parse rejected that whole object, so the
route pass skipped rebuilding the handler's Route, where the old quote
scan had rebuilt it. Parse with YYJSON_READ_ALLOW_INVALID_UNICODE, as
internal/cbm/diagram.c does.

Test: infrascan_decorator_route_metadata_survives_invalid_utf8 fails
with the strict parse, and passes with this change and on 4cf4414.

Signed-off-by: Petteri Ponsimaa <petterip@gmail.com>
app/**/route.{ts,js} files produced 0 Route nodes, so cross-repo
intelligence found no edges into a Next.js backend (DeusData#759).

cbm_extract_nextjs_routes fills route_path/route_method on the
existing Function definition for each exported HTTP-verb handler.
It adds no pass and no synthetic definitions; the route-node pass
builds the Route and HANDLES edge as it does for other frameworks.
CBM_SEMANTIC_INDEX_VERSION goes from 9 to 10 so existing indexes
re-extract once.

Fixes DeusData#759

Signed-off-by: Petteri Ponsimaa <petterip@gmail.com>
@github-actions

Copy link
Copy Markdown

Thanks for opening this — it has been seen, and it is queued.

This note is automated, but it is not a brush-off: it exists so you know where your PR stands instead of having to guess from silence.

Current review status: working through a backlog. 0.9.1-rc.1 is out, so the release freeze that held reviews is over — but it left a large queue of open pull requests behind it, and we are reading through them oldest-first. The background is in discussion #1144.

What that means for this PR, concretely:

  • It will not be closed for inactivity. No stale bot touches pull requests here.
  • It may still sit a while before a human reads it. That is on us, not on you.
  • Older PRs are read first, so a recent one is not being skipped — it is behind a queue.

Things that will genuinely speed it up whenever review does happen:

  • Keep it rebased on main — the tree is moving quickly right now, and a conflicting branch cannot be reviewed as the diff you intended.
  • Get CI green, or say which failures you believe are pre-existing.
  • Keep the change to one claim. Bundled features and refactors get split before they get merged, which costs you a round trip.
  • Every commit needs a sign-off (git commit -s) — CI enforces DCO.

If this fixes a bug, a reproduction we can run is worth more than a description of the symptom.

Thanks for contributing, and sorry in advance for the wait.

Signed-off-by: Petteri Ponsimaa <petterip@gmail.com>
Signed-off-by: Petteri Ponsimaa <petterip@gmail.com>
@petterip

petterip commented Oct 24, 2026 •

Copy link
Copy Markdown
Author

Both failing CI jobs on the latest push look unrelated to this change:

  • test / test-ui: npm could not reach the registry (connect ETIMEDOUT, ENETUNREACH), so no UI test ran.
  • test / test-diag: 9927 passed, 1 failed: cp_trpc_calls_ts_procedure (test_convergence_probe.c:662). It passed in the other eight test jobs of the same run, and passes 13 of 13 times locally on the same diag build (clang-21 ASan+UBSan, same ASAN_OPTIONS). This PR never reaches that file: cbm_extract_nextjs_routes returns at once for paths outside app/ and src/app/.

Could you re-run the failed jobs when convenient? #2738, which this PR stacks on, is green.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Next.js App Router: app/**/route.ts handlers produce 0 Route nodes, so cross-repo-intelligence finds no edges

2 participants