@real-router/core 0.84.0 → 0.85.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. package/dist/cjs/{Router-CUtHMhRV.js → Router-BOyV6_me.js} +2 -2
  2. package/dist/cjs/{Router-CUtHMhRV.js.map → Router-BOyV6_me.js.map} +1 -1
  3. package/dist/cjs/api.js +1 -1
  4. package/dist/cjs/index.js +1 -1
  5. package/dist/esm/Router-A9Vko7z1.mjs +2 -0
  6. package/dist/esm/{Router-sWkNlsWW.mjs.map → Router-A9Vko7z1.mjs.map} +1 -1
  7. package/dist/esm/api.mjs +1 -1
  8. package/dist/esm/index.mjs +1 -1
  9. package/package.json +2 -2
  10. package/dist/cjs/Router.js +0 -2
  11. package/dist/cjs/Router.js.map +0 -1
  12. package/dist/cjs/RouterError.js +0 -2
  13. package/dist/cjs/RouterError.js.map +0 -1
  14. package/dist/cjs/api/cloneRouter.js +0 -2
  15. package/dist/cjs/api/cloneRouter.js.map +0 -1
  16. package/dist/cjs/api/getDependenciesApi.js +0 -2
  17. package/dist/cjs/api/getDependenciesApi.js.map +0 -1
  18. package/dist/cjs/api/getLifecycleApi.js +0 -2
  19. package/dist/cjs/api/getLifecycleApi.js.map +0 -1
  20. package/dist/cjs/api/getPluginApi.js +0 -2
  21. package/dist/cjs/api/getPluginApi.js.map +0 -1
  22. package/dist/cjs/api/getRoutesApi.js +0 -2
  23. package/dist/cjs/api/getRoutesApi.js.map +0 -1
  24. package/dist/cjs/api/helpers.js +0 -2
  25. package/dist/cjs/api/helpers.js.map +0 -1
  26. package/dist/cjs/channels/defaults.js +0 -2
  27. package/dist/cjs/channels/defaults.js.map +0 -1
  28. package/dist/cjs/channels/guard.js +0 -2
  29. package/dist/cjs/channels/guard.js.map +0 -1
  30. package/dist/cjs/channels/modeGate.js +0 -2
  31. package/dist/cjs/channels/modeGate.js.map +0 -1
  32. package/dist/cjs/constants.js +0 -2
  33. package/dist/cjs/constants.js.map +0 -1
  34. package/dist/cjs/createRouter.js +0 -2
  35. package/dist/cjs/createRouter.js.map +0 -1
  36. package/dist/cjs/engine/builder/buildTree.js +0 -2
  37. package/dist/cjs/engine/builder/buildTree.js.map +0 -1
  38. package/dist/cjs/engine/builder/computeCaches.js +0 -2
  39. package/dist/cjs/engine/builder/computeCaches.js.map +0 -1
  40. package/dist/cjs/engine/builder/createRouteTree.js +0 -2
  41. package/dist/cjs/engine/builder/createRouteTree.js.map +0 -1
  42. package/dist/cjs/engine/createMatcher.js +0 -2
  43. package/dist/cjs/engine/createMatcher.js.map +0 -1
  44. package/dist/cjs/engine/operations/routeTreeToDefinitions.js +0 -2
  45. package/dist/cjs/engine/operations/routeTreeToDefinitions.js.map +0 -1
  46. package/dist/cjs/engine/path-matcher/SegmentMatcher.js +0 -2
  47. package/dist/cjs/engine/path-matcher/SegmentMatcher.js.map +0 -1
  48. package/dist/cjs/engine/path-matcher/buildParamMeta.js +0 -2
  49. package/dist/cjs/engine/path-matcher/buildParamMeta.js.map +0 -1
  50. package/dist/cjs/engine/path-matcher/encoding.js +0 -2
  51. package/dist/cjs/engine/path-matcher/encoding.js.map +0 -1
  52. package/dist/cjs/engine/path-matcher/parseSegment.js +0 -2
  53. package/dist/cjs/engine/path-matcher/parseSegment.js.map +0 -1
  54. package/dist/cjs/engine/path-matcher/pathUtils.js +0 -2
  55. package/dist/cjs/engine/path-matcher/pathUtils.js.map +0 -1
  56. package/dist/cjs/engine/path-matcher/percentEncoding.js +0 -2
  57. package/dist/cjs/engine/path-matcher/percentEncoding.js.map +0 -1
  58. package/dist/cjs/engine/path-matcher/registration/buildParts.js +0 -2
  59. package/dist/cjs/engine/path-matcher/registration/buildParts.js.map +0 -1
  60. package/dist/cjs/engine/path-matcher/registration/context.js +0 -2
  61. package/dist/cjs/engine/path-matcher/registration/context.js.map +0 -1
  62. package/dist/cjs/engine/path-matcher/registration/errors.js +0 -2
  63. package/dist/cjs/engine/path-matcher/registration/errors.js.map +0 -1
  64. package/dist/cjs/engine/path-matcher/registration/index.js +0 -2
  65. package/dist/cjs/engine/path-matcher/registration/index.js.map +0 -1
  66. package/dist/cjs/engine/path-matcher/registration/trie.js +0 -2
  67. package/dist/cjs/engine/path-matcher/registration/trie.js.map +0 -1
  68. package/dist/cjs/engine/path-matcher/registration/trieNodes.js +0 -2
  69. package/dist/cjs/engine/path-matcher/registration/trieNodes.js.map +0 -1
  70. package/dist/cjs/engine/search-params/decode.js +0 -2
  71. package/dist/cjs/engine/search-params/decode.js.map +0 -1
  72. package/dist/cjs/engine/search-params/encode.js +0 -2
  73. package/dist/cjs/engine/search-params/encode.js.map +0 -1
  74. package/dist/cjs/engine/search-params/searchParams.js +0 -2
  75. package/dist/cjs/engine/search-params/searchParams.js.map +0 -1
  76. package/dist/cjs/engine/search-params/strategies/array.js +0 -2
  77. package/dist/cjs/engine/search-params/strategies/array.js.map +0 -1
  78. package/dist/cjs/engine/search-params/strategies/boolean.js +0 -2
  79. package/dist/cjs/engine/search-params/strategies/boolean.js.map +0 -1
  80. package/dist/cjs/engine/search-params/strategies/index.js +0 -2
  81. package/dist/cjs/engine/search-params/strategies/index.js.map +0 -1
  82. package/dist/cjs/engine/search-params/strategies/null.js +0 -2
  83. package/dist/cjs/engine/search-params/strategies/null.js.map +0 -1
  84. package/dist/cjs/engine/search-params/strategies/number.js +0 -2
  85. package/dist/cjs/engine/search-params/strategies/number.js.map +0 -1
  86. package/dist/cjs/engine/search-params/utils.js +0 -2
  87. package/dist/cjs/engine/search-params/utils.js.map +0 -1
  88. package/dist/cjs/engine/validation/route-batch.js +0 -2
  89. package/dist/cjs/engine/validation/route-batch.js.map +0 -1
  90. package/dist/cjs/engine/validation/routes.js +0 -2
  91. package/dist/cjs/engine/validation/routes.js.map +0 -1
  92. package/dist/cjs/getNavigator.js +0 -2
  93. package/dist/cjs/getNavigator.js.map +0 -1
  94. package/dist/cjs/guards.js +0 -2
  95. package/dist/cjs/guards.js.map +0 -1
  96. package/dist/cjs/helpers.js +0 -2
  97. package/dist/cjs/helpers.js.map +0 -1
  98. package/dist/cjs/internals.js +0 -2
  99. package/dist/cjs/internals.js.map +0 -1
  100. package/dist/cjs/limits.js +0 -2
  101. package/dist/cjs/limits.js.map +0 -1
  102. package/dist/cjs/namespaces/DependenciesNamespace/dependenciesStore.js +0 -2
  103. package/dist/cjs/namespaces/DependenciesNamespace/dependenciesStore.js.map +0 -1
  104. package/dist/cjs/namespaces/EventBusNamespace/EventBusNamespace.js +0 -2
  105. package/dist/cjs/namespaces/EventBusNamespace/EventBusNamespace.js.map +0 -1
  106. package/dist/cjs/namespaces/NavigationNamespace/InFlightNavigation.js +0 -2
  107. package/dist/cjs/namespaces/NavigationNamespace/InFlightNavigation.js.map +0 -1
  108. package/dist/cjs/namespaces/NavigationNamespace/NavigationNamespace.js +0 -2
  109. package/dist/cjs/namespaces/NavigationNamespace/NavigationNamespace.js.map +0 -1
  110. package/dist/cjs/namespaces/NavigationNamespace/constants.js +0 -2
  111. package/dist/cjs/namespaces/NavigationNamespace/constants.js.map +0 -1
  112. package/dist/cjs/namespaces/NavigationNamespace/transition/completeTransition.js +0 -2
  113. package/dist/cjs/namespaces/NavigationNamespace/transition/completeTransition.js.map +0 -1
  114. package/dist/cjs/namespaces/NavigationNamespace/transition/errorHandling.js +0 -2
  115. package/dist/cjs/namespaces/NavigationNamespace/transition/errorHandling.js.map +0 -1
  116. package/dist/cjs/namespaces/NavigationNamespace/transition/executeNavigation.js +0 -2
  117. package/dist/cjs/namespaces/NavigationNamespace/transition/executeNavigation.js.map +0 -1
  118. package/dist/cjs/namespaces/NavigationNamespace/transition/guardPhase.js +0 -2
  119. package/dist/cjs/namespaces/NavigationNamespace/transition/guardPhase.js.map +0 -1
  120. package/dist/cjs/namespaces/NavigationNamespace/transition/navigateToNotFound.js +0 -2
  121. package/dist/cjs/namespaces/NavigationNamespace/transition/navigateToNotFound.js.map +0 -1
  122. package/dist/cjs/namespaces/OptionsNamespace/OptionsNamespace.js +0 -2
  123. package/dist/cjs/namespaces/OptionsNamespace/OptionsNamespace.js.map +0 -1
  124. package/dist/cjs/namespaces/OptionsNamespace/constants.js +0 -2
  125. package/dist/cjs/namespaces/OptionsNamespace/constants.js.map +0 -1
  126. package/dist/cjs/namespaces/OptionsNamespace/helpers.js +0 -2
  127. package/dist/cjs/namespaces/OptionsNamespace/helpers.js.map +0 -1
  128. package/dist/cjs/namespaces/OptionsNamespace/validators.js +0 -2
  129. package/dist/cjs/namespaces/OptionsNamespace/validators.js.map +0 -1
  130. package/dist/cjs/namespaces/PluginsNamespace/PluginsNamespace.js +0 -2
  131. package/dist/cjs/namespaces/PluginsNamespace/PluginsNamespace.js.map +0 -1
  132. package/dist/cjs/namespaces/PluginsNamespace/constants.js +0 -2
  133. package/dist/cjs/namespaces/PluginsNamespace/constants.js.map +0 -1
  134. package/dist/cjs/namespaces/PluginsNamespace/validators.js +0 -2
  135. package/dist/cjs/namespaces/PluginsNamespace/validators.js.map +0 -1
  136. package/dist/cjs/namespaces/RouteLifecycleNamespace/RouteLifecycleNamespace.js +0 -2
  137. package/dist/cjs/namespaces/RouteLifecycleNamespace/RouteLifecycleNamespace.js.map +0 -1
  138. package/dist/cjs/namespaces/RouterLifecycleNamespace/RouterLifecycleNamespace.js +0 -2
  139. package/dist/cjs/namespaces/RouterLifecycleNamespace/RouterLifecycleNamespace.js.map +0 -1
  140. package/dist/cjs/namespaces/RouterLifecycleNamespace/constants.js +0 -2
  141. package/dist/cjs/namespaces/RouterLifecycleNamespace/constants.js.map +0 -1
  142. package/dist/cjs/namespaces/RoutesNamespace/RoutesNamespace.js +0 -2
  143. package/dist/cjs/namespaces/RoutesNamespace/RoutesNamespace.js.map +0 -1
  144. package/dist/cjs/namespaces/RoutesNamespace/constants.js +0 -2
  145. package/dist/cjs/namespaces/RoutesNamespace/constants.js.map +0 -1
  146. package/dist/cjs/namespaces/RoutesNamespace/forwardChain.js +0 -2
  147. package/dist/cjs/namespaces/RoutesNamespace/forwardChain.js.map +0 -1
  148. package/dist/cjs/namespaces/RoutesNamespace/helpers.js +0 -2
  149. package/dist/cjs/namespaces/RoutesNamespace/helpers.js.map +0 -1
  150. package/dist/cjs/namespaces/RoutesNamespace/routeGuards.js +0 -2
  151. package/dist/cjs/namespaces/RoutesNamespace/routeGuards.js.map +0 -1
  152. package/dist/cjs/namespaces/RoutesNamespace/routesStore.js +0 -2
  153. package/dist/cjs/namespaces/RoutesNamespace/routesStore.js.map +0 -1
  154. package/dist/cjs/namespaces/StateNamespace/StateNamespace.js +0 -2
  155. package/dist/cjs/namespaces/StateNamespace/StateNamespace.js.map +0 -1
  156. package/dist/cjs/pipeline/buildURL.js +0 -2
  157. package/dist/cjs/pipeline/buildURL.js.map +0 -1
  158. package/dist/cjs/pipeline/canonicalize.js +0 -2
  159. package/dist/cjs/pipeline/canonicalize.js.map +0 -1
  160. package/dist/cjs/pipeline/materialize.js +0 -2
  161. package/dist/cjs/pipeline/materialize.js.map +0 -1
  162. package/dist/cjs/routerFSM.js +0 -2
  163. package/dist/cjs/routerFSM.js.map +0 -1
  164. package/dist/cjs/transitionPath.js +0 -2
  165. package/dist/cjs/transitionPath.js.map +0 -1
  166. package/dist/cjs/utils/event-emitter/EventEmitter.js +0 -2
  167. package/dist/cjs/utils/event-emitter/EventEmitter.js.map +0 -1
  168. package/dist/cjs/utils/fsm/fsm.js +0 -2
  169. package/dist/cjs/utils/fsm/fsm.js.map +0 -1
  170. package/dist/cjs/utils/logger/RouterLogger.js +0 -2
  171. package/dist/cjs/utils/logger/RouterLogger.js.map +0 -1
  172. package/dist/cjs/utils/logger/constants.js +0 -2
  173. package/dist/cjs/utils/logger/constants.js.map +0 -1
  174. package/dist/cjs/wiring/wireNamespaces.js +0 -2
  175. package/dist/cjs/wiring/wireNamespaces.js.map +0 -1
  176. package/dist/esm/Router-sWkNlsWW.mjs +0 -2
