Goal

Every app/*/routes.ts map is merged at build into a single frozen BunRoutes table; module handlers run inside the existing compose() middleware; the prod bundle does no runtime scan.

Definition of done

  • A macro discovers app/*/routes.ts via Bun.Glob and reads each module's routes export.
  • Maps are merged into one BunRoutes literal reusing the existing BunRoutes/HTTPMethod types from src/types.ts.
  • Duplicate path + method across modules is a build error naming both modules.
  • The boundary check from E1 runs over the same discovered set.
  • The prod bundle holds the inlined table and does no runtime scan (asserted alongside build.test.ts); dev re-discovers under bun --hot.

The macro

New: src/build/macros/modules.macro.ts — mirrors the existing routes.macro.ts contract (return value inlined as a literal; runtime never scans).

// modules.macro.ts — build time only, consumed with { type: "macro" }
export async function MODULE_ROUTES(appDir: string) {
  const files = await Array.fromAsync(new Bun.Glob("*/routes.ts").scan(appDir));
  return mergeRouteMaps(files); // { "/invoices/:id": { GET }, ... } — one BunRoutes literal
}

Reuses directly

Nothing here is a new runtime. The macro is a sibling of the existing src/build/macros/*.macro.ts pipeline; it leans on Bun.Glob discovery, the Bun.serve route table (BunRoutes), the compose() middleware + PattiesContext, the bun --hot dev loop, and the existing deploy adapters. The only net-new code is the merge + collision step. The deliberate non-goals (no DI, no decorators) are recorded in ADR-009.

Merge & collision detection

Each module's map is namespaced by intent, not auto-prefixed (the route-prefixing call is open — see Decisions). The merge is a shallow combine with conflict detection.

CaseResult
Two modules, disjoint pathsmerged table
Same path, different methodsmerged into one path entry
Same path + same method in two modulesbuild error naming both modules + file:line

Verify

bun --filter patties test modules   # discovery + merge + collision error
bun --filter patties test build     # assert: no Bun.Glob / scan at runtime

Dependencies

Blocked by

E1 — the convention and the boundary check it reuses.

Blocks

E3 — the starter scaffolds modules that this macro discovers.