Epic 2 — Route discovery & merge macro
A Bun.build macro — a sibling of patties' existing route/island/agent macros — discovers each module's routes.ts with Bun.Glob, merges the maps into one route table (detecting path collisions), and inlines it as a literal. The production server is Bun.serve({ routes }) and scans nothing. See Architecture · build-time discovery.
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.tsviaBun.Globand reads each module'sroutesexport. - Maps are merged into one
BunRoutesliteral reusing the existingBunRoutes/HTTPMethodtypes fromsrc/types.ts. - Duplicate
path + methodacross 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 underbun --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.
| Case | Result |
|---|---|
| Two modules, disjoint paths | merged table |
| Same path, different methods | merged into one path entry |
| Same path + same method in two modules | build 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