@@ -1 +0,0 @@
1
- {"version":3,"file":"defaults.js","names":[],"sources":["../../../src/channels/defaults.ts"],"sourcesContent":["// packages/core/src/channels/defaults.ts\n\nimport { assertChannelCorrect } from \"./guard\";\n\nimport type { Params, SearchParams } from \"../types\";\n\n/**\n * Withholds a query default whose key the caller already filled with the RETIRED\n * single-bag spelling — the rule #1570 states for a `forwardTo` chain's\n * defaults, applied where no seam runs to enforce it.\n *\n * Nothing is moved between channels: the caller's key stays in the bag the\n * caller chose, only the default is declined. Without this the default and the\n * caller's params-twin sit in DIFFERENT channels, where no merge ranks them, and\n * the query default wins by default — the §1.1 priority inversion the channel\n * split exists to remove.\n *\n * ⚠ Scoped to `declaredQuery` — the route's `?`-declared names — and the scope\n * is what keeps `buildPath` in step with every other producer. Only a DECLARED\n * query name can have a params-bag \"twin\" at all: that spelling is the v1\n * single-bag form the migrated entry points retired, so withholding is the whole\n * point. A key the route declares NOWHERE (`/u` + `defaultSearch { theme }`) or\n * one that owns a PATH SLOT beside its query twin (`/items/:id?id`, the\n * #843/#1549 carve-out) is not a twin — the caller's params entry and the query\n * default describe different things, and withholding there printed an href the\n * route's own `matchPath` immediately rewrote (the #1552/#1578 class: href ≠\n * destination, with `buildPath` the only producer out of agreement).\n *\n * `undefined` is absence (#1550 / #1551), so a caller's removal marker does not\n * count as \"already filled\" and the default survives it.\n *\n * Returns the input untouched (no allocation) when nothing is withheld — the\n * common path, and the only one the zero-default hot path ever takes.\n */\nexport function withholdFilledSlots(\n defaults: SearchParams | undefined,\n params: Params,\n declaredQuery: readonly string[],\n): SearchParams | undefined {\n if (defaults === undefined || declaredQuery.length === 0) {\n return defaults;\n }\n\n let kept: Record<string, unknown> | undefined;\n let dropped = false;\n\n for (const [key, value] of Object.entries(defaults)) {\n // `Object.hasOwn` before the read, exactly as `findMisChanneledKey` does in\n // `./guard` and for the same reason: a bare `params[key]` walks\n // the PROTOTYPE, so a route declaring `?toString` / `?constructor` /\n // `?valueOf` read as \"the caller already filled this slot\" on an EMPTY bag.\n // The default was then withheld from every LITERAL-form producer while the\n // resolving form still applied it — `buildPath` out of agreement with\n // `navigate`, printing an href its own `matchPath` does not reproduce, which\n // is the #1552/#1578 class this very rule exists to close. (`makeState`\n // joined the literal form in Phase 4, so it withholds too; the shape stayed\n // unreachable there because `PluginApi.makeState`'s P1 guard refuses the\n // triggering bag on the same predicate.)\n if (\n Object.hasOwn(params, key) &&\n params[key] !== undefined &&\n declaredQuery.includes(key)\n ) {\n dropped = true;\n continue;\n }\n\n kept ??= {};\n kept[key] = value;\n }\n\n return dropped ? (kept as SearchParams | undefined) : defaults;\n}\n\n/**\n * Config-time channel check: a route's `defaultParams` may not name a key the\n * route declares with `?`.\n *\n * The static half of \"params and search meet only in the URL\". Without it the\n * router builds a state out of its OWN config that its OWN always-on channel\n * guard then rejects — `start()` throwing `WRONG_CHANNEL` about a bag the user\n * never passed, which is the deferred-crash shape core's invariant guards exist\n * to prevent. The dynamic half (a forwarding hop whose target is only known at\n * resolution) is caught at the `forwardState` seam instead.\n *\n * Runs over the WHOLE config after every rebuild rather than over the routes\n * just added: `setRootPath(\"?lang\")` declares a name on every route at once, so\n * a config that was legal a moment ago can stop being legal without any route\n * changing.\n *\n * Takes `queryNamesOf` as DATA rather than reaching for a matcher, so this\n * subsystem imports nothing from the namespaces or the engine — the same\n * inversion `src/pipeline` makes with its `RouteResolver` port. The caller owns\n * the derivation AND its caches, which matters here: every call site runs on\n * PREPARED artifacts before any swap, with caches local to the attempt, because\n * checking against the live store would validate a tree the rejected batch has\n * not installed. `RoutesNamespace/helpers.assertRouteDefaultChannelsFor` is that\n * caller — a small adapter holding the per-attempt caches, so the four entry\n * points do not each rebuild the closure.\n */\nexport function assertRouteDefaultChannels(\n defaultParams: Readonly<Record<string, Params>>,\n queryNamesOf: (name: string) => readonly string[],\n method: string,\n): void {\n for (const [name, defaults] of Object.entries(defaultParams)) {\n assertChannelCorrect(\n method,\n name,\n defaults,\n queryNamesOf(name),\n \"this route's `defaultParams`\",\n \"Move it to `defaultSearch`\",\n );\n }\n}\n"],"mappings":"8BAkCA,SAAgB,EACd,EACA,EACA,EAC0B,CAC1B,GAAI,IAAa,IAAA,IAAa,EAAc,SAAW,EACrD,OAAO,EAGT,IAAI,EACA,EAAU,GAEd,IAAK,GAAM,CAAC,EAAK,KAAU,OAAO,QAAQ,CAAQ,EAAG,CAYnD,GACE,OAAO,OAAO,EAAQ,CAAG,GACzB,EAAO,KAAS,IAAA,IAChB,EAAc,SAAS,CAAG,EAC1B,CACA,EAAU,GACV,QACF,CAEA,IAAS,CAAC,EACV,EAAK,GAAO,CACd,CAEA,OAAO,EAAW,EAAoC,CACxD,CA4BA,SAAgB,EACd,EACA,EACA,EACM,CACN,IAAK,GAAM,CAAC,EAAM,KAAa,OAAO,QAAQ,CAAa,EACzD,EAAA,qBACE,EACA,EACA,EACA,EAAa,CAAI,EACjB,+BACA,4BACF,CAEJ"}
@@ -1,2 +0,0 @@
1
- function e(e,t){if(t.length!==0&&e!==void 0)for(let n of t){if(!Object.hasOwn(e,n))continue;let t;try{t=e[n]}catch{return}if(t!==void 0)return n}}function t(t,r,i,a,o,s){let c=e(i,a);if(c!==void 0)throw TypeError(`[router.${t}] ${n(r,c,typeof o==`function`?o():o,s)}`)}function n(e,t,n="the `params` argument",r="Pass it in `search` instead"){return`Route "${e}" declares \`${t}\` as a query param, but it was given in ${n} — the path channel. ${r}; the two channels are separate since RFC-4 M2 and the router never moves a key between them.`}exports.assertChannelCorrect=t,exports.findMisChanneledKey=e,exports.misChanneledKeyMessage=n;
2
- //# sourceMappingURL=guard.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"guard.js","names":[],"sources":["../../../src/channels/guard.ts"],"sourcesContent":["// packages/core/src/channels/guard.ts\n\nimport type { Params } from \"../types\";\n\n/**\n * THE predicate of the always-on channel guard: the first key the caller put in\n * the PATH bag while the route declares it as a QUERY param, or `undefined`\n * when the bag is channel-correct.\n *\n * A DETECTOR, not a normaliser — the key is never moved. Moving it is what\n * `separateChannels` (stage ②) used to do — a function that no longer exists.\n * Channel-correctness is the producer's contract now, not a repair the pipeline\n * performs behind everyone's back.\n *\n * Scans `queryNames` (a route's declared query names — small, cached) rather\n * than the bag, so there is no `Object.keys` allocation, and short-circuits on\n * a route with no query declarations, which is the common case.\n *\n * `undefined` is absence on both sides (#1550 / #1551), so an\n * `undefined`-valued key is NOT a mis-channel: it is the documented removal\n * marker `persistent-params` relies on, and it never reaches a built state\n * anyway. A name that also occupies a path slot (`/items/:id?id`) is absent\n * from `queryNames` by construction (#843 / #1549 carve-out), so the collision\n * form is legitimately path-owned and passes.\n *\n * @internal\n */\nexport function findMisChanneledKey(\n params: Params | undefined,\n queryNames: readonly string[],\n): string | undefined {\n if (queryNames.length === 0 || params === undefined) {\n return undefined;\n }\n\n for (const key of queryNames) {\n if (!Object.hasOwn(params, key)) {\n continue;\n }\n\n let value: unknown;\n\n try {\n value = params[key];\n } catch {\n // A DIAGNOSTIC must never become the thing that throws. The bag may be\n // backed by accessors (a Proxy, a getter, a framework's reactive object),\n // and reading one here happens EARLIER than any consumer would have read\n // it — so an accessor that throws would surface from the guard instead of\n // from the code that actually needed the value, moving the origin of an\n // existing failure. Treat it as \"nothing to report\" and let the real\n // consumer hit the same accessor exactly as it did before.\n return undefined;\n }\n\n if (value !== undefined) {\n return key;\n }\n }\n\n return undefined;\n}\n\n/**\n * THE centralized channel check — the single place a mis-channelled bag is\n * refused, wherever it came from.\n *\n * Replaces the repair `separateChannels` (stage ②, since deleted) used to\n * perform at the `forwardState` seam. A key the route declares with `?`, sitting in the PATH\n * bag, is a producer's mistake — the producer named the route, so it knows the\n * declaration — and the router now says so instead of quietly moving the field\n * into the other object. Moving it was invisible: the caller kept believing\n * their bag was the one that shipped, and two producers of the SAME intent\n * could disagree about which channel a key ended up in.\n *\n * `source` names WHOSE bag is wrong, which is the whole diagnostic value at a\n * seam: the caller's argument, a `forwardState` interceptor's return, or the\n * output of a route's own `decodeParams`. It takes a THUNK as well as a string\n * because the seam sits on the navigation hot path — a source that has to be\n * composed (naming the route a chain forwarded from) must not build its string\n * on every call just to discard it on the 99.99% of calls that pass.\n *\n * @internal\n */\nexport function assertChannelCorrect(\n method: string,\n routeName: string,\n params: Params | undefined,\n queryNames: readonly string[],\n source?: string | (() => string),\n remedy?: string,\n): void {\n const key = findMisChanneledKey(params, queryNames);\n\n if (key !== undefined) {\n throw new TypeError(\n `[router.${method}] ${misChanneledKeyMessage(\n routeName,\n key,\n typeof source === \"function\" ? source() : source,\n remedy,\n )}`,\n );\n }\n}\n\n/**\n * The guard's actionable message. One builder for every position, so the\n * wording a user sees does not depend on which door they came through — the\n * facade's `TypeError`, the seam's, the decoder's, and `navigateToState`'s\n * `RouterError(WRONG_CHANNEL)`, which needs the wording WITHOUT the throw and is\n * why this is a separate function from {@link assertChannelCorrect}.\n *\n * @internal\n */\nexport function misChanneledKeyMessage(\n routeName: string,\n key: string,\n source = \"the `params` argument\",\n remedy = \"Pass it in `search` instead\",\n): string {\n return `Route \"${routeName}\" declares \\`${key}\\` as a query param, but it was given in ${source} — the path channel. ${remedy}; the two channels are separate since RFC-4 M2 and the router never moves a key between them.`;\n}\n"],"mappings":"AA2BA,SAAgB,EACd,EACA,EACoB,CAChB,KAAW,SAAW,GAAK,IAAW,IAAA,GAI1C,IAAK,IAAM,KAAO,EAAY,CAC5B,GAAI,CAAC,OAAO,OAAO,EAAQ,CAAG,EAC5B,SAGF,IAAI,EAEJ,GAAI,CACF,EAAQ,EAAO,EACjB,MAAQ,CAQN,MACF,CAEA,GAAI,IAAU,IAAA,GACZ,OAAO,CAEX,CAGF,CAuBA,SAAgB,EACd,EACA,EACA,EACA,EACA,EACA,EACM,CACN,IAAM,EAAM,EAAoB,EAAQ,CAAU,EAElD,GAAI,IAAQ,IAAA,GACV,MAAU,UACR,WAAW,EAAO,IAAI,EACpB,EACA,EACA,OAAO,GAAW,WAAa,EAAO,EAAI,EAC1C,CACF,GACF,CAEJ,CAWA,SAAgB,EACd,EACA,EACA,EAAS,wBACT,EAAS,8BACD,CACR,MAAO,UAAU,EAAU,eAAe,EAAI,2CAA2C,EAAO,uBAAuB,EAAO,8FAChI"}
@@ -1,2 +0,0 @@
1
- const e=require("../constants.js");function t(t,n,r){let i,a=!1;for(let[e,o]of Object.entries(t))n.includes(e)?(i??={},i[e]=o):(a=!0,r?.(e));return a?Object.freeze(i??e.EMPTY_SEARCH):t}exports.admittedSearch=t;
2
- //# sourceMappingURL=modeGate.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"modeGate.js","names":["EMPTY_SEARCH"],"sources":["../../../src/channels/modeGate.ts"],"sourcesContent":["// packages/core/src/channels/modeGate.ts\n\nimport { EMPTY_SEARCH } from \"../constants\";\n\nimport type { SearchParams } from \"../types\";\n\n/**\n * The mode gate (#1575): the query channel restricted to what the active\n * `queryParamsMode` will actually PRINT.\n *\n * Under `loose` the build prints undeclared keys too, so the whole bag is\n * admitted and the caller skips this entirely (`admitsUndeclaredQuery()`).\n * Under `default` / `strict` the build prints declared names only — so a key\n * that survives into `state.search` here can never appear in `state.path`, and\n * the two channels of one state disagree. Filtering at the merge, on BOTH\n * directions, is what buys the invariant `keys(state.search) ⊆\n * keys(matchPath(state.path).search)` in every mode.\n *\n * A DROP, not a move: the key does not migrate to `state.params` (that would\n * re-create the channel ambiguity #1553 is about) — it simply is not state.\n * `validation-plugin` reports the drop; bare core is silent by the same\n * always-on-fixes / opt-in-diagnoses split as the channel guard.\n *\n * Returns the input bag unchanged when nothing is dropped, so the common case\n * (a route whose query keys are all declared) allocates nothing.\n *\n * @internal\n */\nexport function admittedSearch<S extends SearchParams>(\n search: S,\n queryNames: readonly string[],\n onDropped?: (key: string) => void,\n): S {\n let admitted: Record<string, unknown> | undefined;\n let dropped = false;\n\n // `Object.entries` (own enumerable only) rather than `for…in` + `Object.hasOwn`\n // — the same idiom the deleted `separateChannels` used, and it keeps the guard branch\n // out of the file instead of leaving one no test can reach.\n for (const [key, value] of Object.entries(search)) {\n if (queryNames.includes(key)) {\n admitted ??= {};\n admitted[key] = value;\n } else {\n dropped = true;\n // The drop is silent in bare core; `validation-plugin` passes a reporter.\n // Reported from HERE rather than re-derived by the caller so the message\n // can never disagree with what was actually dropped, and so the scan\n // happens once. The callback is only ever supplied when a validator is\n // installed, so the default path stays a plain filter.\n onDropped?.(key);\n }\n }\n\n if (!dropped) {\n return search;\n }\n\n // Frozen, because this is the ONLY branch that hands back a bag the caller did\n // not already freeze: `search` arrives frozen from `mergeWithDefault`, and the\n // no-drop branch returns it untouched. Before nav-pipeline Phase 2 the gap was\n // invisible — every consumer re-merged (and re-froze) downstream in the\n // then-separate `makeState`. `materialize` deliberately does not, so an\n // unfrozen `admitted` reached `state.search` verbatim and broke \"states are\n // deeply frozen\" for exactly the states the gate had touched. Phase 4 folded\n // `makeState` onto `canonicalize` + `materialize`, so there is no re-merge\n // left anywhere: this freeze is now the only one on the drop path.\n return Object.freeze(admitted ?? EMPTY_SEARCH) as S;\n}\n"],"mappings":"mCA4BA,SAAgB,EACd,EACA,EACA,EACG,CACH,IAAI,EACA,EAAU,GAKd,IAAK,GAAM,CAAC,EAAK,KAAU,OAAO,QAAQ,CAAM,EAC1C,EAAW,SAAS,CAAG,GACzB,IAAa,CAAC,EACd,EAAS,GAAO,IAEhB,EAAU,GAMV,IAAY,CAAG,GAiBnB,OAbK,EAaE,OAAO,OAAO,GAAYA,EAAAA,YAAY,EAZpC,CAaX"}
@@ -1,2 +0,0 @@
1
- const e=Object.freeze({ROUTER_NOT_STARTED:`NOT_STARTED`,NO_START_PATH_OR_STATE:`NO_START_PATH_OR_STATE`,ROUTER_ALREADY_STARTED:`ALREADY_STARTED`,ROUTE_NOT_FOUND:`ROUTE_NOT_FOUND`,SAME_STATES:`SAME_STATES`,CANNOT_DEACTIVATE:`CANNOT_DEACTIVATE`,CANNOT_ACTIVATE:`CANNOT_ACTIVATE`,TRANSITION_ERR:`TRANSITION_ERR`,TRANSITION_CANCELLED:`CANCELLED`,ROUTER_DISPOSED:`DISPOSED`,PLUGIN_CONFLICT:`PLUGIN_CONFLICT`,CONTEXT_NAMESPACE_ALREADY_CLAIMED:`CONTEXT_NAMESPACE_ALREADY_CLAIMED`,REENTRANT_NAVIGATION:`REENTRANT_NAVIGATION`,REENTRANT_TREE_MUTATION:`REENTRANT_TREE_MUTATION`,ROUTER_NOT_STOPPED:`NOT_STOPPED`,WRONG_CHANNEL:`WRONG_CHANNEL`}),t=`@@router/UNKNOWN_ROUTE`,n={UNKNOWN_ROUTE:t},r={ROUTER_START:`onStart`,ROUTER_STOP:`onStop`,TRANSITION_START:`onTransitionStart`,TRANSITION_LEAVE_APPROVE:`onTransitionLeaveApprove`,TRANSITION_CANCEL:`onTransitionCancel`,TRANSITION_SUCCESS:`onTransitionSuccess`,TRANSITION_ERROR:`onTransitionError`},i={ROUTER_START:`$start`,ROUTER_STOP:`$stop`,TRANSITION_START:`$$start`,TRANSITION_LEAVE_APPROVE:`$$leaveApprove`,TRANSITION_CANCEL:`$$cancel`,TRANSITION_SUCCESS:`$$success`,TRANSITION_ERROR:`$$error`},a={maxDependencies:100,maxPlugins:50,maxListeners:1e4,warnListeners:1e3,maxLifecycleHandlers:200},o=Object.freeze({}),s=Object.freeze({}),c=Object.freeze({deactivated:Object.freeze([]),activated:Object.freeze([]),intersection:``}),l=Object.freeze({phase:`activating`,reason:`success`,segments:c});exports.DEFAULT_LIMITS=a,exports.DEFAULT_TRANSITION=l,exports.EMPTY_PARAMS=o,exports.EMPTY_SEARCH=s,exports.UNKNOWN_ROUTE=t,exports.constants=n,exports.errorCodes=e,exports.events=i,exports.plugins=r;
2
- //# sourceMappingURL=constants.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"constants.js","names":[],"sources":["../../src/constants.ts"],"sourcesContent":["// packages/core/src/constants.ts\n\nimport type {\n EventToNameMap,\n EventToPluginMap,\n ErrorCodeToValueMap,\n ErrorCodeKeys,\n ErrorCodeValues,\n TransitionMeta,\n} from \"./types\";\n\nexport type ConstantsKeys = \"UNKNOWN_ROUTE\";\n\nexport type Constants = Record<ConstantsKeys, string>;\n\n// =============================================================================\n// Error Codes (migrated from router-error)\n// =============================================================================\n\nexport type ErrorCodes = Record<ErrorCodeKeys, ErrorCodeValues>;\n\n/**\n * Error codes for router operations.\n * Used to identify specific failure scenarios in navigation and lifecycle.\n * Frozen to prevent accidental modifications.\n */\nexport const errorCodes: ErrorCodeToValueMap = Object.freeze({\n ROUTER_NOT_STARTED: \"NOT_STARTED\", // navigate() called before start()\n NO_START_PATH_OR_STATE: \"NO_START_PATH_OR_STATE\", // start() without initial route\n ROUTER_ALREADY_STARTED: \"ALREADY_STARTED\", // start() called twice\n ROUTE_NOT_FOUND: \"ROUTE_NOT_FOUND\", // Navigation to non-existent route\n SAME_STATES: \"SAME_STATES\", // Navigate to current route without reload\n CANNOT_DEACTIVATE: \"CANNOT_DEACTIVATE\", // canDeactivate guard blocked navigation\n CANNOT_ACTIVATE: \"CANNOT_ACTIVATE\", // canActivate guard blocked navigation\n TRANSITION_ERR: \"TRANSITION_ERR\", // Generic transition failure\n TRANSITION_CANCELLED: \"CANCELLED\", // Navigation cancelled by user or new navigation\n ROUTER_DISPOSED: \"DISPOSED\", // Router has been disposed\n PLUGIN_CONFLICT: \"PLUGIN_CONFLICT\", // Plugin tried to extend router with already-existing property\n CONTEXT_NAMESPACE_ALREADY_CLAIMED: \"CONTEXT_NAMESPACE_ALREADY_CLAIMED\", // Plugin tried to claim a context namespace already owned by another plugin\n REENTRANT_NAVIGATION: \"REENTRANT_NAVIGATION\", // navigate() called synchronously from inside a transition-event listener (banned — use await/async listener)\n REENTRANT_TREE_MUTATION: \"REENTRANT_TREE_MUTATION\", // route-CRUD called synchronously from inside a subscribeChanges handler (banned — use await/async/queueMicrotask)\n ROUTER_NOT_STOPPED: \"NOT_STOPPED\", // clear() called while a state is committed — tear down with stop() first, or swap the tree with replace() (#1612)\n WRONG_CHANNEL: \"WRONG_CHANNEL\", // a declared query key was supplied in the path channel (`params`) instead of `search` (#1572)\n});\n\n/**\n * General router constants.\n * Special route names and identifiers.\n */\nexport const UNKNOWN_ROUTE = \"@@router/UNKNOWN_ROUTE\";\n\nexport const constants: Constants = {\n UNKNOWN_ROUTE,\n};\n\n/**\n * Plugin method names.\n * Maps to methods that plugins can implement to hook into router lifecycle.\n */\nexport const plugins: EventToPluginMap = {\n ROUTER_START: \"onStart\", // Plugin method called when router starts\n ROUTER_STOP: \"onStop\", // Plugin method called when router stops\n TRANSITION_START: \"onTransitionStart\", // Plugin method called when navigation begins\n TRANSITION_LEAVE_APPROVE: \"onTransitionLeaveApprove\", // Plugin method called when deactivation guards pass\n TRANSITION_CANCEL: \"onTransitionCancel\", // Plugin method called when navigation cancelled\n TRANSITION_SUCCESS: \"onTransitionSuccess\", // Plugin method called when navigation succeeds\n TRANSITION_ERROR: \"onTransitionError\", // Plugin method called when navigation fails\n};\n\n/**\n * Event names for router event system.\n * Used with addEventListener/removeEventListener for reactive subscriptions.\n */\nexport const events: EventToNameMap = {\n ROUTER_START: \"$start\", // Emitted when router.start() succeeds\n ROUTER_STOP: \"$stop\", // Emitted when router.stop() is called\n TRANSITION_START: \"$$start\", // Emitted when navigation begins\n TRANSITION_LEAVE_APPROVE: \"$$leaveApprove\", // Emitted when deactivation guards pass\n TRANSITION_CANCEL: \"$$cancel\", // Emitted when navigation is cancelled\n TRANSITION_SUCCESS: \"$$success\", // Emitted when navigation completes successfully\n TRANSITION_ERROR: \"$$error\", // Emitted when navigation fails\n};\n\nexport const DEFAULT_LIMITS = {\n maxDependencies: 100,\n maxPlugins: 50,\n maxListeners: 10_000,\n warnListeners: 1000,\n maxLifecycleHandlers: 200,\n} as const;\n\nexport const EMPTY_PARAMS: Readonly<Record<string, never>> = Object.freeze({});\n\n/**\n * Shared frozen empty query bag reused for `State.search` when a navigation\n * carries no query params — the search-channel twin of {@link EMPTY_PARAMS}\n * (RFC-4 M2 / #1548). Lets `makeState` reuse one frozen `{}` (zero transient\n * allocation, #1027) instead of minting an object per query-less state.\n */\nexport const EMPTY_SEARCH: Readonly<Record<string, never>> = Object.freeze({});\n\nconst FROZEN_EMPTY_SEGMENTS = Object.freeze({\n deactivated: Object.freeze([]) as unknown as string[],\n activated: Object.freeze([]) as unknown as string[],\n intersection: \"\",\n});\n\nexport const DEFAULT_TRANSITION = Object.freeze({\n phase: \"activating\",\n reason: \"success\",\n segments: FROZEN_EMPTY_SEGMENTS,\n}) as TransitionMeta;\n"],"mappings":"AA0BA,MAAa,EAAkC,OAAO,OAAO,CAC3D,mBAAoB,cACpB,uBAAwB,yBACxB,uBAAwB,kBACxB,gBAAiB,kBACjB,YAAa,cACb,kBAAmB,oBACnB,gBAAiB,kBACjB,eAAgB,iBAChB,qBAAsB,YACtB,gBAAiB,WACjB,gBAAiB,kBACjB,kCAAmC,oCACnC,qBAAsB,uBACtB,wBAAyB,0BACzB,mBAAoB,cACpB,cAAe,eACjB,CAAC,EAMY,EAAgB,yBAEhB,EAAuB,CAClC,eACF,EAMa,EAA4B,CACvC,aAAc,UACd,YAAa,SACb,iBAAkB,oBAClB,yBAA0B,2BAC1B,kBAAmB,qBACnB,mBAAoB,sBACpB,iBAAkB,mBACpB,EAMa,EAAyB,CACpC,aAAc,SACd,YAAa,QACb,iBAAkB,UAClB,yBAA0B,iBAC1B,kBAAmB,WACnB,mBAAoB,YACpB,iBAAkB,SACpB,EAEa,EAAiB,CAC5B,gBAAiB,IACjB,WAAY,GACZ,aAAc,IACd,cAAe,IACf,qBAAsB,GACxB,EAEa,EAAgD,OAAO,OAAO,CAAC,CAAC,EAQhE,EAAgD,OAAO,OAAO,CAAC,CAAC,EAEvE,EAAwB,OAAO,OAAO,CAC1C,YAAa,OAAO,OAAO,CAAC,CAAC,EAC7B,UAAW,OAAO,OAAO,CAAC,CAAC,EAC3B,aAAc,EAChB,CAAC,EAEY,EAAqB,OAAO,OAAO,CAC9C,MAAO,aACP,OAAQ,UACR,SAAU,CACZ,CAAC"}
@@ -1,2 +0,0 @@
1
- const e=require("./Router.js"),t=(t=[],n={},r={})=>new e.Router(t,n,r);exports.createRouter=t;
2
- //# sourceMappingURL=createRouter.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"createRouter.js","names":["Router"],"sources":["../../src/createRouter.ts"],"sourcesContent":["// packages/core/src/createRouter.ts\n\nimport { Router } from \"./Router\";\n\nimport type { DefaultDependencies, Options, Route } from \"./types\";\n\n/**\n * Creates a new router instance.\n *\n * @param routes - Array of route definitions\n * @param options - Router configuration options\n * @param dependencies - Dependencies to inject into the router\n * @returns A new Router instance\n *\n * @example\n * const router = createRouter([\n * { name: 'home', path: '/' },\n * { name: 'users', path: '/users' },\n * ]);\n *\n * router.start('/');\n */\nexport const createRouter = <\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n routes: Route<Dependencies>[] = [],\n options: Partial<Options<Dependencies>> = {},\n dependencies: Dependencies = {} as Dependencies,\n): Router<Dependencies> => {\n return new Router<Dependencies>(routes, options, dependencies);\n};\n"],"mappings":"+BAsBa,GAGX,EAAgC,CAAC,EACjC,EAA0C,CAAC,EAC3C,EAA6B,CAAC,IAEvB,IAAIA,EAAAA,OAAqB,EAAQ,EAAS,CAAY"}
@@ -1,2 +0,0 @@
1
- function e(t,n){let r=t.path,i=r.startsWith(`~`),a=i?r.slice(1):r,o=a!==``&&!a.startsWith(`/`)&&!a.startsWith(`?`)?`/${a}`:a,s={name:t.name,path:o,absolute:i,children:[],parent:n};if(t.children)for(let n of t.children){let t=e(n,s);s.children.push(t)}return s}function t(t,n,r){let i=e({name:t,path:n},null);for(let t of r){let n=e(t,i);i.children.push(n)}return i}exports.buildTree=t;
2
- //# sourceMappingURL=buildTree.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"buildTree.js","names":[],"sources":["../../../../src/engine/builder/buildTree.ts"],"sourcesContent":["/**\n * Tree Building.\n *\n * Constructs the mutable tree structure from route definitions.\n *\n * @module builder/buildTree\n */\n\nimport type { RouteDefinition } from \"../types\";\n\n// =============================================================================\n// Mutable Node Type (internal)\n// =============================================================================\n\n/**\n * Mutable version of RouteTree used during building.\n * After building, this is converted to immutable RouteTree.\n */\nexport interface MutableRouteNode {\n name: string;\n path: string;\n absolute: boolean;\n children: MutableRouteNode[];\n parent: MutableRouteNode | null;\n}\n\n// =============================================================================\n// Building Functions\n// =============================================================================\n\n/**\n * Creates a mutable node from a route definition.\n *\n * @param definition - Route definition object\n * @param definition.name - Route name\n * @param definition.path - Route path\n * @param definition.children - Optional child routes\n * @param parent - Parent node (null for root)\n * @returns Mutable node\n */\nfunction createNode(\n definition: {\n name: string;\n path: string;\n children?: RouteDefinition[] | undefined;\n },\n parent: MutableRouteNode | null,\n): MutableRouteNode {\n const path = definition.path;\n const absolute = path.startsWith(\"~\");\n const stripped = absolute ? path.slice(1) : path;\n // #1407: normalize a missing leading \"/\". The trie, buildFullPath, and every\n // downstream layer assume a leading-\"/\" path; a non-absolute path without one\n // (`foo`, `:id`) fuses onto its parent across the segment boundary, and an\n // absolute `~foo` (slash-less) compiles a dead route (the trie scans from\n // index 1, dropping the first char). After the `~`-strip, prepend \"/\" to any\n // non-empty path that lacks it — `foo`/`~foo` → `/foo`, `:id` → `/:id`,\n // `foo?q` → `/foo?q` — leaving `/foo`/`~/foo` and the empty root untouched.\n // A query-only path (`?q`) has no leading path segment to slash (the `?` starts\n // the query), so it is skipped too. The path-matcher's \"I only see leading-'/'\n // paths\" invariant becomes correct-by-construction.\n const normalizedPath =\n stripped !== \"\" && !stripped.startsWith(\"/\") && !stripped.startsWith(\"?\")\n ? `/${stripped}`\n : stripped;\n\n const node: MutableRouteNode = {\n name: definition.name,\n path: normalizedPath,\n absolute,\n children: [],\n parent,\n };\n\n // Recursively add children\n if (definition.children) {\n for (const childDef of definition.children) {\n const childNode = createNode(childDef, node);\n\n node.children.push(childNode);\n }\n }\n\n return node;\n}\n\n/**\n * Builds the mutable tree structure from route definitions.\n *\n * Simplified single-pass algorithm:\n * - Creates root node\n * - Adds each route as direct child of root\n * - createNode() handles nested children recursively\n *\n * @param rootName - Root node name (typically \"\")\n * @param rootPath - Root node path (typically \"\")\n * @param routes - Route definitions\n * @returns Mutable root node\n */\nexport function buildTree(\n rootName: string,\n rootPath: string,\n routes: readonly RouteDefinition[],\n): MutableRouteNode {\n const root = createNode({ name: rootName, path: rootPath }, null);\n\n for (const route of routes) {\n const node = createNode(route, root);\n\n root.children.push(node);\n }\n\n return root;\n}\n"],"mappings":"AAwCA,SAAS,EACP,EAKA,EACkB,CAClB,IAAM,EAAO,EAAW,KAClB,EAAW,EAAK,WAAW,GAAG,EAC9B,EAAW,EAAW,EAAK,MAAM,CAAC,EAAI,EAWtC,EACJ,IAAa,IAAM,CAAC,EAAS,WAAW,GAAG,GAAK,CAAC,EAAS,WAAW,GAAG,EACpE,IAAI,IACJ,EAEA,EAAyB,CAC7B,KAAM,EAAW,KACjB,KAAM,EACN,WACA,SAAU,CAAC,EACX,QACF,EAGA,GAAI,EAAW,SACb,IAAK,IAAM,KAAY,EAAW,SAAU,CAC1C,IAAM,EAAY,EAAW,EAAU,CAAI,EAE3C,EAAK,SAAS,KAAK,CAAS,CAC9B,CAGF,OAAO,CACT,CAeA,SAAgB,EACd,EACA,EACA,EACkB,CAClB,IAAM,EAAO,EAAW,CAAE,KAAM,EAAU,KAAM,CAAS,EAAG,IAAI,EAEhE,IAAK,IAAM,KAAS,EAAQ,CAC1B,IAAM,EAAO,EAAW,EAAO,CAAI,EAEnC,EAAK,SAAS,KAAK,CAAI,CACzB,CAEA,OAAO,CACT"}
@@ -1,2 +0,0 @@
1
- const e=require("../path-matcher/buildParamMeta.js"),t=Object.freeze(new Map),n=Object.freeze([]);function r(e){return e.parent?.name?`${e.parent.fullName}.${e.name}`:e.name}function i(e){let t=new Map;for(let n of e)t.set(n.name,n);return t}function a(e,t){let n=[],r=[];for(let i of e){let e=o(i,t);n.push(e),e.absolute||r.push(e)}return{childrenMap:i(n),nonAbsoluteChildren:r}}function o(i,o){let s=e.buildParamMeta(i.path),c=s.urlParams.length===0&&s.queryParams.length===0&&s.spatParams.length===0&&s.pathPattern===i.path?e.EMPTY_PARAM_META:s,l=c.paramTypeMap,u={name:i.name,path:i.path,absolute:i.absolute,parent:o,children:void 0,paramMeta:c,nonAbsoluteChildren:void 0,fullName:``,paramTypeMap:l};if(u.fullName=r(u),i.children.length===0)u.children=t,u.nonAbsoluteChildren=n;else{let{childrenMap:e,nonAbsoluteChildren:t}=a(i.children,u);u.children=e,u.nonAbsoluteChildren=t,Object.freeze(u.nonAbsoluteChildren),Object.freeze(u.children)}return Object.freeze(l),Object.freeze(c.urlParams),Object.freeze(c.queryParams),Object.freeze(c.spatParams),Object.freeze(c),Object.freeze(u),u}function s(e){return o(e,null)}exports.computeCaches=s;
2
- //# sourceMappingURL=computeCaches.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"computeCaches.js","names":["buildParamMeta","EMPTY_PARAM_META"],"sources":["../../../../src/engine/builder/computeCaches.ts"],"sourcesContent":["/**\n * Cache Computation.\n *\n * Computes all pre-computed caches and freezes the tree.\n *\n * @module builder/computeCaches\n */\n\nimport { buildParamMeta, EMPTY_PARAM_META } from \"../path-matcher\";\n\nimport type { MutableRouteNode } from \"./buildTree\";\nimport type { RouteTree } from \"../types\";\n\n// =============================================================================\n// Shared Sentinels (avoid per-node allocation for leaf nodes)\n// =============================================================================\n\nconst EMPTY_CHILDREN_MAP: ReadonlyMap<string, RouteTree> = Object.freeze(\n new Map<string, RouteTree>(),\n);\nconst EMPTY_CHILDREN_ARRAY: readonly RouteTree[] = Object.freeze(\n [] as RouteTree[],\n);\n\n// =============================================================================\n// Cache Computation Functions\n// =============================================================================\n\n/**\n * Computes the full dot-notation name for a node.\n * Parent fullName is always computed before children.\n *\n * @param node - Node to compute for\n * @returns Full name (e.g., \"users.profile\")\n */\nfunction computeFullName(node: RouteTree): string {\n if (!node.parent?.name) {\n return node.name;\n }\n\n // Parent fullName is always set before children are processed\n return `${node.parent.fullName}.${node.name}`;\n}\n\n// =============================================================================\n// Main Computation Function\n// =============================================================================\n\n/**\n * Computes children Map from mutable children array.\n *\n * Children are stored in definition order. Matching priority is handled\n * by the segment trie, not by iteration order.\n *\n * @param childrenArray - Array of processed child nodes\n * @returns ReadonlyMap of child name -> child node (in definition order)\n */\nfunction computeChildrenMap(\n childrenArray: RouteTree[],\n): ReadonlyMap<string, RouteTree> {\n const map = new Map<string, RouteTree>();\n\n for (const child of childrenArray) {\n map.set(child.name, child);\n }\n\n return map;\n}\n\n/**\n * Recursively processes child nodes and computes the children map.\n *\n * @param mutableChildren - Array of mutable child nodes\n * @param parent - Already-processed parent node\n * @returns Children map and non-absolute children array\n */\nfunction processChildren(\n mutableChildren: readonly MutableRouteNode[],\n parent: RouteTree,\n): {\n childrenMap: ReadonlyMap<string, RouteTree>;\n nonAbsoluteChildren: RouteTree[];\n} {\n const childrenArray: RouteTree[] = [];\n const nonAbsoluteChildren: RouteTree[] = [];\n\n for (const childMutable of mutableChildren) {\n const child = processNode(childMutable, parent);\n\n childrenArray.push(child);\n\n if (!child.absolute) {\n nonAbsoluteChildren.push(child);\n }\n }\n\n return {\n childrenMap: computeChildrenMap(childrenArray),\n nonAbsoluteChildren,\n };\n}\n\n/**\n * Recursively processes a mutable node into a frozen RouteTree.\n *\n * This creates a new object with all caches computed and freezes it for\n * immutability.\n *\n * @param mutable - Mutable node to process\n * @param parent - Already-processed parent node (null for root)\n * @returns Frozen RouteTree\n */\nfunction processNode(\n mutable: MutableRouteNode,\n parent: RouteTree | null,\n): RouteTree {\n const freshParamMeta = buildParamMeta(mutable.path);\n // Fully-static node: every collection is a #1009 sentinel and pathPattern is\n // reference-equal to the input path (no query was sliced off) — the wrapper\n // carries zero information, so retain the ONE shared frozen EMPTY_PARAM_META\n // instead of a fresh 6-field object per node. The swap happens here (the\n // retaining consumer), NOT inside buildParamMeta: the validation gate reads\n // `pathPattern` off fresh results and must keep seeing the real pattern.\n const paramMeta =\n freshParamMeta.urlParams.length === 0 &&\n freshParamMeta.queryParams.length === 0 &&\n freshParamMeta.spatParams.length === 0 &&\n freshParamMeta.pathPattern === mutable.path\n ? EMPTY_PARAM_META\n : freshParamMeta;\n const paramTypeMap = paramMeta.paramTypeMap;\n\n // Skeleton node: children and nonAbsoluteChildren are set after recursive\n // child processing, which requires a parent reference to this node.\n const node = {\n name: mutable.name,\n path: mutable.path,\n absolute: mutable.absolute,\n parent,\n children: undefined as unknown as ReadonlyMap<string, RouteTree>,\n paramMeta,\n nonAbsoluteChildren: undefined as unknown as RouteTree[],\n // Stryker disable next-line StringLiteral: equivalent — placeholder overwritten unconditionally on the next statement (`node.fullName = computeFullName(node)`); the initial value is never observed.\n fullName: \"\",\n paramTypeMap,\n };\n\n node.fullName = computeFullName(node);\n\n if (mutable.children.length === 0) {\n node.children = EMPTY_CHILDREN_MAP;\n node.nonAbsoluteChildren = EMPTY_CHILDREN_ARRAY as RouteTree[];\n } else {\n const { childrenMap, nonAbsoluteChildren } = processChildren(\n mutable.children,\n node,\n );\n\n node.children = childrenMap;\n node.nonAbsoluteChildren = nonAbsoluteChildren;\n\n Object.freeze(node.nonAbsoluteChildren);\n Object.freeze(node.children);\n }\n\n Object.freeze(paramTypeMap);\n\n // Close the immutability contract on the nested paramMeta (#747): the node\n // is frozen, but its paramMeta object and arrays were left mutable, so a\n // tree reachable from the public API could be mutated. paramTypeMap is the\n // same ref frozen just above.\n Object.freeze(paramMeta.urlParams);\n Object.freeze(paramMeta.queryParams);\n Object.freeze(paramMeta.spatParams);\n Object.freeze(paramMeta);\n\n Object.freeze(node);\n\n return node;\n}\n\n/**\n * Computes all caches and freezes the tree.\n *\n * This is the final step in building a RouteTree — the result is completely\n * immutable.\n *\n * @param mutableRoot - Mutable root node\n * @returns Frozen RouteTree\n */\nexport function computeCaches(mutableRoot: MutableRouteNode): RouteTree {\n return processNode(mutableRoot, null);\n}\n"],"mappings":"qDAiBM,EAAqD,OAAO,OAChE,IAAI,GACN,EACM,EAA6C,OAAO,OACxD,CAAC,CACH,EAaA,SAAS,EAAgB,EAAyB,CAMhD,OALK,EAAK,QAAQ,KAKX,GAAG,EAAK,OAAO,SAAS,GAAG,EAAK,OAJ9B,EAAK,IAKhB,CAeA,SAAS,EACP,EACgC,CAChC,IAAM,EAAM,IAAI,IAEhB,IAAK,IAAM,KAAS,EAClB,EAAI,IAAI,EAAM,KAAM,CAAK,EAG3B,OAAO,CACT,CASA,SAAS,EACP,EACA,EAIA,CACA,IAAM,EAA6B,CAAC,EAC9B,EAAmC,CAAC,EAE1C,IAAK,IAAM,KAAgB,EAAiB,CAC1C,IAAM,EAAQ,EAAY,EAAc,CAAM,EAE9C,EAAc,KAAK,CAAK,EAEnB,EAAM,UACT,EAAoB,KAAK,CAAK,CAElC,CAEA,MAAO,CACL,YAAa,EAAmB,CAAa,EAC7C,qBACF,CACF,CAYA,SAAS,EACP,EACA,EACW,CACX,IAAM,EAAiBA,EAAAA,eAAe,EAAQ,IAAI,EAO5C,EACJ,EAAe,UAAU,SAAW,GACpC,EAAe,YAAY,SAAW,GACtC,EAAe,WAAW,SAAW,GACrC,EAAe,cAAgB,EAAQ,KACnCC,EAAAA,iBACA,EACA,EAAe,EAAU,aAIzB,EAAO,CACX,KAAM,EAAQ,KACd,KAAM,EAAQ,KACd,SAAU,EAAQ,SAClB,SACA,SAAU,IAAA,GACV,YACA,oBAAqB,IAAA,GAErB,SAAU,GACV,cACF,EAIA,GAFA,EAAK,SAAW,EAAgB,CAAI,EAEhC,EAAQ,SAAS,SAAW,EAC9B,EAAK,SAAW,EAChB,EAAK,oBAAsB,MACtB,CACL,GAAM,CAAE,cAAa,uBAAwB,EAC3C,EAAQ,SACR,CACF,EAEA,EAAK,SAAW,EAChB,EAAK,oBAAsB,EAE3B,OAAO,OAAO,EAAK,mBAAmB,EACtC,OAAO,OAAO,EAAK,QAAQ,CAC7B,CAeA,OAbA,OAAO,OAAO,CAAY,EAM1B,OAAO,OAAO,EAAU,SAAS,EACjC,OAAO,OAAO,EAAU,WAAW,EACnC,OAAO,OAAO,EAAU,UAAU,EAClC,OAAO,OAAO,CAAS,EAEvB,OAAO,OAAO,CAAI,EAEX,CACT,CAWA,SAAgB,EAAc,EAA0C,CACtE,OAAO,EAAY,EAAa,IAAI,CACtC"}
@@ -1,2 +0,0 @@
1
- const e=require("./buildTree.js"),t=require("./computeCaches.js");function n(n,r,i){return t.computeCaches(e.buildTree(n,r,i))}exports.createRouteTree=n;
2
- //# sourceMappingURL=createRouteTree.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"createRouteTree.js","names":["computeCaches","buildTree"],"sources":["../../../../src/engine/builder/createRouteTree.ts"],"sourcesContent":["/**\n * Route Tree Builder.\n *\n * Creates an immutable RouteTree from route definitions.\n *\n * @module builder/createRouteTree\n */\n\nimport { buildTree } from \"./buildTree\";\nimport { computeCaches } from \"./computeCaches\";\n\nimport type { RouteDefinition, RouteTree } from \"../types\";\n\n/**\n * Creates an immutable RouteTree from route definitions.\n *\n * @param name - Root node name (typically empty string)\n * @param path - Root node path (typically empty string)\n * @param routes - Route definitions to add\n * @returns Immutable RouteTree\n *\n * @example\n * ```typescript\n * const tree = createRouteTree(\"\", \"\", [\n * { name: \"users\", path: \"/users\" },\n * { name: \"users.profile\", path: \"/:id\" },\n * ]);\n * ```\n */\nexport function createRouteTree(\n name: string,\n path: string,\n routes: readonly RouteDefinition[],\n): RouteTree {\n return computeCaches(buildTree(name, path, routes));\n}\n"],"mappings":"kEA6BA,SAAgB,EACd,EACA,EACA,EACW,CACX,OAAOA,EAAAA,cAAcC,EAAAA,UAAU,EAAM,EAAM,CAAM,CAAC,CACpD"}
@@ -1,2 +0,0 @@
1
- const e=require("./path-matcher/SegmentMatcher.js"),t=require("./search-params/searchParams.js");function n(n){let r=n?.queryParams;return new e.SegmentMatcher({...n?.caseSensitive!==void 0&&{caseSensitive:n.caseSensitive},...n?.strictTrailingSlash!==void 0&&{strictTrailingSlash:n.strictTrailingSlash},...n?.strictQueryParams!==void 0&&{strictQueryParams:n.strictQueryParams},...n?.urlParamsEncoding!==void 0&&{urlParamsEncoding:n.urlParamsEncoding},parseQueryString:e=>t.parseQuery(e,r),buildQueryString:e=>t.build(e,r)})}exports.createMatcher=n;
2
- //# sourceMappingURL=createMatcher.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"createMatcher.js","names":["SegmentMatcher","parseQuery","build"],"sources":["../../../src/engine/createMatcher.ts"],"sourcesContent":["/**\n * Matcher Factory.\n *\n * Creates a pre-configured path matcher with search-params DI baked in.\n * This is the public API for creating matchers — SegmentMatcher class\n * and search-params functions are internal implementation details.\n *\n * @module route-tree/createMatcher\n */\n\nimport { SegmentMatcher } from \"./path-matcher\";\nimport { parseQuery, build } from \"./search-params\";\n\nimport type { Options } from \"./search-params\";\n\n// =============================================================================\n// Public Types (route-tree's own — no path-matcher or search-params types leak)\n// =============================================================================\n\n/**\n * Query string formatting options.\n *\n * Controls how arrays, booleans, nulls, and numbers are serialized in query strings.\n */\nexport type QueryParamsConfig = Readonly<Options>;\n\n/**\n * Options for creating a path matcher.\n */\nexport interface CreateMatcherOptions {\n readonly caseSensitive?: boolean;\n readonly strictTrailingSlash?: boolean;\n readonly strictQueryParams?: boolean;\n readonly urlParamsEncoding?: \"default\" | \"uri\" | \"uriComponent\" | \"none\";\n readonly queryParams?: QueryParamsConfig;\n}\n\n/**\n * Path matcher instance type.\n *\n * Opaque type — consumers use methods (match, buildPath, hasRoute, etc.)\n * without knowing the underlying SegmentMatcher implementation.\n */\nexport type Matcher = SegmentMatcher;\n\n// =============================================================================\n// Factory\n// =============================================================================\n\n/**\n * Creates a path matcher with search-params DI baked in.\n *\n * @param options - Matcher configuration\n * @returns Configured matcher instance\n *\n * @example\n * ```typescript\n * const matcher = createMatcher({\n * strictTrailingSlash: true,\n * queryParams: { booleanFormat: \"auto\" },\n * });\n * matcher.registerTree(tree);\n * const result = matcher.match(\"/users/123\");\n * ```\n */\nexport function createMatcher(options?: CreateMatcherOptions): Matcher {\n const qp = options?.queryParams;\n\n // Conditional spread: exactOptionalPropertyTypes forbids setting optional\n // properties to undefined — only include properties that are defined.\n return new SegmentMatcher({\n ...(options?.caseSensitive !== undefined && {\n caseSensitive: options.caseSensitive,\n }),\n ...(options?.strictTrailingSlash !== undefined && {\n strictTrailingSlash: options.strictTrailingSlash,\n }),\n ...(options?.strictQueryParams !== undefined && {\n strictQueryParams: options.strictQueryParams,\n }),\n ...(options?.urlParamsEncoding !== undefined && {\n urlParamsEncoding: options.urlParamsEncoding,\n }),\n // qs is ALREADY the query substring (SegmentMatcher split at the first \"?\");\n // parseQuery parses it verbatim — a path-accepting wrapper would re-split at a\n // \"?\" inside a query value and drop the param (#1292).\n parseQueryString: (qs: string) => parseQuery(qs, qp),\n buildQueryString: (params: Record<string, unknown>) => build(params, qp),\n });\n}\n"],"mappings":"iGAiEA,SAAgB,EAAc,EAAyC,CACrE,IAAM,EAAK,GAAS,YAIpB,OAAO,IAAIA,EAAAA,eAAe,CACxB,GAAI,GAAS,gBAAkB,IAAA,IAAa,CAC1C,cAAe,EAAQ,aACzB,EACA,GAAI,GAAS,sBAAwB,IAAA,IAAa,CAChD,oBAAqB,EAAQ,mBAC/B,EACA,GAAI,GAAS,oBAAsB,IAAA,IAAa,CAC9C,kBAAmB,EAAQ,iBAC7B,EACA,GAAI,GAAS,oBAAsB,IAAA,IAAa,CAC9C,kBAAmB,EAAQ,iBAC7B,EAIA,iBAAmB,GAAeC,EAAAA,WAAW,EAAI,CAAE,EACnD,iBAAmB,GAAoCC,EAAAA,MAAM,EAAQ,CAAE,CACzE,CAAC,CACH"}
@@ -1,2 +0,0 @@
1
- function e(t){let n=t.absolute?`~${t.path}`:t.path,r={name:t.name,path:n};return t.children.size>0&&(r.children=Array.from(t.children.values(),e)),r}function t(t){return Array.from(t.children.values(),e)}exports.nodeToDefinition=e,exports.routeTreeToDefinitions=t;
2
- //# sourceMappingURL=routeTreeToDefinitions.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"routeTreeToDefinitions.js","names":[],"sources":["../../../../src/engine/operations/routeTreeToDefinitions.ts"],"sourcesContent":["/**\n * RouteTree to RouteDefinition Converter.\n *\n * Converts a RouteTree back to an array of RouteDefinition objects.\n * Used for serialization and router cloning.\n *\n * @module operations/routeTreeToDefinitions\n */\n\nimport type { RouteDefinition, RouteTree } from \"../types\";\n\n/**\n * Converts a single RouteTree node to a RouteDefinition.\n *\n * If the node has absolute=true, the path is prefixed with '~'\n * to reconstruct the original definition format.\n *\n * @param node - RouteTree node to convert\n * @returns RouteDefinition with name, path, and optional children\n */\nexport function nodeToDefinition(node: RouteTree): RouteDefinition {\n // Reconstruct absolute path marker if needed\n const path = node.absolute ? `~${node.path}` : node.path;\n\n const def: RouteDefinition = {\n name: node.name,\n path,\n };\n\n if (node.children.size > 0) {\n def.children = Array.from(node.children.values(), nodeToDefinition);\n }\n\n return def;\n}\n\n/**\n * Converts a RouteTree back to an array of RouteDefinition objects.\n *\n * This is the inverse of createRouteTree - it extracts the minimal\n * definition data (name, path, children) from the computed tree.\n *\n * Note: Extra properties stored in RouteDefinition (like canActivate,\n * forwardTo, encodeParams, etc.) are NOT preserved in RouteTree,\n * so they won't be present in the output.\n *\n * @example\n * ```ts\n * const tree = createRouteTree(\"\", \"\", [\n * { name: \"users\", path: \"/users\", children: [\n * { name: \"profile\", path: \"/:id\" }\n * ]}\n * ]);\n *\n * const definitions = routeTreeToDefinitions(tree);\n * // [{ name: \"users\", path: \"/users\", children: [{ name: \"profile\", path: \"/:id\" }] }]\n * ```\n *\n * @param tree - RouteTree to convert\n * @returns Array of RouteDefinition objects (top-level routes only)\n */\nexport function routeTreeToDefinitions(tree: RouteTree): RouteDefinition[] {\n return Array.from(tree.children.values(), nodeToDefinition);\n}\n"],"mappings":"AAoBA,SAAgB,EAAiB,EAAkC,CAEjE,IAAM,EAAO,EAAK,SAAW,IAAI,EAAK,OAAS,EAAK,KAE9C,EAAuB,CAC3B,KAAM,EAAK,KACX,MACF,EAMA,OAJI,EAAK,SAAS,KAAO,IACvB,EAAI,SAAW,MAAM,KAAK,EAAK,SAAS,OAAO,EAAG,CAAgB,GAG7D,CACT,CA2BA,SAAgB,EAAuB,EAAoC,CACzE,OAAO,MAAM,KAAK,EAAK,SAAS,OAAO,EAAG,CAAgB,CAC5D"}
@@ -1,2 +0,0 @@
1
- const e=require("./encoding.js"),t=require("./pathUtils.js"),n=require("./percentEncoding.js"),r=require("./registration/index.js");function i(e){return typeof e==`string`?e:typeof e==`object`?JSON.stringify(e):String(e)}const a=Object.freeze({});var o=class{get options(){return this.#e}#e;#t=t.createSegmentNode();#n=new Map;#r=new Map;#i={cleanPath:``,normalized:``,queryString:void 0};#a=[];#o=``;#s;#c;constructor(t){this.#e={caseSensitive:t.caseSensitive??!0,strictTrailingSlash:t.strictTrailingSlash??!1,strictQueryParams:t.strictQueryParams??!1,urlParamsEncoding:t.urlParamsEncoding??`default`,parseQueryString:t.parseQueryString,buildQueryString:t.buildQueryString},this.#s=this.#e.caseSensitive,this.#c=this.#e.urlParamsEncoding===`none`?null:e.DECODING_METHODS[this.#e.urlParamsEncoding]}registerTree(e){this.#a=e.paramMeta.queryParams,r.registerNode({root:this.#t,options:this.#e,routesByName:this.#n,staticCache:this.#r,rootQueryParams:this.#a,rootUrlParams:e.paramMeta.urlParams},e,``,[],null)}match(e){if(!this.#f(e))return;let{cleanPath:t,normalized:n,queryString:r}=this.#i,i=this.#s?n:n.toLowerCase(),a=this.#r.get(i);if(a)return this.#e.strictTrailingSlash&&!this.#g(t,a)?void 0:r===void 0&&a.cachedResult?a.cachedResult:this.#m(a,{},r);let o={},s=this.#_(n,o);if(s&&!(this.#e.strictTrailingSlash&&!this.#g(t,s))&&this.#b(o))return this.#m(s,o,r)}buildPath(e,t,n,r){let i=this.#n.get(e);if(!i)throw Error(`[SegmentMatcher.buildPath] '${e}' is not defined`);let a=this.#l(i,t),o=this.#u(a,r?.trailingSlash),s=this.#d(i,n??t,r?.queryParamsMode);return o+(s?`?${s}`:``)}getSegmentsByName(e){return this.#n.get(e)?.matchSegments}getMetaByName(e){return this.#n.get(e)?.meta}getDeclaredQueryParams(e){return this.#n.get(e)?.declaredQueryParams}hasRoute(e){return this.#n.has(e)}#l(e,t){let n=e.buildStaticParts,r=e.buildParamSlots;if(r.length===0)return n[0];let a=n[0];for(let[e,o]of r.entries()){let r=t?.[o.paramName];if(r==null)throw Error(`[SegmentMatcher.buildPath] Missing required param '${o.paramName}'`);if(r===``)throw Error(`[SegmentMatcher.buildPath] Missing required param '${o.paramName}' (empty string)`);let s=o.encoder(i(r));a+=s+n[e+1]}return a}#u(e,t){return t===`always`&&!e.endsWith(`/`)?`${e}/`:t===`never`&&e!==`/`&&e.endsWith(`/`)?e.slice(0,-1):e}#d(e,t,n){if(!t||e.declaredQueryParams.length===0&&n!==`loose`)return``;let r={},i=!1;for(let n of e.declaredQueryParams)n in t&&(r[n]=t[n],i=!0);if(n===`loose`)for(let n in t)!Object.hasOwn(t,n)||e.declaredQueryParamsSet.has(n)||e.buildParamNamesSet.has(n)||(r[n]=t[n],i=!0);return i?this.#e.buildQueryString(r):``}#f(e){if(e===``&&(e=`/`),e.codePointAt(0)!==47)return!1;let n=this.#p(e);if(n===-2)return!1;n===-3&&(e=this.#o);let r=n>=0?e.slice(0,n):e,i=n>=0?e.slice(n+1):void 0;if(i!==void 0){let e=i.indexOf(`#`);e!==-1&&(i=i.slice(0,e))}let a=t.normalizeTrailingSlash(r);return this.#i.cleanPath=r,this.#i.normalized=a,this.#i.queryString=i,!0}#p(e){let t=!1;for(let n=0;n<e.length;n++){let r=e.codePointAt(n);if(r===35)return this.#o=e.slice(0,n),-3;if(r===63)return n;if(r>=128)return-2;if(r===47){if(t)return-2;t=!0}else t=!1}return-1}#m(e,t,n){let r=a;if(n!==void 0){let t=this.#h(e,n);if(t===void 0)return;r=t}return{segments:e.matchSegments,params:t,search:r,meta:e.meta}}#h(e,t){let n;try{n=this.#e.parseQueryString(t)}catch{return}if(this.#e.strictQueryParams){let t=e.declaredQueryParamsSet;for(let e in n)if(!t.has(e))return}return n}#g(e,t){return(e.length>1&&e.endsWith(`/`))===t.hasTrailingSlash}#_(e,t){return e.length===1?this.#t.slashChildRoute??this.#t.route:this.#v(this.#t,e,1,t)}#v(e,t,n,r){let i=e,a=t.length,o=this.#s;for(;n<=a;){let e=t.indexOf(`/`,n),s=e===-1?a:e,c=t.slice(n,s),l=o?c:c.toLowerCase(),u;if(l in i.staticChildren)u=i.staticChildren[l];else if(i.paramChild){let e=i.paramChild;if(i.splatChild!==void 0){let a={[e.name]:c},o=this.#v(e.node,t,s+1,a);return o===void 0?this.#y(i.splatChild,t,n,r):(Object.assign(r,a),o)}u=e.node,r[e.name]=c}else if(i.splatChild)return this.#y(i.splatChild,t,n,r);else return;i=u,n=s+1}return i.slashChildRoute??i.route}#y(e,t,n,r){let i=e.node;if(!i.hasChildren)return r[e.name]=t.slice(n),i.route;let a={},o=this.#v(i,t,n,a);return o?(Object.assign(r,a),o):(r[e.name]=t.slice(n),i.route)}#b(e){let t=this.#c;if(!t)return!0;for(let r in e){let i=e[r];if(i.includes(`%`)){if(!n.validatePercentEncoding(i))return!1;try{e[r]=t(i)}catch{return!1}}}return!0}};exports.SegmentMatcher=o;
2
- //# sourceMappingURL=SegmentMatcher.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"SegmentMatcher.js","names":["#options","#root","createSegmentNode","#routesByName","#staticCache","#prepared","#caseSensitive","#decode","DECODING_METHODS","#rootQueryParams","#preparePath","#checkTrailingSlash","#buildResult","#traverse","#decodeParams","#buildUrlPath","#applyTrailingSlash","#buildQueryStringForBuild","#scanPath","#scanTruncated","normalizeTrailingSlash","#parseSearch","#traverseFrom","#matchSplat","validatePercentEncoding"],"sources":["../../../../src/engine/path-matcher/SegmentMatcher.ts"],"sourcesContent":["import { DECODING_METHODS } from \"./encoding\";\nimport { createSegmentNode, normalizeTrailingSlash } from \"./pathUtils\";\nimport { validatePercentEncoding } from \"./percentEncoding\";\nimport { registerNode } from \"./registration\";\n\nimport type {\n BuildPathOptions,\n CompiledRoute,\n MatcherInputNode,\n MatchResult,\n ResolvedMatcherOptions,\n SegmentMatcherOptions,\n SegmentNode,\n} from \"./types\";\n\n// =============================================================================\n// Helpers\n// =============================================================================\n\n/** Coerces a route param value (typed `unknown`, contractually primitive) to\n * the string the encoder receives. Objects are JSON-stringified. */\nfunction stringifyParamValue(value: unknown): string {\n // Stryker disable next-line BlockStatement: equivalent — String(value) === value for a string, so removing the early return is identical\n if (typeof value === \"string\") {\n return value;\n }\n\n if (typeof value === \"object\") {\n return JSON.stringify(value);\n }\n\n // eslint-disable-next-line @typescript-eslint/no-base-to-string -- route params are typed `unknown` but contractually primitive\n return String(value);\n}\n\n/** Shared frozen empty query object reused for every query-less match. */\nconst EMPTY_SEARCH: Readonly<Record<string, unknown>> = Object.freeze({});\n\n// =============================================================================\n// SegmentMatcher Class\n// =============================================================================\n\nexport class SegmentMatcher {\n get options(): ResolvedMatcherOptions {\n return this.#options;\n }\n\n readonly #options: ResolvedMatcherOptions;\n\n readonly #root: SegmentNode = createSegmentNode();\n readonly #routesByName = new Map<string, CompiledRoute>();\n readonly #staticCache = new Map<string, CompiledRoute>();\n\n // H1: Reusable object eliminates tuple allocation per match() call\n readonly #prepared = {\n // Stryker disable next-line StringLiteral: equivalent — #prepared.cleanPath is overwritten by #preparePath before any read (dead initializer)\n cleanPath: \"\",\n // Stryker disable next-line StringLiteral: equivalent — #prepared.normalized is overwritten by #preparePath before any read (dead initializer)\n normalized: \"\",\n queryString: undefined as string | undefined,\n };\n\n // Stryker disable next-line ArrayDeclaration: equivalent — #rootQueryParams is overwritten by registerTree before any read (dead initializer)\n #rootQueryParams: readonly string[] = [];\n // Stryker disable next-line StringLiteral: equivalent — #scanTruncated is set by #scanPath before it is read (dead initializer)\n #scanTruncated = \"\";\n\n readonly #caseSensitive: boolean;\n readonly #decode: ((param: string) => string) | null;\n\n constructor(options: SegmentMatcherOptions) {\n this.#options = {\n caseSensitive: options.caseSensitive ?? true,\n strictTrailingSlash: options.strictTrailingSlash ?? false,\n strictQueryParams: options.strictQueryParams ?? false,\n urlParamsEncoding: options.urlParamsEncoding ?? \"default\",\n parseQueryString: options.parseQueryString,\n buildQueryString: options.buildQueryString,\n };\n\n this.#caseSensitive = this.#options.caseSensitive;\n this.#decode =\n this.#options.urlParamsEncoding === \"none\"\n ? null\n : DECODING_METHODS[this.#options.urlParamsEncoding];\n }\n\n registerTree(node: MatcherInputNode): void {\n this.#rootQueryParams = node.paramMeta.queryParams;\n registerNode(\n {\n root: this.#root,\n options: this.#options,\n routesByName: this.#routesByName,\n staticCache: this.#staticCache,\n rootQueryParams: this.#rootQueryParams,\n rootUrlParams: node.paramMeta.urlParams,\n },\n node,\n \"\",\n [],\n null,\n );\n }\n\n match(path: string): MatchResult | undefined {\n if (!this.#preparePath(path)) {\n return undefined;\n }\n\n const { cleanPath, normalized, queryString } = this.#prepared;\n\n const cacheKey = this.#caseSensitive\n ? normalized\n : // Stryker disable next-line MethodExpression: equivalent — the case-insensitive cache key only governs a hit; a miss falls through to #traverse (also case-insensitive), same result (proven by injection)\n normalized.toLowerCase();\n const cached = this.#staticCache.get(cacheKey);\n\n // Stryker disable next-line BlockStatement: equivalent — emptying the cache-hit block routes through #traverse, which resolves the same route (cache is a pure optimization)\n if (cached) {\n if (\n this.#options.strictTrailingSlash &&\n !this.#checkTrailingSlash(cleanPath, cached)\n ) {\n return undefined;\n }\n\n // Stryker disable next-line BlockStatement: equivalent — #buildResult recomputes cached.cachedResult identically (cache short-circuit)\n if (queryString === undefined && cached.cachedResult) {\n return cached.cachedResult;\n }\n\n return this.#buildResult(cached, {}, queryString);\n }\n\n const params: Record<string, string> = {};\n const route = this.#traverse(normalized, params);\n\n if (!route) {\n return undefined;\n }\n\n if (\n this.#options.strictTrailingSlash &&\n !this.#checkTrailingSlash(cleanPath, route)\n ) {\n return undefined;\n }\n\n // Decode the captured params. `match()` must never throw — a malformed\n // percent sequence (#737) makes this return false → the URL is unmatched.\n if (!this.#decodeParams(params)) {\n return undefined;\n }\n\n return this.#buildResult(route, params, queryString);\n }\n\n buildPath(\n name: string,\n params?: Record<string, unknown>,\n search?: Record<string, unknown>,\n options?: BuildPathOptions,\n ): string {\n const route = this.#routesByName.get(name);\n\n if (!route) {\n throw new Error(`[SegmentMatcher.buildPath] '${name}' is not defined`);\n }\n\n const path = this.#buildUrlPath(route, params);\n const finalPath = this.#applyTrailingSlash(path, options?.trailingSlash);\n // Search-aware (RFC-4 M2 / #1548): when an explicit `search` bag is passed,\n // the query string is built from it; the path comes from `params`. So a\n // colliding name (`/items/:id?id` with `buildPath(\"items\", {id:5}, {id:7})`)\n // emits `/items/5?id=7` — path wins its slot, query wins its own (the killed\n // #843 precedence). A v1 caller passes no `search`, so `search ?? params`\n // falls back to extracting the query half from the single bag, unchanged.\n const queryString = this.#buildQueryStringForBuild(\n route,\n search ?? params,\n options?.queryParamsMode,\n );\n\n return finalPath + (queryString ? `?${queryString}` : \"\");\n }\n\n getSegmentsByName(name: string): readonly MatcherInputNode[] | undefined {\n // Derived from #routesByName — `compiled.matchSegments` is the same frozen\n // array a dedicated #segmentsByName index would store, so the index was\n // pure duplication (#1010).\n return this.#routesByName.get(name)?.matchSegments;\n }\n\n getMetaByName(\n name: string,\n ): Readonly<Record<string, Record<string, \"url\" | \"query\">>> | undefined {\n return this.#routesByName.get(name)?.meta;\n }\n\n /**\n * The route's declared query-param names — the SAME registry the query-string\n * build reads (`#buildQueryStringForBuild`), so a consumer classifying keys\n * into channels cannot drift from what this matcher actually prints (#1556).\n *\n * Unlike a walk over {@link getSegmentsByName}, this includes the ROOT node's\n * `?`-declarations (`setRootPath(\"?a&b\")` — how persistent-params declares its\n * keys): the root is captured in `#rootQueryParams` at `registerTree` and is\n * deliberately NOT part of `matchSegments`, so a segment walk silently misses\n * it. Path-slot collisions (`/items/:id?id`) are NOT filtered here — the\n * caller owns that policy (core subtracts its `urlParams`, #843 / #1549).\n */\n getDeclaredQueryParams(name: string): readonly string[] | undefined {\n return this.#routesByName.get(name)?.declaredQueryParams;\n }\n\n hasRoute(name: string): boolean {\n return this.#routesByName.has(name);\n }\n\n #buildUrlPath(\n route: CompiledRoute,\n params: Record<string, unknown> | undefined,\n ): string {\n const parts = route.buildStaticParts;\n const slots = route.buildParamSlots;\n\n // Stryker disable next-line BlockStatement: equivalent — the general loop returns parts[0] when slots is empty, identical to this fast path\n if (slots.length === 0) {\n return parts[0];\n }\n\n let result = parts[0];\n\n for (const [i, slot] of slots.entries()) {\n const value = params?.[slot.paramName];\n\n // 3-token grammar (M1): every param slot is required — no optional-omit\n // branch. A missing param is an error.\n if (value === undefined || value === null) {\n throw new Error(\n `[SegmentMatcher.buildPath] Missing required param '${slot.paramName}'`,\n );\n }\n\n // #740 item 3: an empty value collapses the segment, silently producing a\n // path that matches the parent route (`buildPath(\"u.p\", {id:\"\"})` →\n // `/users/` → matches `u`). Reject it like a missing param.\n if (value === \"\") {\n throw new Error(\n `[SegmentMatcher.buildPath] Missing required param '${slot.paramName}' (empty string)`,\n );\n }\n\n const encoded = slot.encoder(stringifyParamValue(value));\n\n result += encoded + parts[i + 1];\n }\n\n return result;\n }\n\n #applyTrailingSlash(\n path: string,\n mode: BuildPathOptions[\"trailingSlash\"],\n ): string {\n if (mode === \"always\" && !path.endsWith(\"/\")) {\n return `${path}/`;\n }\n\n /* v8 ignore next 3 -- @preserve: trailing slash may not appear in buildStaticParts; integration-tested via core */\n if (mode === \"never\" && path !== \"/\" && path.endsWith(\"/\")) {\n return path.slice(0, -1);\n }\n\n return path;\n }\n\n #buildQueryStringForBuild(\n route: CompiledRoute,\n params: Record<string, unknown> | undefined,\n queryParamsMode: BuildPathOptions[\"queryParamsMode\"],\n ): string {\n if (!params) {\n return \"\";\n }\n\n // Stryker disable next-line BlockStatement: equivalent — the downstream !hasKeys guard also returns '' for a route with no declared query params\n if (route.declaredQueryParams.length === 0 && queryParamsMode !== \"loose\") {\n return \"\";\n }\n\n const queryObj: Record<string, unknown> = {};\n // Stryker disable next-line BooleanLiteral: equivalent — buildQueryString({}) === '' so the hasKeys initial value is unobservable when no keys are added\n let hasKeys = false;\n\n for (const name of route.declaredQueryParams) {\n // Stryker disable next-line BlockStatement: equivalent — buildQueryString strips undefined, so adding absent declared keys instead of continue changes nothing\n if (!(name in params)) {\n continue;\n }\n\n queryObj[name] = params[name];\n hasKeys = true;\n }\n\n if (queryParamsMode === \"loose\") {\n for (const paramKey in params) {\n if (\n !Object.hasOwn(params, paramKey) ||\n route.declaredQueryParamsSet.has(paramKey) ||\n route.buildParamNamesSet.has(paramKey)\n ) {\n continue;\n }\n\n queryObj[paramKey] = params[paramKey];\n hasKeys = true;\n }\n }\n\n // Stryker disable next-line BlockStatement: equivalent — buildQueryString({}) === '' so removing the !hasKeys early return yields the same ''\n if (!hasKeys) {\n return \"\";\n }\n\n return this.#options.buildQueryString(queryObj);\n }\n\n // H2: Single-pass scanner — replaces 4 separate scans (indexOf(\"#\"), regex unicode, indexOf(\"?\"), includes(\"//\"))\n #preparePath(path: string): boolean {\n if (path === \"\") {\n path = \"/\";\n }\n\n if (path.codePointAt(0) !== 0x2f /* / */) {\n return false;\n }\n\n const qIdx = this.#scanPath(path);\n\n if (qIdx === -2) {\n return false;\n }\n\n if (qIdx === -3) {\n path = this.#scanTruncated;\n }\n\n const pathPart = qIdx >= 0 ? path.slice(0, qIdx) : path;\n let queryString = qIdx >= 0 ? path.slice(qIdx + 1) : undefined;\n\n // #842: a fragment (`#…`) AFTER the query separator is not seen by\n // #scanPath (it returns at the first `?`), so it would otherwise be folded\n // into the query string and parsed into a param value (e.g. `?a=1#frag` →\n // `a=\"1#frag\"`). A fragment is everything after the first `#` in the whole\n // URL and must be stripped before query parsing — a native indexOf on the\n // (short) query substring, only when a query exists, is ~free (a `#` BEFORE\n // the `?` is already handled by #scanPath via the -3 truncation branch).\n if (queryString !== undefined) {\n const hashIdx = queryString.indexOf(\"#\");\n\n if (hashIdx !== -1) {\n queryString = queryString.slice(0, hashIdx);\n }\n }\n\n const normalized = normalizeTrailingSlash(pathPart);\n\n this.#prepared.cleanPath = pathPart;\n this.#prepared.normalized = normalized;\n this.#prepared.queryString = queryString;\n\n return true;\n }\n\n // Returns: qIdx >= 0 (found ?), -1 (no ? or #), -2 (invalid), -3 (truncated at #, result in #scanTruncated)\n #scanPath(path: string): number {\n let prevSlash = false;\n\n for (let i = 0; i < path.length; i++) {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- bounds-checked by loop condition\n const ch = path.codePointAt(i)!;\n\n if (ch === 0x23 /* # */) {\n this.#scanTruncated = path.slice(0, i);\n\n return -3;\n }\n\n if (ch === 0x3f /* ? */) {\n return i;\n }\n\n if (ch >= 0x80) {\n return -2;\n }\n\n if (ch === 0x2f /* / */) {\n if (prevSlash) {\n return -2;\n }\n\n prevSlash = true;\n } else {\n prevSlash = false;\n }\n }\n\n return -1;\n }\n\n #buildResult(\n route: CompiledRoute,\n params: Record<string, unknown>,\n queryString: string | undefined,\n ): MatchResult | undefined {\n let search: Readonly<Record<string, unknown>> = EMPTY_SEARCH;\n\n if (queryString !== undefined) {\n const parsed = this.#parseSearch(route, queryString);\n\n if (parsed === undefined) {\n return undefined;\n }\n\n // Query goes ONLY into its own channel now (RFC-4 M2 / #1548). `params`\n // stays path-only — the A2 back-compat fold into `params`, and with it\n // the query-overwrites-path precedence (#843), are gone.\n search = parsed;\n }\n\n return {\n segments: route.matchSegments,\n params,\n search,\n meta: route.meta,\n };\n }\n\n // Parses the query string into its OWN object — the query channel (RFC-4 M2 /\n // #1548). Returns undefined (→ match yields undefined) when the URL is\n // unmatchable: the injected parser threw, or strict mode saw an undeclared\n // key. The injected parser (searchParams.ts) already hardens `__proto__` as an\n // own key (#855/#1293), so the parsed object is returned directly — no per-key\n // re-hardening (that folding, and `assignQueryParam`, live on only for the A3\n // back-compat merge into `params`).\n #parseSearch(\n route: CompiledRoute,\n queryString: string,\n ): Record<string, unknown> | undefined {\n let search: Record<string, unknown>;\n\n try {\n search = this.#options.parseQueryString(queryString);\n } catch {\n // The injected query parser decodes percent-encoding too, so the same\n // valid-hex/invalid-UTF-8 sequence that breaks path params (e.g.\n // `?x=%E0%41`) makes it throw a URIError. `match()` must never throw —\n // treat the whole URL as unmatched so the router resolves to\n // UNKNOWN_ROUTE instead of crashing on start() (#737).\n return undefined;\n }\n\n if (this.#options.strictQueryParams) {\n const declared = route.declaredQueryParamsSet;\n\n for (const key in search) {\n if (!declared.has(key)) {\n return undefined;\n }\n }\n }\n\n return search;\n }\n\n #checkTrailingSlash(cleanPath: string, route: CompiledRoute): boolean {\n const inputHasSlash = cleanPath.length > 1 && cleanPath.endsWith(\"/\");\n\n return inputHasSlash === route.hasTrailingSlash;\n }\n\n #traverse(\n path: string,\n params: Record<string, string>,\n ): CompiledRoute | undefined {\n /* v8 ignore start -- @preserve: root \"/\" is always in #staticCache */\n if (path.length === 1) {\n return this.#root.slashChildRoute ?? this.#root.route;\n }\n /* v8 ignore stop */\n\n return this.#traverseFrom(this.#root, path, 1, params);\n }\n\n // The core match hot loop. The #1288 param+splat junction is inlined here\n // rather than extracted into a helper, because a per-param helper call\n // regresses the common single-param path ~5% (spike-measured).\n // eslint-disable-next-line sonarjs/cognitive-complexity -- inlined #1288 junction (see above): the static/param/splat dispatch is deliberately one function for the hot path\n #traverseFrom(\n startNode: SegmentNode,\n path: string,\n start: number,\n params: Record<string, string>,\n ): CompiledRoute | undefined {\n let node = startNode;\n const length = path.length;\n const caseSensitive = this.#caseSensitive;\n\n while (start <= length) {\n const end = path.indexOf(\"/\", start);\n const segmentEnd = end === -1 ? length : end;\n const segment = path.slice(start, segmentEnd);\n\n const lookupKey = caseSensitive ? segment : segment.toLowerCase();\n let next: SegmentNode;\n\n if (lookupKey in node.staticChildren) {\n next = node.staticChildren[lookupKey];\n } else if (node.paramChild) {\n const pc = node.paramChild;\n\n // #1288: validated sub-traverse on a param+splat junction. The param\n // branch is tried on a scratch object and commits ONLY if it structurally\n // completes (\"param wins if its branch can complete\", INVARIANTS Matching #8);\n // otherwise the splat sibling captures. Junction-free param hops (no splat\n // sibling) never enter this block — the common single-param path below is\n // untouched.\n if (node.splatChild !== undefined) {\n const childParams: Record<string, string> = { [pc.name]: segment };\n\n const taken = this.#traverseFrom(\n pc.node,\n path,\n segmentEnd + 1,\n childParams,\n );\n\n if (taken !== undefined) {\n Object.assign(params, childParams);\n\n return taken;\n }\n\n return this.#matchSplat(node.splatChild, path, start, params);\n }\n\n next = pc.node;\n params[pc.name] = segment;\n } else if (node.splatChild) {\n return this.#matchSplat(node.splatChild, path, start, params);\n } else {\n return undefined;\n }\n\n node = next;\n start = segmentEnd + 1;\n }\n\n return node.slashChildRoute ?? node.route;\n }\n\n #matchSplat(\n splatChild: { node: SegmentNode; name: string },\n path: string,\n start: number,\n params: Record<string, string>,\n ): CompiledRoute | undefined {\n const sn = splatChild.node;\n\n // Stryker disable next-line BlockStatement: equivalent — leaf-splat fast path; the #traverseFrom fallback returns the same route+params (proven via hasChildren injection)\n if (!sn.hasChildren) {\n params[splatChild.name] = path.slice(start);\n\n return sn.route;\n }\n\n const childParams: Record<string, string> = {};\n const specific = this.#traverseFrom(sn, path, start, childParams);\n\n // #1288: a structurally-complete specific child wins over the wildcard\n // capture; otherwise the splat captures the rest of the path.\n if (specific) {\n Object.assign(params, childParams);\n\n return specific;\n }\n\n params[splatChild.name] = path.slice(start);\n\n return sn.route;\n }\n\n #decodeParams(params: Record<string, string>): boolean {\n const decode = this.#decode;\n\n if (!decode) {\n return true;\n }\n\n for (const key in params) {\n const value = params[key];\n\n // Stryker disable next-line StringLiteral,BlockStatement: equivalent — includes('%') is a skip-optimization; decoding a %-free value is a no-op, so always-proceeding is identical\n if (!value.includes(\"%\")) {\n continue;\n }\n\n // Stryker disable next-line BlockStatement: equivalent — redundant with the try/catch below — decodeURIComponent throws on the same invalid-% input (proven by injection)\n if (!validatePercentEncoding(value)) {\n return false;\n }\n\n try {\n params[key] = decode(value);\n } catch {\n // `validatePercentEncoding` only checks `%XX` *syntax*. A sequence that\n // is syntactically valid but semantically invalid UTF-8 (e.g. `%E0%41`,\n // `%C0%80`, `%FF`) still makes `decodeURIComponent`/`decodeURI` throw a\n // URIError. `match()` must never throw — reject the path so the router\n // resolves to UNKNOWN_ROUTE instead of crashing on start() (#737).\n return false;\n }\n }\n\n return true;\n }\n}\n\nexport { createSegmentNode } from \"./pathUtils\";\n"],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- const e=require("./parseSegment.js"),t=/[<>]/u;function n(e){for(let t=0;t<e.length;t+=1){if(e.codePointAt(t)!==63)continue;let n=t+1<e.length?e.codePointAt(t+1):-1;if(n!==-1&&n!==47&&n!==63&&n!==60)return t}return-1}const r=Object.freeze([]),i=Object.freeze({}),a=Object.freeze({urlParams:r,queryParams:r,spatParams:r,paramTypeMap:i,pathPattern:``});function o(t,n,r,i){for(let a of e.splitPathSegments(t)){if(a.length===0)continue;let t=e.parseSegment(a);`error`in t||t.kind===`static`||(n.push(t.name),i[t.name]=`url`,t.kind===`splat`&&r.push(t.name))}}function s(e){let t=[],r=[],i=[],a={},s=n(e);if(s!==-1){let t=e.slice(s+1).split(`&`);for(let e of t){let t=e.trim();t.length>0&&(r.push(t),a[t]=`query`)}e=e.slice(0,s)}return o(e,t,i,a),c(t,r,i,a,e)}function c(e,t,n,a,o){return{urlParams:e.length===0?r:e,queryParams:t.length===0?r:t,spatParams:n.length===0?r:n,paramTypeMap:e.length===0&&t.length===0?i:a,pathPattern:o}}exports.EMPTY_PARAM_META=a,exports.INVALID_QUERY_NAME_RGX=t,exports.buildParamMeta=s;
2
- //# sourceMappingURL=buildParamMeta.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"buildParamMeta.js","names":["splitPathSegments","parseSegment"],"sources":["../../../../src/engine/path-matcher/buildParamMeta.ts"],"sourcesContent":["/**\n * Route Parameter Metadata Extraction.\n *\n * Extracts parameter metadata from route path patterns without requiring\n * a full path-parser instance. Replaces parser.urlParams/queryParams/spatParams.\n *\n * @module buildParamMeta\n */\n\nimport { parseSegment, splitPathSegments } from \"./parseSegment\";\n\nimport type { ParamMeta } from \"./types\";\n\n/**\n * A query-param NAME may not contain `<`/`>` (#1242 §5.1) — a constraint\n * delimiter leaked into the query via a reverse-order modifier typo (`/a/:b?<c>`\n * parses the `?` as the query start, making `<c>` the query name). Consumed by\n * the route-tree gate and the `registerTree` backstop; relocated here from the\n * deleted `constraint-grammar.ts` when M1 removed constraints (query-param name\n * validation is a query concern, and this module owns query extraction).\n */\nexport const INVALID_QUERY_NAME_RGX = /[<>]/u;\n\nconst QUESTION = 0x3f; // ?\nconst SLASH = 0x2f; // /\nconst LT = 0x3c; // <\n\n/**\n * Locates the query separator `?` in a route path — the FIRST `?` whose tail is\n * non-empty and does not begin with `/`, `?`, or `<` (M1 §3.3). The 3-token\n * grammar leaves `?` a single role (there is no optional modifier and no\n * constraint body to hide one), so no length-preserving mask is needed. The three\n * excluded tails keep a REMOVED form in the path part, where `parseSegment`\n * rejects it with a recipe instead of mis-reading it as a query declaration:\n * - end-of-string (`/:id?`) and `/` (`/:id?/edit`) → a bare `:x?` optional;\n * - `?` (`/:id??tab`) → the leading `?` is the optional, the later `?` the query;\n * - `<` (`/a/:b?<x>`) → a reverse-order `:b?<x>` (optional then a former constraint).\n *\n * @param path - a route path\n * @returns the index of the query separator, or -1 if there is none\n */\nfunction findQuerySeparator(path: string): number {\n for (let i = 0; i < path.length; i += 1) {\n if (path.codePointAt(i) !== QUESTION) {\n continue;\n }\n\n // `next` is the code point after the `?`, or the `-1` sentinel at end-of-string.\n // The `-1` sentinel is the SOLE end-of-string guard — the former separate\n // `next !== undefined` conjunct was dead (the ternary bounds the index, so\n // `codePointAt` never returns `undefined`; the `!` is a type assertion, not a\n // runtime branch, so it keeps the scan at 100% coverage). Mirrors `#scanPath`.\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- ternary-bounded in-range index; codePointAt is defined\n const next = i + 1 < path.length ? path.codePointAt(i + 1)! : -1;\n\n if (next !== -1 && next !== SLASH && next !== QUESTION && next !== LT) {\n return i;\n }\n }\n\n return -1;\n}\n\n/**\n * Builds parameter metadata from a route path pattern.\n *\n * Extracts URL parameters, query parameters, and splat parameters\n * from the path pattern string.\n *\n * @param path - Route path pattern (e.g., \"/users/:id/posts/:postId?q\")\n * @returns Parameter metadata object\n *\n * @example\n * ```typescript\n * buildParamMeta(\"/users/:id\")\n * // → { urlParams: [\"id\"], queryParams: [], spatParams: [], paramTypeMap: { id: \"url\" } }\n *\n * buildParamMeta(\"/search?q&page\")\n * // → { urlParams: [], queryParams: [\"q\", \"page\"], spatParams: [],\n * // paramTypeMap: { q: \"query\", page: \"query\" } }\n *\n * buildParamMeta(\"/files/*path\")\n * // → { urlParams: [\"path\"], queryParams: [], spatParams: [\"path\"],\n * // paramTypeMap: { path: \"url\" } }\n * ```\n */\n// Shared frozen sentinels for the common no-params case — avoid a fresh empty\n// array/object per route (#1009). ParamMeta fields are Readonly*; match/build\n// only read them, and computeCaches' Object.freeze on the arrays/object is a\n// no-op on an already-frozen shared instance.\nconst EMPTY_PARAM_NAMES: readonly string[] = Object.freeze([]);\nconst EMPTY_PARAM_TYPE_MAP: Readonly<Record<string, \"url\" | \"query\">> =\n Object.freeze({});\n\n// Whole-meta shared sentinel for the fully-static case: every collection is a\n// #1009 sentinel AND pathPattern degenerates to the input path itself (no query\n// to strip), so the wrapper carries zero per-route information. The RETAINING\n// caller (route-tree's computeCaches) swaps a matching fresh result for this\n// instance — buildParamMeta itself keeps returning fresh objects so the\n// validation gate can read the real pathPattern of arbitrary input paths.\n// `pathPattern` is \"\" here; the one stored-meta reader (`registerNode`) falls\n// back to `node.path` on identity match.\nexport const EMPTY_PARAM_META: ParamMeta = Object.freeze({\n urlParams: EMPTY_PARAM_NAMES,\n queryParams: EMPTY_PARAM_NAMES,\n spatParams: EMPTY_PARAM_NAMES,\n paramTypeMap: EMPTY_PARAM_TYPE_MAP,\n pathPattern: \"\",\n});\n\n/**\n * Extracts URL/splat params from a path's segments into the given accumulators\n * via the canonical `parseSegment` tokenizer. Split out of `buildParamMeta` so\n * the builder stays under the cognitive-complexity budget. A malformed segment\n * (token errors) or a `static` segment contributes nothing — a malformed route is\n * rejected downstream before it compiles, so its meta is moot.\n */\nfunction collectUrlParams(\n path: string,\n urlParams: string[],\n spatParams: string[],\n paramTypeMap: Record<string, \"url\" | \"query\">,\n): void {\n for (const segment of splitPathSegments(path)) {\n if (segment.length === 0) {\n continue;\n }\n\n const token = parseSegment(segment);\n\n if (\"error\" in token || token.kind === \"static\") {\n continue;\n }\n\n urlParams.push(token.name);\n paramTypeMap[token.name] = \"url\";\n\n if (token.kind === \"splat\") {\n spatParams.push(token.name);\n }\n }\n}\n\nexport function buildParamMeta(path: string): ParamMeta {\n const urlParams: string[] = [];\n const queryParams: string[] = [];\n const spatParams: string[] = [];\n const paramTypeMap: Record<string, \"url\" | \"query\"> = {};\n\n // Locate the real query separator (M1 §3.3: first `?` whose tail is not a\n // former optional/reverse form).\n const separator = findQuerySeparator(path);\n\n if (separator !== -1) {\n const queryString = path.slice(separator + 1);\n const params = queryString.split(\"&\");\n\n for (const param of params) {\n const paramName = param.trim();\n\n if (paramName.length > 0) {\n queryParams.push(paramName);\n paramTypeMap[paramName] = \"query\";\n }\n }\n\n path = path.slice(0, separator);\n }\n\n collectUrlParams(path, urlParams, spatParams, paramTypeMap);\n\n return shareEmptyCollections(\n urlParams,\n queryParams,\n spatParams,\n paramTypeMap,\n path,\n );\n}\n\n// #1009: swap each freshly-built empty collection for a shared frozen sentinel\n// — factored out of buildParamMeta so the hot builder stays under the cognitive-\n// complexity budget. match/build only read these (Readonly*), and computeCaches'\n// Object.freeze is a no-op on an already-frozen shared instance.\nfunction shareEmptyCollections(\n urlParams: string[],\n queryParams: string[],\n spatParams: string[],\n paramTypeMap: Record<string, \"url\" | \"query\">,\n pathPattern: string,\n): ParamMeta {\n return {\n urlParams: urlParams.length === 0 ? EMPTY_PARAM_NAMES : urlParams,\n queryParams: queryParams.length === 0 ? EMPTY_PARAM_NAMES : queryParams,\n spatParams: spatParams.length === 0 ? EMPTY_PARAM_NAMES : spatParams,\n paramTypeMap:\n urlParams.length === 0 && queryParams.length === 0\n ? EMPTY_PARAM_TYPE_MAP\n : paramTypeMap,\n pathPattern,\n };\n}\n"],"mappings":"qCAqBa,EAAyB,QAoBtC,SAAS,EAAmB,EAAsB,CAChD,IAAK,IAAI,EAAI,EAAG,EAAI,EAAK,OAAQ,GAAK,EAAG,CACvC,GAAI,EAAK,YAAY,CAAC,IAAM,GAC1B,SASF,IAAM,EAAO,EAAI,EAAI,EAAK,OAAS,EAAK,YAAY,EAAI,CAAC,EAAK,GAE9D,GAAI,IAAS,IAAM,IAAS,IAAS,IAAS,IAAY,IAAS,GACjE,OAAO,CAEX,CAEA,MAAO,EACT,CA6BA,MAAM,EAAuC,OAAO,OAAO,CAAC,CAAC,EACvD,EACJ,OAAO,OAAO,CAAC,CAAC,EAUL,EAA8B,OAAO,OAAO,CACvD,UAAW,EACX,YAAa,EACb,WAAY,EACZ,aAAc,EACd,YAAa,EACf,CAAC,EASD,SAAS,EACP,EACA,EACA,EACA,EACM,CACN,IAAK,IAAM,KAAWA,EAAAA,kBAAkB,CAAI,EAAG,CAC7C,GAAI,EAAQ,SAAW,EACrB,SAGF,IAAM,EAAQC,EAAAA,aAAa,CAAO,EAE9B,UAAW,GAAS,EAAM,OAAS,WAIvC,EAAU,KAAK,EAAM,IAAI,EACzB,EAAa,EAAM,MAAQ,MAEvB,EAAM,OAAS,SACjB,EAAW,KAAK,EAAM,IAAI,EAE9B,CACF,CAEA,SAAgB,EAAe,EAAyB,CACtD,IAAM,EAAsB,CAAC,EACvB,EAAwB,CAAC,EACzB,EAAuB,CAAC,EACxB,EAAgD,CAAC,EAIjD,EAAY,EAAmB,CAAI,EAEzC,GAAI,IAAc,GAAI,CAEpB,IAAM,EADc,EAAK,MAAM,EAAY,CAClB,CAAC,CAAC,MAAM,GAAG,EAEpC,IAAK,IAAM,KAAS,EAAQ,CAC1B,IAAM,EAAY,EAAM,KAAK,EAEzB,EAAU,OAAS,IACrB,EAAY,KAAK,CAAS,EAC1B,EAAa,GAAa,QAE9B,CAEA,EAAO,EAAK,MAAM,EAAG,CAAS,CAChC,CAIA,OAFA,EAAiB,EAAM,EAAW,EAAY,CAAY,EAEnD,EACL,EACA,EACA,EACA,EACA,CACF,CACF,CAMA,SAAS,EACP,EACA,EACA,EACA,EACA,EACW,CACX,MAAO,CACL,UAAW,EAAU,SAAW,EAAI,EAAoB,EACxD,YAAa,EAAY,SAAW,EAAI,EAAoB,EAC5D,WAAY,EAAW,SAAW,EAAI,EAAoB,EAC1D,aACE,EAAU,SAAW,GAAK,EAAY,SAAW,EAC7C,EACA,EACN,aACF,CACF"}
@@ -1,2 +0,0 @@
1
- const e=/[^\w!$'()*+,.:;|~-]/gu,t=/[^\w!$'()*+,.:;|~-]/u,n=/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g,r=e=>t=>{try{return e(t)}catch{return e(t.replaceAll(n,`�`))}},i=r(t=>t.replaceAll(e,e=>encodeURIComponent(e))),a=e=>t.test(e)?i(e):e,o={default:a,uri:r(encodeURI),uriComponent:r(encodeURIComponent),none:e=>e},s={default:decodeURIComponent,uri:decodeURI,uriComponent:decodeURIComponent,none:e=>e},c=(e,t)=>{let n=o[t],r=String(e).split(`/`),i=n(r[0]);for(let e=1;e<r.length;e++)i+=`/`+n(r[e]);return i};exports.DECODING_METHODS=s,exports.ENCODING_METHODS=o,exports.encodeParam=c,exports.encodeURIComponentExcludingSubDelims=a;
2
- //# sourceMappingURL=encoding.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"encoding.js","names":[],"sources":["../../../../src/engine/path-matcher/encoding.ts"],"sourcesContent":["/**\n * URL Parameter Encoding.\n *\n * Encoding strategies for URL parameters.\n *\n * @module encoding\n */\n\n/**\n * Performance-critical code: string concatenation is 2x faster than template\n * literals/String.raw in V8 hot paths. See benchmarks for details.\n */\n/* eslint-disable prefer-template */\n\nimport type { URLParamsEncodingType } from \"./types\";\n\n// =============================================================================\n// Encoding Constants\n// =============================================================================\n\n/**\n * Optimized regex: only matches characters that ACTUALLY need encoding.\n * Excludes:\n * - A-Za-z0-9: alphanumeric (unreserved per RFC 3986)\n * - -._~: unreserved characters per RFC 3986\n * - !$'()*+,:;|: sub-delimiters we want to preserve\n *\n * Performance: This regex skips alphanumeric characters entirely,\n * avoiding unnecessary encodeURIComponent calls that return the same value.\n *\n * IMPORTANT: The 'u' (Unicode) flag makes the regex iterate by code point, so a\n * PAIRED surrogate (emoji, outside the BMP) coalesces into one code point that\n * encodeURIComponent accepts. An UNPAIRED (lone) surrogate is itself a single code\n * point that still matches the class and reaches encodeURIComponent, which throws\n * \"URI malformed\" on it — `encodeURIComponentExcludingSubDelims`'s slow path\n * catches that and sanitizes it to U+FFFD via a lone-surrogate regex, keeping\n * buildPath total (#1315).\n */\nconst NEEDS_ENCODING_REGEX = /[^\\w!$'()*+,.:;|~-]/gu;\n\n/**\n * Fast check regex (without global flag) to test if encoding is needed at all.\n * Used for pre-check optimization: if string contains only safe chars, skip encoding.\n *\n * IMPORTANT: The 'u' flag ensures proper handling of emoji/surrogate pairs.\n */\nconst NEEDS_ENCODING_TEST = /[^\\w!$'()*+,.:;|~-]/u;\n\n// =============================================================================\n// Encoding Helper Functions\n// =============================================================================\n\n/**\n * A lone (unpaired) surrogate — a high surrogate not followed by a low, or a low\n * not preceded by a high. A manual, lib-target-agnostic `String.prototype.toWellFormed`\n * (ES2024): consumers compile this `src` under their own `tsconfig` (whose `lib` may\n * predate es2024 — e.g. `hash-plugin`), so a regex `replace` avoids a `toWellFormed`\n * type error there while producing the identical result.\n */\nconst LONE_SURROGATE_RGX =\n /[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?<![\\uD800-\\uDBFF])[\\uDC00-\\uDFFF]/g;\n\n/**\n * Wraps an encoder so a lone (unpaired) surrogate — the only input\n * `encodeURIComponent` / `encodeURI` reject (`URIError`) — is sanitized to U+FFFD\n * and re-encoded instead of throwing, keeping `buildPath` total (#1315). The\n * surrogate is already non-round-trippable garbage. `path-matcher` has zero deps, so\n * this mirrors search-params' `safeEncode` rather than importing it (a deliberate\n * twin, like the `getTypeDescription` copy in route-tree).\n */\nconst totalize =\n (encoder: (s: string) => string) =>\n (segment: string): string => {\n try {\n return encoder(segment);\n } catch {\n return encoder(segment.replaceAll(LONE_SURROGATE_RGX, \"�\"));\n }\n };\n\n// Only the slow path can throw a `URIError` — a lone surrogate always matches\n// `NEEDS_ENCODING_REGEX`, so it never reaches the all-safe fast path — hence the\n// try/catch sits here and the 29-57x fast path below pays nothing for it.\nconst encodeSlowPath = totalize((segment: string): string =>\n segment.replaceAll(NEEDS_ENCODING_REGEX, (match) =>\n encodeURIComponent(match),\n ),\n);\n\n/**\n * Encode a segment while preserving sub-delimiters.\n * Uses pre-check optimization: fast path for strings without special chars.\n *\n * Performance improvement over naive approach:\n * - Alphanumeric strings: 29-57x faster\n * - Strings with special chars: 2-3x faster\n */\nexport const encodeURIComponentExcludingSubDelims = (\n segment: string,\n): string => {\n // Fast path: if no special chars, return as-is\n // Stryker disable next-line BlockStatement: equivalent — pure optimization; `replaceAll(NEEDS_ENCODING_REGEX, ...)` below is a no-op on a string with no encodable chars, so emptying this early return yields the identical value. ConditionalExpression stays live (killable `->true` sibling returns unencoded strings).\n if (!NEEDS_ENCODING_TEST.test(segment)) {\n return segment;\n }\n\n return encodeSlowPath(segment);\n};\n\n// =============================================================================\n// Encoding Methods Map\n// =============================================================================\n\n/**\n * Encoding functions for each encoding type.\n *\n * Modes:\n * - `default` - encodeURIComponent preserving sub-delimiters (+, :, ', !, ,, ;, *)\n * - `uri` - encodeURI/decodeURI\n * - `uriComponent` - encodeURIComponent/decodeURIComponent\n * - `none` - no encoding/decoding\n */\nexport const ENCODING_METHODS: Record<\n URLParamsEncodingType,\n (param: string) => string\n> = {\n default: encodeURIComponentExcludingSubDelims,\n uri: totalize(encodeURI),\n uriComponent: totalize(encodeURIComponent),\n none: (val) => val,\n};\n\n/**\n * Decoding functions for each encoding type.\n */\nexport const DECODING_METHODS: Record<\n URLParamsEncodingType,\n (param: string) => string\n> = {\n default: decodeURIComponent,\n uri: decodeURI,\n uriComponent: decodeURIComponent,\n // `none` decoding is never reached through `match`: `SegmentMatcher` special-cases\n // `urlParamsEncoding === \"none\"` to `#decode = null` and skips `#decodeParams`\n // entirely (so a \"none\" route also skips %-validation — a deliberate behaviour, not\n // merely perf). This identity entry exists only for `Record` type completeness and is\n // exercised by the exempt `tests/property/encoding.properties.ts` round-trip.\n /* v8 ignore next -- unreachable via match (none → null); see the comment above */\n none: (val) => val,\n};\n\n// =============================================================================\n// Main Encoding Function\n// =============================================================================\n\n/**\n * Encodes a SPLAT URL parameter value: each `/`-delimited segment is encoded with the\n * strategy's encoder, preserving the `/` separators.\n *\n * Splat-only by design (#860): a NON-splat param is encoded by `ENCODING_METHODS[encoding]`\n * directly — `registration/buildParts.ts`'s `makeBuildParamSlot` routes only SPLAT slots\n * through here — so the former `!isSpatParam` fast path was unreachable dead code (surfaced\n * by the public-API test migration) and was dropped.\n *\n * @param param - The splat parameter value to encode\n * @param encoding - The encoding strategy to use\n * @returns The encoded splat value (each segment encoded, `/` preserved)\n *\n * @example\n * ```typescript\n * encodeParam('docs/readme.md', 'default'); // => 'docs/readme.md'\n * encodeParam('a/hello world', 'default'); // => 'a/hello%20world'\n * ```\n */\nexport const encodeParam = (\n param: string | number | boolean,\n encoding: URLParamsEncodingType,\n): string => {\n const encoder = ENCODING_METHODS[encoding];\n const str = String(param);\n\n // Encode each \"/\"-segment separately, preserving the separators.\n // H6 optimization: string concatenation is 2x faster than template literals\n const segments = str.split(\"/\");\n let result = encoder(segments[0]);\n\n for (let i = 1; i < segments.length; i++) {\n result += \"/\" + encoder(segments[i]);\n }\n\n return result;\n};\n"],"mappings":"AAsCA,MAAM,EAAuB,wBAQvB,EAAsB,uBAatB,EACJ,0EAUI,EACH,GACA,GAA4B,CAC3B,GAAI,CACF,OAAO,EAAQ,CAAO,CACxB,MAAQ,CACN,OAAO,EAAQ,EAAQ,WAAW,EAAoB,GAAG,CAAC,CAC5D,CACF,EAKI,EAAiB,EAAU,GAC/B,EAAQ,WAAW,EAAuB,GACxC,mBAAmB,CAAK,CAC1B,CACF,EAUa,EACX,GAIK,EAAoB,KAAK,CAAO,EAI9B,EAAe,CAAO,EAHpB,EAmBE,EAGT,CACF,QAAS,EACT,IAAK,EAAS,SAAS,EACvB,aAAc,EAAS,kBAAkB,EACzC,KAAO,GAAQ,CACjB,EAKa,EAGT,CACF,QAAS,mBACT,IAAK,UACL,aAAc,mBAOd,KAAO,GAAQ,CACjB,EAyBa,GACX,EACA,IACW,CACX,IAAM,EAAU,EAAiB,GAK3B,EAJM,OAAO,CAIA,CAAC,CAAC,MAAM,GAAG,EAC1B,EAAS,EAAQ,EAAS,EAAE,EAEhC,IAAK,IAAI,EAAI,EAAG,EAAI,EAAS,OAAQ,IACnC,GAAU,IAAM,EAAQ,EAAS,EAAE,EAGrC,OAAO,CACT"}
@@ -1,2 +0,0 @@
1
- const e=e=>e===58||e===42;function t(e){let t=[],n=0;for(let r=0;r<e.length;r+=1)e.charCodeAt(r)===47&&(t.push(e.slice(n,r)),n=r+1);return t.push(e.slice(n)),t}function n(t){let n=t.length;if(n===0)return{kind:`static`,text:``};if(!e(t.charCodeAt(0))){for(let r=0;r<n;r+=1){let i=t.charCodeAt(r);if(i===60||i===62)return{error:`constraint-removed`};if(e(i)&&r+1<n)return{error:`fused-marker`}}return t.charCodeAt(n-1)===63?{error:`name-less`}:{kind:`static`,text:t}}let r=t.charCodeAt(0)===42,i=1;for(;i<n;){let e=t.charCodeAt(i);if(e===60||e===62||e===63)break;i+=1}let a=t.slice(1,i);return a.length===0?{error:`name-less`}:e(a.charCodeAt(a.length-1))?{error:`trailing-marker`}:i<n?t.charCodeAt(i)===63?{error:`optional-removed`}:{error:`constraint-removed`}:r?{kind:`splat`,name:a}:{kind:`param`,name:a}}function r(e){for(let r of t(e)){let e=n(r);if(`error`in e)return e.error}}function i(e){let r=t(e);for(let e=0;e<r.length;e+=1){let t=n(r[e]);if(`error`in t){if(t.error===`optional-removed`){let t=r[e],n=[...r];return n[e]=t.slice(0,t.indexOf(`?`)),{code:`optional-removed`,segment:t,withoutSegment:r.filter((t,n)=>n!==e).join(`/`),requiredForm:n.join(`/`)}}return t.error===`constraint-removed`?{code:`constraint-removed`,segment:r[e]}:void 0}}}exports.describeRemovedForm=i,exports.findSegmentGrammarError=r,exports.parseSegment=n,exports.splitPathSegments=t;
2
- //# sourceMappingURL=parseSegment.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"parseSegment.js","names":[],"sources":["../../../../src/engine/path-matcher/parseSegment.ts"],"sourcesContent":["/**\n * Canonical route-segment tokenizer.\n *\n * The single owner of \"where does a name / marker / constraint end\" for ONE\n * path segment (post-`/`-split). It replaces the five name-boundary compositions\n * of `PARAM_NAME_PATTERN` currently spread across `buildParamMeta` (L1),\n * `registration` (L2 build + L3 trie), and `route-tree`'s validation gate (L4),\n * so those layers can never disagree on a boundary (the gate↔backstop drift\n * class — #858 / #1050 / #1150 / #1311 / #1149 / #1324).\n *\n * A single left-to-right `charCodeAt` scan produces either a token tuple or a\n * typed error. Grammar — **3 tokens only** (`static | :param | *splat`); the\n * grammar has no optional `:x?` or `<re>` constraint forms. Any `<`/`>` or a\n * post-name `?` in the path is a *registration error* carrying a replacement\n * recipe (`optional-removed` / `constraint-removed`), not a token:\n * 1. Leading `:`/`*` → param/splat; otherwise `static` (a marker glued *after* a\n * static prefix ⇒ `fused-marker`; any `<`/`>` (a former constraint) ⇒\n * `constraint-removed`; a trailing `?` on a marker-less segment ⇒ `name-less`\n * — the modifier has no param name, #1241 / `/faq?`).\n * 2. name = any char except `<`/`?` (no `/` remains inside a segment); a name\n * ending in a bare `:`/`*` ⇒ `trailing-marker` (#1324). A *mid* marker stays\n * a name char — `:a:b` → name `a:b`, preserved.\n * 3. empty name ⇒ `name-less` (#858).\n * 4. a `<` after the name (a former `<re>` constraint) ⇒ `constraint-removed`.\n * 5. a post-name `?` (a former optional modifier, on `:param` or `*splat`) ⇒\n * `optional-removed`.\n *\n * @module parseSegment\n */\n\n/* eslint-disable unicorn/prefer-code-point, unicorn/prefer-includes-over-repeated-comparisons, sonarjs/cognitive-complexity -- charCodeAt code-unit scan + a single inlined branchy pass are this RFC's char-scan perf basis (§9); the same deliberate choices as registration/trie.ts hasNonAsciiSegment (#1285) and SegmentMatcher's inlined #traverseFrom. A `[LT,GT,QUESTION].includes(code)` boundary check would allocate an array literal per scanned char. Markers compared are ASCII (`:` `*` `<` `>` `?`, < 0x80). */\n\n/** A successfully tokenized segment (3-token grammar: `static | :param | *splat`). */\nexport type SegmentTokens =\n | { readonly kind: \"static\"; readonly text: string }\n | { readonly kind: \"param\"; readonly name: string }\n | { readonly kind: \"splat\"; readonly name: string };\n\n/** Grammar-shape rejections, each mirroring a registration guard. */\nexport type SegmentErrorCode =\n | \"name-less\" // #858 — a marker with no name\n | \"trailing-marker\" // #1324 — a param name ending in a bare `:`/`*`\n | \"fused-marker\" // #1050 — a marker glued after a static prefix\n | \"optional-removed\" // M1 — a `:x?`/`*x?` optional modifier (removed; two sibling routes)\n | \"constraint-removed\"; // M1 — a `<re>` constraint or stray `<`/`>` (removed; validate in a guard)\n\nexport interface SegmentError {\n readonly error: SegmentErrorCode;\n}\n\nconst COLON = 58; // :\nconst STAR = 42; // *\nconst LT = 60; // <\nconst GT = 62; // >\nconst QUESTION = 63; // ?\nconst SLASH = 47; // /\n\nconst isMarker = (code: number): boolean => code === COLON || code === STAR;\n\n/**\n * Splits a path into its `/`-delimited segments. A plain `/`-split (M1): the\n * 3-token grammar has no `<...>` constraint whose body could legally contain a\n * `/`, so no constraint-awareness is needed — a stray `<`/`>` is a\n * `constraint-removed` error, caught per segment by `parseSegment`. This is the\n * **segmentation** half of the path-grammar unification: `parseSegment` owns the\n * per-segment grammar, `splitPathSegments` owns where a segment begins and ends.\n *\n * @param path - a route path (query already stripped by the caller)\n * @returns the segments in order, including empty leading/trailing/`//` segments\n * (the caller skips empties, matching the current behaviour)\n */\nexport function splitPathSegments(path: string): string[] {\n const segments: string[] = [];\n let start = 0;\n\n for (let i = 0; i < path.length; i += 1) {\n if (path.charCodeAt(i) !== SLASH) {\n continue;\n }\n\n segments.push(path.slice(start, i));\n start = i + 1;\n }\n\n segments.push(path.slice(start));\n\n return segments;\n}\n\n/**\n * Tokenizes a single path segment (already split on `/`).\n *\n * @param segment - one `/`-delimited segment of a route path\n * @returns the segment's tokens, or a typed grammar error\n */\nexport function parseSegment(segment: string): SegmentTokens | SegmentError {\n const length = segment.length;\n\n if (length === 0) {\n return { kind: \"static\", text: \"\" };\n }\n\n // ---- static segment (no leading marker) -------------------------------\n if (!isMarker(segment.charCodeAt(0))) {\n for (let i = 0; i < length; i += 1) {\n const code = segment.charCodeAt(i);\n\n // A `<`/`>` (a former `<re>` constraint or a stray delimiter) is no longer\n // grammar — M1 removed constraints. Reject with the constraint recipe.\n if (code === LT || code === GT) {\n return { error: \"constraint-removed\" };\n }\n\n // A marker glued after a static prefix is extracted as a param by build/meta\n // but compiled as a static literal by the trie (#1050) — reject it as fused.\n // A marker ENDING the segment (a static ending in `:`/`*` — `/a:`, `/a*`, F2)\n // is NOT fused: caught by `i + 1 < length` being false. Every other following\n // char is fused — including a `?` (`a:?`): that shape never reaches the\n // tokenizer through a real path (a `?` after a bare marker is not a valid\n // `:name?` form, so the query mask strips it before `/`-segmentation), so a\n // direct call correctly reports fused-marker. (`a<`/`a>` already returned\n // `constraint-removed` above, so no `<`-follows exception is needed here.)\n if (isMarker(code) && i + 1 < length) {\n return { error: \"fused-marker\" };\n }\n }\n\n // A trailing `?` is a former optional modifier; on a marker-less segment (no\n // param name) it is a modifier-with-no-name — name-less (#858/#1241, `/faq?`),\n // NOT `optional-removed` (there is no param to route to two siblings). The\n // backstop rejects it by the SAME rule: its `endsWith(\"?\")` fork routes the\n // segment to `extractParamName` → this tokenizer. Owning the `?` here (not\n // only in the marker branch) is what lets the gate and backstop agree on it —\n // otherwise the gate reads `faq?` as a valid static (#1324 §4).\n if (segment.charCodeAt(length - 1) === QUESTION) {\n return { error: \"name-less\" };\n }\n\n return { kind: \"static\", text: segment };\n }\n\n const splat = segment.charCodeAt(0) === STAR;\n\n // ---- name: up to the first `<`/`>` (former constraint delimiter, reserved —\n // В1.3) or `?` (former optional). A segment holds no `/`. -----------------\n let cursor = 1;\n\n while (cursor < length) {\n const code = segment.charCodeAt(cursor);\n\n if (code === LT || code === GT || code === QUESTION) {\n break;\n }\n\n cursor += 1;\n }\n\n const name = segment.slice(1, cursor);\n\n if (name.length === 0) {\n return { error: \"name-less\" }; // #858\n }\n\n if (isMarker(name.charCodeAt(name.length - 1))) {\n return { error: \"trailing-marker\" }; // #1324\n }\n\n // ---- former constraint / optional modifiers (removed in M1) ------------\n // The name scan stops at the first `<`/`>` or `?`. Either is a form removed\n // in M1: a `<re>` constraint (also a stray `<`/`>` — В1.3), or a `:x?`/`*x?`\n // optional. Only `?` is the optional; `<`/`>` are the constraint recipe.\n // Reject with the matching replacement recipe rather than tokenize it.\n if (cursor < length) {\n return segment.charCodeAt(cursor) === QUESTION\n ? { error: \"optional-removed\" }\n : { error: \"constraint-removed\" }; // LT or GT\n }\n\n return splat ? { kind: \"splat\", name } : { kind: \"param\", name };\n}\n\n/**\n * Returns the first per-segment grammar error in a path, or `undefined` if every\n * segment tokenizes cleanly.\n *\n * The **validation-facing** entry over the tokenizer: `route-tree`'s\n * `validateRoutePath` calls this instead of re-running its own split+parse loop,\n * so the gate and the matcher's own grammar cannot drift (#1324) and the loop\n * stays single-sourced here — the tokenizer primitives (`parseSegment`,\n * `splitPathSegments`) need not leak into the package's public surface. An empty\n * segment tokenizes as `static` (never an error), so leading/trailing/`//`\n * empties are skipped naturally.\n *\n * @param path - a route path (query already stripped by the caller)\n * @returns the first `SegmentErrorCode` (scanned left to right), or `undefined`\n */\nexport function findSegmentGrammarError(\n path: string,\n): SegmentErrorCode | undefined {\n for (const segment of splitPathSegments(path)) {\n const token = parseSegment(segment);\n\n if (\"error\" in token) {\n return token.error;\n }\n }\n\n return undefined;\n}\n\n/** A removed-form (M1) match, describing the offending segment and — for an\n * optional — the two sibling paths that replace it (path without the optional\n * segment + path with the param made required). The route-tree gate uses this to\n * build a route-contextual replacement recipe; the matcher backstop uses only the\n * error code (a shorter, path-free recipe). */\nexport type RemovedForm =\n | {\n readonly code: \"optional-removed\";\n readonly segment: string;\n readonly withoutSegment: string;\n readonly requiredForm: string;\n }\n | { readonly code: \"constraint-removed\"; readonly segment: string };\n\n/**\n * The rich (route-tree gate) view over the tokenizer for a removed form: finds\n * the first `optional-removed` / `constraint-removed` segment and, for an\n * optional, computes its two replacement sibling paths from the ACTUAL path\n * (dropping the segment → without-form; dropping the trailing `?` → required\n * form). Returns `undefined` if no removed form is present (the gate then uses\n * `findSegmentGrammarError` for a surviving grammar rejection).\n *\n * @param path - a route path (query already stripped by the caller)\n */\nexport function describeRemovedForm(path: string): RemovedForm | undefined {\n const segments = splitPathSegments(path);\n\n for (let i = 0; i < segments.length; i += 1) {\n const token = parseSegment(segments[i]);\n\n if (!(\"error\" in token)) {\n continue;\n }\n\n // First error wins (mirrors `findSegmentGrammarError`): describe it ONLY if\n // it is a removed form, else return undefined so the caller falls to the\n // surviving-code message — this keeps the gate's reason in lockstep with the\n // matcher backstop's first-error verdict.\n if (token.error === \"optional-removed\") {\n const segment = segments[i];\n const required = [...segments];\n\n // Drop the `?` optional modifier AND everything after it (the tokenizer\n // stopped the name at the first `?`, so it is the modifier). Using the `?`\n // index — not a blind `slice(0, -1)` — keeps the required sibling VALID for\n // a reverse/compound form whose `?` is not the last char: `:b?<x>` → `:b`\n // (not `:b?<x`), `:id??` → `:id` (not `:id?`). #1516\n required[i] = segment.slice(0, segment.indexOf(\"?\"));\n\n return {\n code: \"optional-removed\",\n segment,\n withoutSegment: segments.filter((_, j) => j !== i).join(\"/\"),\n requiredForm: required.join(\"/\"),\n };\n }\n\n return token.error === \"constraint-removed\"\n ? { code: \"constraint-removed\", segment: segments[i] }\n : undefined;\n }\n\n return undefined;\n}\n"],"mappings":"AAkDA,MAOM,EAAY,GAA0B,IAAS,IAAS,IAAS,GAcvE,SAAgB,EAAkB,EAAwB,CACxD,IAAM,EAAqB,CAAC,EACxB,EAAQ,EAEZ,IAAK,IAAI,EAAI,EAAG,EAAI,EAAK,OAAQ,GAAK,EAChC,EAAK,WAAW,CAAC,IAAM,KAI3B,EAAS,KAAK,EAAK,MAAM,EAAO,CAAC,CAAC,EAClC,EAAQ,EAAI,GAKd,OAFA,EAAS,KAAK,EAAK,MAAM,CAAK,CAAC,EAExB,CACT,CAQA,SAAgB,EAAa,EAA+C,CAC1E,IAAM,EAAS,EAAQ,OAEvB,GAAI,IAAW,EACb,MAAO,CAAE,KAAM,SAAU,KAAM,EAAG,EAIpC,GAAI,CAAC,EAAS,EAAQ,WAAW,CAAC,CAAC,EAAG,CACpC,IAAK,IAAI,EAAI,EAAG,EAAI,EAAQ,GAAK,EAAG,CAClC,IAAM,EAAO,EAAQ,WAAW,CAAC,EAIjC,GAAI,IAAS,IAAM,IAAS,GAC1B,MAAO,CAAE,MAAO,oBAAqB,EAYvC,GAAI,EAAS,CAAI,GAAK,EAAI,EAAI,EAC5B,MAAO,CAAE,MAAO,cAAe,CAEnC,CAaA,OAJI,EAAQ,WAAW,EAAS,CAAC,IAAM,GAC9B,CAAE,MAAO,WAAY,EAGvB,CAAE,KAAM,SAAU,KAAM,CAAQ,CACzC,CAEA,IAAM,EAAQ,EAAQ,WAAW,CAAC,IAAM,GAIpC,EAAS,EAEb,KAAO,EAAS,GAAQ,CACtB,IAAM,EAAO,EAAQ,WAAW,CAAM,EAEtC,GAAI,IAAS,IAAM,IAAS,IAAM,IAAS,GACzC,MAGF,GAAU,CACZ,CAEA,IAAM,EAAO,EAAQ,MAAM,EAAG,CAAM,EAqBpC,OAnBI,EAAK,SAAW,EACX,CAAE,MAAO,WAAY,EAG1B,EAAS,EAAK,WAAW,EAAK,OAAS,CAAC,CAAC,EACpC,CAAE,MAAO,iBAAkB,EAQhC,EAAS,EACJ,EAAQ,WAAW,CAAM,IAAM,GAClC,CAAE,MAAO,kBAAmB,EAC5B,CAAE,MAAO,oBAAqB,EAG7B,EAAQ,CAAE,KAAM,QAAS,MAAK,EAAI,CAAE,KAAM,QAAS,MAAK,CACjE,CAiBA,SAAgB,EACd,EAC8B,CAC9B,IAAK,IAAM,KAAW,EAAkB,CAAI,EAAG,CAC7C,IAAM,EAAQ,EAAa,CAAO,EAElC,GAAI,UAAW,EACb,OAAO,EAAM,KAEjB,CAGF,CA0BA,SAAgB,EAAoB,EAAuC,CACzE,IAAM,EAAW,EAAkB,CAAI,EAEvC,IAAK,IAAI,EAAI,EAAG,EAAI,EAAS,OAAQ,GAAK,EAAG,CAC3C,IAAM,EAAQ,EAAa,EAAS,EAAE,EAEhC,aAAW,EAQjB,IAAI,EAAM,QAAU,mBAAoB,CACtC,IAAM,EAAU,EAAS,GACnB,EAAW,CAAC,GAAG,CAAQ,EAS7B,MAFA,GAAS,GAAK,EAAQ,MAAM,EAAG,EAAQ,QAAQ,GAAG,CAAC,EAE5C,CACL,KAAM,mBACN,UACA,eAAgB,EAAS,QAAQ,EAAG,IAAM,IAAM,CAAC,CAAC,CAAC,KAAK,GAAG,EAC3D,aAAc,EAAS,KAAK,GAAG,CACjC,CACF,CAEA,OAAO,EAAM,QAAU,qBACnB,CAAE,KAAM,qBAAsB,QAAS,EAAS,EAAG,EACnD,IAAA,EAJJ,CAKF,CAGF"}
@@ -1,2 +0,0 @@
1
- const e=Object.freeze(Object.create(null));function t(){return{staticChildren:e,hasChildren:!1,paramChild:void 0,splatChild:void 0,route:void 0,slashChildRoute:void 0}}function n(e){return e.length>1&&e.endsWith(`/`)?e.slice(0,-1):e}function r(e,t){return e===``?t:t===``?e:e+t}exports.EMPTY_STATIC_CHILDREN=e,exports.buildFullPath=r,exports.createSegmentNode=t,exports.normalizeTrailingSlash=n;
2
- //# sourceMappingURL=pathUtils.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"pathUtils.js","names":[],"sources":["../../../../src/engine/path-matcher/pathUtils.ts"],"sourcesContent":["import type { SegmentNode } from \"./types\";\n\n// Every trie node used to allocate its own `Object.create(null)` for\n// `staticChildren`. A null-proto empty object is V8 dictionary-mode from birth\n// (~192 B — own map + backing store, ~3× a plain `{}`), and the leaf-majority\n// (one node per registered route) never gains a static child, so it held that\n// empty object purely to answer the match-path `key in node.staticChildren`\n// read. Share ONE frozen empty null-proto sentinel across every fresh node;\n// `processSegment` (registration/trie.ts) copies-on-write — swaps in a fresh\n// mutable null-proto object — before the first real write. The frozen shell\n// fails loud if a write ever skips that guard. Mirrors the #1009 `EMPTY_*`\n// sentinels in `registration/context.ts`.\nexport const EMPTY_STATIC_CHILDREN: Record<string, SegmentNode> = Object.freeze(\n Object.create(null) as Record<string, SegmentNode>,\n);\n\nexport function createSegmentNode(): SegmentNode {\n return {\n staticChildren: EMPTY_STATIC_CHILDREN,\n // Stryker disable next-line BooleanLiteral: equivalent — for a leaf splat node the `!hasChildren` fast path and the `#traverseFrom` fallback both set `params[name] = slice` and return `sn.route`; nodes that gain children overwrite this flag during registration. Proven: forcing `true` keeps the full suite green (it only un-covers the now-unreachable fast path).\n hasChildren: false,\n paramChild: undefined,\n splatChild: undefined,\n route: undefined,\n slashChildRoute: undefined,\n };\n}\n\nexport function normalizeTrailingSlash(path: string): string {\n if (path.length > 1 && path.endsWith(\"/\")) {\n return path.slice(0, -1);\n }\n\n return path;\n}\n\nexport function buildFullPath(parentPath: string, nodePath: string): string {\n // Stryker disable next-line StringLiteral,BlockStatement: equivalent — empty-operand fast path; `parentPath + nodePath` already yields the identical string when `parentPath === \"\"`, so emptying the block or never triggering it (sentinel comparand) changes no result. ConditionalExpression stays live (killable `->true` sibling).\n if (parentPath === \"\") {\n return nodePath;\n }\n\n // Stryker disable next-line StringLiteral,BlockStatement: equivalent — symmetric empty-operand fast path; `parentPath + nodePath` already equals `parentPath` when `nodePath === \"\"`. ConditionalExpression stays live (killable `->true` sibling).\n if (nodePath === \"\") {\n return parentPath;\n }\n\n return parentPath + nodePath;\n}\n"],"mappings":"AAYA,MAAa,EAAqD,OAAO,OACvE,OAAO,OAAO,IAAI,CACpB,EAEA,SAAgB,GAAiC,CAC/C,MAAO,CACL,eAAgB,EAEhB,YAAa,GACb,WAAY,IAAA,GACZ,WAAY,IAAA,GACZ,MAAO,IAAA,GACP,gBAAiB,IAAA,EACnB,CACF,CAEA,SAAgB,EAAuB,EAAsB,CAK3D,OAJI,EAAK,OAAS,GAAK,EAAK,SAAS,GAAG,EAC/B,EAAK,MAAM,EAAG,EAAE,EAGlB,CACT,CAEA,SAAgB,EAAc,EAAoB,EAA0B,CAW1E,OATI,IAAe,GACV,EAIL,IAAa,GACR,EAGF,EAAa,CACtB"}
@@ -1,2 +0,0 @@
1
- function e(e){return e>=48&&e<=57||e>=65&&e<=70||e>=97&&e<=102}function t(t){let n=0;for(;n<t.length;)if(t.codePointAt(n)===37){if(n+2>=t.length)return!1;let r=t.codePointAt(n+1)??0,i=t.codePointAt(n+2)??0;if(!e(r)||!e(i))return!1;n+=3}else n++;return!0}exports.validatePercentEncoding=t;
2
- //# sourceMappingURL=percentEncoding.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"percentEncoding.js","names":[],"sources":["../../../../src/engine/path-matcher/percentEncoding.ts"],"sourcesContent":["function isHexCodePoint(code: number): boolean {\n return (\n (code >= 0x30 && code <= 0x39) ||\n (code >= 0x41 && code <= 0x46) ||\n (code >= 0x61 && code <= 0x66)\n );\n}\n\nexport function validatePercentEncoding(value: string): boolean {\n let i = 0;\n\n while (i < value.length) {\n if (value.codePointAt(i) === 0x25 /* % */) {\n // Stryker disable next-line ArithmeticOperator,BlockStatement: equivalent — a truncated \"%\" is independently rejected downstream by the `?? 0` fallback (codePointAt past end -> 0, never a hex code point), so weakening (`i - 2`) or emptying this early-exit changes no observable result. Proven: removing the whole block keeps the full unit+property+stress suite green. The ConditionalExpression/EqualityOperator variants here stay live (they have killable siblings).\n if (i + 2 >= value.length) {\n return false;\n }\n\n /* v8 ignore start -- @preserve: codePointAt cannot return undefined due to bounds check above */\n const hex1 = value.codePointAt(i + 1) ?? 0;\n const hex2 = value.codePointAt(i + 2) ?? 0;\n /* v8 ignore stop */\n\n if (!isHexCodePoint(hex1) || !isHexCodePoint(hex2)) {\n return false;\n }\n\n i += 3;\n } else {\n i++;\n }\n }\n\n return true;\n}\n"],"mappings":"AAAA,SAAS,EAAe,EAAuB,CAC7C,OACG,GAAQ,IAAQ,GAAQ,IACxB,GAAQ,IAAQ,GAAQ,IACxB,GAAQ,IAAQ,GAAQ,GAE7B,CAEA,SAAgB,EAAwB,EAAwB,CAC9D,IAAI,EAAI,EAER,KAAO,EAAI,EAAM,QACf,GAAI,EAAM,YAAY,CAAC,IAAM,GAAc,CAEzC,GAAI,EAAI,GAAK,EAAM,OACjB,MAAO,GAIT,IAAM,EAAO,EAAM,YAAY,EAAI,CAAC,GAAK,EACnC,EAAO,EAAM,YAAY,EAAI,CAAC,GAAK,EAGzC,GAAI,CAAC,EAAe,CAAI,GAAK,CAAC,EAAe,CAAI,EAC/C,MAAO,GAGT,GAAK,CACP,KACE,KAIJ,MAAO,EACT"}
@@ -1,2 +0,0 @@
1
- const e=require("../parseSegment.js"),t=require("../encoding.js"),n=require("./context.js");function r(e,n,r){let i=n.has(e.name);return{paramName:e.name,encoder:i?e=>t.encodeParam(e,r):t.ENCODING_METHODS[r]}}function i(t,i,a,o){let s=new Set(o),c=new Set;for(let e of i){for(let t of e.paramMeta.urlParams)s.add(t);for(let t of e.paramMeta.spatParams)c.add(t)}if(s.size===0)return{buildStaticParts:[t],buildParamSlots:n.EMPTY_PARAM_SLOTS};let l=[],u=[],d=``,f=e.splitPathSegments(t),p=f.length-1;for(let[t,n]of f.entries()){let i=e.parseSegment(n);if(!(!(`error`in i)&&i.kind===`splat`&&t!==p)){if(t>0&&(d+=`/`),`error`in i||i.kind===`static`){d+=n;continue}l.push(d),d=``,u.push(r(i,c,a))}}return l.push(d),{buildStaticParts:l,buildParamSlots:u}}exports.compileBuildParts=i;
2
- //# sourceMappingURL=buildParts.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"buildParts.js","names":["encodeParam","ENCODING_METHODS","EMPTY_PARAM_SLOTS","splitPathSegments","parseSegment"],"sources":["../../../../../src/engine/path-matcher/registration/buildParts.ts"],"sourcesContent":["// Build-path template compilation: turns a (constraint-stripped) route path into\n// `buildStaticParts` + `buildParamSlots` through the shared `parseSegment` tokenizer (Реш.1).\n\nimport { encodeParam, ENCODING_METHODS } from \"../encoding\";\nimport {\n parseSegment,\n splitPathSegments,\n type SegmentTokens,\n} from \"../parseSegment\";\nimport { EMPTY_PARAM_SLOTS } from \"./context\";\n\nimport type {\n BuildParamSlot,\n MatcherInputNode,\n URLParamsEncodingType,\n} from \"../types\";\n\n/**\n * Builds one `BuildParamSlot` from a param/splat token. The encoder is the single\n * `encodeParam` implementation the encoding unit/property suites assert (the splat\n * variant encodes each segment individually, preserving `/`), so prod and the\n * oracle can't drift (#860).\n */\nfunction makeBuildParamSlot(\n token: Extract<SegmentTokens, { kind: \"param\" | \"splat\" }>,\n allSplatParams: ReadonlySet<string>,\n encoding: URLParamsEncodingType,\n): BuildParamSlot {\n const isSplat = allSplatParams.has(token.name);\n\n return {\n paramName: token.name,\n encoder: isSplat\n ? (value: string): string => encodeParam(value, encoding)\n : ENCODING_METHODS[encoding],\n };\n}\n\nexport function compileBuildParts(\n normalizedPath: string,\n segments: readonly MatcherInputNode[],\n encoding: URLParamsEncodingType,\n rootUrlParams: readonly string[],\n): {\n buildStaticParts: readonly string[];\n buildParamSlots: readonly BuildParamSlot[];\n} {\n // `normalizedPath` carries the ROOT path as its prefix, but the root node is\n // absent from `segments` — so seeding these from the segment walk alone made a\n // root-declared slot invisible here, and the fast path below emitted the whole\n // path (`:tenant` included) as literal static text (#1567).\n const allUrlParams = new Set<string>(rootUrlParams);\n // Splat names come from the segment walk only: a ROOT splat is never the final\n // segment (a route path always follows it), so it never becomes a slot and its\n // encoder is never chosen. (#1567 seeded it from the root too; #1568 made that\n // unreachable — see the finality rule below.)\n const allSplatParams = new Set<string>();\n\n for (const segment of segments) {\n for (const param of segment.paramMeta.urlParams) {\n allUrlParams.add(param);\n }\n\n for (const param of segment.paramMeta.spatParams) {\n allSplatParams.add(param);\n }\n }\n\n // Stryker disable next-line BlockStatement: equivalent — fast path; the param-compile loop below yields [normalizedPath]/[] when allUrlParams is empty — identical output. Proven by injection.\n if (allUrlParams.size === 0) {\n return {\n buildStaticParts: [normalizedPath],\n buildParamSlots: EMPTY_PARAM_SLOTS,\n };\n }\n\n const parts: string[] = [];\n const slots: BuildParamSlot[] = [];\n let current = \"\";\n\n // Build the static-parts / param-slots template through the SAME tokenizer the\n // trie (L3) and `buildParamMeta` (L1) consume — `parseSegment`, not a parallel\n // `paramRgx` — so build's param NAME can no longer drift from the trie's\n // (#1050/#1150 build≠match closed structurally, not just by the round-trip\n // property). The path is already constraint-stripped (`matchPath`), so\n // `parseSegment` sees only name/optional; each `/` separator `splitPathSegments`\n // split away is re-added to the running static part.\n const pathSegments = splitPathSegments(normalizedPath);\n const lastIndex = pathSegments.length - 1;\n\n for (const [i, pathSegment] of pathSegments.entries()) {\n const token = parseSegment(pathSegment);\n\n // A splat binds ONLY as the final segment. The trie matches a splat node's\n // children at the splat's OWN position (INVARIANTS Matching #24: `/n/*rest`\n // + `/n/*rest/edit` → `/n/edit` resolves to the child), so a splat with\n // anything after it always captures the empty string. Emitting a slot for it\n // printed a URL `match` could not resolve — or, under a splat parent, one\n // that fell back to the parent's wildcard. It contributes neither text nor\n // separator. (#1568)\n if (!(\"error\" in token) && token.kind === \"splat\" && i !== lastIndex) {\n continue;\n }\n\n if (i > 0) {\n current += \"/\";\n }\n\n if (\"error\" in token || token.kind === \"static\") {\n // Static text — or a malformed segment, whose route is rejected at\n // `registerTree` before these buildParts are ever read (output moot).\n current += pathSegment;\n continue;\n }\n\n // param | splat: close the accumulated static part, emit a slot.\n parts.push(current);\n current = \"\";\n slots.push(makeBuildParamSlot(token, allSplatParams, encoding));\n }\n\n parts.push(current);\n\n return { buildStaticParts: parts, buildParamSlots: slots };\n}\n"],"mappings":"4FAuBA,SAAS,EACP,EACA,EACA,EACgB,CAChB,IAAM,EAAU,EAAe,IAAI,EAAM,IAAI,EAE7C,MAAO,CACL,UAAW,EAAM,KACjB,QAAS,EACJ,GAA0BA,EAAAA,YAAY,EAAO,CAAQ,EACtDC,EAAAA,iBAAiB,EACvB,CACF,CAEA,SAAgB,EACd,EACA,EACA,EACA,EAIA,CAKA,IAAM,EAAe,IAAI,IAAY,CAAa,EAK5C,EAAiB,IAAI,IAE3B,IAAK,IAAM,KAAW,EAAU,CAC9B,IAAK,IAAM,KAAS,EAAQ,UAAU,UACpC,EAAa,IAAI,CAAK,EAGxB,IAAK,IAAM,KAAS,EAAQ,UAAU,WACpC,EAAe,IAAI,CAAK,CAE5B,CAGA,GAAI,EAAa,OAAS,EACxB,MAAO,CACL,iBAAkB,CAAC,CAAc,EACjC,gBAAiBC,EAAAA,iBACnB,EAGF,IAAM,EAAkB,CAAC,EACnB,EAA0B,CAAC,EAC7B,EAAU,GASR,EAAeC,EAAAA,kBAAkB,CAAc,EAC/C,EAAY,EAAa,OAAS,EAExC,IAAK,GAAM,CAAC,EAAG,KAAgB,EAAa,QAAQ,EAAG,CACrD,IAAM,EAAQC,EAAAA,aAAa,CAAW,EASlC,OAAE,UAAW,IAAU,EAAM,OAAS,SAAW,IAAM,GAQ3D,IAJI,EAAI,IACN,GAAW,KAGT,UAAW,GAAS,EAAM,OAAS,SAAU,CAG/C,GAAW,EACX,QACF,CAGA,EAAM,KAAK,CAAO,EAClB,EAAU,GACV,EAAM,KAAK,EAAmB,EAAO,EAAgB,CAAQ,CAAC,CAL9D,CAMF,CAIA,OAFA,EAAM,KAAK,CAAO,EAEX,CAAE,iBAAkB,EAAO,gBAAiB,CAAM,CAC3D"}
@@ -1,2 +0,0 @@
1
- const e=Object.freeze([]),t=Object.freeze(new Set),n=Object.freeze([]),r=Object.freeze({}),i=Object.freeze({}),a=Object.freeze({});exports.EMPTY_PARAMS=r,exports.EMPTY_PARAM_SLOTS=n,exports.EMPTY_ROUTE_META=a,exports.EMPTY_SEARCH=i,exports.EMPTY_STRINGS=e,exports.EMPTY_STRING_SET=t;
2
- //# sourceMappingURL=context.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"context.js","names":[],"sources":["../../../../../src/engine/path-matcher/registration/context.ts"],"sourcesContent":["// Shared registration context: the `RegistrationState` interface and the frozen\n// empty-value sentinels (#1009). Leaf module (no sibling imports) so\n// `trie`/`trieNodes`/`buildParts` can depend on `RegistrationState` without\n// cycling back to the orchestrator.\n\nimport type {\n BuildParamSlot,\n CompiledRoute,\n ResolvedMatcherOptions,\n SegmentNode,\n} from \"../types\";\n\n// Shared frozen sentinels for the no-params common case — avoid a fresh empty\n// Set/array per route (#1009). All are ReadonlySet/[] and read-only on the\n// match/build hot paths.\nexport const EMPTY_STRINGS: readonly string[] = Object.freeze([]);\n\n// #1240 §5: freeze the Set shell too, so the \"Shared frozen sentinels\" claim\n// above holds for ALL of them and the #1009 sentinels are consistent with route-tree's\n// frozen `EMPTY_CHILDREN_MAP`. `Object.freeze` locks only the shell (not `.add`\n// — see route-tree INVARIANTS CC1), but these are `Readonly`-typed and never mutated.\nexport const EMPTY_STRING_SET: ReadonlySet<string> = Object.freeze(\n new Set<string>(),\n);\n\nexport const EMPTY_PARAM_SLOTS: readonly BuildParamSlot[] = Object.freeze([]);\n\nexport const EMPTY_PARAMS: Readonly<Record<string, unknown>> = Object.freeze(\n {},\n);\n\n/** Frozen empty query bag for precomputed query-less cached results (RFC-4 M2). */\nexport const EMPTY_SEARCH: Readonly<Record<string, unknown>> = Object.freeze(\n {},\n);\n\n// Shared frozen sentinel for a route whose every segment has an empty\n// paramTypeMap (all-static chain): `buildMeta` returns this instead of a fresh\n// per-route `{ [fullName]: {} }` record — with N distinct route names those\n// records degrade into N dictionary-mode objects (route-unique keys) while\n// carrying zero information. Consumers do keyed lookups (`meta[name]`) and\n// treat a missing entry as \"no params\", so empty-entry ≡ missing-entry; the\n// sentinel stays truthy for the route-found check in core's buildNavigateState.\nexport const EMPTY_ROUTE_META: Readonly<\n Record<string, Record<string, \"url\" | \"query\">>\n> = Object.freeze({});\n\nexport interface RegistrationState {\n readonly root: SegmentNode;\n readonly options: ResolvedMatcherOptions;\n readonly routesByName: Map<string, CompiledRoute>;\n readonly staticCache: Map<string, CompiledRoute>;\n readonly rootQueryParams: readonly string[];\n // The root node is deliberately absent from `matchSegments` (see SegmentMatcher\n // `registerTree`), so anything derived from a segment walk is root-blind. Its\n // QUERY declarations already had to be threaded separately (#1556); its PATH\n // slots need the same treatment for the build side (#1567).\n readonly rootUrlParams: readonly string[];\n}\n"],"mappings":"AAeA,MAAa,EAAmC,OAAO,OAAO,CAAC,CAAC,EAMnD,EAAwC,OAAO,OAC1D,IAAI,GACN,EAEa,EAA+C,OAAO,OAAO,CAAC,CAAC,EAE/D,EAAkD,OAAO,OACpE,CAAC,CACH,EAGa,EAAkD,OAAO,OACpE,CAAC,CACH,EASa,EAET,OAAO,OAAO,CAAC,CAAC"}
@@ -1,2 +0,0 @@
1
- function e(e,t,n){throw Error(`[SegmentMatcher.registerTree] Parameter name conflict at the same path position: '${n}${e}' and '${n}${t}'. A parametric URL segment binds to a single name across every route that shares that position — the value cannot be captured under two names. Rename one so both routes agree (e.g. use '${n}${e}' in both).`)}function t(){throw Error(`[SegmentMatcher.registerTree] Empty parameter name: a parameter marker (':' or '*') must be followed by a name (e.g. ':id', '*rest'). A name-less marker, or a trailing '?' with no parameter name, would capture under an empty key at match but emit a literal at build — the two disagree, so it is rejected.`)}function n(e){throw Error(`[SegmentMatcher.registerTree] Fused parameter marker in segment "${e}": a ':'/'*' marker must begin a segment (e.g. 'a/:b', not 'a:b'). build extracts it as a param while the trie treats the segment as a literal — the two disagree.`)}function r(e){throw Error(`[SegmentMatcher.registerTree] Trailing parameter marker in segment "${e}": a param name cannot end in a bare ':' or '*' (e.g. ':y*' — the name is 'y' plus a stray marker). build/meta would capture the marker into the name while the gate rejects it as name-less — the two disagree, so it is rejected.`)}function i(e){throw Error(`[SegmentMatcher.registerTree] Optional params are not supported: "${e}" — declare two sibling routes instead (one with the segment, one without). The route hierarchy already expresses optionality.`)}function a(e){throw Error(`[SegmentMatcher.registerTree] Regex constraints are not supported: "<"/">" are reserved in path segments ("${e}"). Match the segment as a plain string and validate the value in a guard (canActivate) or app code.`)}function o(e){throw Error(`[SegmentMatcher.registerTree] Non-ASCII static segment "${e}": match rejects non-ASCII input and compares static keys raw, so this route would never match. Percent-encode it (e.g. "/caf%C3%A9") or use a param.`)}function s(e,o){switch(e){case`name-less`:return t();case`trailing-marker`:return r(o);case`fused-marker`:return n(o);case`optional-removed`:return i(o);case`constraint-removed`:return a(o)}}function c(e,t){let n=new Set,r=``;for(let e of t){if(n.has(e)){r=e;break}n.add(e)}throw Error(`[SegmentMatcher.registerTree] Duplicate parameter name ':${r}' in route "${e}": a param name must be unique within a route — two positions cannot both bind ':${r}' (the second silently overwrites the first). Rename one.`)}function l(e,t){throw Error(`[SegmentMatcher.registerTree] Invalid query-param declaration "${t}" in route "${e}": a query-param name cannot contain '<' or '>' — it would never round-trip. Rename the query param.`)}function u(e,t){throw Error(`[SegmentMatcher.registerTree] Duplicate route path: routes "${e}" and "${t}" resolve to the same URL. The later registration would silently shadow the earlier (its deep link would resolve to the other route). Give them distinct paths.`)}function d(e,t){throw Error(`[SegmentMatcher.registerTree] Index route "${e}" (path "/") under the splat parent "${t}" is not supported: the index sits on the splat node, which the wildcard match never reaches, so it is unreachable. Give the index a distinct path, or make the parent static.`)}exports.throwDuplicateParamName=c,exports.throwDuplicateRoutePath=u,exports.throwEmptyParamName=t,exports.throwInvalidQueryParamName=l,exports.throwNonAsciiStatic=o,exports.throwParamNameConflict=e,exports.throwSegmentGrammarError=s,exports.throwSlashChildUnderDynamicParent=d;
2
- //# sourceMappingURL=errors.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"errors.js","names":[],"sources":["../../../../../src/engine/path-matcher/registration/errors.ts"],"sourcesContent":["// Registration error factories: every `throw*` helper the pipeline raises, plus the\n// `throwSegmentGrammarError` code→message dispatcher (Реш.2-A). Pure leaf — each\n// builds a message and throws; none reads trie state.\n\nimport type { SegmentErrorCode } from \"../parseSegment\";\n\n/**\n * Guards against param-name aliasing in the segment trie (issue #736).\n *\n * A parametric (`:name`) or splat (`*name`) position in the trie is keyed by\n * **position**, not by name — but the captured value is written under the name\n * recorded on that position. When two *different* routes share a position under\n * *different* names (e.g. `/user/:id` and `/user/:slug/profile`),\n * first-registration wins the name, so the second route silently captures its\n * value under the wrong key. Through `rewritePathOnMatch` that silent key-swap\n * becomes a hard `start()` crash on a legitimate config — so we reject the\n * ambiguity loudly at registration instead of corrupting matches.\n *\n * The conflict is strictly **cross-route**. Under the 3-token grammar (M1, #1516)\n * insertion is a strict linear walk — a route never revisits a slot it created\n * (the former optional-omit fork, which could land two differently-named params\n * on one position within a single route, is gone). So any name mismatch at a\n * position is unconditionally a prior route's slot: `ensureParamChild` throws on\n * `name !== paramName` with no `ownNodes` exception.\n */\nexport function throwParamNameConflict(\n existingName: string,\n newName: string,\n marker: \":\" | \"*\",\n): never {\n throw new Error(\n `[SegmentMatcher.registerTree] Parameter name conflict at the same path ` +\n `position: '${marker}${existingName}' and '${marker}${newName}'. A ` +\n `parametric URL segment binds to a single name across every route that ` +\n `shares that position — the value cannot be captured under two names. ` +\n `Rename one so both routes agree (e.g. use '${marker}${existingName}' in both).`,\n );\n}\n\n/**\n * A bare marker (`:` or `*` with no name) compiles to a phantom empty-named\n * slot: match captures the value under `\"\"`, buildPath emits the literal marker,\n * and buildParamMeta reports no param at all — a three-way match/build/meta\n * desync of the same class as #736/#738 (#858). Reject it at registration,\n * symmetrically for both markers, instead of corrupting the trie.\n */\nexport function throwEmptyParamName(): never {\n // Marker-agnostic: this fires for a bare ':'/'*' (`/x/:`, `/x/*`), a marker\n // carrying only a modifier char with no name (`/x/:?`, `/x/:<...>`), AND a\n // static segment with a trailing '?' (`/faq?`) — all routed here via\n // `extractParamName`. So the message must NOT claim a specific ':' marker\n // (there isn't one for `/faq?`, #1241).\n throw new Error(\n `[SegmentMatcher.registerTree] Empty parameter name: a parameter marker ` +\n `(':' or '*') must be followed by a name (e.g. ':id', '*rest'). A name-less ` +\n `marker, or a trailing '?' with no parameter name, would capture under an ` +\n `empty key at match but emit a literal at build — the two disagree, so it ` +\n `is rejected.`,\n );\n}\n\n/**\n * Rejects a `:`/`*` marker fused to a static prefix within a segment (`a:b`,\n * `x:id`, `a*b`): the build/meta param regexes are unanchored and extract it as\n * a param, but this trie honors a marker only at segment start and compiles the\n * segment as a static literal — so `buildPath` emits an unmatchable URL while\n * `match` rejects it (#1050). The sibling of {@link throwEmptyParamName} (#858):\n * an ambiguous marker placement the three parsers cannot agree on. route-tree's\n * validation gate catches this first with a route-contextual error; this is the\n * standalone registration backstop.\n */\nfunction throwFusedMarker(segment: string): never {\n throw new Error(\n `[SegmentMatcher.registerTree] Fused parameter marker in segment \"${segment}\": ` +\n `a ':'/'*' marker must begin a segment (e.g. 'a/:b', not 'a:b'). build extracts ` +\n `it as a param while the trie treats the segment as a literal — the two disagree.`,\n );\n}\n\n/**\n * Rejects a param name ending in a bare marker (`:y*`, `:y:`, #1324): the\n * build/meta name class (`[^/?<]+`) greedily swallows the trailing `:`/`*` into\n * the name (`y*`) while the route-tree gate reads it as a name-less marker and\n * rejects — a real gate↔backstop divergence (formerly excluded from the parity\n * property, gate-masked in production). `parseSegment` ends the name before a\n * trailing marker, so this backstop now agrees with the gate. The sibling of\n * {@link throwEmptyParamName} (#858) / {@link throwFusedMarker} (#1050) on the\n * trailing-marker axis.\n */\nfunction throwTrailingMarker(segment: string): never {\n throw new Error(\n `[SegmentMatcher.registerTree] Trailing parameter marker in segment \"${segment}\": ` +\n `a param name cannot end in a bare ':' or '*' (e.g. ':y*' — the name is 'y' plus a ` +\n `stray marker). build/meta would capture the marker into the name while the gate ` +\n `rejects it as name-less — the two disagree, so it is rejected.`,\n );\n}\n\n/**\n * `optional-removed` (M1): a `:x?`/`*x?` optional modifier. The backstop tier —\n * a short, path-free recipe (the route-tree gate's rich tier computes the two\n * concrete sibling paths). Optional params were dropped for zero corpus use +\n * the axis's largest bug cluster; the hierarchy already expresses optionality.\n */\nfunction throwOptionalRemoved(segment: string): never {\n throw new Error(\n `[SegmentMatcher.registerTree] Optional params are not supported: \"${segment}\" — ` +\n `declare two sibling routes instead (one with the segment, one without). ` +\n `The route hierarchy already expresses optionality.`,\n );\n}\n\n/**\n * `constraint-removed` (M1): a `<re>` constraint or a stray `<`/`>`. The backstop\n * tier — a short recipe (the gate's rich tier names the offending segment). Regex\n * constraints were dropped; validate the value in a guard instead.\n */\nfunction throwConstraintRemoved(segment: string): never {\n throw new Error(\n `[SegmentMatcher.registerTree] Regex constraints are not supported: \"<\"/\">\" are ` +\n `reserved in path segments (\"${segment}\"). Match the segment as a plain ` +\n `string and validate the value in a guard (canActivate) or app code.`,\n );\n}\n\nexport function throwNonAsciiStatic(segment: string): never {\n throw new Error(\n `[SegmentMatcher.registerTree] Non-ASCII static segment \"${segment}\": match ` +\n `rejects non-ASCII input and compares static keys raw, so this route would ` +\n `never match. Percent-encode it (e.g. \"/caf%C3%A9\") or use a param.`,\n );\n}\n\n/**\n * Dispatches a `parseSegment` grammar-error code (the per-segment backstop) to the\n * matching matcher-level throw — the single place mapping the tokenizer's verdict\n * onto the message, so the reject reason stays byte-identical per code. The two\n * removed-form codes (M1) route to their short recipe throws; the route-tree gate\n * catches the same forms first with its richer route-contextual recipe.\n */\nexport function throwSegmentGrammarError(\n code: SegmentErrorCode,\n segment: string,\n): never {\n switch (code) {\n case \"name-less\": {\n return throwEmptyParamName();\n }\n case \"trailing-marker\": {\n return throwTrailingMarker(segment);\n }\n case \"fused-marker\": {\n return throwFusedMarker(segment);\n }\n case \"optional-removed\": {\n return throwOptionalRemoved(segment);\n }\n case \"constraint-removed\": {\n return throwConstraintRemoved(segment);\n }\n }\n}\n\nexport function throwDuplicateParamName(\n routeName: string,\n names: readonly string[],\n): never {\n const seen = new Set<string>();\n let duplicate = \"\";\n\n for (const name of names) {\n if (seen.has(name)) {\n duplicate = name;\n\n break;\n }\n\n seen.add(name);\n }\n\n throw new Error(\n `[SegmentMatcher.registerTree] Duplicate parameter name ':${duplicate}' in ` +\n `route \"${routeName}\": a param name must be unique within a route — two ` +\n `positions cannot both bind ':${duplicate}' (the second silently overwrites ` +\n `the first). Rename one.`,\n );\n}\n\nexport function throwInvalidQueryParamName(\n routeName: string,\n name: string,\n): never {\n throw new Error(\n `[SegmentMatcher.registerTree] Invalid query-param declaration \"${name}\" in ` +\n `route \"${routeName}\": a query-param name cannot contain '<' or '>' — it would ` +\n `never round-trip. Rename the query param.`,\n );\n}\n\nexport function throwDuplicateRoutePath(\n existingName: string,\n newName: string,\n): never {\n throw new Error(\n `[SegmentMatcher.registerTree] Duplicate route path: routes \"${existingName}\" ` +\n `and \"${newName}\" resolve to the same URL. The later registration would ` +\n `silently shadow the earlier (its deep link would resolve to the other ` +\n `route). Give them distinct paths.`,\n );\n}\n\nexport function throwSlashChildUnderDynamicParent(\n routeName: string,\n parentPath: string,\n): never {\n throw new Error(\n `[SegmentMatcher.registerTree] Index route \"${routeName}\" (path \"/\") under the ` +\n `splat parent \"${parentPath}\" is not supported: the index sits on the splat ` +\n `node, which the wildcard match never reaches, so it is unreachable. Give the ` +\n `index a distinct path, or make the parent static.`,\n );\n}\n"],"mappings":"AAyBA,SAAgB,EACd,EACA,EACA,EACO,CACP,MAAU,MACR,qFACgB,IAAS,EAAa,SAAS,IAAS,EAAQ,6LAGhB,IAAS,EAAa,YACxE,CACF,CASA,SAAgB,GAA6B,CAM3C,MAAU,MACR,kTAKF,CACF,CAYA,SAAS,EAAiB,EAAwB,CAChD,MAAU,MACR,oEAAoE,EAAQ,mKAG9E,CACF,CAYA,SAAS,EAAoB,EAAwB,CACnD,MAAU,MACR,uEAAuE,EAAQ,oOAIjF,CACF,CAQA,SAAS,EAAqB,EAAwB,CACpD,MAAU,MACR,qEAAqE,EAAQ,+HAG/E,CACF,CAOA,SAAS,EAAuB,EAAwB,CACtD,MAAU,MACR,8GACiC,EAAQ,qGAE3C,CACF,CAEA,SAAgB,EAAoB,EAAwB,CAC1D,MAAU,MACR,2DAA2D,EAAQ,sJAGrE,CACF,CASA,SAAgB,EACd,EACA,EACO,CACP,OAAQ,EAAR,CACE,IAAK,YACH,OAAO,EAAoB,EAE7B,IAAK,kBACH,OAAO,EAAoB,CAAO,EAEpC,IAAK,eACH,OAAO,EAAiB,CAAO,EAEjC,IAAK,mBACH,OAAO,EAAqB,CAAO,EAErC,IAAK,qBACH,OAAO,EAAuB,CAAO,CAEzC,CACF,CAEA,SAAgB,EACd,EACA,EACO,CACP,IAAM,EAAO,IAAI,IACb,EAAY,GAEhB,IAAK,IAAM,KAAQ,EAAO,CACxB,GAAI,EAAK,IAAI,CAAI,EAAG,CAClB,EAAY,EAEZ,KACF,CAEA,EAAK,IAAI,CAAI,CACf,CAEA,MAAU,MACR,4DAA4D,EAAU,cAC1D,EAAU,mFACY,EAAU,0DAE9C,CACF,CAEA,SAAgB,EACd,EACA,EACO,CACP,MAAU,MACR,kEAAkE,EAAK,cAC3D,EAAU,qGAExB,CACF,CAEA,SAAgB,EACd,EACA,EACO,CACP,MAAU,MACR,+DAA+D,EAAa,SAClE,EAAQ,gKAGpB,CACF,CAEA,SAAgB,EACd,EACA,EACO,CACP,MAAU,MACR,8CAA8C,EAAU,uCACrC,EAAW,+KAGhC,CACF"}
@@ -1,2 +0,0 @@
1
- const e=require("../parseSegment.js"),t=require("../buildParamMeta.js"),n=require("../pathUtils.js"),r=require("./context.js"),i=require("./buildParts.js"),a=require("./errors.js"),o=require("./trie.js");function s(r,i,o,l,u){let d=i.fullName===``;d||l.push(i);let f=i.absolute,p=i.paramMeta===t.EMPTY_PARAM_META?i.path:i.paramMeta.pathPattern,m=f&&p.startsWith(`~`)?p.slice(1):p,h=f?m:p;for(let t of e.splitPathSegments(h)){let n=e.parseSegment(t);`error`in n&&a.throwSegmentGrammarError(n.error,t)}let g=h,_=f?g:n.buildFullPath(o,g),v=d?u:c(r,i,_,f?``:o,l,u);for(let e of i.children.values())s(r,e,_,l,v);d||l.pop()}function c(e,t,o,s,c,u){let g=p(o,s),_=Object.freeze([...c]),v=l(_),y=n.normalizeTrailingSlash(o),b=m(e.rootQueryParams,c),{buildStaticParts:x,buildParamSlots:S}=i.compileBuildParts(g?n.normalizeTrailingSlash(s):y,g?c.slice(0,-1):c,e.options.urlParamsEncoding,e.rootUrlParams),C=S.map(e=>e.paramName),w=C.length===0?r.EMPTY_STRING_SET:new Set(C);w.size!==C.length&&a.throwDuplicateParamName(t.fullName,C),h(t.fullName,b);let T={name:t.fullName,parent:u,matchSegments:_,meta:v,declaredQueryParams:b,declaredQueryParamsSet:b.length===0?r.EMPTY_STRING_SET:new Set(b),hasTrailingSlash:o.length>1&&o.endsWith(`/`),buildStaticParts:x,buildParamSlots:S,buildParamNamesSet:w,cachedResult:void 0};return t.paramMeta.urlParams.length===0&&(T.cachedResult=Object.freeze({segments:T.matchSegments,params:r.EMPTY_PARAMS,search:r.EMPTY_SEARCH,meta:T.meta})),e.routesByName.set(t.fullName,T),g?d(e,T,s):f(e,T,o,y,t),T}function l(e){let t;for(let n of e)u(n.paramTypeMap)&&(t??={},t[n.fullName]=n.paramTypeMap);return t===void 0?r.EMPTY_ROUTE_META:Object.freeze(t)}function u(e){for(let t in e)if(Object.hasOwn(e,t))return!0;return!1}function d(e,t,r){o.insertSlashChildIntoTrie(e,t,r);let i=n.normalizeTrailingSlash(r),a=e.options.caseSensitive?i:i.toLowerCase();e.staticCache.has(a)&&e.staticCache.set(a,t)}function f(e,t,n,r,i){if(o.insertIntoTrie(e,t,n),i.paramMeta.urlParams.length===0){let n=e.options.caseSensitive?r:r.toLowerCase();e.staticCache.set(n,t)}}function p(e,t){return n.normalizeTrailingSlash(e)===n.normalizeTrailingSlash(t)}function m(e,t){let n=[];e.length>0&&n.push(...e);for(let e of t)e.paramMeta.queryParams.length>0&&n.push(...e.paramMeta.queryParams);return n.length===0?r.EMPTY_STRINGS:n}function h(e,n){for(let r of n)t.INVALID_QUERY_NAME_RGX.test(r)&&a.throwInvalidQueryParamName(e,r)}exports.registerNode=s;
2
- //# sourceMappingURL=index.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","names":["EMPTY_PARAM_META","splitPathSegments","parseSegment","buildFullPath","normalizeTrailingSlash","compileBuildParts","EMPTY_STRING_SET","EMPTY_PARAMS","EMPTY_SEARCH","EMPTY_ROUTE_META","EMPTY_STRINGS","INVALID_QUERY_NAME_RGX"],"sources":["../../../../../src/engine/path-matcher/registration/index.ts"],"sourcesContent":["// Registration entry + orchestration: `registerNode` (the one public entry, re-exported\n// for `SegmentMatcher`) runs the per-node grammar pre-pass (Реш.2-A), compile, and trie\n// insertion, plus the route-meta / query-and-constraint collection helpers.\n// Concerns split into ./context ./errors ./trieNodes ./trie ./buildParts.\n\nimport { EMPTY_PARAM_META, INVALID_QUERY_NAME_RGX } from \"../buildParamMeta\";\nimport { parseSegment, splitPathSegments } from \"../parseSegment\";\nimport { buildFullPath, normalizeTrailingSlash } from \"../pathUtils\";\nimport { compileBuildParts } from \"./buildParts\";\nimport {\n EMPTY_PARAMS,\n EMPTY_SEARCH,\n EMPTY_ROUTE_META,\n EMPTY_STRINGS,\n EMPTY_STRING_SET,\n type RegistrationState,\n} from \"./context\";\nimport {\n throwDuplicateParamName,\n throwInvalidQueryParamName,\n throwSegmentGrammarError,\n} from \"./errors\";\nimport { insertIntoTrie, insertSlashChildIntoTrie } from \"./trie\";\n\nimport type { CompiledRoute, MatcherInputNode } from \"../types\";\n\nexport type { RegistrationState } from \"./context\";\n\nexport function registerNode(\n state: RegistrationState,\n node: MatcherInputNode,\n parentPath: string,\n segments: MatcherInputNode[],\n parentRoute: CompiledRoute | null,\n): void {\n const isRoot = node.fullName === \"\";\n\n if (!isRoot) {\n segments.push(node);\n }\n\n const isAbsolute = node.absolute;\n // The EMPTY_PARAM_META sentinel (fully-static node) carries pathPattern \"\";\n // its real pattern is the node's own path (sentinel is only installed when\n // the two were reference-equal).\n const pathPattern =\n node.paramMeta === EMPTY_PARAM_META\n ? node.path\n : node.paramMeta.pathPattern;\n const strippedPattern =\n isAbsolute && pathPattern.startsWith(\"~\")\n ? pathPattern.slice(1)\n : pathPattern;\n const rawNodePath = isAbsolute ? strippedPattern : pathPattern;\n\n // Per-segment grammar backstop: the trie's own grammar verdict reads the SAME\n // `parseSegment` tokenizer the route-tree gate reads (`findSegmentGrammarError`),\n // so backstop and gate cannot drift on a per-segment form. One pass over the RAW\n // path rejects every rejection form: name-less (#858), fused marker (#1050),\n // trailing marker (#1324), and the removed forms `optional-removed` /\n // `constraint-removed` (M1). So `processSegment` / `extractParamName` downstream\n // see only grammatically valid `static | :param | *splat` segments.\n for (const segment of splitPathSegments(rawNodePath)) {\n const token = parseSegment(segment);\n\n if (\"error\" in token) {\n throwSegmentGrammarError(token.error, segment);\n }\n }\n\n // 3-token grammar (M1): no `<...>` constraint to strip before trie insertion.\n const nodePath = rawNodePath;\n\n const matchPath = isAbsolute ? nodePath : buildFullPath(parentPath, nodePath);\n\n const compileParentPath = isAbsolute ? \"\" : parentPath;\n\n const currentRoute: CompiledRoute | null = isRoot\n ? parentRoute\n : compileAndRegisterRoute(\n state,\n node,\n matchPath,\n compileParentPath,\n segments,\n parentRoute,\n );\n\n for (const child of node.children.values()) {\n registerNode(state, child, matchPath, segments, currentRoute);\n }\n\n if (!isRoot) {\n segments.pop();\n }\n}\n\nfunction compileAndRegisterRoute(\n state: RegistrationState,\n node: MatcherInputNode,\n matchPath: string,\n parentPath: string,\n segments: MatcherInputNode[],\n parentRoute: CompiledRoute | null,\n): CompiledRoute {\n const slashChild = isSlashChild(matchPath, parentPath);\n\n const frozenSegments = Object.freeze([...segments]);\n const frozenMeta = buildMeta(frozenSegments);\n\n const normalizedPath = normalizeTrailingSlash(matchPath);\n\n const declaredQueryParams = collectDeclaredQueryParams(\n state.rootQueryParams,\n segments,\n );\n\n // Slash-child: use parent path for buildParts (not slash-child's path)\n const buildPath = slashChild\n ? normalizeTrailingSlash(parentPath)\n : normalizedPath;\n\n const { buildStaticParts, buildParamSlots } = compileBuildParts(\n buildPath,\n // Stryker disable next-line MethodExpression: equivalent — slash-child buildParts: dropping the last segment vs keeping it yields identical buildStaticParts here (no own params on the slash-child). Proven by injection (full suite green).\n slashChild ? segments.slice(0, -1) : segments,\n state.options.urlParamsEncoding,\n state.rootUrlParams,\n );\n\n // #1151: reject a duplicate param name within one route's full path (`/:id/:id`,\n // a param+splat clash `/:x/*x`, or a parent `/a/:x` + child `/:x`). buildParamSlots\n // keeps duplicates; the trie stores them at DIFFERENT positions under the SAME\n // name, so match's later capture silently overwrites the earlier and\n // rewritePathOnMatch then rewrites the user's URL from the single survivor. The\n // #736 conflict guard only fires on DIFFERENTLY-named params at ONE position, so\n // this same-name case slips through.\n const buildParamNames = buildParamSlots.map((slot) => slot.paramName);\n const buildParamNamesSet =\n buildParamNames.length === 0 ? EMPTY_STRING_SET : new Set(buildParamNames);\n\n if (buildParamNamesSet.size !== buildParamNames.length) {\n throwDuplicateParamName(node.fullName, buildParamNames);\n }\n\n // #1242 §5.1/§5.2/§5.3: validate query-param DECLARATIONS. A declared query name\n // must be a clean token — reject one carrying constraint/query metacharacters\n // (`:b?<\\d+>` declares query `<\\d+>`; `?tab=1` declares `tab=1`), and reject a name\n // never round-trips (`<`/`>` in the name). A name shared with a path param\n // (`/a/:tab?tab`) is legal under M2 — separate params/search channels (#1548).\n validateQueryParamDeclarations(node.fullName, declaredQueryParams);\n\n const compiled: CompiledRoute = {\n name: node.fullName,\n parent: parentRoute,\n matchSegments: frozenSegments,\n meta: frozenMeta,\n declaredQueryParams,\n declaredQueryParamsSet:\n declaredQueryParams.length === 0\n ? EMPTY_STRING_SET\n : new Set(declaredQueryParams),\n hasTrailingSlash: matchPath.length > 1 && matchPath.endsWith(\"/\"),\n buildStaticParts,\n buildParamSlots,\n buildParamNamesSet,\n // Initialized here (not added conditionally below) so static and param\n // routes share one hidden class — avoids a megamorphic CompiledRoute (#1009).\n cachedResult: undefined,\n };\n\n // Stryker disable next-line ConditionalExpression,EqualityOperator,BlockStatement: equivalent — cachedResult is a pure match() optimization; #buildResult recomputes the same value on a miss (proven: disabling the whole static cache keeps the unit+property suite green)\n if (node.paramMeta.urlParams.length === 0) {\n compiled.cachedResult = Object.freeze({\n segments: compiled.matchSegments,\n params: EMPTY_PARAMS,\n search: EMPTY_SEARCH,\n meta: compiled.meta,\n });\n }\n\n state.routesByName.set(node.fullName, compiled);\n\n if (slashChild) {\n registerSlashChild(state, compiled, parentPath);\n } else {\n registerStandardRoute(state, compiled, matchPath, normalizedPath, node);\n }\n\n return compiled;\n}\n\nfunction buildMeta(\n segments: readonly MatcherInputNode[],\n): Readonly<Record<string, Record<string, \"url\" | \"query\">>> {\n let meta: Record<string, Record<string, \"url\" | \"query\">> | undefined;\n\n for (const segment of segments) {\n if (!hasAnyParam(segment.paramTypeMap)) {\n continue;\n }\n\n meta ??= {};\n meta[segment.fullName] = segment.paramTypeMap;\n }\n\n return meta === undefined ? EMPTY_ROUTE_META : Object.freeze(meta);\n}\n\n// Allocation-free emptiness probe for a segment's paramTypeMap (Object.keys\n// would allocate a fresh array per segment during registration).\nfunction hasAnyParam(\n paramTypeMap: Readonly<Record<string, \"url\" | \"query\">>,\n): boolean {\n for (const key in paramTypeMap) {\n if (Object.hasOwn(paramTypeMap, key)) {\n return true;\n }\n }\n\n return false;\n}\n\nfunction registerSlashChild(\n state: RegistrationState,\n compiled: CompiledRoute,\n parentPath: string,\n): void {\n insertSlashChildIntoTrie(state, compiled, parentPath);\n\n const parentNormalized = normalizeTrailingSlash(parentPath);\n const cacheKey = state.options.caseSensitive\n ? parentNormalized\n : parentNormalized.toLowerCase();\n\n if (state.staticCache.has(cacheKey)) {\n state.staticCache.set(cacheKey, compiled);\n }\n}\n\nfunction registerStandardRoute(\n state: RegistrationState,\n compiled: CompiledRoute,\n matchPath: string,\n normalizedPath: string,\n node: MatcherInputNode,\n): void {\n insertIntoTrie(state, compiled, matchPath);\n\n // Stryker disable next-line ConditionalExpression,EqualityOperator,BlockStatement: equivalent — staticCache is a pure match() optimization; #traverse resolves the same route on a miss (proven: disabling the whole static cache keeps the unit+property suite green)\n if (node.paramMeta.urlParams.length === 0) {\n const cacheKey = state.options.caseSensitive\n ? normalizedPath\n : // Stryker disable next-line MethodExpression: equivalent — the case-insensitive cache key only governs a hit; a miss falls through to #traverse, which is also case-insensitive\n normalizedPath.toLowerCase();\n\n state.staticCache.set(cacheKey, compiled);\n }\n}\n\nfunction isSlashChild(matchPath: string, parentPath: string): boolean {\n const normalizedMatch = normalizeTrailingSlash(matchPath);\n const normalizedParent = normalizeTrailingSlash(parentPath);\n\n return normalizedMatch === normalizedParent;\n}\n\nfunction collectDeclaredQueryParams(\n rootQueryParams: readonly string[],\n segments: readonly MatcherInputNode[],\n): readonly string[] {\n // Stryker disable next-line ArrayDeclaration: equivalent — the array is populated then returned; a phantom seed element is never read back (declared-param loop skips absent keys, buildQueryString ignores it). Proven by injection.\n const queryParams: string[] = [];\n\n // Include query params declared on the root node (e.g., a root path like \"?mode\")\n if (rootQueryParams.length > 0) {\n queryParams.push(...rootQueryParams);\n }\n\n for (const segment of segments) {\n if (segment.paramMeta.queryParams.length > 0) {\n queryParams.push(...segment.paramMeta.queryParams);\n }\n }\n\n return queryParams.length === 0 ? EMPTY_STRINGS : queryParams;\n}\n\nfunction validateQueryParamDeclarations(\n routeName: string,\n queryParams: readonly string[],\n): void {\n // A path/query name collision (`/a/:tab?tab`) is legal under M2 — `tab` lives\n // in `state.params` AND `state.search` as separate channels (RFC-4 M2 /\n // #1548); the former \"declared as both\" rejection is gone. Only a query name\n // that can never round-trip (contains `<`/`>`) is still rejected.\n for (const name of queryParams) {\n if (INVALID_QUERY_NAME_RGX.test(name)) {\n throwInvalidQueryParamName(routeName, name);\n }\n }\n}\n"],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- const e=require("../pathUtils.js"),t=require("./errors.js"),n=require("./trieNodes.js");function r(e,n){e.route!==void 0&&e.route!==n&&t.throwDuplicateRoutePath(e.route.name,n.name),e.route=n}function i(e){for(let t=0;t<e.length;t++)if(e.charCodeAt(t)>=128)return!0;return!1}function a(t,n,i){let a=e.normalizeTrailingSlash(i);if(a===`/`){r(t.root,n);return}o(t,t.root,a,1,n)}function o(e,t,n,i,a){let o=n.length;for(;i<=o;){let r=n.indexOf(`/`,i),a=r===-1?o:r,s=n.slice(i,a);t=u(e,t,s),i=a+1}r(t,a)}function s(e,n,r){r.slice(r.lastIndexOf(`/`)+1).startsWith(`*`)&&t.throwSlashChildUnderDynamicParent(n.name,r);let i=c(e,r);i.slashChildRoute=n}function c(e,t){return l(e,e.root,t)}function l(t,n,r){let i=e.normalizeTrailingSlash(r);if(i===`/`||i===``)return n;let a=n,o=1,s=i.length;for(;o<=s;){let e=i.indexOf(`/`,o),n=e===-1?s:e;if(n<=o)break;let r=i.slice(o,n);a=u(t,a,r),o=n+1}return a}function u(r,a,o){if(o.startsWith(`*`)){let e=n.ensureSplatChild(a,n.extractParamName(o));return a.hasChildren=!0,e}if(o.startsWith(`:`)){let e=n.ensureParamChild(a,n.extractParamName(o));return a.hasChildren=!0,e}i(o)&&t.throwNonAsciiStatic(o);let s=r.options.caseSensitive?o:o.toLowerCase();return s in a.staticChildren||(a.staticChildren===e.EMPTY_STATIC_CHILDREN&&(a.staticChildren=Object.create(null)),a.staticChildren[s]=e.createSegmentNode(),a.hasChildren=!0),a.staticChildren[s]}exports.insertIntoTrie=a,exports.insertSlashChildIntoTrie=s;
2
- //# sourceMappingURL=trie.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"trie.js","names":["normalizeTrailingSlash","ensureSplatChild","extractParamName","ensureParamChild","EMPTY_STATIC_CHILDREN","createSegmentNode"],"sources":["../../../../../src/engine/path-matcher/registration/trie.ts"],"sourcesContent":["// Trie insertion + walking: the recursive `insertIntoTrieFrom` linear walk, slash-child\n// insertion, per-segment `processSegment`, and the `walkTrie` lookups. Builds the\n// segment trie from the node builders in `./trieNodes`.\n\nimport {\n createSegmentNode,\n EMPTY_STATIC_CHILDREN,\n normalizeTrailingSlash,\n} from \"../pathUtils\";\nimport {\n throwDuplicateRoutePath,\n throwNonAsciiStatic,\n throwSlashChildUnderDynamicParent,\n} from \"./errors\";\nimport {\n ensureParamChild,\n ensureSplatChild,\n extractParamName,\n} from \"./trieNodes\";\n\nimport type { CompiledRoute, SegmentNode } from \"../types\";\nimport type { RegistrationState } from \"./context\";\n\n/**\n * #1153: writes a terminal route, rejecting a second write by a DIFFERENT route —\n * two routes compiling to the same effective path (flat vs nested `/a/b`, or `/x`\n * vs `/x/`), where the later would silently shadow the earlier (its deep link\n * would resolve to the other route). A revisit by the SAME route is idempotent.\n * (With the 3-token grammar every terminal write is a full insertion — the former\n * WEAK optional-omit `??=` writes are gone with optional params.)\n */\nfunction writeTerminalRoute(node: SegmentNode, compiled: CompiledRoute): void {\n if (node.route !== undefined && node.route !== compiled) {\n throwDuplicateRoutePath(node.route.name, compiled.name);\n }\n\n node.route = compiled;\n}\n\n/**\n * #1154: whether a STATIC segment carries a code point outside ASCII (≥ U+0080).\n * A raw non-ASCII static (`café`) registers but never matches — match rejects\n * non-ASCII input and compares static keys raw. A per-code-point scan (`for…of`\n * iterates by code point, so surrogate pairs are handled).\n */\nfunction hasNonAsciiSegment(segment: string): boolean {\n // #1285: charCodeAt (code UNIT) index loop, not for-of code points. For a\n // \"has non-ASCII\" predicate the result is identical — any surrogate (≥ 0xD800) is\n // itself ≥ 0x80, so an astral char is still flagged — without the iterator +\n // code-point decoding cost per static segment of every registered route.\n for (let i = 0; i < segment.length; i++) {\n // eslint-disable-next-line unicorn/prefer-code-point -- charCodeAt (code unit) is intentional: a \"has non-ASCII\" test needs only units (a surrogate is itself >= 0x80), and it skips the code-point decoding that codePointAt does per index (#1285)\n if (segment.charCodeAt(i) >= 0x80) {\n return true;\n }\n }\n\n return false;\n}\n\nexport function insertIntoTrie(\n state: RegistrationState,\n compiled: CompiledRoute,\n fullPath: string,\n): void {\n const normalized = normalizeTrailingSlash(fullPath);\n\n if (normalized === \"/\") {\n writeTerminalRoute(state.root, compiled);\n\n return;\n }\n\n insertIntoTrieFrom(state, state.root, normalized, 1, compiled);\n}\n\nfunction insertIntoTrieFrom(\n state: RegistrationState,\n node: SegmentNode,\n path: string,\n start: number,\n compiled: CompiledRoute,\n): void {\n const length = path.length;\n\n // 3-token grammar (M1): every segment is `static | :param | *splat` — a single\n // linear walk down the trie (no optional take/skip fork, so no `visited` memo,\n // and — since a route never revisits a slot it created — no #736 `ownNodes` set).\n while (start <= length) {\n const end = path.indexOf(\"/\", start);\n const segmentEnd = end === -1 ? length : end;\n const segment = path.slice(start, segmentEnd);\n\n node = processSegment(state, node, segment);\n start = segmentEnd + 1;\n }\n\n writeTerminalRoute(node, compiled);\n}\n\nexport function insertSlashChildIntoTrie(\n state: RegistrationState,\n compiled: CompiledRoute,\n parentPath: string,\n): void {\n // #1242 §5.4: an index route (path \"/\") under a parent whose path ends in a\n // SPLAT is unreachable — `slashChildRoute` sits on the splat node, which\n // `#matchSplat`'s fast path never reads. A REQUIRED-param parent (`/users/:id`,\n // `/a/:b/c`) has a single form and its slash-child is coherent (existing\n // behaviour) — allowed. (The former OPTIONAL-param arm, #1294, is gone with\n // optional params — M1.)\n const lastSegment = parentPath.slice(parentPath.lastIndexOf(\"/\") + 1);\n\n if (lastSegment.startsWith(\"*\")) {\n throwSlashChildUnderDynamicParent(compiled.name, parentPath);\n }\n\n const node = walkTrie(state, parentPath);\n\n node.slashChildRoute = compiled;\n}\n\nfunction walkTrie(state: RegistrationState, fullPath: string): SegmentNode {\n return walkTrieFrom(state, state.root, fullPath);\n}\n\nfunction walkTrieFrom(\n state: RegistrationState,\n startNode: SegmentNode,\n path: string,\n): SegmentNode {\n const normalized = normalizeTrailingSlash(path);\n\n /* v8 ignore start -- defensive: slash-child always passes valid path */\n if (normalized === \"/\" || normalized === \"\") {\n return startNode;\n }\n /* v8 ignore stop */\n\n let node = startNode;\n let start = 1;\n const length = normalized.length;\n\n // Slash-child re-walks an already-inserted path of the same route family —\n // names always match, so the conflict guard never fires.\n\n while (start <= length) {\n const end = normalized.indexOf(\"/\", start);\n const segmentEnd = end === -1 ? length : end;\n\n /* v8 ignore start -- defensive: indexOf always returns valid index for non-empty segments */\n if (segmentEnd <= start) {\n break;\n }\n /* v8 ignore stop */\n\n const segment = normalized.slice(start, segmentEnd);\n\n node = processSegment(state, node, segment);\n start = segmentEnd + 1;\n }\n\n return node;\n}\n\nfunction processSegment(\n state: RegistrationState,\n node: SegmentNode,\n segment: string,\n): SegmentNode {\n if (segment.startsWith(\"*\")) {\n // extractParamName (via parseSegment) rejects a name-less `*` (#858) AND a\n // trailing marker (`*y:`, #1324) — the splat name shares one boundary with\n // the param branch and the route-tree gate.\n const splatName = extractParamName(segment);\n const child = ensureSplatChild(node, splatName);\n\n // Stryker disable next-line BooleanLiteral: equivalent — sets hasChildren on the node ACQUIRING a splat child; only a splat NODE's own hasChildren is read (in #matchSplat), and splat-of-splat is unreachable (splat is terminal-greedy). Proven by injection.\n node.hasChildren = true;\n\n return child;\n }\n\n if (segment.startsWith(\":\")) {\n const paramName = extractParamName(segment);\n const child = ensureParamChild(node, paramName);\n\n node.hasChildren = true;\n\n return child;\n }\n\n // The segment does not start with a marker, so it compiles as a static literal.\n // A `:`/`*` fused to a static prefix within it (`a:b`, `x:id`, `a*b`, #1050) was\n // already rejected by the per-segment grammar pass in `registerNode` — a\n // fused-marker segment never reaches this literal compilation.\n\n // #1154: a raw non-ASCII code point in a STATIC segment (`/café`, `/меню`).\n // match rejects any input byte ≥ 0x80 (`#scanPath`) AND compares static trie\n // keys raw (never percent-decoded), so such a route registers but is\n // unmatchable — `buildPath` emits `/café`, which its own `match` rejects (a dead\n // route). Reject at registration with the percent-encode workaround. A non-ASCII\n // PARAM name or constraint is unaffected (only static text is compared raw).\n if (hasNonAsciiSegment(segment)) {\n throwNonAsciiStatic(segment);\n }\n\n const key = state.options.caseSensitive ? segment : segment.toLowerCase();\n\n if (!(key in node.staticChildren)) {\n // Copy-on-write off the shared frozen EMPTY_STATIC_CHILDREN sentinel: the\n // first static child this node gains earns it a fresh mutable null-proto map.\n if (node.staticChildren === EMPTY_STATIC_CHILDREN) {\n node.staticChildren = Object.create(null) as Record<string, SegmentNode>;\n }\n\n node.staticChildren[key] = createSegmentNode();\n node.hasChildren = true;\n }\n\n return node.staticChildren[key];\n}\n"],"mappings":"wFA+BA,SAAS,EAAmB,EAAmB,EAA+B,CACxE,EAAK,QAAU,IAAA,IAAa,EAAK,QAAU,GAC7C,EAAA,wBAAwB,EAAK,MAAM,KAAM,EAAS,IAAI,EAGxD,EAAK,MAAQ,CACf,CAQA,SAAS,EAAmB,EAA0B,CAKpD,IAAK,IAAI,EAAI,EAAG,EAAI,EAAQ,OAAQ,IAElC,GAAI,EAAQ,WAAW,CAAC,GAAK,IAC3B,MAAO,GAIX,MAAO,EACT,CAEA,SAAgB,EACd,EACA,EACA,EACM,CACN,IAAM,EAAaA,EAAAA,uBAAuB,CAAQ,EAElD,GAAI,IAAe,IAAK,CACtB,EAAmB,EAAM,KAAM,CAAQ,EAEvC,MACF,CAEA,EAAmB,EAAO,EAAM,KAAM,EAAY,EAAG,CAAQ,CAC/D,CAEA,SAAS,EACP,EACA,EACA,EACA,EACA,EACM,CACN,IAAM,EAAS,EAAK,OAKpB,KAAO,GAAS,GAAQ,CACtB,IAAM,EAAM,EAAK,QAAQ,IAAK,CAAK,EAC7B,EAAa,IAAQ,GAAK,EAAS,EACnC,EAAU,EAAK,MAAM,EAAO,CAAU,EAE5C,EAAO,EAAe,EAAO,EAAM,CAAO,EAC1C,EAAQ,EAAa,CACvB,CAEA,EAAmB,EAAM,CAAQ,CACnC,CAEA,SAAgB,EACd,EACA,EACA,EACM,CAOc,EAAW,MAAM,EAAW,YAAY,GAAG,EAAI,CAErD,CAAC,CAAC,WAAW,GAAG,GAC5B,EAAA,kCAAkC,EAAS,KAAM,CAAU,EAG7D,IAAM,EAAO,EAAS,EAAO,CAAU,EAEvC,EAAK,gBAAkB,CACzB,CAEA,SAAS,EAAS,EAA0B,EAA+B,CACzE,OAAO,EAAa,EAAO,EAAM,KAAM,CAAQ,CACjD,CAEA,SAAS,EACP,EACA,EACA,EACa,CACb,IAAM,EAAaA,EAAAA,uBAAuB,CAAI,EAG9C,GAAI,IAAe,KAAO,IAAe,GACvC,OAAO,EAIT,IAAI,EAAO,EACP,EAAQ,EACN,EAAS,EAAW,OAK1B,KAAO,GAAS,GAAQ,CACtB,IAAM,EAAM,EAAW,QAAQ,IAAK,CAAK,EACnC,EAAa,IAAQ,GAAK,EAAS,EAGzC,GAAI,GAAc,EAChB,MAIF,IAAM,EAAU,EAAW,MAAM,EAAO,CAAU,EAElD,EAAO,EAAe,EAAO,EAAM,CAAO,EAC1C,EAAQ,EAAa,CACvB,CAEA,OAAO,CACT,CAEA,SAAS,EACP,EACA,EACA,EACa,CACb,GAAI,EAAQ,WAAW,GAAG,EAAG,CAK3B,IAAM,EAAQC,EAAAA,iBAAiB,EADbC,EAAAA,iBAAiB,CACU,CAAC,EAK9C,MAFA,GAAK,YAAc,GAEZ,CACT,CAEA,GAAI,EAAQ,WAAW,GAAG,EAAG,CAE3B,IAAM,EAAQC,EAAAA,iBAAiB,EADbD,EAAAA,iBAAiB,CACU,CAAC,EAI9C,MAFA,GAAK,YAAc,GAEZ,CACT,CAaI,EAAmB,CAAO,GAC5B,EAAA,oBAAoB,CAAO,EAG7B,IAAM,EAAM,EAAM,QAAQ,cAAgB,EAAU,EAAQ,YAAY,EAaxE,OAXM,KAAO,EAAK,iBAGZ,EAAK,iBAAmBE,EAAAA,wBAC1B,EAAK,eAAiB,OAAO,OAAO,IAAI,GAG1C,EAAK,eAAe,GAAOC,EAAAA,kBAAkB,EAC7C,EAAK,YAAc,IAGd,EAAK,eAAe,EAC7B"}
@@ -1,2 +0,0 @@
1
- const e=require("../parseSegment.js"),t=require("../pathUtils.js"),n=require("./errors.js");function r(t){let r=e.parseSegment(t);return(`error`in r||r.kind===`static`)&&n.throwEmptyParamName(),r.name}function i(e,r){return e.paramChild?e.paramChild.name!==r&&n.throwParamNameConflict(e.paramChild.name,r,`:`):e.paramChild={node:t.createSegmentNode(),name:r},e.paramChild.node}function a(e,r){return e.splatChild?e.splatChild.name!==r&&n.throwParamNameConflict(e.splatChild.name,r,`*`):e.splatChild={node:t.createSegmentNode(),name:r},e.splatChild.node}exports.ensureParamChild=i,exports.ensureSplatChild=a,exports.extractParamName=r;
2
- //# sourceMappingURL=trieNodes.js.map