Repository navigation
Description
Summary
findAllRoutes (interpreter) and compileRouter(router, { matchAll: true }) (compiled) return the same set of matches but in a different order when two dynamic routes share the same tree node (same param node, or same wildcard node). This is a pre-existing divergence (reproduces on main, independent of #186) surfaced while reviewing #186.
- Interpreter emits same-node siblings in insertion order (
matches.push(...match)/ per-entry push insrc/operations/find-all.ts). - Compiler emits them sorted by match weight (
.sort((a,b) => b.weight - a.weight)+r.unshift(...)insrc/compiler.tscompileMethodMatch/compileFinalMatch).
These two orderings only coincide when insertion order happens to line up with weight order, so the divergence is masked in most tests.
Reproduction
import { createRouter, addRoute, findAllRoutes } from "rou3";
import { compileRouter } from "rou3/compiler";
function run(routes, path) {
const r = createRouter();
for (const p of routes) addRoute(r, "GET", p, { path: p });
const interp = findAllRoutes(r, "GET", path).map((m) => m.data.path);
const compiled = compileRouter(r, { matchAll: true })("GET", path).map((m) => m.data.path);
console.log(routes, "@", path, { interp, compiled });
}
run(["/:id/**:rest", "/:id/**"], "/a/b");
// interp: ["/:id/**:rest", "/:id/**"]
// compiled: ["/:id/**", "/:id/**:rest"] <- reversed
run(["/foo/:id", "/foo/*"], "/foo/x");
// interp: ["/foo/:id", "/foo/*"]
// compiled: ["/foo/*", "/foo/:id"] <- reversed
run(["/:id(\\d+)", "/*"], "/42");
// interp: ["/:id(\\d+)", "/*"]
// compiled: ["/*", "/:id(\\d+)"] <- reversedThe _findAllRoutes test helper in test/find-all.test.ts asserts expect(compiledRes).toEqual(res), so any test using these route sets/insertion orders would fail — the current suite just doesn't cover them.
Why this matters
findAllRoutes documents its result order as least→most specific (see AGENTS.md). Neither engine fully honors that for same-node siblings:
- The interpreter uses raw insertion order, so it does not actually sort by specificity.
- The compiler sorts by weight (≈ specificity), so it can contradict both the interpreter and insertion order.
Suggested direction
Decide the intended contract and make both engines agree:
- If least→most specificity is the contract, the interpreter should order same-node param/wildcard siblings by specificity rather than insertion order, and the compiler's weight model should match it.
- If insertion order is acceptable, document that and align the compiler to emit in insertion order for equal-specificity siblings (so
matchAlloutput matches the interpreter).
Add regression tests using reversed-insertion route sets (the three above) once the contract is fixed — they currently expose the divergence.
Context
Found during review of #186 (which fixed a related but distinct compiler weight-inflation regression for optional ** vs required **:name). That fix restored the pre-#186 behavior for natural insertion order but does not address this broader insertion-order-vs-weight-order mismatch.
Activity
fix: order findAllRoutes same-node siblings by specificity (#187)