diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 0000000..01943c0 --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,5 @@ +## 2026-09-07 - Cached expensive URL normalization in router loop + +**Learning:** URL normalization (`pathCandidates`) was being re-evaluated for every single route in the routing table loop (`config.routes`). This function decodes URI strings and loops up to 8 times to handle edge cases like `%2f` and dot segments. This led to O(N) evaluations, heavily degrading performance for configurations with many routes. + +**Action:** Caching the result of `pathCandidates` inside the routing loop prevents it from being recalculated. The cache is initialized lazily so that configurations where the host doesn't match don't incur the cost at all. I also cached the request method uppercase string to save those recalculations too. diff --git a/packages/jouska/src/router.ts b/packages/jouska/src/router.ts index 0d56d73..8500c41 100644 --- a/packages/jouska/src/router.ts +++ b/packages/jouska/src/router.ts @@ -292,6 +292,8 @@ export const matchUrl = ( headers: Headers, ): Match | undefined => { const host = url.hostname.toLowerCase(); + const requestMethod = method.toUpperCase(); + let cachedPathCandidates: string[] | undefined; for (let i = 0; i < config.routes.length; i++) { const route = config.routes[i]!; @@ -300,13 +302,15 @@ export const matchUrl = ( if (hostPattern !== undefined && !hostMatches(hostPattern, host)) { continue; } - if ( - pathPrefix !== undefined && - !pathCandidates(url.pathname).some((c) => pathMatches(pathPrefix, c)) - ) { - continue; + if (pathPrefix !== undefined) { + if (cachedPathCandidates === undefined) { + cachedPathCandidates = pathCandidates(url.pathname); + } + if (!cachedPathCandidates.some((c) => pathMatches(pathPrefix, c))) { + continue; + } } - if (methods !== undefined && !methods.some((m) => m.toUpperCase() === method.toUpperCase())) { + if (methods !== undefined && !methods.some((m) => m.toUpperCase() === requestMethod)) { continue; } if (!conditionsHold(route, url, headers)) {