@real-router/core 0.79.0 → 0.79.1

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 (363) hide show
  1. package/dist/cjs/Router-DGUed2F4.js +2 -0
  2. package/dist/cjs/{Router-BRuC-daj.js.map → Router-DGUed2F4.js.map} +1 -1
  3. package/dist/cjs/{Router-BG5yNYUG.d.ts → Router.d.ts} +5 -4
  4. package/dist/cjs/Router.d.ts.map +1 -0
  5. package/dist/cjs/Router.js +2 -0
  6. package/dist/cjs/Router.js.map +1 -0
  7. package/dist/cjs/RouterError.d.ts +182 -0
  8. package/dist/cjs/RouterError.d.ts.map +1 -0
  9. package/dist/cjs/RouterError.js +2 -0
  10. package/dist/cjs/RouterError.js.map +1 -0
  11. package/dist/cjs/api/cloneRouter.d.ts +86 -0
  12. package/dist/cjs/api/cloneRouter.d.ts.map +1 -0
  13. package/dist/cjs/api/cloneRouter.js +2 -0
  14. package/dist/cjs/api/cloneRouter.js.map +1 -0
  15. package/dist/cjs/api/getDependenciesApi.d.ts +7 -0
  16. package/dist/cjs/api/getDependenciesApi.d.ts.map +1 -0
  17. package/dist/cjs/api/getDependenciesApi.js +2 -0
  18. package/dist/cjs/api/getDependenciesApi.js.map +1 -0
  19. package/dist/cjs/api/getLifecycleApi.d.ts +7 -0
  20. package/dist/cjs/api/getLifecycleApi.d.ts.map +1 -0
  21. package/dist/cjs/api/getLifecycleApi.js +2 -0
  22. package/dist/cjs/api/getLifecycleApi.js.map +1 -0
  23. package/dist/cjs/api/getPluginApi.d.ts +7 -0
  24. package/dist/cjs/api/getPluginApi.d.ts.map +1 -0
  25. package/dist/cjs/api/getPluginApi.js +2 -0
  26. package/dist/cjs/api/getPluginApi.js.map +1 -0
  27. package/dist/cjs/api/getRoutesApi.d.ts +7 -0
  28. package/dist/cjs/api/getRoutesApi.d.ts.map +1 -0
  29. package/dist/cjs/api/getRoutesApi.js +2 -0
  30. package/dist/cjs/api/getRoutesApi.js.map +1 -0
  31. package/dist/cjs/api/helpers.js +2 -0
  32. package/dist/cjs/api/helpers.js.map +1 -0
  33. package/dist/cjs/api/types.d.ts +9 -0
  34. package/dist/cjs/api/types.d.ts.map +1 -0
  35. package/dist/cjs/api.d.ts +8 -105
  36. package/dist/cjs/api.js +1 -1
  37. package/dist/cjs/api.js.map +1 -1
  38. package/dist/cjs/{cloneRouter-3AOUeQ8M.js → cloneRouter-Ch25z3Z2.js} +2 -2
  39. package/dist/cjs/{cloneRouter-3AOUeQ8M.js.map → cloneRouter-Ch25z3Z2.js.map} +1 -1
  40. package/dist/cjs/constants.d.ts +25 -0
  41. package/dist/cjs/constants.d.ts.map +1 -0
  42. package/dist/cjs/constants.js +2 -0
  43. package/dist/cjs/constants.js.map +1 -0
  44. package/dist/cjs/createRouter.d.ts +23 -0
  45. package/dist/cjs/createRouter.d.ts.map +1 -0
  46. package/dist/cjs/createRouter.js +2 -0
  47. package/dist/cjs/createRouter.js.map +1 -0
  48. package/dist/cjs/engine/builder/buildTree.js +2 -0
  49. package/dist/cjs/engine/builder/buildTree.js.map +1 -0
  50. package/dist/cjs/engine/builder/computeCaches.js +2 -0
  51. package/dist/cjs/engine/builder/computeCaches.js.map +1 -0
  52. package/dist/cjs/engine/builder/createRouteTree.js +2 -0
  53. package/dist/cjs/engine/builder/createRouteTree.js.map +1 -0
  54. package/dist/cjs/engine/builder/types.d.ts +48 -0
  55. package/dist/cjs/engine/builder/types.d.ts.map +1 -0
  56. package/dist/cjs/engine/createMatcher.d.ts +29 -0
  57. package/dist/cjs/engine/createMatcher.d.ts.map +1 -0
  58. package/dist/cjs/engine/createMatcher.js +2 -0
  59. package/dist/cjs/engine/createMatcher.js.map +1 -0
  60. package/dist/cjs/engine/operations/routeTreeToDefinitions.js +2 -0
  61. package/dist/cjs/engine/operations/routeTreeToDefinitions.js.map +1 -0
  62. package/dist/cjs/engine/path-matcher/SegmentMatcher.d.ts +16 -0
  63. package/dist/cjs/engine/path-matcher/SegmentMatcher.d.ts.map +1 -0
  64. package/dist/cjs/engine/path-matcher/SegmentMatcher.js +2 -0
  65. package/dist/cjs/engine/path-matcher/SegmentMatcher.js.map +1 -0
  66. package/dist/cjs/engine/path-matcher/buildParamMeta.js +2 -0
  67. package/dist/cjs/engine/path-matcher/buildParamMeta.js.map +1 -0
  68. package/dist/cjs/engine/path-matcher/encoding.js +2 -0
  69. package/dist/cjs/engine/path-matcher/encoding.js.map +1 -0
  70. package/dist/cjs/engine/path-matcher/parseSegment.js +2 -0
  71. package/dist/cjs/engine/path-matcher/parseSegment.js.map +1 -0
  72. package/dist/cjs/engine/path-matcher/pathUtils.js +2 -0
  73. package/dist/cjs/engine/path-matcher/pathUtils.js.map +1 -0
  74. package/dist/cjs/engine/path-matcher/percentEncoding.js +2 -0
  75. package/dist/cjs/engine/path-matcher/percentEncoding.js.map +1 -0
  76. package/dist/cjs/engine/path-matcher/registration/buildParts.js +2 -0
  77. package/dist/cjs/engine/path-matcher/registration/buildParts.js.map +1 -0
  78. package/dist/cjs/engine/path-matcher/registration/context.js +2 -0
  79. package/dist/cjs/engine/path-matcher/registration/context.js.map +1 -0
  80. package/dist/cjs/engine/path-matcher/registration/errors.js +2 -0
  81. package/dist/cjs/engine/path-matcher/registration/errors.js.map +1 -0
  82. package/dist/cjs/engine/path-matcher/registration/index.js +2 -0
  83. package/dist/cjs/engine/path-matcher/registration/index.js.map +1 -0
  84. package/dist/cjs/engine/path-matcher/registration/trie.js +2 -0
  85. package/dist/cjs/engine/path-matcher/registration/trie.js.map +1 -0
  86. package/dist/cjs/engine/path-matcher/registration/trieNodes.js +2 -0
  87. package/dist/cjs/engine/path-matcher/registration/trieNodes.js.map +1 -0
  88. package/dist/cjs/{types-DaXCTxi1.d.ts → engine/path-matcher/types.d.ts} +2 -47
  89. package/dist/cjs/engine/path-matcher/types.d.ts.map +1 -0
  90. package/dist/cjs/engine/search-params/decode.js +2 -0
  91. package/dist/cjs/engine/search-params/decode.js.map +1 -0
  92. package/dist/cjs/engine/search-params/encode.js +2 -0
  93. package/dist/cjs/engine/search-params/encode.js.map +1 -0
  94. package/dist/cjs/engine/search-params/searchParams.js +2 -0
  95. package/dist/cjs/engine/search-params/searchParams.js.map +1 -0
  96. package/dist/cjs/engine/search-params/strategies/array.js +2 -0
  97. package/dist/cjs/engine/search-params/strategies/array.js.map +1 -0
  98. package/dist/cjs/engine/search-params/strategies/boolean.js +2 -0
  99. package/dist/cjs/engine/search-params/strategies/boolean.js.map +1 -0
  100. package/dist/cjs/engine/search-params/strategies/index.js +2 -0
  101. package/dist/cjs/engine/search-params/strategies/index.js.map +1 -0
  102. package/dist/cjs/engine/search-params/strategies/null.js +2 -0
  103. package/dist/cjs/engine/search-params/strategies/null.js.map +1 -0
  104. package/dist/cjs/engine/search-params/strategies/number.js +2 -0
  105. package/dist/cjs/engine/search-params/strategies/number.js.map +1 -0
  106. package/dist/cjs/engine/search-params/types.d.ts +72 -0
  107. package/dist/cjs/engine/search-params/types.d.ts.map +1 -0
  108. package/dist/cjs/engine/search-params/utils.js +2 -0
  109. package/dist/cjs/engine/search-params/utils.js.map +1 -0
  110. package/dist/cjs/engine/validation/route-batch.d.ts +38 -0
  111. package/dist/cjs/engine/validation/route-batch.d.ts.map +1 -0
  112. package/dist/cjs/engine/validation/route-batch.js +2 -0
  113. package/dist/cjs/engine/validation/route-batch.js.map +1 -0
  114. package/dist/cjs/engine/validation/routes.js +2 -0
  115. package/dist/cjs/engine/validation/routes.js.map +1 -0
  116. package/dist/cjs/foundation/event-emitter/EventEmitter.js +2 -0
  117. package/dist/cjs/foundation/event-emitter/EventEmitter.js.map +1 -0
  118. package/dist/cjs/foundation/fsm/fsm.js +2 -0
  119. package/dist/cjs/foundation/fsm/fsm.js.map +1 -0
  120. package/dist/cjs/foundation/logger/RouterLogger.js +2 -0
  121. package/dist/cjs/foundation/logger/RouterLogger.js.map +1 -0
  122. package/dist/cjs/foundation/logger/constants.js +2 -0
  123. package/dist/cjs/foundation/logger/constants.js.map +1 -0
  124. package/dist/cjs/getNavigator.d.ts +6 -0
  125. package/dist/cjs/getNavigator.d.ts.map +1 -0
  126. package/dist/cjs/getNavigator.js +2 -0
  127. package/dist/cjs/getNavigator.js.map +1 -0
  128. package/dist/cjs/guards.js +2 -0
  129. package/dist/cjs/guards.js.map +1 -0
  130. package/dist/cjs/helpers.js +2 -0
  131. package/dist/cjs/helpers.js.map +1 -0
  132. package/dist/cjs/index.d.ts +17 -239
  133. package/dist/cjs/index.js +1 -1
  134. package/dist/cjs/internals.d.ts +99 -0
  135. package/dist/cjs/internals.d.ts.map +1 -0
  136. package/dist/cjs/internals.js +2 -0
  137. package/dist/cjs/internals.js.map +1 -0
  138. package/dist/cjs/namespaces/DependenciesNamespace/dependenciesStore.d.ts +10 -0
  139. package/dist/cjs/namespaces/DependenciesNamespace/dependenciesStore.d.ts.map +1 -0
  140. package/dist/cjs/namespaces/DependenciesNamespace/dependenciesStore.js +2 -0
  141. package/dist/cjs/namespaces/DependenciesNamespace/dependenciesStore.js.map +1 -0
  142. package/dist/cjs/namespaces/EventBusNamespace/EventBusNamespace.js +2 -0
  143. package/dist/cjs/namespaces/EventBusNamespace/EventBusNamespace.js.map +1 -0
  144. package/dist/cjs/namespaces/NavigationNamespace/NavigationNamespace.js +2 -0
  145. package/dist/cjs/namespaces/NavigationNamespace/NavigationNamespace.js.map +1 -0
  146. package/dist/cjs/namespaces/NavigationNamespace/constants.js +2 -0
  147. package/dist/cjs/namespaces/NavigationNamespace/constants.js.map +1 -0
  148. package/dist/cjs/namespaces/NavigationNamespace/transition/completeTransition.js +2 -0
  149. package/dist/cjs/namespaces/NavigationNamespace/transition/completeTransition.js.map +1 -0
  150. package/dist/cjs/namespaces/NavigationNamespace/transition/errorHandling.js +2 -0
  151. package/dist/cjs/namespaces/NavigationNamespace/transition/errorHandling.js.map +1 -0
  152. package/dist/cjs/namespaces/NavigationNamespace/transition/guardPhase.js +2 -0
  153. package/dist/cjs/namespaces/NavigationNamespace/transition/guardPhase.js.map +1 -0
  154. package/dist/cjs/namespaces/OptionsNamespace/OptionsNamespace.js +2 -0
  155. package/dist/cjs/namespaces/OptionsNamespace/OptionsNamespace.js.map +1 -0
  156. package/dist/cjs/namespaces/OptionsNamespace/constants.js +2 -0
  157. package/dist/cjs/namespaces/OptionsNamespace/constants.js.map +1 -0
  158. package/dist/cjs/namespaces/OptionsNamespace/helpers.js +2 -0
  159. package/dist/cjs/namespaces/OptionsNamespace/helpers.js.map +1 -0
  160. package/dist/cjs/namespaces/OptionsNamespace/validators.js +2 -0
  161. package/dist/cjs/namespaces/OptionsNamespace/validators.js.map +1 -0
  162. package/dist/cjs/namespaces/PluginsNamespace/PluginsNamespace.js +2 -0
  163. package/dist/cjs/namespaces/PluginsNamespace/PluginsNamespace.js.map +1 -0
  164. package/dist/cjs/namespaces/PluginsNamespace/constants.js +2 -0
  165. package/dist/cjs/namespaces/PluginsNamespace/constants.js.map +1 -0
  166. package/dist/cjs/namespaces/PluginsNamespace/validators.js +2 -0
  167. package/dist/cjs/namespaces/PluginsNamespace/validators.js.map +1 -0
  168. package/dist/cjs/namespaces/RouteLifecycleNamespace/RouteLifecycleNamespace.d.ts +174 -0
  169. package/dist/cjs/namespaces/RouteLifecycleNamespace/RouteLifecycleNamespace.d.ts.map +1 -0
  170. package/dist/cjs/namespaces/RouteLifecycleNamespace/RouteLifecycleNamespace.js +2 -0
  171. package/dist/cjs/namespaces/RouteLifecycleNamespace/RouteLifecycleNamespace.js.map +1 -0
  172. package/dist/cjs/namespaces/RouteLifecycleNamespace/types.d.ts +17 -0
  173. package/dist/cjs/namespaces/RouteLifecycleNamespace/types.d.ts.map +1 -0
  174. package/dist/cjs/namespaces/RouterLifecycleNamespace/RouterLifecycleNamespace.js +2 -0
  175. package/dist/cjs/namespaces/RouterLifecycleNamespace/RouterLifecycleNamespace.js.map +1 -0
  176. package/dist/cjs/namespaces/RouterLifecycleNamespace/constants.js +2 -0
  177. package/dist/cjs/namespaces/RouterLifecycleNamespace/constants.js.map +1 -0
  178. package/dist/cjs/namespaces/RoutesNamespace/RoutesNamespace.js +2 -0
  179. package/dist/cjs/namespaces/RoutesNamespace/RoutesNamespace.js.map +1 -0
  180. package/dist/cjs/namespaces/RoutesNamespace/constants.js +2 -0
  181. package/dist/cjs/namespaces/RoutesNamespace/constants.js.map +1 -0
  182. package/dist/cjs/namespaces/RoutesNamespace/forwardChain.d.ts +5 -0
  183. package/dist/cjs/namespaces/RoutesNamespace/forwardChain.d.ts.map +1 -0
  184. package/dist/cjs/namespaces/RoutesNamespace/forwardChain.js +2 -0
  185. package/dist/cjs/namespaces/RoutesNamespace/forwardChain.js.map +1 -0
  186. package/dist/cjs/namespaces/RoutesNamespace/helpers.js +2 -0
  187. package/dist/cjs/namespaces/RoutesNamespace/helpers.js.map +1 -0
  188. package/dist/cjs/namespaces/RoutesNamespace/routeGuards.js +2 -0
  189. package/dist/cjs/namespaces/RoutesNamespace/routeGuards.js.map +1 -0
  190. package/dist/cjs/namespaces/RoutesNamespace/routesStore.d.ts +42 -0
  191. package/dist/cjs/namespaces/RoutesNamespace/routesStore.d.ts.map +1 -0
  192. package/dist/cjs/namespaces/RoutesNamespace/routesStore.js +2 -0
  193. package/dist/cjs/namespaces/RoutesNamespace/routesStore.js.map +1 -0
  194. package/dist/cjs/namespaces/RoutesNamespace/types.d.ts +57 -0
  195. package/dist/cjs/namespaces/RoutesNamespace/types.d.ts.map +1 -0
  196. package/dist/cjs/namespaces/StateNamespace/StateNamespace.js +2 -0
  197. package/dist/cjs/namespaces/StateNamespace/StateNamespace.js.map +1 -0
  198. package/dist/cjs/namespaces/StateNamespace/helpers.js +2 -0
  199. package/dist/cjs/namespaces/StateNamespace/helpers.js.map +1 -0
  200. package/dist/cjs/routerFSM.js +2 -0
  201. package/dist/cjs/routerFSM.js.map +1 -0
  202. package/dist/cjs/stateMetaStore.js +2 -0
  203. package/dist/cjs/stateMetaStore.js.map +1 -0
  204. package/dist/cjs/transitionPath.js +2 -0
  205. package/dist/cjs/transitionPath.js.map +1 -0
  206. package/dist/cjs/{RouterValidator-CZ8kPkrI.d.ts → types/RouterValidator.d.ts} +2 -2
  207. package/dist/cjs/types/RouterValidator.d.ts.map +1 -0
  208. package/dist/cjs/types/api.d.ts +184 -0
  209. package/dist/cjs/types/api.d.ts.map +1 -0
  210. package/dist/cjs/types/base.d.ts +86 -0
  211. package/dist/cjs/types/base.d.ts.map +1 -0
  212. package/dist/cjs/types/constants.d.ts +72 -0
  213. package/dist/cjs/types/constants.d.ts.map +1 -0
  214. package/dist/cjs/types/internal.d.ts +9 -0
  215. package/dist/cjs/types/internal.d.ts.map +1 -0
  216. package/dist/cjs/types/limits.d.ts +45 -0
  217. package/dist/cjs/types/limits.d.ts.map +1 -0
  218. package/dist/cjs/types/route-node-types.d.ts +46 -0
  219. package/dist/cjs/types/route-node-types.d.ts.map +1 -0
  220. package/dist/cjs/types/router.d.ts +313 -0
  221. package/dist/cjs/types/router.d.ts.map +1 -0
  222. package/dist/cjs/types/tree-changed.d.ts +55 -0
  223. package/dist/cjs/types/tree-changed.d.ts.map +1 -0
  224. package/dist/cjs/types.d.ts +208 -2
  225. package/dist/cjs/types.d.ts.map +1 -0
  226. package/dist/cjs/utils/createRequestScope.d.ts +111 -0
  227. package/dist/cjs/utils/createRequestScope.d.ts.map +1 -0
  228. package/dist/cjs/utils/createRequestScope.js +2 -0
  229. package/dist/cjs/utils/createRequestScope.js.map +1 -0
  230. package/dist/cjs/utils/getStaticPaths.d.ts +7 -0
  231. package/dist/cjs/utils/getStaticPaths.d.ts.map +1 -0
  232. package/dist/cjs/utils/getStaticPaths.js +2 -0
  233. package/dist/cjs/utils/getStaticPaths.js.map +1 -0
  234. package/dist/cjs/utils/hydrateRouter.d.ts +69 -0
  235. package/dist/cjs/utils/hydrateRouter.d.ts.map +1 -0
  236. package/dist/cjs/utils/hydrateRouter.js +2 -0
  237. package/dist/cjs/utils/hydrateRouter.js.map +1 -0
  238. package/dist/cjs/utils/serializeRouterState.d.ts +84 -0
  239. package/dist/cjs/utils/serializeRouterState.d.ts.map +1 -0
  240. package/dist/cjs/utils/serializeRouterState.js +2 -0
  241. package/dist/cjs/utils/serializeRouterState.js.map +1 -0
  242. package/dist/cjs/utils/serializeState.d.ts +50 -0
  243. package/dist/cjs/utils/serializeState.d.ts.map +1 -0
  244. package/dist/cjs/utils/serializeState.js +2 -0
  245. package/dist/cjs/utils/serializeState.js.map +1 -0
  246. package/dist/cjs/utils.d.ts +5 -1
  247. package/dist/cjs/utils.js +1 -1
  248. package/dist/cjs/validation.d.ts +6 -518
  249. package/dist/cjs/wiring/wireNamespaces.js +2 -0
  250. package/dist/cjs/wiring/wireNamespaces.js.map +1 -0
  251. package/dist/esm/Router-Bk6PfSE8.mjs +2 -0
  252. package/dist/esm/{Router-DikzugaD.mjs.map → Router-Bk6PfSE8.mjs.map} +1 -1
  253. package/dist/esm/{Router-Cy75qbgU.d.mts → Router.d.mts} +5 -4
  254. package/dist/esm/Router.d.mts.map +1 -0
  255. package/dist/esm/RouterError.d.mts +182 -0
  256. package/dist/esm/RouterError.d.mts.map +1 -0
  257. package/dist/esm/api/cloneRouter.d.mts +86 -0
  258. package/dist/esm/api/cloneRouter.d.mts.map +1 -0
  259. package/dist/esm/api/getDependenciesApi.d.mts +7 -0
  260. package/dist/esm/api/getDependenciesApi.d.mts.map +1 -0
  261. package/dist/esm/api/getLifecycleApi.d.mts +7 -0
  262. package/dist/esm/api/getLifecycleApi.d.mts.map +1 -0
  263. package/dist/esm/api/getPluginApi.d.mts +7 -0
  264. package/dist/esm/api/getPluginApi.d.mts.map +1 -0
  265. package/dist/esm/api/getRoutesApi.d.mts +7 -0
  266. package/dist/esm/api/getRoutesApi.d.mts.map +1 -0
  267. package/dist/esm/api/types.d.mts +9 -0
  268. package/dist/esm/api/types.d.mts.map +1 -0
  269. package/dist/esm/api.d.mts +8 -105
  270. package/dist/esm/api.mjs +1 -1
  271. package/dist/esm/api.mjs.map +1 -1
  272. package/dist/esm/{cloneRouter-CAh5vA3l.mjs → cloneRouter-D4rnqmi9.mjs} +2 -2
  273. package/dist/esm/{cloneRouter-CAh5vA3l.mjs.map → cloneRouter-D4rnqmi9.mjs.map} +1 -1
  274. package/dist/esm/constants.d.mts +25 -0
  275. package/dist/esm/constants.d.mts.map +1 -0
  276. package/dist/esm/createRouter.d.mts +23 -0
  277. package/dist/esm/createRouter.d.mts.map +1 -0
  278. package/dist/esm/engine/builder/types.d.mts +48 -0
  279. package/dist/esm/engine/builder/types.d.mts.map +1 -0
  280. package/dist/esm/engine/createMatcher.d.mts +29 -0
  281. package/dist/esm/engine/createMatcher.d.mts.map +1 -0
  282. package/dist/esm/engine/path-matcher/SegmentMatcher.d.mts +16 -0
  283. package/dist/esm/engine/path-matcher/SegmentMatcher.d.mts.map +1 -0
  284. package/dist/esm/{types-DaXCTxi1.d.mts → engine/path-matcher/types.d.mts} +2 -47
  285. package/dist/esm/engine/path-matcher/types.d.mts.map +1 -0
  286. package/dist/esm/engine/search-params/types.d.mts +72 -0
  287. package/dist/esm/engine/search-params/types.d.mts.map +1 -0
  288. package/dist/esm/engine/validation/route-batch.d.mts +38 -0
  289. package/dist/esm/engine/validation/route-batch.d.mts.map +1 -0
  290. package/dist/esm/getNavigator.d.mts +6 -0
  291. package/dist/esm/getNavigator.d.mts.map +1 -0
  292. package/dist/esm/index.d.mts +17 -239
  293. package/dist/esm/index.mjs +1 -1
  294. package/dist/esm/internals.d.mts +99 -0
  295. package/dist/esm/internals.d.mts.map +1 -0
  296. package/dist/esm/namespaces/DependenciesNamespace/dependenciesStore.d.mts +10 -0
  297. package/dist/esm/namespaces/DependenciesNamespace/dependenciesStore.d.mts.map +1 -0
  298. package/dist/esm/namespaces/RouteLifecycleNamespace/RouteLifecycleNamespace.d.mts +174 -0
  299. package/dist/esm/namespaces/RouteLifecycleNamespace/RouteLifecycleNamespace.d.mts.map +1 -0
  300. package/dist/esm/namespaces/RouteLifecycleNamespace/types.d.mts +17 -0
  301. package/dist/esm/namespaces/RouteLifecycleNamespace/types.d.mts.map +1 -0
  302. package/dist/esm/namespaces/RoutesNamespace/forwardChain.d.mts +5 -0
  303. package/dist/esm/namespaces/RoutesNamespace/forwardChain.d.mts.map +1 -0
  304. package/dist/esm/namespaces/RoutesNamespace/routesStore.d.mts +42 -0
  305. package/dist/esm/namespaces/RoutesNamespace/routesStore.d.mts.map +1 -0
  306. package/dist/esm/namespaces/RoutesNamespace/types.d.mts +57 -0
  307. package/dist/esm/namespaces/RoutesNamespace/types.d.mts.map +1 -0
  308. package/dist/esm/{RouterValidator-CZ8kPkrI.d.mts → types/RouterValidator.d.mts} +2 -2
  309. package/dist/esm/types/RouterValidator.d.mts.map +1 -0
  310. package/dist/esm/types/api.d.mts +184 -0
  311. package/dist/esm/types/api.d.mts.map +1 -0
  312. package/dist/esm/types/base.d.mts +86 -0
  313. package/dist/esm/types/base.d.mts.map +1 -0
  314. package/dist/esm/types/constants.d.mts +72 -0
  315. package/dist/esm/types/constants.d.mts.map +1 -0
  316. package/dist/esm/types/internal.d.mts +9 -0
  317. package/dist/esm/types/internal.d.mts.map +1 -0
  318. package/dist/esm/types/limits.d.mts +45 -0
  319. package/dist/esm/types/limits.d.mts.map +1 -0
  320. package/dist/esm/types/route-node-types.d.mts +46 -0
  321. package/dist/esm/types/route-node-types.d.mts.map +1 -0
  322. package/dist/esm/types/router.d.mts +313 -0
  323. package/dist/esm/types/router.d.mts.map +1 -0
  324. package/dist/esm/types/tree-changed.d.mts +55 -0
  325. package/dist/esm/types/tree-changed.d.mts.map +1 -0
  326. package/dist/esm/types.d.mts +208 -2
  327. package/dist/esm/types.d.mts.map +1 -0
  328. package/dist/esm/utils/createRequestScope.d.mts +111 -0
  329. package/dist/esm/utils/createRequestScope.d.mts.map +1 -0
  330. package/dist/esm/utils/getStaticPaths.d.mts +7 -0
  331. package/dist/esm/utils/getStaticPaths.d.mts.map +1 -0
  332. package/dist/esm/utils/hydrateRouter.d.mts +69 -0
  333. package/dist/esm/utils/hydrateRouter.d.mts.map +1 -0
  334. package/dist/esm/utils/serializeRouterState.d.mts +84 -0
  335. package/dist/esm/utils/serializeRouterState.d.mts.map +1 -0
  336. package/dist/esm/utils/serializeState.d.mts +50 -0
  337. package/dist/esm/utils/serializeState.d.mts.map +1 -0
  338. package/dist/esm/utils.d.mts +5 -1
  339. package/dist/esm/utils.mjs +1 -1
  340. package/dist/esm/validation.d.mts +6 -518
  341. package/package.json +2 -2
  342. package/dist/cjs/Router-BG5yNYUG.d.ts.map +0 -1
  343. package/dist/cjs/Router-BRuC-daj.js +0 -2
  344. package/dist/cjs/RouterValidator-CZ8kPkrI.d.ts.map +0 -1
  345. package/dist/cjs/api.d.ts.map +0 -1
  346. package/dist/cjs/index-CAnGQpnz.d.ts +0 -974
  347. package/dist/cjs/index-CAnGQpnz.d.ts.map +0 -1
  348. package/dist/cjs/index-DDrryY2U.d.ts +0 -309
  349. package/dist/cjs/index-DDrryY2U.d.ts.map +0 -1
  350. package/dist/cjs/index.d.ts.map +0 -1
  351. package/dist/cjs/types-DaXCTxi1.d.ts.map +0 -1
  352. package/dist/cjs/validation.d.ts.map +0 -1
  353. package/dist/esm/Router-Cy75qbgU.d.mts.map +0 -1
  354. package/dist/esm/Router-DikzugaD.mjs +0 -2
  355. package/dist/esm/RouterValidator-CZ8kPkrI.d.mts.map +0 -1
  356. package/dist/esm/api.d.mts.map +0 -1
  357. package/dist/esm/index-CAnGQpnz.d.mts +0 -974
  358. package/dist/esm/index-CAnGQpnz.d.mts.map +0 -1
  359. package/dist/esm/index-DnSU6huX.d.mts +0 -309
  360. package/dist/esm/index-DnSU6huX.d.mts.map +0 -1
  361. package/dist/esm/index.d.mts.map +0 -1
  362. package/dist/esm/types-DaXCTxi1.d.mts.map +0 -1
  363. package/dist/esm/validation.d.mts.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"Router-BRuC-daj.js","names":["DEFAULT_LIMITS","EMPTY_PARAMS","#callbacks","#dispatching","#onListenerError","#onListenerWarn","#limits","#warnedEvents","#invokeIsolated","#callListener","#config","#currentThreshold","#writeLog","#writeToConsole","#invokeCallback","#inCallback","#reportError","DEFAULT_LIMITS","EMPTY_PARAMS","DEFAULT_LIMITS","LONE_SURROGATE_RGX","splitPathSegments","parseSegment","parseSegment","EMPTY_PARAM_META","splitPathSegments","parseSegment","INVALID_QUERY_NAME_RGX","#options","#root","#routesByName","#staticCache","#prepared","#caseSensitive","#decode","#rootQueryParams","#preparePath","#checkTrailingSlash","#buildResult","#traverse","#decodeParams","#buildUrlPath","#applyTrailingSlash","#buildQueryStringForBuild","#scanPath","#scanTruncated","#mergeQueryParams","#traverseFrom","#matchSplat","buildParamMeta","EMPTY_PARAM_META","encodeValue","#options","#frozenState","#previousState","#deps","EMPTY_PARAMS","PLUGINS_CONST","EVENTS_CONST","#plugins","#unsubscribes","#deps","#startPlugin","#deduplicateBatch","#definitionActivateFactories","#externalActivateFactories","#definitionDeactivateFactories","#externalDeactivateFactories","#canDeactivateFunctions","#canActivateFunctions","#functionsTuple","#deps","#getFactoryMaps","#registerHandler","#clearGuard","#recompileSlot","#checkGuardSync","#store","#deps","#getBuildPathOptions","#mergeDefaultParams","#resolveDynamicForward","#cachedBuildPathOpts","#cachedOptionsSource","#deps","#executeNavigation","#abortPreviousNavigation","EMPTY_PARAMS","#currentController","#navigationId","#handleNoGuardsLeave","#finishAsyncNavigation","#cleanupController","#handleNavigateError","#deps","#context","#transitions","#listeners","#state","#currentTransitions","#actions","#listenerCount","#fsm","#emitter","#abortController","#leaveListeners","#currentToState","#setupFSMActions","#dispatchDepth","#pendingToState","#pendingFromState","#pendingError","#pendingCancelReason","#checkListenerThreshold","#getValidator","#emitPendingError","getInternals","#options","#limits","#dependenciesStore","#state","#routes","#routeLifecycle","#plugins","#navigation","#lifecycle","#eventBus","#onSuppressedNavigateError","#onSuppressedStartError","#isExpectedRejection","createBinaryInterceptable","EMPTY_PARAMS","createInterceptable","#assertNotReentrant","#suppressUnhandledRejection","getInternals","#unwindFailedStart","#markDisposed"],"sources":["../../src/constants.ts","../../src/foundation/event-emitter/EventEmitter.ts","../../src/foundation/logger/constants.ts","../../src/foundation/logger/RouterLogger.ts","../../src/guards.ts","../../src/helpers.ts","../../src/namespaces/DependenciesNamespace/dependenciesStore.ts","../../src/engine/builder/buildTree.ts","../../src/engine/path-matcher/encoding.ts","../../src/engine/path-matcher/pathUtils.ts","../../src/engine/path-matcher/percentEncoding.ts","../../src/engine/path-matcher/registration/context.ts","../../src/engine/path-matcher/registration/buildParts.ts","../../src/engine/path-matcher/registration/errors.ts","../../src/engine/path-matcher/registration/trieNodes.ts","../../src/engine/path-matcher/registration/trie.ts","../../src/engine/path-matcher/registration/index.ts","../../src/engine/path-matcher/SegmentMatcher.ts","../../src/engine/builder/computeCaches.ts","../../src/engine/builder/createRouteTree.ts","../../src/engine/operations/routeTreeToDefinitions.ts","../../src/engine/search-params/decode.ts","../../src/engine/search-params/utils.ts","../../src/engine/search-params/strategies/array.ts","../../src/engine/search-params/strategies/boolean.ts","../../src/engine/search-params/strategies/null.ts","../../src/engine/search-params/strategies/number.ts","../../src/engine/search-params/strategies/index.ts","../../src/engine/search-params/encode.ts","../../src/engine/search-params/searchParams.ts","../../src/engine/createMatcher.ts","../../src/namespaces/OptionsNamespace/constants.ts","../../src/namespaces/OptionsNamespace/helpers.ts","../../src/namespaces/OptionsNamespace/validators.ts","../../src/namespaces/OptionsNamespace/OptionsNamespace.ts","../../src/namespaces/StateNamespace/helpers.ts","../../src/stateMetaStore.ts","../../src/namespaces/StateNamespace/StateNamespace.ts","../../src/namespaces/PluginsNamespace/constants.ts","../../src/namespaces/PluginsNamespace/validators.ts","../../src/namespaces/PluginsNamespace/PluginsNamespace.ts","../../src/namespaces/RouteLifecycleNamespace/RouteLifecycleNamespace.ts","../../src/namespaces/RoutesNamespace/constants.ts","../../src/namespaces/RoutesNamespace/helpers.ts","../../src/namespaces/RoutesNamespace/forwardChain.ts","../../src/namespaces/RoutesNamespace/routesStore.ts","../../src/transitionPath.ts","../../src/namespaces/RoutesNamespace/RoutesNamespace.ts","../../src/RouterError.ts","../../src/namespaces/NavigationNamespace/constants.ts","../../src/namespaces/NavigationNamespace/transition/completeTransition.ts","../../src/namespaces/NavigationNamespace/transition/errorHandling.ts","../../src/namespaces/NavigationNamespace/transition/guardPhase.ts","../../src/namespaces/NavigationNamespace/NavigationNamespace.ts","../../src/namespaces/RouterLifecycleNamespace/RouterLifecycleNamespace.ts","../../src/foundation/fsm/fsm.ts","../../src/routerFSM.ts","../../src/namespaces/EventBusNamespace/EventBusNamespace.ts","../../src/namespaces/RouterLifecycleNamespace/constants.ts","../../src/wiring/wireNamespaces.ts","../../src/Router.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});\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\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","import type {\n EventEmitterLimits,\n EventEmitterOptions,\n Unsubscribe,\n} from \"./types\";\n\nconst DEFAULT_LIMITS: EventEmitterLimits = {\n maxListeners: 0,\n warnListeners: 0,\n};\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\ntype AnyCallback = Function;\n\n/**\n * Generic typed event emitter with listener limits, duplicate detection,\n * re-entrancy coalescing, and per-listener error isolation.\n *\n * All limits are opt-in via constructor options.\n */\nexport class EventEmitter<TEventMap extends Record<string, unknown[]>> {\n readonly #callbacks = new Map<string, Set<AnyCallback>>();\n // Names currently being dispatched. A re-entrant `emit` of an event already\n // on this set is coalesced to a no-op (see `emit`), so an event can never\n // re-enter its own dispatch — recursion is structurally impossible (depth ≤ 1)\n // with no depth bound and no stack-overflow path (#1033).\n readonly #dispatching = new Set<string>();\n #warnedEvents: Set<string> | null = null;\n #limits: EventEmitterLimits = DEFAULT_LIMITS;\n readonly #onListenerError:\n ((eventName: string, error: unknown) => void) | null;\n readonly #onListenerWarn: ((eventName: string, count: number) => void) | null;\n\n constructor(options?: EventEmitterOptions) {\n if (options?.limits) {\n this.#limits = options.limits;\n }\n\n this.#onListenerError = options?.onListenerError ?? null;\n this.#onListenerWarn = options?.onListenerWarn ?? null;\n }\n\n /**\n * Validates that a callback is a function.\n */\n static validateCallback(\n cb: unknown,\n eventName: string,\n // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n ): asserts cb is Function {\n if (typeof cb !== \"function\") {\n throw new TypeError(\n `Expected callback to be a function for event ${eventName}`,\n );\n }\n }\n\n /**\n * Replaces current limits with the provided limits.\n */\n setLimits(limits: EventEmitterLimits): void {\n this.#limits = limits;\n }\n\n /**\n * Adds an event listener and returns an unsubscribe function.\n * Throws on duplicate listeners or when maxListeners is reached.\n *\n * Registration is atomic (validate-before-mutate, #1358): every rejection\n * check runs against the CURRENT record (read once, never created early), the\n * advisory warn hook runs before any mutation, and the record is created +\n * the listener added only after all checks pass. So a throw — a rejected\n * limit, or a throwing `onListenerWarn` — leaves NO side-effect behind: no\n * orphaned empty record (#1167) and no burnt warn latch (#1168).\n */\n on<E extends keyof TEventMap & string>(\n eventName: E,\n cb: (...args: TEventMap[E]) => void,\n ): Unsubscribe {\n const existing = this.#callbacks.get(eventName);\n const size = existing?.size ?? 0;\n\n if (existing?.has(cb)) {\n throw new Error(`Duplicate listener for \"${eventName}\"`);\n }\n\n const { maxListeners, warnListeners } = this.#limits;\n\n // Enforce the hard limit before warning, so onListenerWarn never fires for\n // a registration that then throws (the warnListeners === maxListeners case).\n if (maxListeners !== 0 && size >= maxListeners) {\n throw new Error(\n `Listener limit (${maxListeners}) reached for \"${eventName}\"`,\n );\n }\n\n // Warn at most once per emitter+event, using the PRE-add size. The hook is\n // invoked first and the latch set only after it returns without throwing, so\n // a throwing hook fails the registration atomically and leaves the latch\n // unspent — the next (W+1)th registration warns as documented (#1168). The\n // latch keeps the advisory hint \"exactly once\" across off/on churn around\n // the threshold; reset by clearAll() or by removing the last listener.\n if (\n warnListeners !== 0 &&\n size === warnListeners &&\n this.#onListenerWarn !== null\n ) {\n this.#warnedEvents ??= new Set();\n\n if (!this.#warnedEvents.has(eventName)) {\n this.#onListenerWarn(eventName, warnListeners);\n this.#warnedEvents.add(eventName);\n }\n }\n\n // Mutate last — create the record only now, so a rejected registration\n // above never strands an empty record (#1167).\n let set = existing;\n\n if (set === undefined) {\n set = new Set();\n this.#callbacks.set(eventName, set);\n }\n\n set.add(cb);\n\n return () => {\n this.off(eventName, cb);\n };\n }\n\n /**\n * Removes an event listener.\n */\n off<E extends keyof TEventMap & string>(\n eventName: E,\n cb: (...args: TEventMap[E]) => void,\n ): void {\n const set = this.#callbacks.get(eventName);\n\n if (!set) {\n return;\n }\n\n set.delete(cb);\n\n if (set.size === 0) {\n // Release per-event records once the last listener is gone, so consumers\n // with dynamic event names don't accumulate empty Sets unbounded\n // (listenerCount stays 0 either way, masking the growth). See #750.\n this.#callbacks.delete(eventName);\n this.#warnedEvents?.delete(eventName);\n }\n }\n\n /**\n * Emits an event, calling all registered listeners with the provided args.\n *\n * Uses snapshot iteration — listeners added/removed during emit don't affect\n * the current invocation. Per-listener errors are caught and reported via the\n * `onListenerError` callback; other listeners still run.\n *\n * Re-entrant emit is coalesced: emitting an event that is already being\n * dispatched (a listener that synchronously re-emits the same event) is a\n * no-op, so dispatch never recurses into itself (#1033).\n *\n * Uses explicit params instead of rest params to avoid V8 array materialization.\n * Extra undefined args are harmless — JS functions ignore extra arguments.\n */\n emit(\n eventName: keyof TEventMap & string,\n arg1?: unknown,\n arg2?: unknown,\n arg3?: unknown,\n arg4?: unknown,\n ): void {\n const set = this.#callbacks.get(eventName);\n\n if (!set || set.size === 0) {\n return;\n }\n\n // Coalesce a re-entrant emit of an in-flight event (depth ≤ 1, #1033).\n if (this.#dispatching.has(eventName)) {\n return;\n }\n\n // arguments.length is O(1) in V8 strict mode — no deopt\n const argc = arguments.length - 1;\n\n this.#dispatching.add(eventName);\n\n try {\n // Single-listener fast path — skip the [...set] snapshot allocation.\n if (set.size === 1) {\n const [cb] = set;\n\n this.#invokeIsolated(eventName, cb, argc, arg1, arg2, arg3, arg4);\n } else {\n const listeners = [...set];\n\n for (const cb of listeners) {\n this.#invokeIsolated(eventName, cb, argc, arg1, arg2, arg3, arg4);\n }\n }\n } finally {\n this.#dispatching.delete(eventName);\n }\n }\n\n /**\n * Removes all listeners and resets the warn latch.\n *\n * Does NOT touch `#dispatching`: the in-flight coalesce guard is owned by the\n * active `emit` frame (added when dispatch starts, self-released in that\n * frame's `finally`). Clearing it here would lift the guard for a live frame\n * when `clearAll()` runs from inside a listener, so a re-entrant same-event\n * emit would no longer coalesce and would re-enter — violating the depth-≤-1\n * contract (#1164). The guard self-releases; `clearAll()` has no business\n * sweeping state owned by active emit frames.\n */\n clearAll(): void {\n this.#callbacks.clear();\n this.#warnedEvents = null;\n }\n\n /**\n * Returns the number of listeners for the given event.\n */\n listenerCount(eventName: keyof TEventMap & string): number {\n return this.#callbacks.get(eventName)?.size ?? 0;\n }\n\n /**\n * Returns whether the given event is currently being dispatched (an `emit`\n * for it is on the stack). Single source of truth for \"is this event\n * in-flight\" — consumers read it to reject re-entrant operations that would\n * trigger such an emit (the emit itself would be coalesced regardless).\n */\n isDispatching(eventName: keyof TEventMap & string): boolean {\n return this.#dispatching.has(eventName);\n }\n\n // ===========================================================================\n // Private methods\n // ===========================================================================\n\n /**\n * Calls a listener with the correct number of arguments.\n * Dispatches by argc to preserve exact call semantics.\n */\n #invokeIsolated(\n eventName: keyof TEventMap & string,\n cb: AnyCallback,\n argc: number,\n arg1: unknown,\n arg2: unknown,\n arg3: unknown,\n arg4: unknown,\n ): void {\n try {\n const result = this.#callListener(cb, argc, arg1, arg2, arg3, arg4);\n\n // A listener typed `=> void` may still return a Promise at runtime (an\n // async hook or any-cast misuse). The sync `catch` below cannot see its\n // rejection, so route it to the same `#onListenerError` sink — otherwise\n // it escapes as a Node `unhandledRejection` (fatal under\n // `--unhandled-rejections=strict`, the Node 22+ default). Centralised here\n // so every listener kind (plugin hooks, `subscribe`, …) is isolated\n // symmetrically (#1412; `subscribe`'s per-site #944 wrapper folds in).\n if (\n result !== null &&\n result !== undefined &&\n typeof (result as PromiseLike<unknown>).then === \"function\"\n ) {\n Promise.resolve(result as PromiseLike<unknown>).catch(\n (error: unknown) => {\n this.#onListenerError?.(eventName, error);\n },\n );\n }\n } catch (error) {\n this.#onListenerError?.(eventName, error);\n }\n }\n\n #callListener(\n cb: AnyCallback,\n argc: number,\n arg1: unknown,\n arg2: unknown,\n arg3: unknown,\n arg4: unknown,\n ): unknown {\n switch (argc) {\n case 0: {\n return (cb as () => unknown)();\n }\n case 1: {\n return (cb as (a: unknown) => unknown)(arg1);\n }\n case 2: {\n return (cb as (a: unknown, b: unknown) => unknown)(arg1, arg2);\n }\n case 3: {\n return (cb as (a: unknown, b: unknown, c: unknown) => unknown)(\n arg1,\n arg2,\n arg3,\n );\n }\n default: {\n return (\n cb as (a: unknown, b: unknown, c: unknown, d: unknown) => unknown\n )(arg1, arg2, arg3, arg4);\n }\n }\n }\n\n // (record creation is inlined into `on()` so a rejected registration never\n // creates one — see the atomicity note there, #1167/#1358.)\n}\n","import type { LogLevel, LogLevelConfig } from \"../../types\";\n\n/**\n * Numeric mapping for log message severity levels.\n *\n * Maps each severity level to a numeric value for threshold comparison.\n * Lower values = lower severity, higher values = higher severity.\n *\n * Used internally to determine if a message should be filtered based on\n * the configured threshold level.\n *\n * Mapping:\n * - `log`: 0 (lowest severity - informational)\n * - `warn`: 1 (medium severity - warnings)\n * - `error`: 2 (highest severity - critical errors)\n *\n * @example\n * ```ts\n * const messageLevel = LOG_LEVELS['warn']; // 1\n * const threshold = 2; // error-only\n * const shouldFilter = messageLevel < threshold; // true (warn is filtered)\n * ```\n *\n * @internal This is used for internal filtering logic\n */\nexport const LOG_LEVELS: Record<LogLevel, number> = Object.freeze({\n log: 0,\n warn: 1,\n error: 2,\n});\n\n/**\n * Numeric thresholds for logger configuration levels.\n *\n * Maps each configuration level to a minimum threshold value.\n * Messages with a severity level below this threshold are filtered out.\n *\n * Threshold logic:\n * - A message is shown if: `LOG_LEVELS[messageLevel] >= LEVEL_CONFIGS[configLevel]`\n * - Higher threshold value = stricter filtering = fewer messages shown\n *\n * Mapping:\n * - `all`: 0 (no filtering - show everything)\n * - Shows: log (0), warn (1), error (2) ✓\n * - `warn-error`: 1 (filter log messages)\n * - Shows: warn (1), error (2) ✓\n * - Filters: log (0) ✗\n * - `error-only`: 2 (filter log and warn messages)\n * - Shows: error (2) ✓\n * - Filters: log (0), warn (1) ✗\n * - `none`: 3 (filter all messages - complete silence)\n * - Filters: log (0), warn (1), error (2) ✗\n *\n * @example\n * ```ts\n * // Configuration: warn-error\n * const threshold = LEVEL_CONFIGS['warn-error']; // 1\n *\n * // Check if 'log' message should be shown\n * LOG_LEVELS['log'] >= threshold // 0 >= 1 = false (filtered)\n *\n * // Check if 'warn' message should be shown\n * LOG_LEVELS['warn'] >= threshold // 1 >= 1 = true (shown)\n *\n * // Check if 'error' message should be shown\n * LOG_LEVELS['error'] >= threshold // 2 >= 1 = true (shown)\n * ```\n *\n * @internal This is used for internal threshold comparison\n */\nexport const LEVEL_CONFIGS: Record<LogLevelConfig, number> = Object.freeze({\n all: 0,\n \"warn-error\": 1,\n \"error-only\": 2,\n none: 3,\n});\n","// packages/core/src/foundation/logger/RouterLogger.ts\n\nimport { LOG_LEVELS, LEVEL_CONFIGS } from \"./constants\";\n\nimport type {\n LogLevel,\n LoggerConfig,\n LogLevelConfig,\n LogCallback,\n} from \"../../types\";\n\n/**\n * Internal config type with required callbackIgnoresLevel\n * (always initialized to false)\n */\ninterface InternalLoggerConfig {\n level: LogLevelConfig;\n callback?: LogCallback | undefined;\n callbackIgnoresLevel: boolean;\n}\n\n/**\n * Logger class for centralized logging with configurable levels and callbacks.\n *\n * Features:\n * - Three log levels: log, warn, error\n * - Configurable threshold filtering (all, warn-error, error-only, none)\n * - Optional callback for custom log processing\n * - Callback can optionally ignore level threshold\n * - Context-based message formatting\n *\n * @example\n * ```ts\n * import { logger } from './Logger';\n *\n * // Configure logger\n * logger.configure({ level: 'warn-error' });\n *\n * // Use logger\n * logger.log('Router', 'Navigation started'); // Won't show (below threshold)\n * logger.warn('Router', 'Deprecated API used'); // Will show\n * ```\n */\nexport class RouterLogger {\n /** Internal configuration storage using private field */\n readonly #config: InternalLoggerConfig = {\n level: \"all\",\n callbackIgnoresLevel: false,\n };\n\n /** Cached numeric threshold value for performance (avoids repeated lookups) */\n #currentThreshold = 0;\n\n /**\n * Re-entrancy guard: true while a user callback is executing. Prevents a\n * callback that itself calls `logger.*` from recursing back through\n * `#invokeCallback` (which would otherwise spin ~5.9k deep until a swallowed\n * RangeError, see #791). Console output is unaffected.\n */\n #inCallback = false;\n\n /**\n * @param config - Optional initial configuration (level / callback /\n * callbackIgnoresLevel), applied once at construction.\n *\n * Each router owns its own `RouterLogger` instance, built from\n * `options.logger` in the `Router` constructor. This replaces the former\n * process-global singleton whose `configure()` leaked across every router in\n * the process — the last `createRouter` won (#724).\n */\n constructor(config?: Partial<LoggerConfig>) {\n if (config) {\n this.configure(config);\n }\n }\n\n /**\n * Configures the logger with new settings.\n *\n * @param config - Partial configuration to merge with existing config\n * @param config.level - Minimum log level to output ('all' | 'warn-error' | 'error-only' | 'none')\n * @param config.callback - Optional callback function to receive log messages\n * @param config.callbackIgnoresLevel - If true, callback receives all messages regardless of level\n *\n * @example\n * ```ts\n * // Set minimum level to warnings\n * logger.configure({ level: 'warn-error' });\n *\n * // Add custom callback that ignores level\n * logger.configure({\n * callback: (level, context, message) => {\n * sendToAnalytics({ level, context, message });\n * },\n * callbackIgnoresLevel: true\n * });\n * ```\n */\n configure(config: Partial<LoggerConfig>): void {\n // Read each field ONCE into a local — an unstable getter must not be re-read\n // between validation and storage: re-reading could pass validation with a\n // valid level and then store a later, unvalidated one, disabling the\n // threshold filter (a TOCTOU, #1162).\n const level = config.level;\n\n if (level !== undefined) {\n // Validate that the provided level is a valid configuration level\n if (!Object.hasOwn(LEVEL_CONFIGS, level)) {\n throw new Error(\n `Invalid log level: \"${level}\". Valid levels are: ${Object.keys(LEVEL_CONFIGS).join(\", \")}`,\n );\n }\n\n this.#config.level = level;\n this.#currentThreshold = LEVEL_CONFIGS[level];\n }\n if (Object.hasOwn(config, \"callback\")) {\n this.#config.callback = config.callback;\n }\n\n const callbackIgnoresLevel = config.callbackIgnoresLevel;\n\n if (callbackIgnoresLevel !== undefined) {\n this.#config.callbackIgnoresLevel = callbackIgnoresLevel;\n }\n }\n\n /**\n * Returns the current logger configuration.\n *\n * @returns Current configuration object with level, callback, and callbackIgnoresLevel\n *\n * @example\n * ```ts\n * const config = logger.getConfig();\n * console.log(config.level); // 'warn'\n * console.log(config.callbackIgnoresLevel); // false\n * ```\n */\n getConfig(): LoggerConfig {\n return {\n level: this.#config.level,\n callback: this.#config.callback,\n callbackIgnoresLevel: this.#config.callbackIgnoresLevel,\n };\n }\n\n /**\n * Logs an informational message at 'log' level.\n *\n * This is the lowest severity level. Messages are shown when level is 'all'.\n *\n * @param context - Context identifier (e.g., 'Router', 'Plugin')\n * @param message - Main log message\n * @param args - Additional arguments to log (objects, arrays, etc.)\n *\n * @example\n * ```ts\n * logger.log('Router', 'Navigation started', { from: '/home', to: '/about' });\n * // Output: [Router] Navigation started { from: '/home', to: '/about' }\n * ```\n */\n log(context: string, message: string, ...args: unknown[]): void {\n this.#writeLog(\"log\", context, message, args);\n }\n\n /**\n * Logs a warning message at 'warn' level.\n *\n * Use for deprecation notices, non-critical issues, or potential problems.\n * Messages are shown when level is 'all' or 'warn-error'.\n *\n * @param context - Context identifier (e.g., 'Router', 'Plugin')\n * @param message - Warning message\n * @param args - Additional arguments to log\n *\n * @example\n * ```ts\n * logger.warn('Router', 'Using deprecated API', { method: 'oldNavigate' });\n * // Output: [Router] Using deprecated API { method: 'oldNavigate' }\n * ```\n */\n warn(context: string, message: string, ...args: unknown[]): void {\n this.#writeLog(\"warn\", context, message, args);\n }\n\n /**\n * Logs an error message at 'error' level.\n *\n * Use for critical errors, exceptions, or failures that require attention.\n * Messages are shown when level is 'all', 'warn-error', or 'error-only'.\n *\n * @param context - Context identifier (e.g., 'Router', 'Plugin')\n * @param message - Error message\n * @param args - Additional arguments to log (often error objects)\n *\n * @example\n * ```ts\n * logger.error('Router', 'Navigation failed', new Error('Route not found'));\n * // Output: [Router] Navigation failed Error: Route not found\n * ```\n */\n error(context: string, message: string, ...args: unknown[]): void {\n this.#writeLog(\"error\", context, message, args);\n }\n\n /**\n * Central logging method that coordinates console output and callback invocation.\n *\n * This method implements the core logging logic:\n * 1. Early exit optimization for 'none' level (unless callback ignores level)\n * 2. Level threshold comparison for console output filtering\n * 3. Delegates to #writeToConsole and #invokeCallback\n *\n * @param level - Log level ('log' | 'warn' | 'error')\n * @param context - Context identifier\n * @param message - Log message\n * @param args - Additional arguments\n *\n * @private\n */\n #writeLog(\n level: LogLevel,\n context: string,\n message: string,\n args: unknown[],\n ): void {\n // Early exit optimization: if level is 'none' and callback doesn't ignore level,\n // skip all processing (both console and callback)\n // Stryker disable next-line BlockStatement: equivalent — emptying this early-exit block falls through, but at level \"none\" the downstream guards already yield no output: #writeToConsole skips (threshold 3 > every message level) and #invokeCallback returns (this branch runs only when callbackIgnoresLevel is false). Pure perf shortcut; the ConditionalExpression →true sibling on this line stays killed (not silenced here).\n if (this.#config.level === \"none\" && !this.#config.callbackIgnoresLevel) {\n return;\n }\n\n // Convert message level to numeric value for threshold comparison\n // LOG_LEVELS: { log: 0, warn: 1, error: 2 }\n const messageLevelValue = LOG_LEVELS[level];\n\n // Determine if this message should skip console output\n // Example: if threshold is 'warn' (1), then 'log' messages (0) are skipped\n const shouldSkipConsole = messageLevelValue < this.#currentThreshold;\n\n // Console output (respects level threshold)\n if (!shouldSkipConsole) {\n this.#writeToConsole(level, context, message, args);\n }\n\n // Callback handling (may ignore level threshold based on config)\n this.#invokeCallback(level, context, message, shouldSkipConsole, args);\n }\n\n /**\n * Writes a formatted log message to the console.\n *\n * Features:\n * - Formats message with context: \"[Context] message\"\n * - Uses appropriate console method (log/warn/error)\n * - Safe: checks for console existence (for non-browser environments)\n *\n * @param level - Console method to use ('log' | 'warn' | 'error')\n * @param context - Context identifier (prepended to message if present)\n * @param message - Log message\n * @param args - Additional arguments to pass to console\n *\n * @private\n */\n #writeToConsole(\n level: LogLevel,\n context: string,\n message: string,\n args: unknown[],\n ): void {\n // Safety check: ensure console exists and has the required method\n // This is important for environments like Node.js tests or edge cases\n if (\n typeof console !== \"undefined\" &&\n typeof console[level] === \"function\"\n ) {\n // Format message with context bracket notation for visual clarity\n // Note: formatting is done inside the check to avoid unnecessary string allocation\n // when console is not available\n const formattedMessage = context ? `[${context}] ${message}` : message;\n\n console[level](formattedMessage, ...args);\n }\n }\n\n /**\n * Invokes the configured callback with log data, respecting level settings.\n *\n * Complex logic handling:\n * 1. Skip if no callback configured\n * 2. Skip if callback respects level AND message is below threshold\n * 3. Call callback with error handling (prevents callback errors from breaking logger)\n *\n * The callbackIgnoresLevel flag enables two modes:\n * - false (default): callback only receives messages that pass threshold (same as console)\n * - true: callback receives ALL messages regardless of threshold (useful for analytics)\n *\n * @param level - Log level\n * @param context - Context identifier\n * @param message - Log message\n * @param shouldSkipConsole - Whether console output was skipped (used for level logic)\n * @param args - Additional arguments\n *\n * @private\n */\n #invokeCallback(\n level: LogLevel,\n context: string,\n message: string,\n shouldSkipConsole: boolean,\n args: unknown[],\n ): void {\n // Early exit: no callback configured, or callback respects level and message is filtered\n if (\n !this.#config.callback ||\n (!this.#config.callbackIgnoresLevel && shouldSkipConsole)\n ) {\n return;\n }\n\n // Re-entrancy guard: a callback calling logger.* re-enters here via\n // #writeLog → #invokeCallback. Skip the nested invocation so the pattern is\n // a safe no-op (console output already happened in #writeLog) instead of\n // recursing to a swallowed RangeError (#791).\n if (this.#inCallback) {\n return;\n }\n\n // Wrap callback invocation in try-catch to prevent user code errors\n // from breaking the logger or causing cascading failures\n this.#inCallback = true;\n try {\n // An async callback (`(...) => Promise<void>` is assignable to the\n // void-typed LogCallback) returns a Promise whose rejection would otherwise\n // leak as a Node `unhandledRejection` — process-fatal under\n // `--unhandled-rejections=strict` (Node 22+ default). Read the runtime\n // return and isolate it like core's subscribe (#944): duck-check the\n // thenable + `.catch` into the same console.error sink a sync throw uses\n // (#1161).\n // eslint-disable-next-line @typescript-eslint/no-confusing-void-expression -- read the runtime Promise of a void-typed async callback (#1161)\n const result: unknown = this.#config.callback(\n level,\n context,\n message,\n ...args,\n );\n\n if (\n result !== null &&\n result !== undefined &&\n typeof (result as PromiseLike<unknown>).then === \"function\"\n ) {\n Promise.resolve(result as PromiseLike<unknown>).catch(\n (error: unknown) => {\n this.#reportError(\"[Logger] Error in async callback:\", error);\n },\n );\n }\n } catch (error) {\n // Fallback error reporting if the callback throws synchronously\n this.#reportError(\"[Logger] Error in callback:\", error);\n } finally {\n this.#inCallback = false;\n }\n }\n\n // Report a callback error via console.error directly — never call the logger\n // (would recurse). Shared by the sync-throw catch and the async-rejection\n // `.catch` (#1161). Console-safety guard mirrors #writeToConsole.\n #reportError(message: string, error: unknown): void {\n if (typeof console !== \"undefined\" && typeof console.error === \"function\") {\n console.error(message, error);\n }\n }\n}\n","// packages/core/src/guards.ts\n\nimport type { LoggerConfig, LogLevelConfig, Route } from \"./types\";\nimport type { RouterValidator } from \"./types/RouterValidator\";\n\n// ============================================================================\n// Structural invariant guards (dependencies + route-tree shape)\n// ============================================================================\n\nexport function guardDependencies(deps: unknown): void {\n if (\n !deps ||\n typeof deps !== \"object\" ||\n (deps as { constructor: unknown }).constructor !== Object\n ) {\n throw new TypeError(\"dependencies must be a plain object\");\n }\n for (const key in deps as Record<string, unknown>) {\n if (Object.getOwnPropertyDescriptor(deps, key)?.get) {\n throw new TypeError(`dependencies cannot contain getters: \"${key}\"`);\n }\n }\n}\n\n/* eslint-disable @typescript-eslint/no-explicit-any -- accepts any Route type */\nexport function guardRouteStructure(\n routes: Route<any>[],\n validator?: RouterValidator | null,\n): void {\n /* eslint-enable @typescript-eslint/no-explicit-any */\n for (const route of routes) {\n const routeValue: unknown = route;\n\n if (\n routeValue === null ||\n typeof routeValue !== \"object\" ||\n Array.isArray(routeValue)\n ) {\n throw new TypeError(\"route must be a non-array object\");\n }\n\n validator?.routes.guardRouteCallbacks(route as Route);\n validator?.routes.guardNoAsyncCallbacks(route as Route);\n const children = (route as Route).children;\n\n if (children) {\n guardRouteStructure(children, validator);\n }\n }\n}\n\n// ============================================================================\n// Logger config assertion (RealRouter-specific)\n// ============================================================================\n\nconst VALID_LEVELS_SET = new Set<string>([\n \"all\",\n \"warn-error\",\n \"error-only\",\n \"none\",\n]);\n\nfunction isValidLevel(value: unknown): value is LogLevelConfig {\n return typeof value === \"string\" && VALID_LEVELS_SET.has(value);\n}\n\nfunction formatValue(value: unknown): string {\n if (typeof value === \"string\") {\n return `\"${value}\"`;\n }\n if (typeof value === \"object\") {\n return JSON.stringify(value);\n }\n\n // eslint-disable-next-line @typescript-eslint/no-base-to-string\n return String(value);\n}\n\nexport function assertLoggerConfig(\n config: unknown,\n): asserts config is LoggerConfig {\n if (typeof config !== \"object\") {\n throw new TypeError(\"Logger config must be an object\");\n }\n\n // `typeof null === \"object\"`, so TS still sees `object | null` here — but the\n // sole caller (Router's ctor) gates on `if (loggerConfig)`, so null/falsy never\n // arrives; treat it as the non-null object the gate guarantees.\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- gated caller never passes null\n const obj = config!;\n\n // Check for unknown properties\n for (const key of Object.keys(obj)) {\n if (\n key !== \"level\" &&\n key !== \"callback\" &&\n key !== \"callbackIgnoresLevel\"\n ) {\n throw new TypeError(`Unknown logger config property: \"${key}\"`);\n }\n }\n\n // Validate level if present\n if (\"level\" in obj && obj.level !== undefined && !isValidLevel(obj.level)) {\n throw new TypeError(\n `Invalid logger level: ${formatValue(obj.level)}. Expected: \"all\" | \"warn-error\" | \"error-only\" | \"none\"`,\n );\n }\n\n // Validate callback if present\n if (\n \"callback\" in obj &&\n obj.callback !== undefined &&\n typeof obj.callback !== \"function\"\n ) {\n throw new TypeError(\n `Logger callback must be a function, got ${typeof obj.callback}`,\n );\n }\n\n // Validate callbackIgnoresLevel if present (logger.configure does not type-check it)\n if (\n \"callbackIgnoresLevel\" in obj &&\n obj.callbackIgnoresLevel !== undefined &&\n typeof obj.callbackIgnoresLevel !== \"boolean\"\n ) {\n throw new TypeError(\n `Logger callbackIgnoresLevel must be a boolean, got ${typeof obj.callbackIgnoresLevel}`,\n );\n }\n}\n","// packages/core/src/helpers.ts\n\nimport { DEFAULT_LIMITS, EMPTY_PARAMS } from \"./constants\";\n\nimport type { Params, State, LimitsConfig } from \"./types\";\nimport type { Limits } from \"./types/internal\";\n\n// =============================================================================\n// State Helpers\n// =============================================================================\n\n/**\n * Shallow-freezes a State object in place.\n *\n * Freezes only the top-level State object (blocks reassignment of `name`,\n * `params`, `path`, `transition`, `context`). Nested objects (`params`,\n * `transition`, `transition.segments`, `transition.segments.{deactivated,activated}`)\n * are expected to be **already frozen at creation time** by their producers:\n *\n * - `params` frozen in `makeState()` / `navigateToNotFound()`\n * - `transition`, `segments`, `deactivated`, `activated` frozen in\n * `buildTransitionMeta()` (or inline in `navigateToNotFound()`)\n *\n * `state.context` is **intentionally not frozen** — plugins write to it via\n * `claim.write(state, value)` after state creation.\n *\n * @internal\n */\nexport function freezeStateInPlace<T extends State>(state: T): T {\n // `Object.freeze` returns non-objects (incl. null/undefined) unchanged, so the\n // former `if (!state) return state` guard was redundant — callers also gate it\n // (`state ? freezeStateInPlace(state) : undefined`) and `T extends State` is\n // typed non-null.\n return Object.freeze(state);\n}\n\n/**\n * Merges user limits with defaults.\n * Returns frozen object for immutability.\n */\nexport function createLimits(userLimits: Partial<LimitsConfig> = {}): Limits {\n return { ...DEFAULT_LIMITS, ...userLimits };\n}\n\n// =============================================================================\n// Params Helpers\n// =============================================================================\n\n/**\n * Strips `undefined` values from a params object before handoff to the query\n * string engine and state storage.\n *\n * **Why this exists:** `router.navigate(name, { x: undefined })` must not put\n * `x` into the resulting URL (publicly documented contract). The underlying\n * query engine (`search-params`) already does this, but the contract belongs\n * to `@real-router/core` — this function guarantees it at the core boundary\n * so that:\n * - Plugin interceptors on `forwardState` that inject `undefined` values are\n * caught before they reach the engine\n * - `state.params` never contains `undefined` values (roundtrip consistent\n * with URL)\n * - The contract is verifiable at core's own test surface (doesn't depend on\n * engine behavior for regression detection)\n *\n * Single pass. When nothing survives (empty input, or every value `undefined`)\n * it returns the shared frozen `EMPTY_PARAMS` singleton, so `makeState`'s\n * `params === EMPTY_PARAMS` reuse branch fires and an empty-params navigation\n * allocates zero transient `{}` (#1027); a non-empty input returns a fresh\n * object. Either way reference identity is not preserved across calls, and the\n * result MUST be treated as read-only — callers must not mutate it (the empty\n * case is a shared frozen singleton).\n */\nexport function normalizeParams(params: Params): Params;\n\nexport function normalizeParams(params: undefined): undefined;\n\nexport function normalizeParams(params: Params | undefined): Params | undefined;\n\nexport function normalizeParams(\n params: Params | undefined,\n): Params | undefined {\n if (params === undefined) {\n return params;\n }\n\n let normalized: Params | undefined;\n\n for (const key in params) {\n if (!Object.hasOwn(params, key)) {\n continue;\n }\n\n const value = params[key];\n\n if (value !== undefined) {\n // Lazy allocation: an all-empty / all-undefined input costs zero objects.\n normalized ??= {};\n normalized[key] = value;\n }\n }\n\n // Reuse the shared singleton when nothing survived so makeState's\n // `params === EMPTY_PARAMS` reuse branch fires (#1027).\n return normalized ?? EMPTY_PARAMS;\n}\n","import { DEFAULT_LIMITS } from \"../../constants\";\n\nimport type { DefaultDependencies } from \"../../types\";\nimport type { Limits } from \"../../types/internal\";\n\nexport interface DependenciesStore<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n> {\n dependencies: Partial<Dependencies>;\n limits: Limits;\n}\n\nexport function createDependenciesStore<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n initialDependencies: Partial<Dependencies> = {},\n): DependenciesStore<Dependencies> {\n const dependencies = Object.create(null) as Partial<Dependencies>;\n\n for (const key in initialDependencies) {\n if (initialDependencies[key] !== undefined) {\n dependencies[key] = initialDependencies[key];\n }\n }\n\n return {\n dependencies,\n limits: DEFAULT_LIMITS,\n };\n}\n","/**\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 normalizedPath = absolute ? path.slice(1) : path;\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","/**\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","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","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","// 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// 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}\n","// 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): {\n buildStaticParts: readonly string[];\n buildParamSlots: readonly BuildParamSlot[];\n} {\n const allUrlParams = new Set<string>();\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\n for (const [i, pathSegment] of pathSegments.entries()) {\n if (i > 0) {\n current += \"/\";\n }\n\n const token = parseSegment(pathSegment);\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","// 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 throwPathQueryNameCollision(\n routeName: string,\n name: string,\n): never {\n throw new Error(\n `[SegmentMatcher.registerTree] Name collision in route \"${routeName}\": \"${name}\" ` +\n `is declared as BOTH a path param (':${name}') and a query param ('?${name}'). ` +\n `buildPath would emit its value twice (once in the path, once in the query). ` +\n `Rename one.`,\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","// Trie node construction: param-name extraction + the param/splat child creators\n// (#736). Consumed by `trie` during insertion.\n\nimport { parseSegment } from \"../parseSegment\";\nimport { createSegmentNode } from \"../pathUtils\";\nimport { throwEmptyParamName, throwParamNameConflict } from \"./errors\";\n\nimport type { SegmentNode } from \"../types\";\n\n/**\n * Extracts the param name from a marker-led segment (`:name` / `*name`),\n * delegating the boundary to the canonical `parseSegment` tokenizer (#1324) so the\n * trie backstop, the route-tree gate, and `buildParamMeta` share ONE grammar and\n * cannot drift. `registerNode`'s per-segment grammar pre-pass has already rejected\n * every malformed form (name-less #858, trailing marker `:y*`/`:y:` #1324, fused\n * marker #1050, and the M1 removed optional/constraint forms), so a `:param` /\n * `*splat` name is guaranteed here — the error/`static` branches below are an\n * unreachable typed backstop. Single source for the param branch in\n * `processSegment` (the 3-token walk no longer forks, #1516).\n */\nexport function extractParamName(segment: string): string {\n const token = parseSegment(segment);\n\n // registerNode's per-segment grammar pass (Реш.2) rejects every malformed segment\n // — name-less (#858), trailing-marker (#1324), fused-marker (#1050), constraint\n // forms — before trie insertion, so a param|splat name is guaranteed here. The\n // error/`static` branches are unreachable, kept as a typed defensive backstop.\n /* v8 ignore start -- unreachable: registerNode's grammar pass rejects non-name segments first */\n if (\"error\" in token || token.kind === \"static\") {\n throwEmptyParamName();\n }\n /* v8 ignore stop */\n\n return token.name;\n}\n\n/**\n * Returns the param child of `node`, creating it on first use. A pre-existing\n * child with a *different* name is a #736 conflict (two routes binding the same\n * trie position under different names). With the 3-token grammar (M1) insertion is\n * a strict linear walk, so a single route never revisits a slot it created — the\n * former optional-omit `ownNodes` exception is gone with optional params.\n */\nexport function ensureParamChild(\n node: SegmentNode,\n paramName: string,\n): SegmentNode {\n if (!node.paramChild) {\n node.paramChild = { node: createSegmentNode(), name: paramName };\n } else if (node.paramChild.name !== paramName) {\n throwParamNameConflict(node.paramChild.name, paramName, \":\");\n }\n\n return node.paramChild.node;\n}\n\n/** Splat counterpart of {@link ensureParamChild}. */\nexport function ensureSplatChild(\n node: SegmentNode,\n splatName: string,\n): SegmentNode {\n if (!node.splatChild) {\n node.splatChild = { node: createSegmentNode(), name: splatName };\n } else if (node.splatChild.name !== splatName) {\n throwParamNameConflict(node.splatChild.name, splatName, \"*\");\n }\n\n return node.splatChild.node;\n}\n","// 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","// 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_ROUTE_META,\n EMPTY_STRINGS,\n EMPTY_STRING_SET,\n type RegistrationState,\n} from \"./context\";\nimport {\n throwDuplicateParamName,\n throwInvalidQueryParamName,\n throwPathQueryNameCollision,\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 );\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 // shared with a path param (`/a/:tab?tab`), where buildPath would emit the value\n // twice (`/a/x?tab=x`). Both degraded silently before.\n validateQueryParamDeclarations(\n node.fullName,\n declaredQueryParams,\n buildParamNamesSet,\n );\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 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 urlParamNames: ReadonlySet<string>,\n): void {\n for (const name of queryParams) {\n if (INVALID_QUERY_NAME_RGX.test(name)) {\n throwInvalidQueryParamName(routeName, name);\n }\n\n if (urlParamNames.has(name)) {\n throwPathQueryNameCollision(routeName, name);\n }\n }\n}\n","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/** Folds a parsed query param into the accumulator as an own DATA property. A\n * plain `params[name] = value` invokes the inherited `__proto__` accessor for the\n * literal key `\"__proto__\"` — so the param silently vanishes (string value) or\n * swaps the local prototype (object value), annulling search-params' #855 own-key\n * hardening one layer up. `defineProperty` writes a genuine own entry. (#1293) */\nfunction assignQueryParam(\n params: Record<string, unknown>,\n name: string,\n value: unknown,\n): void {\n if (name === \"__proto__\") {\n Object.defineProperty(params, name, {\n value,\n writable: true,\n enumerable: true,\n configurable: true,\n });\n } else {\n params[name] = value;\n }\n}\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 },\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 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 const queryString = this.#buildQueryStringForBuild(\n route,\n 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 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 if (\n queryString !== undefined &&\n !this.#mergeQueryParams(route, params, queryString)\n ) {\n return undefined;\n }\n\n return {\n segments: route.matchSegments,\n params,\n meta: route.meta,\n };\n }\n\n // Parses the query string and folds it into `params`. Returns false (→ match\n // yields undefined) when the URL is unmatchable: the injected parser threw, or\n // strict mode saw an undeclared key.\n //\n // Precedence (#843, INVARIANTS Matching #25): query params are merged into the\n // SAME object that already holds the path params, so a query key equal to a\n // path-param name OVERWRITES the path value (`match(\"/u/5?id=9\")` → `{id:\"9\"}`).\n // Intentional and documented: `buildPath` never emits a path param as a query\n // key, so the build→match roundtrip is unaffected; the collision only arises\n // for hand-crafted/adversarial URLs where a query shadows a path segment.\n #mergeQueryParams(\n route: CompiledRoute,\n params: Record<string, unknown>,\n queryString: string,\n ): boolean {\n let queryParams: Record<string, unknown>;\n\n try {\n queryParams = 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 false;\n }\n\n if (this.#options.strictQueryParams) {\n const declared = route.declaredQueryParamsSet;\n\n for (const key in queryParams) {\n if (!declared.has(key)) {\n return false;\n }\n\n assignQueryParam(params, key, queryParams[key]);\n }\n } else {\n for (const key in queryParams) {\n assignQueryParam(params, key, queryParams[key]);\n }\n }\n\n return true;\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","/**\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","/**\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","/**\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","/**\n * Decoding functions for search-params.\n *\n * Extracted from encode.ts for better separation of concerns.\n *\n * @module search-params/decode\n */\n\nimport type { ResolvedStrategies } from \"./strategies\";\nimport type { DecodeResult } from \"./types\";\n\n// =============================================================================\n// Value Decoding\n// =============================================================================\n\n/**\n * Decodes a URL-encoded value, handling + as space.\n * Optimized: only replaces + if present, skips decodeURIComponent if not needed.\n *\n * Uses indexOf for checking presence - more mutation-resistant than includes\n * because indexOf(\"x\") returns -1 when not found, while includes(\"\") always returns true.\n */\nexport const decodeValue = (value: string): string => {\n // Stryker disable next-line StringLiteral: equivalent — indexOf(\"%\") -> indexOf(\"\") returns 0, forcing the full decode path; decodeURIComponent is a no-op on a %-free string, so the result is identical (proven by injection). See the indexOf rationale above.\n const percentIdx = value.indexOf(\"%\");\n // Stryker disable next-line StringLiteral: equivalent — indexOf(\"+\") -> indexOf(\"\") returns 0, forcing replaceAll(\"+\",\" \") which is a no-op on a +-free string (proven by injection).\n const plusIdx = value.indexOf(\"+\");\n\n // Fast path: no encoding needed (common case - most values are simple)\n if (percentIdx === -1 && plusIdx === -1) {\n return value;\n }\n\n // Only replace + if present (avoid regex overhead)\n const withSpaces = plusIdx === -1 ? value : value.replaceAll(\"+\", \" \");\n\n // Only decode if % is present\n return percentIdx === -1 ? withSpaces : decodeURIComponent(withSpaces);\n};\n\n// =============================================================================\n// Main Decode\n// =============================================================================\n\n/**\n * Decodes a query parameter value using resolved strategies.\n *\n * @param value - Raw value from query string (undefined for key-only params)\n * @param strategies - Pre-resolved format strategies\n * @returns Decoded value (string, boolean, or null)\n */\nexport const decode = (\n value: string | undefined,\n strategies: ResolvedStrategies,\n): DecodeResult => {\n // Handle undefined (key-only params like ?flag)\n if (value === undefined) {\n return strategies.boolean.decodeUndefined();\n }\n\n // Check raw value for boolean formats (e.g., \"true\"/\"false\")\n const rawResult = strategies.boolean.decodeRaw(value);\n\n if (rawResult !== null) {\n return rawResult;\n }\n\n // Decode URI and check decoded value for boolean formats (e.g., ✓/✗)\n const decoded = decodeValue(value);\n\n const numberResult = strategies.number.decode(decoded);\n\n if (numberResult !== null) {\n return numberResult;\n }\n\n return strategies.boolean.decodeValue(decoded);\n};\n","/**\n * Utility functions for search-params.\n *\n * Internalized from https://github.com/troch/search-params\n *\n * @module search-params/utils\n */\n\n// =============================================================================\n// Total Percent-Encoding\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. This is a manual, lib-target-agnostic\n * `String.prototype.toWellFormed` (ES2024): consumers compile this `src` under their\n * OWN `tsconfig` (whose `lib` may predate es2024 — e.g. `hash-plugin`), so a regex\n * `replace` avoids a `toWellFormed` type error in every consumer while producing the\n * identical result (verified by parity across the surrogate space).\n */\nconst LONE_SURROGATE_RGX =\n /[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?<![\\uD800-\\uDBFF])[\\uDC00-\\uDFFF]/g;\n\n/**\n * Percent-encodes a value, staying TOTAL on a lone (unpaired) surrogate.\n *\n * `encodeURIComponent` throws `URIError` on an unpaired UTF-16 surrogate — the\n * only input it rejects. `parseQuery` accepts such a value (its non-percent decode\n * is an identity fast path), so `build(parseQuery(qs))` would throw and violate the\n * inverse-pair totality invariant `range(parseQuery) ⊆ dom(build)` (INVARIANTS\n * Parse/Build #12). We sanitize each lone surrogate to U+FFFD instead: the first\n * round-trip mutates the (already non-round-trippable) garbage, then stabilises;\n * well-formed inputs are untouched. Single source for BOTH encode sites — scalar/key\n * (`encode.ts`) and array element (`strategies/array.ts`) — so they cannot drift\n * (#1314).\n */\nexport const safeEncode = (value: string | number | boolean): string => {\n try {\n return encodeURIComponent(value);\n } catch (error) {\n // Only a lone surrogate (URIError) is sanitized. Anything else is a real error\n // the caller must see — e.g. a Symbol value throws `TypeError` here, and\n // `String(symbol)` would silently coerce it to \"Symbol(…)\" instead of rethrowing.\n if (!(error instanceof URIError)) {\n throw error;\n }\n\n // Lone surrogate → U+FFFD, keeping build total.\n return encodeURIComponent(\n String(value).replaceAll(LONE_SURROGATE_RGX, \"�\"),\n );\n }\n};\n","/**\n * Array encoding strategies.\n *\n * @module search-params/strategies/array\n */\n\nimport { safeEncode } from \"../utils\";\n\nimport type { NullStrategy } from \"./null\";\nimport type { ArrayFormat } from \"../types\";\n\n// =============================================================================\n// Strategy Interface\n// =============================================================================\n\n/**\n * Strategy for encoding array values.\n */\nexport interface ArrayStrategy {\n /**\n * Encodes an array as a query string segment.\n *\n * @param name - URL-encoded parameter name\n * @param values - Array values to encode\n * @param nullStrategy - Null strategy, so a `null` element encodes to the same\n * wire token a scalar null does (bare key under `default`, dropped under\n * `hidden`) — closing `range(parse) ⊆ dom(build)` (#1155)\n * @returns Query string segment (e.g., \"items=a&items=b\" or \"items=a,b\")\n */\n encodeArray: (\n name: string,\n values: unknown[],\n nullStrategy: NullStrategy,\n ) => string;\n\n /**\n * Splits a raw (URI-encoded) value into array parts during parsing.\n * Returns null if the value is not an array in this format.\n *\n * @param rawValue - Raw value before URI decoding\n * @returns Array of raw parts, or null if not an array\n */\n decodeValue?: (rawValue: string) => string[] | null;\n\n /**\n * When true, the parser orders bracketed elements (`a[n]`) by the numeric\n * index `n` rather than insertion order. Only `index` format sets this. (#856)\n */\n indexed?: boolean;\n}\n\n// =============================================================================\n// Helpers\n// =============================================================================\n\n// Encodes a non-null array element. `null` is handled per-format by the caller\n// (bare-key / skip); this throws only on genuinely unserialisable elements\n// (`undefined`, objects) — which `parse` never produces in an array.\nconst encodeValue = (value: unknown): string => {\n const type = typeof value;\n\n if (type !== \"string\" && type !== \"number\" && type !== \"boolean\") {\n // `null` is handled by the caller (bare-key / skip) and never reaches here,\n // so `type` names the offender directly (`undefined`, `object`, `symbol`, …).\n throw new TypeError(\n `[search-params] Array element must be a string, number, or boolean — received ${type}`,\n );\n }\n\n return safeEncode(value as string | number | boolean);\n};\n\n// Repeats `${name}${suffix}` keys joined by `&`. Shared between `none` (suffix\n// `\"\"`) and `brackets` (suffix `\"[]\"`). A `null` element encodes to the SAME\n// wire token a scalar null does via `nullStrategy` — the bare key\n// `${name}${suffix}` under `nullFormat: \"default\"`, or `\"\"` (dropped, filtered\n// below so no `&&` appears) under `\"hidden\"`. So `parseQuery(\"a&a=1\")` →\n// `{a:[null,\"1\"]}` round-trips to `\"a&a=1\"` instead of throwing (#1155).\nconst repeatKey = (\n name: string,\n values: unknown[],\n suffix: string,\n nullStrategy: NullStrategy,\n): string => {\n const key = `${name}${suffix}`;\n const parts: string[] = [];\n\n for (const value of values) {\n if (value === null) {\n const encoded = nullStrategy.encode(key);\n\n if (encoded) {\n parts.push(encoded);\n }\n } else {\n parts.push(`${key}=${encodeValue(value)}`);\n }\n }\n\n return parts.join(\"&\");\n};\n\n// =============================================================================\n// Strategy Implementations\n// =============================================================================\n\n/**\n * Repeated keys without brackets.\n * Example: items=a&items=b\n */\nexport const noneArrayStrategy: ArrayStrategy = {\n encodeArray: (name, values, nullStrategy) =>\n repeatKey(name, values, \"\", nullStrategy),\n};\n\n/**\n * Bracket notation without index.\n * Example: items[]=a&items[]=b\n */\nexport const bracketsArrayStrategy: ArrayStrategy = {\n encodeArray: (name, values, nullStrategy) =>\n repeatKey(name, values, \"[]\", nullStrategy),\n};\n\n/**\n * Indexed bracket notation.\n * Example: items[0]=a&items[1]=b\n */\nexport const indexArrayStrategy: ArrayStrategy = {\n encodeArray: (name, values, nullStrategy) => {\n const parts: string[] = [];\n\n for (const [i, value] of values.entries()) {\n const key = `${name}[${i}]`;\n\n if (value === null) {\n const encoded = nullStrategy.encode(key);\n\n if (encoded) {\n parts.push(encoded);\n }\n } else {\n parts.push(`${key}=${encodeValue(value)}`);\n }\n }\n\n return parts.join(\"&\");\n },\n\n indexed: true,\n};\n\n/**\n * Comma-separated values.\n * Example: items=a,b,c\n *\n * Comma has no per-element bare-key form (an empty part like `a=,` decodes to\n * the empty string, not `null`), so a `null` element is unrepresentable and\n * dropped. `parse` only yields null-in-array under `comma` via a bracketed\n * chunk (`a[]`) — a wire/format mismatch, never the comma-native path — so this\n * is a total-but-lossy edge. The `nullStrategy` arg is intentionally omitted\n * (a 2-arg impl satisfies the 3-arg interface).\n */\nexport const commaArrayStrategy: ArrayStrategy = {\n encodeArray: (name, values) => {\n const parts: string[] = [];\n\n for (const value of values) {\n if (value !== null) {\n parts.push(encodeValue(value));\n }\n }\n\n if (parts.length === 0) {\n return \"\";\n }\n\n return `${name}=${parts.join(\",\")}`;\n },\n\n decodeValue: (rawValue) => {\n // No unencoded comma → not an array (single value).\n // Encoded commas (%2C) are part of the value, not separators.\n if (!rawValue.includes(\",\")) {\n return null;\n }\n\n return rawValue.split(\",\");\n },\n};\n\n/**\n * Map of array format to strategy implementation.\n */\nexport const arrayStrategies: Record<ArrayFormat, ArrayStrategy> = {\n none: noneArrayStrategy,\n brackets: bracketsArrayStrategy,\n index: indexArrayStrategy,\n comma: commaArrayStrategy,\n};\n","/**\n * Boolean encoding/decoding strategies.\n *\n * @module search-params/strategies/boolean\n */\n\nimport type { DecodeResult, BooleanFormat } from \"../types\";\n\n// =============================================================================\n// Strategy Map\n// =============================================================================\n\n// =============================================================================\n// Strategy Interface\n// =============================================================================\n\n/**\n * Strategy for encoding/decoding boolean values.\n */\nexport interface BooleanStrategy {\n /**\n * Encodes a boolean value as a query string segment.\n *\n * @param name - URL-encoded parameter name\n * @param value - Boolean value to encode\n * @returns Query string segment (e.g., \"flag=true\" or just \"flag\")\n */\n encode: (name: string, value: boolean) => string;\n\n /**\n * Handles undefined value (key-only params like ?flag).\n *\n * @returns Decoded value for undefined\n */\n decodeUndefined: () => DecodeResult;\n\n /**\n * Decodes a raw (not URI-decoded) value before decodeValue() is called.\n * Used for formats that check raw values (e.g., \"auto\" checks \"true\"/\"false\").\n *\n * @param rawValue - Raw value before URI decoding\n * @returns boolean if matched, null to continue with URI decoding\n */\n decodeRaw: (rawValue: string) => boolean | null;\n\n /**\n * Decodes a URI-decoded value.\n *\n * @param decodedValue - Value after URI decoding\n * @returns Decoded value (boolean or the same string)\n */\n decodeValue: (decodedValue: string) => DecodeResult;\n}\n\n// =============================================================================\n// Strategy Implementations\n// =============================================================================\n\n/**\n * No special boolean handling - values are treated as strings.\n */\nexport const noneBooleanStrategy: BooleanStrategy = {\n encode: (name, value) => `${name}=${value}`,\n decodeUndefined: () => null,\n decodeRaw: () => null, // No raw value matching\n decodeValue: (decoded) => decoded, // Return as-is\n};\n\n/**\n * Auto-detect \"true\"/\"false\" strings as boolean values.\n * Checks raw value before URI decoding.\n */\nexport const autoBooleanStrategy: BooleanStrategy = {\n encode: (name, value) => `${name}=${value}`,\n decodeUndefined: () => null,\n decodeRaw: (raw) => {\n if (raw === \"true\") {\n return true;\n }\n if (raw === \"false\") {\n return false;\n }\n\n return null; // Continue with URI decoding\n },\n decodeValue: (decoded) => decoded, // Return decoded string\n};\n\n/**\n * Scalar true values are key-only (no =value); scalar false keeps an explicit\n * `=false`. Example: `?flag` for true, `?flag=false` for false. Array elements\n * always carry an explicit value (`?a=true&a=false`), so decoding must map both\n * `\"true\"`→true and `\"false\"`→false to round-trip booleans losslessly in either\n * position — a key-only param is `true` (decodeUndefined), and any explicit\n * `=true`/`=false` decodes back to the boolean (not the string).\n */\nexport const emptyTrueBooleanStrategy: BooleanStrategy = {\n encode: (name, value) => (value ? name : `${name}=false`),\n decodeUndefined: () => true, // Key-only means true\n decodeRaw: (raw) => {\n if (raw === \"true\") {\n return true;\n }\n if (raw === \"false\") {\n return false;\n }\n\n return null; // Continue with URI decoding\n },\n decodeValue: (decoded) => decoded, // Return as-is\n};\n\n/**\n * Map of boolean format to strategy implementation.\n */\nexport const booleanStrategies: Record<BooleanFormat, BooleanStrategy> = {\n none: noneBooleanStrategy,\n auto: autoBooleanStrategy,\n \"empty-true\": emptyTrueBooleanStrategy,\n};\n","/**\n * Null encoding/decoding strategies.\n *\n * @module search-params/strategies/null\n */\n\n// =============================================================================\n// Strategy Interface\n// =============================================================================\n\n/**\n * Strategy for encoding/decoding null values.\n */\n// =============================================================================\n// Strategy Map\n// =============================================================================\n\nimport type { NullFormat } from \"../types\";\n\nexport interface NullStrategy {\n /**\n * Encodes a null value as a query string segment.\n *\n * @param name - URL-encoded parameter name\n * @returns Query string segment (e.g., \"key\" or \"\")\n */\n encode: (name: string) => string;\n}\n\n// =============================================================================\n// Strategy Implementations\n// =============================================================================\n\n/**\n * Default null format - key only without value.\n * Example: ?key (no equals sign)\n */\nexport const defaultNullStrategy: NullStrategy = {\n encode: (name) => name,\n};\n\n/**\n * Null values are hidden (omitted from query string).\n * Example: (nothing)\n */\nexport const hiddenNullStrategy: NullStrategy = {\n encode: () => \"\",\n};\n\n/**\n * Map of null format to strategy implementation.\n */\nexport const nullStrategies: Record<NullFormat, NullStrategy> = {\n default: defaultNullStrategy,\n hidden: hiddenNullStrategy,\n};\n","/**\n * Number decoding strategies.\n *\n * @module search-params/strategies/number\n */\n\nimport type { NumberFormat } from \"../types\";\n\n// =============================================================================\n// Strategy Interface\n// =============================================================================\n\n/**\n * Strategy for decoding number values.\n */\nexport interface NumberStrategy {\n decode: (decodedValue: string) => number | null;\n}\n\n// =============================================================================\n// Strategy Implementations\n// =============================================================================\n\n/**\n * No special number handling - values remain strings.\n */\nexport const noneNumberStrategy: NumberStrategy = {\n decode: () => null, // passthrough\n};\n\n/**\n * Auto-detect numeric values and parse as numbers.\n * Matches integers and decimals via charCode scan (faster than regex for short strings).\n *\n * Recognizes an optional leading `-` so negatives round-trip symmetrically with the\n * values `navigate()`/`build()` produce (`build({ n: -5 })` → `\"n=-5\"`). Leading-zero\n * and unsafe-integer rejection apply to the magnitude regardless of sign. Exponent\n * notation stays a string — `build` never emits a canonical safe exponent, and unsafe\n * exponents would lose precision. (#742)\n */\nexport const autoNumberStrategy: NumberStrategy = {\n decode: (value) => {\n const length = value.length;\n\n if (length === 0) {\n return null;\n }\n\n // Optional leading minus; the magnitude (digits) begins at `start`.\n const start = value.codePointAt(0) === 45 ? 1 : 0; // '-'\n\n // A bare \"-\" has no magnitude.\n if (start === length) {\n return null;\n }\n\n // Leading zeros are not canonical numbers (\"00\", \"007\", \"-007\") — preserve as strings.\n // Allow \"0\" and \"0.x\" (single zero or decimal starting with 0).\n if (\n length - start > 1 &&\n value.codePointAt(start) === 48 &&\n value.codePointAt(start + 1) !== 46\n ) {\n return null;\n }\n\n let hasDot = false;\n\n for (let i = start; i < length; i++) {\n const ch = value.codePointAt(i);\n\n if (ch !== undefined && ch >= 48 && ch <= 57) {\n continue; // '0'-'9'\n }\n\n if (ch === 46 && !hasDot && i !== start && i !== length - 1) {\n hasDot = true;\n\n continue;\n }\n\n return null; // non-digit, non-dot, or invalid dot position\n }\n\n const num = Number(value);\n\n // Negative zero is not round-trippable: build(-0) emits \"0\" and\n // String(-0) === \"0\", so \"-0\"/\"-0.0\" must stay strings. (#898)\n if (Object.is(num, -0)) {\n return null;\n }\n\n // Reject unsafe integers — precision loss would corrupt the value on roundtrip.\n if (!Number.isSafeInteger(num) && !hasDot) {\n return null;\n }\n\n return num;\n },\n};\n\n// =============================================================================\n// Strategy Map\n// =============================================================================\n\n/**\n * Map of number format to strategy implementation.\n */\nexport const numberStrategies: Record<NumberFormat, NumberStrategy> = {\n auto: autoNumberStrategy,\n none: noneNumberStrategy,\n};\n","/**\n * Search Params Strategies - Factory and Types.\n *\n * Provides a unified interface for format-specific encoding/decoding strategies.\n * Strategies are resolved once when options are created, avoiding repeated\n * format checks during encode/decode operations.\n *\n * @module search-params/strategies\n */\n\nimport { arrayStrategies, type ArrayStrategy } from \"./array\";\nimport { booleanStrategies, type BooleanStrategy } from \"./boolean\";\nimport { nullStrategies, type NullStrategy } from \"./null\";\nimport { numberStrategies, type NumberStrategy } from \"./number\";\n\nimport type { FinalOptions } from \"../types\";\n\n// =============================================================================\n// Exports\n// =============================================================================\n\nexport type { ArrayStrategy } from \"./array\";\n\nexport type { BooleanStrategy } from \"./boolean\";\n\nexport type { NullStrategy } from \"./null\";\n\nexport type { NumberStrategy } from \"./number\";\n\n// =============================================================================\n// Resolved Strategies\n// =============================================================================\n\n/**\n * Pre-resolved strategies based on options.\n * Created once when makeOptions() is called, avoiding repeated lookups.\n */\nexport interface ResolvedStrategies {\n readonly boolean: BooleanStrategy;\n readonly null: NullStrategy;\n readonly number: NumberStrategy;\n readonly array: ArrayStrategy;\n}\n\n/**\n * Resolves strategies based on format options.\n *\n * @param arrayFormat - Array format\n * @param booleanFormat - Boolean format\n * @param nullFormat - Null format\n * @param numberFormat - Number format\n * @returns Resolved strategy implementations\n */\n/**\n * Fail fast on an unknown format. A `queryParams` typo in a JS consumer (no TS to\n * forbid it) otherwise indexes the strategy map to `undefined`, deferring a cryptic\n * `TypeError` to first use — which the router's `SegmentMatcher.#mergeQueryParams`\n * catch-all then masks as `UNKNOWN_ROUTE` for EVERY query URL, with zero diagnostics\n * (#1318). TS consumers are unaffected (the union types already forbid the typo).\n */\nconst requireStrategy = <T>(\n strategy: T | undefined,\n field: string,\n value: string,\n allowed: string,\n): T => {\n if (strategy === undefined) {\n throw new TypeError(\n `[search-params] Unknown ${field} \"${value}\" — expected ${allowed}`,\n );\n }\n\n return strategy;\n};\n\nexport const resolveStrategies = (\n arrayFormat: FinalOptions[\"arrayFormat\"],\n booleanFormat: FinalOptions[\"booleanFormat\"],\n nullFormat: FinalOptions[\"nullFormat\"],\n numberFormat: FinalOptions[\"numberFormat\"],\n): ResolvedStrategies => ({\n boolean: requireStrategy(\n booleanStrategies[booleanFormat],\n \"booleanFormat\",\n booleanFormat,\n '\"none\" | \"auto\" | \"empty-true\"',\n ),\n null: requireStrategy(\n nullStrategies[nullFormat],\n \"nullFormat\",\n nullFormat,\n '\"default\" | \"hidden\"',\n ),\n number: requireStrategy(\n numberStrategies[numberFormat],\n \"numberFormat\",\n numberFormat,\n '\"none\" | \"auto\"',\n ),\n array: requireStrategy(\n arrayStrategies[arrayFormat],\n \"arrayFormat\",\n arrayFormat,\n '\"none\" | \"brackets\" | \"index\" | \"comma\"',\n ),\n});\n\n// =============================================================================\n// Default Strategies\n// =============================================================================\n\n/**\n * Default strategies matching DEFAULT_OPTIONS.\n * Used when no custom options are provided.\n */\nexport const DEFAULT_STRATEGIES: ResolvedStrategies = {\n boolean: booleanStrategies.auto,\n null: nullStrategies.default,\n number: numberStrategies.auto,\n array: arrayStrategies.none,\n};\n","/**\n * Encoding functions for search-params.\n *\n * Uses Strategy pattern for format-specific encoding.\n * Strategies are resolved once when options are created.\n *\n * @module search-params/encode\n */\n\nimport {\n DEFAULT_STRATEGIES,\n resolveStrategies,\n type ResolvedStrategies,\n} from \"./strategies\";\nimport { safeEncode } from \"./utils\";\n\nimport type { FinalOptions, Options } from \"./types\";\n\n// =============================================================================\n// Default Query Params\n// =============================================================================\n\n/**\n * Default query parameter options. Single source of truth for all packages.\n */\nexport const DEFAULT_QUERY_PARAMS: FinalOptions = {\n arrayFormat: \"none\",\n booleanFormat: \"auto\",\n nullFormat: \"default\",\n numberFormat: \"auto\",\n};\n\n// =============================================================================\n// Options with Strategies\n// =============================================================================\n\n/**\n * Extended options with pre-resolved strategies.\n */\nexport interface OptionsWithStrategies extends FinalOptions {\n readonly strategies: ResolvedStrategies;\n}\n\n/**\n * Cached default options with strategies - avoids allocation when no options passed.\n */\nconst DEFAULT_OPTIONS: OptionsWithStrategies = {\n ...DEFAULT_QUERY_PARAMS,\n strategies: DEFAULT_STRATEGIES,\n};\n\n/**\n * Creates options with defaults and pre-resolved strategies.\n * Returns cached DEFAULT_OPTIONS when no custom options are specified.\n */\nexport const makeOptions = (opts?: Options): OptionsWithStrategies => {\n if (\n !opts ||\n (opts.arrayFormat === undefined &&\n opts.booleanFormat === undefined &&\n opts.nullFormat === undefined &&\n opts.numberFormat === undefined)\n ) {\n return DEFAULT_OPTIONS;\n }\n\n // Avoid object spread - direct property assignment is faster\n const arrayFormat = opts.arrayFormat ?? DEFAULT_QUERY_PARAMS.arrayFormat;\n const booleanFormat =\n opts.booleanFormat ?? DEFAULT_QUERY_PARAMS.booleanFormat;\n const nullFormat = opts.nullFormat ?? DEFAULT_QUERY_PARAMS.nullFormat;\n const numberFormat = opts.numberFormat ?? DEFAULT_QUERY_PARAMS.numberFormat;\n\n return {\n arrayFormat,\n booleanFormat,\n nullFormat,\n numberFormat,\n strategies: resolveStrategies(\n arrayFormat,\n booleanFormat,\n nullFormat,\n numberFormat,\n ),\n };\n};\n\n// =============================================================================\n// Value Encoding\n// =============================================================================\n\n/**\n * Encodes a value for use in a URL query string.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument\nexport const encodeValue = (value: any): string => safeEncode(value);\n\n// =============================================================================\n// Main Encode\n// =============================================================================\n\n/**\n * Encodes a parameter name and value as a query string segment.\n *\n * Uses pre-resolved strategies for format-specific encoding.\n * Strategies handle all formats uniformly - no inline optimizations\n * to avoid equivalent mutants in mutation testing.\n *\n * @param name - Parameter name\n * @param value - Parameter value\n * @param opts - Options with pre-resolved strategies\n * @returns Query string segment (e.g., \"key=value\")\n */\nexport const encode = (\n name: string,\n value: unknown,\n opts: OptionsWithStrategies,\n): string => {\n const encodedName = encodeValue(name);\n\n // Handle each type using strategies\n switch (typeof value) {\n case \"string\":\n case \"number\": {\n return `${encodedName}=${encodeValue(value)}`;\n }\n case \"boolean\": {\n return opts.strategies.boolean.encode(encodedName, value);\n }\n case \"object\": {\n // Null check\n if (value === null) {\n return opts.strategies.null.encode(encodedName);\n }\n // Array check\n if (Array.isArray(value)) {\n return opts.strategies.array.encodeArray(\n encodedName,\n value,\n opts.strategies.null,\n );\n }\n\n // Fallback for other objects - treat as string\n return `${encodedName}=${encodeValue(value)}`;\n }\n default: {\n // Fallback for other types (undefined handled by caller)\n return `${encodedName}=${encodeValue(value)}`;\n }\n }\n};\n","/**\n * Search Params - Query String Parsing and Building.\n *\n * Internalized from https://github.com/troch/search-params (MIT License)\n * for better code control and optimization.\n *\n * @module search-params/searchParams\n */\n\nimport { decode, decodeValue } from \"./decode\";\nimport { encode, makeOptions } from \"./encode\";\n\nimport type { ResolvedStrategies } from \"./strategies\";\nimport type { Options } from \"./types\";\n\n// =============================================================================\n// Internal Helpers\n// =============================================================================\n\n/**\n * Assigns a parameter as an own data property.\n *\n * Plain `params[name] = value` invokes the inherited `__proto__` accessor for the\n * literal key `\"__proto__\"`, so that key would mutate the prototype instead of\n * becoming a real entry; `defineProperty` writes a genuine own property.\n *\n * @internal\n */\nfunction assignParam(\n params: Record<string, unknown>,\n name: string,\n value: unknown,\n): void {\n if (name === \"__proto__\") {\n Object.defineProperty(params, name, {\n value,\n writable: true,\n enumerable: true,\n configurable: true,\n });\n } else {\n params[name] = value;\n }\n}\n\n/**\n * Adds a decoded value to params object, handling array accumulation.\n *\n * Collisions are detected via `Object.hasOwn`, not `params[name] !== undefined`:\n * a query key that shadows an `Object.prototype` member (`valueOf`, `constructor`,\n * `toString`, …) would otherwise read the inherited function and be mistaken for\n * a pre-existing value, corrupting the result into `[<fn>, value]`. (#855)\n *\n * @internal\n */\nfunction addToParams(\n params: Record<string, unknown>,\n decodedName: string,\n decodedValue: unknown,\n hasBrackets: boolean,\n): void {\n if (!Object.hasOwn(params, decodedName)) {\n assignParam(\n params,\n decodedName,\n hasBrackets ? [decodedValue] : decodedValue,\n );\n\n return;\n }\n\n const currentValue = params[decodedName];\n\n if (Array.isArray(currentValue)) {\n currentValue.push(decodedValue);\n } else {\n assignParam(params, decodedName, [currentValue, decodedValue]);\n }\n}\n\n/**\n * Decodes a parameter value through the resolved strategies.\n *\n * @internal\n */\nfunction decodeParamValue(\n searchPart: string,\n eqPos: number,\n end: number,\n hasValue: boolean,\n strategies: ResolvedStrategies,\n): unknown {\n const rawValue = hasValue ? searchPart.slice(eqPos + 1, end) : undefined;\n\n return decode(rawValue, strategies);\n}\n\n/**\n * Reads the non-negative integer index from a bracketed name (`a[12]`).\n *\n * `open` points at the `[`; digits up to the matching `]` form the index. Returns\n * `null` for `[]`, non-digit content (`a[x]`), or a missing `]` — those fall back\n * to insertion-order accumulation. (#856)\n *\n * @internal\n */\nfunction bracketIndex(\n searchPart: string,\n open: number,\n limit: number,\n): number | null {\n let i = open + 1;\n let value = 0;\n let hasDigit = false;\n\n while (i < limit) {\n const ch = searchPart.codePointAt(i);\n\n if (ch === 93) {\n // ']' — a numeric index only if at least one digit preceded it (\"[]\" → null)\n return hasDigit ? value : null;\n }\n\n if (ch !== undefined && ch >= 48 && ch <= 57) {\n value = value * 10 + (ch - 48);\n hasDigit = true;\n i++;\n\n continue;\n }\n\n return null; // non-digit inside brackets — not a numeric index\n }\n\n return null; // no closing ']' (incl. \"[\" at end) — malformed, fall back\n}\n\n/**\n * A single parsed query chunk: the source string plus the boundary offsets and\n * decoded name that `processParamChunk` computes once. Bundled into one\n * descriptor so the indexed-format collector reuses them without a long\n * parameter list (#856).\n *\n * @internal\n */\ninterface ParsedChunk {\n searchPart: string;\n /** Offset of `[` (bracket notation), or the name terminator. */\n nameEnd: number;\n /** Offset where the raw name ends (`=` for valued chunks, else `end`). */\n nameSourceEnd: number;\n /** Offset of `=`, or -1 when the chunk has no value. */\n eqPos: number;\n /** Offset one past the chunk. */\n end: number;\n hasValue: boolean;\n decodedName: string;\n}\n\n/**\n * Collects a bracketed chunk into the index-format group, to be sorted by index\n * after the full pass. Returns `false` when the bracket is not a numeric index\n * (`a[]`, `a[x]`, `a[`), so the caller falls back to insertion-order push. (#856)\n *\n * @internal\n */\nfunction collectIndexedChunk(\n chunk: ParsedChunk,\n strategies: ResolvedStrategies,\n indexedGroups: Map<string, [number, unknown][]>,\n): boolean {\n const {\n searchPart,\n nameEnd,\n nameSourceEnd,\n eqPos,\n end,\n hasValue,\n decodedName,\n } = chunk;\n const index = bracketIndex(searchPart, nameEnd, nameSourceEnd);\n\n if (index === null) {\n return false;\n }\n\n const value = decodeParamValue(searchPart, eqPos, end, hasValue, strategies);\n const group = indexedGroups.get(decodedName);\n\n if (group === undefined) {\n indexedGroups.set(decodedName, [[index, value]]);\n } else {\n group.push([index, value]);\n }\n\n // Stryker disable next-line BooleanLiteral: equivalent — returning false makes the caller ALSO push via insertion order, but indexedGroups was already populated above and parseIntoInternal overwrites the key with the index-sorted result, so the final params are identical (proven by injection).\n return true;\n}\n\n/**\n * Processes a single query parameter chunk and adds to params.\n *\n * `indexedGroups` is supplied only for `arrayFormat: \"index\"`: bracketed chunks\n * with a numeric index are collected there (to be sorted by index after the full\n * pass) instead of pushed in insertion order. (#856)\n *\n * @internal\n */\nfunction processParamChunk(\n searchPart: string,\n start: number,\n end: number,\n params: Record<string, unknown>,\n strategies: ResolvedStrategies,\n eqPos: number,\n indexedGroups?: Map<string, [number, unknown][]>,\n): void {\n // `eqPos` is the position of the next `=` at or after `start`, resolved once by\n // the caller's monotonic cursor (#1316) — never re-scanned here. `eqPos < end`\n // means it falls inside THIS chunk (so the chunk has a value); otherwise the\n // chunk is key-only.\n const hasValue = eqPos !== -1 && eqPos < end;\n\n const nameSourceEnd = hasValue ? eqPos : end;\n let nameEnd = nameSourceEnd;\n let hasBrackets = false;\n\n for (let i = start; i < nameSourceEnd; i++) {\n if (searchPart.codePointAt(i) !== 91) {\n continue;\n }\n\n // '['\n nameEnd = i;\n hasBrackets = true;\n\n break;\n }\n\n const decodedName = decodeValue(searchPart.slice(start, nameEnd));\n\n // Index array format: order by the bracket index, not insertion. A non-numeric\n // bracket (`a[]`, `a[x]`) returns false → falls through to insertion-order push.\n if (\n indexedGroups !== undefined &&\n hasBrackets &&\n collectIndexedChunk(\n { searchPart, nameEnd, nameSourceEnd, eqPos, end, hasValue, decodedName },\n strategies,\n indexedGroups,\n )\n ) {\n return;\n }\n\n // Comma array decode: split raw value before individual element decoding\n if (!hasBrackets && hasValue && strategies.array.decodeValue) {\n const rawValue = searchPart.slice(eqPos + 1, end);\n const parts = strategies.array.decodeValue(rawValue);\n\n if (parts) {\n for (const part of parts) {\n addToParams(params, decodedName, decode(part, strategies), true);\n }\n\n return;\n }\n }\n\n const decodedValue = decodeParamValue(\n searchPart,\n eqPos,\n end,\n hasValue,\n strategies,\n );\n\n addToParams(params, decodedName, decodedValue, hasBrackets);\n}\n\n// =============================================================================\n// Parse\n// =============================================================================\n\n/**\n * Parse an ALREADY-EXTRACTED query string (no path prefix, no leading \"?\") into\n * an object of parameters.\n *\n * The input must already be split at the first \"?\" — `SegmentMatcher.#preparePath`\n * does this before the DI call, so route-tree wires `parseQuery` (not a\n * path-accepting wrapper) as its query parser: re-splitting the input here would\n * break at a \"?\" *inside* a query value (legal per RFC 3986), silently dropping\n * the param (and unmatching the whole URL under `strictQueryParams`). (#1292)\n *\n * @example\n * ```typescript\n * parseQuery(\"page=1&sort=name\");\n * // => { page: 1, sort: \"name\" }\n *\n * parseQuery(\"items[]=a&items[]=b\", { arrayFormat: \"brackets\" });\n * // => { items: [\"a\", \"b\"] }\n * ```\n */\nexport const parseQuery = (\n search: string,\n opts?: Options,\n): Record<string, unknown> => {\n // Fast path: empty query string\n if (search === \"\" || search === \"?\") {\n return {};\n }\n\n // makeOptions(undefined) returns the cached DEFAULT_OPTIONS (auto) — the same\n // defaults `build` uses — so parseQuery(build(x)) === x even without options. (#744)\n const params: Record<string, unknown> = {};\n\n parseIntoInternal(search, params, makeOptions(opts).strategies);\n\n return params;\n};\n\n/**\n * Internal function to parse a query string into a target object.\n * The shared parse engine behind `parseQuery`.\n *\n * @internal\n */\nfunction parseIntoInternal(\n searchPart: string,\n params: Record<string, unknown>,\n strategies: ResolvedStrategies,\n): void {\n // `index` format orders by the bracket index; collect (index, value) pairs and\n // sort after the pass. `undefined` for every other format (no overhead). (#856)\n const indexedGroups = strategies.array.indexed\n ? new Map<string, [number, unknown][]>()\n : undefined;\n\n let start = 0;\n const length = searchPart.length;\n\n // Monotonic cursor for the next `=`. Its position only ever moves forward with\n // `start`, so the whole parse does a single amortised O(n) scan for `=` —\n // replacing `processParamChunk`'s former per-chunk `indexOf(\"=\", start)`, which\n // scanned to the end of the string on every key-only chunk and made `parse`\n // O(n²) on `\"a&a&…\"` (#1316). `-2` = not yet searched; `-1` = no `=` remains.\n let eqCache = -2;\n\n while (start < length) {\n let end = searchPart.indexOf(\"&\", start);\n\n if (end === -1) {\n end = length;\n }\n\n // Skip empty chunks — a `&&`, a leading `&`, or a trailing `&` produces a\n // zero-length span that carries no name and no value. Processing it would\n // decode the empty name to `\"\"` and the missing value to `null`, injecting a\n // junk `{ \"\": null }` param (and `[null, …]` on repeats) (#1156). An\n // intentional empty-key chunk always carries an `=` (`\"=1\"` → `end > start`),\n // so it is unaffected.\n if (end > start) {\n // Advance the cursor only when the cached `=` is behind the current chunk;\n // once it reports `-1` (no `=` left in the string) it is final.\n if (eqCache !== -1 && eqCache < start) {\n eqCache = searchPart.indexOf(\"=\", start);\n }\n\n processParamChunk(\n searchPart,\n start,\n end,\n params,\n strategies,\n eqCache,\n indexedGroups,\n );\n }\n\n start = end + 1;\n }\n\n if (indexedGroups !== undefined) {\n for (const [name, pairs] of indexedGroups) {\n // Stable sort by index (V8 sort is stable) → equal indices keep arrival order.\n pairs.sort((left, right) => left[0] - right[0]);\n assignParam(\n params,\n name,\n pairs.map((pair) => pair[1]),\n );\n }\n }\n}\n\n// =============================================================================\n// Build\n// =============================================================================\n\n/**\n * Build a querystring from an object of parameters.\n *\n * Note: Empty arrays produce an empty string, so `parseQuery(build({ items: [] }))`\n * will not contain the `items` key. This is expected behavior for all array\n * formats including `comma` — the key is erased uniformly (INVARIANTS Parse/Build #9).\n *\n * @example\n * ```typescript\n * build({ page: 1, sort: \"name\" });\n * // => \"page=1&sort=name\"\n *\n * build({ items: [\"a\", \"b\"] }, { arrayFormat: \"brackets\" });\n * // => \"items[]=a&items[]=b\"\n * ```\n */\nexport const build = (\n params: Record<string, unknown>,\n opts?: Options,\n): string => {\n // Fast path for empty params (common case)\n const keys = Object.keys(params);\n\n if (keys.length === 0) {\n return \"\";\n }\n\n const options = makeOptions(opts);\n\n // Optimized: single loop instead of filter().map().filter().join()\n // Avoids creating 3 intermediate arrays\n const parts: string[] = [];\n\n for (const key of keys) {\n const value = params[key];\n\n // Skip undefined values (not serialisable)\n if (value === undefined) {\n continue;\n }\n\n const encoded = encode(key, value, options);\n\n // Skip empty strings (e.g., from nullFormat: \"hidden\")\n if (encoded) {\n parts.push(encoded);\n }\n }\n\n return parts.join(\"&\");\n};\n","/**\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","// packages/core/src/namespaces/OptionsNamespace/constants.ts\n\nimport { DEFAULT_QUERY_PARAMS } from \"../../engine\";\n\nimport type { Options } from \"../../types\";\n\n/**\n * Default options for the router.\n */\nexport const defaultOptions: Options = {\n defaultRoute: \"\",\n defaultParams: {},\n trailingSlash: \"preserve\",\n caseSensitive: true,\n queryParamsMode: \"loose\",\n queryParams: DEFAULT_QUERY_PARAMS,\n urlParamsEncoding: \"default\",\n allowNotFound: true,\n rewritePathOnMatch: true,\n} satisfies Options;\n","// packages/core/src/namespaces/OptionsNamespace/helpers.ts\n\nimport type { Options, Params } from \"../../types\";\n\n/**\n * Recursively freezes an object and all nested objects.\n * Only freezes plain objects, not primitives or special objects.\n */\nexport function deepFreeze<T extends object>(obj: T): Readonly<T> {\n Object.freeze(obj);\n\n for (const value of Object.values(obj as Record<string, unknown>)) {\n if (value && typeof value === \"object\" && value.constructor === Object) {\n deepFreeze(value);\n }\n }\n\n return obj;\n}\n\n/**\n * Resolves an option value that can be static or a callback.\n * If the value is a function, calls it with getDependency and returns the result.\n * Otherwise, returns the value as-is.\n */\nexport function resolveOption(\n value: Options[\"defaultRoute\"],\n getDependency: (name: string) => unknown,\n): string;\n\nexport function resolveOption(\n value: Options[\"defaultParams\"],\n getDependency: (name: string) => unknown,\n): Params;\n\nexport function resolveOption(\n value: Options[\"defaultRoute\"] | Options[\"defaultParams\"],\n getDependency: (name: string) => unknown,\n): string | Params {\n if (typeof value === \"function\") {\n // Runtime getDependency is (name: string) => unknown, but DefaultRouteCallback<object>\n // expects <K extends keyof object>(name: K) => object[K] where keyof object = never.\n // Cast needed to bridge generic constraint mismatch.\n return value(getDependency as never);\n }\n\n return value;\n}\n","// packages/core/src/namespaces/OptionsNamespace/validators.ts\n\n/**\n * Minimal crash guard for options.\n * Full DX validation moved to @real-router/validation-plugin (retrospective pattern).\n */\nexport function validateOptionsIsObject(\n options: unknown,\n): asserts options is Record<string, unknown> {\n if (!options || typeof options !== \"object\" || Array.isArray(options)) {\n throw new TypeError(\"[router.constructor] options must be a plain object\");\n }\n}\n","// packages/core/src/namespaces/OptionsNamespace/OptionsNamespace.ts\n\nimport { defaultOptions } from \"./constants\";\nimport { deepFreeze } from \"./helpers\";\nimport { validateOptionsIsObject } from \"./validators\";\n\nimport type { Options } from \"../../types\";\n\nexport class OptionsNamespace {\n readonly #options: Readonly<Options>;\n\n constructor(initialOptions: Partial<Options> = {}) {\n this.#options = deepFreeze({\n ...defaultOptions,\n ...initialOptions,\n });\n }\n\n static validateOptionsIsObject(\n options: unknown,\n ): asserts options is Record<string, unknown> {\n validateOptionsIsObject(options);\n }\n\n get(): Readonly<Options> {\n return this.#options;\n }\n}\n","// packages/core/src/namespaces/StateNamespace/helpers.ts\n\nexport function areParamValuesEqual(val1: unknown, val2: unknown): boolean {\n if (val1 === val2) {\n return true;\n }\n\n if (Array.isArray(val1) && Array.isArray(val2)) {\n if (val1.length !== val2.length) {\n return false;\n }\n\n // eslint-disable-next-line unicorn/no-for-loop -- hot path: for-of entries() allocates iterator per recursive call\n for (let i = 0; i < val1.length; i++) {\n if (!areParamValuesEqual(val1[i], val2[i])) {\n return false;\n }\n }\n\n return true;\n }\n\n return false;\n}\n","// packages/core/src/stateMetaStore.ts\n\nimport type { Params, State } from \"./types\";\n\nconst store = new WeakMap<State, Params>();\n\n/** @internal */\nexport function getStateMetaParams(state: State): Params | undefined {\n return store.get(state);\n}\n\n/** @internal */\nexport function setStateMetaParams(state: State, params: Params): void {\n store.set(state, params);\n}\n","// packages/core/src/namespaces/StateNamespace/StateNamespace.ts\n\nimport { areParamValuesEqual } from \"./helpers\";\nimport { DEFAULT_TRANSITION, EMPTY_PARAMS } from \"../../constants\";\nimport { freezeStateInPlace } from \"../../helpers\";\nimport { setStateMetaParams } from \"../../stateMetaStore\";\n\nimport type { StateNamespaceDependencies } from \"./types\";\nimport type { RouteTreeStateMeta } from \"../../engine\";\nimport type { Params, State } from \"../../types\";\n\n/**\n * Independent namespace for managing router state storage and creation.\n *\n * Static methods handle validation (called by facade).\n * Instance methods handle state storage, freezing, and creation.\n */\nexport class StateNamespace {\n /**\n * Cached frozen state - avoids structuredClone on every getState() call.\n */\n #frozenState: State | undefined = undefined;\n\n /**\n * Previous state before the last setState call.\n */\n #previousState: State | undefined = undefined;\n\n /**\n * Dependencies injected from Router.\n */\n #deps!: StateNamespaceDependencies;\n\n // =========================================================================\n // Instance methods (trust input - already validated by facade)\n // =========================================================================\n\n /**\n * Returns the current router state.\n *\n * The returned state is deeply frozen (immutable) for safety.\n * Returns `undefined` if the router has not been started or has been stopped.\n */\n get<P extends Params = Params>(): State<P> | undefined {\n return this.#frozenState as State<P> | undefined; // NOSONAR -- generic narrowing needed for public API\n }\n\n /**\n * Sets the current router state.\n *\n * The state is deeply frozen before storage to ensure immutability.\n * The previous state is preserved and accessible via `getPrevious()`.\n *\n * @param state - Already validated by facade, or undefined to clear\n */\n set(state: State | undefined): void {\n // Preserve current state as previous before updating\n this.#previousState = this.#frozenState;\n\n // If state is already frozen (from makeState()), use it directly.\n // For external states, freeze in place without cloning.\n this.#frozenState = state ? freezeStateInPlace(state) : undefined;\n }\n\n /**\n * Returns the previous router state (before the last navigation).\n */\n getPrevious(): State | undefined {\n return this.#previousState;\n }\n\n reset(): void {\n this.#frozenState = undefined;\n this.#previousState = undefined;\n }\n\n // =========================================================================\n // Dependency Injection\n // =========================================================================\n\n /**\n * Sets dependencies for state creation methods.\n * Must be called before using makeState, areStatesEqual, etc.\n */\n setDependencies(deps: StateNamespaceDependencies): void {\n this.#deps = deps;\n }\n\n // =========================================================================\n // State Creation Methods\n // =========================================================================\n\n /**\n * Creates a state object for a route.\n *\n * `params` is frozen at creation so it is always immutable, even when\n * `skipFreeze=true` is passed to defer the outer `Object.freeze(state)` call.\n * This keeps params-freezing invariants independent of transition-pipeline\n * mutation (e.g. `completeTransition` attaching `state.transition`).\n *\n * `context` is initialized as a fresh empty object — intentionally NOT frozen\n * so plugins can publish data via `claim.write(state, value)` after creation.\n */\n makeState<P extends Params = Params>(\n name: string,\n params?: P,\n path?: string,\n meta?: RouteTreeStateMeta,\n skipFreeze?: boolean,\n ): State<P> {\n // Optimization: O(1) lookup instead of O(depth) ancestor iteration\n const defaultParamsConfig = this.#deps.getDefaultParams();\n const hasDefaultParams = Object.hasOwn(defaultParamsConfig, name);\n\n // Conditional allocation: avoid spreading when no defaultParams exist\n let mergedParams: P;\n\n if (hasDefaultParams) {\n mergedParams = Object.freeze({\n ...defaultParamsConfig[name],\n ...params,\n }) as P;\n } else if (!params || params === EMPTY_PARAMS) {\n mergedParams = EMPTY_PARAMS as P;\n } else {\n mergedParams = Object.freeze({ ...params });\n }\n\n const state = {\n name,\n params: mergedParams,\n path: path ?? this.#deps.buildPath(name, params),\n context: {},\n ...(!skipFreeze && { transition: DEFAULT_TRANSITION }),\n } as State<P>;\n\n if (meta) {\n setStateMetaParams(state, meta);\n }\n\n return skipFreeze ? state : freezeStateInPlace(state);\n }\n\n // =========================================================================\n // State Comparison Methods\n // =========================================================================\n\n /**\n * Compares two states for equality.\n * By default, ignores query params (only compares URL params).\n */\n areStatesEqual(\n state1: State | undefined,\n state2: State | undefined,\n ignoreQueryParams = true,\n ): boolean {\n if (!state1 || !state2) {\n return !!state1 === !!state2;\n }\n\n if (state1.name !== state2.name) {\n return false;\n }\n\n if (ignoreQueryParams) {\n // URL (path) param names are cached at the routes layer and invalidated\n // on every tree mutation, so this stays correct after replace() (#723).\n const urlParams = this.#deps.getUrlParams(state1.name);\n\n for (const urlParam of urlParams) {\n if (\n !areParamValuesEqual(state1.params[urlParam], state2.params[urlParam])\n ) {\n return false;\n }\n }\n\n return true;\n }\n\n const state1Keys = Object.keys(state1.params);\n const state2Keys = Object.keys(state2.params);\n\n if (state1Keys.length !== state2Keys.length) {\n return false;\n }\n\n for (const param of state1Keys) {\n if (\n !(param in state2.params) ||\n !areParamValuesEqual(state1.params[param], state2.params[param])\n ) {\n return false;\n }\n }\n\n return true;\n }\n}\n","// packages/core/src/namespaces/PluginsNamespace/constants.ts\n\nimport {\n events as EVENTS_CONST,\n plugins as PLUGINS_CONST,\n} from \"../../constants\";\n\nimport type { EventName } from \"../../types\";\n\n/**\n * Maps plugin method names to router event names.\n */\nexport const EVENTS_MAP = {\n [PLUGINS_CONST.ROUTER_START]: EVENTS_CONST.ROUTER_START,\n [PLUGINS_CONST.ROUTER_STOP]: EVENTS_CONST.ROUTER_STOP,\n [PLUGINS_CONST.TRANSITION_SUCCESS]: EVENTS_CONST.TRANSITION_SUCCESS,\n [PLUGINS_CONST.TRANSITION_START]: EVENTS_CONST.TRANSITION_START,\n [PLUGINS_CONST.TRANSITION_LEAVE_APPROVE]:\n EVENTS_CONST.TRANSITION_LEAVE_APPROVE,\n [PLUGINS_CONST.TRANSITION_ERROR]: EVENTS_CONST.TRANSITION_ERROR,\n [PLUGINS_CONST.TRANSITION_CANCEL]: EVENTS_CONST.TRANSITION_CANCEL,\n} as const satisfies Record<\n (typeof PLUGINS_CONST)[keyof typeof PLUGINS_CONST],\n EventName\n>;\n\n/**\n * Plugin method names that correspond to router events.\n */\nexport const EVENT_METHOD_NAMES = Object.keys(\n EVENTS_MAP,\n) as (keyof typeof EVENTS_MAP)[];\n\nexport const LOGGER_CONTEXT = \"router.usePlugin\";\n","// packages/core/src/namespaces/PluginsNamespace/validators.ts\n\n/**\n * Static validation functions for PluginsNamespace.\n * Called by Router facade before instance methods.\n */\n\nimport type { Plugin } from \"../../types\";\n\n/**\n * Validates that a plugin factory returned a valid plugin object.\n */\nexport function validatePlugin(plugin: Plugin): void {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (!(plugin && typeof plugin === \"object\") || Array.isArray(plugin)) {\n throw new TypeError(\n `[router.usePlugin] Plugin factory must return an object, got ${typeof plugin}`,\n );\n }\n\n // Detect async factory (returns Promise)\n if (typeof (plugin as unknown as { then?: unknown }).then === \"function\") {\n throw new TypeError(\n `[router.usePlugin] Async plugin factories are not supported. ` +\n `Factory returned a Promise instead of a plugin object.`,\n );\n }\n}\n","// packages/core/src/namespaces/PluginsNamespace/PluginsNamespace.ts\n\nimport { EVENTS_MAP, EVENT_METHOD_NAMES, LOGGER_CONTEXT } from \"./constants\";\nimport { validatePlugin } from \"./validators\";\n\nimport type { PluginsDependencies } from \"./types\";\nimport type {\n DefaultDependencies,\n Plugin,\n Unsubscribe,\n PluginFactory,\n} from \"../../types\";\n\n/**\n * Independent namespace for managing plugins.\n *\n * Static methods handle validation (called by facade).\n * Instance methods handle storage and business logic.\n */\nexport class PluginsNamespace<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n> {\n readonly #plugins = new Set<PluginFactory<Dependencies>>();\n readonly #unsubscribes = new Set<Unsubscribe>();\n\n #deps!: PluginsDependencies<Dependencies>;\n\n // =========================================================================\n // Static validation methods (called by facade before instance methods)\n // Proxy to functions in validators.ts for separation of concerns\n // =========================================================================\n\n static validatePlugin(plugin: Plugin): void {\n validatePlugin(plugin);\n }\n\n // =========================================================================\n // Dependency injection\n // =========================================================================\n\n setDependencies(deps: PluginsDependencies<Dependencies>): void {\n this.#deps = deps;\n }\n\n // =========================================================================\n // Instance methods (trust input - already validated by facade)\n // =========================================================================\n\n /**\n * Returns the number of registered plugins.\n * Used by facade for limit validation.\n */\n /* v8 ignore next 3 -- @preserve: only called via validator interface (ctx.validator?.plugins.validatePluginLimit), not reachable without validation plugin */\n count(): number {\n return this.#plugins.size;\n }\n\n /**\n * Registers one or more plugin factories.\n * Returns unsubscribe function to remove all added plugins.\n * Input already validated by facade (limit, duplicates).\n *\n * @param factories - Already validated by facade\n */\n use(...factories: PluginFactory<Dependencies>[]): Unsubscribe {\n // Emit warnings for count thresholds (not validation, just warnings)\n this.#deps\n .getValidator()\n ?.plugins.validateCountThresholds(this.#plugins.size + factories.length);\n\n // Fast path for single plugin (common case)\n if (factories.length === 1) {\n const factory = factories[0];\n const cleanup = this.#startPlugin(factory);\n\n this.#plugins.add(factory);\n\n let unsubscribed = false;\n\n const unsubscribe: Unsubscribe = () => {\n if (unsubscribed) {\n return;\n }\n\n unsubscribed = true;\n this.#plugins.delete(factory);\n this.#unsubscribes.delete(unsubscribe);\n try {\n cleanup();\n } catch (error) {\n this.#deps.logger.error(\n LOGGER_CONTEXT,\n \"Error during cleanup:\",\n error,\n );\n }\n };\n\n this.#unsubscribes.add(unsubscribe);\n\n return unsubscribe;\n }\n\n // Deduplicate batch with warning (validation already done by facade)\n const seenInBatch = this.#deduplicateBatch(factories);\n\n // Track successfully initialized plugins for cleanup\n const initializedPlugins: {\n factory: PluginFactory<Dependencies>;\n cleanup: Unsubscribe;\n }[] = [];\n\n // Initialize deduplicated plugins sequentially\n try {\n for (const plugin of seenInBatch) {\n const cleanup = this.#startPlugin(plugin);\n\n initializedPlugins.push({ factory: plugin, cleanup });\n }\n } catch (error) {\n // Rollback on failure - cleanup all initialized plugins\n for (const { cleanup } of initializedPlugins) {\n try {\n cleanup();\n } catch (cleanupError) {\n this.#deps.logger.error(\n LOGGER_CONTEXT,\n \"Cleanup error:\",\n cleanupError,\n );\n }\n }\n\n throw error;\n }\n\n // Commit phase - add to registry\n for (const { factory } of initializedPlugins) {\n this.#plugins.add(factory);\n }\n\n // Return unsubscribe function\n let unsubscribed = false;\n\n const unsubscribe: Unsubscribe = () => {\n if (unsubscribed) {\n return;\n }\n\n unsubscribed = true;\n this.#unsubscribes.delete(unsubscribe);\n\n for (const { factory } of initializedPlugins) {\n this.#plugins.delete(factory);\n }\n\n for (const { cleanup } of initializedPlugins) {\n try {\n cleanup();\n } catch (error) {\n this.#deps.logger.error(\n LOGGER_CONTEXT,\n \"Error during cleanup:\",\n error,\n );\n }\n }\n };\n\n this.#unsubscribes.add(unsubscribe);\n\n return unsubscribe;\n }\n\n /**\n * Returns registered plugin factories.\n */\n getAll(): PluginFactory<Dependencies>[] {\n return [...this.#plugins];\n }\n\n /**\n * Checks if a plugin factory is registered.\n * Used internally by validation to avoid array allocation.\n */\n /* v8 ignore next 3 -- @preserve: only called via validator interface, not reachable without validation plugin */\n has(factory: PluginFactory<Dependencies>): boolean {\n return this.#plugins.has(factory);\n }\n\n /**\n * Disposes all registered plugins by running their teardown callbacks\n * and removing event listener subscriptions.\n * Active disposal is required because plugins have an active lifecycle\n * (event subscriptions, teardown hooks).\n * Named \"dispose\" (not \"clear\") because there is active cleanup to perform.\n */\n disposeAll(): void {\n for (const unsubscribe of this.#unsubscribes) {\n unsubscribe();\n }\n\n this.#plugins.clear();\n this.#unsubscribes.clear();\n }\n\n // =========================================================================\n // Private methods\n // =========================================================================\n\n /**\n * Deduplicates batch with warning for duplicates within batch.\n * Validation (existing duplicates) is done by facade.\n */\n #deduplicateBatch(\n plugins: PluginFactory<Dependencies>[],\n ): Set<PluginFactory<Dependencies>> {\n const seenInBatch = new Set<PluginFactory<Dependencies>>();\n\n for (const plugin of plugins) {\n if (seenInBatch.has(plugin)) {\n this.#deps.getValidator()?.plugins.warnBatchDuplicates(plugins);\n } else {\n seenInBatch.add(plugin);\n }\n }\n\n return seenInBatch;\n }\n\n #startPlugin(pluginFactory: PluginFactory<Dependencies>): Unsubscribe {\n const appliedPlugin = this.#deps.compileFactory(pluginFactory);\n\n PluginsNamespace.validatePlugin(appliedPlugin);\n this.#deps.getValidator()?.plugins.validatePluginKeys(appliedPlugin);\n\n Object.freeze(appliedPlugin);\n\n // Collect all unsubscribe functions\n const removeEventListeners: Unsubscribe[] = [];\n\n // Subscribe plugin methods to corresponding router events\n for (const methodName of EVENT_METHOD_NAMES) {\n if (methodName in appliedPlugin) {\n if (typeof appliedPlugin[methodName] === \"function\") {\n removeEventListeners.push(\n this.#deps.addEventListener(\n EVENTS_MAP[methodName],\n appliedPlugin[methodName],\n ),\n );\n\n if (methodName === \"onStart\" && this.#deps.canNavigate()) {\n this.#deps.getValidator()?.plugins.warnPluginAfterStart(methodName);\n }\n } else {\n this.#deps.getValidator()?.plugins.warnPluginMethodType(methodName);\n }\n }\n }\n\n // Return composite cleanup function\n return () => {\n for (const removeListener of removeEventListeners) {\n removeListener();\n }\n\n if (typeof appliedPlugin.teardown === \"function\") {\n appliedPlugin.teardown();\n }\n };\n }\n}\n","// packages/core/src/namespaces/RouteLifecycleNamespace/RouteLifecycleNamespace.ts\n\nimport type { RouteLifecycleDependencies } from \"./types\";\nimport type {\n DefaultDependencies,\n GuardFn,\n State,\n GuardFnFactory,\n} from \"../../types\";\n\n// Boolean shorthand has only two possible values, so the guard and its factory\n// are module-level singletons — registering `true`/`false` reuses one cached\n// factory instead of allocating a fresh closure per call (#962).\nconst TRUE_GUARD: GuardFn = () => true;\nconst FALSE_GUARD: GuardFn = () => false;\nconst TRUE_FACTORY: GuardFnFactory = () => TRUE_GUARD;\nconst FALSE_FACTORY: GuardFnFactory = () => FALSE_GUARD;\n\n/**\n * Converts a boolean value to a guard function factory.\n * Used for the shorthand syntax where true/false is passed instead of a function.\n * Returns one of two cached factories — no per-call allocation (#962).\n */\nfunction booleanToFactory<Dependencies extends DefaultDependencies>(\n value: boolean,\n): GuardFnFactory<Dependencies> {\n return value ? TRUE_FACTORY : FALSE_FACTORY;\n}\n\n/**\n * Origin lane for a guard clear. Every `clearCanActivate` / `clearCanDeactivate`\n * caller names its lane — there is no origin-blind default — so a new call site\n * cannot silently wipe both the route-config and the external guard (#1171):\n *\n * - `\"definition\"` — clear only the route-config guard (`update(name, {…: null})`, #952).\n * - `\"external\"` — clear only the external, component-managed guard\n * (`removeXGuard()` and post-leave auto-cleanup — the inverse of `addXGuard()`).\n * - `\"both\"` — clear both (route removal / router teardown; the route is gone).\n */\nexport type GuardClearScope = \"definition\" | \"external\" | \"both\";\n\n/**\n * Source of truth for `canActivate` / `canDeactivate` guards.\n *\n * Storage is split by origin into four factory Maps (definition vs external,\n * each ×activate/deactivate); a single compiled-function Map per kind backs\n * navigation (\"external wins\" — when a route holds both a definition and an\n * external guard, the compiled guard is the external one, regardless of\n * registration order; #1174). `getFunctions()` returns a cached\n * `[deactivate, activate]` tuple for the hot path (stable reference, no\n * per-navigate allocation).\n *\n * All input validation is handled upstream by `getLifecycleApi` and\n * `getRoutesApi` — this class has no static methods.\n *\n * **Ordering convention.** Every paired surface lists **deactivate before\n * activate**: `getFunctions()` / `getFactories()` return `[deactivate,\n * activate]`, `getFactoriesByOrigin()` returns that tuple per origin, and\n * `canNavigateTo(toDeactivate, toActivate, …)` takes deactivate first. Keep\n * any new paired surface consistent with this order.\n */\nexport class RouteLifecycleNamespace<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n> {\n // Storage split by origin: definition vs external. Resolution is\n // EXTERNAL-WINS regardless of registration order (#1174): the compiled slot\n // reflects the external factory whenever one exists, else the definition. One\n // policy across every path — `#registerHandler` (keeps external over a later\n // definition), `#recompileSlot`, and `clearDefinitionGuards` (#1192) — so a\n // clone's fixed definition→external replay yields the source's effective guard\n // with no extra tracking. Both semantics are expressed over these primary Maps.\n readonly #definitionActivateFactories = new Map<\n string,\n GuardFnFactory<Dependencies>\n >();\n readonly #externalActivateFactories = new Map<\n string,\n GuardFnFactory<Dependencies>\n >();\n readonly #definitionDeactivateFactories = new Map<\n string,\n GuardFnFactory<Dependencies>\n >();\n readonly #externalDeactivateFactories = new Map<\n string,\n GuardFnFactory<Dependencies>\n >();\n // Compiled-function view. Single Map per kind because navigation does not\n // distinguish origin — it just runs the effective guard. Set on add\n // (external-wins — a definition does not overwrite a live external, #1174)\n // and recompiled on clear from whichever origin Map still holds the slot.\n readonly #canDeactivateFunctions = new Map<string, GuardFn>();\n readonly #canActivateFunctions = new Map<string, GuardFn>();\n // Cached tuple — Maps never change reference, so this is stable\n readonly #functionsTuple: [Map<string, GuardFn>, Map<string, GuardFn>] = [\n this.#canDeactivateFunctions,\n this.#canActivateFunctions,\n ];\n\n #deps!: RouteLifecycleDependencies<Dependencies>;\n\n setDependencies(deps: RouteLifecycleDependencies<Dependencies>): void {\n this.#deps = deps;\n }\n\n getHandlerCount(type: \"activate\" | \"deactivate\"): number {\n const definitionMap =\n type === \"activate\"\n ? this.#definitionActivateFactories\n : this.#definitionDeactivateFactories;\n const externalMap =\n type === \"activate\"\n ? this.#externalActivateFactories\n : this.#externalDeactivateFactories;\n\n if (definitionMap.size === 0) {\n return externalMap.size;\n }\n\n if (externalMap.size === 0) {\n return definitionMap.size;\n }\n\n const names = new Set(definitionMap.keys());\n\n for (const name of externalMap.keys()) {\n names.add(name);\n }\n\n return names.size;\n }\n\n /**\n * Pre-flights the #961 handler-limit `RangeError` into the route-CRUD PREPARE\n * phase (#1046). `#registerHandler`'s per-slot limit check throws AFTER the\n * tree/config swap, so `add`/`replace`/`update` tore post-commit when the\n * validator was installed and the per-type count was at `maxLifecycleHandlers`.\n * Running the same check here — before any store mutation — restores atomicity\n * (#951/#956/#698): a batch that would exceed the limit aborts before a single\n * write.\n *\n * Only NEW slots count (an overwrite leaves the union count unchanged, mirroring\n * `#registerHandler`). For `replace` (`clearsDefinition = true`) the definition\n * guards are about to be cleared, so the projection runs against the surviving\n * EXTERNAL guards only — exactly the post-clear state the install loop sees.\n * Plugin-gated: a no-op without the validator (the limit is opt-in).\n *\n * @param activateNames - route names a `canActivate` would be registered for\n * @param deactivateNames - route names a `canDeactivate` would be registered for\n * @param clearsDefinition - true for `replace` (definition guards cleared first)\n */\n preflightHandlerLimit(\n activateNames: Iterable<string>,\n deactivateNames: Iterable<string>,\n clearsDefinition: boolean,\n ): void {\n const validator = this.#deps.getValidator();\n\n if (!validator) {\n return;\n }\n\n const check = (\n type: \"activate\" | \"deactivate\",\n names: Iterable<string>,\n methodName: string,\n ): void => {\n const { definition, external } = this.#getFactoryMaps(type);\n\n // A name already holding a guard of this type is an overwrite (no new\n // slot, mirroring `#registerHandler`). After a definition-clear (replace)\n // only EXTERNAL guards survive, so the existing-name check — and the base\n // count below — run against `external` alone, matching the post-clear\n // install state the loop sees.\n let newSlots = 0;\n\n for (const name of names) {\n const isExisting = clearsDefinition\n ? external.has(name)\n : definition.has(name) || external.has(name);\n\n if (!isExisting) {\n newSlots++;\n }\n }\n\n if (newSlots === 0) {\n return;\n }\n\n // The install loop throws when a new-slot registration observes\n // `count >= max`. Starting from `base` and adding `newSlots` new names,\n // the highest pre-register count it reaches is `base + newSlots - 1` —\n // replicate that worst case so the pre-flight throws iff the loop would.\n const base = clearsDefinition\n ? external.size\n : this.getHandlerCount(type);\n\n validator.lifecycle.validateHandlerLimit(base + newSlots - 1, methodName);\n };\n\n check(\"activate\", activateNames, \"canActivate\");\n check(\"deactivate\", deactivateNames, \"canDeactivate\");\n }\n\n // =========================================================================\n // Instance methods\n // =========================================================================\n\n /**\n * Adds a canActivate guard for a route.\n *\n * @param name - Route name (input-validated by facade)\n * @param handler - Guard function or boolean (input-validated by facade)\n * @param isFromDefinition - True when guard comes from route definition\n * (lands in the definition Map; subject to `clearDefinitionGuards()`).\n * False (default) when added via `getLifecycleApi().addActivateGuard(...)`\n * (lands in the external Map; survives `replace()`).\n *\n * External wins at runtime (#1174): when a route holds both a definition and\n * an external guard, the compiled function is the external one, regardless of\n * registration order. Within one origin the most recent add overwrites. Origin\n * determines which Map the factory is filed under (relevant for\n * `clearDefinitionGuards()` and `cloneRouter` re-registration).\n */\n addCanActivate(\n name: string,\n handler: GuardFnFactory<Dependencies> | boolean,\n isFromDefinition = false,\n precompiledFn?: GuardFn,\n ): void {\n this.#registerHandler(\n \"activate\",\n name,\n handler,\n isFromDefinition,\n \"canActivate\",\n precompiledFn,\n );\n }\n\n /**\n * Adds a canDeactivate guard for a route.\n *\n * Symmetric counterpart to {@link addCanActivate}.\n */\n addCanDeactivate(\n name: string,\n handler: GuardFnFactory<Dependencies> | boolean,\n isFromDefinition = false,\n precompiledFn?: GuardFn,\n ): void {\n this.#registerHandler(\n \"deactivate\",\n name,\n handler,\n isFromDefinition,\n \"canDeactivate\",\n precompiledFn,\n );\n }\n\n /**\n * Removes a canActivate guard for a route. `scope` names the origin lane\n * (see {@link GuardClearScope}) — there is no origin-blind default, so every\n * caller commits to a lane and a new call site cannot silently clear both.\n * Delegates to {@link #clearGuard} (mirrors the add side's `#registerHandler`).\n *\n * @param name - Route name (already validated by facade)\n * @param scope - Which origin(s) to clear: `\"definition\"` / `\"external\"` / `\"both\"`\n */\n clearCanActivate(name: string, scope: GuardClearScope): void {\n this.#clearGuard(\"activate\", name, scope);\n }\n\n /**\n * Removes a canDeactivate guard for a route. Symmetric counterpart to\n * {@link clearCanActivate}.\n *\n * The `\"external\"` lane is what makes a route-config `canDeactivate` durable:\n * post-leave auto-cleanup (`completeTransition`) and `removeDeactivateGuard()`\n * unregister only the external, component-managed guard (router5 mount/unmount\n * heritage), while a definition guard survives for re-entry — symmetric with\n * definition `canActivate`, which lives as long as the route is in the tree\n * (#1171). Clearing both by default made a config guard one-shot: the first\n * permitted leave erased it, so re-entry was unguarded, `getRoutesApi().get()`\n * lost the field, and a clone taken after the leave never received it\n * (clone invariant #6).\n *\n * @param name - Route name (already validated by facade)\n * @param scope - Which origin(s) to clear: `\"definition\"` / `\"external\"` / `\"both\"`\n */\n clearCanDeactivate(name: string, scope: GuardClearScope): void {\n this.#clearGuard(\"deactivate\", name, scope);\n }\n\n /**\n * Clears all lifecycle handlers (canActivate and canDeactivate).\n * Used by clearRoutes to reset all lifecycle state.\n */\n clearAll(): void {\n this.#definitionActivateFactories.clear();\n this.#externalActivateFactories.clear();\n this.#definitionDeactivateFactories.clear();\n this.#externalDeactivateFactories.clear();\n this.#canActivateFunctions.clear();\n this.#canDeactivateFunctions.clear();\n }\n\n /**\n * Clears only lifecycle handlers that were registered from route definitions.\n * Used by HMR `replace()` to remove definition-sourced guards without\n * touching externally-added guards.\n *\n * For a slot where BOTH a definition and an external guard exist, the external\n * factory survives — and the compiled function is RECOMPILED from it (#1192).\n * Under external-wins (#1174) the compiled slot is already the external guard,\n * so this recompile is idempotent — it re-derives the surviving external factory\n * through the same choke point that keeps clearing correct (and stays robust if\n * the compiled slot were ever out of sync). For a definition-only slot, the\n * compiled function is dropped.\n */\n clearDefinitionGuards(): void {\n for (const name of this.#definitionActivateFactories.keys()) {\n if (this.#externalActivateFactories.has(name)) {\n this.#recompileSlot(\"activate\", name);\n } else {\n this.#canActivateFunctions.delete(name);\n }\n }\n\n for (const name of this.#definitionDeactivateFactories.keys()) {\n if (this.#externalDeactivateFactories.has(name)) {\n this.#recompileSlot(\"deactivate\", name);\n } else {\n this.#canDeactivateFunctions.delete(name);\n }\n }\n\n this.#definitionActivateFactories.clear();\n this.#definitionDeactivateFactories.clear();\n }\n\n /**\n * Returns lifecycle factories as a flat `[deactivate, activate]` tuple of\n * `Record<name, factory>` — the effective view where external wins over\n * definition for the same slot. Used by `getRoutesApi` to enrich route\n * objects with their current canActivate / canDeactivate factories and by\n * the route-removal cleanup path.\n *\n * For cloneRouter (which needs to preserve origin on re-registration), use\n * {@link getFactoriesByOrigin} instead.\n */\n getFactories(): [\n Record<string, GuardFnFactory<Dependencies>>,\n Record<string, GuardFnFactory<Dependencies>>,\n ] {\n const deactivateRecord: Record<string, GuardFnFactory<Dependencies>> = {};\n const activateRecord: Record<string, GuardFnFactory<Dependencies>> = {};\n\n for (const [name, factory] of this.#definitionDeactivateFactories) {\n deactivateRecord[name] = factory;\n }\n for (const [name, factory] of this.#externalDeactivateFactories) {\n deactivateRecord[name] = factory;\n }\n\n for (const [name, factory] of this.#definitionActivateFactories) {\n activateRecord[name] = factory;\n }\n for (const [name, factory] of this.#externalActivateFactories) {\n activateRecord[name] = factory;\n }\n\n return [deactivateRecord, activateRecord];\n }\n\n /**\n * Returns factories tagged by origin — definition and external as separate\n * `[deactivate, activate]` tuples. Used by `cloneRouter` to re-register\n * guards on the clone with their original origin flag preserved.\n */\n getFactoriesByOrigin(): {\n definition: [\n Record<string, GuardFnFactory<Dependencies>>,\n Record<string, GuardFnFactory<Dependencies>>,\n ];\n external: [\n Record<string, GuardFnFactory<Dependencies>>,\n Record<string, GuardFnFactory<Dependencies>>,\n ];\n } {\n const defDeact: Record<string, GuardFnFactory<Dependencies>> = {};\n const defAct: Record<string, GuardFnFactory<Dependencies>> = {};\n const extensionDeact: Record<string, GuardFnFactory<Dependencies>> = {};\n const extensionAct: Record<string, GuardFnFactory<Dependencies>> = {};\n\n for (const [name, factory] of this.#definitionDeactivateFactories) {\n defDeact[name] = factory;\n }\n for (const [name, factory] of this.#definitionActivateFactories) {\n defAct[name] = factory;\n }\n for (const [name, factory] of this.#externalDeactivateFactories) {\n extensionDeact[name] = factory;\n }\n for (const [name, factory] of this.#externalActivateFactories) {\n extensionAct[name] = factory;\n }\n\n return {\n definition: [defDeact, defAct],\n external: [extensionDeact, extensionAct],\n };\n }\n\n /**\n * Returns compiled lifecycle functions for transition execution.\n *\n * @returns Tuple of [canDeactivateFunctions, canActivateFunctions] as Maps\n */\n getFunctions(): [Map<string, GuardFn>, Map<string, GuardFn>] {\n return this.#functionsTuple;\n }\n\n canNavigateTo(\n toDeactivate: string[],\n toActivate: string[],\n toState: State,\n fromState: State | undefined,\n ): boolean {\n for (const segment of toDeactivate) {\n if (\n !this.#checkGuardSync(\n this.#canDeactivateFunctions,\n segment,\n toState,\n fromState,\n \"canNavigateTo\",\n )\n ) {\n return false;\n }\n }\n\n for (const segment of toActivate) {\n if (\n !this.#checkGuardSync(\n this.#canActivateFunctions,\n segment,\n toState,\n fromState,\n \"canNavigateTo\",\n )\n ) {\n return false;\n }\n }\n\n return true;\n }\n\n /**\n * Compiles a guard factory to its `GuardFn` WITHOUT registering it — surfaces\n * a throwing / non-function factory eagerly. The prepare-then-commit\n * add/replace path (`adoptRouteArtifacts`) calls this for every pending guard\n * BEFORE the store swap (#956), so a malformed factory aborts the mutation\n * with the store untouched. The returned function is then installed via the\n * `precompiledFn` argument of {@link addCanActivate} / {@link addCanDeactivate}\n * — no re-compile, so a factory with compile-time side effects runs exactly\n * once. Same boolean-shorthand handling + compile + non-function check as the\n * inline `#registerHandler` path, so a route-config `canActivate: true`\n * (boolean shorthand, runtime-reachable via the public route type) compiles to\n * the cached `TRUE_GUARD`/`FALSE_GUARD` instead of throwing on a non-callable.\n */\n compileGuardFactory(\n handler: GuardFnFactory<Dependencies> | boolean,\n methodName: string,\n ): GuardFn {\n const factory =\n typeof handler === \"boolean\"\n ? booleanToFactory<Dependencies>(handler)\n : handler;\n const fn = this.#deps.compileFactory(factory);\n\n if (typeof fn !== \"function\") {\n throw new TypeError(\n `[router.${methodName}] Factory must return a function, got ${typeof fn}`,\n );\n }\n\n return fn;\n }\n\n // =========================================================================\n // Private methods (business logic)\n // =========================================================================\n\n /**\n * Routes a registration into the origin-specific factory Map and updates the\n * compiled function under EXTERNAL-WINS (#1174): the just-added factory becomes\n * the compiled guard unless it is a definition registered while an external\n * guard is already live (then external stays effective; the definition is still\n * stored for a later `clearDefinitionGuards()`). Within one origin the most\n * recent add overwrites.\n * Emits overwrite / threshold warnings symmetric with the pre-refactor\n * single-Map behaviour: any prior entry for the slot — same origin or\n * cross-origin — counts as an overwrite for the warning surface; only a\n * brand-new slot (no entry in either Map) increments the threshold check.\n */\n #registerHandler(\n type: \"activate\" | \"deactivate\",\n name: string,\n handler: GuardFnFactory<Dependencies> | boolean,\n isFromDefinition: boolean,\n methodName: string,\n precompiledFn?: GuardFn,\n ): void {\n const factoryMaps = this.#getFactoryMaps(type);\n const functions =\n type === \"activate\"\n ? this.#canActivateFunctions\n : this.#canDeactivateFunctions;\n const targetMap = isFromDefinition\n ? factoryMaps.definition\n : factoryMaps.external;\n const otherMap = isFromDefinition\n ? factoryMaps.external\n : factoryMaps.definition;\n\n const isOverwrite = targetMap.has(name) || otherMap.has(name);\n\n if (isOverwrite) {\n this.#deps\n .getValidator()\n ?.lifecycle.warnOverwrite(name, type, methodName);\n } else {\n // Single enforcement choke point for EVERY registration path: programmatic\n // (getLifecycleApi) and route-config (getRoutesApi.add/update, where\n // isFromDefinition=true). The hard limit throws here so route-config guards\n // are bounded exactly like programmatic ones (#961); the approaching-limit\n // warning follows. Only new slots count toward the limit — an overwrite\n // leaves the count unchanged. `getHandlerCount` is read once and only when\n // the validator is installed (opt-in), so the no-plugin path stays free.\n const validator = this.#deps.getValidator();\n\n if (validator) {\n const count = this.getHandlerCount(type);\n\n validator.lifecycle.validateHandlerLimit(count, methodName);\n validator.lifecycle.validateCountThresholds(count + 1, methodName);\n }\n }\n\n const factory =\n typeof handler === \"boolean\"\n ? booleanToFactory<Dependencies>(handler)\n : handler;\n\n // Capture the slot's prior factory (if any) BEFORE the overwrite, so a\n // compile-throw can be rolled back to the previously-valid guard rather\n // than dropping it (#963).\n const previousFactory = targetMap.get(name);\n\n targetMap.set(name, factory);\n\n // External-wins (#1174): the compiled slot reflects the external guard\n // whenever one exists, regardless of registration order. A definition\n // registered while an external guard is live is still stored (so a later\n // replace()-clear can recompile from it via `#recompileSlot`) but does NOT\n // overwrite the compiled function — external stays effective. This makes\n // `#registerHandler` consistent with `#recompileSlot` / `clearDefinitionGuards`\n // (both external-wins, #1192), so the whole namespace has ONE policy, and\n // cloneRouter's fixed definition→external replay yields the same effective\n // guard as the source with no extra origin tracking. (The factory is still\n // compiled below to validate it and to keep the rollback path symmetric.)\n const externalWins = isFromDefinition && otherMap.has(name);\n\n try {\n // A pre-validated function (from the #956 add/replace pre-compile) is\n // installed directly — no re-compile; otherwise compile + non-function\n // check here (`compileGuardFactory` throws on a bad factory).\n const fn = precompiledFn ?? this.compileGuardFactory(factory, methodName);\n\n if (!externalWins) {\n functions.set(name, fn);\n }\n } catch (error) {\n // Roll the slot back to its pre-call state: restore the previous factory\n // on an overwrite (#963), else clear the slot. `#recompileSlot` then\n // resets the compiled function from whichever origin Map still holds an\n // entry — the restored same-origin factory, a surviving cross-origin one,\n // or (empty slot) deletes the compiled function.\n if (previousFactory === undefined) {\n targetMap.delete(name);\n } else {\n targetMap.set(name, previousFactory);\n }\n\n this.#recompileSlot(type, name);\n\n throw error;\n }\n }\n\n /**\n * Shared implementation for {@link clearCanActivate} / {@link clearCanDeactivate}\n * — the clear-side counterpart to {@link #registerHandler}. `scope` selects the\n * origin lane (no origin-blind default, #1171); when one origin is cleared and\n * the other survives, `#recompileSlot` recompiles the compiled function from\n * the survivor (external wins, #1174).\n */\n #clearGuard(\n type: \"activate\" | \"deactivate\",\n name: string,\n scope: GuardClearScope,\n ): void {\n const { definition, external } = this.#getFactoryMaps(type);\n const clearedDefinition =\n scope === \"external\" ? false : definition.delete(name);\n const clearedExternal =\n scope === \"definition\" ? false : external.delete(name);\n\n if (clearedDefinition || clearedExternal) {\n this.#recompileSlot(type, name);\n }\n }\n\n /**\n * Recompiles the compiled-function slot from whichever origin Map still has\n * an entry for `name` after a clear. External wins over definition; if\n * neither has an entry, the compiled function is deleted.\n */\n #recompileSlot(type: \"activate\" | \"deactivate\", name: string): void {\n const factoryMaps = this.#getFactoryMaps(type);\n const functions =\n type === \"activate\"\n ? this.#canActivateFunctions\n : this.#canDeactivateFunctions;\n\n const effective =\n factoryMaps.external.get(name) ?? factoryMaps.definition.get(name);\n\n if (!effective) {\n functions.delete(name);\n\n return;\n }\n\n try {\n const fn = this.#deps.compileFactory(effective);\n\n /* v8 ignore next 4 -- @preserve: stored factories were validated at add time, compileFactory should yield a function on second call too */\n if (typeof fn !== \"function\") {\n functions.delete(name);\n\n return;\n }\n\n functions.set(name, fn);\n } catch {\n /* v8 ignore next 2 -- @preserve: defensive — a user-provided factory could theoretically throw on re-compile (state changed since add time); deleting the function blocks navigation on that slot */\n functions.delete(name);\n }\n }\n\n #getFactoryMaps(type: \"activate\" | \"deactivate\"): {\n definition: Map<string, GuardFnFactory<Dependencies>>;\n external: Map<string, GuardFnFactory<Dependencies>>;\n } {\n return type === \"activate\"\n ? {\n definition: this.#definitionActivateFactories,\n external: this.#externalActivateFactories,\n }\n : {\n definition: this.#definitionDeactivateFactories,\n external: this.#externalDeactivateFactories,\n };\n }\n\n /**\n * Shared implementation for synchronous guard checks.\n * Warns if a guard returns a Promise (async guards are not supported in sync mode).\n * Catches exceptions and treats them as navigation-blocking (`false`).\n *\n * @param functions - Map of compiled guard functions to look up\n * @param name - Route name to check the guard for\n * @param toState - Target navigation state\n * @param fromState - Current state (`undefined` on initial navigation)\n * @param methodName - Public API method name for warning messages\n */\n #checkGuardSync(\n functions: Map<string, GuardFn>,\n name: string,\n toState: State,\n fromState: State | undefined,\n methodName: string,\n ): boolean {\n const guardFn = functions.get(name);\n\n if (!guardFn) {\n return true;\n }\n\n try {\n const result = guardFn(toState, fromState);\n\n if (typeof result === \"boolean\") {\n return result;\n }\n\n this.#deps.getValidator()?.lifecycle.warnAsyncGuardSync(name, methodName);\n\n return false;\n } catch (error) {\n // #959: a throwing sync guard must not vanish silently. `navigate()`\n // surfaces the same throw via `handleGuardError` → TRANSITION_ERROR; the\n // sync predicate (`canNavigateTo`) has no error channel, so core logs it\n // directly. This is an OPERATIONAL signal (the guard crashed — distinct\n // from the opt-in validator DX warnings above for which the validator is\n // the right home): the navigation is still treated as blocked (`false`).\n this.#deps.logger.warn(\n `router.${methodName}`,\n `Guard for \"${name}\" threw — treated as navigation-blocking (returned false)`,\n error,\n );\n\n return false;\n }\n }\n}\n","// packages/core/src/namespaces/RoutesNamespace/constants.ts\n\n/**\n * Default route name for the root node.\n */\nexport const DEFAULT_ROUTE_NAME = \"\";\n\n/**\n * Keys that belong to a route's structural/config surface — everything NOT in\n * this set is a plugin-defined **custom field** (e.g. lifecycle hooks,\n * `preload`, `searchSchema`), stored in `RoutesStore.routeCustomFields`.\n *\n * Single source of truth for the custom-field split, shared by route\n * registration (`add`/`replace`) and `update` so both classify patch keys\n * identically. `name`/`path`/`children` define route identity and are not\n * patchable via `update`; the remaining six are the structural/guard config.\n */\nexport const STANDARD_ROUTE_KEYS: ReadonlySet<string> = new Set([\n \"name\",\n \"path\",\n \"children\",\n \"canActivate\",\n \"canDeactivate\",\n \"forwardTo\",\n \"encodeParams\",\n \"decodeParams\",\n \"defaultParams\",\n]);\n","// packages/core/src/namespaces/RoutesNamespace/helpers.ts\n\nimport type { RouteConfig } from \"./types\";\nimport type { RouteDefinition } from \"../../engine\";\nimport type {\n DefaultDependencies,\n ForwardToCallback,\n Params,\n Route,\n} from \"../../types\";\n\n/**\n * Creates an empty RouteConfig.\n */\nexport function createEmptyConfig(): RouteConfig {\n return {\n decoders: Object.create(null) as Record<string, (params: Params) => Params>,\n encoders: Object.create(null) as Record<string, (params: Params) => Params>,\n defaultParams: Object.create(null) as Record<string, Params>,\n forwardMap: Object.create(null) as Record<string, string>,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n forwardFnMap: Object.create(null) as Record<string, ForwardToCallback<any>>,\n };\n}\n\n/**\n * Copies every {@link RouteConfig} sub-map's entries from `source` into\n * `target` (shallow per map — entries are shared by reference). Driven by\n * `Object.keys(source)` instead of one `Object.assign` per field, so a newly\n * added config sub-field is carried over automatically with nothing to forget\n * at each copy site (#965). Both configs are produced by\n * {@link createEmptyConfig}, so every key in `source` also exists on `target`,\n * and every value is a record object — the invariant this enumeration relies on.\n */\nexport function assignConfigEntries(\n target: RouteConfig,\n source: RouteConfig,\n): void {\n for (const key of Object.keys(source) as (keyof RouteConfig)[]) {\n Object.assign(target[key], source[key]);\n }\n}\n\n// ============================================================================\n// Route Tree Helpers\n// ============================================================================\n\n/**\n * Checks if all params from source exist with same values in target.\n * Small function body allows V8 inlining.\n */\nexport function paramsMatch(source: Params, target: Params): boolean {\n for (const key in source) {\n if (source[key] !== target[key]) {\n return false;\n }\n }\n\n return true;\n}\n\n/**\n * Checks params match, skipping keys present in skipKeys.\n */\nexport function paramsMatchExcluding(\n source: Params,\n target: Params,\n skipKeys: Params,\n): boolean {\n for (const key in source) {\n if (key in skipKeys) {\n continue;\n }\n if (source[key] !== target[key]) {\n return false;\n }\n }\n\n return true;\n}\n\n/**\n * Returns a copy of `defaultParams` with query-typed keys removed, based on\n * `ownMeta` (the per-route paramTypeMap from the matcher). When no query keys\n * are present, returns the input by reference (zero-allocation fast path).\n *\n * `ownMeta` may be `undefined` for non-registered route names — this is the\n * caller's escape hatch (e.g. `getMetaByName(name)?.[name]`); a missing meta\n * means no query type information is available, so defaults pass through\n * unchanged.\n */\nexport function stripQueryDefaults(\n defaultParams: Params,\n ownMeta: Record<string, \"url\" | \"query\"> | undefined,\n): Params {\n if (!ownMeta || !hasQueryDefault(defaultParams, ownMeta)) {\n return defaultParams;\n }\n\n const filtered: Params = {};\n\n for (const key in defaultParams) {\n if (ownMeta[key] !== \"query\") {\n filtered[key] = defaultParams[key];\n }\n }\n\n return filtered;\n}\n\nfunction hasQueryDefault(\n defaultParams: Params,\n ownMeta: Record<string, \"url\" | \"query\">,\n): boolean {\n for (const key in defaultParams) {\n if (ownMeta[key] === \"query\") {\n return true;\n }\n }\n\n return false;\n}\n\n/**\n * Sanitizes a route by keeping only essential properties.\n */\nexport function sanitizeRoute<Dependencies extends DefaultDependencies>(\n route: Route<Dependencies>,\n): RouteDefinition {\n const sanitized: RouteDefinition = {\n name: route.name,\n path: route.path,\n };\n\n if (route.children) {\n sanitized.children = route.children.map((child) => sanitizeRoute(child));\n }\n\n return sanitized;\n}\n\n/**\n * Recursively removes a route from definitions array.\n */\nexport function removeFromDefinitions(\n definitions: RouteDefinition[],\n routeName: string,\n parentPrefix = \"\",\n): boolean {\n for (let i = 0; i < definitions.length; i++) {\n const route = definitions[i];\n const fullName = parentPrefix\n ? `${parentPrefix}.${route.name}`\n : route.name;\n\n if (fullName === routeName) {\n definitions.splice(i, 1);\n\n return true;\n }\n\n if (\n route.children &&\n routeName.startsWith(`${fullName}.`) &&\n removeFromDefinitions(route.children, routeName, fullName)\n ) {\n return true;\n }\n }\n\n return false;\n}\n\n/**\n * Clears configuration entries that match the predicate.\n */\nexport function clearConfigEntries<T>(\n config: Record<string, T>,\n matcher: (key: string) => boolean,\n): void {\n for (const key of Object.keys(config)) {\n if (matcher(key)) {\n delete config[key];\n }\n }\n}\n\n/**\n * Used by matchPath() when trailingSlash is \"preserve\": the matcher's\n * buildPath() with an unset trailingSlash mode strips trailing slashes,\n * but \"preserve\" means the source path's trailing-slash choice wins.\n * If the source had a trailing slash, re-attach it to the rewritten path.\n * The reverse case (rewritten has trailing, source does not) is not\n * reachable with the current matcher — it never adds a trailing slash\n * with undefined mode.\n */\nexport function matchSourceTrailingSlash(\n sourcePath: string,\n rewrittenPath: string,\n): string {\n const queryIndex = rewrittenPath.search(/[?#]/);\n const pathPart =\n queryIndex === -1 ? rewrittenPath : rewrittenPath.slice(0, queryIndex);\n\n // Stryker disable next-line LogicalOperator: equivalent — buildPath strips trailing slashes, so the rewritten path never ends with \"/\" unless it IS \"/\" (already caught by the `=== \"/\"` operand). `endsWith(\"/\")` is unreachable-true, so `||` ≡ `&&`.\n if (pathPart === \"/\" || pathPart.endsWith(\"/\")) {\n return rewrittenPath;\n }\n\n const sourceQueryIndex = sourcePath.search(/[?#]/);\n const sourcePathPart =\n sourceQueryIndex === -1\n ? sourcePath\n : sourcePath.slice(0, sourceQueryIndex);\n\n if (!(sourcePathPart.length > 1 && sourcePathPart.endsWith(\"/\"))) {\n return rewrittenPath;\n }\n\n const querySuffix = queryIndex === -1 ? \"\" : rewrittenPath.slice(queryIndex);\n\n return `${pathPart}/${querySuffix}`;\n}\n","// packages/core/src/namespaces/RoutesNamespace/forwardChain.ts\n\nexport function resolveForwardChain(\n startRoute: string,\n forwardMap: Record<string, string>,\n maxDepth = 100,\n): string {\n const visited = new Set<string>();\n const chain: string[] = [startRoute];\n let current = startRoute;\n\n while (forwardMap[current]) {\n const next = forwardMap[current];\n\n if (visited.has(next)) {\n const cycleStart = chain.indexOf(next);\n const cycle = [...chain.slice(cycleStart), next];\n\n throw new Error(`Circular forwardTo: ${cycle.join(\" → \")}`);\n }\n\n visited.add(current);\n chain.push(next);\n current = next;\n\n if (chain.length > maxDepth) {\n throw new Error(\n `forwardTo chain exceeds maximum depth (${maxDepth}): ${chain.join(\" → \")}`,\n );\n }\n }\n\n return current;\n}\n","// packages/core/src/namespaces/RoutesNamespace/routesStore.ts\n\nimport { DEFAULT_ROUTE_NAME, STANDARD_ROUTE_KEYS } from \"./constants\";\nimport { resolveForwardChain } from \"./forwardChain\";\nimport {\n assignConfigEntries,\n createEmptyConfig,\n sanitizeRoute,\n} from \"./helpers\";\nimport {\n createMatcher,\n createRouteTree,\n routeTreeToDefinitions,\n} from \"../../engine\";\n\nimport type { RouteConfig, RoutesDependencies } from \"./types\";\nimport type {\n CreateMatcherOptions,\n Matcher,\n RouteDefinition,\n RouteTree,\n} from \"../../engine\";\nimport type {\n DefaultDependencies,\n ForwardToCallback,\n GuardFn,\n Params,\n RouteConfigUpdate,\n RouterLogger,\n GuardFnFactory,\n Route,\n} from \"../../types\";\nimport type { RouteLifecycleNamespace } from \"../RouteLifecycleNamespace\";\n\n// =============================================================================\n// Interfaces\n// =============================================================================\n\nexport interface RoutesStore<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n> {\n /**\n * DERIVED VIEW, not stored state: reconstructed from `tree` on every access\n * via `routeTreeToDefinitions` (the lossless inverse cloneRouter already\n * relies on — the `~` absolute marker is restored, child order is the\n * definition order). The tree is the single source of truth, so a third\n * retained copy of the route table (~30 B/route) is not kept. Every reader\n * is a cold CRUD/plugin-registration path; the derive is O(N).\n *\n * The returned array is a FRESH snapshot each time — mutating it never\n * affects the store (pass an explicitly-mutated snapshot to\n * `commitTreeChanges` instead, as `remove` does).\n */\n readonly definitions: RouteDefinition[];\n readonly config: RouteConfig;\n tree: RouteTree;\n matcher: Matcher;\n /**\n * Per-route-name cache of URL (path) param names, derived from `matcher` and\n * read by `RoutesNamespace.getUrlParams` (powers `areStatesEqual` /\n * `isActiveRoute`). Cleared on every `matcher` rebuild so comparisons never\n * stay frozen to a route's pre-mutation param shape (#723).\n */\n readonly urlParamsCache: Map<string, string[]>;\n resolvedForwardMap: Record<string, string>;\n routeCustomFields: Record<string, Record<string, unknown>>;\n rootPath: string;\n readonly matcherOptions: CreateMatcherOptions | undefined;\n depsStore: RoutesDependencies<Dependencies> | undefined;\n lifecycleNamespace: RouteLifecycleNamespace<Dependencies> | undefined;\n readonly pendingCanActivate: Map<string, GuardFnFactory<Dependencies>>;\n readonly pendingCanDeactivate: Map<string, GuardFnFactory<Dependencies>>;\n}\n\n// =============================================================================\n// Tree operations\n// =============================================================================\n\nfunction rebuildTree(\n definitions: readonly RouteDefinition[],\n rootPath: string,\n matcherOptions: CreateMatcherOptions | undefined,\n): { tree: RouteTree; matcher: Matcher } {\n const tree = createRouteTree(DEFAULT_ROUTE_NAME, rootPath, definitions);\n const matcher = createMatcher(matcherOptions);\n\n matcher.registerTree(tree);\n\n return { tree, matcher };\n}\n\n/**\n * Rebuilds tree+matcher in place from `definitions` (defaults to the current\n * tree's own derived definitions — the same-table case, e.g. a rootPath\n * change).\n */\nexport function rebuildTreeInPlace<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n store: RoutesStore<Dependencies>,\n definitions: readonly RouteDefinition[] = store.definitions,\n): void {\n const result = rebuildTree(definitions, store.rootPath, store.matcherOptions);\n\n store.tree = result.tree;\n store.matcher = result.matcher;\n store.urlParamsCache.clear();\n}\n\nexport function commitTreeChanges<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n store: RoutesStore<Dependencies>,\n definitions: readonly RouteDefinition[],\n): void {\n rebuildTreeInPlace(store, definitions);\n store.resolvedForwardMap = refreshForwardMap(store.config);\n}\n\n// =============================================================================\n// Store reset\n// =============================================================================\n\n/**\n * Clears all routes and resets config.\n * Does NOT clear lifecycle handlers or state — caller handles that.\n */\nexport function resetStore<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(store: RoutesStore<Dependencies>): void {\n clearRouteData(store);\n rebuildTreeInPlace(store, []);\n}\n\n/**\n * Clears route data without rebuilding the tree.\n * Used by replace() to avoid double rebuild (clearRouteData + commitTreeChanges).\n * `definitions` needs no clearing — it is derived from the tree, which the\n * caller rebuilds (resetStore → empty, replace → the new artifacts).\n */\nexport function clearRouteData<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(store: RoutesStore<Dependencies>): void {\n Object.assign(store.config, createEmptyConfig());\n\n store.resolvedForwardMap = Object.create(null) as Record<string, string>;\n store.routeCustomFields = Object.create(null) as Record<\n string,\n Record<string, unknown>\n >;\n}\n\n// =============================================================================\n// Forward map\n// =============================================================================\n\nexport function refreshForwardMap(config: RouteConfig): Record<string, string> {\n const map = Object.create(null) as Record<string, string>;\n\n for (const fromRoute of Object.keys(config.forwardMap)) {\n map[fromRoute] = resolveForwardChain(fromRoute, config.forwardMap);\n }\n\n return map;\n}\n\n// =============================================================================\n// Route handler registration\n// =============================================================================\n\n/**\n * Throws if `forwardTo` is an async function (native or transpiled). Async\n * forwardTo callbacks break the synchronous matchPath/buildPath contract.\n * Runs inside `registerForwardTo` (the add/replace build path, before any store\n * mutation) AND inside `getRoutesApi`'s `updateForwardTo` (the update path), so\n * `update(name, { forwardTo: async })` is rejected at registration with the same\n * actionable error instead of deferring a generic TypeError to navigation (#967).\n */\nexport function assertForwardToNotAsync(\n forwardTo: unknown,\n fullName: string,\n): void {\n if (typeof forwardTo !== \"function\") {\n return;\n }\n\n const isNativeAsync =\n (forwardTo as { constructor: { name: string } }).constructor.name ===\n \"AsyncFunction\";\n const isTranspiledAsync = (forwardTo as { toString: () => string })\n .toString()\n .includes(\"__awaiter\");\n\n if (isNativeAsync || isTranspiledAsync) {\n throw new TypeError(\n `forwardTo callback cannot be async for route \"${fullName}\". ` +\n `Async functions break matchPath/buildPath.`,\n );\n }\n}\n\nfunction registerForwardTo<Dependencies extends DefaultDependencies>(\n route: Route<Dependencies>,\n fullName: string,\n config: RouteConfig,\n logger: RouterLogger,\n): void {\n if (route.canActivate) {\n /* v8 ignore next -- @preserve: edge case, both string and function tested separately */\n const forwardTarget =\n typeof route.forwardTo === \"string\" ? route.forwardTo : \"[dynamic]\";\n\n logger.warn(\n \"real-router\",\n `Route \"${fullName}\" has both forwardTo and canActivate. ` +\n `canActivate will be ignored because forwardTo creates a redirect (industry standard). ` +\n `Move canActivate to the target route \"${forwardTarget}\".`,\n );\n }\n\n if (route.canDeactivate) {\n /* v8 ignore next -- @preserve: edge case, both string and function tested separately */\n const forwardTarget =\n typeof route.forwardTo === \"string\" ? route.forwardTo : \"[dynamic]\";\n\n logger.warn(\n \"real-router\",\n `Route \"${fullName}\" has both forwardTo and canDeactivate. ` +\n `canDeactivate will be ignored because forwardTo creates a redirect (industry standard). ` +\n `Move canDeactivate to the target route \"${forwardTarget}\".`,\n );\n }\n\n assertForwardToNotAsync(route.forwardTo, fullName);\n\n // forwardTo is guaranteed to exist at this point\n if (typeof route.forwardTo === \"string\") {\n config.forwardMap[fullName] = route.forwardTo;\n } else {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n config.forwardFnMap[fullName] = route.forwardTo!;\n }\n}\n\nfunction registerSingleRouteHandlers<Dependencies extends DefaultDependencies>(\n route: Route<Dependencies>,\n fullName: string,\n config: RouteConfig,\n routeCustomFields: Record<string, Record<string, unknown>>,\n pendingCanActivate: Map<string, GuardFnFactory<Dependencies>>,\n pendingCanDeactivate: Map<string, GuardFnFactory<Dependencies>>,\n logger: RouterLogger,\n): void {\n const customFields = Object.fromEntries(\n Object.entries(route).filter(([key]) => !STANDARD_ROUTE_KEYS.has(key)),\n );\n\n if (Object.keys(customFields).length > 0) {\n routeCustomFields[fullName] = customFields;\n }\n\n // Guards are collected here and registered into the lifecycle later — by\n // `adoptRouteArtifacts` (add/replace) or `RoutesNamespace.flushPendingGuards`\n // (initial routes, the final step of the Router constructor — #1331) — so\n // the build stays a pure, side-effect-free preparation step.\n if (route.canActivate) {\n pendingCanActivate.set(fullName, route.canActivate);\n }\n\n if (route.canDeactivate) {\n pendingCanDeactivate.set(fullName, route.canDeactivate);\n }\n\n if (route.forwardTo) {\n registerForwardTo(route, fullName, config, logger);\n }\n\n if (route.decodeParams) {\n config.decoders[fullName] = (params: Params): Params =>\n route.decodeParams?.(params) ?? params;\n }\n\n if (route.encodeParams) {\n config.encoders[fullName] = (params: Params): Params =>\n route.encodeParams?.(params) ?? params;\n }\n\n if (route.defaultParams) {\n config.defaultParams[fullName] = route.defaultParams;\n }\n}\n\nfunction registerAllRouteHandlers<Dependencies extends DefaultDependencies>(\n routes: readonly Route<Dependencies>[],\n config: RouteConfig,\n routeCustomFields: Record<string, Record<string, unknown>>,\n pendingCanActivate: Map<string, GuardFnFactory<Dependencies>>,\n pendingCanDeactivate: Map<string, GuardFnFactory<Dependencies>>,\n logger: RouterLogger,\n parentName = \"\",\n): void {\n for (const route of routes) {\n const fullName = parentName ? `${parentName}.${route.name}` : route.name;\n\n registerSingleRouteHandlers(\n route,\n fullName,\n config,\n routeCustomFields,\n pendingCanActivate,\n pendingCanDeactivate,\n logger,\n );\n\n if (route.children) {\n registerAllRouteHandlers(\n route.children,\n config,\n routeCustomFields,\n pendingCanActivate,\n pendingCanDeactivate,\n logger,\n fullName,\n );\n }\n }\n}\n\n// =============================================================================\n// Prepare-then-commit (issue #698)\n//\n// add()/replace() build the complete new store state into LOCAL structures, and\n// only swap it into the store once every core-level error has surfaced from the\n// build itself (async/circular forwardTo throw in registerAllRouteHandlers /\n// refreshForwardMap; invalid path constraint throws in rebuildTree). The store\n// is mutated only by `adoptRouteArtifacts`, which compiles every prepared guard\n// factory BEFORE the swap (#956): a factory that throws on compile (or returns a\n// non-function) aborts there, with the store still untouched. So all error\n// classes — core-level build errors AND malformed guard factories — surface\n// before any mutation, leaving the existing routes intact (full atomicity). The\n// silent-corruption cases route-tree never throws on (duplicate name vs an\n// existing route, a name duplicated within the batch, missing parent) are caught\n// up front by `assertAddable`.\n// =============================================================================\n\n/**\n * The fully-built, ready-to-swap result of preparing a route mutation. Holds\n * everything `adoptRouteArtifacts` assigns into the store.\n */\ninterface RouteArtifacts<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n> {\n readonly config: RouteConfig;\n readonly routeCustomFields: Record<string, Record<string, unknown>>;\n readonly pendingCanActivate: Map<string, GuardFnFactory<Dependencies>>;\n readonly pendingCanDeactivate: Map<string, GuardFnFactory<Dependencies>>;\n readonly tree: RouteTree;\n readonly matcher: Matcher;\n readonly resolvedForwardMap: Record<string, string>;\n}\n\n/** Null-proto shallow clone of a RouteConfig (preserves every sub-map's contents). */\nfunction cloneConfig(config: RouteConfig): RouteConfig {\n const clone = createEmptyConfig();\n\n assignConfigEntries(clone, config);\n\n return clone;\n}\n\n/**\n * Returns a new definitions array with `added` inserted, without mutating the\n * input. For a top-level add the existing definitions are shallow-copied and\n * `added` appended. For a parented add the spine down to the parent is cloned\n * (siblings/other branches are shared by reference) and `added` appended to the\n * parent's children. Caller guarantees the parent path exists (see assertAddable).\n */\nfunction insertAddedDefinitions(\n definitions: readonly RouteDefinition[],\n added: RouteDefinition[],\n parentSegments: readonly string[],\n): RouteDefinition[] {\n if (parentSegments.length === 0) {\n return [...definitions, ...added];\n }\n\n const [head, ...rest] = parentSegments;\n\n return definitions.map((def) => {\n if (def.name !== head) {\n return def;\n }\n\n const children = def.children ?? [];\n\n return {\n ...def,\n children:\n rest.length === 0\n ? [...children, ...added]\n : insertAddedDefinitions(children, added, rest),\n };\n });\n}\n\n/** Depth-first walk yielding each route's full dotted name (no side effects). */\nfunction walkRouteNames<Dependencies extends DefaultDependencies>(\n routes: readonly Route<Dependencies>[],\n parentName: string,\n onName: (fullName: string) => void,\n): void {\n for (const route of routes) {\n const fullName = parentName ? `${parentName}.${route.name}` : route.name;\n\n onName(fullName);\n\n if (route.children) {\n walkRouteNames(route.children, fullName, onName);\n }\n }\n}\n\n/**\n * Rejects a route name duplicated WITHIN a single batch — the silent-overwrite\n * case route-tree stays last-wins on (#953 for `add`, #968 for `replace`). Walks\n * the same depth-first dotted names, but tracks them in a local Set: a name seen\n * twice in one array means the caller's second route would silently shadow the\n * first (`matchPath` for the first route's path becomes unreachable). Mirrors\n * validation-plugin's batch-dup message (route-tree `checkBatchNameDuplicate`)\n * so the no-plugin error matches the with-plugin one. `methodName` is \"addRoute\"\n * for both add and replace — the plugin reports \"addRoute\" for replace batches\n * too, so this keeps with/without-plugin parity.\n */\nexport function assertNoDuplicateNamesInBatch<\n Dependencies extends DefaultDependencies,\n>(\n routes: readonly Route<Dependencies>[],\n parentName: string,\n methodName: string,\n): void {\n const seen = new Set<string>();\n\n walkRouteNames(routes, parentName, (fullName) => {\n if (seen.has(fullName)) {\n throw new Error(\n `[router.${methodName}] Duplicate route \"${fullName}\" in batch`,\n );\n }\n\n seen.add(fullName);\n });\n}\n\nconst INTERNAL_ROUTE_PREFIX = \"@@\";\n\n/**\n * Rejects a single (bare) route name that uses the reserved \"@@\" prefix —\n * internal / system names such as UNKNOWN_ROUTE (`\"@@router/UNKNOWN_ROUTE\"`).\n * Mutating such a name would let a real URL `matchPath` to a state with\n * `name === UNKNOWN_ROUTE`, silently conflating a genuine route with \"not\n * found\". This always-on guard protected all four mutators (#238) until the\n * validation-extraction (`d1ebff80`) demoted it to the opt-in\n * validation-plugin; only `add` was restored (#954), so `remove`/`update`\n * regained it via this helper (#1047). Mirrors validation-plugin's\n * `throwIfInternalRoute` message so the no-plugin error matches the with-plugin\n * one.\n */\nexport function assertNoInternalRouteName(\n name: string,\n methodName: string,\n): void {\n if (name.startsWith(INTERNAL_ROUTE_PREFIX)) {\n throw new Error(\n `[router.${methodName}] Route name \"${name}\" uses the reserved \"${INTERNAL_ROUTE_PREFIX}\" prefix. Routes with this prefix are internal and cannot be modified through the public API.`,\n );\n }\n}\n\n/**\n * Batch counterpart to {@link assertNoInternalRouteName}: rejects any route in\n * the batch (recursing children) whose BARE leaf name uses the reserved \"@@\"\n * prefix (the prefix is on the leaf, not the dotted fullName). Used by `add`\n * (#954) and `replace` (#1047).\n */\nexport function assertNoInternalNamesInBatch<\n Dependencies extends DefaultDependencies,\n>(routes: readonly Route<Dependencies>[], methodName: string): void {\n for (const route of routes) {\n assertNoInternalRouteName(route.name, methodName);\n\n if (route.children) {\n assertNoInternalNamesInBatch(route.children, methodName);\n }\n }\n}\n\n/**\n * Rejects two routes that share the same `path` at the same parent level WITHIN\n * a single `add` batch (#955). The matcher resolves a path collision last-wins,\n * so the earlier route stays addressable by name (`has` / `buildPath`) but is\n * unreachable by URL (`matchPath` returns the later route) — a silent shadow.\n * Paths only collide among siblings, so seen paths are tracked per parent\n * fullName. Mirrors validation-plugin's message (route-tree\n * `checkBatchPathDuplicate`) so the no-plugin error matches the with-plugin one.\n * Scoped to the batch (not the existing tree) per #955 — the in-batch case the\n * issue describes.\n */\nexport function assertNoDuplicatePathsInBatch<\n Dependencies extends DefaultDependencies,\n>(\n routes: readonly Route<Dependencies>[],\n parentName: string,\n methodName: string,\n): void {\n const seenByParent = new Map<string, Set<string>>();\n\n const walk = (\n siblings: readonly Route<Dependencies>[],\n parent: string,\n ): void => {\n for (const route of siblings) {\n const paths = seenByParent.get(parent);\n\n if (paths?.has(route.path)) {\n throw new Error(\n `[router.${methodName}] Path \"${route.path}\" is already defined`,\n );\n }\n\n if (paths) {\n paths.add(route.path);\n } else {\n seenByParent.set(parent, new Set([route.path]));\n }\n\n if (route.children) {\n walk(route.children, parent ? `${parent}.${route.name}` : route.name);\n }\n }\n };\n\n walk(routes, parentName);\n}\n\n/**\n * Up-front guard for `add` against the corruptions route-tree stays silent on: a\n * missing `parent`, a name that collides with an EXISTING route, a name\n * duplicated WITHIN the batch, a reserved \"@@\"-prefixed name (which would shadow\n * an internal/system route name), and a path duplicated among siblings WITHIN\n * the batch (any of which would otherwise be silently overwritten/shadowed).\n * Throws before any build.\n */\nexport function assertAddable<Dependencies extends DefaultDependencies>(\n store: RoutesStore<Dependencies>,\n routes: readonly Route<Dependencies>[],\n parentName: string | undefined,\n): void {\n assertNoInternalNamesInBatch(routes, \"addRoute\");\n\n if (parentName !== undefined && !store.matcher.hasRoute(parentName)) {\n throw new Error(\n `[router.addRoute] Parent route \"${parentName}\" does not exist`,\n );\n }\n\n walkRouteNames(routes, parentName ?? \"\", (fullName) => {\n if (store.matcher.hasRoute(fullName)) {\n throw new Error(`[router.addRoute] Route \"${fullName}\" already exists`);\n }\n });\n\n assertNoDuplicateNamesInBatch(routes, parentName ?? \"\", \"addRoute\");\n assertNoDuplicatePathsInBatch(routes, parentName ?? \"\", \"addRoute\");\n}\n\n/**\n * Builds RouteArtifacts from a final definitions array and the routes whose\n * handlers (config + guards) populate `config`/`routeCustomFields`. Guards are\n * collected into the returned pending maps (depsStore is intentionally omitted\n * so nothing compiles or touches the lifecycle here). THROWS on async/circular\n * forwardTo and invalid path constraint — before the caller mutates the store.\n *\n * Takes a single args object: the positional list hit 8 parameters (S107) when\n * the per-router `logger` (#724) joined it, and named fields read better at the\n * two call sites anyway.\n */\nfunction buildArtifacts<Dependencies extends DefaultDependencies>({\n definitions,\n routesForHandlers,\n config,\n routeCustomFields,\n handlerParentName,\n rootPath,\n matcherOptions,\n logger,\n}: {\n definitions: readonly RouteDefinition[];\n routesForHandlers: readonly Route<Dependencies>[];\n config: RouteConfig;\n routeCustomFields: Record<string, Record<string, unknown>>;\n handlerParentName: string;\n rootPath: string;\n matcherOptions: CreateMatcherOptions | undefined;\n logger: RouterLogger;\n}): RouteArtifacts<Dependencies> {\n const pendingCanActivate = new Map<string, GuardFnFactory<Dependencies>>();\n const pendingCanDeactivate = new Map<string, GuardFnFactory<Dependencies>>();\n\n registerAllRouteHandlers(\n routesForHandlers,\n config,\n routeCustomFields,\n pendingCanActivate,\n pendingCanDeactivate,\n logger,\n handlerParentName,\n );\n\n const resolvedForwardMap = refreshForwardMap(config);\n const { tree, matcher } = rebuildTree(definitions, rootPath, matcherOptions);\n\n return {\n config,\n routeCustomFields,\n pendingCanActivate,\n pendingCanDeactivate,\n tree,\n matcher,\n resolvedForwardMap,\n };\n}\n\n/** Builds the merged artifacts for an incremental `add` (existing ∪ new). */\nexport function buildAddArtifacts<Dependencies extends DefaultDependencies>(\n store: RoutesStore<Dependencies>,\n routes: readonly Route<Dependencies>[],\n parentName: string | undefined,\n logger: RouterLogger,\n): RouteArtifacts<Dependencies> {\n const definitions = insertAddedDefinitions(\n store.definitions,\n routes.map((route) => sanitizeRoute(route)),\n parentName === undefined ? [] : parentName.split(\".\"),\n );\n\n return buildArtifacts({\n definitions,\n routesForHandlers: routes,\n config: cloneConfig(store.config),\n routeCustomFields: Object.assign(\n Object.create(null) as Record<string, Record<string, unknown>>,\n store.routeCustomFields,\n ),\n handlerParentName: parentName ?? \"\",\n rootPath: store.rootPath,\n matcherOptions: store.matcherOptions,\n logger,\n });\n}\n\n/** Builds the fresh artifacts for a full `replace` (standalone new set). */\nexport function buildReplaceArtifacts<Dependencies extends DefaultDependencies>(\n routes: readonly Route<Dependencies>[],\n rootPath: string,\n matcherOptions: CreateMatcherOptions | undefined,\n logger: RouterLogger,\n): RouteArtifacts<Dependencies> {\n return buildArtifacts({\n definitions: routes.map((route) => sanitizeRoute(route)),\n routesForHandlers: routes,\n config: createEmptyConfig(),\n routeCustomFields: Object.create(null) as Record<\n string,\n Record<string, unknown>\n >,\n handlerParentName: \"\",\n rootPath,\n matcherOptions,\n logger,\n });\n}\n\n/**\n * Compiles every pending guard factory up front, returning\n * `[name, factory, compiledFn]` triples for installation. THROWS from `compile`\n * on the first factory that throws on compile or returns a non-function — the\n * pre-swap validation that makes `adoptRouteArtifacts` atomic for malformed\n * guards (#956). Compiling here (not at install) means a factory with\n * compile-time side effects runs exactly once.\n */\nfunction compilePendingGuards<Dependencies extends DefaultDependencies>(\n pending: Map<string, GuardFnFactory<Dependencies>>,\n compile: (\n handler: GuardFnFactory<Dependencies>,\n methodName: string,\n ) => GuardFn,\n methodName: string,\n): [string, GuardFnFactory<Dependencies>, GuardFn][] {\n const compiled: [string, GuardFnFactory<Dependencies>, GuardFn][] = [];\n\n for (const [name, factory] of pending) {\n compiled.push([name, factory, compile(factory, methodName)]);\n }\n\n return compiled;\n}\n\n/** Pre-compiled guard triples for {@link adoptRouteArtifacts} install. */\nexport interface CompiledArtifactGuards<\n Dependencies extends DefaultDependencies,\n> {\n activate: [string, GuardFnFactory<Dependencies>, GuardFn][];\n deactivate: [string, GuardFnFactory<Dependencies>, GuardFn][];\n}\n\n/**\n * Compiles an artifacts' pending guard factories up front (#956), THROWING on\n * the first factory that throws on compile or returns a non-function.\n *\n * `replaceRoutes` calls this in its PREPARE phase — **before**\n * `clearDefinitionGuards()` — and hands the result to `adoptRouteArtifacts`, so\n * a compile-throw aborts with BOTH the tree AND the old definition guards intact\n * (#1193, mirroring #1046's handler-limit hoist). `add` has no clear step, so\n * `adoptRouteArtifacts` compiles inline for it.\n */\nexport function compileArtifactGuards<Dependencies extends DefaultDependencies>(\n artifacts: RouteArtifacts<Dependencies>,\n deps: RoutesDependencies<Dependencies>,\n): CompiledArtifactGuards<Dependencies> {\n return {\n activate: compilePendingGuards(\n artifacts.pendingCanActivate,\n deps.compileGuard,\n \"canActivate\",\n ),\n deactivate: compilePendingGuards(\n artifacts.pendingCanDeactivate,\n deps.compileGuard,\n \"canDeactivate\",\n ),\n };\n}\n\n/**\n * Commits prepared artifacts into the store in place. Every pending guard\n * factory is compiled BEFORE the tree/config swap (#956): a factory that throws\n * on compile (or returns a non-function) aborts here with the store untouched,\n * so `add`/`replace` are atomic for malformed guards too — not just core build\n * errors. The tree/config assignments are pure and cannot throw; the\n * pre-compiled guards are then installed without re-compiling (the factory ran\n * once, at the pre-compile above). `depsStore` is always set on a wired router,\n * which is the only path that reaches `add`/`replace`.\n */\nexport function adoptRouteArtifacts<Dependencies extends DefaultDependencies>(\n store: RoutesStore<Dependencies>,\n artifacts: RouteArtifacts<Dependencies>,\n precompiled?: CompiledArtifactGuards<Dependencies>,\n): void {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- depsStore is set once the router is wired; add/replace only run on a wired router (constructor-time registration uses createRoutesStore)\n const deps = store.depsStore!;\n\n // Pre-swap compile: surfaces a malformed guard factory before any mutation.\n // `replace()` pre-compiles in its PREPARE phase (BEFORE clearDefinitionGuards)\n // and passes the result here, so a compile-throw never erases the old\n // definition guards (#1193); `add` has no clear step and compiles inline.\n const { activate: compiledActivate, deactivate: compiledDeactivate } =\n precompiled ?? compileArtifactGuards(artifacts, deps);\n\n // Atomic swap — pure assignments, cannot throw. (`definitions` is derived\n // from `tree`, so swapping the tree IS the definitions swap.)\n Object.assign(store.config, artifacts.config);\n store.routeCustomFields = artifacts.routeCustomFields;\n store.tree = artifacts.tree;\n store.matcher = artifacts.matcher;\n store.urlParamsCache.clear();\n store.resolvedForwardMap = artifacts.resolvedForwardMap;\n\n // Install pre-compiled guards — no re-compile, no throw.\n for (const [name, factory, fn] of compiledActivate) {\n deps.addActivateGuard(name, factory, fn);\n }\n\n for (const [name, factory, fn] of compiledDeactivate) {\n deps.addDeactivateGuard(name, factory, fn);\n }\n}\n\n/**\n * COMMIT core for `update()` — the field-patch counterpart to\n * {@link adoptRouteArtifacts} (add/replace) / {@link commitTreeChanges} (remove)\n * / {@link resetStore} (clear), co-located here so all four route-CRUD commit\n * cores live in one file. Stays **NO_TREE_REBUILD**: an O(1) per-field patch\n * that never rebuilds the tree/matcher (so it must NOT funnel through\n * `adoptRouteArtifacts`).\n *\n * Prepare-then-commit (#951): every throwing step runs in PREPARE, before any\n * store write, so a rejected update leaves the route's prior config fully intact\n * — an async/cyclic `forwardTo` (#967), a guard factory that throws on compile\n * (#956 seam), a throwing custom-field getter, and the #961 handler-limit\n * pre-flight (#1046). Returns the structural fields for the caller's conditional\n * TREE_CHANGED emit, computed from the single destructure here so core invokes\n * each user getter once (#797 / #952 `null`-clears-definition-only preserved).\n */\nexport function commitRouteUpdate<Dependencies extends DefaultDependencies>(\n store: RoutesStore<Dependencies>,\n lifecycle: RouteLifecycleNamespace<Dependencies>,\n name: string,\n updates: RouteConfigUpdate<Dependencies>,\n): {\n forwardTo?: string | ForwardToCallback<Dependencies> | null | undefined;\n defaultParams?: Params | null | undefined;\n decodeParams?: ((params: Params) => Params) | null | undefined;\n encodeParams?: ((params: Params) => Params) | null | undefined;\n} {\n const {\n forwardTo,\n defaultParams,\n decodeParams,\n encodeParams,\n canActivate,\n canDeactivate,\n } = updates;\n\n // ===== PREPARE — compute every change into LOCALS. Any throw here aborts\n // before a single store write, so the whole field set is applied\n // all-or-nothing (#951).\n const forwardToPlan =\n forwardTo === undefined\n ? undefined\n : prepareForwardTo(name, forwardTo, store.config);\n\n const nextCustomFields = prepareCustomFields(store, name, updates);\n\n // Guard factories are compiled NOW (a throwing factory surfaces in PREPARE);\n // the precompiled function is installed in COMMIT without re-invoking the\n // factory, so a factory side effect runs exactly once (reuses the #956\n // compile-then-install seam). Compiled after the other prepares so a throw\n // upstream skips invoking the factory at all.\n const activateFn =\n canActivate === undefined || canActivate === null\n ? undefined\n : lifecycle.compileGuardFactory(canActivate, \"canActivate\");\n const deactivateFn =\n canDeactivate === undefined || canDeactivate === null\n ? undefined\n : lifecycle.compileGuardFactory(canDeactivate, \"canDeactivate\");\n\n // Pre-flight the #961 handler-limit before the COMMIT writes, so an at-limit\n // update that adds a NEW guard slot aborts before forwardTo / scalar config\n // land (#1046, #951). A slot is new only when `name` does not already hold a\n // guard of that type — an overwrite does not count.\n lifecycle.preflightHandlerLimit(\n activateFn === undefined ? [] : [name],\n deactivateFn === undefined ? [] : [name],\n false,\n );\n\n // ===== COMMIT — pure writes from here; nothing below throws.\n // Custom (plugin-defined) fields. Consumers read these lazily via\n // getRouteConfig (lifecycle hooks, preload, searchSchema), so no TREE_CHANGED\n // is needed — the next read sees the new value; the caller's emit stays\n // structural-only by design (О-7).\n if (nextCustomFields !== undefined) {\n if (Object.keys(nextCustomFields).length > 0) {\n store.routeCustomFields[name] = nextCustomFields;\n } else {\n delete store.routeCustomFields[name];\n }\n }\n\n if (forwardToPlan !== undefined) {\n store.config.forwardMap = forwardToPlan.forwardMap;\n store.config.forwardFnMap = forwardToPlan.forwardFnMap;\n store.resolvedForwardMap = forwardToPlan.resolved;\n }\n\n commitScalarConfig(store, name, {\n defaultParams,\n decodeParams,\n encodeParams,\n });\n\n // Install the guards from their PREPARE-phase precompiled functions; a `null`\n // clears the definition-origin guard only (#952). See commitGuardUpdate.\n commitGuardUpdate(lifecycle, \"activate\", name, canActivate, activateFn);\n commitGuardUpdate(lifecycle, \"deactivate\", name, canDeactivate, deactivateFn);\n\n return { forwardTo, defaultParams, decodeParams, encodeParams };\n}\n\n/**\n * PREPARE step for a `forwardTo` update (#951 atomicity): computes the new\n * forward maps and the resolved forward chain into LOCALS and returns them\n * WITHOUT touching the store. A throw here — an async `forwardTo` (#967) or a\n * cycle surfaced by `refreshForwardMap` — aborts `update()` before any field is\n * committed. The caller writes the returned bundle into the store in its COMMIT\n * phase. (Mirrors the build-then-swap shape of #698, but the swap is deferred to\n * the caller so it can be sequenced with the other prepared fields.)\n */\nfunction prepareForwardTo<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n name: string,\n forwardTo: string | ForwardToCallback<Dependencies> | null,\n config: RouteConfig,\n): {\n forwardMap: RouteConfig[\"forwardMap\"];\n forwardFnMap: RouteConfig[\"forwardFnMap\"];\n resolved: Record<string, string>;\n} {\n // #967: reject an async forwardTo at update time — parity with add/replace\n // (registerForwardTo runs the same check on the build path). A no-op for\n // string/null. Without this the async callback is stored silently and\n // surfaces later as a generic \"must return a string, got object\" TypeError\n // from #resolveDynamicForward at navigation. Runs first, before any clone.\n assertForwardToNotAsync(forwardTo, name);\n\n const forwardMap = Object.assign(\n Object.create(null) as RouteConfig[\"forwardMap\"],\n config.forwardMap,\n );\n const forwardFnMap = Object.assign(\n Object.create(null) as RouteConfig[\"forwardFnMap\"],\n config.forwardFnMap,\n );\n\n if (forwardTo === null) {\n delete forwardMap[name];\n delete forwardFnMap[name];\n } else if (typeof forwardTo === \"string\") {\n delete forwardFnMap[name];\n forwardMap[name] = forwardTo;\n } else {\n delete forwardMap[name];\n forwardFnMap[name] = forwardTo;\n }\n\n const resolved = refreshForwardMap({ ...config, forwardMap });\n\n return { forwardMap, forwardFnMap, resolved };\n}\n\n/**\n * PREPARE step for a route's plugin-defined **custom fields** (#951) — the\n * `update` counterpart to how `add`/`replace` register them\n * (`registerSingleRouteHandlers`). A custom field is any patch key not in\n * {@link STANDARD_ROUTE_KEYS}.\n *\n * Computes the merged record and RETURNS it for the caller to commit;\n * `undefined` means no custom-field key was present, so the caller leaves the\n * store untouched. Semantics mirror the scalar fields in\n * {@link commitScalarConfig}: shallow-merge by patch key, `null` removes a\n * single field, `undefined` is a no-op (leaves the field untouched). When the\n * merge empties the record, the caller drops the whole entry so `getRouteConfig`\n * returns `undefined` — symmetric with `add`, which only stores a record when at\n * least one custom field exists.\n *\n * Reading the custom-field getters HERE (in PREPARE, not at commit) is what lets\n * a throwing getter abort the whole update before any field is written. The\n * merged record is a **fresh object**, never mutated in place: `cloneRouter`\n * shares per-route custom-field records by reference (`Object.assign`), so\n * replacing the reference keeps a clone isolated from post-clone updates on the\n * source.\n */\nfunction prepareCustomFields<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n store: RoutesStore<Dependencies>,\n name: string,\n updates: RouteConfigUpdate<Dependencies>,\n): Record<string, unknown> | undefined {\n let next: Record<string, unknown> | undefined;\n\n // `Object.keys` (not `Object.entries`): a value is read only AFTER the\n // standard-key guard, so structural-field getters — already read once by\n // `update`'s destructuring — are not re-invoked. `Object.entries` would read\n // every value eagerly, double-invoking a `defaultParams`/`forwardTo` getter\n // and breaking the \"user getter called once\" invariant.\n // eslint-disable-next-line unicorn/prefer-object-iterable-methods -- see above\n for (const key of Object.keys(updates)) {\n if (STANDARD_ROUTE_KEYS.has(key)) {\n continue;\n }\n\n const value = (updates as Record<string, unknown>)[key];\n\n // `undefined` mirrors the structural path: leave the field untouched.\n if (value === undefined) {\n continue;\n }\n\n // Clone-on-first-write — keeps clones (which alias this record) isolated.\n next ??= { ...store.routeCustomFields[name] };\n\n if (value === null) {\n delete next[key];\n } else {\n next[key] = value;\n }\n }\n\n return next;\n}\n\n/**\n * COMMIT step for the scalar config fields of an update (#951): writes\n * `defaultParams` / `decodeParams` / `encodeParams` in place. These assignments\n * are pure and never throw, so they run in the COMMIT phase after every throwing\n * field has been validated in PREPARE. `forwardTo` is handled separately — it\n * has its own throwing prepare step ({@link prepareForwardTo}).\n */\nfunction commitScalarConfig<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n store: RoutesStore<Dependencies>,\n name: string,\n updates: {\n defaultParams?: Params | null | undefined;\n decodeParams?: ((params: Params) => Params) | null | undefined;\n encodeParams?: ((params: Params) => Params) | null | undefined;\n },\n): void {\n if (updates.defaultParams !== undefined) {\n if (updates.defaultParams === null) {\n delete store.config.defaultParams[name];\n } else {\n store.config.defaultParams[name] = updates.defaultParams;\n }\n }\n\n if (updates.decodeParams !== undefined) {\n if (updates.decodeParams === null) {\n delete store.config.decoders[name];\n } else {\n const decoder = updates.decodeParams;\n\n store.config.decoders[name] = (params: Params): Params =>\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- runtime fallback if user-provided decoder violates its return type\n decoder(params) ?? params;\n }\n }\n\n if (updates.encodeParams !== undefined) {\n if (updates.encodeParams === null) {\n delete store.config.encoders[name];\n } else {\n const encoder = updates.encodeParams;\n\n store.config.encoders[name] = (params: Params): Params =>\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- runtime fallback if user-provided encoder violates its return type\n encoder(params) ?? params;\n }\n }\n}\n\n/**\n * COMMIT step for one guard field of an update (#951). `undefined` is a no-op;\n * `null` clears the DEFINITION-origin guard only, preserving an external guard\n * (#952); a factory installs together with its PREPARE-phase `precompiledFn`\n * (no re-compile — #956 seam). Extracted from `update()` so its prepare/commit\n * orchestration stays within the cognitive-complexity budget.\n */\nfunction commitGuardUpdate<Dependencies extends DefaultDependencies>(\n lifecycle: RouteLifecycleNamespace<Dependencies>,\n kind: \"activate\" | \"deactivate\",\n name: string,\n value: GuardFnFactory<Dependencies> | null | undefined,\n precompiledFn: GuardFn | undefined,\n): void {\n if (value === undefined) {\n return;\n }\n\n if (kind === \"activate\") {\n if (value === null) {\n lifecycle.clearCanActivate(name, \"definition\");\n } else {\n lifecycle.addCanActivate(name, value, true, precompiledFn);\n }\n } else if (value === null) {\n lifecycle.clearCanDeactivate(name, \"definition\");\n } else {\n lifecycle.addCanDeactivate(name, value, true, precompiledFn);\n }\n}\n\n// =============================================================================\n// Factory\n// =============================================================================\n\nexport function createRoutesStore<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n routes: Route<Dependencies>[],\n matcherOptions: CreateMatcherOptions | undefined,\n logger: RouterLogger,\n): RoutesStore<Dependencies> {\n // Initial routes are a standalone set at rootPath \"\" — same build the\n // prepare-then-commit `replace` path uses. Guards land in the pending maps,\n // flushed by `flushPendingGuards()` at the end of the Router constructor\n // (#1331); `setDependencies` itself is a pure assignment.\n //\n // Reject the silent-corruption cases BEFORE building, giving the constructor\n // parity with `add()` / `replace()` — the third and last route-population\n // entry point (#1351): within-batch duplicate names (#953/#968) and reserved\n // \"@@\" names (#954). Without these the constructor silently last-wins a\n // duplicate-name sibling (the first route is dropped → its deep-link 404s)\n // while add/replace throw. `methodName` is \"addRoute\" so all three entry\n // points surface the identical bare-core error. (Duplicate PATHS are already\n // rejected downstream by the path-matcher backstop #1153, so they are not\n // re-checked here.)\n assertNoInternalNamesInBatch(routes, \"addRoute\");\n assertNoDuplicateNamesInBatch(routes, \"\", \"addRoute\");\n\n const artifacts = buildReplaceArtifacts(routes, \"\", matcherOptions, logger);\n\n const store: RoutesStore<Dependencies> = {\n // Deferred access: the getter runs only after `store` is initialized.\n get definitions() {\n return routeTreeToDefinitions(store.tree);\n },\n config: artifacts.config,\n tree: artifacts.tree,\n matcher: artifacts.matcher,\n urlParamsCache: new Map(),\n resolvedForwardMap: artifacts.resolvedForwardMap,\n routeCustomFields: artifacts.routeCustomFields,\n rootPath: \"\",\n matcherOptions,\n depsStore: undefined,\n lifecycleNamespace: undefined,\n pendingCanActivate: artifacts.pendingCanActivate,\n pendingCanDeactivate: artifacts.pendingCanDeactivate,\n };\n\n return store;\n}\n","// packages/core/src/transitionPath.ts\n\nimport { getStateMetaParams } from \"./stateMetaStore\";\n\nimport type { State } from \"./types\";\n\n/**\n * Parameters extracted from a route segment.\n * Maps parameter names to their string values.\n */\ntype PrimitiveParam = string | number | boolean;\n\n/**\n * Represents a transition path between two router states.\n * Contains information about which route segments need to be activated/deactivated.\n */\nexport interface TransitionPath {\n /** The common ancestor route segment where paths diverge */\n intersection: string;\n /** Route segments that need to be deactivated (in reverse order) */\n toDeactivate: string[];\n /** Route segments that need to be activated (in order) */\n toActivate: string[];\n}\n\n// Constants for better maintainability\nconst ROUTE_SEGMENT_SEPARATOR = \".\";\nconst EMPTY_INTERSECTION = \"\";\nconst DEFAULT_ROUTE_NAME = \"\";\nconst FROZEN_EMPTY_ARRAY: string[] = [];\n\nObject.freeze(FROZEN_EMPTY_ARRAY);\n\n/**\n * Handles conversion of route names with many segments (5+).\n * Internal helper for nameToIDs function.\n *\n * Uses optimized hybrid approach: split to get segments, then slice original\n * string to build cumulative paths. This approach is 65-81% faster than\n * string concatenation for typical cases (5-10 segments).\n *\n * @param name - Route name with 5 or more segments\n * @returns Array of cumulative segment IDs\n * @throws {Error} If route depth exceeds maximum allowed\n * @internal\n */\nfunction nameToIDsGeneral(name: string): string[] {\n // We know there are at least 5 segments at this point (after fast paths)\n const segments = name.split(ROUTE_SEGMENT_SEPARATOR);\n const segmentCount = segments.length;\n\n // First segment is always just itself\n const ids: string[] = [segments[0]];\n\n // Calculate cumulative lengths and slice from original string\n // This avoids repeated string concatenation (O(k²) → O(k))\n let cumulativeLength = segments[0].length;\n\n for (let i = 1; i < segmentCount - 1; i++) {\n cumulativeLength += 1 + segments[i].length; // +1 for dot separator\n ids.push(name.slice(0, cumulativeLength));\n }\n\n // Last segment is always the full route name\n ids.push(name);\n\n return ids;\n}\n\nconst PRIMITIVE_TYPES: ReadonlySet<string> = new Set([\n \"string\",\n \"number\",\n \"boolean\",\n]);\n\nfunction isPrimitive(value: unknown): value is PrimitiveParam {\n return PRIMITIVE_TYPES.has(typeof value);\n}\n\n/**\n * Compares segment parameters between two states without creating intermediate objects.\n * Returns true if all primitive params for the given segment are equal in both states.\n */\nfunction segmentParamsEqual(\n name: string,\n toMetaParams: Record<string, unknown>,\n toState: State,\n fromState: State,\n): boolean {\n const keys = toMetaParams[name];\n\n if (!keys || typeof keys !== \"object\") {\n return true;\n }\n\n for (const key of Object.keys(keys)) {\n const toVal = toState.params[key];\n const fromVal = fromState.params[key];\n\n if (\n isPrimitive(toVal) &&\n isPrimitive(fromVal) &&\n String(toVal) !== String(fromVal)\n ) {\n return false;\n }\n }\n\n return true;\n}\n\n/**\n * Finds the point where two state paths diverge based on segments and parameters.\n * Compares both segment names and their parameters to find the first difference.\n *\n * @param toMetaParams - Cached meta.params from toState (avoids per-segment WeakMap lookup)\n * @param toState - Target state\n * @param fromState - Source state\n * @param toStateIds - Segment IDs for target state\n * @param fromStateIds - Segment IDs for source state\n * @param maxI - Maximum index to check (minimum of both arrays)\n * @returns Index of first difference, or maxI if all checked segments match\n */\nfunction pointOfDifference(\n toMetaParams: Record<string, unknown>,\n toState: State,\n fromState: State,\n toStateIds: string[],\n fromStateIds: string[],\n maxI: number,\n): number {\n for (let i = 0; i < maxI; i++) {\n const toSegment = toStateIds[i];\n const fromSegment = fromStateIds[i];\n\n // Different segment names - immediate difference\n if (toSegment !== fromSegment) {\n return i;\n }\n\n if (!segmentParamsEqual(toSegment, toMetaParams, toState, fromState)) {\n return i;\n }\n }\n\n return maxI;\n}\n\n/**\n * Converts a route name to an array of hierarchical segment identifiers.\n * Each segment ID includes all parent segments in the path.\n *\n * @param name - Route name in dot notation (e.g., 'users.profile.edit')\n * @returns Array of cumulative segment IDs\n * @throws {Error} If route depth exceeds maximum allowed depth\n *\n * @example\n * // Simple route\n * nameToIDs('users');\n * // Returns: ['users']\n *\n * @example\n * // Nested route\n * nameToIDs('users.profile.edit');\n * // Returns: ['users', 'users.profile', 'users.profile.edit']\n *\n * @example\n * // Empty string (root route)\n * nameToIDs('');\n * // Returns: ['']\n *\n * @remarks\n * Input parameter is NOT validated in this function for performance reasons.\n * Validation significantly slows down nameToIDs execution.\n * The input should be validated by the function/method that calls nameToIDs.\n */\n// Module-global cache (shared across all router instances): bounded in practice by\n// the app's route-name vocabulary, which is stable across cloneRouter() requests, so\n// it does not grow per request. Intentionally NOT cleared on dispose() — it is not\n// per-router, so one router's teardown must not evict entries other routers rely on.\nconst nameToIDsCache = new Map<string, string[]>();\n\nexport function nameToIDs(name: string): string[] {\n const cached = nameToIDsCache.get(name);\n\n // Stryker disable next-line BlockStatement: equivalent — dropping the cache-hit early return recomputes the identical frozen id chain (the cache is a perf optimization, not a correctness gate).\n if (cached) {\n return cached;\n }\n\n const result = computeNameToIDs(name);\n\n Object.freeze(result);\n nameToIDsCache.set(name, result);\n\n return result;\n}\n\nfunction computeNameToIDs(name: string): string[] {\n if (!name) {\n return [DEFAULT_ROUTE_NAME];\n }\n\n const firstDot = name.indexOf(ROUTE_SEGMENT_SEPARATOR);\n\n if (firstDot === -1) {\n return [name];\n }\n\n const secondDot = name.indexOf(ROUTE_SEGMENT_SEPARATOR, firstDot + 1);\n\n if (secondDot === -1) {\n return [name.slice(0, firstDot), name];\n }\n\n const thirdDot = name.indexOf(ROUTE_SEGMENT_SEPARATOR, secondDot + 1);\n\n // Stryker disable next-line UnaryOperator,BlockStatement: equivalent — inverting/emptying the 3-segment fast path routes the name through nameToIDsGeneral (below), which yields the identical id chain (same rationale as the L242 ArithmeticOperator disable). The ConditionalExpression/EqualityOperator siblings stay live (→true and !== are killed).\n if (thirdDot === -1) {\n return [name.slice(0, firstDot), name.slice(0, secondDot), name];\n }\n\n // Stryker disable next-line ArithmeticOperator: equivalent — `thirdDot - 1` makes fourthDot non-(-1), routing 5+ segment names through nameToIDsGeneral, which yields the identical id chain.\n const fourthDot = name.indexOf(ROUTE_SEGMENT_SEPARATOR, thirdDot + 1);\n\n // Stryker disable next-line UnaryOperator,BlockStatement: equivalent — inverting/emptying the 4-segment fast path routes the name through nameToIDsGeneral (below), which yields the identical id chain (same rationale as the L242 ArithmeticOperator disable). The ConditionalExpression/EqualityOperator siblings stay live (→true and !== are killed).\n if (fourthDot === -1) {\n return [\n name.slice(0, firstDot),\n name.slice(0, secondDot),\n name.slice(0, thirdDot),\n name,\n ];\n }\n\n return nameToIDsGeneral(name);\n}\n\n/**\n * Calculates the transition path between two router states.\n * Determines which route segments need to be deactivated and activated\n * to transition from one state to another.\n *\n * @param toState - Target state to transition to\n * @param fromState - Current state to transition from (optional)\n * @returns Transition path with intersection and segments to activate/deactivate\n *\n * @throws {TypeError} When toState is null or undefined\n * @throws {TypeError} When toState is not an object\n * @throws {TypeError} When toState.name is missing or not a string\n * @throws {TypeError} When toState.params is missing or not an object\n * @throws {TypeError} When toState.path is missing or not a string\n * @throws {TypeError} When toState.name contains invalid route format:\n * - Contains only whitespace (e.g., \" \")\n * - Has consecutive dots (e.g., \"users..profile\")\n * - Has leading/trailing dots (e.g., \".users\" or \"users.\")\n * - Segments don't match pattern [a-zA-Z_][a-zA-Z0-9_-]* (e.g., \"users.123\")\n * - Contains spaces or special characters (e.g., \"users profile\")\n * - Exceeds maximum length (8192 characters)\n * @throws {TypeError} When fromState is provided and has any of the validation errors listed above for toState\n *\n * @example\n * // ✅ Valid calls\n * getTransitionPath({ name: 'users.profile', params: {}, path: '/users/profile' });\n * getTransitionPath(toState, fromState);\n * getTransitionPath({ name: '', params: {}, path: '/' }); // root route\n *\n * @example\n * // ❌ Invalid calls that throw TypeError\n * getTransitionPath(null); // toState is null\n * getTransitionPath(undefined); // toState is undefined\n * getTransitionPath({}); // missing required fields\n * getTransitionPath({ name: 123, params: {}, path: '/' }); // name not a string\n * getTransitionPath({ name: 'home', path: '/' }); // missing params\n * getTransitionPath({ name: 'users..profile', params: {}, path: '/' }); // consecutive dots\n * getTransitionPath({ name: '.users', params: {}, path: '/' }); // leading dot\n * getTransitionPath({ name: 'users.', params: {}, path: '/' }); // trailing dot\n * getTransitionPath({ name: 'users profile', params: {}, path: '/' }); // contains space\n * getTransitionPath({ name: 'users.123', params: {}, path: '/' }); // segment starts with number\n * getTransitionPath(validToState, { name: 'invalid..route', params: {}, path: '/' }); // fromState invalid\n *\n * @example\n * // Full activation (no fromState)\n * getTransitionPath(makeState('users.profile'));\n * // Returns: {\n * // intersection: '',\n * // toActivate: ['users', 'users.profile'],\n * // toDeactivate: []\n * // }\n *\n * @example\n * // Partial transition with common ancestor\n * getTransitionPath(\n * makeState('users.profile'),\n * makeState('users.list')\n * );\n * // Returns: {\n * // intersection: 'users',\n * // toActivate: ['users.profile'],\n * // toDeactivate: ['users.list']\n * // }\n *\n * @example\n * // Complete route change\n * getTransitionPath(\n * makeState('admin.dashboard'),\n * makeState('users.profile')\n * );\n * // Returns: {\n * // intersection: '',\n * // toActivate: ['admin', 'admin.dashboard'],\n * // toDeactivate: ['users.profile', 'users']\n * // }\n */\n// Single-entry cache: shouldUpdateNode calls getTransitionPath N times per\n// navigation with the same state objects (once per subscribed node).\n// Cache by reference eliminates N-1 redundant computations.\n// Module-global (≤2 State refs); not cleared on dispose — negligible, not per-router.\nlet cached1To: State | undefined;\nlet cached1From: State | undefined;\nlet cached1Result: TransitionPath | null = null;\n\nlet cached2To: State | undefined;\nlet cached2From: State | undefined;\nlet cached2Result: TransitionPath | null = null;\n\nfunction computeTransitionPath(\n toState: State,\n fromState?: State,\n): TransitionPath {\n // ===== FAST PATH 1: Initial navigation (no fromState) =====\n // This is the best performing case in benchmarks (5M ops/sec)\n if (!fromState) {\n return {\n intersection: EMPTY_INTERSECTION,\n toActivate: nameToIDs(toState.name),\n toDeactivate: FROZEN_EMPTY_ARRAY,\n };\n }\n\n // ===== FAST PATH 3: Missing meta requires full reload =====\n // Single WeakMap lookup per state, reused in pointOfDifference/segmentParamsEqual\n const toMetaParams = getStateMetaParams(toState);\n const fromMetaParams = getStateMetaParams(fromState);\n\n if (!toMetaParams && !fromMetaParams) {\n // FAST PATH 3 (both states meta-less). Consumers that land here read the\n // result order-INSENSITIVELY, so the from-chain is returned as-is\n // (root→leaf, no reverse needed):\n // • `shouldUpdateNode` reads `toDeactivate` by MEMBERSHIP (`.includes`).\n // • Externally-supplied meta-less states (e.g. a plugin passing a raw\n // `{name, params, path}` to `navigateToState`) land here. Since #1170,\n // `navigateToState` carries the source's WeakMap meta across its writable\n // shell, so start()/popstate states are NOT meta-less. A `replace()`\n // survivor stays meta-less but is benign: the next transition's `toState`\n // always carries meta (buildNavigateState), so this both-meta-less path\n // is not reached from it.\n // (`canNavigateTo` no longer reaches this path — since #970 it builds its\n // toState WITH meta, mirroring buildNavigateState.)\n // The navigate pipeline always carries meta (buildNavigateState) → STANDARD\n // PATH below, which trims the shared ancestor and reverses correctly.\n return {\n intersection: EMPTY_INTERSECTION,\n toActivate: nameToIDs(toState.name),\n toDeactivate: nameToIDs(fromState.name),\n };\n }\n\n // ===== STANDARD PATH: Routes with parameters =====\n const toStateIds = nameToIDs(toState.name);\n const fromStateIds = nameToIDs(fromState.name);\n // Stryker disable next-line MethodExpression: equivalent — Math.max reads one index past the shorter id array; that slot is undefined, so the `toSegment !== fromSegment` check in pointOfDifference returns the same divergence index Math.min would stop at.\n const maxI = Math.min(fromStateIds.length, toStateIds.length);\n\n const i = pointOfDifference(\n (toMetaParams ?? fromMetaParams) as Record<string, unknown>,\n toState,\n fromState,\n toStateIds,\n fromStateIds,\n maxI,\n );\n\n // Optimization: Build deactivation list in reverse order directly\n // instead of slice(i).toReversed() which creates 2 arrays\n let toDeactivate: string[];\n\n if (i >= fromStateIds.length) {\n toDeactivate = FROZEN_EMPTY_ARRAY;\n } else if (i === 0 && fromStateIds.length === 1) {\n // Single-segment route: reversed = original, reuse cached frozen array\n toDeactivate = fromStateIds;\n } else {\n toDeactivate = [];\n\n for (let j = fromStateIds.length - 1; j >= i; j--) {\n toDeactivate.push(fromStateIds[j]);\n }\n }\n\n // Build activation list — reuse cached frozen array when using full list\n const toActivate = i === 0 ? toStateIds : toStateIds.slice(i);\n\n // Determine intersection point (common ancestor)\n const intersection = i > 0 ? fromStateIds[i - 1] : EMPTY_INTERSECTION;\n\n return {\n intersection,\n toDeactivate,\n toActivate,\n };\n}\n\nexport function getTransitionPath(\n toState: State,\n fromState?: State,\n): TransitionPath {\n // Stryker disable BlockStatement: equivalent — both cache short-circuits below; emptying either early-return recomputes the identical TransitionPath (computeTransitionPath is deterministic for the same to/from states) and re-caches it. Restored right after.\n if (\n cached1Result !== null &&\n toState === cached1To &&\n fromState === cached1From\n ) {\n return cached1Result;\n }\n\n if (\n cached2Result !== null &&\n toState === cached2To &&\n fromState === cached2From\n ) {\n return cached2Result;\n }\n // Stryker restore BlockStatement\n\n const result = computeTransitionPath(toState, fromState);\n\n cached2To = cached1To;\n cached2From = cached1From;\n cached2Result = cached1Result;\n\n cached1To = toState;\n cached1From = fromState;\n cached1Result = result;\n\n return result;\n}\n","// packages/core/src/namespaces/RoutesNamespace/RoutesNamespace.ts\n\nimport { DEFAULT_ROUTE_NAME } from \"./constants\";\nimport {\n matchSourceTrailingSlash,\n paramsMatch,\n paramsMatchExcluding,\n stripQueryDefaults,\n} from \"./helpers\";\nimport {\n createRoutesStore,\n rebuildTreeInPlace,\n resetStore,\n} from \"./routesStore\";\nimport { constants, DEFAULT_TRANSITION } from \"../../constants\";\nimport { getTransitionPath } from \"../../transitionPath\";\n\nimport type { RoutesStore } from \"./routesStore\";\nimport type { RoutesDependencies } from \"./types\";\nimport type {\n CreateMatcherOptions,\n RouteParams,\n RouteTree,\n RouteTreeState,\n} from \"../../engine\";\nimport type {\n DefaultDependencies,\n ForwardToCallback,\n Options,\n Params,\n RouterLogger,\n State,\n Route,\n} from \"../../types\";\nimport type { RouteLifecycleNamespace } from \"../RouteLifecycleNamespace\";\n\nfunction collectUrlParamsArray(segments: readonly RouteTree[]): string[] {\n const params: string[] = [];\n\n for (const segment of segments) {\n for (const param of segment.paramMeta.urlParams) {\n params.push(param);\n }\n }\n\n return params;\n}\n\nfunction createRouteState<P extends RouteParams = RouteParams>(\n matchResult: {\n readonly segments: readonly { fullName: string }[];\n readonly params: Readonly<Record<string, unknown>>;\n readonly meta: Readonly<Record<string, Record<string, \"url\" | \"query\">>>;\n },\n name?: string,\n): RouteTreeState<P> {\n // The matcher yields ≥1 segment for every successful match, each carrying the\n // cumulative route name as `fullName`, so the last element is always present.\n // (Formerly `buildNameFromSegments` with a `?? \"\"` fallback — that branch was\n // unreachable defensive cruft propped up by a white-box test; inlined here.)\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- matcher invariant: a successful match is never empty\n const resolvedName = name ?? matchResult.segments.at(-1)!.fullName;\n\n return {\n name: resolvedName,\n params: matchResult.params as P,\n meta: matchResult.meta,\n };\n}\n\ninterface CachedBuildPathOpts {\n readonly trailingSlash?: \"always\" | \"never\" | undefined;\n readonly queryParamsMode?: \"default\" | \"strict\" | \"loose\" | undefined;\n}\n\n/**\n * Independent namespace for managing routes.\n *\n * Static methods handle validation (called by facade).\n * Instance methods handle storage and business logic.\n */\nexport class RoutesNamespace<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n> {\n readonly #store: RoutesStore<Dependencies>;\n #cachedBuildPathOpts: CachedBuildPathOpts | undefined;\n // Source `options` reference captured on the first #getBuildPathOptions call;\n // used only by the dev-build immutability assertion below (#957).\n #cachedOptionsSource: Options | undefined;\n\n get #deps(): RoutesDependencies<Dependencies> {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n return this.#store.depsStore!;\n }\n\n constructor(\n // No `= []` default: the sole caller (Router's ctor) always passes its own\n // already-defaulted `routes` — a namespace-level default would be dead code\n // and a default-before-required-params smell (S1788).\n routes: Route<Dependencies>[],\n matcherOptions: CreateMatcherOptions | undefined,\n logger: RouterLogger,\n ) {\n this.#store = createRoutesStore(routes, matcherOptions, logger);\n }\n\n /**\n * Creates a predicate function to check if a route node should be updated.\n * Note: Argument validation is done by facade (Router.ts) via validateShouldUpdateNodeArgs.\n */\n static shouldUpdateNode(\n nodeName: string,\n ): (toState: State, fromState?: State) => boolean {\n return (toState: State, fromState?: State): boolean => {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (!(toState && typeof toState === \"object\" && \"name\" in toState)) {\n throw new TypeError(\n \"[router.shouldUpdateNode] toState must be valid State object\",\n );\n }\n\n if (toState.transition.reload) {\n return true;\n }\n\n // Root node (DEFAULT_ROUTE_NAME === \"\") has no route-level identity — it\n // represents \"any route\". It must update on every transition so that\n // consumers subscribed via useRouteNode(\"\") (including RouteView at\n // the top of the tree) see every change. This matches the documented\n // contract in adapter docs: `useRouteNode(\"\")` — Root — ALL route\n // changes. See #519 for the missed transitions it was suffering from\n // (users → users.user had intersection=\"users\", leaving the root node\n // un-updated under a flat <Match segment=\"users.user\" exact> pattern).\n if (nodeName === DEFAULT_ROUTE_NAME) {\n return true;\n }\n\n const { intersection, toActivate, toDeactivate } = getTransitionPath(\n toState,\n fromState,\n );\n\n if (nodeName === intersection) {\n return true;\n }\n\n if (toActivate.includes(nodeName)) {\n return true;\n }\n\n return toDeactivate.includes(nodeName);\n };\n }\n\n // =========================================================================\n // Dependency injection\n // =========================================================================\n\n /**\n * Sets dependencies. Pure assignment — no side effects (#1331).\n *\n * The pending canActivate/canDeactivate factories from initial routes are\n * flushed separately by {@link flushPendingGuards}, called once wiring is\n * complete, so the order of the wire-* calls is unconstrained.\n */\n setDependencies(deps: RoutesDependencies<Dependencies>): void {\n this.#store.depsStore = deps;\n }\n\n /**\n * Registers the pending guard factories collected from initial route\n * definitions. Deferred out of {@link setDependencies} (#1331) so it runs on\n * a fully-built, fully-bound router: a guard factory that calls any\n * `router.*` method sees a ready instance instead of a half-assembled one.\n *\n * Invoked as the last step of the Router constructor. Idempotent after the\n * first call (the pending maps are cleared). Runtime `add()`/`replace()`\n * compile guards in their own PREPARE phase and never populate these maps.\n */\n flushPendingGuards(): void {\n const deps = this.#deps;\n\n for (const [routeName, handler] of this.#store.pendingCanActivate) {\n deps.addActivateGuard(routeName, handler);\n }\n\n this.#store.pendingCanActivate.clear();\n\n for (const [routeName, handler] of this.#store.pendingCanDeactivate) {\n deps.addDeactivateGuard(routeName, handler);\n }\n\n this.#store.pendingCanDeactivate.clear();\n }\n\n /**\n * Sets the lifecycle namespace reference.\n */\n setLifecycleNamespace(\n namespace: RouteLifecycleNamespace<Dependencies> | undefined,\n ): void {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n this.#store.lifecycleNamespace = namespace!;\n }\n\n // =========================================================================\n // Route tree operations\n // =========================================================================\n\n setRootPath(newRootPath: string): void {\n this.#store.rootPath = newRootPath;\n rebuildTreeInPlace(this.#store);\n }\n\n hasRoute(name: string): boolean {\n return this.#store.matcher.hasRoute(name);\n }\n\n clearRoutes(): void {\n resetStore(this.#store);\n }\n\n // =========================================================================\n // Path operations\n // =========================================================================\n\n /**\n * Builds a URL path for a route.\n * Note: Argument validation is done by facade (Router.ts) via validateBuildPathArgs.\n *\n * @param route - Route name\n * @param params - Route parameters\n * @param options - Router options\n */\n buildPath(route: string, params?: Params, options?: Options): string {\n if (route === constants.UNKNOWN_ROUTE) {\n return typeof params?.path === \"string\" ? params.path : \"\";\n }\n\n const paramsWithDefault = Object.hasOwn(\n this.#store.config.defaultParams,\n route,\n )\n ? { ...this.#store.config.defaultParams[route], ...params }\n : /* v8 ignore next -- @preserve: V8 can't track ?? branch in ternary; covered by buildPath tests without params */ (params ??\n {});\n\n const encodedParams =\n typeof this.#store.config.encoders[route] === \"function\"\n ? this.#store.config.encoders[route]({ ...paramsWithDefault })\n : paramsWithDefault;\n\n return this.#store.matcher.buildPath(\n route,\n encodedParams,\n this.#getBuildPathOptions(options),\n );\n }\n\n /**\n * Matches a URL path to a route in the tree.\n * Note: Argument validation is done by facade (Router.ts) via validateMatchPathArgs.\n */\n matchPath<P extends Params = Params>(\n path: string,\n options?: Options,\n ): State<P> | undefined {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- Router.ts always passes options\n const opts = options!;\n\n const matchResult = this.#store.matcher.match(path);\n\n if (!matchResult) {\n return undefined;\n }\n\n const routeState = createRouteState(matchResult);\n const { name, params, meta } = routeState;\n\n const decodedParams =\n typeof this.#store.config.decoders[name] === \"function\"\n ? this.#store.config.decoders[name](params)\n : params;\n\n const { name: routeName, params: routeParams } = this.#deps.forwardState<P>(\n name,\n decodedParams as P,\n );\n\n let builtPath = path;\n\n if (opts.rewritePathOnMatch) {\n const buildParams =\n typeof this.#store.config.encoders[routeName] === \"function\"\n ? this.#store.config.encoders[routeName]({\n ...(routeParams as Params),\n })\n : (routeParams as Record<string, unknown>);\n\n const ts = opts.trailingSlash;\n\n try {\n builtPath = this.#store.matcher.buildPath(routeName, buildParams, {\n trailingSlash: ts === \"never\" || ts === \"always\" ? ts : undefined,\n queryParamsMode: opts.queryParamsMode,\n });\n\n if (ts === \"preserve\") {\n builtPath = matchSourceTrailingSlash(path, builtPath);\n }\n } catch {\n // The match already succeeded (route found, params decoded); only the\n // post-match path rewrite threw — e.g. a custom encoder handed buildPath\n // a query value its codec cannot serialise. Keep the source path\n // un-rewritten rather than discard a valid match (#1157). Opposite of the\n // parse side (#737): there a throw means \"URL not understood\" → unmatched;\n // here the URL WAS matched and only re-canonicalisation failed.\n builtPath = path;\n }\n }\n\n return this.#deps.makeState<P>(routeName, routeParams, builtPath, meta);\n }\n\n /**\n * Applies forwardTo and returns resolved state with merged defaultParams.\n *\n * Merges params in order:\n * 1. Source route defaultParams\n * 2. Provided params\n * 3. Target route defaultParams (after resolving forwardTo)\n */\n forwardState<P extends Params = Params>(\n name: string,\n params: P,\n ): { name: string; params: P } {\n if (Object.hasOwn(this.#store.config.forwardFnMap, name)) {\n const paramsWithSourceDefaults = this.#mergeDefaultParams(name, params);\n const dynamicForward = this.#store.config.forwardFnMap[name];\n const resolved = this.#resolveDynamicForward(\n name,\n dynamicForward,\n params,\n );\n\n return {\n name: resolved,\n params: this.#mergeDefaultParams(resolved, paramsWithSourceDefaults),\n };\n }\n\n const staticForward = this.#store.resolvedForwardMap[name] ?? name;\n\n if (\n staticForward !== name &&\n Object.hasOwn(this.#store.config.forwardFnMap, staticForward)\n ) {\n const paramsWithSourceDefaults = this.#mergeDefaultParams(name, params);\n const targetDynamicForward =\n this.#store.config.forwardFnMap[staticForward];\n const resolved = this.#resolveDynamicForward(\n staticForward,\n targetDynamicForward,\n params,\n );\n\n return {\n name: resolved,\n params: this.#mergeDefaultParams(resolved, paramsWithSourceDefaults),\n };\n }\n\n if (staticForward !== name) {\n const paramsWithSourceDefaults = this.#mergeDefaultParams(name, params);\n\n return {\n name: staticForward,\n params: this.#mergeDefaultParams(\n staticForward,\n paramsWithSourceDefaults,\n ),\n };\n }\n\n return { name, params: this.#mergeDefaultParams(name, params) };\n }\n\n /**\n * Builds a RouteTreeState from already-resolved route name and params.\n * Called by Router.buildState after forwardState is applied at facade level.\n * This allows plugins to intercept forwardState.\n */\n buildStateResolved(\n resolvedName: string,\n resolvedParams: Params,\n ): RouteTreeState | undefined {\n const segments = this.#store.matcher.getSegmentsByName(resolvedName);\n\n if (!segments) {\n return undefined;\n }\n\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const meta = this.#store.matcher.getMetaByName(resolvedName)!;\n\n return createRouteState(\n { segments, params: resolvedParams, meta },\n resolvedName,\n );\n }\n\n // =========================================================================\n // Query operations\n // =========================================================================\n\n /**\n * Checks if a route is currently active.\n */\n isActiveRoute(\n name: string,\n params: Params = {},\n strictEquality = false,\n ignoreQueryParams = true,\n ): boolean {\n // Note: empty string check is handled by Router.ts facade\n const activeState = this.#deps.getState();\n\n if (!activeState) {\n return false;\n }\n\n const activeName = activeState.name;\n\n // Fast path: check if routes are related before expensive operations\n if (\n activeName !== name &&\n !activeName.startsWith(`${name}.`) &&\n !name.startsWith(`${activeName}.`)\n ) {\n return false;\n }\n\n const defaultParams = this.#store.config.defaultParams[name] as\n Params | undefined;\n\n // Exact match case\n if (strictEquality || activeName === name) {\n const effectiveParams = defaultParams\n ? { ...defaultParams, ...params }\n : params;\n\n const targetState: State = {\n name,\n params: effectiveParams,\n path: \"\",\n transition: DEFAULT_TRANSITION,\n context: {},\n };\n\n return this.#deps.areStatesEqual(\n targetState,\n activeState,\n ignoreQueryParams,\n );\n }\n\n // The fast path above lets through three relations: exact (handled in\n // the previous block), `activeName` descendant of `name`, and `name`\n // descendant of `activeName`. Only the first two count as \"active\" —\n // a link pointing DEEPER than the current state is a navigation option,\n // not an active state. Reject the descendant-of-active case explicitly.\n if (!activeName.startsWith(`${name}.`)) {\n return false;\n }\n\n // Hierarchical check: activeState is a descendant of target (name)\n const activeParams = activeState.params;\n\n if (!paramsMatch(params, activeParams)) {\n return false;\n }\n\n if (!defaultParams) {\n return true;\n }\n\n // Honor `ignoreQueryParams` symmetrically with the exact-match branch\n // above: query-only param differences (e.g. parent has\n // `defaultParams: { sort: \"asc\" }` while the active descendant is\n // `products.detail` with `params: { id: \"6\" }` and no sort) must not\n // disqualify an ancestor link from being active. Strip query-typed\n // keys of `name` from the defaults before comparison; URL-typed keys\n // (`:id`, `:role`, etc.) are still enforced.\n // `name` reaches this point only after the fast-path established a valid\n // hierarchical relation AND `defaultParams` is non-null — both imply the\n // matcher has registered the route. Since the #1414 skip-empty meta, a\n // fully-static route resolves to the shared EMPTY_ROUTE_META with no own\n // entry, so `getMetaByName(name)?.[name]` is undefined there — exactly the\n // \"nothing to strip\" signal stripQueryDefaults short-circuits on (a static\n // route cannot declare query params, hence cannot carry query defaults).\n const defaultsToCheck = ignoreQueryParams\n ? stripQueryDefaults(\n defaultParams,\n this.#store.matcher.getMetaByName(name)?.[name],\n )\n : defaultParams;\n\n return paramsMatchExcluding(defaultsToCheck, activeParams, params);\n }\n\n getMetaForState(\n name: string,\n ): Record<string, Record<string, \"url\" | \"query\">> | undefined {\n return this.#store.matcher.hasRoute(name)\n ? this.#store.matcher.getMetaByName(name)\n : undefined;\n }\n\n getUrlParams(name: string): string[] {\n const cached = this.#store.urlParamsCache.get(name);\n\n // Stryker disable next-line BlockStatement: equivalent — cache short-circuit; emptying the early-return recomputes the identical value (getUrlParams is deterministic per route name) and re-caches it. (ConditionalExpression stays live: `→true` returns undefined on a cache miss = killed.)\n if (cached !== undefined) {\n return cached;\n }\n\n const segments = this.#store.matcher.getSegmentsByName(name);\n const result = segments\n ? collectUrlParamsArray(segments as readonly RouteTree[])\n : [];\n\n this.#store.urlParamsCache.set(name, result);\n\n return result;\n }\n\n getStore(): RoutesStore<Dependencies> {\n return this.#store;\n }\n\n #mergeDefaultParams<P extends Params = Params>(\n routeName: string,\n params: P,\n ): P {\n if (Object.hasOwn(this.#store.config.defaultParams, routeName)) {\n return {\n ...this.#store.config.defaultParams[routeName],\n ...params,\n };\n }\n\n return params;\n }\n\n #getBuildPathOptions(options?: Options): CachedBuildPathOpts {\n // Stryker disable next-line BlockStatement: equivalent — cache short-circuit; emptying the early-return rebuilds the identical buildPath options (deterministic) and re-caches them. (ConditionalExpression stays live: `→false` always rebuilds but a real consumer test pins the cached identity.)\n if (this.#cachedBuildPathOpts) {\n /* v8 ignore next 5 -- @preserve: dev assertion guarding a future caller that passes per-call varying options; the sole caller (Router.buildPath, always via this.#options.get()) passes the same immutable, deep-frozen per-instance options, so this branch is unreachable through the public API by construction (#957) */\n if (options !== this.#cachedOptionsSource) {\n this.#deps.logger.warn(\n \"router.buildPath\",\n \"`options` differs from the cached source reference; router options are immutable per router instance, so the first-cached buildPath options are reused (#957).\",\n );\n }\n\n return this.#cachedBuildPathOpts;\n }\n\n this.#cachedOptionsSource = options;\n\n const ts = options?.trailingSlash;\n\n this.#cachedBuildPathOpts = Object.freeze({\n trailingSlash: ts === \"never\" || ts === \"always\" ? ts : undefined,\n queryParamsMode: options?.queryParamsMode,\n });\n\n return this.#cachedBuildPathOpts;\n }\n\n #resolveDynamicForward(\n startName: string,\n startFn: ForwardToCallback<Dependencies>,\n params: Params,\n ): string {\n const visited = new Set<string>([startName]);\n\n let current = startFn(this.#deps.getDependency, params);\n let depth = 0;\n const MAX_DEPTH = 100;\n\n if (typeof current !== \"string\") {\n throw new TypeError(\n `forwardTo callback must return a string, got ${typeof current}`,\n );\n }\n\n while (depth < MAX_DEPTH) {\n if (this.#store.matcher.getSegmentsByName(current) === undefined) {\n throw new Error(`Route \"${current}\" does not exist`);\n }\n\n if (visited.has(current)) {\n const chain = [...visited, current].join(\" → \");\n\n throw new Error(`Circular forwardTo: ${chain}`);\n }\n\n visited.add(current);\n\n if (Object.hasOwn(this.#store.config.forwardFnMap, current)) {\n const fn = this.#store.config.forwardFnMap[\n current\n ] as ForwardToCallback<Dependencies>;\n\n current = fn(this.#deps.getDependency, params);\n\n depth++;\n continue;\n }\n\n const staticForward = this.#store.config.forwardMap[current];\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (staticForward !== undefined) {\n current = staticForward;\n depth++;\n continue;\n }\n\n return current;\n }\n\n throw new Error(`forwardTo exceeds maximum depth of ${MAX_DEPTH}`);\n }\n}\n","// packages/core/src/RouterError.ts\n\nimport { errorCodes } from \"./constants\";\n\n// Pre-compute Set of error code values for O(1) lookup in setCode()\n// This avoids creating array and doing linear search on every setCode() call\nconst errorCodeValues = new Set(Object.values(errorCodes));\n\n// Reserved built-in properties - throw error if user tries to set these\nconst reservedProperties = new Set([\"code\", \"segment\", \"path\"]);\n\n// Reserved method names - silently ignore attempts to overwrite these\nconst reservedMethods = new Set([\n \"setCode\",\n \"setErrorInstance\",\n \"setAdditionalFields\",\n \"hasField\",\n \"getField\",\n \"toJSON\",\n]);\n\nexport class RouterError extends Error {\n [key: string]: unknown;\n\n // Using public properties to ensure structural compatibility\n // with RouterError interface in core-types\n readonly segment: string | undefined;\n readonly path: string | undefined;\n\n // Note: code appears to be writable but setCode() should be used\n // to properly update both code and message together\n code: string;\n\n /**\n * Creates a new RouterError instance.\n *\n * The options object accepts built-in fields (message, segment, path)\n * and any additional custom fields, which will all be attached to the error instance.\n *\n * @param code - The error code (e.g., \"ROUTE_NOT_FOUND\", \"CANNOT_ACTIVATE\")\n * @param options - Optional configuration object\n * @param options.message - Custom error message (defaults to code if not provided)\n * @param options.segment - The route segment where the error occurred\n * @param options.path - The full path where the error occurred\n *\n * @example\n * ```typescript\n * // Basic error\n * const err1 = new RouterError(\"ROUTE_NOT_FOUND\");\n *\n * // Error with custom message\n * const err2 = new RouterError(\"ERR\", { message: \"Something went wrong\" });\n *\n * // Error with context and custom fields\n * const err3 = new RouterError(\"CANNOT_ACTIVATE\", {\n * message: \"Insufficient permissions\",\n * segment: \"admin\",\n * path: \"/admin/users\",\n * userId: \"123\" // custom field\n * });\n * ```\n */\n constructor(\n code: string,\n {\n message,\n segment,\n path,\n ...rest\n }: {\n [key: string]: unknown;\n message?: string | undefined;\n segment?: string | undefined;\n path?: string | undefined;\n } = {},\n ) {\n super(message ?? code);\n\n // Subclasses don't auto-set `name`; without this `error.name` inherits\n // \"Error\", breaking `error.name === \"RouterError\"` checks at catch sites that\n // can't `instanceof` across bundle boundaries.\n this.name = \"RouterError\";\n\n this.code = code;\n this.segment = segment;\n this.path = path;\n\n // Assign custom fields, checking reserved properties and filtering out reserved method names\n // Issue #39: Throw for reserved properties to match setAdditionalFields behavior\n for (const [key, value] of Object.entries(rest)) {\n if (reservedProperties.has(key)) {\n throw new TypeError(\n `[RouterError] Cannot set reserved property \"${key}\"`,\n );\n }\n\n if (!reservedMethods.has(key)) {\n this[key] = value;\n }\n }\n }\n\n /**\n * Updates the error code and conditionally updates the message.\n *\n * If the current message is one of the standard error code values\n * (e.g., \"ROUTE_NOT_FOUND\", \"SAME_STATES\"), it will be replaced with the new code.\n * This allows keeping error messages in sync with codes when using standard error codes.\n *\n * If the message is custom (not a standard error code), it will be preserved.\n *\n * @param newCode - The new error code to set\n *\n * @example\n * // Message follows code (standard error code as message)\n * const err = new RouterError(\"ROUTE_NOT_FOUND\", { message: \"ROUTE_NOT_FOUND\" });\n * err.setCode(\"CUSTOM_ERROR\"); // message becomes \"CUSTOM_ERROR\"\n *\n * @example\n * // Custom message is preserved\n * const err = new RouterError(\"ERR\", { message: \"Custom error message\" });\n * err.setCode(\"NEW_CODE\"); // message stays \"Custom error message\"\n */\n setCode(newCode: string): void {\n this.code = newCode;\n\n // Only update message if it's a standard error code value (not a custom message)\n if (errorCodeValues.has(this.message)) {\n this.message = newCode;\n }\n }\n\n /**\n * Copies properties from another Error instance to this RouterError.\n *\n * This method updates the message, cause, and stack trace from the provided error.\n * Useful for wrapping native errors while preserving error context.\n *\n * @param err - The Error instance to copy properties from\n * @throws {TypeError} If err is null or undefined\n *\n * @example\n * ```typescript\n * const routerErr = new RouterError(\"TRANSITION_ERR\");\n * try {\n * // some operation that might fail\n * } catch (nativeErr) {\n * routerErr.setErrorInstance(nativeErr);\n * throw routerErr;\n * }\n * ```\n */\n setErrorInstance(err: Error): void {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (!err) {\n throw new TypeError(\n \"[RouterError.setErrorInstance] err parameter is required and must be an Error instance\",\n );\n }\n\n this.message = err.message;\n this.cause = err.cause;\n this.stack = err.stack ?? \"\";\n }\n\n /**\n * Adds custom fields to the error object.\n *\n * This method allows attaching arbitrary data to the error for debugging or logging purposes.\n * All fields become accessible as properties on the error instance and are included in JSON serialization.\n *\n * Reserved method names (setCode, setErrorInstance, setAdditionalFields, hasField, getField, toJSON)\n * are automatically filtered out to prevent accidental overwriting of class methods.\n *\n * @param fields - Object containing custom fields to add to the error\n *\n * @example\n * ```typescript\n * const err = new RouterError(\"CANNOT_ACTIVATE\");\n * err.setAdditionalFields({\n * userId: \"123\",\n * attemptedRoute: \"/admin\",\n * reason: \"insufficient permissions\"\n * });\n *\n * console.log(err.userId); // \"123\"\n * console.log(JSON.stringify(err)); // includes all custom fields\n * ```\n */\n setAdditionalFields(fields: Record<string, unknown>): void {\n // Assign fields, throwing for reserved properties, silently ignoring methods\n for (const [key, value] of Object.entries(fields)) {\n if (reservedProperties.has(key)) {\n throw new TypeError(\n `[RouterError.setAdditionalFields] Cannot set reserved property \"${key}\"`,\n );\n }\n\n if (!reservedMethods.has(key)) {\n this[key] = value;\n }\n }\n }\n\n /**\n * Checks if a custom field exists on the error object.\n *\n * This method checks for both custom fields added via setAdditionalFields()\n * and built-in fields (code, message, segment, etc.).\n *\n * @param key - The field name to check\n * @returns `true` if the field exists, `false` otherwise\n *\n * @example\n * ```typescript\n * const err = new RouterError(\"ERR\", { segment: \"users\" });\n * err.setAdditionalFields({ userId: \"123\" });\n *\n * err.hasField(\"userId\"); // true\n * err.hasField(\"segment\"); // true\n * err.hasField(\"unknown\"); // false\n * ```\n */\n hasField(key: string): boolean {\n return key in this;\n }\n\n /**\n * Retrieves a custom field value from the error object.\n *\n * This method can access both custom fields and built-in fields.\n * Returns `undefined` if the field doesn't exist.\n *\n * @param key - The field name to retrieve\n * @returns The field value, or `undefined` if it doesn't exist\n *\n * @example\n * ```typescript\n * const err = new RouterError(\"ERR\");\n * err.setAdditionalFields({ userId: \"123\", role: \"admin\" });\n *\n * err.getField(\"userId\"); // \"123\"\n * err.getField(\"role\"); // \"admin\"\n * err.getField(\"code\"); // \"ERR\" (built-in field)\n * err.getField(\"unknown\"); // undefined\n * ```\n */\n getField(key: string): unknown {\n return this[key];\n }\n\n /**\n * Serializes the error to a JSON-compatible object.\n *\n * This method is automatically called by JSON.stringify() and includes:\n * - Built-in fields: code, message, segment (if set), path (if set)\n * - All custom fields added via setAdditionalFields() or constructor\n * - Excludes: stack trace (for security/cleanliness)\n *\n * @returns A plain object representation of the error, suitable for JSON serialization\n *\n * @example\n * ```typescript\n * const err = new RouterError(\"ROUTE_NOT_FOUND\", {\n * message: \"Route not found\",\n * path: \"/admin/users/123\"\n * });\n * err.setAdditionalFields({ userId: \"123\" });\n *\n * JSON.stringify(err);\n * // {\n * // \"code\": \"ROUTE_NOT_FOUND\",\n * // \"message\": \"Route not found\",\n * // \"path\": \"/admin/users/123\",\n * // \"userId\": \"123\"\n * // }\n * ```\n */\n toJSON(): Record<string, unknown> {\n const result: Record<string, unknown> = {\n code: this.code,\n message: this.message,\n };\n\n if (this.segment !== undefined) {\n result.segment = this.segment;\n }\n if (this.path !== undefined) {\n result.path = this.path;\n }\n\n // add all public fields\n // Using Set.has() for O(1) lookup instead of Array.includes() O(n)\n // Overall complexity: O(n) instead of O(n*m)\n const excludeKeys = new Set([\n \"code\",\n \"message\",\n \"segment\",\n \"path\",\n \"stack\",\n // `name` is now an own enumerable prop (constructor sets it to\n // \"RouterError\"); it's class metadata, not a custom field — keep it out of\n // the serialized output (preserves toJSON shape).\n \"name\",\n ]);\n\n for (const key in this) {\n if (Object.hasOwn(this, key) && !excludeKeys.has(key)) {\n result[key] = this[key];\n }\n }\n\n return result;\n }\n}\n","// packages/core/src/namespaces/NavigationNamespace/constants.ts\n\nimport { errorCodes } from \"../../constants\";\nimport { RouterError } from \"../../RouterError\";\n\nimport type { State } from \"../../types\";\n\n// =============================================================================\n// Cached Errors & Rejected Promises (Performance Optimization)\n// =============================================================================\n// Pre-create error instances and rejected promises for sync error paths\n// in navigate(). Eliminates per-call allocations:\n// - new RouterError() — object + stack trace capture (~500ns-2μs)\n// - Promise.reject() — promise allocation\n// - .catch(handler) — derived promise from suppression\n//\n// Trade-off: All error instances share the same stack trace (points here).\n// This is acceptable because:\n// 1. These errors indicate expected conditions, not internal bugs\n// 2. Error code and message are sufficient for debugging\n// 3. The facade skips .catch() suppression for cached promises (zero alloc)\n// =============================================================================\n\nexport const CACHED_NOT_STARTED_ERROR = new RouterError(\n errorCodes.ROUTER_NOT_STARTED,\n);\n\nexport const CACHED_ROUTE_NOT_FOUND_ERROR = new RouterError(\n errorCodes.ROUTE_NOT_FOUND,\n);\n\nexport const CACHED_SAME_STATES_ERROR = new RouterError(errorCodes.SAME_STATES);\n\n// Pre-suppressed rejected promises — .catch() at module load prevents\n// unhandled rejection warnings. The facade skips additional .catch() calls\n// via the lastSyncRejected flag (zero derived-promise allocation).\nexport const CACHED_NOT_STARTED_REJECTION: Promise<State> = Promise.reject(\n CACHED_NOT_STARTED_ERROR,\n);\n\nexport const CACHED_ROUTE_NOT_FOUND_REJECTION: Promise<State> = Promise.reject(\n CACHED_ROUTE_NOT_FOUND_ERROR,\n);\n\nexport const CACHED_SAME_STATES_REJECTION: Promise<State> = Promise.reject(\n CACHED_SAME_STATES_ERROR,\n);\n\n// Suppress once at module load — prevents unhandled rejection events.\n// Subsequent .catch() / await by user code still works correctly:\n// a rejected promise stays rejected forever, each .catch() creates\n// its own derived promise and fires its handler.\nCACHED_NOT_STARTED_REJECTION.catch(() => {}); // NOSONAR -- intentional suppression, not a promise chain\nCACHED_ROUTE_NOT_FOUND_REJECTION.catch(() => {}); // NOSONAR\nCACHED_SAME_STATES_REJECTION.catch(() => {}); // NOSONAR\n","import { errorCodes, constants } from \"../../../constants\";\nimport { RouterError } from \"../../../RouterError\";\n\nimport type { NavigationOptions, State, TransitionMeta } from \"../../../types\";\nimport type { NavigationDependencies, NavigationContext } from \"../types\";\n\ntype MutableTransitionMeta = {\n -readonly [K in keyof TransitionMeta]: TransitionMeta[K];\n};\n\nfunction buildTransitionMeta(\n fromState: State | undefined,\n opts: NavigationOptions,\n toDeactivate: string[],\n toActivate: string[],\n intersection: string,\n): TransitionMeta {\n Object.freeze(toDeactivate);\n Object.freeze(toActivate);\n\n const segments = Object.freeze({\n deactivated: toDeactivate,\n activated: toActivate,\n intersection,\n });\n\n const meta: MutableTransitionMeta = {\n phase: \"activating\",\n reason: \"success\",\n segments,\n };\n\n if (fromState?.name !== undefined) {\n meta.from = fromState.name;\n }\n\n if (opts.reload !== undefined) {\n meta.reload = opts.reload;\n }\n\n if (opts.replace !== undefined) {\n meta.replace = opts.replace;\n }\n\n if (opts.redirected !== undefined) {\n meta.redirected = opts.redirected;\n }\n\n return Object.freeze(meta);\n}\n\nfunction stripSignal({\n signal: _,\n ...rest\n}: NavigationOptions): NavigationOptions {\n return rest;\n}\n\nexport function completeTransition(\n deps: NavigationDependencies,\n nav: NavigationContext,\n): State {\n const { toState, fromState, opts, toDeactivate, toActivate, intersection } =\n nav;\n\n if (\n toState.name !== constants.UNKNOWN_ROUTE &&\n !deps.hasRoute(toState.name)\n ) {\n const err = new RouterError(errorCodes.ROUTE_NOT_FOUND, {\n routeName: toState.name,\n });\n\n deps.sendTransitionFail(toState, fromState, err);\n\n throw err;\n }\n\n if (fromState) {\n for (const name of toDeactivate) {\n if (!toActivate.includes(name) && nav.canDeactivateFunctions.has(name)) {\n deps.clearCanDeactivate(name);\n }\n }\n }\n\n (toState as { transition: TransitionMeta }).transition = buildTransitionMeta(\n fromState,\n opts,\n toDeactivate,\n toActivate,\n intersection,\n );\n\n const finalState = Object.freeze(toState);\n\n deps.setState(finalState);\n\n const transitionOpts = opts.signal === undefined ? opts : stripSignal(opts);\n\n deps.sendTransitionDone(finalState, fromState, transitionOpts);\n\n return finalState;\n}\n","// packages/core/src/namespaces/NavigationNamespace/transition/errorHandling.ts\n\nimport { errorCodes } from \"../../../constants\";\nimport { RouterError } from \"../../../RouterError\";\n\nimport type { State } from \"../../../types\";\nimport type { NavigationDependencies } from \"../types\";\n\nexport function routeTransitionError(\n deps: NavigationDependencies,\n error: unknown,\n toState: State,\n fromState: State | undefined,\n): void {\n const routerError = error as RouterError;\n\n if (\n routerError.code === errorCodes.TRANSITION_CANCELLED ||\n routerError.code === errorCodes.ROUTE_NOT_FOUND\n ) {\n return;\n }\n\n deps.sendTransitionFail(toState, fromState, routerError);\n}\n\nexport function handleGuardError(\n error: unknown,\n errorCode: string,\n segment: string,\n): never {\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw new RouterError(errorCodes.TRANSITION_CANCELLED);\n }\n\n // A guard can also signal a quiet cancel by throwing\n // RouterError(TRANSITION_CANCELLED) directly — the same intent as a thrown\n // AbortError. Preserve it as-is instead of letting rethrowAsRouterError\n // overwrite the code with CANNOT_ACTIVATE / CANNOT_DEACTIVATE: that code\n // drives the downstream suppression (routeTransitionError early-returns,\n // fire-and-forget stays silent), so re-coding would surface the intended\n // quiet cancel as a reported transition error (#933).\n if (\n error instanceof RouterError &&\n error.code === errorCodes.TRANSITION_CANCELLED\n ) {\n throw error;\n }\n\n rethrowAsRouterError(error, errorCode, segment);\n}\n\n/**\n * Error metadata structure for transition errors.\n * Contains information extracted from caught exceptions.\n */\nexport interface SyncErrorMetadata {\n [key: string]: unknown;\n message?: string;\n stack?: string | undefined;\n cause?: unknown;\n segment?: string;\n}\n\n/**\n * Re-throws a caught error as a RouterError with the given error code.\n * If the error is already a RouterError, sets the code directly.\n * Otherwise wraps it with wrapSyncError metadata.\n */\nexport function rethrowAsRouterError(\n error: unknown,\n errorCode: string,\n segment: string,\n): never {\n if (error instanceof RouterError) {\n error.setCode(errorCode);\n\n throw error;\n }\n\n throw new RouterError(errorCode, wrapSyncError(error, segment));\n}\n\n// Own-enumerable keys that must never be copied from a thrown object onto the\n// RouterError metadata:\n// - `code` / `segment` / `path` are reserved — the RouterError constructor\n// throws a TypeError on them (#39).\n// - `then` would make the RouterError itself thenable, so a consumer that\n// awaits it (or passes it through Promise.resolve / returns it from an async\n// function) would have it assimilated as a Promise instead of treated as a\n// plain rejection reason (#947).\nconst reservedRouterErrorProps = new Set([\"code\", \"segment\", \"path\", \"then\"]);\n\n/**\n * Wraps a synchronously thrown value into structured error metadata.\n *\n * This helper extracts useful debugging information from various thrown values:\n * - Error instances: extracts message, stack, and cause (ES2022+)\n * - Plain objects: spreads properties into metadata\n * - Primitives (string, number, etc.): returns minimal metadata\n *\n * @param thrown - The value caught in a try-catch block\n * @param segment - Route segment name (for lifecycle hooks)\n * @returns Structured error metadata for RouterError\n */\nexport function wrapSyncError(\n thrown: unknown,\n segment: string,\n): SyncErrorMetadata {\n const base: SyncErrorMetadata = { segment };\n\n // Handle Error instances - extract all useful properties\n if (thrown instanceof Error) {\n return {\n ...base,\n message: thrown.message,\n stack: thrown.stack,\n // Error.cause requires ES2022+ - safely access if present\n ...(\"cause\" in thrown &&\n thrown.cause !== undefined && { cause: thrown.cause }),\n };\n }\n\n // Handle plain objects - spread properties into metadata, filtering reserved props\n if (thrown && typeof thrown === \"object\") {\n const filtered: Record<string, unknown> = {};\n\n for (const [key, value] of Object.entries(thrown)) {\n // Skip reserved / hazardous keys: #39 (constructor TypeError on code/\n // segment/path) and #947 (`then` would make the error thenable).\n if (!reservedRouterErrorProps.has(key)) {\n filtered[key] = value;\n }\n }\n\n return { ...base, ...filtered };\n }\n\n // Primitives (string, number, boolean, null, undefined, symbol, bigint)\n // Return base metadata only - the primitive value isn't useful as metadata\n return base;\n}\n","import { handleGuardError } from \"./errorHandling\";\nimport { errorCodes } from \"../../../constants\";\nimport { RouterError } from \"../../../RouterError\";\n\nimport type { GuardFn, State } from \"../../../types\";\n\nasync function resolveAsyncGuard(\n promise: Promise<boolean>,\n errorCode: string,\n segment: string,\n): Promise<void> {\n let result: boolean;\n\n try {\n result = await promise;\n } catch (error: unknown) {\n handleGuardError(error, errorCode, segment);\n\n return; // unreachable — handleGuardError returns never\n }\n\n if (!result) {\n throw new RouterError(errorCode, { segment });\n }\n}\n\nasync function resolveRemainingGuards( // NOSONAR -- params kept flat to avoid object allocation on hot path\n guards: Map<string, GuardFn>,\n segments: string[],\n errorCode: string,\n toState: State,\n fromState: State | undefined,\n signal: AbortSignal | undefined,\n isActive: () => boolean,\n startIndex: number,\n firstResult: Promise<boolean>,\n firstSegment: string,\n): Promise<void> {\n await resolveAsyncGuard(firstResult, errorCode, firstSegment);\n\n for (let i = startIndex; i < segments.length; i++) {\n // Stryker disable next-line BlockStatement: equivalent — emptying the body drops the async-tail cancellation throw, but supersession is independently enforced by the navigationId/isCurrentNav checks in NavigationNamespace, so the navigation still rejects with TRANSITION_CANCELLED (full suite green). The ConditionalExpression sibling stays live (its →true variant always-cancels and is killed by async-guard tests).\n if (!isActive()) {\n throw new RouterError(errorCodes.TRANSITION_CANCELLED);\n }\n\n const segment = segments[i];\n const guardFn = guards.get(segment);\n\n if (!guardFn) {\n continue;\n }\n\n // Stryker disable next-line BooleanLiteral: equivalent — guardResult is unconditionally reassigned by guardFn() on the next line, or the catch calls handleGuardError() (returns never), so the init value is never read.\n let guardResult: boolean | Promise<boolean> = false;\n\n try {\n guardResult = guardFn(toState, fromState, signal);\n } catch (error: unknown) {\n handleGuardError(error, errorCode, segment);\n }\n\n if (guardResult instanceof Promise) {\n await resolveAsyncGuard(guardResult, errorCode, segment);\n continue;\n }\n\n if (!guardResult) {\n throw new RouterError(errorCode, { segment });\n }\n }\n}\n\nasync function finishAsyncPipeline( // NOSONAR\n deactivateCompletion: Promise<void>,\n activateGuards: Map<string, GuardFn>,\n toActivate: string[],\n shouldActivate: boolean,\n toState: State,\n fromState: State | undefined,\n signal: AbortSignal,\n isActive: () => boolean,\n emitLeaveApprove: () => Promise<void> | undefined,\n): Promise<void> {\n await deactivateCompletion;\n\n if (!isActive()) {\n throw new RouterError(errorCodes.TRANSITION_CANCELLED);\n }\n\n const leaveResult = emitLeaveApprove();\n\n if (leaveResult !== undefined) {\n await leaveResult;\n\n // Stryker disable next-line BlockStatement: equivalent — defensive-redundancy: this post-leave cancellation re-check is already enforced upstream (navigationId / isCurrentNav supersession in NavigationNamespace), so emptying the throw is unobservable. Mirrors the disabled L42/L115/L228 cancel-checks.\n /* v8 ignore next 3 -- @preserve: V8 cannot track cancellation check through async leave continuation after Promise.allSettled */\n if (!isActive()) {\n throw new RouterError(errorCodes.TRANSITION_CANCELLED);\n }\n }\n\n if (shouldActivate) {\n const pending = runGuards(\n activateGuards,\n toActivate,\n errorCodes.CANNOT_ACTIVATE,\n toState,\n fromState,\n signal,\n isActive,\n );\n\n if (pending !== undefined) {\n await pending;\n }\n\n // Stryker disable next-line BlockStatement: equivalent — same async-tail cancellation throw as L42; dropping it is covered by the outer navigationId guard (full suite green). CE sibling stays live (→true killed by async-guard tests).\n if (!isActive()) {\n throw new RouterError(errorCodes.TRANSITION_CANCELLED);\n }\n }\n}\n\nexport function executeGuardPipeline( // NOSONAR\n deactivateGuards: Map<string, GuardFn>,\n activateGuards: Map<string, GuardFn>,\n toDeactivate: string[],\n toActivate: string[],\n shouldDeactivate: boolean,\n shouldActivate: boolean,\n toState: State,\n fromState: State | undefined,\n signal: AbortSignal,\n isActive: () => boolean,\n emitLeaveApprove: () => Promise<void> | undefined,\n): Promise<void> | undefined {\n if (shouldDeactivate) {\n const pending = runGuards(\n deactivateGuards,\n toDeactivate,\n errorCodes.CANNOT_DEACTIVATE,\n toState,\n fromState,\n signal,\n isActive,\n );\n\n if (pending !== undefined) {\n return finishAsyncPipeline(\n pending,\n activateGuards,\n toActivate,\n shouldActivate,\n toState,\n fromState,\n signal,\n isActive,\n emitLeaveApprove,\n );\n }\n }\n\n if (!isActive()) {\n throw new RouterError(errorCodes.TRANSITION_CANCELLED);\n }\n\n const leaveResult = emitLeaveApprove();\n\n if (leaveResult !== undefined) {\n return finishAfterAsyncLeave(\n leaveResult,\n /* v8 ignore next -- @preserve: false-branch unreachable — navigateToNotFound bypasses guards pipeline */\n shouldActivate ? activateGuards : undefined,\n toActivate,\n toState,\n fromState,\n signal,\n isActive,\n );\n }\n\n if (shouldActivate) {\n return runGuards(\n activateGuards,\n toActivate,\n errorCodes.CANNOT_ACTIVATE,\n toState,\n fromState,\n signal,\n isActive,\n );\n }\n\n return undefined;\n}\n\nasync function finishAfterAsyncLeave(\n leaveCompletion: Promise<void>,\n activateGuards: Map<string, GuardFn> | undefined,\n toActivate: string[],\n toState: State,\n fromState: State | undefined,\n signal: AbortSignal,\n isActive: () => boolean,\n): Promise<void> {\n await leaveCompletion;\n\n // Stryker disable next-line BlockStatement: equivalent — defensive-redundancy: same post-leave cancellation re-check as guardPhase's other `!isActive()` guards; supersession is enforced upstream (navigationId / isCurrentNav), so emptying the throw is unobservable.\n /* v8 ignore next 3 -- @preserve: unreachable after #663 — signal abort\n mid-leave rejects via settleLeavePromises, so `await leaveCompletion`\n throws directly instead of completing with a stale isActive() */\n if (!isActive()) {\n throw new RouterError(errorCodes.TRANSITION_CANCELLED);\n }\n\n /* v8 ignore next -- @preserve: false-branch unreachable — navigateToNotFound bypasses guards pipeline */\n if (activateGuards !== undefined) {\n const pending = runGuards(\n activateGuards,\n toActivate,\n errorCodes.CANNOT_ACTIVATE,\n toState,\n fromState,\n signal,\n isActive,\n );\n\n if (pending !== undefined) {\n await pending;\n }\n\n // Stryker disable next-line BlockStatement: equivalent — same async-tail cancellation throw as L42; dropping it is covered by the outer navigationId guard (full suite green). CE sibling stays live (→true killed by async-guard tests).\n if (!isActive()) {\n throw new RouterError(errorCodes.TRANSITION_CANCELLED);\n }\n }\n}\n\nfunction runGuards(\n guards: Map<string, GuardFn>,\n segments: string[],\n errorCode: string,\n toState: State,\n fromState: State | undefined,\n signal: AbortSignal | undefined,\n isActive: () => boolean,\n): Promise<void> | undefined {\n for (const [i, segment] of segments.entries()) {\n if (!isActive()) {\n throw new RouterError(errorCodes.TRANSITION_CANCELLED);\n }\n\n const guardFn = guards.get(segment);\n\n if (!guardFn) {\n continue;\n }\n\n // Stryker disable next-line BooleanLiteral: equivalent — guardResult is unconditionally reassigned by guardFn() on the next line, or the catch calls handleGuardError() (returns never), so the init value is never read.\n let guardResult: boolean | Promise<boolean> = false;\n\n try {\n guardResult = guardFn(toState, fromState, signal);\n } catch (error: unknown) {\n handleGuardError(error, errorCode, segment);\n }\n\n if (guardResult instanceof Promise) {\n return resolveRemainingGuards(\n guards,\n segments,\n errorCode,\n toState,\n fromState,\n signal,\n isActive,\n i + 1,\n guardResult,\n segment,\n );\n }\n\n if (!guardResult) {\n throw new RouterError(errorCode, { segment });\n }\n }\n\n return undefined;\n}\n","import {\n CACHED_NOT_STARTED_REJECTION,\n CACHED_ROUTE_NOT_FOUND_ERROR,\n CACHED_ROUTE_NOT_FOUND_REJECTION,\n CACHED_SAME_STATES_ERROR,\n CACHED_SAME_STATES_REJECTION,\n} from \"./constants\";\nimport { completeTransition } from \"./transition/completeTransition\";\nimport { routeTransitionError } from \"./transition/errorHandling\";\nimport { executeGuardPipeline } from \"./transition/guardPhase\";\nimport { EMPTY_PARAMS, errorCodes, constants } from \"../../constants\";\nimport { RouterError } from \"../../RouterError\";\nimport { getStateMetaParams, setStateMetaParams } from \"../../stateMetaStore\";\nimport { getTransitionPath, nameToIDs } from \"../../transitionPath\";\n\nimport type { NavigationContext, NavigationDependencies } from \"./types\";\nimport type { TransitionPath } from \"../../transitionPath\";\nimport type {\n GuardFn,\n NavigationOptions,\n Params,\n State,\n TransitionMeta,\n} from \"../../types\";\n\nconst FROZEN_ACTIVATED: string[] = Object.freeze([\n constants.UNKNOWN_ROUTE,\n]) as unknown as string[];\nconst FROZEN_REPLACE_OPTS: NavigationOptions = Object.freeze({ replace: true });\n\nfunction forceReplaceFromUnknown(\n opts: NavigationOptions,\n fromState: State | undefined,\n): NavigationOptions {\n return fromState?.name === constants.UNKNOWN_ROUTE && !opts.replace\n ? { ...opts, replace: true }\n : opts;\n}\n\nfunction isSameNavigation(\n fromState: State | undefined,\n opts: NavigationOptions,\n toState: State,\n): boolean {\n return (\n !!fromState &&\n !opts.reload &&\n !opts.force &&\n fromState.path === toState.path\n );\n}\n\n/**\n * Independent namespace for managing navigation.\n *\n * Handles navigate(), navigateToDefault(), navigateToNotFound(), and transition state.\n *\n * Performance: navigate() uses optimistic sync execution — guards run synchronously\n * until one returns a Promise, then switches to async. This eliminates Promise/AbortController\n * overhead for the common case (no guards or sync guards).\n */\nexport class NavigationNamespace {\n // Stryker disable next-line BooleanLiteral: equivalent — reset to false at the top of every navigate()/navigateToState()/navigateToDefault(), so the initializer value is never observed.\n lastSyncResolved = false;\n lastSyncRejected = false;\n #deps!: NavigationDependencies;\n #currentController: AbortController | null = null;\n #navigationId = 0;\n\n // =========================================================================\n // Dependency injection\n // =========================================================================\n\n setDependencies(deps: NavigationDependencies): void {\n this.#deps = deps;\n }\n\n // =========================================================================\n // Instance methods\n // =========================================================================\n\n navigate(\n name: string,\n params: Params,\n opts: NavigationOptions,\n ): Promise<State> {\n this.lastSyncResolved = false;\n const deps = this.#deps;\n\n // Fast-path sync rejections: cached error + cached Promise.reject\n // No allocations, no throw/catch overhead, facade skips .catch() suppression\n if (!deps.canNavigate()) {\n // Stryker disable next-line BooleanLiteral: equivalent — #721 optimization flag, not a correctness gate. Not flagging the cached (pre-suppressed) rejection routes the facade to the else-branch, which re-attaches a harmless .catch; no observable difference.\n this.lastSyncRejected = true;\n\n return CACHED_NOT_STARTED_REJECTION;\n }\n\n let toState: State | undefined;\n\n try {\n toState = deps.buildNavigateState(name, params);\n } catch (error) {\n /* v8 ignore next 3 -- @preserve: reachable only via validator-driven\n throws from buildNavigateState (validateStateBuilderArgs) — covered\n in @real-router/validation-plugin's suite, not in core. */\n // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors -- preserve original throw shape from user-provided buildNavigateState\n return Promise.reject(error);\n }\n\n if (!toState) {\n deps.emitTransitionError(\n undefined,\n deps.getState(),\n CACHED_ROUTE_NOT_FOUND_ERROR,\n );\n // Stryker disable next-line BooleanLiteral: equivalent — #721 optimization flag (see L95): the cached rejection is suppressed by the facade else-branch regardless of the flag.\n this.lastSyncRejected = true;\n\n return CACHED_ROUTE_NOT_FOUND_REJECTION;\n }\n\n return this.#executeNavigation(toState, opts);\n }\n\n /**\n * Navigate to a fully-built `State` directly, skipping `buildNavigateState`\n * (forwardState + buildPath + meta lookup). Used by URL plugins after they\n * have already produced a `State` from a browser-initiated event via\n * `api.matchPath(url)` — see issue #525.\n *\n * Semantics vs. `navigate(name, params, opts)`:\n * - `forwardState` is NOT re-applied. matchPath already runs it; reapplying\n * is redundant in the idempotent case and can race in the dynamic case.\n * - `buildPath` is NOT re-run. The caller's `state.path` is used as-is —\n * so `trailingSlash:\"preserve\"` matchedState paths flow through unchanged\n * (closes #525 Q2). `buildPath` interceptors do NOT run; the URL the\n * user navigated to is the source of truth for this code path.\n * - All other pipeline steps run unchanged: SAME_STATES check, FSM\n * transition, guards, `subscribeLeave`, `completeTransition`,\n * plugin lifecycle hooks.\n */\n navigateToState(state: State, opts: NavigationOptions): Promise<State> {\n this.lastSyncResolved = false;\n const deps = this.#deps;\n\n if (!deps.canNavigate()) {\n // Stryker disable next-line BooleanLiteral: equivalent — #721 optimization flag (see L95): the cached rejection is suppressed by the facade else-branch regardless of the flag.\n this.lastSyncRejected = true;\n\n return CACHED_NOT_STARTED_REJECTION;\n }\n\n // Reject states whose route no longer exists (e.g. the route tree was\n // mutated between matchPath and navigateToState). UNKNOWN_ROUTE is\n // structurally legal — it is the navigateToNotFound output shape.\n if (state.name !== constants.UNKNOWN_ROUTE && !deps.hasRoute(state.name)) {\n const err = new RouterError(errorCodes.ROUTE_NOT_FOUND, {\n routeName: state.name,\n });\n\n deps.emitTransitionError(undefined, deps.getState(), err);\n\n // This is a FRESH reject (carries `routeName`), not one of the\n // pre-suppressed CACHED_*_REJECTION singletons. `lastSyncRejected`\n // contractually means \"I returned a pre-suppressed cached rejection —\n // skip your .catch()\", so leaving it unset lets the facade attach its\n // own suppression. Setting it here leaked an unhandledRejection on\n // fire-and-forget calls (#721).\n return Promise.reject(err);\n }\n\n // States from `matchPath` are deeply frozen (`freezeStateInPlace`).\n // `completeTransition` mutates `toState.transition` and `context` is\n // intentionally extensible for plugin claim writes, so we hand the\n // pipeline a writable shell — same shape `makeState(skipFreeze=true)`\n // produces. `params` stays referentially shared (already frozen).\n // `transition` is omitted so completeTransition can assign it.\n const writableState = {\n name: state.name,\n params: state.params,\n path: state.path,\n context: { ...state.context },\n } as State;\n\n // Carry the route-meta binding (#1170). `matchPath` / `makeState` attach it\n // to the frozen source `state` via a WeakMap keyed by object reference, so\n // the fresh writable shell would otherwise be meta-less. Without it, two\n // consecutive popstate navigations make both `toState` AND `fromState`\n // meta-less, dropping `getTransitionPath` to FAST PATH 3 (full chains):\n // ancestor guards re-run and browser-back can block where `navigate()`\n // succeeds.\n const meta = getStateMetaParams(state);\n\n if (meta !== undefined) {\n setStateMetaParams(writableState, meta);\n }\n\n return this.#executeNavigation(writableState, opts);\n }\n\n navigateToDefault(opts: NavigationOptions): Promise<State> {\n // Reset the sync-resolution flag on entry, mirroring navigate() and\n // navigateToState(). start() leaves `lastSyncResolved = true`, and the\n // early reject paths below return before delegating to navigate(), so a\n // stale `true` would make the facade take the \"already resolved\" branch\n // and skip .catch() suppression — leaking an unhandledRejection on\n // fire-and-forget calls (#721).\n this.lastSyncResolved = false;\n const deps = this.#deps;\n const options = deps.getOptions();\n\n if (!options.defaultRoute) {\n return Promise.reject(\n new RouterError(errorCodes.ROUTE_NOT_FOUND, {\n routeName: \"defaultRoute not configured\",\n }),\n );\n }\n\n let route: string;\n let params: Params;\n\n try {\n ({ route, params } = deps.resolveDefault());\n } catch (error) {\n // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors -- preserve original throw shape from user-provided resolveDefault callback\n return Promise.reject(error);\n }\n\n if (!route) {\n return Promise.reject(\n new RouterError(errorCodes.ROUTE_NOT_FOUND, {\n routeName: \"defaultRoute resolved to empty\",\n }),\n );\n }\n\n return this.navigate(route, params, opts);\n }\n\n navigateToNotFound(path: string): State {\n // #1186 — liveness gate. This internal commit primitive has no FSM\n // transition of its own, so without this check a `dispose()` that lands\n // while a start-interceptor is parked (FSM already DISPOSED) would let the\n // resuming pipeline commit an UNKNOWN_ROUTE state on the disposed router and\n // `start()` resolve. Symmetric with `navigateToState`'s `canNavigate()` gate\n // (the matched-route branch is already protected). `!isActive()` also covers\n // a merely-stopped (IDLE) router: the only reachable path to that is a direct\n // `router.navigateToNotFound()` on a stopped instance (internal callers run\n // during STARTING, which is active), so the ROUTER_DISPOSED code is slightly\n // broad there — fail-closed is deliberate (committing on a stopped router is\n // out of contract), and the disposed race is the case that matters.\n if (!this.#deps.isActive()) {\n throw new RouterError(errorCodes.ROUTER_DISPOSED);\n }\n\n this.#abortPreviousNavigation();\n\n const fromState = this.#deps.getState();\n const deactivated: string[] = fromState\n ? nameToIDs(fromState.name).toReversed()\n : [];\n\n Object.freeze(deactivated);\n\n const segments: TransitionMeta[\"segments\"] = {\n deactivated,\n activated: FROZEN_ACTIVATED,\n intersection: \"\",\n };\n\n Object.freeze(segments);\n\n const transitionMeta: TransitionMeta = {\n phase: \"activating\",\n ...(fromState && { from: fromState.name }),\n reason: \"success\",\n replace: true,\n segments,\n };\n\n Object.freeze(transitionMeta);\n\n const state: State = {\n name: constants.UNKNOWN_ROUTE,\n params: EMPTY_PARAMS,\n path,\n transition: transitionMeta,\n context: {},\n };\n\n Object.freeze(state);\n\n this.#deps.setState(state);\n this.#deps.emitTransitionSuccess(state, fromState, FROZEN_REPLACE_OPTS);\n\n return state;\n }\n\n /**\n * Aborts and releases the in-flight navigation's `AbortController` (waking the\n * parked async pipeline via `onInternalAbort`). This is the\n * **effect** of the FSM `CANCEL` action (`handleCancel` → injected\n * `deps.abortCurrentController`), not something cancellation sources call\n * directly — so \"FSM `CANCEL` ⟹ controller aborted\" holds in one place (RFC\n * navigation-cancellation-unification §5). `reason` (e.g. an external\n * `opts.signal`'s reason, #943) becomes the controller's `signal.reason`;\n * defaults to `TRANSITION_CANCELLED`.\n */\n abortCurrentController(reason?: unknown): void {\n this.#currentController?.abort(\n reason ?? new RouterError(errorCodes.TRANSITION_CANCELLED),\n );\n this.#currentController = null;\n }\n\n #executeNavigation(toState: State, opts: NavigationOptions): Promise<State> {\n const deps = this.#deps;\n let fromState: State | undefined;\n let transitionStarted = false;\n let controller: AbortController | null = null;\n\n try {\n fromState = deps.getState();\n opts = forceReplaceFromUnknown(opts, fromState);\n\n if (isSameNavigation(fromState, opts, toState)) {\n deps.emitTransitionError(toState, fromState, CACHED_SAME_STATES_ERROR);\n // Stryker disable next-line BooleanLiteral: equivalent — #721 optimization flag (see L95): the cached rejection is suppressed by the facade else-branch regardless of the flag.\n this.lastSyncRejected = true;\n\n return CACHED_SAME_STATES_REJECTION;\n }\n\n this.#abortPreviousNavigation(opts.signal);\n\n // Stryker disable next-line UpdateOperator: equivalent — `#navigationId` is only ever compared by identity (`!== myId`) to detect supersession; uniqueness per navigation is all that matters, so `--` (decreasing ids) is indistinguishable from `++`.\n const myId = ++this.#navigationId;\n\n // #1169 commit-gate — liveness snapshot captured BEFORE the pre-commit\n // listener windows. A listener's `stop()`/`dispose()` runs `clearAll()`,\n // which empties the listener lists, so the marker must be read now, not at\n // the commit site (that self-destruct was the QB/QE hole, RFC §5-bis).\n // `suspendable` is true only when a synchronous supersede is reachable — an\n // external `opts.signal`, `subscribeLeave` listeners, or a pre-commit\n // plugin listener (`onTransitionStart` / `onTransitionLeaveApprove`); the\n // pure synchronous navigate (none of these) is uncancellable and skips the\n // gate, keeping the #307 hot path perf-neutral.\n const suspendable =\n opts.signal !== undefined ||\n deps.hasLeaveListeners() ||\n deps.hasPreCommitListeners();\n\n deps.startTransition(toState, fromState);\n transitionStarted = true;\n\n // Post-`startTransition` supersession is now caught at the commit-gate\n // below (before `completeTransition`'s setState): a `stop()`/`dispose()`/\n // external-abort from the TRANSITION_START listener leaves the FSM in\n // IDLE/DISPOSED, which `!deps.isActive()` detects. (Async supersession is\n // additionally caught in `#finishAsyncNavigation` / the guard pipeline's\n // `isCurrentNav`; a reentrant navigate() is banned — REENTRANT_NAVIGATION.)\n\n const [canDeactivateFunctions, canActivateFunctions] =\n deps.getLifecycleFunctions();\n const isUnknownRoute = toState.name === constants.UNKNOWN_ROUTE;\n\n const transitionPath = getTransitionPath(toState, fromState);\n const { toDeactivate, toActivate, intersection } = transitionPath;\n\n const shouldDeactivate =\n fromState && !opts.forceDeactivate && toDeactivate.length > 0;\n const shouldActivate = !isUnknownRoute && toActivate.length > 0;\n const hasGuards =\n canDeactivateFunctions.size > 0 || canActivateFunctions.size > 0;\n\n const confirmedToState = toState;\n\n if (!hasGuards) {\n const asyncLeave = this.#handleNoGuardsLeave(\n confirmedToState,\n fromState,\n myId,\n opts,\n transitionPath,\n canDeactivateFunctions,\n );\n\n if (asyncLeave !== undefined) {\n return asyncLeave;\n }\n }\n\n // Stryker disable next-line ConditionalExpression: equivalent — running the guard pipeline on the no-guards path does not double-emit LEAVE_APPROVE (full suite green with `if (true)`); the BlockStatement mutant stays live (killed by guarded-route tests).\n // eslint-disable-next-line unicorn/prefer-else-if -- two exhaustive `if`s read clearer here than an else-if; merging cascades into no-negated-condition / no-unnecessary-condition in this hot guard-setup branch\n if (hasGuards) {\n controller = new AbortController();\n this.#currentController = controller;\n const isCurrentNav = () =>\n this.#navigationId === myId && deps.isActive();\n\n const signal = controller.signal;\n\n const emitLeaveApproveCallback = (): Promise<void> | undefined => {\n deps.sendLeaveApprove(confirmedToState, fromState);\n\n if (deps.hasLeaveListeners()) {\n return deps.awaitLeaveListeners(\n confirmedToState,\n fromState,\n signal,\n );\n }\n\n return undefined;\n };\n\n const guardCompletion = executeGuardPipeline(\n canDeactivateFunctions,\n canActivateFunctions,\n toDeactivate,\n toActivate,\n !!shouldDeactivate,\n shouldActivate,\n toState,\n fromState,\n signal,\n isCurrentNav,\n emitLeaveApproveCallback,\n );\n\n if (guardCompletion !== undefined) {\n return this.#finishAsyncNavigation(\n guardCompletion,\n {\n toState,\n fromState,\n opts,\n toDeactivate,\n toActivate,\n intersection,\n canDeactivateFunctions,\n },\n controller,\n myId,\n );\n }\n\n if (!isCurrentNav()) {\n throw new RouterError(errorCodes.TRANSITION_CANCELLED);\n }\n\n this.#cleanupController(controller, false);\n }\n\n // #1169 commit-gate — refuse to commit a navigation cancelled or\n // terminated during a listener window, BEFORE `completeTransition`'s\n // setState. The FSM table (D-full) already prevents the forceState\n // resurrection; this prevents the state commit that precedes the emit.\n // Gated on `suspendable` so the pure sync hot path pays nothing. A\n // `stop()`/`dispose()` from the listener lands the FSM in IDLE/DISPOSED\n // (caught by `!isActive()`); an external `opts.signal` abort is caught\n // directly. No `#navigationId` check: a reentrant navigate() (the only\n // thing that could bump it synchronously) is banned (REENTRANT_NAVIGATION,\n // §4), so on this sync path `#navigationId === myId` always holds — async\n // supersede is caught in `#finishAsyncNavigation`'s `isCurrentNav`.\n if (suspendable && (!deps.isActive() || opts.signal?.aborted === true)) {\n throw new RouterError(errorCodes.TRANSITION_CANCELLED);\n }\n\n const finalState = completeTransition(deps, {\n toState,\n fromState,\n opts,\n toDeactivate,\n toActivate,\n intersection,\n canDeactivateFunctions,\n });\n\n // Mark sync-resolution only AFTER completeTransition returns. It emits\n // TRANSITION_SUCCESS; listener throws are all isolated via onListenerError\n // (the emitter re-throws nothing now that re-entrant emits are coalesced,\n // #1033, and reentrant navigate/CRUD are banned), so the emit itself does\n // not throw. Should completeTransition throw for any other reason, setting\n // the flag optimistically BEFORE it would leave the flag stale-true, so the\n // facade would read lastSyncResolved and skip its suppressing `.catch()` —\n // the rejection would then leak as a Node unhandledRejection. Post-emit\n // placement keeps the flag false on a throw, so control falls to catch and\n // the facade attaches its `.catch`.\n // Stryker disable next-line BooleanLiteral: equivalent — flipping to false routes the facade to the else-branch, which attaches a harmless .catch to an already-resolved promise; there is no rejection to suppress.\n this.lastSyncResolved = true;\n\n return Promise.resolve(finalState);\n } catch (error) {\n this.#handleNavigateError(\n error,\n controller,\n transitionStarted,\n toState,\n fromState,\n );\n\n // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors -- preserve original throw shape from guards or transition pipeline\n return Promise.reject(error);\n }\n }\n\n async #finishAsyncNavigation(\n guardCompletion: Promise<void>,\n nav: NavigationContext,\n controller: AbortController,\n myId: number,\n ): Promise<State> {\n const deps = this.#deps;\n const isActive = () =>\n this.#navigationId === myId &&\n !controller.signal.aborted &&\n deps.isActive();\n\n const externalSignal = nav.opts.signal;\n let onExternalAbort: (() => void) | undefined;\n let onInternalAbort: (() => void) | undefined;\n let succeeded = false;\n let failureReason: unknown;\n\n // #1018: race the guard completion against the controller's abort so a\n // non-cooperative guard whose Promise never settles (and ignores `signal`)\n // cannot wedge navigate() forever. `abortRace` RESOLVES on abort, so the\n // post-race `isActive()` check below throws TRANSITION_CANCELLED — the same\n // path that already handles a guard which swallows the abort and resolves\n // `true`. `stop()`/`dispose()`/supersede all abort the controller. Mirrors\n // the leave-path protection `settleLeavePromises` (#663/#673).\n const abortRace = new Promise<void>((resolve) => {\n if (controller.signal.aborted) {\n resolve();\n\n return;\n }\n\n onInternalAbort = () => {\n resolve();\n };\n\n controller.signal.addEventListener(\"abort\", onInternalAbort, {\n once: true,\n });\n });\n\n // Consume `guardCompletion` when the abort wins the race: a slow or\n // never-settling guard that settles later then has no awaiter, which would\n // surface as an unhandled rejection without this catch.\n guardCompletion.catch(() => {\n /* settlement consumed — the race already decided the navigation */\n });\n\n try {\n if (externalSignal) {\n if (externalSignal.aborted) {\n throw new RouterError(errorCodes.TRANSITION_CANCELLED, {\n reason: externalSignal.reason,\n });\n }\n\n // Bridge an external `{ signal }` abort onto the internal controller.\n // It is NOT scoped to `controller.signal` (the old `{ signal }` option)\n // because success no longer aborts the controller (#722) — the listener\n // is detached explicitly in `finally` instead.\n onExternalAbort = () => {\n // (#1030): route the external abort through the FSM. The\n // `CANCEL` action aborts the internal controller (waking THIS pipeline)\n // with the external `reason` (#943 — surfaces via the leave signal) AND\n // returns the FSM to READY, atomically. No direct `controller.abort`\n // here — \"FSM CANCEL ⟹ controller aborted\" lives in one place\n // (handleCancel). onExternalAbort only fires while the navigation is in\n // flight (the listener is removed in `finally` once it settles), so the\n // FSM is always cancellable here.\n deps.cancelNavigation(externalSignal.reason);\n };\n // Stryker disable next-line ObjectLiteral: equivalent — `{ once: true }` is redundant: the per-navigation signal aborts at most once and is discarded unaborted on success, and the `finally` block explicitly removeEventListener's it.\n externalSignal.addEventListener(\"abort\", onExternalAbort, {\n // Stryker disable next-line BooleanLiteral: equivalent — `once` redundant (see ObjectLiteral above); the listener is explicitly removed in `finally`.\n once: true,\n });\n }\n\n await Promise.race([guardCompletion, abortRace]);\n\n if (!isActive()) {\n throw new RouterError(errorCodes.TRANSITION_CANCELLED);\n }\n\n const state = completeTransition(deps, nav);\n\n succeeded = true;\n\n return state;\n } catch (error) {\n failureReason = error;\n\n routeTransitionError(deps, error, nav.toState, nav.fromState);\n\n throw error;\n // NB: the `} finally {}` BlockStatement mutant SURVIVES but is EQUIVALENT —\n // emptying the finally only skips #cleanupController, which is unobservable\n // (defense-in-depth: on a CANCEL the FSM CANCEL action already aborted+nulled\n // the controller via abortCurrentController, RFC §5; the success-path\n // ref-release is proven unobservable — see #cleanupController's disable). It\n // cannot be inline-`Stryker disable`d: the catch `}` and finally `{` share one\n // line, so there is no comment position that targets the finally body. Left documented.\n } finally {\n // Stryker disable next-line ConditionalExpression,BlockStatement: equivalent — listener cleanup is redundant: the per-navigation signal is discarded on completion, so skipping the removeEventListener leaks nothing observable.\n if (onExternalAbort) {\n // Stryker disable next-line StringLiteral: equivalent — cleanup event name is redundant (listener is `{ once: true }` and the signal is discarded), so a wrong name removes nothing observable.\n externalSignal?.removeEventListener(\"abort\", onExternalAbort);\n }\n\n // Detach the abort-race listener before #cleanupController aborts the\n // controller below, so the cleanup abort cannot re-fire it. `undefined`\n // only when the controller was already aborted at setup (the early-resolve\n // branch above registered no listener).\n if (onInternalAbort) {\n controller.signal.removeEventListener(\"abort\", onInternalAbort);\n }\n\n // Success drops the controller without aborting (the subscribeLeave signal\n // must stay unaborted); cancel/error aborts it with the originating reason\n // so captured signals expose the real cause via `signal.reason` (#943).\n this.#cleanupController(controller, !succeeded, failureReason);\n }\n }\n\n #handleNavigateError(\n error: unknown,\n controller: AbortController | null,\n transitionStarted: boolean,\n toState: State | undefined,\n fromState: State | undefined,\n ): void {\n if (controller) {\n this.#cleanupController(controller, true, error);\n }\n\n if (transitionStarted && toState) {\n routeTransitionError(this.#deps, error, toState, fromState);\n }\n }\n\n #handleNoGuardsLeave(\n toState: State,\n fromState: State | undefined,\n myId: number,\n opts: NavigationOptions,\n transitionPath: TransitionPath,\n canDeactivateFunctions: Map<string, GuardFn>,\n ): Promise<State> | undefined {\n const deps = this.#deps;\n\n deps.sendLeaveApprove(toState, fromState);\n\n if (deps.hasLeaveListeners()) {\n const controller = new AbortController();\n\n // Track as the current navigation BEFORE listeners run so a reentrant\n // navigate() / stop() / dispose() from a sync listener aborts THIS leave\n // signal — parity with the guard path (#722). On success the controller is\n // released without aborting (see #cleanupController).\n this.#currentController = controller;\n\n let leaveResult: Promise<void> | undefined;\n\n try {\n leaveResult = deps.awaitLeaveListeners(\n toState,\n fromState,\n controller.signal,\n );\n } catch (error) {\n // A sync listener threw — the navigation fails; abort the leave signal\n // with the thrown value so a listener that captured the signal sees the\n // real cause via `signal.reason`, not a generic AbortError (#943).\n this.#cleanupController(controller, true, error);\n\n throw error;\n }\n\n if (leaveResult !== undefined) {\n return this.#finishAsyncNavigation(\n leaveResult,\n {\n toState,\n fromState,\n opts,\n toDeactivate: transitionPath.toDeactivate,\n toActivate: transitionPath.toActivate,\n intersection: transitionPath.intersection,\n canDeactivateFunctions,\n },\n controller,\n myId,\n );\n }\n\n // Sync listeners settled. A synchronous reentrant navigate() can no longer\n // supersede here (banned, RFC §4), so the leave always succeeds: release the\n // controller WITHOUT aborting (the subscribeLeave signal must stay live).\n this.#cleanupController(controller, false);\n\n return undefined;\n }\n\n // No leave listeners: nothing synchronous could have superseded this\n // navigation during the LEAVE_APPROVE emit (reentrant navigate is banned).\n return undefined;\n }\n\n /**\n * Release a navigation's AbortController. The same `controller.signal` is\n * handed to `subscribeLeave` listeners, so it must abort **only** when the\n * navigation is cancelled or errors — never on success (#722). On the success\n * path pass `cancelled = false`: the reference is dropped without aborting, so\n * a listener that captured the signal still sees `aborted === false`.\n *\n * On the failure/cancellation path (`cancelled = true`) pass the originating\n * `reason` so `signal.reason` carries router/error context (a `RouterError`,\n * or the value a sync leave listener threw) — consistent with the cancellation\n * abort `RouterError(TRANSITION_CANCELLED)`, not a generic `AbortError` (#943).\n * `abort()` is idempotent: a controller already aborted by a superseding\n * navigation keeps its first (also-meaningful) reason.\n */\n #cleanupController(\n controller: AbortController,\n cancelled: boolean,\n reason?: unknown,\n ): void {\n if (cancelled) {\n controller.abort(reason);\n }\n\n // Stryker disable next-line ConditionalExpression,EqualityOperator,BlockStatement: equivalent — controller identity-guard; cleanup correctness is enforced by #abortPreviousNavigation + the navigationId/isCurrentNav checks. Full suite stays green with `=== → !==` (nulls the wrong controller) and with the body removed (ref never nulled), so no mutant here is observable.\n if (this.#currentController === controller) {\n this.#currentController = null;\n }\n }\n\n #abortPreviousNavigation(externalSignal?: AbortSignal): void {\n if (this.#deps.isTransitioning()) {\n this.#deps.logger.warn(\n \"router.navigate\",\n \"Concurrent navigation detected on shared router instance. \" +\n \"For SSR, use cloneRouter() to create isolated instance per request.\",\n );\n // The FSM CANCEL action aborts the previous controller — no\n // direct controller.abort here (RFC navigation-cancellation-unification §5).\n this.#deps.cancelNavigation();\n }\n\n if (externalSignal?.aborted) {\n throw new RouterError(errorCodes.TRANSITION_CANCELLED, {\n reason: externalSignal.reason,\n });\n }\n }\n}\n","// packages/core/src/namespaces/RouterLifecycleNamespace/RouterLifecycleNamespace.ts\n\nimport { errorCodes } from \"../../constants\";\nimport { RouterError } from \"../../RouterError\";\n\nimport type { RouterLifecycleDependencies } from \"./types\";\nimport type { NavigationOptions, State } from \"../../types\";\n\nconst REPLACE_OPTS: NavigationOptions = Object.freeze({ replace: true });\n\n/**\n * Independent namespace for managing router lifecycle.\n *\n * Handles start() and stop(). Lifecycle state (isActive, isStarted) is managed\n * by RouterFSM in the facade (Router.ts).\n */\nexport class RouterLifecycleNamespace {\n #deps!: RouterLifecycleDependencies;\n\n // =========================================================================\n // Dependency injection\n // =========================================================================\n\n /**\n * Sets dependencies for lifecycle operations.\n * Must be called before using lifecycle methods.\n */\n setDependencies(deps: RouterLifecycleDependencies): void {\n this.#deps = deps;\n }\n\n // =========================================================================\n // Instance methods\n // =========================================================================\n\n /**\n * Starts the router with the given path.\n *\n * Guards (concurrent start, already started) are handled by the facade via\n * RouterFSM state checks before this method is called.\n */\n async start(startPath: string): Promise<State> {\n const deps = this.#deps;\n\n // #1185: this method is the start-interceptor target — it runs AFTER the\n // whole interceptor chain. A stop() during that window sent STOP\n // (STARTING → IDLE via the FSM table), so if the router is back at IDLE the\n // start was cancelled mid-window; reject instead of committing a state on a\n // stopped router (mirrors the guard phase, which cancels from\n // TRANSITION_STARTED). `isIdle()` is deliberate — a dispose() mid-window\n // leaves the FSM DISPOSED, which the navigateToState / navigateToNotFound\n // liveness gate rejects as ROUTER_DISPOSED (#1186), not conflated with a\n // cancel.\n if (deps.isIdle()) {\n throw new RouterError(errorCodes.TRANSITION_CANCELLED);\n }\n\n const options = deps.getOptions();\n\n // Invariant guard (#939): core is platform-agnostic, so the caller must\n // provide a string path. Without a browser-plugin start interceptor to\n // inject a location, a non-string `startPath` (e.g. `start(undefined)`)\n // would otherwise reach matchPath() and throw a cryptic, code-less\n // `TypeError: …codePointAt` deep inside path-matcher. This guard runs AFTER\n // the interceptor chain (browser-plugin substitutes the location upstream),\n // so it only fires when nothing supplied a path — turning the cryptic crash\n // into an actionable error. Symmetric with the subscribe / navigateToNotFound\n // type guards; the validator deliberately permits `undefined` at the facade\n // for exactly the browser-plugin-override case.\n if (typeof startPath !== \"string\") {\n throw new TypeError(\n `[router.start] path must be a string, got ${typeof startPath}`,\n );\n }\n\n const matchedState = deps.matchPath(startPath);\n\n if (!matchedState && !options.allowNotFound) {\n const err = new RouterError(errorCodes.ROUTE_NOT_FOUND, {\n path: startPath,\n });\n\n deps.emitTransitionError(undefined, undefined, err);\n\n throw err;\n }\n\n deps.completeStart();\n\n if (matchedState) {\n // navigateToState commits matchedState verbatim — same primitive URL\n // plugins use on popstate / navigate-event (#525). Keeps trailing-slash\n // and any other source-URL flavor that matchPath produced; skips the\n // redundant forwardState+buildPath round-trip in buildNavigateState.\n return deps.navigateToState(matchedState, REPLACE_OPTS);\n }\n\n return deps.navigateToNotFound(startPath);\n }\n\n /**\n * Stops the router and resets state.\n *\n * Called for READY / TRANSITION_STARTED and, since #1185, STARTING (a stop()\n * that cancels a parked start — state is still `undefined` there, so\n * `clearState()` is a no-op that keeps the reset symmetric). The facade\n * handles IDLE / DISPOSED.\n */\n stop(): void {\n this.#deps.clearState();\n }\n}\n","import type { FSMConfig, TransitionInfo, TransitionListener } from \"./types\";\n\n/**\n * Shared guard for the engine-wide invariant \"the state is declared in\n * `config.transitions`\". Applied at every state-entry-point (constructor\n * `initial` and `on`'s `from`) so an undeclared state fails loud with\n * an explicit error instead of bricking the FSM or dead-registering an action\n * (#885). Returns the state's transition map for the caller to reuse.\n */\nfunction requireDeclared<TStates extends string, TEvents extends string>(\n transitions: Record<TStates, Partial<Record<TEvents, TStates>>>,\n state: TStates,\n where: string,\n): Partial<Record<TEvents, TStates>> {\n const stateTransitions = transitions[state];\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- runtime guard for JS / cast / string-typed callers passing a state outside TStates\n if (stateTransitions === undefined) {\n throw new Error(\n `[FSM.${where}] state \"${state}\" is not declared in config.transitions`,\n );\n }\n\n return stateTransitions;\n}\n\n/**\n * Synchronous finite state machine engine.\n *\n * Reentrancy: `send()` inside `onTransition` listener is allowed but unbounded —\n * callers are responsible for preventing infinite loops.\n *\n * Exceptions: if a listener throws, the exception propagates to the caller.\n * State is already updated before listeners fire, so `getState()` reflects the\n * new state even if the exception escapes `send()`.\n */\nexport class FSM<\n TStates extends string,\n TEvents extends string,\n TContext,\n TPayloadMap extends Partial<Record<TEvents, unknown>> = Record<never, never>,\n> {\n #state: TStates;\n #currentTransitions: Partial<Record<TEvents, TStates>>;\n #listenerCount = 0;\n #actions: Map<TStates, Map<TEvents, (payload: unknown) => void>> | null =\n null;\n readonly #context: TContext;\n readonly #transitions: Record<TStates, Partial<Record<TEvents, TStates>>>;\n readonly #listeners: (TransitionListener<\n TStates,\n TEvents,\n TPayloadMap\n > | null)[] = [];\n\n constructor(config: FSMConfig<TStates, TEvents, TContext>) {\n this.#state = config.initial;\n this.#context = config.context;\n this.#transitions = config.transitions;\n this.#currentTransitions = requireDeclared(\n config.transitions,\n config.initial,\n \"constructor\",\n );\n\n // #1159: validate table closure — every declared transition target must\n // itself be a declared state. `send()` applies table values\n // (`this.#transitions[nextState]`) without re-checking, so a dangling\n // target would silently enter an undeclared state (violating Validity #1)\n // and brick `canSend()` (violating No-bricking #10). One cold-path\n // O(states×events) pass at construction fails loud instead — the fourth\n // state-entry-point, mirroring the `initial` / `on` guards. Explicit\n // `undefined` values are the declared \"no transition\" no-op (send() returns\n // the current state) and are skipped. Post-construction mutation of the\n // shared table stays a documented GIGO boundary (Edge #5).\n for (const state of Object.keys(config.transitions)) {\n const stateTransitions = config.transitions[state as TStates];\n\n for (const event of Object.keys(stateTransitions)) {\n const target = stateTransitions[event as TEvents];\n\n if (target !== undefined) {\n requireDeclared(config.transitions, target, \"constructor\");\n }\n }\n }\n }\n\n send<E extends TEvents>(\n event: E,\n ...args: E extends keyof TPayloadMap ? [TPayloadMap[E]] : [undefined?]\n ): TStates {\n const nextState = this.#currentTransitions[event];\n\n if (nextState === undefined) {\n return this.#state;\n }\n\n const from = this.#state;\n\n this.#state = nextState;\n this.#currentTransitions = this.#transitions[nextState];\n\n const payload = args[0] as TPayloadMap[TEvents] | undefined;\n\n if (this.#actions !== null) {\n const action = this.#actions.get(from)?.get(event);\n\n if (action !== undefined) {\n action(payload);\n }\n }\n\n // Stryker disable next-line ConditionalExpression: equivalent — count>0 is a perf gate to skip the dispatch loop; `true` always enters it, but with no live listener `#listeners` holds only null slots and the loop body guards `listener !== null`, so dispatch is a no-op either way. The EqualityOperator `<=0` sibling on this line stays killed (not silenced here).\n if (this.#listenerCount > 0) {\n // `info` is structurally a valid TransitionInfo, but the distributive\n // union can't be matched to one variant while `event`/`payload` are\n // generic here — erase through `unknown` (TS2352), same spirit as the\n // `args[0]` cast above.\n const info = {\n from,\n to: nextState,\n event,\n payload,\n } as unknown as TransitionInfo<TStates, TEvents, TPayloadMap>;\n\n for (const listener of this.#listeners) {\n if (listener !== null) {\n listener(info);\n }\n }\n }\n\n return this.#state;\n }\n\n canSend(event: TEvents): boolean {\n return this.#currentTransitions[event] !== undefined;\n }\n\n getState(): TStates {\n return this.#state;\n }\n\n getContext(): TContext {\n return this.#context;\n }\n\n on<E extends TEvents>(\n from: TStates,\n event: E,\n action: E extends keyof TPayloadMap\n ? (payload: TPayloadMap[E]) => void\n : () => void,\n ): () => void {\n requireDeclared(this.#transitions, from, \"on\");\n\n this.#actions ??= new Map();\n\n let stateActions = this.#actions.get(from);\n\n if (!stateActions) {\n stateActions = new Map();\n this.#actions.set(from, stateActions);\n }\n\n const capturedAction = action as (payload: unknown) => void;\n\n stateActions.set(event, capturedAction);\n\n return () => {\n // Stryker disable next-line OptionalChaining: equivalent — `#actions` is assigned (`??= new Map()` above) before this unsubscribe closure is created and returned, so it is never null when the closure runs; `?.` can't short-circuit and behaves identically to `.get`.\n const stateMap = this.#actions?.get(from);\n\n if (stateMap?.get(event) === capturedAction) {\n stateMap.delete(event);\n }\n };\n }\n\n onTransition(\n listener: (info: TransitionInfo<TStates, TEvents, TPayloadMap>) => void,\n ): () => void {\n const nullIndex = this.#listeners.indexOf(null);\n let index: number;\n\n if (nullIndex === -1) {\n index = this.#listeners.length;\n this.#listeners.push(listener);\n } else {\n this.#listeners[nullIndex] = listener;\n index = nullIndex;\n }\n\n this.#listenerCount++;\n let subscribed = true;\n\n return () => {\n if (!subscribed) {\n return;\n }\n\n subscribed = false;\n this.#listeners[index] = null;\n // Stryker disable next-line UpdateOperator: equivalent — #listenerCount feeds only the `> 0` loop gate; `++` inflates it but the loop then iterates already-nulled slots (no-op), and no public reader exposes the count, so the miscount is unobservable.\n this.#listenerCount--;\n };\n }\n}\n","// packages/core/src/routerFSM.ts\n\nimport { FSM } from \"./foundation/fsm\";\n\nimport type { FSMConfig } from \"./foundation/fsm\";\nimport type { NavigationOptions, State } from \"./types\";\n\n/**\n * Router FSM states.\n *\n * - IDLE: Router not started or stopped\n * - STARTING: Router is initializing\n * - READY: Router is ready for navigation\n * - TRANSITION_STARTED: Navigation in progress (before deactivation guards)\n * - LEAVE_APPROVED: Deactivation guards passed, activation guards pending\n * - DISPOSED: Router has been disposed (R2+)\n */\nexport const routerStates = {\n IDLE: \"IDLE\",\n STARTING: \"STARTING\",\n READY: \"READY\",\n TRANSITION_STARTED: \"TRANSITION_STARTED\",\n LEAVE_APPROVED: \"LEAVE_APPROVED\",\n DISPOSED: \"DISPOSED\",\n} as const;\n\nexport type RouterState = (typeof routerStates)[keyof typeof routerStates];\n\n/**\n * Router FSM events.\n *\n * - START: Begin router initialization\n * - STARTED: Router initialization complete\n * - NAVIGATE: Begin navigation\n * - COMPLETE: Navigation completed successfully\n * - FAIL: Navigation or initialization failed\n * - CANCEL: Navigation cancelled\n * - STOP: Stop router\n * - DISPOSE: Dispose router (R2+)\n */\nexport const routerEvents = {\n START: \"START\",\n STARTED: \"STARTED\",\n NAVIGATE: \"NAVIGATE\",\n LEAVE_APPROVE: \"LEAVE_APPROVE\",\n COMPLETE: \"COMPLETE\",\n FAIL: \"FAIL\",\n CANCEL: \"CANCEL\",\n STOP: \"STOP\",\n DISPOSE: \"DISPOSE\",\n} as const;\n\nexport type RouterEvent = (typeof routerEvents)[keyof typeof routerEvents];\n\n/**\n * Per-event payloads for the router FSM (#1169 commit-gate). The three hot\n * navigation transitions carry their transition states so the FSM action\n * dispatched by `send()` emits the matching transition event — i.e. events are\n * literal consequences of FSM transitions (no `forceState` + manual emit). See\n * `EventBusNamespace.#setupFSMActions`.\n */\nexport interface RouterPayloads {\n NAVIGATE: { toState: State; fromState?: State | undefined };\n LEAVE_APPROVE: { toState: State; fromState?: State | undefined };\n COMPLETE: {\n toState: State;\n fromState?: State | undefined;\n opts?: NavigationOptions | undefined;\n };\n}\n\n/**\n * Router FSM configuration.\n *\n * Transitions:\n * - IDLE → STARTING (START), DISPOSED (DISPOSE)\n * - STARTING → READY (STARTED), IDLE (FAIL, STOP), DISPOSED (DISPOSE)\n * - READY → TRANSITION_STARTED (NAVIGATE), READY (FAIL, self-loop for early validation errors), IDLE (STOP), DISPOSED (DISPOSE)\n * - TRANSITION_STARTED → LEAVE_APPROVED (LEAVE_APPROVE), TRANSITION_STARTED (NAVIGATE, self-loop), READY (CANCEL, FAIL), DISPOSED (DISPOSE)\n * - LEAVE_APPROVED → READY (COMPLETE, CANCEL, FAIL), TRANSITION_STARTED (NAVIGATE), DISPOSED (DISPOSE)\n * - DISPOSED → (no transitions)\n *\n * DISPOSE is wired from every non-DISPOSED state so `router.dispose()` always\n * settles the FSM at DISPOSED. The facade orchestrates cleanup through IDLE\n * for healthy flows; the direct transitions guarantee the FSM is not left\n * stuck if cleanup is skipped (e.g. dispose mid-STARTING when the start\n * pipeline threw before STARTED/FAIL).\n */\nconst routerFSMConfig: FSMConfig<RouterState, RouterEvent, null> = {\n initial: routerStates.IDLE,\n context: null,\n transitions: {\n [routerStates.IDLE]: {\n [routerEvents.START]: routerStates.STARTING,\n [routerEvents.DISPOSE]: routerStates.DISPOSED,\n },\n [routerStates.STARTING]: {\n [routerEvents.STARTED]: routerStates.READY,\n [routerEvents.FAIL]: routerStates.IDLE,\n [routerEvents.STOP]: routerStates.IDLE,\n [routerEvents.DISPOSE]: routerStates.DISPOSED,\n },\n [routerStates.READY]: {\n [routerEvents.NAVIGATE]: routerStates.TRANSITION_STARTED,\n [routerEvents.FAIL]: routerStates.READY,\n [routerEvents.STOP]: routerStates.IDLE,\n [routerEvents.DISPOSE]: routerStates.DISPOSED,\n },\n [routerStates.TRANSITION_STARTED]: {\n [routerEvents.NAVIGATE]: routerStates.TRANSITION_STARTED,\n [routerEvents.LEAVE_APPROVE]: routerStates.LEAVE_APPROVED,\n [routerEvents.CANCEL]: routerStates.READY,\n [routerEvents.FAIL]: routerStates.READY,\n [routerEvents.DISPOSE]: routerStates.DISPOSED,\n },\n [routerStates.LEAVE_APPROVED]: {\n [routerEvents.NAVIGATE]: routerStates.TRANSITION_STARTED,\n [routerEvents.COMPLETE]: routerStates.READY,\n [routerEvents.CANCEL]: routerStates.READY,\n [routerEvents.FAIL]: routerStates.READY,\n [routerEvents.DISPOSE]: routerStates.DISPOSED,\n },\n [routerStates.DISPOSED]: {},\n },\n};\n\n/**\n * Factory function to create a router FSM instance.\n *\n * @returns FSM instance with initial state \"IDLE\"\n */\nexport function createRouterFSM(): FSM<\n RouterState,\n RouterEvent,\n null,\n RouterPayloads\n> {\n return new FSM<RouterState, RouterEvent, null, RouterPayloads>(\n routerFSMConfig,\n );\n}\n","// packages/core/src/namespaces/EventBusNamespace/EventBusNamespace.ts\n\nimport { errorCodes, events } from \"../../constants\";\nimport { RouterError } from \"../../RouterError\";\nimport { routerEvents, routerStates } from \"../../routerFSM\";\n\nimport type { EventBusOptions } from \"./types\";\nimport type { EventEmitter } from \"../../foundation/event-emitter\";\nimport type { FSM } from \"../../foundation/fsm\";\nimport type { RouterEvent, RouterPayloads, RouterState } from \"../../routerFSM\";\nimport type {\n EventName,\n LeaveFn,\n LeaveState,\n NavigationOptions,\n Plugin,\n State,\n SubscribeFn,\n TreeChangedEvent,\n Unsubscribe,\n EventMethodMap,\n} from \"../../types\";\nimport type { RouterEventMap } from \"../../types/internal\";\nimport type { RouterValidator } from \"../../types/RouterValidator\";\n\n/**\n * Internal-only event key for route-tree mutations. Lives on the same\n * `EventEmitter` as the 7 transition events but never enters the public\n * `EventName` union — reachable only through\n * `getRoutesApi(router).subscribeChanges()`.\n */\nconst TREE_CHANGED = \"TREE_CHANGED\";\n\nfunction ensureError(value: unknown): Error {\n return value instanceof Error ? value : new Error(String(value));\n}\n\nfunction settleLeavePromises(\n promises: Promise<void>[],\n firstSyncError: unknown,\n signal: AbortSignal,\n): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n // #1197 — canonicalize the abort outcome: reject with a\n // `RouterError(TRANSITION_CANCELLED)` carrying the external reason, so the\n // no-guards leave path classifies identically to the guard path (no raw\n // reject, no spurious TRANSITION_ERROR). Internal sources\n // (supersede/stop/dispose) already abort with such a RouterError — thread it\n // through unchanged so the #943 reason is preserved.\n const onAbort = (): void => {\n const reason: unknown = signal.reason;\n\n reject(\n reason instanceof RouterError &&\n reason.code === errorCodes.TRANSITION_CANCELLED\n ? reason\n : new RouterError(errorCodes.TRANSITION_CANCELLED, { reason }),\n );\n };\n\n if (signal.aborted) {\n onAbort();\n\n return;\n }\n\n // Stryker disable next-line ObjectLiteral,BooleanLiteral: equivalent — `{ once: true }` is redundant: onAbort fires at most once (a signal aborts once) and the success path explicitly removeEventListener's it, so dropping `once` is unobservable. StringLiteral sibling stays live (the \"abort\" event name is killed).\n signal.addEventListener(\"abort\", onAbort, { once: true });\n\n void Promise.allSettled(promises).then((results) => {\n // Stryker disable next-line StringLiteral: equivalent — this cleanup name is redundant: onAbort is registered with `{ once: true }` and the per-navigation signal is discarded unaborted on success, so failing to remove the listener leaks nothing observable.\n signal.removeEventListener(\"abort\", onAbort);\n\n // Stryker disable next-line BlockStatement: equivalent — emptying the post-allSettled abort-race early-return falls through to resolve()/reject(), but the abort handler already settled the promise, so the extra settle is a no-op. CE sibling stays live (→true hangs the pipeline = killed via timeout).\n if (signal.aborted) {\n // Race lost to abort — the abort handler already rejected; do nothing\n return;\n }\n\n if (firstSyncError !== undefined) {\n reject(ensureError(firstSyncError));\n\n return;\n }\n\n const rejected = results.find(\n (result): result is PromiseRejectedResult =>\n result.status === \"rejected\",\n );\n\n if (rejected !== undefined) {\n reject(ensureError(rejected.reason));\n\n return;\n }\n\n resolve();\n });\n });\n}\n\nexport class EventBusNamespace {\n readonly #fsm: FSM<RouterState, RouterEvent, null, RouterPayloads>;\n readonly #emitter: EventEmitter<RouterEventMap>;\n // Effect of the FSM CANCEL action: aborts the in-flight navigation's\n // controller. Wired to NavigationNamespace.\n readonly #abortController: (reason?: unknown) => void;\n // Lazy accessor for the opt-in RouterValidator (wired by wireNamespaces).\n // Returns `null` until validation-plugin is registered — so the proactive\n // listener-count threshold (#1188) costs the no-plugin path nothing.\n #getValidator: (() => RouterValidator | null) | undefined;\n readonly #leaveListeners: LeaveFn[] = [];\n\n // Depth of the synchronous transition-dispatch window — elevated while a\n // transition event is being emitted (`emitTransition*`) or a `subscribeLeave`\n // listener batch runs. `isProcessing()` reads it so the navigation facade can\n // reject a synchronous reentrant navigate() from inside a transition listener\n // with REENTRANT_NAVIGATION (RFC navigation-cancellation-unification §4). A\n // counter (not a boolean) tolerates legitimately-nested transition emits. The\n // ceiling-bounded \"allow reentrant navigate, throw RecursionDepthError at\n // `maxEventDepth`\" behaviour (#935/#945) is gone: such a navigate now throws\n // REENTRANT_NAVIGATION at depth 1, before it can recurse. (The emitter's old\n // `maxEventDepth` depth-bound is gone too — re-entrant emits are coalesced to a\n // no-op at the emitter, #1033 — so no event can re-enter its own dispatch.)\n #dispatchDepth = 0;\n\n #currentToState: State | undefined;\n #pendingToState: State | undefined;\n #pendingFromState: State | undefined;\n #pendingError: unknown;\n // Abort reason for the pending CANCEL — read by handleCancel, set by sendCancel.\n #pendingCancelReason: unknown;\n\n constructor(options: EventBusOptions) {\n this.#fsm = options.routerFSM;\n this.#emitter = options.emitter;\n this.#abortController = options.abortController;\n this.#currentToState = undefined;\n this.#setupFSMActions();\n }\n\n static validateSubscribeListener(listener: unknown): void {\n if (typeof listener !== \"function\") {\n throw new TypeError(\n \"[router.subscribe] Expected a function. \" +\n \"For Observable pattern use observable(router) from @real-router/rx\",\n );\n }\n }\n\n /**\n * Validates the `subscribeLeave` listener. Unlike\n * {@link validateSubscribeListener}, the error carries **no**\n * `@real-router/rx` hint — rx exposes the Observable pattern for *success*\n * transitions (`observable(router)`, `state$`, `events$`), not for leave\n * events, so steering leave-listener misuse toward rx would mislead. The\n * asymmetry is intentional (mirrored in `core/CLAUDE.md`).\n */\n static validateSubscribeLeaveListener(listener: unknown): void {\n if (typeof listener !== \"function\") {\n throw new TypeError(\"[router.subscribeLeave] Expected a function\");\n }\n }\n\n emitRouterStart(): void {\n this.#emitter.emit(events.ROUTER_START);\n }\n\n emitRouterStop(): void {\n this.#emitter.emit(events.ROUTER_STOP);\n }\n\n emitTransitionStart(toState: State, fromState?: State): void {\n this.#dispatchDepth++;\n try {\n this.#emitter.emit(events.TRANSITION_START, toState, fromState);\n } finally {\n this.#dispatchDepth--;\n }\n }\n\n emitTransitionSuccess(\n toState: State,\n fromState?: State,\n opts?: NavigationOptions,\n ): void {\n this.#dispatchDepth++;\n try {\n this.#emitter.emit(events.TRANSITION_SUCCESS, toState, fromState, opts);\n } finally {\n this.#dispatchDepth--;\n }\n }\n\n emitTransitionError(\n toState?: State,\n fromState?: State,\n error?: RouterError,\n ): void {\n this.#dispatchDepth++;\n try {\n this.#emitter.emit(events.TRANSITION_ERROR, toState, fromState, error);\n } finally {\n this.#dispatchDepth--;\n }\n }\n\n emitTransitionCancel(toState: State, fromState?: State): void {\n this.#dispatchDepth++;\n try {\n this.#emitter.emit(events.TRANSITION_CANCEL, toState, fromState);\n } finally {\n this.#dispatchDepth--;\n }\n }\n\n emitTransitionLeaveApprove(toState: State, fromState?: State): void {\n this.#dispatchDepth++;\n try {\n this.#emitter.emit(events.TRANSITION_LEAVE_APPROVE, toState, fromState);\n } finally {\n this.#dispatchDepth--;\n }\n }\n\n /**\n * True while a transition event is being dispatched synchronously — an\n * `emitTransition*` call or a `subscribeLeave` listener batch is on the stack.\n * The navigation facade reads this to reject a synchronous reentrant\n * navigate() from inside a transition listener (RFC §4).\n */\n isProcessing(): boolean {\n return this.#dispatchDepth > 0;\n }\n\n /**\n * Emits the internal `TREE_CHANGED` event after a structural route-tree\n * mutation. Reuses the shared `EventEmitter` — so re-entrancy coalescing\n * (#1033) and per-listener error isolation (`onListenerError`) apply\n * automatically.\n */\n emitTreeChanged(event: TreeChangedEvent): void {\n this.#emitter.emit(TREE_CHANGED, event);\n }\n\n /**\n * True while a `TREE_CHANGED` event is being dispatched synchronously.\n * Delegates to the emitter's own in-flight tracking (#1034) — `getRoutesApi`\n * reads this to reject reentrant route-CRUD from a `subscribeChanges` handler\n * (#1032).\n */\n isEmittingTreeChanged(): boolean {\n return this.#emitter.isDispatching(TREE_CHANGED);\n }\n\n /**\n * Subscribes to `TREE_CHANGED`. **Lenient** duplicate semantics (mirrors\n * {@link subscribe}): each call wraps the handler in a fresh closure, so N\n * registrations of the same reference produce N independent subscriptions.\n */\n subscribeTreeChanged(\n handler: (event: TreeChangedEvent) => void,\n ): Unsubscribe {\n // Same disposed-state enforcement as subscribe()/subscribeLeave() (#946),\n // completing the guard across all three subscription primitives — extended\n // here to the internal route-tree channel (#982). A `subscribeChanges`\n // reference bound before dispose() (`const s = routes.subscribeChanges\n // .bind(routes)`) reaches this method via the getRoutesApi delegate, which\n // — unlike its add/remove/update siblings — does not itself check\n // isDisposed(). Without this guard, `emitter.on` would re-register a\n // TREE_CHANGED listener that can never fire (clearAll already ran, the FSM\n // is DISPOSED, the route tree is torn down, no future emit) — a silent\n // no-op, the internal-channel counterpart of the #946 hazard.\n if (this.isDisposed()) {\n throw new RouterError(errorCodes.ROUTER_DISPOSED);\n }\n\n return this.#emitter.on(TREE_CHANGED, (event: TreeChangedEvent) => {\n handler(event);\n });\n }\n\n /** Number of active `TREE_CHANGED` listeners (drives conditional emit). */\n treeChangedListenerCount(): number {\n return this.#emitter.listenerCount(TREE_CHANGED);\n }\n\n sendStart(): void {\n this.#fsm.send(routerEvents.START);\n }\n\n sendStop(): void {\n this.#fsm.send(routerEvents.STOP);\n }\n\n sendDispose(): void {\n this.#fsm.send(routerEvents.DISPOSE);\n }\n\n sendStarted(): void {\n this.#fsm.send(routerEvents.STARTED);\n }\n\n sendNavigate(toState: State, fromState?: State): void {\n this.#currentToState = toState;\n // Table-driven: the FSM action emits TRANSITION_START (#1169 D-full). A\n // NAVIGATE that the table rejects is a no-op — the FSM never leaves an\n // invalid state and no event fires.\n this.#fsm.send(routerEvents.NAVIGATE, { toState, fromState });\n }\n\n sendComplete(\n state: State,\n fromState?: State,\n opts: NavigationOptions = {},\n ): void {\n // Table-driven: the FSM action emits TRANSITION_SUCCESS (#1169 D-full).\n // COMPLETE from IDLE/DISPOSED (a listener stopped/disposed mid-transition)\n // is a table no-op — no resurrection, no phantom success emit.\n this.#fsm.send(routerEvents.COMPLETE, { toState: state, fromState, opts });\n\n // Nav committed — clear so a later stop()/dispose() cannot cancel a finished\n // navigation. Unconditional now that synchronous reentrant navigate is banned\n // (RFC §4): nothing can replace #currentToState during the emit above, so the\n // #308 reentrant-preserve guard is no longer needed.\n this.#currentToState = undefined;\n }\n\n sendLeaveApprove(toState: State, fromState?: State): void {\n // Table-driven: the FSM action emits TRANSITION_LEAVE_APPROVE (#1169 D-full).\n // LEAVE_APPROVE from IDLE/DISPOSED is a table no-op — no resurrection.\n this.#fsm.send(routerEvents.LEAVE_APPROVE, { toState, fromState });\n }\n\n sendFail(toState?: State, fromState?: State, error?: unknown): void {\n this.#pendingToState = toState;\n this.#pendingFromState = fromState;\n this.#pendingError = error;\n this.#fsm.send(routerEvents.FAIL);\n\n // Nav failed — clear (unconditional; synchronous reentrant navigate is\n // banned (RFC §4), so nothing replaces #currentToState during the emit).\n this.#currentToState = undefined;\n }\n\n /**\n * Surfaces a `TRANSITION_ERROR` for callers that do **not** know — or do not\n * control — the current FSM state: the plugin-facing `emitTransitionError`\n * primitive (`getPluginApi`), the dispose chain, and validator / same-state\n * rejections. It is the state-agnostic counterpart to {@link sendFail}.\n *\n * **What \"Safe\" means here.** The error event is never *dropped*, whatever the\n * FSM state — it does **not** mean the method catches every error. Errors\n * thrown *inside* a `TRANSITION_ERROR` listener are isolated by the\n * `EventEmitter`'s per-listener `onListenerError` sink, not by this method.\n *\n * **Why it branches on its own FSM state.** When the FSM is settled in `READY`\n * (no transition in flight) it routes through the FSM `FAIL` action via\n * {@link sendFail}, so the error rides the normal FSM-driven emit. Otherwise —\n * the router may be starting, mid-transition, or torn down — it emits\n * `TRANSITION_ERROR` directly: a fire-and-forget error report from an unknown\n * state must not drive a second FSM transition that could collide with an\n * in-flight one. Both branches guarantee the event reaches subscribers.\n */\n sendFailSafe(toState?: State, fromState?: State, error?: unknown): void {\n if (this.isReady()) {\n this.sendFail(toState, fromState, error);\n } else {\n this.emitTransitionError(toState, fromState, error as RouterError);\n }\n }\n\n sendCancel(toState: State, fromState?: State, reason?: unknown): void {\n this.#pendingToState = toState;\n this.#pendingFromState = fromState;\n this.#pendingCancelReason = reason;\n this.#fsm.send(routerEvents.CANCEL);\n\n // Nav cancelled — clear (unconditional; synchronous reentrant navigate is\n // banned (RFC §4), so nothing replaces #currentToState during the emit).\n this.#currentToState = undefined;\n }\n\n canBeginTransition(): boolean {\n return this.#fsm.canSend(routerEvents.NAVIGATE);\n }\n\n canStart(): boolean {\n return this.#fsm.canSend(routerEvents.START);\n }\n\n canCancel(): boolean {\n return this.#fsm.canSend(routerEvents.CANCEL);\n }\n\n isActive(): boolean {\n const fsmState = this.#fsm.getState();\n\n return fsmState !== routerStates.IDLE && fsmState !== routerStates.DISPOSED;\n }\n\n isDisposed(): boolean {\n return this.#fsm.getState() === routerStates.DISPOSED;\n }\n\n isTransitioning(): boolean {\n const state = this.#fsm.getState();\n\n return (\n state === routerStates.TRANSITION_STARTED ||\n state === routerStates.LEAVE_APPROVED\n );\n }\n\n isLeaveApproved(): boolean {\n return this.#fsm.getState() === routerStates.LEAVE_APPROVED;\n }\n\n isReady(): boolean {\n return this.#fsm.getState() === routerStates.READY;\n }\n\n isStarting(): boolean {\n return this.#fsm.getState() === routerStates.STARTING;\n }\n\n isIdle(): boolean {\n return this.#fsm.getState() === routerStates.IDLE;\n }\n\n /**\n * Plugin-author API for subscribing to internal router events.\n *\n * @remarks\n *\n * **Duplicate-registration semantics — strict (throws).** Passing the same\n * callback reference twice for the same event throws\n * `Error(\"Duplicate listener for ...\")` from the underlying `EventEmitter`.\n * This is loud-on-misuse by design: plugin code is expected to register\n * each callback once. The contract differs from {@link subscribe} /\n * {@link subscribeLeave}, which are end-user surfaces and silently accept\n * duplicates.\n */\n addEventListener<E extends EventName>(\n eventName: E,\n cb: Plugin[EventMethodMap[E]],\n ): Unsubscribe {\n this.#checkListenerThreshold(eventName, \"addEventListener\");\n\n return this.#emitter.on(\n eventName,\n cb as (...args: RouterEventMap[typeof eventName]) => void,\n );\n }\n\n /**\n * End-user / UI-binding API for subscribing to successful transitions.\n *\n * @remarks\n *\n * **Duplicate-registration semantics — independent.** Each call wraps\n * `listener` in a fresh closure and registers it as a distinct internal\n * slot. `router.subscribe(fn)` twice produces **two** active subscriptions;\n * `fn` fires twice per `TRANSITION_SUCCESS`. The returned `Unsubscribe` is\n * paired with its specific call — invoking it removes exactly that\n * registration.\n *\n * This contract differs from {@link addEventListener} (plugin API, throws\n * on duplicate). End-user code that wants idempotent registration must\n * gate itself, e.g. `if (!unsub) unsub = router.subscribe(fn);`.\n */\n subscribe(listener: SubscribeFn): Unsubscribe {\n // Enforce the disposed state HERE, not only on the facade. A reference\n // bound before dispose() (`const s = router.subscribe.bind(router)`)\n // bypasses the facade's #markDisposed swap and reaches this method\n // directly. Without this guard, `emitter.on` would silently re-register a\n // listener that can never fire (clearAll already ran, FSM is DISPOSED, no\n // future emit) — a silent no-op / stuck-UI hazard (#946).\n if (this.isDisposed()) {\n throw new RouterError(errorCodes.ROUTER_DISPOSED);\n }\n\n this.#checkListenerThreshold(events.TRANSITION_SUCCESS, \"subscribe\");\n\n // `subscribe` is fire-and-forget; the listener's return value is ignored at\n // the type level (`SubscribeFn` is `=> void`). A void-typed async listener\n // still returns a rejecting Promise at runtime, so return it to the emitter,\n // whose central isolation routes the rejection to the same `onListenerError`\n // sink a sync throw flows through — instead of leaking an `unhandledRejection`\n // (fatal under `--unhandled-rejections=strict`, the Node 22+ default). This\n // is #944, now folded into the emitter's central #1412 async isolation — the\n // former per-site `.catch` here is redundant (symmetric with `subscribeLeave`,\n // which isolates via `Promise.allSettled`).\n return this.#emitter.on(\n events.TRANSITION_SUCCESS,\n (toState: State, fromState?: State) => {\n // Return the listener's runtime value to the emitter so its central\n // isolation catches an async listener's rejection (#944/#1412). Read the\n // void-typed result into `unknown` first — a returned `void` expression\n // is forbidden, but returning the read `unknown` is fine.\n // eslint-disable-next-line @typescript-eslint/no-confusing-void-expression -- read the void-typed listener's runtime value so it can be returned for central async isolation (#944/#1412)\n const result: unknown = listener({\n route: toState,\n previousRoute: fromState,\n });\n\n return result;\n },\n );\n }\n\n /**\n * End-user / UI-binding API for subscribing to **approved** route departures\n * (`LEAVE_APPROVED` phase): all `canDeactivate` guards have passed, but the\n * departure is **tentative, not committed** — an activation (`canActivate`)\n * guard can still reject (or the target route be removed mid-transition),\n * leaving the user on the current route (#932). Treat the leave as tentative\n * for non-idempotent side-effects and use the payload `signal` (which aborts\n * with the failure reason, #943) to roll back when the navigation does not\n * commit. Async listeners block the activation phase.\n *\n * @remarks\n *\n * **Duplicate-registration semantics — independent.** Each call pushes\n * `listener` onto the internal array; `router.subscribeLeave(fn)` twice\n * produces two entries and `fn` fires twice per leave. Each returned\n * `Unsubscribe` is **idempotent** (a `removed` flag, #1349) and removes\n * exactly ONE entry — the first still matching the reference (`indexOf`\n * semantic). So a repeated call of one unsubscribe is a true no-op and does\n * **not** touch the other registration; N subscribes + M *distinct*\n * unsubscribes leave N − M entries. Which physical entry survives is\n * irrelevant — the reference is the same.\n *\n * Contract differs from {@link addEventListener} (throws on duplicate).\n * For idempotent *registration* (one active subscription), gate at the call site.\n */\n subscribeLeave(listener: LeaveFn): Unsubscribe {\n // Same disposed-state enforcement as subscribe() (#946): a pre-bound\n // reference would otherwise push onto #leaveListeners after dispose() and\n // silently never fire (FSM is DISPOSED, no LEAVE_APPROVE emit).\n if (this.isDisposed()) {\n throw new RouterError(errorCodes.ROUTER_DISPOSED);\n }\n\n this.#leaveListeners.push(listener);\n\n // Idempotency flag (#1349), mirroring extendRouter / addInterceptor (#1198).\n // Without it, a double call would `indexOf(listener)` again and splice a\n // DUPLICATE registration of the same fn — silently deactivating another\n // subscriber whose own unsubscribe was never called. The `Unsubscribe`\n // contract names subscribeLeave as idempotent. (Unlike addInterceptor, the\n // `idx !== -1` guard stays: `dispose()` empties `#leaveListeners` via\n // `clearAll`, so an unsubscribe called after dispose reaches this with\n // idx === -1.)\n let removed = false;\n\n return () => {\n if (removed) {\n return;\n }\n\n removed = true;\n\n const idx = this.#leaveListeners.indexOf(listener);\n\n if (idx !== -1) {\n this.#leaveListeners.splice(idx, 1);\n }\n };\n }\n\n hasLeaveListeners(): boolean {\n return this.#leaveListeners.length > 0;\n }\n\n /**\n * True when a plugin listener runs in a PRE-COMMIT transition window where a\n * synchronous `stop()`/`dispose()` can supersede the in-flight navigation\n * before it commits — i.e. `onTransitionStart` (TRANSITION_START) or\n * `onTransitionLeaveApprove` (TRANSITION_LEAVE_APPROVE). (`subscribeLeave` is\n * tracked separately via `hasLeaveListeners`; `onTransitionSuccess` fires\n * post-commit and cannot cancel.) Read into the pre-`startTransition` liveness\n * snapshot so the commit-gate (#1169) fires for these windows too.\n */\n hasPreCommitListeners(): boolean {\n return (\n this.#emitter.listenerCount(events.TRANSITION_START) > 0 ||\n this.#emitter.listenerCount(events.TRANSITION_LEAVE_APPROVE) > 0\n );\n }\n\n awaitLeaveListeners(\n toState: State,\n fromState: State | undefined,\n signal: AbortSignal,\n ): Promise<void> | undefined {\n if (fromState === undefined) {\n return undefined;\n }\n\n // Freeze the payload wrapper so listeners cannot mutate it (`payload.route`\n // is already deep-frozen via the State immutability invariant; this closes\n // the wrapper-mutation gap surfaced by audit `probe-05-payload-frozen`).\n const leaveState: LeaveState = Object.freeze({\n route: fromState,\n nextRoute: toState,\n signal,\n });\n\n let promises: Promise<void>[] | undefined;\n let firstSyncError: unknown;\n\n // Snapshot before iteration — a listener that reentrantly calls\n // `subscribeLeave(newFn)` or its own `unsubscribe()` must not affect the\n // current emit cycle. Symmetric with the EventEmitter snapshot invariant\n // (PR #666 / #659).\n const snapshot = [...this.#leaveListeners];\n\n // Elevated across the SYNC leave-listener dispatch: `isProcessing()` reads\n // it, so a sync subscribeLeave listener that calls navigate() is rejected\n // with REENTRANT_NAVIGATION at the facade (RFC §4). The `finally` restores it\n // before any async tail (returned promise) runs, so a DEFERRED navigate from\n // an async listener (after its first `await`) sees depth 0 and is allowed.\n this.#dispatchDepth++;\n\n try {\n for (const listener of snapshot) {\n try {\n const result = listener(leaveState);\n\n if (result !== undefined && typeof result.then === \"function\") {\n promises ??= [];\n promises.push(result);\n }\n } catch (error: unknown) {\n if (firstSyncError === undefined) {\n firstSyncError = error;\n }\n }\n }\n } finally {\n this.#dispatchDepth--;\n }\n\n if (promises === undefined) {\n if (firstSyncError !== undefined) {\n throw ensureError(firstSyncError);\n }\n\n return undefined;\n }\n\n return settleLeavePromises(promises, firstSyncError, signal);\n }\n\n clearAll(): void {\n this.#emitter.clearAll();\n this.#leaveListeners.length = 0;\n }\n\n setLimits(limits: { maxListeners: number; warnListeners: number }): void {\n this.#emitter.setLimits(limits);\n }\n\n /**\n * Injects the lazy validator accessor (wireNamespaces), mirroring\n * `PluginsNamespace` / `RouteLifecycleNamespace`. The closure reads the live\n * `RouterInternals.validator`, so a validation-plugin registered AFTER wiring\n * is still observed on the next `subscribe` / `addEventListener`.\n */\n setValidatorAccessor(getValidator: () => RouterValidator | null): void {\n this.#getValidator = getValidator;\n }\n\n // Single guarded entry point for routing a cancel into the FSM `CANCEL` action\n // — used by every source: stop/dispose (RouterLifecycle) pass no reason;\n // supersede / external `opts.signal` (via the wiring `cancelNavigation` dep)\n // pass the abort reason (#943). `canCancel()` makes it a no-op outside a\n // cancellable FSM state (#1034: was a second, unguarded `cancelNavigation` path).\n sendCancelIfPossible(fromState: State | undefined, reason?: unknown): void {\n const toState = this.#currentToState;\n\n if (!this.canCancel() || toState === undefined) {\n return;\n }\n\n this.sendCancel(toState, fromState, reason);\n }\n\n /**\n * Proactive listener-count threshold (#1188) — mirrors the plugins /\n * lifecycle / dependencies counters. Opt-in: the emitter's per-event count is\n * read ONLY when the validator is installed, so the bare-core hot path pays\n * nothing. `count` is the POST-add size (`listenerCount + 1`), matching\n * `RouteLifecycleNamespace`'s `count + 1`, so warn/error fire exactly when the\n * new listener reaches the threshold. Core keeps the emitter's bare-`Error`\n * hard cap; this only surfaces an actionable signal well before it.\n */\n #checkListenerThreshold(eventName: EventName, methodName: string): void {\n const validator = this.#getValidator?.();\n\n if (validator) {\n validator.eventBus.validateCountThresholds(\n this.#emitter.listenerCount(eventName) + 1,\n eventName,\n methodName,\n );\n }\n }\n\n #emitPendingError(): void {\n this.emitTransitionError(\n this.#pendingToState,\n this.#pendingFromState,\n this.#pendingError as RouterError | undefined,\n );\n\n // Clear the pending payload once this FAIL action has consumed it. `#pending*`\n // is only meaningful in the window between the sendFail()/sendFailSafe() that\n // sets it and this emit; keeping it afterwards pins a stale State/RouterError\n // on the instance and leaves an implicit \"valid only in this window\" coupling\n // (#949). Hygiene only — every consumer overwrites the fields before\n // re-reading (handleCancel reads what its own sendCancel just set), so there\n // is no observable behaviour change.\n this.#pendingToState = undefined;\n this.#pendingFromState = undefined;\n this.#pendingError = undefined;\n }\n\n #setupFSMActions(): void {\n const fsm = this.#fsm;\n\n fsm.on(routerStates.STARTING, routerEvents.STARTED, () => {\n this.emitRouterStart();\n });\n\n fsm.on(routerStates.READY, routerEvents.STOP, () => {\n this.emitRouterStop();\n });\n\n // NAVIGATE / LEAVE_APPROVE / COMPLETE emit their transition event as the FSM\n // action (payload = the transition states), so `send()` from an invalid\n // state (e.g. COMPLETE from IDLE after a listener's stop()/dispose()) is a\n // table no-op that emits nothing — the FSM table is the sole authority over\n // state, no `forceState` resurrection (#1169 D-full). NAVIGATE fires from\n // READY plus the TRANSITION_STARTED / LEAVE_APPROVED self-loops (supersede).\n const emitNavigate = (payload: RouterPayloads[\"NAVIGATE\"]): void => {\n this.emitTransitionStart(payload.toState, payload.fromState);\n };\n\n fsm.on(routerStates.READY, routerEvents.NAVIGATE, emitNavigate);\n fsm.on(\n routerStates.TRANSITION_STARTED,\n routerEvents.NAVIGATE,\n emitNavigate,\n );\n fsm.on(routerStates.LEAVE_APPROVED, routerEvents.NAVIGATE, emitNavigate);\n\n fsm.on(\n routerStates.TRANSITION_STARTED,\n routerEvents.LEAVE_APPROVE,\n (payload) => {\n this.emitTransitionLeaveApprove(payload.toState, payload.fromState);\n },\n );\n\n fsm.on(routerStates.LEAVE_APPROVED, routerEvents.COMPLETE, (payload) => {\n this.emitTransitionSuccess(\n payload.toState,\n payload.fromState,\n payload.opts,\n );\n });\n\n const handleCancel = () => {\n const toState = this.#pendingToState;\n const reason = this.#pendingCancelReason;\n\n this.#pendingCancelReason = undefined;\n\n // (RFC navigation-cancellation-unification §5): the FSM CANCEL\n // action OWNS the abort. Aborting the in-flight controller wakes the parked\n // async pipeline (#1018) and rejects navigate() — the post-race local\n // isActive() sees signal.aborted; `reason` surfaces as the leave signal's\n // reason (#943). Order: (i) abort (wake) then (ii) emit TRANSITION_CANCEL.\n // No cycle: onInternalAbort is wake-only, it does not re-enter cancel.\n this.#abortController(reason);\n\n /* v8 ignore next -- @preserve: #pendingToState guaranteed set by sendCancel before send() */\n if (toState === undefined) {\n return;\n }\n\n this.emitTransitionCancel(toState, this.#pendingFromState);\n };\n\n fsm.on(routerStates.TRANSITION_STARTED, routerEvents.CANCEL, handleCancel);\n fsm.on(routerStates.LEAVE_APPROVED, routerEvents.CANCEL, handleCancel);\n\n fsm.on(routerStates.LEAVE_APPROVED, routerEvents.FAIL, () => {\n this.#emitPendingError();\n });\n\n fsm.on(routerStates.STARTING, routerEvents.FAIL, () => {\n this.#emitPendingError();\n });\n\n fsm.on(routerStates.READY, routerEvents.FAIL, () => {\n this.#emitPendingError();\n });\n\n fsm.on(routerStates.TRANSITION_STARTED, routerEvents.FAIL, () => {\n this.#emitPendingError();\n });\n }\n}\n","// packages/core/src/namespaces/RouterLifecycleNamespace/constants.ts\n\nimport { errorCodes } from \"../../constants\";\nimport { RouterError } from \"../../RouterError\";\n\n// =============================================================================\n// Cached Errors (Performance Optimization)\n// =============================================================================\n// Pre-create error instances to avoid object allocation on hot paths.\n// Error creation involves: new object, stack trace capture (~500ns-2μs).\n// Cached errors skip this overhead entirely.\n//\n// Trade-off: All error instances share the same stack trace (points here).\n// This is acceptable because:\n// 1. These errors indicate user misconfiguration, not internal bugs\n// 2. Error code and message are sufficient for debugging\n// 3. Performance gain (~80% for error paths) outweighs stack trace loss\n// =============================================================================\n\n/**\n * Cached error for start() called when router is already started/starting.\n */\nexport const CACHED_ALREADY_STARTED_ERROR = new RouterError(\n errorCodes.ROUTER_ALREADY_STARTED,\n);\n","// packages/core/src/wiring/wireNamespaces.ts\n\nimport { normalizeParams } from \"../helpers\";\nimport { getInternals } from \"../internals\";\nimport { resolveOption } from \"../namespaces/OptionsNamespace\";\n\nimport type { NamespaceBag } from \"./types\";\nimport type { NavigationDependencies } from \"../namespaces/NavigationNamespace\";\nimport type { PluginsDependencies } from \"../namespaces/PluginsNamespace\";\nimport type { RouteLifecycleDependencies } from \"../namespaces/RouteLifecycleNamespace\";\nimport type { RouterLifecycleDependencies } from \"../namespaces/RouterLifecycleNamespace\";\nimport type { RoutesDependencies } from \"../namespaces/RoutesNamespace\";\nimport type { Router } from \"../Router\";\nimport type { DefaultDependencies, Params } from \"../types\";\nimport type { RouterValidator } from \"../types/RouterValidator\";\n\n/**\n * Compiles a guard/plugin factory against the router + a cached `getDependency`\n * accessor. One generic function serves both RouteLifecycle (`GuardFnFactory →\n * GuardFn`) and Plugins (`PluginFactory → Plugin`).\n */\ntype CompileFactory<Dependencies extends DefaultDependencies> = <T>(\n factory: (\n router: Router<Dependencies>,\n getDependency: <K extends keyof Dependencies>(name: K) => Dependencies[K],\n ) => T,\n) => T;\n\n/**\n * Wires the inter-namespace dependencies of a freshly-constructed router.\n *\n * Replaces the former `RouterWiringBuilder` class + `wireRouter` director\n * (#1334): a single call-site with a fixed sequence and nothing to build is a\n * pack of procedures over a shared bag, not a builder — plain functions over a\n * `NamespaceBag` drop the triple-repeated field list and the builder instance.\n *\n * Call order is arbitrary (#1331): no `wire*` function runs user code or\n * eagerly reads another namespace's deps. (`wireLimits` is the one eager\n * *write* — it hands the frozen limits object to dependenciesStore/eventBus;\n * the rest only store deps-closures.) Initial-route guard factories are\n * flushed afterwards, from the constructor's `flushPendingGuards()` call.\n */\nexport function wireNamespaces<Dependencies extends DefaultDependencies>(\n ns: NamespaceBag<Dependencies>,\n): void {\n // One shared factory for both guard and plugin compilation (#1334); the\n // `getDependency` closure is allocated once here, not per compile call.\n const compileFactory = createCompileFactory(ns);\n\n // Shared by RouteLifecycle and Plugins — one allocation. Internals are\n // registered before wiring (#1331), so this never throws; returns null until\n // validation-plugin installs the validator.\n const getValidator = (): RouterValidator | null =>\n getInternals(ns.router).validator;\n\n wireLimits(ns);\n wireEventBus(ns, getValidator);\n wireRouteLifecycle(ns, compileFactory, getValidator);\n wireRoutes(ns);\n wirePlugins(ns, compileFactory, getValidator);\n wireNavigation(ns);\n wireRouterLifecycle(ns);\n wireState(ns);\n}\n\nfunction createCompileFactory<Dependencies extends DefaultDependencies>(\n ns: NamespaceBag<Dependencies>,\n): CompileFactory<Dependencies> {\n const { router, dependenciesStore } = ns;\n\n const getDependency = <K extends keyof Dependencies>(\n name: K,\n ): Dependencies[K] => dependenciesStore.dependencies[name] as Dependencies[K];\n\n return <T>(\n factory: (\n router: Router<Dependencies>,\n getDependency: <K extends keyof Dependencies>(name: K) => Dependencies[K],\n ) => T,\n ): T => factory(router, getDependency);\n}\n\nfunction wireLimits<Dependencies extends DefaultDependencies>(\n ns: NamespaceBag<Dependencies>,\n): void {\n ns.dependenciesStore.limits = ns.limits;\n ns.eventBus.setLimits({\n maxListeners: ns.limits.maxListeners,\n warnListeners: ns.limits.warnListeners,\n });\n}\n\n/**\n * Hands EventBusNamespace the shared lazy validator accessor so `subscribe` /\n * `addEventListener` can run the opt-in listener-count threshold (#1188) — the\n * emitter-side parallel to the plugins / lifecycle / dependencies counters.\n */\nfunction wireEventBus<Dependencies extends DefaultDependencies>(\n ns: NamespaceBag<Dependencies>,\n getValidator: () => RouterValidator | null,\n): void {\n ns.eventBus.setValidatorAccessor(getValidator);\n}\n\nfunction wireRouteLifecycle<Dependencies extends DefaultDependencies>(\n ns: NamespaceBag<Dependencies>,\n compileFactory: CompileFactory<Dependencies>,\n getValidator: () => RouterValidator | null,\n): void {\n const deps: RouteLifecycleDependencies<Dependencies> = {\n logger: getInternals(ns.router).logger,\n compileFactory,\n getValidator,\n };\n\n ns.routeLifecycle.setDependencies(deps);\n}\n\nfunction wireRoutes<Dependencies extends DefaultDependencies>(\n ns: NamespaceBag<Dependencies>,\n): void {\n const deps: RoutesDependencies<Dependencies> = {\n logger: getInternals(ns.router).logger,\n addActivateGuard: (name, handler, precompiledFn) => {\n ns.routeLifecycle.addCanActivate(name, handler, true, precompiledFn);\n },\n addDeactivateGuard: (name, handler, precompiledFn) => {\n ns.routeLifecycle.addCanDeactivate(name, handler, true, precompiledFn);\n },\n compileGuard: (handler, methodName) =>\n ns.routeLifecycle.compileGuardFactory(handler, methodName),\n makeState: (name, params, path, meta) =>\n ns.state.makeState(name, params, path, meta),\n getState: () => ns.state.get(),\n areStatesEqual: (state1, state2, ignoreQueryParams) =>\n ns.state.areStatesEqual(state1, state2, ignoreQueryParams),\n getDependency: (name) =>\n ns.dependenciesStore.dependencies[name] as Dependencies[typeof name],\n forwardState: <P extends Params = Params>(name: string, params: P) => {\n const ctx = getInternals(ns.router);\n\n ctx.validator?.routes.validateStateBuilderArgs(\n name,\n params,\n \"forwardState\",\n );\n\n return ctx.forwardState(name, params);\n },\n };\n\n ns.routes.setDependencies(deps);\n ns.routes.setLifecycleNamespace(ns.routeLifecycle);\n}\n\nfunction wirePlugins<Dependencies extends DefaultDependencies>(\n ns: NamespaceBag<Dependencies>,\n compileFactory: CompileFactory<Dependencies>,\n getValidator: () => RouterValidator | null,\n): void {\n const deps: PluginsDependencies<Dependencies> = {\n logger: getInternals(ns.router).logger,\n addEventListener: (eventName, cb) =>\n ns.eventBus.addEventListener(eventName, cb),\n canNavigate: () => ns.eventBus.canBeginTransition(),\n compileFactory,\n getValidator,\n };\n\n ns.plugins.setDependencies(deps);\n}\n\nfunction wireNavigation<Dependencies extends DefaultDependencies>(\n ns: NamespaceBag<Dependencies>,\n): void {\n const deps: NavigationDependencies = {\n logger: getInternals(ns.router).logger,\n getOptions: () => ns.options.get(),\n hasRoute: (name) => ns.routes.hasRoute(name),\n getState: () => ns.state.get(),\n setState: (state) => {\n ns.state.set(state);\n },\n buildNavigateState: (routeName, routeParams) => {\n const ctx = getInternals(ns.router);\n\n ctx.validator?.routes.validateStateBuilderArgs(\n routeName,\n routeParams,\n \"navigate\",\n );\n\n const forwarded = ctx.forwardState(routeName, routeParams);\n const name = forwarded.name;\n const params = normalizeParams(forwarded.params);\n const meta = ns.routes.getMetaForState(name);\n\n if (meta === undefined) {\n return;\n }\n\n const path = ctx.buildPath(name, params);\n\n return ns.state.makeState(name, params, path, meta, true);\n },\n resolveDefault: () => {\n const options = ns.options.get();\n const ctx = getInternals(ns.router);\n\n const route = resolveOption(\n options.defaultRoute,\n (name: string) =>\n ns.dependenciesStore.dependencies[name as keyof Dependencies],\n );\n const params = resolveOption(\n options.defaultParams,\n /* v8 ignore next -- @preserve: unreachable unless defaultParams is a callback that calls getDependency */\n (name: string) =>\n ns.dependenciesStore.dependencies[name as keyof Dependencies],\n );\n\n if (typeof options.defaultRoute === \"function\") {\n ctx.validator?.options.validateResolvedDefaultRoute(\n route,\n ctx.routeGetStore(),\n );\n }\n\n return { route, params };\n },\n startTransition: (toState, fromState) => {\n ns.eventBus.sendNavigate(toState, fromState);\n },\n cancelNavigation: (reason) => {\n ns.eventBus.sendCancelIfPossible(ns.state.get(), reason);\n },\n sendTransitionDone: (state, fromState, opts) => {\n ns.eventBus.sendComplete(state, fromState, opts);\n },\n sendTransitionFail: (toState, fromState, error) => {\n ns.eventBus.sendFail(toState, fromState, error);\n },\n emitTransitionError: (toState, fromState, error) => {\n ns.eventBus.sendFailSafe(toState, fromState, error);\n },\n emitTransitionSuccess: (toState, fromState, opts) => {\n ns.eventBus.emitTransitionSuccess(toState, fromState, opts);\n },\n sendLeaveApprove: (toState, fromState) => {\n ns.eventBus.sendLeaveApprove(toState, fromState);\n },\n canNavigate: () => ns.eventBus.canBeginTransition(),\n getLifecycleFunctions: () => ns.routeLifecycle.getFunctions(),\n isActive: () => ns.router.isActive(),\n isTransitioning: () => ns.eventBus.isTransitioning(),\n // Post-leave auto-cleanup unregisters only the EXTERNAL (component-managed)\n // guard; a route-config (definition) guard survives for re-entry (#1171).\n clearCanDeactivate: (name: string) => {\n ns.routeLifecycle.clearCanDeactivate(name, \"external\");\n },\n hasLeaveListeners: () => ns.eventBus.hasLeaveListeners(),\n hasPreCommitListeners: () => ns.eventBus.hasPreCommitListeners(),\n awaitLeaveListeners: (toState, fromState, signal) =>\n ns.eventBus.awaitLeaveListeners(toState, fromState, signal),\n };\n\n ns.navigation.setDependencies(deps);\n}\n\nfunction wireRouterLifecycle<Dependencies extends DefaultDependencies>(\n ns: NamespaceBag<Dependencies>,\n): void {\n const deps: RouterLifecycleDependencies = {\n getOptions: () => ns.options.get(),\n navigateToState: (state, opts) =>\n ns.navigation.navigateToState(state, opts),\n navigateToNotFound: (path) => ns.navigation.navigateToNotFound(path),\n clearState: () => {\n ns.state.set(undefined);\n },\n matchPath: (path) => ns.routes.matchPath(path, ns.options.get()),\n completeStart: () => {\n ns.eventBus.sendStarted();\n },\n isIdle: () => ns.eventBus.isIdle(),\n emitTransitionError: (toState, fromState, error) => {\n ns.eventBus.sendFail(toState, fromState, error);\n },\n };\n\n ns.lifecycle.setDependencies(deps);\n}\n\nfunction wireState<Dependencies extends DefaultDependencies>(\n ns: NamespaceBag<Dependencies>,\n): void {\n ns.state.setDependencies({\n getDefaultParams: () => ns.routes.getStore().config.defaultParams,\n buildPath: (name, params) => {\n const ctx = getInternals(ns.router);\n\n return ctx.buildPath(name, params);\n },\n getUrlParams: (name) => ns.routes.getUrlParams(name),\n });\n}\n","// packages/core/src/Router.ts\n\n/**\n * Router class - facade with integrated namespaces.\n *\n * All functionality is now provided by namespace classes.\n */\n\nimport { EMPTY_PARAMS, errorCodes } from \"./constants\";\nimport { EventEmitter } from \"./foundation/event-emitter\";\nimport { RouterLogger } from \"./foundation/logger\";\nimport {\n assertLoggerConfig,\n guardDependencies,\n guardRouteStructure,\n} from \"./guards\";\nimport { createLimits, normalizeParams } from \"./helpers\";\nimport {\n createBinaryInterceptable,\n createInterceptable,\n getInternals,\n registerInternals,\n} from \"./internals\";\nimport {\n EventBusNamespace,\n NavigationNamespace,\n OptionsNamespace,\n PluginsNamespace,\n RouteLifecycleNamespace,\n RouterLifecycleNamespace,\n RoutesNamespace,\n StateNamespace,\n createDependenciesStore,\n} from \"./namespaces\";\nimport { CACHED_ALREADY_STARTED_ERROR } from \"./namespaces/RouterLifecycleNamespace/constants\";\nimport { RouterError } from \"./RouterError\";\nimport { createRouterFSM } from \"./routerFSM\";\nimport { getTransitionPath } from \"./transitionPath\";\nimport { wireNamespaces } from \"./wiring\";\n\nimport type { CreateMatcherOptions } from \"./engine\";\nimport type { RouterInternals } from \"./internals\";\nimport type { DependenciesStore } from \"./namespaces\";\nimport type {\n DefaultDependencies,\n LeaveFn,\n NavigationOptions,\n Options,\n Params,\n Router as RouterInterface,\n State,\n SubscribeFn,\n Unsubscribe,\n PluginFactory,\n Route,\n} from \"./types\";\nimport type { Limits, RouterEventMap } from \"./types/internal\";\n\nconst EMPTY_OPTS: Readonly<NavigationOptions> = Object.freeze({});\n\n// Module-level so #isExpectedRejection allocates nothing per navigate()/start() call.\n// These are expected navigation outcomes owned by the caller, not internal\n// bugs — the safety net stays silent for them and lets awaiting callers see\n// the rejection. CANNOT_ACTIVATE / CANNOT_DEACTIVATE belong here: a guard\n// blocking (or a plugin's guard-blocked back()/forward()) is a normal result,\n// so a fire-and-forget call must not emit a spurious \"Unexpected navigation\n// error\" (#721).\nconst SUPPRESSED_ERROR_CODES: ReadonlySet<string> = new Set([\n errorCodes.SAME_STATES,\n errorCodes.TRANSITION_CANCELLED,\n errorCodes.ROUTER_NOT_STARTED,\n errorCodes.ROUTE_NOT_FOUND,\n errorCodes.CANNOT_ACTIVATE,\n errorCodes.CANNOT_DEACTIVATE,\n]);\n\n/**\n * Router class with integrated namespace architecture.\n *\n * All functionality is provided by namespace classes:\n * - OptionsNamespace: getOptions (immutable)\n * - DependenciesStore: get/set/remove dependencies\n * - EventEmitter: subscribe\n * - StateNamespace: state storage (getState, setState, getPreviousState)\n * - RoutesNamespace: route tree operations\n * - RouteLifecycleNamespace: canActivate/canDeactivate guards\n * - PluginsNamespace: plugin lifecycle\n * - NavigationNamespace: navigate\n * - RouterLifecycleNamespace: start, stop, isStarted\n *\n * @internal This class implementation is internal. Use createRouter() instead.\n */\nexport class Router<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n> implements RouterInterface<Dependencies> {\n [key: string]: unknown;\n\n // ============================================================================\n // Namespaces\n // ============================================================================\n\n readonly #options: OptionsNamespace;\n readonly #limits: Limits;\n readonly #dependenciesStore: DependenciesStore<Dependencies>;\n readonly #state: StateNamespace;\n readonly #routes: RoutesNamespace<Dependencies>;\n readonly #routeLifecycle: RouteLifecycleNamespace<Dependencies>;\n readonly #plugins: PluginsNamespace<Dependencies>;\n readonly #navigation: NavigationNamespace;\n readonly #lifecycle: RouterLifecycleNamespace;\n\n readonly #eventBus: EventBusNamespace;\n\n /**\n * Per-instance suppressors for fire-and-forget navigate / start. They log\n * through THIS router's logger (built in the constructor) — so, unlike the\n * former static \"one allocation per class\" closures, they are two closures per\n * instance. Trade accepted with the per-router logger (#724): the logger is\n * per-instance, so its suppressors must be too. The #931 category split is\n * preserved — navigate failures log under \"router.navigate\", start failures\n * under \"router.start\" (a start interceptor throwing a plain Error after\n * next() committed, #763, or a cryptic path TypeError — neither a suppressed\n * RouterError). The log line IS reachable: a subscribeLeave listener that\n * throws rejects navigate() with the original NON-suppressed error, and a\n * Symbol path-param's stringify TypeError is likewise non-suppressed.\n */\n readonly #onSuppressedNavigateError: (error: unknown) => void;\n readonly #onSuppressedStartError: (error: unknown) => void;\n\n // ============================================================================\n // Constructor\n // ============================================================================\n\n /**\n * @param routes - Route definitions\n * @param options - Router options\n * @param dependencies - DI dependencies\n */\n constructor(\n routes: Route<Dependencies>[] = [],\n options: Partial<Options> = {},\n dependencies: Dependencies = {} as Dependencies,\n ) {\n // Extract the logger config WITHOUT mutating the caller's `options` object\n // (#724). `routerOptions` is the logger-stripped view handed to the options\n // pipeline so `logger` never lands in the frozen router options.\n const { logger: loggerConfig, ...routerOptions } = options;\n\n if (loggerConfig) {\n assertLoggerConfig(loggerConfig);\n }\n\n // Per-router logger instance — replaces the former process-global singleton\n // whose configure() leaked across every router in the process, last\n // createRouter winning (#724). Stored on ctx (registerInternals below), so\n // the facade reads getInternals(this).logger; namespaces receive it via\n // their deps at wiring; plugins reach it through getPluginApi(router).logger.\n const logger = new RouterLogger(loggerConfig);\n\n // Per-instance fire-and-forget suppressors (see field declarations): they\n // log through THIS router's logger, so they are built here, not static.\n this.#onSuppressedNavigateError = (error: unknown): void => {\n if (Router.#isExpectedRejection(error)) {\n return;\n }\n\n logger.error(\"router.navigate\", \"Unexpected navigation error\", error);\n };\n this.#onSuppressedStartError = (error: unknown): void => {\n if (Router.#isExpectedRejection(error)) {\n return;\n }\n\n logger.error(\"router.start\", \"Unexpected start error\", error);\n };\n\n // =========================================================================\n // Validate inputs before creating namespaces\n // =========================================================================\n\n // Always validate the caller's options (catches non-object / array inputs)\n OptionsNamespace.validateOptionsIsObject(options);\n\n // Unconditional guard-level validation before creating namespaces\n guardDependencies(dependencies);\n\n // Stryker disable next-line EqualityOperator: equivalent — `>= 0` is always true, but `guardRouteStructure([])` on an empty array is a no-op, so validating an empty list behaves identically to skipping it. (ConditionalExpression stays live: `→false` skips validation of a real route list and is killable.)\n if (routes.length > 0) {\n guardRouteStructure(routes);\n }\n\n // =========================================================================\n // Create Namespaces\n // =========================================================================\n\n this.#options = new OptionsNamespace(routerOptions);\n this.#limits = createLimits(routerOptions.limits);\n this.#dependenciesStore =\n createDependenciesStore<Dependencies>(dependencies);\n this.#state = new StateNamespace();\n this.#routes = new RoutesNamespace<Dependencies>(\n routes,\n deriveMatcherOptions(this.#options.get()),\n logger,\n );\n this.#routeLifecycle = new RouteLifecycleNamespace<Dependencies>();\n this.#plugins = new PluginsNamespace<Dependencies>();\n this.#navigation = new NavigationNamespace();\n this.#lifecycle = new RouterLifecycleNamespace();\n\n // =========================================================================\n // Initialize EventBus\n // =========================================================================\n\n const routerFSM = createRouterFSM();\n\n const emitter = new EventEmitter<RouterEventMap>({\n // Shared per-listener error sink: EventEmitter reports synchronous listener\n // throws here, and EventBusNamespace.subscribe routes an async listener's\n // rejected Promise through the SAME sink (#944) — both land in one place.\n onListenerError: (eventName, error) => {\n logger.error(\"Router\", `Error in listener for ${eventName}:`, error);\n },\n onListenerWarn: (eventName, count) => {\n logger.warn(\n \"router.addEventListener\",\n `Event \"${eventName}\" has ${count} listeners — possible memory leak`,\n );\n },\n });\n\n this.#eventBus = new EventBusNamespace({\n routerFSM,\n emitter,\n // The FSM CANCEL action aborts the in-flight\n // navigation controller via this injected effect — \"FSM CANCEL ⟹\n // controller aborted\" in one place. `#navigation` is constructed above.\n abortController: (reason) => {\n this.#navigation.abortCurrentController(reason);\n },\n });\n\n // =========================================================================\n // Register Internals (WeakMap for plugin/infrastructure access)\n // =========================================================================\n // Registered BEFORE wiring (#1331) so every namespace's deps-closure sees a\n // router already present in the internals registry — `getInternals(router)`\n // never throws during wiring, and guard factories flushed at the end of the\n // constructor see a fully-registered instance.\n\n const interceptorsMap: RouterInternals[\"interceptors\"] = new Map();\n\n registerInternals(this, {\n logger,\n makeState: (name, params, path, meta) =>\n this.#state.makeState(name, params, path, meta),\n // `as unknown as` is required: createBinaryInterceptable returns a\n // non-generic `(a: A, b: B) => R`, but RouterInternals[\"forwardState\"]\n // is declared with a generic parameter `<P extends Params = Params>`,\n // which tsc will not infer from the non-generic source. Sonar S4325\n // misclassifies this as a redundant cast.\n forwardState: createBinaryInterceptable(\n \"forwardState\",\n (name: string, params: Params) =>\n this.#routes.forwardState(name, params),\n interceptorsMap,\n ) as unknown as RouterInternals[\"forwardState\"],\n buildStateResolved: (name, params) =>\n this.#routes.buildStateResolved(name, params),\n matchPath: (path, matchOptions) =>\n this.#routes.matchPath(path, matchOptions),\n getOptions: () => this.#options.get(),\n addEventListener: (eventName, cb) =>\n this.#eventBus.addEventListener(eventName, cb),\n treeChanged: {\n emit: (event) => {\n this.#eventBus.emitTreeChanged(event);\n },\n subscribe: (handler) => this.#eventBus.subscribeTreeChanged(handler),\n listenerCount: () => this.#eventBus.treeChangedListenerCount(),\n isEmitting: () => this.#eventBus.isEmittingTreeChanged(),\n },\n buildPath: createBinaryInterceptable(\n \"buildPath\",\n (route: string, params?: Params) =>\n this.#routes.buildPath(\n route,\n params ?? EMPTY_PARAMS,\n this.#options.get(),\n ),\n interceptorsMap,\n ),\n emitTransitionError: (error) => {\n this.#eventBus.sendFailSafe(undefined, this.#state.get(), error);\n },\n emitTransitionSuccess: (toState, fromState, opts) => {\n this.#eventBus.emitTransitionSuccess(toState, fromState, opts);\n },\n navigateToNotFound: (path) => this.#navigation.navigateToNotFound(path),\n start: createInterceptable(\n \"start\",\n (path: string) => {\n return this.#lifecycle.start(path);\n },\n interceptorsMap,\n ),\n navigateToState: (state, navOpts) => {\n // Plugin-only navigation primitive (#525). Mirrors the same\n // unhandled-rejection suppression and lastSync* bookkeeping used by\n // the public Router.navigate facade so plugin call-sites can\n // fire-and-forget the returned promise (popstate handlers do).\n this.#assertNotReentrant();\n\n const promiseState = this.#navigation.navigateToState(\n state,\n navOpts ?? EMPTY_OPTS,\n );\n\n if (this.#navigation.lastSyncResolved) {\n this.#navigation.lastSyncResolved = false;\n } else if (this.#navigation.lastSyncRejected) {\n this.#navigation.lastSyncRejected = false;\n } else {\n this.#suppressUnhandledRejection(promiseState);\n }\n\n return promiseState;\n },\n interceptors: interceptorsMap,\n setRootPath: (rootPath) => {\n this.#routes.setRootPath(rootPath);\n },\n getRootPath: () => this.#routes.getStore().rootPath,\n getTree: () => this.#routes.getStore().tree,\n isDisposed: () => this.#eventBus.isDisposed(),\n validator: null,\n // Dependencies (issue #172)\n dependenciesGetStore: () => this.#dependenciesStore,\n // Clone support (issue #173)\n getCloneState: () => ({\n options: { ...this.#options.get() },\n dependencies: { ...this.#dependenciesStore.dependencies },\n pluginFactories: this.#plugins.getAll(),\n // `logger` is a const in this constructor's scope (a RouterLogger class\n // instance), so getConfig() yields the resolved config a clone inherits\n // — frozen options don't carry `logger`, so cloneRouter reads it here.\n loggerConfig: logger.getConfig(),\n }),\n routeGetStore: () => this.#routes.getStore(),\n // Cross-namespace state (issue #174)\n getStateName: () => this.#state.get()?.name,\n isTransitioning: () => this.#eventBus.isTransitioning(),\n clearState: () => {\n this.#state.set(undefined);\n },\n setState: (state) => {\n this.#state.set(state);\n },\n routerExtensions: [],\n contextClaimRecords: new Set(),\n hydrationState: null,\n });\n\n // =========================================================================\n // Wire Dependencies\n // =========================================================================\n\n wireNamespaces<Dependencies>({\n router: this,\n options: this.#options,\n limits: this.#limits,\n dependenciesStore: this.#dependenciesStore,\n state: this.#state,\n routes: this.#routes,\n routeLifecycle: this.#routeLifecycle,\n plugins: this.#plugins,\n navigation: this.#navigation,\n lifecycle: this.#lifecycle,\n eventBus: this.#eventBus,\n });\n\n // =========================================================================\n // Bind Public Methods\n // =========================================================================\n // All public methods that access private fields must be bound to preserve\n // `this` context when methods are extracted as references.\n // See: https://github.com/tc39/proposal-bind-operator\n // =========================================================================\n\n // Path & State Building\n this.isActiveRoute = this.isActiveRoute.bind(this);\n this.buildPath = this.buildPath.bind(this);\n\n // State Management\n this.getState = this.getState.bind(this);\n this.getPreviousState = this.getPreviousState.bind(this);\n this.areStatesEqual = this.areStatesEqual.bind(this);\n this.shouldUpdateNode = this.shouldUpdateNode.bind(this);\n\n // Router Lifecycle\n this.isActive = this.isActive.bind(this);\n this.start = this.start.bind(this);\n this.stop = this.stop.bind(this);\n this.dispose = this.dispose.bind(this);\n\n // Route Lifecycle (Guards)\n this.canNavigateTo = this.canNavigateTo.bind(this);\n\n // Plugins\n this.usePlugin = this.usePlugin.bind(this);\n\n // Navigation\n this.navigate = this.navigate.bind(this);\n this.navigateToDefault = this.navigateToDefault.bind(this);\n this.navigateToNotFound = this.navigateToNotFound.bind(this);\n\n // Subscription\n this.subscribe = this.subscribe.bind(this);\n this.subscribeLeave = this.subscribeLeave.bind(this);\n this.isLeaveApproved = this.isLeaveApproved.bind(this);\n\n // =========================================================================\n // Flush initial-route guard factories\n // =========================================================================\n // Deferred out of wiring (#1331): the pending canActivate/canDeactivate\n // factories from initial route definitions are compiled and executed HERE,\n // on the fully-built and bound router — a factory calling read-only methods\n // (`buildPath()`, `isActiveRoute()`, `getState()`) no longer hits a\n // half-assembled instance. Side-effectful calls (`navigate`, `usePlugin`,\n // route-CRUD) stay OUT OF CONTRACT: factories re-execute outside the\n // constructor (cloneRouter re-compiles definition guards per clone;\n // #recompileSlot re-runs a factory after a definition-only clear), so any\n // side effect would duplicate per re-execution — see CLAUDE.md. Runtime\n // add()/replace() compile guards in their own PREPARE phase and never touch\n // these pending maps.\n //\n // Fail-closed on a factory throw: by this point a router reference leaked\n // from an earlier factory is fully operational, while later guards would\n // stay silently unregistered — a fail-open guard bypass. Disposing before\n // the rethrow turns any leaked reference into a ROUTER_DISPOSED-throwing\n // husk (pre-#1331 such a reference was inert because getInternals threw).\n try {\n this.#routes.flushPendingGuards();\n } catch (error) {\n this.dispose();\n\n throw error;\n }\n }\n\n // ============================================================================\n // Path & State Building\n // ============================================================================\n\n isActiveRoute(\n name: string,\n params?: Params,\n strictEquality?: boolean,\n ignoreQueryParams?: boolean,\n ): boolean {\n getInternals(this).validator?.routes.validateIsActiveRouteArgs(\n name,\n params,\n strictEquality,\n ignoreQueryParams,\n );\n\n getInternals(this).validator?.routes.validateRouteName(\n name,\n \"isActiveRoute\",\n );\n\n // Empty string is special case - warn and return false (root node is not a parent)\n if (name === \"\") {\n getInternals(this).logger.warn(\n \"real-router\",\n 'isActiveRoute(\"\") called with empty string. Root node is not considered a parent of any route.',\n );\n\n return false;\n }\n\n return this.#routes.isActiveRoute(\n name,\n params,\n strictEquality,\n ignoreQueryParams,\n );\n }\n\n buildPath(route: string, params?: Params): string {\n const ctx = getInternals(this);\n\n ctx.validator?.routes.validateBuildPathArgs(route);\n ctx.validator?.navigation.validateParams(params, \"buildPath\");\n\n return ctx.buildPath(route, normalizeParams(params));\n }\n\n // ============================================================================\n // State Management (delegated to StateNamespace)\n // ============================================================================\n\n getState<P extends Params = Params>(): State<P> | undefined {\n return this.#state.get<P>();\n }\n\n getPreviousState(): State | undefined {\n return this.#state.getPrevious();\n }\n\n areStatesEqual(\n state1: State | undefined,\n state2: State | undefined,\n ignoreQueryParams = true,\n ): boolean {\n getInternals(this).validator?.state.validateAreStatesEqualArgs(\n state1,\n state2,\n ignoreQueryParams,\n );\n\n return this.#state.areStatesEqual(state1, state2, ignoreQueryParams);\n }\n\n shouldUpdateNode(\n nodeName: string,\n ): (toState: State, fromState?: State) => boolean {\n getInternals(this).validator?.routes.validateShouldUpdateNodeArgs(nodeName);\n\n return RoutesNamespace.shouldUpdateNode(nodeName);\n }\n\n // ============================================================================\n // Router Lifecycle\n // ============================================================================\n\n isActive(): boolean {\n return this.#eventBus.isActive();\n }\n\n start(startPath: string): Promise<State> {\n if (!this.#eventBus.canStart()) {\n return Promise.reject(CACHED_ALREADY_STARTED_ERROR);\n }\n\n getInternals(this).validator?.navigation.validateStartArgs(startPath);\n\n // FSM bookkeeping is split across the facade and RouterLifecycleNamespace by\n // design, NOT a missed consolidation (#940): `sendStart()` runs HERE, before\n // the interceptor chain, so the STARTING window spans the whole start\n // pipeline. A pre-`next()` interceptor throw then unwinds via STARTING →\n // `sendFail`, which emits TRANSITION_ERROR from STARTING (EventBusNamespace\n // FAIL action) for `onTransitionError` plugins. Moving `sendStart()` into the\n // namespace (the interceptor *target*) would skip STARTING on a pre-`next()`\n // throw — the namespace is never reached — silently dropping that\n // TRANSITION_ERROR: a #668 regression. The commit (`completeStart`) lives in\n // the namespace; recovery needs facade state (`#state`, `#lifecycle`), so it\n // stays here in `#unwindFailedStart`.\n this.#eventBus.sendStart();\n\n // Convert sync interceptor throws to rejections so the recovery path is\n // reachable; otherwise the throw escapes synchronously, the FSM is left in\n // STARTING, and the router is permanently bricked (#668).\n let internalStart: Promise<State>;\n\n try {\n const chainResult: unknown = getInternals(this).start(startPath);\n\n // A `start` interceptor that returns without calling next() yields a\n // non-thenable (typically undefined); the `.catch` below would then throw\n // a cryptic `TypeError: ...reading 'catch'` and leave the FSM stuck in\n // STARTING. Reject with an actionable message so recovery unwinds via\n // #unwindFailedStart — the same deferred-crash class as the #939\n // start-path guard (#1411).\n internalStart =\n typeof (chainResult as { then?: unknown } | null | undefined)?.then ===\n \"function\"\n ? (chainResult as Promise<State>)\n : Promise.reject(\n new TypeError(\n \"[router.start] a `start` interceptor returned without calling next(). Every start interceptor must return `next(path)`.\",\n ),\n );\n } catch (syncError: unknown) {\n // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors -- preserve original throw shape from user-provided start interceptor\n internalStart = Promise.reject(syncError);\n }\n\n const promiseState = internalStart.catch((error: unknown) =>\n this.#unwindFailedStart(error),\n );\n\n this.#suppressUnhandledRejection(\n promiseState,\n this.#onSuppressedStartError,\n );\n\n return promiseState;\n }\n\n stop(): this {\n // SendCancelIfPossible → FSM CANCEL → the CANCEL action\n // aborts the in-flight controller (waking the pipeline). No separate abort.\n this.#eventBus.sendCancelIfPossible(this.#state.get());\n\n // `isStarting()` is included (#1185): a stop() while `start()` is parked in\n // an async start-interceptor (FSM STARTING, before `next()`) must cancel the\n // start, not silently no-op. `sendStop()` takes STARTING → IDLE (FSM table),\n // and `RouterLifecycleNamespace.start` re-checks `isIdle()` after the\n // interceptor chain and rejects with TRANSITION_CANCELLED — mirroring the\n // guard-phase behavior (which already cancels from TRANSITION_STARTED).\n if (\n !this.#eventBus.isReady() &&\n !this.#eventBus.isTransitioning() &&\n !this.#eventBus.isStarting()\n ) {\n return this;\n }\n\n this.#lifecycle.stop();\n this.#eventBus.sendStop();\n\n return this;\n }\n\n dispose(): void {\n // Stryker disable next-line BlockStatement: equivalent — emptying the early-return re-runs the dispose body on a 2nd call, but it is fully idempotent (FSM `send(DISPOSE)` no-ops from DISPOSED, `disposeAll()` already cleared `#unsubscribes`, every clear is idempotent). (ConditionalExpression stays live: `→true` always-returns and never disposes = killed.)\n if (this.#eventBus.isDisposed()) {\n return;\n }\n\n // the FSM CANCEL action aborts the in-flight controller.\n this.#eventBus.sendCancelIfPossible(this.#state.get());\n\n if (this.#eventBus.isReady() || this.#eventBus.isTransitioning()) {\n this.#lifecycle.stop();\n this.#eventBus.sendStop();\n }\n\n this.#eventBus.sendDispose();\n this.#eventBus.clearAll();\n\n this.#plugins.disposeAll();\n\n // Safety net: clean up extensions plugins failed to remove in teardown\n const ctx = getInternals(this);\n\n for (const extension of ctx.routerExtensions) {\n for (const key of extension.keys) {\n delete (this as Record<string, unknown>)[key];\n }\n }\n\n ctx.routerExtensions.length = 0;\n\n // Safety net: release context namespace claims plugins failed to release in teardown\n ctx.contextClaimRecords.clear();\n\n // Safety net: drop interceptors plugins failed to remove in teardown (#1199).\n // The third per-plugin registration channel — symmetric with routerExtensions\n // / contextClaimRecords above. `buildPath` is not method-swapped by dispose\n // and reads this Map live, so a leaked interceptor would otherwise still run\n // on the disposed router.\n ctx.interceptors.clear();\n\n this.#routes.clearRoutes();\n this.#routeLifecycle.clearAll();\n this.#state.reset();\n this.#dependenciesStore.dependencies = Object.create(\n null,\n ) as Partial<Dependencies>;\n\n this.#markDisposed();\n }\n\n // ============================================================================\n // Route Lifecycle (Guards)\n // ============================================================================\n\n canNavigateTo(name: string, params?: Params): boolean {\n const ctx = getInternals(this);\n\n ctx.validator?.routes.validateRouteName(name, \"canNavigateTo\");\n ctx.validator?.navigation.validateParams(params, \"canNavigateTo\");\n\n if (!this.#routes.hasRoute(name)) {\n return false;\n }\n\n const { name: resolvedName, params: resolvedParams } = ctx.forwardState(\n name,\n params ?? {},\n );\n\n // Build `toState` exactly as `buildNavigateState` does — WITH route-meta and\n // normalized params — so `getTransitionPath` takes its STANDARD PATH and\n // trims the shared ancestor, mirroring navigate's guard set (#970). A\n // meta-less `toState` makes both sides meta-less (the committed `getState()`\n // carries no meta after a path-matched `start()`), so `getTransitionPath`\n // takes FAST PATH 3 and (de)activates the WHOLE chain incl. shared ancestors\n // → false-negative (\"Link disabled though the click would succeed\").\n // `normalizeParams` also aligns the params guards observe with navigate's.\n // `skipFreeze` (5th arg) mirrors the navigate guard phase, where guards see\n // an unfrozen, transition-less `toState` (freeze happens later in\n // `completeTransition`).\n //\n // A capability predicate must answer, not throw: if the target path can't be\n // built from these params (e.g. a required path param is missing), the route\n // is simply unreachable with this input — return `false` rather than letting\n // `buildPath` throw (#725).\n let toState: State;\n\n try {\n const normalizedParams = normalizeParams(resolvedParams);\n const meta = this.#routes.getMetaForState(resolvedName);\n const path = ctx.buildPath(resolvedName, normalizedParams);\n\n toState = this.#state.makeState(\n resolvedName,\n normalizedParams,\n path,\n meta,\n true,\n );\n } catch {\n return false;\n }\n\n const fromState = this.#state.get();\n\n const { toDeactivate, toActivate } = getTransitionPath(toState, fromState);\n\n return this.#routeLifecycle.canNavigateTo(\n toDeactivate,\n toActivate,\n toState,\n fromState,\n );\n }\n\n // ============================================================================\n // Plugins\n // ============================================================================\n\n usePlugin(\n ...plugins: (PluginFactory<Dependencies> | false | null | undefined)[]\n ): Unsubscribe {\n // Post-dispose guard, mirroring #946 for subscribe/subscribeLeave. A\n // reference captured before dispose() (`const up = router.usePlugin`)\n // bypasses the #markDisposed method swap, so the swap alone is not enough:\n // without this, the factory would run on a disposed router (real side\n // effects), listeners would land in the cleared emitter, and teardown would\n // never fire — a silent zombie plugin (#1196).\n if (this.#eventBus.isDisposed()) {\n throw new RouterError(errorCodes.ROUTER_DISPOSED);\n }\n\n const filtered = plugins.filter(Boolean) as PluginFactory<Dependencies>[];\n\n if (filtered.length === 0) {\n return () => {};\n }\n\n const ctx = getInternals(this);\n\n ctx.validator?.plugins.validatePluginLimit(\n this.#plugins.count(),\n this.#limits,\n );\n for (const plugin of filtered) {\n // `getAll()` sits inside the optional-chain argument on purpose: with no\n // validator installed (production default) the `?.` short-circuits and the\n // array is never allocated. Hoisting it out would either allocate on the\n // no-validator hot path or push the dev-only branch out of coverage.\n ctx.validator?.plugins.validateNoDuplicatePlugins(\n plugin,\n this.#plugins.getAll(),\n );\n }\n\n return this.#plugins.use(...filtered);\n }\n\n // ============================================================================\n // Subscription (backed by EventEmitter)\n // ============================================================================\n\n subscribe(listener: SubscribeFn): Unsubscribe {\n EventBusNamespace.validateSubscribeListener(listener);\n\n return this.#eventBus.subscribe(listener);\n }\n\n subscribeLeave(listener: LeaveFn): Unsubscribe {\n EventBusNamespace.validateSubscribeLeaveListener(listener);\n\n return this.#eventBus.subscribeLeave(listener);\n }\n\n isLeaveApproved(): boolean {\n return this.#eventBus.isLeaveApproved();\n }\n\n // ============================================================================\n // Navigation\n // ============================================================================\n\n navigate(\n routeName: string,\n routeParams?: Params,\n options?: NavigationOptions,\n ): Promise<State> {\n this.#assertNotReentrant();\n\n const ctx = getInternals(this);\n\n ctx.validator?.navigation.validateNavigateArgs(routeName);\n ctx.validator?.navigation.validateParams(routeParams, \"navigate\");\n\n const opts = options ?? EMPTY_OPTS;\n\n ctx.validator?.navigation.validateNavigationOptions(opts, \"navigate\");\n\n const promiseState = this.#navigation.navigate(\n routeName,\n routeParams ?? EMPTY_PARAMS,\n opts,\n );\n\n if (this.#navigation.lastSyncResolved) {\n this.#navigation.lastSyncResolved = false;\n } else if (this.#navigation.lastSyncRejected) {\n // Cached rejection — already pre-suppressed at module load, skip .catch()\n this.#navigation.lastSyncRejected = false;\n } else {\n this.#suppressUnhandledRejection(promiseState);\n }\n\n return promiseState;\n }\n\n navigateToDefault(options?: NavigationOptions): Promise<State> {\n this.#assertNotReentrant();\n\n const ctx = getInternals(this);\n\n ctx.validator?.navigation.validateNavigateToDefaultArgs(options);\n\n const opts = options ?? EMPTY_OPTS;\n\n ctx.validator?.navigation.validateNavigationOptions(\n opts,\n \"navigateToDefault\",\n );\n\n const promiseState = this.#navigation.navigateToDefault(opts);\n\n if (this.#navigation.lastSyncResolved) {\n this.#navigation.lastSyncResolved = false;\n } else if (this.#navigation.lastSyncRejected) {\n this.#navigation.lastSyncRejected = false;\n } else {\n this.#suppressUnhandledRejection(promiseState);\n }\n\n return promiseState;\n }\n\n navigateToNotFound(path?: string): State {\n this.#assertNotReentrant();\n\n if (!this.#eventBus.isActive()) {\n throw new RouterError(errorCodes.ROUTER_NOT_STARTED);\n }\n\n if (path !== undefined && typeof path !== \"string\") {\n throw new TypeError(\n `[router.navigateToNotFound] path must be a string, got ${typeof path}`,\n );\n }\n\n if (path !== undefined) {\n return this.#navigation.navigateToNotFound(path);\n }\n\n // #1172: a path-less call derives the default path from the committed state.\n // During the two-phase start window the router is active (`isActive()` true)\n // while `getState()` is still undefined, so throw an actionable RouterError\n // instead of a cryptic `TypeError` from dereferencing the absent state —\n // same class as the #939 always-on invariant guards.\n const current = this.#state.get();\n\n if (current === undefined) {\n throw new RouterError(errorCodes.ROUTER_NOT_STARTED, {\n message:\n \"[router.navigateToNotFound] cannot derive the path before the start navigation commits — pass an explicit path\",\n });\n }\n\n return this.#navigation.navigateToNotFound(current.path);\n }\n\n /**\n * Classifies a fire-and-forget rejection as an EXPECTED outcome that must\n * stay silent (no log). Shared by the navigate and start suppressors so the\n * suppression contract lives in one place.\n *\n * A suppressed RouterError code is a normal caller-owned navigation result\n * (a guard block, SAME_STATES, ROUTER_NOT_STARTED, …) — see\n * SUPPRESSED_ERROR_CODES (#721).\n *\n * The #945 RecursionDepthError carve-out is gone: a reentrant navigate() from a\n * listener can no longer self-feed — it throws REENTRANT_NAVIGATION\n * synchronously at the facade (RFC navigation-cancellation-unification §4), so\n * navigate()'s promise never rejects with a recursion error (re-entrant emits\n * are coalesced at the emitter, #1033; reentrant route-CRUD throws\n * REENTRANT_TREE_MUTATION to the CRUD caller, #1032 — not through a navigate\n * promise).\n */\n static #isExpectedRejection(error: unknown): boolean {\n return (\n error instanceof RouterError && SUPPRESSED_ERROR_CODES.has(error.code)\n );\n }\n\n /**\n * Fire-and-forget safety: prevents unhandled rejection warnings when\n * navigate/navigateToDefault/start is called without await. Expected errors\n * are silently suppressed; unexpected ones are logged under `onSuppressed`'s\n * category — navigate by default; start() passes #onSuppressedStartError so\n * its failures are logged as \"router.start\", not \"router.navigate\" (#931).\n */\n #suppressUnhandledRejection(\n promise: Promise<State>,\n onSuppressed: (error: unknown) => void = this.#onSuppressedNavigateError,\n ): void {\n promise.catch(onSuppressed);\n }\n\n /**\n * Rejects a synchronous reentrant navigation — `navigate` /\n * `navigateToDefault` / `navigateToState` / `navigateToNotFound` called from\n * inside a transition-event listener while a transition is being dispatched\n * (RFC navigation-cancellation-unification §4). Throws synchronously: inside a\n * listener the emit's `onListenerError` isolation surfaces it (visible,\n * non-fatal); a DEFERRED (async / microtask) navigate from a listener runs\n * after dispatch settles and is allowed. Always-on core invariant guard (not\n * validator-gated).\n */\n #assertNotReentrant(): void {\n if (this.#eventBus.isProcessing()) {\n throw new RouterError(errorCodes.REENTRANT_NAVIGATION);\n }\n }\n\n /**\n * Settles the FSM after a failed start pipeline, then re-throws so the\n * rejection still surfaces to the caller. Three cases, by what the pipeline\n * reached before throwing:\n *\n * - **Pre-commit, READY** (`isReady()` and no committed state): an interceptor\n * threw after `completeStart()` reached READY but before any state committed\n * (e.g. an activation guard blocked the start navigation) — return READY →\n * IDLE via `stop()` so the router is reusable.\n * - **Pre-commit, STARTING** (`isStarting()`): the pipeline threw before\n * `completeStart()` — a sync interceptor throw before `next()`, or a throw\n * inside the namespace before commit — so unwind STARTING → IDLE via\n * `sendFail`, which also emits TRANSITION_ERROR from STARTING (#668).\n * - **Post-commit, READY with committed state** (neither branch fires): a\n * loader/interceptor threw AFTER `navigateToState` committed and emitted\n * TRANSITION_SUCCESS (the SSR/RSC loader window). Keep the committed state —\n * rolling back would retract an observed success (\"phantom success\", #763);\n * the error still surfaces via the re-throw.\n */\n #unwindFailedStart(error: unknown): never {\n if (this.#eventBus.isReady() && this.#state.get() === undefined) {\n this.#lifecycle.stop();\n this.#eventBus.sendStop();\n } else if (this.#eventBus.isStarting()) {\n this.#eventBus.sendFail(undefined, undefined, error);\n }\n\n throw error;\n }\n\n #markDisposed(): void {\n this.navigate = throwDisposed;\n this.navigateToDefault = throwDisposed;\n this.navigateToNotFound = throwDisposed;\n this.start = throwDisposed;\n this.stop = throwDisposed;\n this.usePlugin = throwDisposed;\n\n this.subscribe = throwDisposed;\n this.subscribeLeave = throwDisposed;\n this.canNavigateTo = throwDisposed;\n }\n}\n\nfunction throwDisposed(): never {\n throw new RouterError(errorCodes.ROUTER_DISPOSED);\n}\n\n/**\n * Derives CreateMatcherOptions from router Options.\n * Maps core option names to matcher option names.\n */\nfunction deriveMatcherOptions(\n options: Readonly<Options>,\n): CreateMatcherOptions {\n return {\n strictTrailingSlash: options.trailingSlash === \"strict\",\n caseSensitive: options.caseSensitive,\n strictQueryParams: options.queryParamsMode === \"strict\",\n urlParamsEncoding: options.urlParamsEncoding,\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n queryParams: options.queryParams!,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"Router-DGUed2F4.js","names":["DEFAULT_LIMITS","EMPTY_PARAMS","#callbacks","#dispatching","#onListenerError","#onListenerWarn","#limits","#warnedEvents","#invokeIsolated","#callListener","#config","#currentThreshold","#writeLog","#writeToConsole","#invokeCallback","#inCallback","#reportError","DEFAULT_LIMITS","EMPTY_PARAMS","DEFAULT_LIMITS","LONE_SURROGATE_RGX","splitPathSegments","parseSegment","parseSegment","EMPTY_PARAM_META","splitPathSegments","parseSegment","INVALID_QUERY_NAME_RGX","#options","#root","#routesByName","#staticCache","#prepared","#caseSensitive","#decode","#rootQueryParams","#preparePath","#checkTrailingSlash","#buildResult","#traverse","#decodeParams","#buildUrlPath","#applyTrailingSlash","#buildQueryStringForBuild","#scanPath","#scanTruncated","#mergeQueryParams","#traverseFrom","#matchSplat","buildParamMeta","EMPTY_PARAM_META","encodeValue","#options","#frozenState","#previousState","#deps","EMPTY_PARAMS","PLUGINS_CONST","EVENTS_CONST","#plugins","#unsubscribes","#deps","#startPlugin","#deduplicateBatch","#definitionActivateFactories","#externalActivateFactories","#definitionDeactivateFactories","#externalDeactivateFactories","#canDeactivateFunctions","#canActivateFunctions","#functionsTuple","#deps","#getFactoryMaps","#registerHandler","#clearGuard","#recompileSlot","#checkGuardSync","#store","#deps","#getBuildPathOptions","#mergeDefaultParams","#resolveDynamicForward","#cachedBuildPathOpts","#cachedOptionsSource","#deps","#executeNavigation","#abortPreviousNavigation","EMPTY_PARAMS","#currentController","#navigationId","#handleNoGuardsLeave","#finishAsyncNavigation","#cleanupController","#handleNavigateError","#deps","#context","#transitions","#listeners","#state","#currentTransitions","#actions","#listenerCount","#fsm","#emitter","#abortController","#leaveListeners","#currentToState","#setupFSMActions","#dispatchDepth","#pendingToState","#pendingFromState","#pendingError","#pendingCancelReason","#checkListenerThreshold","#getValidator","#emitPendingError","getInternals","#options","#limits","#dependenciesStore","#state","#routes","#routeLifecycle","#plugins","#navigation","#lifecycle","#eventBus","#onSuppressedNavigateError","#onSuppressedStartError","#isExpectedRejection","createBinaryInterceptable","EMPTY_PARAMS","createInterceptable","#assertNotReentrant","#suppressUnhandledRejection","getInternals","#unwindFailedStart","#markDisposed"],"sources":["../../src/constants.ts","../../src/foundation/event-emitter/EventEmitter.ts","../../src/foundation/logger/constants.ts","../../src/foundation/logger/RouterLogger.ts","../../src/guards.ts","../../src/helpers.ts","../../src/namespaces/DependenciesNamespace/dependenciesStore.ts","../../src/engine/builder/buildTree.ts","../../src/engine/path-matcher/encoding.ts","../../src/engine/path-matcher/pathUtils.ts","../../src/engine/path-matcher/percentEncoding.ts","../../src/engine/path-matcher/registration/context.ts","../../src/engine/path-matcher/registration/buildParts.ts","../../src/engine/path-matcher/registration/errors.ts","../../src/engine/path-matcher/registration/trieNodes.ts","../../src/engine/path-matcher/registration/trie.ts","../../src/engine/path-matcher/registration/index.ts","../../src/engine/path-matcher/SegmentMatcher.ts","../../src/engine/builder/computeCaches.ts","../../src/engine/builder/createRouteTree.ts","../../src/engine/operations/routeTreeToDefinitions.ts","../../src/engine/search-params/decode.ts","../../src/engine/search-params/utils.ts","../../src/engine/search-params/strategies/array.ts","../../src/engine/search-params/strategies/boolean.ts","../../src/engine/search-params/strategies/null.ts","../../src/engine/search-params/strategies/number.ts","../../src/engine/search-params/strategies/index.ts","../../src/engine/search-params/encode.ts","../../src/engine/search-params/searchParams.ts","../../src/engine/createMatcher.ts","../../src/namespaces/OptionsNamespace/constants.ts","../../src/namespaces/OptionsNamespace/helpers.ts","../../src/namespaces/OptionsNamespace/validators.ts","../../src/namespaces/OptionsNamespace/OptionsNamespace.ts","../../src/namespaces/StateNamespace/helpers.ts","../../src/stateMetaStore.ts","../../src/namespaces/StateNamespace/StateNamespace.ts","../../src/namespaces/PluginsNamespace/constants.ts","../../src/namespaces/PluginsNamespace/validators.ts","../../src/namespaces/PluginsNamespace/PluginsNamespace.ts","../../src/namespaces/RouteLifecycleNamespace/RouteLifecycleNamespace.ts","../../src/namespaces/RoutesNamespace/constants.ts","../../src/namespaces/RoutesNamespace/helpers.ts","../../src/namespaces/RoutesNamespace/forwardChain.ts","../../src/namespaces/RoutesNamespace/routesStore.ts","../../src/transitionPath.ts","../../src/namespaces/RoutesNamespace/RoutesNamespace.ts","../../src/RouterError.ts","../../src/namespaces/NavigationNamespace/constants.ts","../../src/namespaces/NavigationNamespace/transition/completeTransition.ts","../../src/namespaces/NavigationNamespace/transition/errorHandling.ts","../../src/namespaces/NavigationNamespace/transition/guardPhase.ts","../../src/namespaces/NavigationNamespace/NavigationNamespace.ts","../../src/namespaces/RouterLifecycleNamespace/RouterLifecycleNamespace.ts","../../src/foundation/fsm/fsm.ts","../../src/routerFSM.ts","../../src/namespaces/EventBusNamespace/EventBusNamespace.ts","../../src/namespaces/RouterLifecycleNamespace/constants.ts","../../src/wiring/wireNamespaces.ts","../../src/Router.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});\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\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","import type {\n EventEmitterLimits,\n EventEmitterOptions,\n Unsubscribe,\n} from \"./types\";\n\nconst DEFAULT_LIMITS: EventEmitterLimits = {\n maxListeners: 0,\n warnListeners: 0,\n};\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\ntype AnyCallback = Function;\n\n/**\n * Generic typed event emitter with listener limits, duplicate detection,\n * re-entrancy coalescing, and per-listener error isolation.\n *\n * All limits are opt-in via constructor options.\n */\nexport class EventEmitter<TEventMap extends Record<string, unknown[]>> {\n readonly #callbacks = new Map<string, Set<AnyCallback>>();\n // Names currently being dispatched. A re-entrant `emit` of an event already\n // on this set is coalesced to a no-op (see `emit`), so an event can never\n // re-enter its own dispatch — recursion is structurally impossible (depth ≤ 1)\n // with no depth bound and no stack-overflow path (#1033).\n readonly #dispatching = new Set<string>();\n #warnedEvents: Set<string> | null = null;\n #limits: EventEmitterLimits = DEFAULT_LIMITS;\n readonly #onListenerError:\n ((eventName: string, error: unknown) => void) | null;\n readonly #onListenerWarn: ((eventName: string, count: number) => void) | null;\n\n constructor(options?: EventEmitterOptions) {\n if (options?.limits) {\n this.#limits = options.limits;\n }\n\n this.#onListenerError = options?.onListenerError ?? null;\n this.#onListenerWarn = options?.onListenerWarn ?? null;\n }\n\n /**\n * Validates that a callback is a function.\n */\n static validateCallback(\n cb: unknown,\n eventName: string,\n // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n ): asserts cb is Function {\n if (typeof cb !== \"function\") {\n throw new TypeError(\n `Expected callback to be a function for event ${eventName}`,\n );\n }\n }\n\n /**\n * Replaces current limits with the provided limits.\n */\n setLimits(limits: EventEmitterLimits): void {\n this.#limits = limits;\n }\n\n /**\n * Adds an event listener and returns an unsubscribe function.\n * Throws on duplicate listeners or when maxListeners is reached.\n *\n * Registration is atomic (validate-before-mutate, #1358): every rejection\n * check runs against the CURRENT record (read once, never created early), the\n * advisory warn hook runs before any mutation, and the record is created +\n * the listener added only after all checks pass. So a throw — a rejected\n * limit, or a throwing `onListenerWarn` — leaves NO side-effect behind: no\n * orphaned empty record (#1167) and no burnt warn latch (#1168).\n */\n on<E extends keyof TEventMap & string>(\n eventName: E,\n cb: (...args: TEventMap[E]) => void,\n ): Unsubscribe {\n const existing = this.#callbacks.get(eventName);\n const size = existing?.size ?? 0;\n\n if (existing?.has(cb)) {\n throw new Error(`Duplicate listener for \"${eventName}\"`);\n }\n\n const { maxListeners, warnListeners } = this.#limits;\n\n // Enforce the hard limit before warning, so onListenerWarn never fires for\n // a registration that then throws (the warnListeners === maxListeners case).\n if (maxListeners !== 0 && size >= maxListeners) {\n throw new Error(\n `Listener limit (${maxListeners}) reached for \"${eventName}\"`,\n );\n }\n\n // Warn at most once per emitter+event, using the PRE-add size. The hook is\n // invoked first and the latch set only after it returns without throwing, so\n // a throwing hook fails the registration atomically and leaves the latch\n // unspent — the next (W+1)th registration warns as documented (#1168). The\n // latch keeps the advisory hint \"exactly once\" across off/on churn around\n // the threshold; reset by clearAll() or by removing the last listener.\n if (\n warnListeners !== 0 &&\n size === warnListeners &&\n this.#onListenerWarn !== null\n ) {\n this.#warnedEvents ??= new Set();\n\n if (!this.#warnedEvents.has(eventName)) {\n this.#onListenerWarn(eventName, warnListeners);\n this.#warnedEvents.add(eventName);\n }\n }\n\n // Mutate last — create the record only now, so a rejected registration\n // above never strands an empty record (#1167).\n let set = existing;\n\n if (set === undefined) {\n set = new Set();\n this.#callbacks.set(eventName, set);\n }\n\n set.add(cb);\n\n return () => {\n this.off(eventName, cb);\n };\n }\n\n /**\n * Removes an event listener.\n */\n off<E extends keyof TEventMap & string>(\n eventName: E,\n cb: (...args: TEventMap[E]) => void,\n ): void {\n const set = this.#callbacks.get(eventName);\n\n if (!set) {\n return;\n }\n\n set.delete(cb);\n\n if (set.size === 0) {\n // Release per-event records once the last listener is gone, so consumers\n // with dynamic event names don't accumulate empty Sets unbounded\n // (listenerCount stays 0 either way, masking the growth). See #750.\n this.#callbacks.delete(eventName);\n this.#warnedEvents?.delete(eventName);\n }\n }\n\n /**\n * Emits an event, calling all registered listeners with the provided args.\n *\n * Uses snapshot iteration — listeners added/removed during emit don't affect\n * the current invocation. Per-listener errors are caught and reported via the\n * `onListenerError` callback; other listeners still run.\n *\n * Re-entrant emit is coalesced: emitting an event that is already being\n * dispatched (a listener that synchronously re-emits the same event) is a\n * no-op, so dispatch never recurses into itself (#1033).\n *\n * Uses explicit params instead of rest params to avoid V8 array materialization.\n * Extra undefined args are harmless — JS functions ignore extra arguments.\n */\n emit(\n eventName: keyof TEventMap & string,\n arg1?: unknown,\n arg2?: unknown,\n arg3?: unknown,\n arg4?: unknown,\n ): void {\n const set = this.#callbacks.get(eventName);\n\n if (!set || set.size === 0) {\n return;\n }\n\n // Coalesce a re-entrant emit of an in-flight event (depth ≤ 1, #1033).\n if (this.#dispatching.has(eventName)) {\n return;\n }\n\n // arguments.length is O(1) in V8 strict mode — no deopt\n const argc = arguments.length - 1;\n\n this.#dispatching.add(eventName);\n\n try {\n // Single-listener fast path — skip the [...set] snapshot allocation.\n if (set.size === 1) {\n const [cb] = set;\n\n this.#invokeIsolated(eventName, cb, argc, arg1, arg2, arg3, arg4);\n } else {\n const listeners = [...set];\n\n for (const cb of listeners) {\n this.#invokeIsolated(eventName, cb, argc, arg1, arg2, arg3, arg4);\n }\n }\n } finally {\n this.#dispatching.delete(eventName);\n }\n }\n\n /**\n * Removes all listeners and resets the warn latch.\n *\n * Does NOT touch `#dispatching`: the in-flight coalesce guard is owned by the\n * active `emit` frame (added when dispatch starts, self-released in that\n * frame's `finally`). Clearing it here would lift the guard for a live frame\n * when `clearAll()` runs from inside a listener, so a re-entrant same-event\n * emit would no longer coalesce and would re-enter — violating the depth-≤-1\n * contract (#1164). The guard self-releases; `clearAll()` has no business\n * sweeping state owned by active emit frames.\n */\n clearAll(): void {\n this.#callbacks.clear();\n this.#warnedEvents = null;\n }\n\n /**\n * Returns the number of listeners for the given event.\n */\n listenerCount(eventName: keyof TEventMap & string): number {\n return this.#callbacks.get(eventName)?.size ?? 0;\n }\n\n /**\n * Returns whether the given event is currently being dispatched (an `emit`\n * for it is on the stack). Single source of truth for \"is this event\n * in-flight\" — consumers read it to reject re-entrant operations that would\n * trigger such an emit (the emit itself would be coalesced regardless).\n */\n isDispatching(eventName: keyof TEventMap & string): boolean {\n return this.#dispatching.has(eventName);\n }\n\n // ===========================================================================\n // Private methods\n // ===========================================================================\n\n /**\n * Calls a listener with the correct number of arguments.\n * Dispatches by argc to preserve exact call semantics.\n */\n #invokeIsolated(\n eventName: keyof TEventMap & string,\n cb: AnyCallback,\n argc: number,\n arg1: unknown,\n arg2: unknown,\n arg3: unknown,\n arg4: unknown,\n ): void {\n try {\n const result = this.#callListener(cb, argc, arg1, arg2, arg3, arg4);\n\n // A listener typed `=> void` may still return a Promise at runtime (an\n // async hook or any-cast misuse). The sync `catch` below cannot see its\n // rejection, so route it to the same `#onListenerError` sink — otherwise\n // it escapes as a Node `unhandledRejection` (fatal under\n // `--unhandled-rejections=strict`, the Node 22+ default). Centralised here\n // so every listener kind (plugin hooks, `subscribe`, …) is isolated\n // symmetrically (#1412; `subscribe`'s per-site #944 wrapper folds in).\n if (\n result !== null &&\n result !== undefined &&\n typeof (result as PromiseLike<unknown>).then === \"function\"\n ) {\n Promise.resolve(result as PromiseLike<unknown>).catch(\n (error: unknown) => {\n this.#onListenerError?.(eventName, error);\n },\n );\n }\n } catch (error) {\n this.#onListenerError?.(eventName, error);\n }\n }\n\n #callListener(\n cb: AnyCallback,\n argc: number,\n arg1: unknown,\n arg2: unknown,\n arg3: unknown,\n arg4: unknown,\n ): unknown {\n switch (argc) {\n case 0: {\n return (cb as () => unknown)();\n }\n case 1: {\n return (cb as (a: unknown) => unknown)(arg1);\n }\n case 2: {\n return (cb as (a: unknown, b: unknown) => unknown)(arg1, arg2);\n }\n case 3: {\n return (cb as (a: unknown, b: unknown, c: unknown) => unknown)(\n arg1,\n arg2,\n arg3,\n );\n }\n default: {\n return (\n cb as (a: unknown, b: unknown, c: unknown, d: unknown) => unknown\n )(arg1, arg2, arg3, arg4);\n }\n }\n }\n\n // (record creation is inlined into `on()` so a rejected registration never\n // creates one — see the atomicity note there, #1167/#1358.)\n}\n","import type { LogLevel, LogLevelConfig } from \"../../types\";\n\n/**\n * Numeric mapping for log message severity levels.\n *\n * Maps each severity level to a numeric value for threshold comparison.\n * Lower values = lower severity, higher values = higher severity.\n *\n * Used internally to determine if a message should be filtered based on\n * the configured threshold level.\n *\n * Mapping:\n * - `log`: 0 (lowest severity - informational)\n * - `warn`: 1 (medium severity - warnings)\n * - `error`: 2 (highest severity - critical errors)\n *\n * @example\n * ```ts\n * const messageLevel = LOG_LEVELS['warn']; // 1\n * const threshold = 2; // error-only\n * const shouldFilter = messageLevel < threshold; // true (warn is filtered)\n * ```\n *\n * @internal This is used for internal filtering logic\n */\nexport const LOG_LEVELS: Record<LogLevel, number> = Object.freeze({\n log: 0,\n warn: 1,\n error: 2,\n});\n\n/**\n * Numeric thresholds for logger configuration levels.\n *\n * Maps each configuration level to a minimum threshold value.\n * Messages with a severity level below this threshold are filtered out.\n *\n * Threshold logic:\n * - A message is shown if: `LOG_LEVELS[messageLevel] >= LEVEL_CONFIGS[configLevel]`\n * - Higher threshold value = stricter filtering = fewer messages shown\n *\n * Mapping:\n * - `all`: 0 (no filtering - show everything)\n * - Shows: log (0), warn (1), error (2) ✓\n * - `warn-error`: 1 (filter log messages)\n * - Shows: warn (1), error (2) ✓\n * - Filters: log (0) ✗\n * - `error-only`: 2 (filter log and warn messages)\n * - Shows: error (2) ✓\n * - Filters: log (0), warn (1) ✗\n * - `none`: 3 (filter all messages - complete silence)\n * - Filters: log (0), warn (1), error (2) ✗\n *\n * @example\n * ```ts\n * // Configuration: warn-error\n * const threshold = LEVEL_CONFIGS['warn-error']; // 1\n *\n * // Check if 'log' message should be shown\n * LOG_LEVELS['log'] >= threshold // 0 >= 1 = false (filtered)\n *\n * // Check if 'warn' message should be shown\n * LOG_LEVELS['warn'] >= threshold // 1 >= 1 = true (shown)\n *\n * // Check if 'error' message should be shown\n * LOG_LEVELS['error'] >= threshold // 2 >= 1 = true (shown)\n * ```\n *\n * @internal This is used for internal threshold comparison\n */\nexport const LEVEL_CONFIGS: Record<LogLevelConfig, number> = Object.freeze({\n all: 0,\n \"warn-error\": 1,\n \"error-only\": 2,\n none: 3,\n});\n","// packages/core/src/foundation/logger/RouterLogger.ts\n\nimport { LOG_LEVELS, LEVEL_CONFIGS } from \"./constants\";\n\nimport type {\n LogLevel,\n LoggerConfig,\n LogLevelConfig,\n LogCallback,\n} from \"../../types\";\n\n/**\n * Internal config type with required callbackIgnoresLevel\n * (always initialized to false)\n */\ninterface InternalLoggerConfig {\n level: LogLevelConfig;\n callback?: LogCallback | undefined;\n callbackIgnoresLevel: boolean;\n}\n\n/**\n * Logger class for centralized logging with configurable levels and callbacks.\n *\n * Features:\n * - Three log levels: log, warn, error\n * - Configurable threshold filtering (all, warn-error, error-only, none)\n * - Optional callback for custom log processing\n * - Callback can optionally ignore level threshold\n * - Context-based message formatting\n *\n * @example\n * ```ts\n * import { logger } from './Logger';\n *\n * // Configure logger\n * logger.configure({ level: 'warn-error' });\n *\n * // Use logger\n * logger.log('Router', 'Navigation started'); // Won't show (below threshold)\n * logger.warn('Router', 'Deprecated API used'); // Will show\n * ```\n */\nexport class RouterLogger {\n /** Internal configuration storage using private field */\n readonly #config: InternalLoggerConfig = {\n level: \"all\",\n callbackIgnoresLevel: false,\n };\n\n /** Cached numeric threshold value for performance (avoids repeated lookups) */\n #currentThreshold = 0;\n\n /**\n * Re-entrancy guard: true while a user callback is executing. Prevents a\n * callback that itself calls `logger.*` from recursing back through\n * `#invokeCallback` (which would otherwise spin ~5.9k deep until a swallowed\n * RangeError, see #791). Console output is unaffected.\n */\n #inCallback = false;\n\n /**\n * @param config - Optional initial configuration (level / callback /\n * callbackIgnoresLevel), applied once at construction.\n *\n * Each router owns its own `RouterLogger` instance, built from\n * `options.logger` in the `Router` constructor. This replaces the former\n * process-global singleton whose `configure()` leaked across every router in\n * the process — the last `createRouter` won (#724).\n */\n constructor(config?: Partial<LoggerConfig>) {\n if (config) {\n this.configure(config);\n }\n }\n\n /**\n * Configures the logger with new settings.\n *\n * @param config - Partial configuration to merge with existing config\n * @param config.level - Minimum log level to output ('all' | 'warn-error' | 'error-only' | 'none')\n * @param config.callback - Optional callback function to receive log messages\n * @param config.callbackIgnoresLevel - If true, callback receives all messages regardless of level\n *\n * @example\n * ```ts\n * // Set minimum level to warnings\n * logger.configure({ level: 'warn-error' });\n *\n * // Add custom callback that ignores level\n * logger.configure({\n * callback: (level, context, message) => {\n * sendToAnalytics({ level, context, message });\n * },\n * callbackIgnoresLevel: true\n * });\n * ```\n */\n configure(config: Partial<LoggerConfig>): void {\n // Read each field ONCE into a local — an unstable getter must not be re-read\n // between validation and storage: re-reading could pass validation with a\n // valid level and then store a later, unvalidated one, disabling the\n // threshold filter (a TOCTOU, #1162).\n const level = config.level;\n\n if (level !== undefined) {\n // Validate that the provided level is a valid configuration level\n if (!Object.hasOwn(LEVEL_CONFIGS, level)) {\n throw new Error(\n `Invalid log level: \"${level}\". Valid levels are: ${Object.keys(LEVEL_CONFIGS).join(\", \")}`,\n );\n }\n\n this.#config.level = level;\n this.#currentThreshold = LEVEL_CONFIGS[level];\n }\n if (Object.hasOwn(config, \"callback\")) {\n this.#config.callback = config.callback;\n }\n\n const callbackIgnoresLevel = config.callbackIgnoresLevel;\n\n if (callbackIgnoresLevel !== undefined) {\n this.#config.callbackIgnoresLevel = callbackIgnoresLevel;\n }\n }\n\n /**\n * Returns the current logger configuration.\n *\n * @returns Current configuration object with level, callback, and callbackIgnoresLevel\n *\n * @example\n * ```ts\n * const config = logger.getConfig();\n * console.log(config.level); // 'warn'\n * console.log(config.callbackIgnoresLevel); // false\n * ```\n */\n getConfig(): LoggerConfig {\n return {\n level: this.#config.level,\n callback: this.#config.callback,\n callbackIgnoresLevel: this.#config.callbackIgnoresLevel,\n };\n }\n\n /**\n * Logs an informational message at 'log' level.\n *\n * This is the lowest severity level. Messages are shown when level is 'all'.\n *\n * @param context - Context identifier (e.g., 'Router', 'Plugin')\n * @param message - Main log message\n * @param args - Additional arguments to log (objects, arrays, etc.)\n *\n * @example\n * ```ts\n * logger.log('Router', 'Navigation started', { from: '/home', to: '/about' });\n * // Output: [Router] Navigation started { from: '/home', to: '/about' }\n * ```\n */\n log(context: string, message: string, ...args: unknown[]): void {\n this.#writeLog(\"log\", context, message, args);\n }\n\n /**\n * Logs a warning message at 'warn' level.\n *\n * Use for deprecation notices, non-critical issues, or potential problems.\n * Messages are shown when level is 'all' or 'warn-error'.\n *\n * @param context - Context identifier (e.g., 'Router', 'Plugin')\n * @param message - Warning message\n * @param args - Additional arguments to log\n *\n * @example\n * ```ts\n * logger.warn('Router', 'Using deprecated API', { method: 'oldNavigate' });\n * // Output: [Router] Using deprecated API { method: 'oldNavigate' }\n * ```\n */\n warn(context: string, message: string, ...args: unknown[]): void {\n this.#writeLog(\"warn\", context, message, args);\n }\n\n /**\n * Logs an error message at 'error' level.\n *\n * Use for critical errors, exceptions, or failures that require attention.\n * Messages are shown when level is 'all', 'warn-error', or 'error-only'.\n *\n * @param context - Context identifier (e.g., 'Router', 'Plugin')\n * @param message - Error message\n * @param args - Additional arguments to log (often error objects)\n *\n * @example\n * ```ts\n * logger.error('Router', 'Navigation failed', new Error('Route not found'));\n * // Output: [Router] Navigation failed Error: Route not found\n * ```\n */\n error(context: string, message: string, ...args: unknown[]): void {\n this.#writeLog(\"error\", context, message, args);\n }\n\n /**\n * Central logging method that coordinates console output and callback invocation.\n *\n * This method implements the core logging logic:\n * 1. Early exit optimization for 'none' level (unless callback ignores level)\n * 2. Level threshold comparison for console output filtering\n * 3. Delegates to #writeToConsole and #invokeCallback\n *\n * @param level - Log level ('log' | 'warn' | 'error')\n * @param context - Context identifier\n * @param message - Log message\n * @param args - Additional arguments\n *\n * @private\n */\n #writeLog(\n level: LogLevel,\n context: string,\n message: string,\n args: unknown[],\n ): void {\n // Early exit optimization: if level is 'none' and callback doesn't ignore level,\n // skip all processing (both console and callback)\n // Stryker disable next-line BlockStatement: equivalent — emptying this early-exit block falls through, but at level \"none\" the downstream guards already yield no output: #writeToConsole skips (threshold 3 > every message level) and #invokeCallback returns (this branch runs only when callbackIgnoresLevel is false). Pure perf shortcut; the ConditionalExpression →true sibling on this line stays killed (not silenced here).\n if (this.#config.level === \"none\" && !this.#config.callbackIgnoresLevel) {\n return;\n }\n\n // Convert message level to numeric value for threshold comparison\n // LOG_LEVELS: { log: 0, warn: 1, error: 2 }\n const messageLevelValue = LOG_LEVELS[level];\n\n // Determine if this message should skip console output\n // Example: if threshold is 'warn' (1), then 'log' messages (0) are skipped\n const shouldSkipConsole = messageLevelValue < this.#currentThreshold;\n\n // Console output (respects level threshold)\n if (!shouldSkipConsole) {\n this.#writeToConsole(level, context, message, args);\n }\n\n // Callback handling (may ignore level threshold based on config)\n this.#invokeCallback(level, context, message, shouldSkipConsole, args);\n }\n\n /**\n * Writes a formatted log message to the console.\n *\n * Features:\n * - Formats message with context: \"[Context] message\"\n * - Uses appropriate console method (log/warn/error)\n * - Safe: checks for console existence (for non-browser environments)\n *\n * @param level - Console method to use ('log' | 'warn' | 'error')\n * @param context - Context identifier (prepended to message if present)\n * @param message - Log message\n * @param args - Additional arguments to pass to console\n *\n * @private\n */\n #writeToConsole(\n level: LogLevel,\n context: string,\n message: string,\n args: unknown[],\n ): void {\n // Safety check: ensure console exists and has the required method\n // This is important for environments like Node.js tests or edge cases\n if (\n typeof console !== \"undefined\" &&\n typeof console[level] === \"function\"\n ) {\n // Format message with context bracket notation for visual clarity\n // Note: formatting is done inside the check to avoid unnecessary string allocation\n // when console is not available\n const formattedMessage = context ? `[${context}] ${message}` : message;\n\n console[level](formattedMessage, ...args);\n }\n }\n\n /**\n * Invokes the configured callback with log data, respecting level settings.\n *\n * Complex logic handling:\n * 1. Skip if no callback configured\n * 2. Skip if callback respects level AND message is below threshold\n * 3. Call callback with error handling (prevents callback errors from breaking logger)\n *\n * The callbackIgnoresLevel flag enables two modes:\n * - false (default): callback only receives messages that pass threshold (same as console)\n * - true: callback receives ALL messages regardless of threshold (useful for analytics)\n *\n * @param level - Log level\n * @param context - Context identifier\n * @param message - Log message\n * @param shouldSkipConsole - Whether console output was skipped (used for level logic)\n * @param args - Additional arguments\n *\n * @private\n */\n #invokeCallback(\n level: LogLevel,\n context: string,\n message: string,\n shouldSkipConsole: boolean,\n args: unknown[],\n ): void {\n // Early exit: no callback configured, or callback respects level and message is filtered\n if (\n !this.#config.callback ||\n (!this.#config.callbackIgnoresLevel && shouldSkipConsole)\n ) {\n return;\n }\n\n // Re-entrancy guard: a callback calling logger.* re-enters here via\n // #writeLog → #invokeCallback. Skip the nested invocation so the pattern is\n // a safe no-op (console output already happened in #writeLog) instead of\n // recursing to a swallowed RangeError (#791).\n if (this.#inCallback) {\n return;\n }\n\n // Wrap callback invocation in try-catch to prevent user code errors\n // from breaking the logger or causing cascading failures\n this.#inCallback = true;\n try {\n // An async callback (`(...) => Promise<void>` is assignable to the\n // void-typed LogCallback) returns a Promise whose rejection would otherwise\n // leak as a Node `unhandledRejection` — process-fatal under\n // `--unhandled-rejections=strict` (Node 22+ default). Read the runtime\n // return and isolate it like core's subscribe (#944): duck-check the\n // thenable + `.catch` into the same console.error sink a sync throw uses\n // (#1161).\n // eslint-disable-next-line @typescript-eslint/no-confusing-void-expression -- read the runtime Promise of a void-typed async callback (#1161)\n const result: unknown = this.#config.callback(\n level,\n context,\n message,\n ...args,\n );\n\n if (\n result !== null &&\n result !== undefined &&\n typeof (result as PromiseLike<unknown>).then === \"function\"\n ) {\n Promise.resolve(result as PromiseLike<unknown>).catch(\n (error: unknown) => {\n this.#reportError(\"[Logger] Error in async callback:\", error);\n },\n );\n }\n } catch (error) {\n // Fallback error reporting if the callback throws synchronously\n this.#reportError(\"[Logger] Error in callback:\", error);\n } finally {\n this.#inCallback = false;\n }\n }\n\n // Report a callback error via console.error directly — never call the logger\n // (would recurse). Shared by the sync-throw catch and the async-rejection\n // `.catch` (#1161). Console-safety guard mirrors #writeToConsole.\n #reportError(message: string, error: unknown): void {\n if (typeof console !== \"undefined\" && typeof console.error === \"function\") {\n console.error(message, error);\n }\n }\n}\n","// packages/core/src/guards.ts\n\nimport type { LoggerConfig, LogLevelConfig, Route } from \"./types\";\nimport type { RouterValidator } from \"./types/RouterValidator\";\n\n// ============================================================================\n// Structural invariant guards (dependencies + route-tree shape)\n// ============================================================================\n\nexport function guardDependencies(deps: unknown): void {\n if (\n !deps ||\n typeof deps !== \"object\" ||\n (deps as { constructor: unknown }).constructor !== Object\n ) {\n throw new TypeError(\"dependencies must be a plain object\");\n }\n for (const key in deps as Record<string, unknown>) {\n if (Object.getOwnPropertyDescriptor(deps, key)?.get) {\n throw new TypeError(`dependencies cannot contain getters: \"${key}\"`);\n }\n }\n}\n\n/* eslint-disable @typescript-eslint/no-explicit-any -- accepts any Route type */\nexport function guardRouteStructure(\n routes: Route<any>[],\n validator?: RouterValidator | null,\n): void {\n /* eslint-enable @typescript-eslint/no-explicit-any */\n for (const route of routes) {\n const routeValue: unknown = route;\n\n if (\n routeValue === null ||\n typeof routeValue !== \"object\" ||\n Array.isArray(routeValue)\n ) {\n throw new TypeError(\"route must be a non-array object\");\n }\n\n validator?.routes.guardRouteCallbacks(route as Route);\n validator?.routes.guardNoAsyncCallbacks(route as Route);\n const children = (route as Route).children;\n\n if (children) {\n guardRouteStructure(children, validator);\n }\n }\n}\n\n// ============================================================================\n// Logger config assertion (RealRouter-specific)\n// ============================================================================\n\nconst VALID_LEVELS_SET = new Set<string>([\n \"all\",\n \"warn-error\",\n \"error-only\",\n \"none\",\n]);\n\nfunction isValidLevel(value: unknown): value is LogLevelConfig {\n return typeof value === \"string\" && VALID_LEVELS_SET.has(value);\n}\n\nfunction formatValue(value: unknown): string {\n if (typeof value === \"string\") {\n return `\"${value}\"`;\n }\n if (typeof value === \"object\") {\n return JSON.stringify(value);\n }\n\n // eslint-disable-next-line @typescript-eslint/no-base-to-string\n return String(value);\n}\n\nexport function assertLoggerConfig(\n config: unknown,\n): asserts config is LoggerConfig {\n if (typeof config !== \"object\") {\n throw new TypeError(\"Logger config must be an object\");\n }\n\n // `typeof null === \"object\"`, so TS still sees `object | null` here — but the\n // sole caller (Router's ctor) gates on `if (loggerConfig)`, so null/falsy never\n // arrives; treat it as the non-null object the gate guarantees.\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- gated caller never passes null\n const obj = config!;\n\n // Check for unknown properties\n for (const key of Object.keys(obj)) {\n if (\n key !== \"level\" &&\n key !== \"callback\" &&\n key !== \"callbackIgnoresLevel\"\n ) {\n throw new TypeError(`Unknown logger config property: \"${key}\"`);\n }\n }\n\n // Validate level if present\n if (\"level\" in obj && obj.level !== undefined && !isValidLevel(obj.level)) {\n throw new TypeError(\n `Invalid logger level: ${formatValue(obj.level)}. Expected: \"all\" | \"warn-error\" | \"error-only\" | \"none\"`,\n );\n }\n\n // Validate callback if present\n if (\n \"callback\" in obj &&\n obj.callback !== undefined &&\n typeof obj.callback !== \"function\"\n ) {\n throw new TypeError(\n `Logger callback must be a function, got ${typeof obj.callback}`,\n );\n }\n\n // Validate callbackIgnoresLevel if present (logger.configure does not type-check it)\n if (\n \"callbackIgnoresLevel\" in obj &&\n obj.callbackIgnoresLevel !== undefined &&\n typeof obj.callbackIgnoresLevel !== \"boolean\"\n ) {\n throw new TypeError(\n `Logger callbackIgnoresLevel must be a boolean, got ${typeof obj.callbackIgnoresLevel}`,\n );\n }\n}\n","// packages/core/src/helpers.ts\n\nimport { DEFAULT_LIMITS, EMPTY_PARAMS } from \"./constants\";\n\nimport type { Params, State, LimitsConfig } from \"./types\";\nimport type { Limits } from \"./types/internal\";\n\n// =============================================================================\n// State Helpers\n// =============================================================================\n\n/**\n * Shallow-freezes a State object in place.\n *\n * Freezes only the top-level State object (blocks reassignment of `name`,\n * `params`, `path`, `transition`, `context`). Nested objects (`params`,\n * `transition`, `transition.segments`, `transition.segments.{deactivated,activated}`)\n * are expected to be **already frozen at creation time** by their producers:\n *\n * - `params` frozen in `makeState()` / `navigateToNotFound()`\n * - `transition`, `segments`, `deactivated`, `activated` frozen in\n * `buildTransitionMeta()` (or inline in `navigateToNotFound()`)\n *\n * `state.context` is **intentionally not frozen** — plugins write to it via\n * `claim.write(state, value)` after state creation.\n *\n * @internal\n */\nexport function freezeStateInPlace<T extends State>(state: T): T {\n // `Object.freeze` returns non-objects (incl. null/undefined) unchanged, so the\n // former `if (!state) return state` guard was redundant — callers also gate it\n // (`state ? freezeStateInPlace(state) : undefined`) and `T extends State` is\n // typed non-null.\n return Object.freeze(state);\n}\n\n/**\n * Merges user limits with defaults.\n * Returns frozen object for immutability.\n */\nexport function createLimits(userLimits: Partial<LimitsConfig> = {}): Limits {\n return { ...DEFAULT_LIMITS, ...userLimits };\n}\n\n// =============================================================================\n// Params Helpers\n// =============================================================================\n\n/**\n * Strips `undefined` values from a params object before handoff to the query\n * string engine and state storage.\n *\n * **Why this exists:** `router.navigate(name, { x: undefined })` must not put\n * `x` into the resulting URL (publicly documented contract). The underlying\n * query engine (`search-params`) already does this, but the contract belongs\n * to `@real-router/core` — this function guarantees it at the core boundary\n * so that:\n * - Plugin interceptors on `forwardState` that inject `undefined` values are\n * caught before they reach the engine\n * - `state.params` never contains `undefined` values (roundtrip consistent\n * with URL)\n * - The contract is verifiable at core's own test surface (doesn't depend on\n * engine behavior for regression detection)\n *\n * Single pass. When nothing survives (empty input, or every value `undefined`)\n * it returns the shared frozen `EMPTY_PARAMS` singleton, so `makeState`'s\n * `params === EMPTY_PARAMS` reuse branch fires and an empty-params navigation\n * allocates zero transient `{}` (#1027); a non-empty input returns a fresh\n * object. Either way reference identity is not preserved across calls, and the\n * result MUST be treated as read-only — callers must not mutate it (the empty\n * case is a shared frozen singleton).\n */\nexport function normalizeParams(params: Params): Params;\n\nexport function normalizeParams(params: undefined): undefined;\n\nexport function normalizeParams(params: Params | undefined): Params | undefined;\n\nexport function normalizeParams(\n params: Params | undefined,\n): Params | undefined {\n if (params === undefined) {\n return params;\n }\n\n let normalized: Params | undefined;\n\n for (const key in params) {\n if (!Object.hasOwn(params, key)) {\n continue;\n }\n\n const value = params[key];\n\n if (value !== undefined) {\n // Lazy allocation: an all-empty / all-undefined input costs zero objects.\n normalized ??= {};\n normalized[key] = value;\n }\n }\n\n // Reuse the shared singleton when nothing survived so makeState's\n // `params === EMPTY_PARAMS` reuse branch fires (#1027).\n return normalized ?? EMPTY_PARAMS;\n}\n","import { DEFAULT_LIMITS } from \"../../constants\";\n\nimport type { DefaultDependencies } from \"../../types\";\nimport type { Limits } from \"../../types/internal\";\n\nexport interface DependenciesStore<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n> {\n dependencies: Partial<Dependencies>;\n limits: Limits;\n}\n\nexport function createDependenciesStore<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n initialDependencies: Partial<Dependencies> = {},\n): DependenciesStore<Dependencies> {\n const dependencies = Object.create(null) as Partial<Dependencies>;\n\n for (const key in initialDependencies) {\n if (initialDependencies[key] !== undefined) {\n dependencies[key] = initialDependencies[key];\n }\n }\n\n return {\n dependencies,\n limits: DEFAULT_LIMITS,\n };\n}\n","/**\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 normalizedPath = absolute ? path.slice(1) : path;\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","/**\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","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","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","// 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// 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}\n","// 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): {\n buildStaticParts: readonly string[];\n buildParamSlots: readonly BuildParamSlot[];\n} {\n const allUrlParams = new Set<string>();\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\n for (const [i, pathSegment] of pathSegments.entries()) {\n if (i > 0) {\n current += \"/\";\n }\n\n const token = parseSegment(pathSegment);\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","// 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 throwPathQueryNameCollision(\n routeName: string,\n name: string,\n): never {\n throw new Error(\n `[SegmentMatcher.registerTree] Name collision in route \"${routeName}\": \"${name}\" ` +\n `is declared as BOTH a path param (':${name}') and a query param ('?${name}'). ` +\n `buildPath would emit its value twice (once in the path, once in the query). ` +\n `Rename one.`,\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","// Trie node construction: param-name extraction + the param/splat child creators\n// (#736). Consumed by `trie` during insertion.\n\nimport { parseSegment } from \"../parseSegment\";\nimport { createSegmentNode } from \"../pathUtils\";\nimport { throwEmptyParamName, throwParamNameConflict } from \"./errors\";\n\nimport type { SegmentNode } from \"../types\";\n\n/**\n * Extracts the param name from a marker-led segment (`:name` / `*name`),\n * delegating the boundary to the canonical `parseSegment` tokenizer (#1324) so the\n * trie backstop, the route-tree gate, and `buildParamMeta` share ONE grammar and\n * cannot drift. `registerNode`'s per-segment grammar pre-pass has already rejected\n * every malformed form (name-less #858, trailing marker `:y*`/`:y:` #1324, fused\n * marker #1050, and the M1 removed optional/constraint forms), so a `:param` /\n * `*splat` name is guaranteed here — the error/`static` branches below are an\n * unreachable typed backstop. Single source for the param branch in\n * `processSegment` (the 3-token walk no longer forks, #1516).\n */\nexport function extractParamName(segment: string): string {\n const token = parseSegment(segment);\n\n // registerNode's per-segment grammar pass (Реш.2) rejects every malformed segment\n // — name-less (#858), trailing-marker (#1324), fused-marker (#1050), constraint\n // forms — before trie insertion, so a param|splat name is guaranteed here. The\n // error/`static` branches are unreachable, kept as a typed defensive backstop.\n /* v8 ignore start -- unreachable: registerNode's grammar pass rejects non-name segments first */\n if (\"error\" in token || token.kind === \"static\") {\n throwEmptyParamName();\n }\n /* v8 ignore stop */\n\n return token.name;\n}\n\n/**\n * Returns the param child of `node`, creating it on first use. A pre-existing\n * child with a *different* name is a #736 conflict (two routes binding the same\n * trie position under different names). With the 3-token grammar (M1) insertion is\n * a strict linear walk, so a single route never revisits a slot it created — the\n * former optional-omit `ownNodes` exception is gone with optional params.\n */\nexport function ensureParamChild(\n node: SegmentNode,\n paramName: string,\n): SegmentNode {\n if (!node.paramChild) {\n node.paramChild = { node: createSegmentNode(), name: paramName };\n } else if (node.paramChild.name !== paramName) {\n throwParamNameConflict(node.paramChild.name, paramName, \":\");\n }\n\n return node.paramChild.node;\n}\n\n/** Splat counterpart of {@link ensureParamChild}. */\nexport function ensureSplatChild(\n node: SegmentNode,\n splatName: string,\n): SegmentNode {\n if (!node.splatChild) {\n node.splatChild = { node: createSegmentNode(), name: splatName };\n } else if (node.splatChild.name !== splatName) {\n throwParamNameConflict(node.splatChild.name, splatName, \"*\");\n }\n\n return node.splatChild.node;\n}\n","// 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","// 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_ROUTE_META,\n EMPTY_STRINGS,\n EMPTY_STRING_SET,\n type RegistrationState,\n} from \"./context\";\nimport {\n throwDuplicateParamName,\n throwInvalidQueryParamName,\n throwPathQueryNameCollision,\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 );\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 // shared with a path param (`/a/:tab?tab`), where buildPath would emit the value\n // twice (`/a/x?tab=x`). Both degraded silently before.\n validateQueryParamDeclarations(\n node.fullName,\n declaredQueryParams,\n buildParamNamesSet,\n );\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 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 urlParamNames: ReadonlySet<string>,\n): void {\n for (const name of queryParams) {\n if (INVALID_QUERY_NAME_RGX.test(name)) {\n throwInvalidQueryParamName(routeName, name);\n }\n\n if (urlParamNames.has(name)) {\n throwPathQueryNameCollision(routeName, name);\n }\n }\n}\n","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/** Folds a parsed query param into the accumulator as an own DATA property. A\n * plain `params[name] = value` invokes the inherited `__proto__` accessor for the\n * literal key `\"__proto__\"` — so the param silently vanishes (string value) or\n * swaps the local prototype (object value), annulling search-params' #855 own-key\n * hardening one layer up. `defineProperty` writes a genuine own entry. (#1293) */\nfunction assignQueryParam(\n params: Record<string, unknown>,\n name: string,\n value: unknown,\n): void {\n if (name === \"__proto__\") {\n Object.defineProperty(params, name, {\n value,\n writable: true,\n enumerable: true,\n configurable: true,\n });\n } else {\n params[name] = value;\n }\n}\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 },\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 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 const queryString = this.#buildQueryStringForBuild(\n route,\n 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 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 if (\n queryString !== undefined &&\n !this.#mergeQueryParams(route, params, queryString)\n ) {\n return undefined;\n }\n\n return {\n segments: route.matchSegments,\n params,\n meta: route.meta,\n };\n }\n\n // Parses the query string and folds it into `params`. Returns false (→ match\n // yields undefined) when the URL is unmatchable: the injected parser threw, or\n // strict mode saw an undeclared key.\n //\n // Precedence (#843, INVARIANTS Matching #25): query params are merged into the\n // SAME object that already holds the path params, so a query key equal to a\n // path-param name OVERWRITES the path value (`match(\"/u/5?id=9\")` → `{id:\"9\"}`).\n // Intentional and documented: `buildPath` never emits a path param as a query\n // key, so the build→match roundtrip is unaffected; the collision only arises\n // for hand-crafted/adversarial URLs where a query shadows a path segment.\n #mergeQueryParams(\n route: CompiledRoute,\n params: Record<string, unknown>,\n queryString: string,\n ): boolean {\n let queryParams: Record<string, unknown>;\n\n try {\n queryParams = 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 false;\n }\n\n if (this.#options.strictQueryParams) {\n const declared = route.declaredQueryParamsSet;\n\n for (const key in queryParams) {\n if (!declared.has(key)) {\n return false;\n }\n\n assignQueryParam(params, key, queryParams[key]);\n }\n } else {\n for (const key in queryParams) {\n assignQueryParam(params, key, queryParams[key]);\n }\n }\n\n return true;\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","/**\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","/**\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","/**\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","/**\n * Decoding functions for search-params.\n *\n * Extracted from encode.ts for better separation of concerns.\n *\n * @module search-params/decode\n */\n\nimport type { ResolvedStrategies } from \"./strategies\";\nimport type { DecodeResult } from \"./types\";\n\n// =============================================================================\n// Value Decoding\n// =============================================================================\n\n/**\n * Decodes a URL-encoded value, handling + as space.\n * Optimized: only replaces + if present, skips decodeURIComponent if not needed.\n *\n * Uses indexOf for checking presence - more mutation-resistant than includes\n * because indexOf(\"x\") returns -1 when not found, while includes(\"\") always returns true.\n */\nexport const decodeValue = (value: string): string => {\n // Stryker disable next-line StringLiteral: equivalent — indexOf(\"%\") -> indexOf(\"\") returns 0, forcing the full decode path; decodeURIComponent is a no-op on a %-free string, so the result is identical (proven by injection). See the indexOf rationale above.\n const percentIdx = value.indexOf(\"%\");\n // Stryker disable next-line StringLiteral: equivalent — indexOf(\"+\") -> indexOf(\"\") returns 0, forcing replaceAll(\"+\",\" \") which is a no-op on a +-free string (proven by injection).\n const plusIdx = value.indexOf(\"+\");\n\n // Fast path: no encoding needed (common case - most values are simple)\n if (percentIdx === -1 && plusIdx === -1) {\n return value;\n }\n\n // Only replace + if present (avoid regex overhead)\n const withSpaces = plusIdx === -1 ? value : value.replaceAll(\"+\", \" \");\n\n // Only decode if % is present\n return percentIdx === -1 ? withSpaces : decodeURIComponent(withSpaces);\n};\n\n// =============================================================================\n// Main Decode\n// =============================================================================\n\n/**\n * Decodes a query parameter value using resolved strategies.\n *\n * @param value - Raw value from query string (undefined for key-only params)\n * @param strategies - Pre-resolved format strategies\n * @returns Decoded value (string, boolean, or null)\n */\nexport const decode = (\n value: string | undefined,\n strategies: ResolvedStrategies,\n): DecodeResult => {\n // Handle undefined (key-only params like ?flag)\n if (value === undefined) {\n return strategies.boolean.decodeUndefined();\n }\n\n // Check raw value for boolean formats (e.g., \"true\"/\"false\")\n const rawResult = strategies.boolean.decodeRaw(value);\n\n if (rawResult !== null) {\n return rawResult;\n }\n\n // Decode URI and check decoded value for boolean formats (e.g., ✓/✗)\n const decoded = decodeValue(value);\n\n const numberResult = strategies.number.decode(decoded);\n\n if (numberResult !== null) {\n return numberResult;\n }\n\n return strategies.boolean.decodeValue(decoded);\n};\n","/**\n * Utility functions for search-params.\n *\n * Internalized from https://github.com/troch/search-params\n *\n * @module search-params/utils\n */\n\n// =============================================================================\n// Total Percent-Encoding\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. This is a manual, lib-target-agnostic\n * `String.prototype.toWellFormed` (ES2024): consumers compile this `src` under their\n * OWN `tsconfig` (whose `lib` may predate es2024 — e.g. `hash-plugin`), so a regex\n * `replace` avoids a `toWellFormed` type error in every consumer while producing the\n * identical result (verified by parity across the surrogate space).\n */\nconst LONE_SURROGATE_RGX =\n /[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?<![\\uD800-\\uDBFF])[\\uDC00-\\uDFFF]/g;\n\n/**\n * Percent-encodes a value, staying TOTAL on a lone (unpaired) surrogate.\n *\n * `encodeURIComponent` throws `URIError` on an unpaired UTF-16 surrogate — the\n * only input it rejects. `parseQuery` accepts such a value (its non-percent decode\n * is an identity fast path), so `build(parseQuery(qs))` would throw and violate the\n * inverse-pair totality invariant `range(parseQuery) ⊆ dom(build)` (INVARIANTS\n * Parse/Build #12). We sanitize each lone surrogate to U+FFFD instead: the first\n * round-trip mutates the (already non-round-trippable) garbage, then stabilises;\n * well-formed inputs are untouched. Single source for BOTH encode sites — scalar/key\n * (`encode.ts`) and array element (`strategies/array.ts`) — so they cannot drift\n * (#1314).\n */\nexport const safeEncode = (value: string | number | boolean): string => {\n try {\n return encodeURIComponent(value);\n } catch (error) {\n // Only a lone surrogate (URIError) is sanitized. Anything else is a real error\n // the caller must see — e.g. a Symbol value throws `TypeError` here, and\n // `String(symbol)` would silently coerce it to \"Symbol(…)\" instead of rethrowing.\n if (!(error instanceof URIError)) {\n throw error;\n }\n\n // Lone surrogate → U+FFFD, keeping build total.\n return encodeURIComponent(\n String(value).replaceAll(LONE_SURROGATE_RGX, \"�\"),\n );\n }\n};\n","/**\n * Array encoding strategies.\n *\n * @module search-params/strategies/array\n */\n\nimport { safeEncode } from \"../utils\";\n\nimport type { NullStrategy } from \"./null\";\nimport type { ArrayFormat } from \"../types\";\n\n// =============================================================================\n// Strategy Interface\n// =============================================================================\n\n/**\n * Strategy for encoding array values.\n */\nexport interface ArrayStrategy {\n /**\n * Encodes an array as a query string segment.\n *\n * @param name - URL-encoded parameter name\n * @param values - Array values to encode\n * @param nullStrategy - Null strategy, so a `null` element encodes to the same\n * wire token a scalar null does (bare key under `default`, dropped under\n * `hidden`) — closing `range(parse) ⊆ dom(build)` (#1155)\n * @returns Query string segment (e.g., \"items=a&items=b\" or \"items=a,b\")\n */\n encodeArray: (\n name: string,\n values: unknown[],\n nullStrategy: NullStrategy,\n ) => string;\n\n /**\n * Splits a raw (URI-encoded) value into array parts during parsing.\n * Returns null if the value is not an array in this format.\n *\n * @param rawValue - Raw value before URI decoding\n * @returns Array of raw parts, or null if not an array\n */\n decodeValue?: (rawValue: string) => string[] | null;\n\n /**\n * When true, the parser orders bracketed elements (`a[n]`) by the numeric\n * index `n` rather than insertion order. Only `index` format sets this. (#856)\n */\n indexed?: boolean;\n}\n\n// =============================================================================\n// Helpers\n// =============================================================================\n\n// Encodes a non-null array element. `null` is handled per-format by the caller\n// (bare-key / skip); this throws only on genuinely unserialisable elements\n// (`undefined`, objects) — which `parse` never produces in an array.\nconst encodeValue = (value: unknown): string => {\n const type = typeof value;\n\n if (type !== \"string\" && type !== \"number\" && type !== \"boolean\") {\n // `null` is handled by the caller (bare-key / skip) and never reaches here,\n // so `type` names the offender directly (`undefined`, `object`, `symbol`, …).\n throw new TypeError(\n `[search-params] Array element must be a string, number, or boolean — received ${type}`,\n );\n }\n\n return safeEncode(value as string | number | boolean);\n};\n\n// Repeats `${name}${suffix}` keys joined by `&`. Shared between `none` (suffix\n// `\"\"`) and `brackets` (suffix `\"[]\"`). A `null` element encodes to the SAME\n// wire token a scalar null does via `nullStrategy` — the bare key\n// `${name}${suffix}` under `nullFormat: \"default\"`, or `\"\"` (dropped, filtered\n// below so no `&&` appears) under `\"hidden\"`. So `parseQuery(\"a&a=1\")` →\n// `{a:[null,\"1\"]}` round-trips to `\"a&a=1\"` instead of throwing (#1155).\nconst repeatKey = (\n name: string,\n values: unknown[],\n suffix: string,\n nullStrategy: NullStrategy,\n): string => {\n const key = `${name}${suffix}`;\n const parts: string[] = [];\n\n for (const value of values) {\n if (value === null) {\n const encoded = nullStrategy.encode(key);\n\n if (encoded) {\n parts.push(encoded);\n }\n } else {\n parts.push(`${key}=${encodeValue(value)}`);\n }\n }\n\n return parts.join(\"&\");\n};\n\n// =============================================================================\n// Strategy Implementations\n// =============================================================================\n\n/**\n * Repeated keys without brackets.\n * Example: items=a&items=b\n */\nexport const noneArrayStrategy: ArrayStrategy = {\n encodeArray: (name, values, nullStrategy) =>\n repeatKey(name, values, \"\", nullStrategy),\n};\n\n/**\n * Bracket notation without index.\n * Example: items[]=a&items[]=b\n */\nexport const bracketsArrayStrategy: ArrayStrategy = {\n encodeArray: (name, values, nullStrategy) =>\n repeatKey(name, values, \"[]\", nullStrategy),\n};\n\n/**\n * Indexed bracket notation.\n * Example: items[0]=a&items[1]=b\n */\nexport const indexArrayStrategy: ArrayStrategy = {\n encodeArray: (name, values, nullStrategy) => {\n const parts: string[] = [];\n\n for (const [i, value] of values.entries()) {\n const key = `${name}[${i}]`;\n\n if (value === null) {\n const encoded = nullStrategy.encode(key);\n\n if (encoded) {\n parts.push(encoded);\n }\n } else {\n parts.push(`${key}=${encodeValue(value)}`);\n }\n }\n\n return parts.join(\"&\");\n },\n\n indexed: true,\n};\n\n/**\n * Comma-separated values.\n * Example: items=a,b,c\n *\n * Comma has no per-element bare-key form (an empty part like `a=,` decodes to\n * the empty string, not `null`), so a `null` element is unrepresentable and\n * dropped. `parse` only yields null-in-array under `comma` via a bracketed\n * chunk (`a[]`) — a wire/format mismatch, never the comma-native path — so this\n * is a total-but-lossy edge. The `nullStrategy` arg is intentionally omitted\n * (a 2-arg impl satisfies the 3-arg interface).\n */\nexport const commaArrayStrategy: ArrayStrategy = {\n encodeArray: (name, values) => {\n const parts: string[] = [];\n\n for (const value of values) {\n if (value !== null) {\n parts.push(encodeValue(value));\n }\n }\n\n if (parts.length === 0) {\n return \"\";\n }\n\n return `${name}=${parts.join(\",\")}`;\n },\n\n decodeValue: (rawValue) => {\n // No unencoded comma → not an array (single value).\n // Encoded commas (%2C) are part of the value, not separators.\n if (!rawValue.includes(\",\")) {\n return null;\n }\n\n return rawValue.split(\",\");\n },\n};\n\n/**\n * Map of array format to strategy implementation.\n */\nexport const arrayStrategies: Record<ArrayFormat, ArrayStrategy> = {\n none: noneArrayStrategy,\n brackets: bracketsArrayStrategy,\n index: indexArrayStrategy,\n comma: commaArrayStrategy,\n};\n","/**\n * Boolean encoding/decoding strategies.\n *\n * @module search-params/strategies/boolean\n */\n\nimport type { DecodeResult, BooleanFormat } from \"../types\";\n\n// =============================================================================\n// Strategy Map\n// =============================================================================\n\n// =============================================================================\n// Strategy Interface\n// =============================================================================\n\n/**\n * Strategy for encoding/decoding boolean values.\n */\nexport interface BooleanStrategy {\n /**\n * Encodes a boolean value as a query string segment.\n *\n * @param name - URL-encoded parameter name\n * @param value - Boolean value to encode\n * @returns Query string segment (e.g., \"flag=true\" or just \"flag\")\n */\n encode: (name: string, value: boolean) => string;\n\n /**\n * Handles undefined value (key-only params like ?flag).\n *\n * @returns Decoded value for undefined\n */\n decodeUndefined: () => DecodeResult;\n\n /**\n * Decodes a raw (not URI-decoded) value before decodeValue() is called.\n * Used for formats that check raw values (e.g., \"auto\" checks \"true\"/\"false\").\n *\n * @param rawValue - Raw value before URI decoding\n * @returns boolean if matched, null to continue with URI decoding\n */\n decodeRaw: (rawValue: string) => boolean | null;\n\n /**\n * Decodes a URI-decoded value.\n *\n * @param decodedValue - Value after URI decoding\n * @returns Decoded value (boolean or the same string)\n */\n decodeValue: (decodedValue: string) => DecodeResult;\n}\n\n// =============================================================================\n// Strategy Implementations\n// =============================================================================\n\n/**\n * No special boolean handling - values are treated as strings.\n */\nexport const noneBooleanStrategy: BooleanStrategy = {\n encode: (name, value) => `${name}=${value}`,\n decodeUndefined: () => null,\n decodeRaw: () => null, // No raw value matching\n decodeValue: (decoded) => decoded, // Return as-is\n};\n\n/**\n * Auto-detect \"true\"/\"false\" strings as boolean values.\n * Checks raw value before URI decoding.\n */\nexport const autoBooleanStrategy: BooleanStrategy = {\n encode: (name, value) => `${name}=${value}`,\n decodeUndefined: () => null,\n decodeRaw: (raw) => {\n if (raw === \"true\") {\n return true;\n }\n if (raw === \"false\") {\n return false;\n }\n\n return null; // Continue with URI decoding\n },\n decodeValue: (decoded) => decoded, // Return decoded string\n};\n\n/**\n * Scalar true values are key-only (no =value); scalar false keeps an explicit\n * `=false`. Example: `?flag` for true, `?flag=false` for false. Array elements\n * always carry an explicit value (`?a=true&a=false`), so decoding must map both\n * `\"true\"`→true and `\"false\"`→false to round-trip booleans losslessly in either\n * position — a key-only param is `true` (decodeUndefined), and any explicit\n * `=true`/`=false` decodes back to the boolean (not the string).\n */\nexport const emptyTrueBooleanStrategy: BooleanStrategy = {\n encode: (name, value) => (value ? name : `${name}=false`),\n decodeUndefined: () => true, // Key-only means true\n decodeRaw: (raw) => {\n if (raw === \"true\") {\n return true;\n }\n if (raw === \"false\") {\n return false;\n }\n\n return null; // Continue with URI decoding\n },\n decodeValue: (decoded) => decoded, // Return as-is\n};\n\n/**\n * Map of boolean format to strategy implementation.\n */\nexport const booleanStrategies: Record<BooleanFormat, BooleanStrategy> = {\n none: noneBooleanStrategy,\n auto: autoBooleanStrategy,\n \"empty-true\": emptyTrueBooleanStrategy,\n};\n","/**\n * Null encoding/decoding strategies.\n *\n * @module search-params/strategies/null\n */\n\n// =============================================================================\n// Strategy Interface\n// =============================================================================\n\n/**\n * Strategy for encoding/decoding null values.\n */\n// =============================================================================\n// Strategy Map\n// =============================================================================\n\nimport type { NullFormat } from \"../types\";\n\nexport interface NullStrategy {\n /**\n * Encodes a null value as a query string segment.\n *\n * @param name - URL-encoded parameter name\n * @returns Query string segment (e.g., \"key\" or \"\")\n */\n encode: (name: string) => string;\n}\n\n// =============================================================================\n// Strategy Implementations\n// =============================================================================\n\n/**\n * Default null format - key only without value.\n * Example: ?key (no equals sign)\n */\nexport const defaultNullStrategy: NullStrategy = {\n encode: (name) => name,\n};\n\n/**\n * Null values are hidden (omitted from query string).\n * Example: (nothing)\n */\nexport const hiddenNullStrategy: NullStrategy = {\n encode: () => \"\",\n};\n\n/**\n * Map of null format to strategy implementation.\n */\nexport const nullStrategies: Record<NullFormat, NullStrategy> = {\n default: defaultNullStrategy,\n hidden: hiddenNullStrategy,\n};\n","/**\n * Number decoding strategies.\n *\n * @module search-params/strategies/number\n */\n\nimport type { NumberFormat } from \"../types\";\n\n// =============================================================================\n// Strategy Interface\n// =============================================================================\n\n/**\n * Strategy for decoding number values.\n */\nexport interface NumberStrategy {\n decode: (decodedValue: string) => number | null;\n}\n\n// =============================================================================\n// Strategy Implementations\n// =============================================================================\n\n/**\n * No special number handling - values remain strings.\n */\nexport const noneNumberStrategy: NumberStrategy = {\n decode: () => null, // passthrough\n};\n\n/**\n * Auto-detect numeric values and parse as numbers.\n * Matches integers and decimals via charCode scan (faster than regex for short strings).\n *\n * Recognizes an optional leading `-` so negatives round-trip symmetrically with the\n * values `navigate()`/`build()` produce (`build({ n: -5 })` → `\"n=-5\"`). Leading-zero\n * and unsafe-integer rejection apply to the magnitude regardless of sign. Exponent\n * notation stays a string — `build` never emits a canonical safe exponent, and unsafe\n * exponents would lose precision. (#742)\n */\nexport const autoNumberStrategy: NumberStrategy = {\n decode: (value) => {\n const length = value.length;\n\n if (length === 0) {\n return null;\n }\n\n // Optional leading minus; the magnitude (digits) begins at `start`.\n const start = value.codePointAt(0) === 45 ? 1 : 0; // '-'\n\n // A bare \"-\" has no magnitude.\n if (start === length) {\n return null;\n }\n\n // Leading zeros are not canonical numbers (\"00\", \"007\", \"-007\") — preserve as strings.\n // Allow \"0\" and \"0.x\" (single zero or decimal starting with 0).\n if (\n length - start > 1 &&\n value.codePointAt(start) === 48 &&\n value.codePointAt(start + 1) !== 46\n ) {\n return null;\n }\n\n let hasDot = false;\n\n for (let i = start; i < length; i++) {\n const ch = value.codePointAt(i);\n\n if (ch !== undefined && ch >= 48 && ch <= 57) {\n continue; // '0'-'9'\n }\n\n if (ch === 46 && !hasDot && i !== start && i !== length - 1) {\n hasDot = true;\n\n continue;\n }\n\n return null; // non-digit, non-dot, or invalid dot position\n }\n\n const num = Number(value);\n\n // Negative zero is not round-trippable: build(-0) emits \"0\" and\n // String(-0) === \"0\", so \"-0\"/\"-0.0\" must stay strings. (#898)\n if (Object.is(num, -0)) {\n return null;\n }\n\n // Reject unsafe integers — precision loss would corrupt the value on roundtrip.\n if (!Number.isSafeInteger(num) && !hasDot) {\n return null;\n }\n\n return num;\n },\n};\n\n// =============================================================================\n// Strategy Map\n// =============================================================================\n\n/**\n * Map of number format to strategy implementation.\n */\nexport const numberStrategies: Record<NumberFormat, NumberStrategy> = {\n auto: autoNumberStrategy,\n none: noneNumberStrategy,\n};\n","/**\n * Search Params Strategies - Factory and Types.\n *\n * Provides a unified interface for format-specific encoding/decoding strategies.\n * Strategies are resolved once when options are created, avoiding repeated\n * format checks during encode/decode operations.\n *\n * @module search-params/strategies\n */\n\nimport { arrayStrategies, type ArrayStrategy } from \"./array\";\nimport { booleanStrategies, type BooleanStrategy } from \"./boolean\";\nimport { nullStrategies, type NullStrategy } from \"./null\";\nimport { numberStrategies, type NumberStrategy } from \"./number\";\n\nimport type { FinalOptions } from \"../types\";\n\n// =============================================================================\n// Exports\n// =============================================================================\n\nexport type { ArrayStrategy } from \"./array\";\n\nexport type { BooleanStrategy } from \"./boolean\";\n\nexport type { NullStrategy } from \"./null\";\n\nexport type { NumberStrategy } from \"./number\";\n\n// =============================================================================\n// Resolved Strategies\n// =============================================================================\n\n/**\n * Pre-resolved strategies based on options.\n * Created once when makeOptions() is called, avoiding repeated lookups.\n */\nexport interface ResolvedStrategies {\n readonly boolean: BooleanStrategy;\n readonly null: NullStrategy;\n readonly number: NumberStrategy;\n readonly array: ArrayStrategy;\n}\n\n/**\n * Resolves strategies based on format options.\n *\n * @param arrayFormat - Array format\n * @param booleanFormat - Boolean format\n * @param nullFormat - Null format\n * @param numberFormat - Number format\n * @returns Resolved strategy implementations\n */\n/**\n * Fail fast on an unknown format. A `queryParams` typo in a JS consumer (no TS to\n * forbid it) otherwise indexes the strategy map to `undefined`, deferring a cryptic\n * `TypeError` to first use — which the router's `SegmentMatcher.#mergeQueryParams`\n * catch-all then masks as `UNKNOWN_ROUTE` for EVERY query URL, with zero diagnostics\n * (#1318). TS consumers are unaffected (the union types already forbid the typo).\n */\nconst requireStrategy = <T>(\n strategy: T | undefined,\n field: string,\n value: string,\n allowed: string,\n): T => {\n if (strategy === undefined) {\n throw new TypeError(\n `[search-params] Unknown ${field} \"${value}\" — expected ${allowed}`,\n );\n }\n\n return strategy;\n};\n\nexport const resolveStrategies = (\n arrayFormat: FinalOptions[\"arrayFormat\"],\n booleanFormat: FinalOptions[\"booleanFormat\"],\n nullFormat: FinalOptions[\"nullFormat\"],\n numberFormat: FinalOptions[\"numberFormat\"],\n): ResolvedStrategies => ({\n boolean: requireStrategy(\n booleanStrategies[booleanFormat],\n \"booleanFormat\",\n booleanFormat,\n '\"none\" | \"auto\" | \"empty-true\"',\n ),\n null: requireStrategy(\n nullStrategies[nullFormat],\n \"nullFormat\",\n nullFormat,\n '\"default\" | \"hidden\"',\n ),\n number: requireStrategy(\n numberStrategies[numberFormat],\n \"numberFormat\",\n numberFormat,\n '\"none\" | \"auto\"',\n ),\n array: requireStrategy(\n arrayStrategies[arrayFormat],\n \"arrayFormat\",\n arrayFormat,\n '\"none\" | \"brackets\" | \"index\" | \"comma\"',\n ),\n});\n\n// =============================================================================\n// Default Strategies\n// =============================================================================\n\n/**\n * Default strategies matching DEFAULT_OPTIONS.\n * Used when no custom options are provided.\n */\nexport const DEFAULT_STRATEGIES: ResolvedStrategies = {\n boolean: booleanStrategies.auto,\n null: nullStrategies.default,\n number: numberStrategies.auto,\n array: arrayStrategies.none,\n};\n","/**\n * Encoding functions for search-params.\n *\n * Uses Strategy pattern for format-specific encoding.\n * Strategies are resolved once when options are created.\n *\n * @module search-params/encode\n */\n\nimport {\n DEFAULT_STRATEGIES,\n resolveStrategies,\n type ResolvedStrategies,\n} from \"./strategies\";\nimport { safeEncode } from \"./utils\";\n\nimport type { FinalOptions, Options } from \"./types\";\n\n// =============================================================================\n// Default Query Params\n// =============================================================================\n\n/**\n * Default query parameter options. Single source of truth for all packages.\n */\nexport const DEFAULT_QUERY_PARAMS: FinalOptions = {\n arrayFormat: \"none\",\n booleanFormat: \"auto\",\n nullFormat: \"default\",\n numberFormat: \"auto\",\n};\n\n// =============================================================================\n// Options with Strategies\n// =============================================================================\n\n/**\n * Extended options with pre-resolved strategies.\n */\nexport interface OptionsWithStrategies extends FinalOptions {\n readonly strategies: ResolvedStrategies;\n}\n\n/**\n * Cached default options with strategies - avoids allocation when no options passed.\n */\nconst DEFAULT_OPTIONS: OptionsWithStrategies = {\n ...DEFAULT_QUERY_PARAMS,\n strategies: DEFAULT_STRATEGIES,\n};\n\n/**\n * Creates options with defaults and pre-resolved strategies.\n * Returns cached DEFAULT_OPTIONS when no custom options are specified.\n */\nexport const makeOptions = (opts?: Options): OptionsWithStrategies => {\n if (\n !opts ||\n (opts.arrayFormat === undefined &&\n opts.booleanFormat === undefined &&\n opts.nullFormat === undefined &&\n opts.numberFormat === undefined)\n ) {\n return DEFAULT_OPTIONS;\n }\n\n // Avoid object spread - direct property assignment is faster\n const arrayFormat = opts.arrayFormat ?? DEFAULT_QUERY_PARAMS.arrayFormat;\n const booleanFormat =\n opts.booleanFormat ?? DEFAULT_QUERY_PARAMS.booleanFormat;\n const nullFormat = opts.nullFormat ?? DEFAULT_QUERY_PARAMS.nullFormat;\n const numberFormat = opts.numberFormat ?? DEFAULT_QUERY_PARAMS.numberFormat;\n\n return {\n arrayFormat,\n booleanFormat,\n nullFormat,\n numberFormat,\n strategies: resolveStrategies(\n arrayFormat,\n booleanFormat,\n nullFormat,\n numberFormat,\n ),\n };\n};\n\n// =============================================================================\n// Value Encoding\n// =============================================================================\n\n/**\n * Encodes a value for use in a URL query string.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument\nexport const encodeValue = (value: any): string => safeEncode(value);\n\n// =============================================================================\n// Main Encode\n// =============================================================================\n\n/**\n * Encodes a parameter name and value as a query string segment.\n *\n * Uses pre-resolved strategies for format-specific encoding.\n * Strategies handle all formats uniformly - no inline optimizations\n * to avoid equivalent mutants in mutation testing.\n *\n * @param name - Parameter name\n * @param value - Parameter value\n * @param opts - Options with pre-resolved strategies\n * @returns Query string segment (e.g., \"key=value\")\n */\nexport const encode = (\n name: string,\n value: unknown,\n opts: OptionsWithStrategies,\n): string => {\n const encodedName = encodeValue(name);\n\n // Handle each type using strategies\n switch (typeof value) {\n case \"string\":\n case \"number\": {\n return `${encodedName}=${encodeValue(value)}`;\n }\n case \"boolean\": {\n return opts.strategies.boolean.encode(encodedName, value);\n }\n case \"object\": {\n // Null check\n if (value === null) {\n return opts.strategies.null.encode(encodedName);\n }\n // Array check\n if (Array.isArray(value)) {\n return opts.strategies.array.encodeArray(\n encodedName,\n value,\n opts.strategies.null,\n );\n }\n\n // Fallback for other objects - treat as string\n return `${encodedName}=${encodeValue(value)}`;\n }\n default: {\n // Fallback for other types (undefined handled by caller)\n return `${encodedName}=${encodeValue(value)}`;\n }\n }\n};\n","/**\n * Search Params - Query String Parsing and Building.\n *\n * Internalized from https://github.com/troch/search-params (MIT License)\n * for better code control and optimization.\n *\n * @module search-params/searchParams\n */\n\nimport { decode, decodeValue } from \"./decode\";\nimport { encode, makeOptions } from \"./encode\";\n\nimport type { ResolvedStrategies } from \"./strategies\";\nimport type { Options } from \"./types\";\n\n// =============================================================================\n// Internal Helpers\n// =============================================================================\n\n/**\n * Assigns a parameter as an own data property.\n *\n * Plain `params[name] = value` invokes the inherited `__proto__` accessor for the\n * literal key `\"__proto__\"`, so that key would mutate the prototype instead of\n * becoming a real entry; `defineProperty` writes a genuine own property.\n *\n * @internal\n */\nfunction assignParam(\n params: Record<string, unknown>,\n name: string,\n value: unknown,\n): void {\n if (name === \"__proto__\") {\n Object.defineProperty(params, name, {\n value,\n writable: true,\n enumerable: true,\n configurable: true,\n });\n } else {\n params[name] = value;\n }\n}\n\n/**\n * Adds a decoded value to params object, handling array accumulation.\n *\n * Collisions are detected via `Object.hasOwn`, not `params[name] !== undefined`:\n * a query key that shadows an `Object.prototype` member (`valueOf`, `constructor`,\n * `toString`, …) would otherwise read the inherited function and be mistaken for\n * a pre-existing value, corrupting the result into `[<fn>, value]`. (#855)\n *\n * @internal\n */\nfunction addToParams(\n params: Record<string, unknown>,\n decodedName: string,\n decodedValue: unknown,\n hasBrackets: boolean,\n): void {\n if (!Object.hasOwn(params, decodedName)) {\n assignParam(\n params,\n decodedName,\n hasBrackets ? [decodedValue] : decodedValue,\n );\n\n return;\n }\n\n const currentValue = params[decodedName];\n\n if (Array.isArray(currentValue)) {\n currentValue.push(decodedValue);\n } else {\n assignParam(params, decodedName, [currentValue, decodedValue]);\n }\n}\n\n/**\n * Decodes a parameter value through the resolved strategies.\n *\n * @internal\n */\nfunction decodeParamValue(\n searchPart: string,\n eqPos: number,\n end: number,\n hasValue: boolean,\n strategies: ResolvedStrategies,\n): unknown {\n const rawValue = hasValue ? searchPart.slice(eqPos + 1, end) : undefined;\n\n return decode(rawValue, strategies);\n}\n\n/**\n * Reads the non-negative integer index from a bracketed name (`a[12]`).\n *\n * `open` points at the `[`; digits up to the matching `]` form the index. Returns\n * `null` for `[]`, non-digit content (`a[x]`), or a missing `]` — those fall back\n * to insertion-order accumulation. (#856)\n *\n * @internal\n */\nfunction bracketIndex(\n searchPart: string,\n open: number,\n limit: number,\n): number | null {\n let i = open + 1;\n let value = 0;\n let hasDigit = false;\n\n while (i < limit) {\n const ch = searchPart.codePointAt(i);\n\n if (ch === 93) {\n // ']' — a numeric index only if at least one digit preceded it (\"[]\" → null)\n return hasDigit ? value : null;\n }\n\n if (ch !== undefined && ch >= 48 && ch <= 57) {\n value = value * 10 + (ch - 48);\n hasDigit = true;\n i++;\n\n continue;\n }\n\n return null; // non-digit inside brackets — not a numeric index\n }\n\n return null; // no closing ']' (incl. \"[\" at end) — malformed, fall back\n}\n\n/**\n * A single parsed query chunk: the source string plus the boundary offsets and\n * decoded name that `processParamChunk` computes once. Bundled into one\n * descriptor so the indexed-format collector reuses them without a long\n * parameter list (#856).\n *\n * @internal\n */\ninterface ParsedChunk {\n searchPart: string;\n /** Offset of `[` (bracket notation), or the name terminator. */\n nameEnd: number;\n /** Offset where the raw name ends (`=` for valued chunks, else `end`). */\n nameSourceEnd: number;\n /** Offset of `=`, or -1 when the chunk has no value. */\n eqPos: number;\n /** Offset one past the chunk. */\n end: number;\n hasValue: boolean;\n decodedName: string;\n}\n\n/**\n * Collects a bracketed chunk into the index-format group, to be sorted by index\n * after the full pass. Returns `false` when the bracket is not a numeric index\n * (`a[]`, `a[x]`, `a[`), so the caller falls back to insertion-order push. (#856)\n *\n * @internal\n */\nfunction collectIndexedChunk(\n chunk: ParsedChunk,\n strategies: ResolvedStrategies,\n indexedGroups: Map<string, [number, unknown][]>,\n): boolean {\n const {\n searchPart,\n nameEnd,\n nameSourceEnd,\n eqPos,\n end,\n hasValue,\n decodedName,\n } = chunk;\n const index = bracketIndex(searchPart, nameEnd, nameSourceEnd);\n\n if (index === null) {\n return false;\n }\n\n const value = decodeParamValue(searchPart, eqPos, end, hasValue, strategies);\n const group = indexedGroups.get(decodedName);\n\n if (group === undefined) {\n indexedGroups.set(decodedName, [[index, value]]);\n } else {\n group.push([index, value]);\n }\n\n // Stryker disable next-line BooleanLiteral: equivalent — returning false makes the caller ALSO push via insertion order, but indexedGroups was already populated above and parseIntoInternal overwrites the key with the index-sorted result, so the final params are identical (proven by injection).\n return true;\n}\n\n/**\n * Processes a single query parameter chunk and adds to params.\n *\n * `indexedGroups` is supplied only for `arrayFormat: \"index\"`: bracketed chunks\n * with a numeric index are collected there (to be sorted by index after the full\n * pass) instead of pushed in insertion order. (#856)\n *\n * @internal\n */\nfunction processParamChunk(\n searchPart: string,\n start: number,\n end: number,\n params: Record<string, unknown>,\n strategies: ResolvedStrategies,\n eqPos: number,\n indexedGroups?: Map<string, [number, unknown][]>,\n): void {\n // `eqPos` is the position of the next `=` at or after `start`, resolved once by\n // the caller's monotonic cursor (#1316) — never re-scanned here. `eqPos < end`\n // means it falls inside THIS chunk (so the chunk has a value); otherwise the\n // chunk is key-only.\n const hasValue = eqPos !== -1 && eqPos < end;\n\n const nameSourceEnd = hasValue ? eqPos : end;\n let nameEnd = nameSourceEnd;\n let hasBrackets = false;\n\n for (let i = start; i < nameSourceEnd; i++) {\n if (searchPart.codePointAt(i) !== 91) {\n continue;\n }\n\n // '['\n nameEnd = i;\n hasBrackets = true;\n\n break;\n }\n\n const decodedName = decodeValue(searchPart.slice(start, nameEnd));\n\n // Index array format: order by the bracket index, not insertion. A non-numeric\n // bracket (`a[]`, `a[x]`) returns false → falls through to insertion-order push.\n if (\n indexedGroups !== undefined &&\n hasBrackets &&\n collectIndexedChunk(\n { searchPart, nameEnd, nameSourceEnd, eqPos, end, hasValue, decodedName },\n strategies,\n indexedGroups,\n )\n ) {\n return;\n }\n\n // Comma array decode: split raw value before individual element decoding\n if (!hasBrackets && hasValue && strategies.array.decodeValue) {\n const rawValue = searchPart.slice(eqPos + 1, end);\n const parts = strategies.array.decodeValue(rawValue);\n\n if (parts) {\n for (const part of parts) {\n addToParams(params, decodedName, decode(part, strategies), true);\n }\n\n return;\n }\n }\n\n const decodedValue = decodeParamValue(\n searchPart,\n eqPos,\n end,\n hasValue,\n strategies,\n );\n\n addToParams(params, decodedName, decodedValue, hasBrackets);\n}\n\n// =============================================================================\n// Parse\n// =============================================================================\n\n/**\n * Parse an ALREADY-EXTRACTED query string (no path prefix, no leading \"?\") into\n * an object of parameters.\n *\n * The input must already be split at the first \"?\" — `SegmentMatcher.#preparePath`\n * does this before the DI call, so route-tree wires `parseQuery` (not a\n * path-accepting wrapper) as its query parser: re-splitting the input here would\n * break at a \"?\" *inside* a query value (legal per RFC 3986), silently dropping\n * the param (and unmatching the whole URL under `strictQueryParams`). (#1292)\n *\n * @example\n * ```typescript\n * parseQuery(\"page=1&sort=name\");\n * // => { page: 1, sort: \"name\" }\n *\n * parseQuery(\"items[]=a&items[]=b\", { arrayFormat: \"brackets\" });\n * // => { items: [\"a\", \"b\"] }\n * ```\n */\nexport const parseQuery = (\n search: string,\n opts?: Options,\n): Record<string, unknown> => {\n // Fast path: empty query string\n if (search === \"\" || search === \"?\") {\n return {};\n }\n\n // makeOptions(undefined) returns the cached DEFAULT_OPTIONS (auto) — the same\n // defaults `build` uses — so parseQuery(build(x)) === x even without options. (#744)\n const params: Record<string, unknown> = {};\n\n parseIntoInternal(search, params, makeOptions(opts).strategies);\n\n return params;\n};\n\n/**\n * Internal function to parse a query string into a target object.\n * The shared parse engine behind `parseQuery`.\n *\n * @internal\n */\nfunction parseIntoInternal(\n searchPart: string,\n params: Record<string, unknown>,\n strategies: ResolvedStrategies,\n): void {\n // `index` format orders by the bracket index; collect (index, value) pairs and\n // sort after the pass. `undefined` for every other format (no overhead). (#856)\n const indexedGroups = strategies.array.indexed\n ? new Map<string, [number, unknown][]>()\n : undefined;\n\n let start = 0;\n const length = searchPart.length;\n\n // Monotonic cursor for the next `=`. Its position only ever moves forward with\n // `start`, so the whole parse does a single amortised O(n) scan for `=` —\n // replacing `processParamChunk`'s former per-chunk `indexOf(\"=\", start)`, which\n // scanned to the end of the string on every key-only chunk and made `parse`\n // O(n²) on `\"a&a&…\"` (#1316). `-2` = not yet searched; `-1` = no `=` remains.\n let eqCache = -2;\n\n while (start < length) {\n let end = searchPart.indexOf(\"&\", start);\n\n if (end === -1) {\n end = length;\n }\n\n // Skip empty chunks — a `&&`, a leading `&`, or a trailing `&` produces a\n // zero-length span that carries no name and no value. Processing it would\n // decode the empty name to `\"\"` and the missing value to `null`, injecting a\n // junk `{ \"\": null }` param (and `[null, …]` on repeats) (#1156). An\n // intentional empty-key chunk always carries an `=` (`\"=1\"` → `end > start`),\n // so it is unaffected.\n if (end > start) {\n // Advance the cursor only when the cached `=` is behind the current chunk;\n // once it reports `-1` (no `=` left in the string) it is final.\n if (eqCache !== -1 && eqCache < start) {\n eqCache = searchPart.indexOf(\"=\", start);\n }\n\n processParamChunk(\n searchPart,\n start,\n end,\n params,\n strategies,\n eqCache,\n indexedGroups,\n );\n }\n\n start = end + 1;\n }\n\n if (indexedGroups !== undefined) {\n for (const [name, pairs] of indexedGroups) {\n // Stable sort by index (V8 sort is stable) → equal indices keep arrival order.\n pairs.sort((left, right) => left[0] - right[0]);\n assignParam(\n params,\n name,\n pairs.map((pair) => pair[1]),\n );\n }\n }\n}\n\n// =============================================================================\n// Build\n// =============================================================================\n\n/**\n * Build a querystring from an object of parameters.\n *\n * Note: Empty arrays produce an empty string, so `parseQuery(build({ items: [] }))`\n * will not contain the `items` key. This is expected behavior for all array\n * formats including `comma` — the key is erased uniformly (INVARIANTS Parse/Build #9).\n *\n * @example\n * ```typescript\n * build({ page: 1, sort: \"name\" });\n * // => \"page=1&sort=name\"\n *\n * build({ items: [\"a\", \"b\"] }, { arrayFormat: \"brackets\" });\n * // => \"items[]=a&items[]=b\"\n * ```\n */\nexport const build = (\n params: Record<string, unknown>,\n opts?: Options,\n): string => {\n // Fast path for empty params (common case)\n const keys = Object.keys(params);\n\n if (keys.length === 0) {\n return \"\";\n }\n\n const options = makeOptions(opts);\n\n // Optimized: single loop instead of filter().map().filter().join()\n // Avoids creating 3 intermediate arrays\n const parts: string[] = [];\n\n for (const key of keys) {\n const value = params[key];\n\n // Skip undefined values (not serialisable)\n if (value === undefined) {\n continue;\n }\n\n const encoded = encode(key, value, options);\n\n // Skip empty strings (e.g., from nullFormat: \"hidden\")\n if (encoded) {\n parts.push(encoded);\n }\n }\n\n return parts.join(\"&\");\n};\n","/**\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","// packages/core/src/namespaces/OptionsNamespace/constants.ts\n\nimport { DEFAULT_QUERY_PARAMS } from \"../../engine\";\n\nimport type { Options } from \"../../types\";\n\n/**\n * Default options for the router.\n */\nexport const defaultOptions: Options = {\n defaultRoute: \"\",\n defaultParams: {},\n trailingSlash: \"preserve\",\n caseSensitive: true,\n queryParamsMode: \"loose\",\n queryParams: DEFAULT_QUERY_PARAMS,\n urlParamsEncoding: \"default\",\n allowNotFound: true,\n rewritePathOnMatch: true,\n} satisfies Options;\n","// packages/core/src/namespaces/OptionsNamespace/helpers.ts\n\nimport type { Options, Params } from \"../../types\";\n\n/**\n * Recursively freezes an object and all nested objects.\n * Only freezes plain objects, not primitives or special objects.\n */\nexport function deepFreeze<T extends object>(obj: T): Readonly<T> {\n Object.freeze(obj);\n\n for (const value of Object.values(obj as Record<string, unknown>)) {\n if (value && typeof value === \"object\" && value.constructor === Object) {\n deepFreeze(value);\n }\n }\n\n return obj;\n}\n\n/**\n * Resolves an option value that can be static or a callback.\n * If the value is a function, calls it with getDependency and returns the result.\n * Otherwise, returns the value as-is.\n */\nexport function resolveOption(\n value: Options[\"defaultRoute\"],\n getDependency: (name: string) => unknown,\n): string;\n\nexport function resolveOption(\n value: Options[\"defaultParams\"],\n getDependency: (name: string) => unknown,\n): Params;\n\nexport function resolveOption(\n value: Options[\"defaultRoute\"] | Options[\"defaultParams\"],\n getDependency: (name: string) => unknown,\n): string | Params {\n if (typeof value === \"function\") {\n // Runtime getDependency is (name: string) => unknown, but DefaultRouteCallback<object>\n // expects <K extends keyof object>(name: K) => object[K] where keyof object = never.\n // Cast needed to bridge generic constraint mismatch.\n return value(getDependency as never);\n }\n\n return value;\n}\n","// packages/core/src/namespaces/OptionsNamespace/validators.ts\n\n/**\n * Minimal crash guard for options.\n * Full DX validation moved to @real-router/validation-plugin (retrospective pattern).\n */\nexport function validateOptionsIsObject(\n options: unknown,\n): asserts options is Record<string, unknown> {\n if (!options || typeof options !== \"object\" || Array.isArray(options)) {\n throw new TypeError(\"[router.constructor] options must be a plain object\");\n }\n}\n","// packages/core/src/namespaces/OptionsNamespace/OptionsNamespace.ts\n\nimport { defaultOptions } from \"./constants\";\nimport { deepFreeze } from \"./helpers\";\nimport { validateOptionsIsObject } from \"./validators\";\n\nimport type { Options } from \"../../types\";\n\nexport class OptionsNamespace {\n readonly #options: Readonly<Options>;\n\n constructor(initialOptions: Partial<Options> = {}) {\n this.#options = deepFreeze({\n ...defaultOptions,\n ...initialOptions,\n });\n }\n\n static validateOptionsIsObject(\n options: unknown,\n ): asserts options is Record<string, unknown> {\n validateOptionsIsObject(options);\n }\n\n get(): Readonly<Options> {\n return this.#options;\n }\n}\n","// packages/core/src/namespaces/StateNamespace/helpers.ts\n\nexport function areParamValuesEqual(val1: unknown, val2: unknown): boolean {\n if (val1 === val2) {\n return true;\n }\n\n if (Array.isArray(val1) && Array.isArray(val2)) {\n if (val1.length !== val2.length) {\n return false;\n }\n\n // eslint-disable-next-line unicorn/no-for-loop -- hot path: for-of entries() allocates iterator per recursive call\n for (let i = 0; i < val1.length; i++) {\n if (!areParamValuesEqual(val1[i], val2[i])) {\n return false;\n }\n }\n\n return true;\n }\n\n return false;\n}\n","// packages/core/src/stateMetaStore.ts\n\nimport type { Params, State } from \"./types\";\n\nconst store = new WeakMap<State, Params>();\n\n/** @internal */\nexport function getStateMetaParams(state: State): Params | undefined {\n return store.get(state);\n}\n\n/** @internal */\nexport function setStateMetaParams(state: State, params: Params): void {\n store.set(state, params);\n}\n","// packages/core/src/namespaces/StateNamespace/StateNamespace.ts\n\nimport { areParamValuesEqual } from \"./helpers\";\nimport { DEFAULT_TRANSITION, EMPTY_PARAMS } from \"../../constants\";\nimport { freezeStateInPlace } from \"../../helpers\";\nimport { setStateMetaParams } from \"../../stateMetaStore\";\n\nimport type { StateNamespaceDependencies } from \"./types\";\nimport type { RouteTreeStateMeta } from \"../../engine\";\nimport type { Params, State } from \"../../types\";\n\n/**\n * Independent namespace for managing router state storage and creation.\n *\n * Static methods handle validation (called by facade).\n * Instance methods handle state storage, freezing, and creation.\n */\nexport class StateNamespace {\n /**\n * Cached frozen state - avoids structuredClone on every getState() call.\n */\n #frozenState: State | undefined = undefined;\n\n /**\n * Previous state before the last setState call.\n */\n #previousState: State | undefined = undefined;\n\n /**\n * Dependencies injected from Router.\n */\n #deps!: StateNamespaceDependencies;\n\n // =========================================================================\n // Instance methods (trust input - already validated by facade)\n // =========================================================================\n\n /**\n * Returns the current router state.\n *\n * The returned state is deeply frozen (immutable) for safety.\n * Returns `undefined` if the router has not been started or has been stopped.\n */\n get<P extends Params = Params>(): State<P> | undefined {\n return this.#frozenState as State<P> | undefined; // NOSONAR -- generic narrowing needed for public API\n }\n\n /**\n * Sets the current router state.\n *\n * The state is deeply frozen before storage to ensure immutability.\n * The previous state is preserved and accessible via `getPrevious()`.\n *\n * @param state - Already validated by facade, or undefined to clear\n */\n set(state: State | undefined): void {\n // Preserve current state as previous before updating\n this.#previousState = this.#frozenState;\n\n // If state is already frozen (from makeState()), use it directly.\n // For external states, freeze in place without cloning.\n this.#frozenState = state ? freezeStateInPlace(state) : undefined;\n }\n\n /**\n * Returns the previous router state (before the last navigation).\n */\n getPrevious(): State | undefined {\n return this.#previousState;\n }\n\n reset(): void {\n this.#frozenState = undefined;\n this.#previousState = undefined;\n }\n\n // =========================================================================\n // Dependency Injection\n // =========================================================================\n\n /**\n * Sets dependencies for state creation methods.\n * Must be called before using makeState, areStatesEqual, etc.\n */\n setDependencies(deps: StateNamespaceDependencies): void {\n this.#deps = deps;\n }\n\n // =========================================================================\n // State Creation Methods\n // =========================================================================\n\n /**\n * Creates a state object for a route.\n *\n * `params` is frozen at creation so it is always immutable, even when\n * `skipFreeze=true` is passed to defer the outer `Object.freeze(state)` call.\n * This keeps params-freezing invariants independent of transition-pipeline\n * mutation (e.g. `completeTransition` attaching `state.transition`).\n *\n * `context` is initialized as a fresh empty object — intentionally NOT frozen\n * so plugins can publish data via `claim.write(state, value)` after creation.\n */\n makeState<P extends Params = Params>(\n name: string,\n params?: P,\n path?: string,\n meta?: RouteTreeStateMeta,\n skipFreeze?: boolean,\n ): State<P> {\n // Optimization: O(1) lookup instead of O(depth) ancestor iteration\n const defaultParamsConfig = this.#deps.getDefaultParams();\n const hasDefaultParams = Object.hasOwn(defaultParamsConfig, name);\n\n // Conditional allocation: avoid spreading when no defaultParams exist\n let mergedParams: P;\n\n if (hasDefaultParams) {\n mergedParams = Object.freeze({\n ...defaultParamsConfig[name],\n ...params,\n }) as P;\n } else if (!params || params === EMPTY_PARAMS) {\n mergedParams = EMPTY_PARAMS as P;\n } else {\n mergedParams = Object.freeze({ ...params });\n }\n\n const state = {\n name,\n params: mergedParams,\n path: path ?? this.#deps.buildPath(name, params),\n context: {},\n ...(!skipFreeze && { transition: DEFAULT_TRANSITION }),\n } as State<P>;\n\n if (meta) {\n setStateMetaParams(state, meta);\n }\n\n return skipFreeze ? state : freezeStateInPlace(state);\n }\n\n // =========================================================================\n // State Comparison Methods\n // =========================================================================\n\n /**\n * Compares two states for equality.\n * By default, ignores query params (only compares URL params).\n */\n areStatesEqual(\n state1: State | undefined,\n state2: State | undefined,\n ignoreQueryParams = true,\n ): boolean {\n if (!state1 || !state2) {\n return !!state1 === !!state2;\n }\n\n if (state1.name !== state2.name) {\n return false;\n }\n\n if (ignoreQueryParams) {\n // URL (path) param names are cached at the routes layer and invalidated\n // on every tree mutation, so this stays correct after replace() (#723).\n const urlParams = this.#deps.getUrlParams(state1.name);\n\n for (const urlParam of urlParams) {\n if (\n !areParamValuesEqual(state1.params[urlParam], state2.params[urlParam])\n ) {\n return false;\n }\n }\n\n return true;\n }\n\n const state1Keys = Object.keys(state1.params);\n const state2Keys = Object.keys(state2.params);\n\n if (state1Keys.length !== state2Keys.length) {\n return false;\n }\n\n for (const param of state1Keys) {\n if (\n !(param in state2.params) ||\n !areParamValuesEqual(state1.params[param], state2.params[param])\n ) {\n return false;\n }\n }\n\n return true;\n }\n}\n","// packages/core/src/namespaces/PluginsNamespace/constants.ts\n\nimport {\n events as EVENTS_CONST,\n plugins as PLUGINS_CONST,\n} from \"../../constants\";\n\nimport type { EventName } from \"../../types\";\n\n/**\n * Maps plugin method names to router event names.\n */\nexport const EVENTS_MAP = {\n [PLUGINS_CONST.ROUTER_START]: EVENTS_CONST.ROUTER_START,\n [PLUGINS_CONST.ROUTER_STOP]: EVENTS_CONST.ROUTER_STOP,\n [PLUGINS_CONST.TRANSITION_SUCCESS]: EVENTS_CONST.TRANSITION_SUCCESS,\n [PLUGINS_CONST.TRANSITION_START]: EVENTS_CONST.TRANSITION_START,\n [PLUGINS_CONST.TRANSITION_LEAVE_APPROVE]:\n EVENTS_CONST.TRANSITION_LEAVE_APPROVE,\n [PLUGINS_CONST.TRANSITION_ERROR]: EVENTS_CONST.TRANSITION_ERROR,\n [PLUGINS_CONST.TRANSITION_CANCEL]: EVENTS_CONST.TRANSITION_CANCEL,\n} as const satisfies Record<\n (typeof PLUGINS_CONST)[keyof typeof PLUGINS_CONST],\n EventName\n>;\n\n/**\n * Plugin method names that correspond to router events.\n */\nexport const EVENT_METHOD_NAMES = Object.keys(\n EVENTS_MAP,\n) as (keyof typeof EVENTS_MAP)[];\n\nexport const LOGGER_CONTEXT = \"router.usePlugin\";\n","// packages/core/src/namespaces/PluginsNamespace/validators.ts\n\n/**\n * Static validation functions for PluginsNamespace.\n * Called by Router facade before instance methods.\n */\n\nimport type { Plugin } from \"../../types\";\n\n/**\n * Validates that a plugin factory returned a valid plugin object.\n */\nexport function validatePlugin(plugin: Plugin): void {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (!(plugin && typeof plugin === \"object\") || Array.isArray(plugin)) {\n throw new TypeError(\n `[router.usePlugin] Plugin factory must return an object, got ${typeof plugin}`,\n );\n }\n\n // Detect async factory (returns Promise)\n if (typeof (plugin as unknown as { then?: unknown }).then === \"function\") {\n throw new TypeError(\n `[router.usePlugin] Async plugin factories are not supported. ` +\n `Factory returned a Promise instead of a plugin object.`,\n );\n }\n}\n","// packages/core/src/namespaces/PluginsNamespace/PluginsNamespace.ts\n\nimport { EVENTS_MAP, EVENT_METHOD_NAMES, LOGGER_CONTEXT } from \"./constants\";\nimport { validatePlugin } from \"./validators\";\n\nimport type { PluginsDependencies } from \"./types\";\nimport type {\n DefaultDependencies,\n Plugin,\n Unsubscribe,\n PluginFactory,\n} from \"../../types\";\n\n/**\n * Independent namespace for managing plugins.\n *\n * Static methods handle validation (called by facade).\n * Instance methods handle storage and business logic.\n */\nexport class PluginsNamespace<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n> {\n readonly #plugins = new Set<PluginFactory<Dependencies>>();\n readonly #unsubscribes = new Set<Unsubscribe>();\n\n #deps!: PluginsDependencies<Dependencies>;\n\n // =========================================================================\n // Static validation methods (called by facade before instance methods)\n // Proxy to functions in validators.ts for separation of concerns\n // =========================================================================\n\n static validatePlugin(plugin: Plugin): void {\n validatePlugin(plugin);\n }\n\n // =========================================================================\n // Dependency injection\n // =========================================================================\n\n setDependencies(deps: PluginsDependencies<Dependencies>): void {\n this.#deps = deps;\n }\n\n // =========================================================================\n // Instance methods (trust input - already validated by facade)\n // =========================================================================\n\n /**\n * Returns the number of registered plugins.\n * Used by facade for limit validation.\n */\n /* v8 ignore next 3 -- @preserve: only called via validator interface (ctx.validator?.plugins.validatePluginLimit), not reachable without validation plugin */\n count(): number {\n return this.#plugins.size;\n }\n\n /**\n * Registers one or more plugin factories.\n * Returns unsubscribe function to remove all added plugins.\n * Input already validated by facade (limit, duplicates).\n *\n * @param factories - Already validated by facade\n */\n use(...factories: PluginFactory<Dependencies>[]): Unsubscribe {\n // Emit warnings for count thresholds (not validation, just warnings)\n this.#deps\n .getValidator()\n ?.plugins.validateCountThresholds(this.#plugins.size + factories.length);\n\n // Fast path for single plugin (common case)\n if (factories.length === 1) {\n const factory = factories[0];\n const cleanup = this.#startPlugin(factory);\n\n this.#plugins.add(factory);\n\n let unsubscribed = false;\n\n const unsubscribe: Unsubscribe = () => {\n if (unsubscribed) {\n return;\n }\n\n unsubscribed = true;\n this.#plugins.delete(factory);\n this.#unsubscribes.delete(unsubscribe);\n try {\n cleanup();\n } catch (error) {\n this.#deps.logger.error(\n LOGGER_CONTEXT,\n \"Error during cleanup:\",\n error,\n );\n }\n };\n\n this.#unsubscribes.add(unsubscribe);\n\n return unsubscribe;\n }\n\n // Deduplicate batch with warning (validation already done by facade)\n const seenInBatch = this.#deduplicateBatch(factories);\n\n // Track successfully initialized plugins for cleanup\n const initializedPlugins: {\n factory: PluginFactory<Dependencies>;\n cleanup: Unsubscribe;\n }[] = [];\n\n // Initialize deduplicated plugins sequentially\n try {\n for (const plugin of seenInBatch) {\n const cleanup = this.#startPlugin(plugin);\n\n initializedPlugins.push({ factory: plugin, cleanup });\n }\n } catch (error) {\n // Rollback on failure - cleanup all initialized plugins\n for (const { cleanup } of initializedPlugins) {\n try {\n cleanup();\n } catch (cleanupError) {\n this.#deps.logger.error(\n LOGGER_CONTEXT,\n \"Cleanup error:\",\n cleanupError,\n );\n }\n }\n\n throw error;\n }\n\n // Commit phase - add to registry\n for (const { factory } of initializedPlugins) {\n this.#plugins.add(factory);\n }\n\n // Return unsubscribe function\n let unsubscribed = false;\n\n const unsubscribe: Unsubscribe = () => {\n if (unsubscribed) {\n return;\n }\n\n unsubscribed = true;\n this.#unsubscribes.delete(unsubscribe);\n\n for (const { factory } of initializedPlugins) {\n this.#plugins.delete(factory);\n }\n\n for (const { cleanup } of initializedPlugins) {\n try {\n cleanup();\n } catch (error) {\n this.#deps.logger.error(\n LOGGER_CONTEXT,\n \"Error during cleanup:\",\n error,\n );\n }\n }\n };\n\n this.#unsubscribes.add(unsubscribe);\n\n return unsubscribe;\n }\n\n /**\n * Returns registered plugin factories.\n */\n getAll(): PluginFactory<Dependencies>[] {\n return [...this.#plugins];\n }\n\n /**\n * Checks if a plugin factory is registered.\n * Used internally by validation to avoid array allocation.\n */\n /* v8 ignore next 3 -- @preserve: only called via validator interface, not reachable without validation plugin */\n has(factory: PluginFactory<Dependencies>): boolean {\n return this.#plugins.has(factory);\n }\n\n /**\n * Disposes all registered plugins by running their teardown callbacks\n * and removing event listener subscriptions.\n * Active disposal is required because plugins have an active lifecycle\n * (event subscriptions, teardown hooks).\n * Named \"dispose\" (not \"clear\") because there is active cleanup to perform.\n */\n disposeAll(): void {\n for (const unsubscribe of this.#unsubscribes) {\n unsubscribe();\n }\n\n this.#plugins.clear();\n this.#unsubscribes.clear();\n }\n\n // =========================================================================\n // Private methods\n // =========================================================================\n\n /**\n * Deduplicates batch with warning for duplicates within batch.\n * Validation (existing duplicates) is done by facade.\n */\n #deduplicateBatch(\n plugins: PluginFactory<Dependencies>[],\n ): Set<PluginFactory<Dependencies>> {\n const seenInBatch = new Set<PluginFactory<Dependencies>>();\n\n for (const plugin of plugins) {\n if (seenInBatch.has(plugin)) {\n this.#deps.getValidator()?.plugins.warnBatchDuplicates(plugins);\n } else {\n seenInBatch.add(plugin);\n }\n }\n\n return seenInBatch;\n }\n\n #startPlugin(pluginFactory: PluginFactory<Dependencies>): Unsubscribe {\n const appliedPlugin = this.#deps.compileFactory(pluginFactory);\n\n PluginsNamespace.validatePlugin(appliedPlugin);\n this.#deps.getValidator()?.plugins.validatePluginKeys(appliedPlugin);\n\n Object.freeze(appliedPlugin);\n\n // Collect all unsubscribe functions\n const removeEventListeners: Unsubscribe[] = [];\n\n // Subscribe plugin methods to corresponding router events\n for (const methodName of EVENT_METHOD_NAMES) {\n if (methodName in appliedPlugin) {\n if (typeof appliedPlugin[methodName] === \"function\") {\n removeEventListeners.push(\n this.#deps.addEventListener(\n EVENTS_MAP[methodName],\n appliedPlugin[methodName],\n ),\n );\n\n if (methodName === \"onStart\" && this.#deps.canNavigate()) {\n this.#deps.getValidator()?.plugins.warnPluginAfterStart(methodName);\n }\n } else {\n this.#deps.getValidator()?.plugins.warnPluginMethodType(methodName);\n }\n }\n }\n\n // Return composite cleanup function\n return () => {\n for (const removeListener of removeEventListeners) {\n removeListener();\n }\n\n if (typeof appliedPlugin.teardown === \"function\") {\n appliedPlugin.teardown();\n }\n };\n }\n}\n","// packages/core/src/namespaces/RouteLifecycleNamespace/RouteLifecycleNamespace.ts\n\nimport type { RouteLifecycleDependencies } from \"./types\";\nimport type {\n DefaultDependencies,\n GuardFn,\n State,\n GuardFnFactory,\n} from \"../../types\";\n\n// Boolean shorthand has only two possible values, so the guard and its factory\n// are module-level singletons — registering `true`/`false` reuses one cached\n// factory instead of allocating a fresh closure per call (#962).\nconst TRUE_GUARD: GuardFn = () => true;\nconst FALSE_GUARD: GuardFn = () => false;\nconst TRUE_FACTORY: GuardFnFactory = () => TRUE_GUARD;\nconst FALSE_FACTORY: GuardFnFactory = () => FALSE_GUARD;\n\n/**\n * Converts a boolean value to a guard function factory.\n * Used for the shorthand syntax where true/false is passed instead of a function.\n * Returns one of two cached factories — no per-call allocation (#962).\n */\nfunction booleanToFactory<Dependencies extends DefaultDependencies>(\n value: boolean,\n): GuardFnFactory<Dependencies> {\n return value ? TRUE_FACTORY : FALSE_FACTORY;\n}\n\n/**\n * Origin lane for a guard clear. Every `clearCanActivate` / `clearCanDeactivate`\n * caller names its lane — there is no origin-blind default — so a new call site\n * cannot silently wipe both the route-config and the external guard (#1171):\n *\n * - `\"definition\"` — clear only the route-config guard (`update(name, {…: null})`, #952).\n * - `\"external\"` — clear only the external, component-managed guard\n * (`removeXGuard()` and post-leave auto-cleanup — the inverse of `addXGuard()`).\n * - `\"both\"` — clear both (route removal / router teardown; the route is gone).\n */\nexport type GuardClearScope = \"definition\" | \"external\" | \"both\";\n\n/**\n * Source of truth for `canActivate` / `canDeactivate` guards.\n *\n * Storage is split by origin into four factory Maps (definition vs external,\n * each ×activate/deactivate); a single compiled-function Map per kind backs\n * navigation (\"external wins\" — when a route holds both a definition and an\n * external guard, the compiled guard is the external one, regardless of\n * registration order; #1174). `getFunctions()` returns a cached\n * `[deactivate, activate]` tuple for the hot path (stable reference, no\n * per-navigate allocation).\n *\n * All input validation is handled upstream by `getLifecycleApi` and\n * `getRoutesApi` — this class has no static methods.\n *\n * **Ordering convention.** Every paired surface lists **deactivate before\n * activate**: `getFunctions()` / `getFactories()` return `[deactivate,\n * activate]`, `getFactoriesByOrigin()` returns that tuple per origin, and\n * `canNavigateTo(toDeactivate, toActivate, …)` takes deactivate first. Keep\n * any new paired surface consistent with this order.\n */\nexport class RouteLifecycleNamespace<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n> {\n // Storage split by origin: definition vs external. Resolution is\n // EXTERNAL-WINS regardless of registration order (#1174): the compiled slot\n // reflects the external factory whenever one exists, else the definition. One\n // policy across every path — `#registerHandler` (keeps external over a later\n // definition), `#recompileSlot`, and `clearDefinitionGuards` (#1192) — so a\n // clone's fixed definition→external replay yields the source's effective guard\n // with no extra tracking. Both semantics are expressed over these primary Maps.\n readonly #definitionActivateFactories = new Map<\n string,\n GuardFnFactory<Dependencies>\n >();\n readonly #externalActivateFactories = new Map<\n string,\n GuardFnFactory<Dependencies>\n >();\n readonly #definitionDeactivateFactories = new Map<\n string,\n GuardFnFactory<Dependencies>\n >();\n readonly #externalDeactivateFactories = new Map<\n string,\n GuardFnFactory<Dependencies>\n >();\n // Compiled-function view. Single Map per kind because navigation does not\n // distinguish origin — it just runs the effective guard. Set on add\n // (external-wins — a definition does not overwrite a live external, #1174)\n // and recompiled on clear from whichever origin Map still holds the slot.\n readonly #canDeactivateFunctions = new Map<string, GuardFn>();\n readonly #canActivateFunctions = new Map<string, GuardFn>();\n // Cached tuple — Maps never change reference, so this is stable\n readonly #functionsTuple: [Map<string, GuardFn>, Map<string, GuardFn>] = [\n this.#canDeactivateFunctions,\n this.#canActivateFunctions,\n ];\n\n #deps!: RouteLifecycleDependencies<Dependencies>;\n\n setDependencies(deps: RouteLifecycleDependencies<Dependencies>): void {\n this.#deps = deps;\n }\n\n getHandlerCount(type: \"activate\" | \"deactivate\"): number {\n const definitionMap =\n type === \"activate\"\n ? this.#definitionActivateFactories\n : this.#definitionDeactivateFactories;\n const externalMap =\n type === \"activate\"\n ? this.#externalActivateFactories\n : this.#externalDeactivateFactories;\n\n if (definitionMap.size === 0) {\n return externalMap.size;\n }\n\n if (externalMap.size === 0) {\n return definitionMap.size;\n }\n\n const names = new Set(definitionMap.keys());\n\n for (const name of externalMap.keys()) {\n names.add(name);\n }\n\n return names.size;\n }\n\n /**\n * Pre-flights the #961 handler-limit `RangeError` into the route-CRUD PREPARE\n * phase (#1046). `#registerHandler`'s per-slot limit check throws AFTER the\n * tree/config swap, so `add`/`replace`/`update` tore post-commit when the\n * validator was installed and the per-type count was at `maxLifecycleHandlers`.\n * Running the same check here — before any store mutation — restores atomicity\n * (#951/#956/#698): a batch that would exceed the limit aborts before a single\n * write.\n *\n * Only NEW slots count (an overwrite leaves the union count unchanged, mirroring\n * `#registerHandler`). For `replace` (`clearsDefinition = true`) the definition\n * guards are about to be cleared, so the projection runs against the surviving\n * EXTERNAL guards only — exactly the post-clear state the install loop sees.\n * Plugin-gated: a no-op without the validator (the limit is opt-in).\n *\n * @param activateNames - route names a `canActivate` would be registered for\n * @param deactivateNames - route names a `canDeactivate` would be registered for\n * @param clearsDefinition - true for `replace` (definition guards cleared first)\n */\n preflightHandlerLimit(\n activateNames: Iterable<string>,\n deactivateNames: Iterable<string>,\n clearsDefinition: boolean,\n ): void {\n const validator = this.#deps.getValidator();\n\n if (!validator) {\n return;\n }\n\n const check = (\n type: \"activate\" | \"deactivate\",\n names: Iterable<string>,\n methodName: string,\n ): void => {\n const { definition, external } = this.#getFactoryMaps(type);\n\n // A name already holding a guard of this type is an overwrite (no new\n // slot, mirroring `#registerHandler`). After a definition-clear (replace)\n // only EXTERNAL guards survive, so the existing-name check — and the base\n // count below — run against `external` alone, matching the post-clear\n // install state the loop sees.\n let newSlots = 0;\n\n for (const name of names) {\n const isExisting = clearsDefinition\n ? external.has(name)\n : definition.has(name) || external.has(name);\n\n if (!isExisting) {\n newSlots++;\n }\n }\n\n if (newSlots === 0) {\n return;\n }\n\n // The install loop throws when a new-slot registration observes\n // `count >= max`. Starting from `base` and adding `newSlots` new names,\n // the highest pre-register count it reaches is `base + newSlots - 1` —\n // replicate that worst case so the pre-flight throws iff the loop would.\n const base = clearsDefinition\n ? external.size\n : this.getHandlerCount(type);\n\n validator.lifecycle.validateHandlerLimit(base + newSlots - 1, methodName);\n };\n\n check(\"activate\", activateNames, \"canActivate\");\n check(\"deactivate\", deactivateNames, \"canDeactivate\");\n }\n\n // =========================================================================\n // Instance methods\n // =========================================================================\n\n /**\n * Adds a canActivate guard for a route.\n *\n * @param name - Route name (input-validated by facade)\n * @param handler - Guard function or boolean (input-validated by facade)\n * @param isFromDefinition - True when guard comes from route definition\n * (lands in the definition Map; subject to `clearDefinitionGuards()`).\n * False (default) when added via `getLifecycleApi().addActivateGuard(...)`\n * (lands in the external Map; survives `replace()`).\n *\n * External wins at runtime (#1174): when a route holds both a definition and\n * an external guard, the compiled function is the external one, regardless of\n * registration order. Within one origin the most recent add overwrites. Origin\n * determines which Map the factory is filed under (relevant for\n * `clearDefinitionGuards()` and `cloneRouter` re-registration).\n */\n addCanActivate(\n name: string,\n handler: GuardFnFactory<Dependencies> | boolean,\n isFromDefinition = false,\n precompiledFn?: GuardFn,\n ): void {\n this.#registerHandler(\n \"activate\",\n name,\n handler,\n isFromDefinition,\n \"canActivate\",\n precompiledFn,\n );\n }\n\n /**\n * Adds a canDeactivate guard for a route.\n *\n * Symmetric counterpart to {@link addCanActivate}.\n */\n addCanDeactivate(\n name: string,\n handler: GuardFnFactory<Dependencies> | boolean,\n isFromDefinition = false,\n precompiledFn?: GuardFn,\n ): void {\n this.#registerHandler(\n \"deactivate\",\n name,\n handler,\n isFromDefinition,\n \"canDeactivate\",\n precompiledFn,\n );\n }\n\n /**\n * Removes a canActivate guard for a route. `scope` names the origin lane\n * (see {@link GuardClearScope}) — there is no origin-blind default, so every\n * caller commits to a lane and a new call site cannot silently clear both.\n * Delegates to {@link #clearGuard} (mirrors the add side's `#registerHandler`).\n *\n * @param name - Route name (already validated by facade)\n * @param scope - Which origin(s) to clear: `\"definition\"` / `\"external\"` / `\"both\"`\n */\n clearCanActivate(name: string, scope: GuardClearScope): void {\n this.#clearGuard(\"activate\", name, scope);\n }\n\n /**\n * Removes a canDeactivate guard for a route. Symmetric counterpart to\n * {@link clearCanActivate}.\n *\n * The `\"external\"` lane is what makes a route-config `canDeactivate` durable:\n * post-leave auto-cleanup (`completeTransition`) and `removeDeactivateGuard()`\n * unregister only the external, component-managed guard (router5 mount/unmount\n * heritage), while a definition guard survives for re-entry — symmetric with\n * definition `canActivate`, which lives as long as the route is in the tree\n * (#1171). Clearing both by default made a config guard one-shot: the first\n * permitted leave erased it, so re-entry was unguarded, `getRoutesApi().get()`\n * lost the field, and a clone taken after the leave never received it\n * (clone invariant #6).\n *\n * @param name - Route name (already validated by facade)\n * @param scope - Which origin(s) to clear: `\"definition\"` / `\"external\"` / `\"both\"`\n */\n clearCanDeactivate(name: string, scope: GuardClearScope): void {\n this.#clearGuard(\"deactivate\", name, scope);\n }\n\n /**\n * Clears all lifecycle handlers (canActivate and canDeactivate).\n * Used by clearRoutes to reset all lifecycle state.\n */\n clearAll(): void {\n this.#definitionActivateFactories.clear();\n this.#externalActivateFactories.clear();\n this.#definitionDeactivateFactories.clear();\n this.#externalDeactivateFactories.clear();\n this.#canActivateFunctions.clear();\n this.#canDeactivateFunctions.clear();\n }\n\n /**\n * Clears only lifecycle handlers that were registered from route definitions.\n * Used by HMR `replace()` to remove definition-sourced guards without\n * touching externally-added guards.\n *\n * For a slot where BOTH a definition and an external guard exist, the external\n * factory survives — and the compiled function is RECOMPILED from it (#1192).\n * Under external-wins (#1174) the compiled slot is already the external guard,\n * so this recompile is idempotent — it re-derives the surviving external factory\n * through the same choke point that keeps clearing correct (and stays robust if\n * the compiled slot were ever out of sync). For a definition-only slot, the\n * compiled function is dropped.\n */\n clearDefinitionGuards(): void {\n for (const name of this.#definitionActivateFactories.keys()) {\n if (this.#externalActivateFactories.has(name)) {\n this.#recompileSlot(\"activate\", name);\n } else {\n this.#canActivateFunctions.delete(name);\n }\n }\n\n for (const name of this.#definitionDeactivateFactories.keys()) {\n if (this.#externalDeactivateFactories.has(name)) {\n this.#recompileSlot(\"deactivate\", name);\n } else {\n this.#canDeactivateFunctions.delete(name);\n }\n }\n\n this.#definitionActivateFactories.clear();\n this.#definitionDeactivateFactories.clear();\n }\n\n /**\n * Returns lifecycle factories as a flat `[deactivate, activate]` tuple of\n * `Record<name, factory>` — the effective view where external wins over\n * definition for the same slot. Used by `getRoutesApi` to enrich route\n * objects with their current canActivate / canDeactivate factories and by\n * the route-removal cleanup path.\n *\n * For cloneRouter (which needs to preserve origin on re-registration), use\n * {@link getFactoriesByOrigin} instead.\n */\n getFactories(): [\n Record<string, GuardFnFactory<Dependencies>>,\n Record<string, GuardFnFactory<Dependencies>>,\n ] {\n const deactivateRecord: Record<string, GuardFnFactory<Dependencies>> = {};\n const activateRecord: Record<string, GuardFnFactory<Dependencies>> = {};\n\n for (const [name, factory] of this.#definitionDeactivateFactories) {\n deactivateRecord[name] = factory;\n }\n for (const [name, factory] of this.#externalDeactivateFactories) {\n deactivateRecord[name] = factory;\n }\n\n for (const [name, factory] of this.#definitionActivateFactories) {\n activateRecord[name] = factory;\n }\n for (const [name, factory] of this.#externalActivateFactories) {\n activateRecord[name] = factory;\n }\n\n return [deactivateRecord, activateRecord];\n }\n\n /**\n * Returns factories tagged by origin — definition and external as separate\n * `[deactivate, activate]` tuples. Used by `cloneRouter` to re-register\n * guards on the clone with their original origin flag preserved.\n */\n getFactoriesByOrigin(): {\n definition: [\n Record<string, GuardFnFactory<Dependencies>>,\n Record<string, GuardFnFactory<Dependencies>>,\n ];\n external: [\n Record<string, GuardFnFactory<Dependencies>>,\n Record<string, GuardFnFactory<Dependencies>>,\n ];\n } {\n const defDeact: Record<string, GuardFnFactory<Dependencies>> = {};\n const defAct: Record<string, GuardFnFactory<Dependencies>> = {};\n const extensionDeact: Record<string, GuardFnFactory<Dependencies>> = {};\n const extensionAct: Record<string, GuardFnFactory<Dependencies>> = {};\n\n for (const [name, factory] of this.#definitionDeactivateFactories) {\n defDeact[name] = factory;\n }\n for (const [name, factory] of this.#definitionActivateFactories) {\n defAct[name] = factory;\n }\n for (const [name, factory] of this.#externalDeactivateFactories) {\n extensionDeact[name] = factory;\n }\n for (const [name, factory] of this.#externalActivateFactories) {\n extensionAct[name] = factory;\n }\n\n return {\n definition: [defDeact, defAct],\n external: [extensionDeact, extensionAct],\n };\n }\n\n /**\n * Returns compiled lifecycle functions for transition execution.\n *\n * @returns Tuple of [canDeactivateFunctions, canActivateFunctions] as Maps\n */\n getFunctions(): [Map<string, GuardFn>, Map<string, GuardFn>] {\n return this.#functionsTuple;\n }\n\n canNavigateTo(\n toDeactivate: string[],\n toActivate: string[],\n toState: State,\n fromState: State | undefined,\n ): boolean {\n for (const segment of toDeactivate) {\n if (\n !this.#checkGuardSync(\n this.#canDeactivateFunctions,\n segment,\n toState,\n fromState,\n \"canNavigateTo\",\n )\n ) {\n return false;\n }\n }\n\n for (const segment of toActivate) {\n if (\n !this.#checkGuardSync(\n this.#canActivateFunctions,\n segment,\n toState,\n fromState,\n \"canNavigateTo\",\n )\n ) {\n return false;\n }\n }\n\n return true;\n }\n\n /**\n * Compiles a guard factory to its `GuardFn` WITHOUT registering it — surfaces\n * a throwing / non-function factory eagerly. The prepare-then-commit\n * add/replace path (`adoptRouteArtifacts`) calls this for every pending guard\n * BEFORE the store swap (#956), so a malformed factory aborts the mutation\n * with the store untouched. The returned function is then installed via the\n * `precompiledFn` argument of {@link addCanActivate} / {@link addCanDeactivate}\n * — no re-compile, so a factory with compile-time side effects runs exactly\n * once. Same boolean-shorthand handling + compile + non-function check as the\n * inline `#registerHandler` path, so a route-config `canActivate: true`\n * (boolean shorthand, runtime-reachable via the public route type) compiles to\n * the cached `TRUE_GUARD`/`FALSE_GUARD` instead of throwing on a non-callable.\n */\n compileGuardFactory(\n handler: GuardFnFactory<Dependencies> | boolean,\n methodName: string,\n ): GuardFn {\n const factory =\n typeof handler === \"boolean\"\n ? booleanToFactory<Dependencies>(handler)\n : handler;\n const fn = this.#deps.compileFactory(factory);\n\n if (typeof fn !== \"function\") {\n throw new TypeError(\n `[router.${methodName}] Factory must return a function, got ${typeof fn}`,\n );\n }\n\n return fn;\n }\n\n // =========================================================================\n // Private methods (business logic)\n // =========================================================================\n\n /**\n * Routes a registration into the origin-specific factory Map and updates the\n * compiled function under EXTERNAL-WINS (#1174): the just-added factory becomes\n * the compiled guard unless it is a definition registered while an external\n * guard is already live (then external stays effective; the definition is still\n * stored for a later `clearDefinitionGuards()`). Within one origin the most\n * recent add overwrites.\n * Emits overwrite / threshold warnings symmetric with the pre-refactor\n * single-Map behaviour: any prior entry for the slot — same origin or\n * cross-origin — counts as an overwrite for the warning surface; only a\n * brand-new slot (no entry in either Map) increments the threshold check.\n */\n #registerHandler(\n type: \"activate\" | \"deactivate\",\n name: string,\n handler: GuardFnFactory<Dependencies> | boolean,\n isFromDefinition: boolean,\n methodName: string,\n precompiledFn?: GuardFn,\n ): void {\n const factoryMaps = this.#getFactoryMaps(type);\n const functions =\n type === \"activate\"\n ? this.#canActivateFunctions\n : this.#canDeactivateFunctions;\n const targetMap = isFromDefinition\n ? factoryMaps.definition\n : factoryMaps.external;\n const otherMap = isFromDefinition\n ? factoryMaps.external\n : factoryMaps.definition;\n\n const isOverwrite = targetMap.has(name) || otherMap.has(name);\n\n if (isOverwrite) {\n this.#deps\n .getValidator()\n ?.lifecycle.warnOverwrite(name, type, methodName);\n } else {\n // Single enforcement choke point for EVERY registration path: programmatic\n // (getLifecycleApi) and route-config (getRoutesApi.add/update, where\n // isFromDefinition=true). The hard limit throws here so route-config guards\n // are bounded exactly like programmatic ones (#961); the approaching-limit\n // warning follows. Only new slots count toward the limit — an overwrite\n // leaves the count unchanged. `getHandlerCount` is read once and only when\n // the validator is installed (opt-in), so the no-plugin path stays free.\n const validator = this.#deps.getValidator();\n\n if (validator) {\n const count = this.getHandlerCount(type);\n\n validator.lifecycle.validateHandlerLimit(count, methodName);\n validator.lifecycle.validateCountThresholds(count + 1, methodName);\n }\n }\n\n const factory =\n typeof handler === \"boolean\"\n ? booleanToFactory<Dependencies>(handler)\n : handler;\n\n // Capture the slot's prior factory (if any) BEFORE the overwrite, so a\n // compile-throw can be rolled back to the previously-valid guard rather\n // than dropping it (#963).\n const previousFactory = targetMap.get(name);\n\n targetMap.set(name, factory);\n\n // External-wins (#1174): the compiled slot reflects the external guard\n // whenever one exists, regardless of registration order. A definition\n // registered while an external guard is live is still stored (so a later\n // replace()-clear can recompile from it via `#recompileSlot`) but does NOT\n // overwrite the compiled function — external stays effective. This makes\n // `#registerHandler` consistent with `#recompileSlot` / `clearDefinitionGuards`\n // (both external-wins, #1192), so the whole namespace has ONE policy, and\n // cloneRouter's fixed definition→external replay yields the same effective\n // guard as the source with no extra origin tracking. (The factory is still\n // compiled below to validate it and to keep the rollback path symmetric.)\n const externalWins = isFromDefinition && otherMap.has(name);\n\n try {\n // A pre-validated function (from the #956 add/replace pre-compile) is\n // installed directly — no re-compile; otherwise compile + non-function\n // check here (`compileGuardFactory` throws on a bad factory).\n const fn = precompiledFn ?? this.compileGuardFactory(factory, methodName);\n\n if (!externalWins) {\n functions.set(name, fn);\n }\n } catch (error) {\n // Roll the slot back to its pre-call state: restore the previous factory\n // on an overwrite (#963), else clear the slot. `#recompileSlot` then\n // resets the compiled function from whichever origin Map still holds an\n // entry — the restored same-origin factory, a surviving cross-origin one,\n // or (empty slot) deletes the compiled function.\n if (previousFactory === undefined) {\n targetMap.delete(name);\n } else {\n targetMap.set(name, previousFactory);\n }\n\n this.#recompileSlot(type, name);\n\n throw error;\n }\n }\n\n /**\n * Shared implementation for {@link clearCanActivate} / {@link clearCanDeactivate}\n * — the clear-side counterpart to {@link #registerHandler}. `scope` selects the\n * origin lane (no origin-blind default, #1171); when one origin is cleared and\n * the other survives, `#recompileSlot` recompiles the compiled function from\n * the survivor (external wins, #1174).\n */\n #clearGuard(\n type: \"activate\" | \"deactivate\",\n name: string,\n scope: GuardClearScope,\n ): void {\n const { definition, external } = this.#getFactoryMaps(type);\n const clearedDefinition =\n scope === \"external\" ? false : definition.delete(name);\n const clearedExternal =\n scope === \"definition\" ? false : external.delete(name);\n\n if (clearedDefinition || clearedExternal) {\n this.#recompileSlot(type, name);\n }\n }\n\n /**\n * Recompiles the compiled-function slot from whichever origin Map still has\n * an entry for `name` after a clear. External wins over definition; if\n * neither has an entry, the compiled function is deleted.\n */\n #recompileSlot(type: \"activate\" | \"deactivate\", name: string): void {\n const factoryMaps = this.#getFactoryMaps(type);\n const functions =\n type === \"activate\"\n ? this.#canActivateFunctions\n : this.#canDeactivateFunctions;\n\n const effective =\n factoryMaps.external.get(name) ?? factoryMaps.definition.get(name);\n\n if (!effective) {\n functions.delete(name);\n\n return;\n }\n\n try {\n const fn = this.#deps.compileFactory(effective);\n\n /* v8 ignore next 4 -- @preserve: stored factories were validated at add time, compileFactory should yield a function on second call too */\n if (typeof fn !== \"function\") {\n functions.delete(name);\n\n return;\n }\n\n functions.set(name, fn);\n } catch {\n /* v8 ignore next 2 -- @preserve: defensive — a user-provided factory could theoretically throw on re-compile (state changed since add time); deleting the function blocks navigation on that slot */\n functions.delete(name);\n }\n }\n\n #getFactoryMaps(type: \"activate\" | \"deactivate\"): {\n definition: Map<string, GuardFnFactory<Dependencies>>;\n external: Map<string, GuardFnFactory<Dependencies>>;\n } {\n return type === \"activate\"\n ? {\n definition: this.#definitionActivateFactories,\n external: this.#externalActivateFactories,\n }\n : {\n definition: this.#definitionDeactivateFactories,\n external: this.#externalDeactivateFactories,\n };\n }\n\n /**\n * Shared implementation for synchronous guard checks.\n * Warns if a guard returns a Promise (async guards are not supported in sync mode).\n * Catches exceptions and treats them as navigation-blocking (`false`).\n *\n * @param functions - Map of compiled guard functions to look up\n * @param name - Route name to check the guard for\n * @param toState - Target navigation state\n * @param fromState - Current state (`undefined` on initial navigation)\n * @param methodName - Public API method name for warning messages\n */\n #checkGuardSync(\n functions: Map<string, GuardFn>,\n name: string,\n toState: State,\n fromState: State | undefined,\n methodName: string,\n ): boolean {\n const guardFn = functions.get(name);\n\n if (!guardFn) {\n return true;\n }\n\n try {\n const result = guardFn(toState, fromState);\n\n if (typeof result === \"boolean\") {\n return result;\n }\n\n this.#deps.getValidator()?.lifecycle.warnAsyncGuardSync(name, methodName);\n\n return false;\n } catch (error) {\n // #959: a throwing sync guard must not vanish silently. `navigate()`\n // surfaces the same throw via `handleGuardError` → TRANSITION_ERROR; the\n // sync predicate (`canNavigateTo`) has no error channel, so core logs it\n // directly. This is an OPERATIONAL signal (the guard crashed — distinct\n // from the opt-in validator DX warnings above for which the validator is\n // the right home): the navigation is still treated as blocked (`false`).\n this.#deps.logger.warn(\n `router.${methodName}`,\n `Guard for \"${name}\" threw — treated as navigation-blocking (returned false)`,\n error,\n );\n\n return false;\n }\n }\n}\n","// packages/core/src/namespaces/RoutesNamespace/constants.ts\n\n/**\n * Default route name for the root node.\n */\nexport const DEFAULT_ROUTE_NAME = \"\";\n\n/**\n * Keys that belong to a route's structural/config surface — everything NOT in\n * this set is a plugin-defined **custom field** (e.g. lifecycle hooks,\n * `preload`, `searchSchema`), stored in `RoutesStore.routeCustomFields`.\n *\n * Single source of truth for the custom-field split, shared by route\n * registration (`add`/`replace`) and `update` so both classify patch keys\n * identically. `name`/`path`/`children` define route identity and are not\n * patchable via `update`; the remaining six are the structural/guard config.\n */\nexport const STANDARD_ROUTE_KEYS: ReadonlySet<string> = new Set([\n \"name\",\n \"path\",\n \"children\",\n \"canActivate\",\n \"canDeactivate\",\n \"forwardTo\",\n \"encodeParams\",\n \"decodeParams\",\n \"defaultParams\",\n]);\n","// packages/core/src/namespaces/RoutesNamespace/helpers.ts\n\nimport type { RouteConfig } from \"./types\";\nimport type { RouteDefinition } from \"../../engine\";\nimport type {\n DefaultDependencies,\n ForwardToCallback,\n Params,\n Route,\n} from \"../../types\";\n\n/**\n * Creates an empty RouteConfig.\n */\nexport function createEmptyConfig(): RouteConfig {\n return {\n decoders: Object.create(null) as Record<string, (params: Params) => Params>,\n encoders: Object.create(null) as Record<string, (params: Params) => Params>,\n defaultParams: Object.create(null) as Record<string, Params>,\n forwardMap: Object.create(null) as Record<string, string>,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n forwardFnMap: Object.create(null) as Record<string, ForwardToCallback<any>>,\n };\n}\n\n/**\n * Copies every {@link RouteConfig} sub-map's entries from `source` into\n * `target` (shallow per map — entries are shared by reference). Driven by\n * `Object.keys(source)` instead of one `Object.assign` per field, so a newly\n * added config sub-field is carried over automatically with nothing to forget\n * at each copy site (#965). Both configs are produced by\n * {@link createEmptyConfig}, so every key in `source` also exists on `target`,\n * and every value is a record object — the invariant this enumeration relies on.\n */\nexport function assignConfigEntries(\n target: RouteConfig,\n source: RouteConfig,\n): void {\n for (const key of Object.keys(source) as (keyof RouteConfig)[]) {\n Object.assign(target[key], source[key]);\n }\n}\n\n// ============================================================================\n// Route Tree Helpers\n// ============================================================================\n\n/**\n * Checks if all params from source exist with same values in target.\n * Small function body allows V8 inlining.\n */\nexport function paramsMatch(source: Params, target: Params): boolean {\n for (const key in source) {\n if (source[key] !== target[key]) {\n return false;\n }\n }\n\n return true;\n}\n\n/**\n * Checks params match, skipping keys present in skipKeys.\n */\nexport function paramsMatchExcluding(\n source: Params,\n target: Params,\n skipKeys: Params,\n): boolean {\n for (const key in source) {\n if (key in skipKeys) {\n continue;\n }\n if (source[key] !== target[key]) {\n return false;\n }\n }\n\n return true;\n}\n\n/**\n * Returns a copy of `defaultParams` with query-typed keys removed, based on\n * `ownMeta` (the per-route paramTypeMap from the matcher). When no query keys\n * are present, returns the input by reference (zero-allocation fast path).\n *\n * `ownMeta` may be `undefined` for non-registered route names — this is the\n * caller's escape hatch (e.g. `getMetaByName(name)?.[name]`); a missing meta\n * means no query type information is available, so defaults pass through\n * unchanged.\n */\nexport function stripQueryDefaults(\n defaultParams: Params,\n ownMeta: Record<string, \"url\" | \"query\"> | undefined,\n): Params {\n if (!ownMeta || !hasQueryDefault(defaultParams, ownMeta)) {\n return defaultParams;\n }\n\n const filtered: Params = {};\n\n for (const key in defaultParams) {\n if (ownMeta[key] !== \"query\") {\n filtered[key] = defaultParams[key];\n }\n }\n\n return filtered;\n}\n\nfunction hasQueryDefault(\n defaultParams: Params,\n ownMeta: Record<string, \"url\" | \"query\">,\n): boolean {\n for (const key in defaultParams) {\n if (ownMeta[key] === \"query\") {\n return true;\n }\n }\n\n return false;\n}\n\n/**\n * Sanitizes a route by keeping only essential properties.\n */\nexport function sanitizeRoute<Dependencies extends DefaultDependencies>(\n route: Route<Dependencies>,\n): RouteDefinition {\n const sanitized: RouteDefinition = {\n name: route.name,\n path: route.path,\n };\n\n if (route.children) {\n sanitized.children = route.children.map((child) => sanitizeRoute(child));\n }\n\n return sanitized;\n}\n\n/**\n * Recursively removes a route from definitions array.\n */\nexport function removeFromDefinitions(\n definitions: RouteDefinition[],\n routeName: string,\n parentPrefix = \"\",\n): boolean {\n for (let i = 0; i < definitions.length; i++) {\n const route = definitions[i];\n const fullName = parentPrefix\n ? `${parentPrefix}.${route.name}`\n : route.name;\n\n if (fullName === routeName) {\n definitions.splice(i, 1);\n\n return true;\n }\n\n if (\n route.children &&\n routeName.startsWith(`${fullName}.`) &&\n removeFromDefinitions(route.children, routeName, fullName)\n ) {\n return true;\n }\n }\n\n return false;\n}\n\n/**\n * Clears configuration entries that match the predicate.\n */\nexport function clearConfigEntries<T>(\n config: Record<string, T>,\n matcher: (key: string) => boolean,\n): void {\n for (const key of Object.keys(config)) {\n if (matcher(key)) {\n delete config[key];\n }\n }\n}\n\n/**\n * Used by matchPath() when trailingSlash is \"preserve\": the matcher's\n * buildPath() with an unset trailingSlash mode strips trailing slashes,\n * but \"preserve\" means the source path's trailing-slash choice wins.\n * If the source had a trailing slash, re-attach it to the rewritten path.\n * The reverse case (rewritten has trailing, source does not) is not\n * reachable with the current matcher — it never adds a trailing slash\n * with undefined mode.\n */\nexport function matchSourceTrailingSlash(\n sourcePath: string,\n rewrittenPath: string,\n): string {\n const queryIndex = rewrittenPath.search(/[?#]/);\n const pathPart =\n queryIndex === -1 ? rewrittenPath : rewrittenPath.slice(0, queryIndex);\n\n // Stryker disable next-line LogicalOperator: equivalent — buildPath strips trailing slashes, so the rewritten path never ends with \"/\" unless it IS \"/\" (already caught by the `=== \"/\"` operand). `endsWith(\"/\")` is unreachable-true, so `||` ≡ `&&`.\n if (pathPart === \"/\" || pathPart.endsWith(\"/\")) {\n return rewrittenPath;\n }\n\n const sourceQueryIndex = sourcePath.search(/[?#]/);\n const sourcePathPart =\n sourceQueryIndex === -1\n ? sourcePath\n : sourcePath.slice(0, sourceQueryIndex);\n\n if (!(sourcePathPart.length > 1 && sourcePathPart.endsWith(\"/\"))) {\n return rewrittenPath;\n }\n\n const querySuffix = queryIndex === -1 ? \"\" : rewrittenPath.slice(queryIndex);\n\n return `${pathPart}/${querySuffix}`;\n}\n","// packages/core/src/namespaces/RoutesNamespace/forwardChain.ts\n\nexport function resolveForwardChain(\n startRoute: string,\n forwardMap: Record<string, string>,\n maxDepth = 100,\n): string {\n const visited = new Set<string>();\n const chain: string[] = [startRoute];\n let current = startRoute;\n\n while (forwardMap[current]) {\n const next = forwardMap[current];\n\n if (visited.has(next)) {\n const cycleStart = chain.indexOf(next);\n const cycle = [...chain.slice(cycleStart), next];\n\n throw new Error(`Circular forwardTo: ${cycle.join(\" → \")}`);\n }\n\n visited.add(current);\n chain.push(next);\n current = next;\n\n if (chain.length > maxDepth) {\n throw new Error(\n `forwardTo chain exceeds maximum depth (${maxDepth}): ${chain.join(\" → \")}`,\n );\n }\n }\n\n return current;\n}\n","// packages/core/src/namespaces/RoutesNamespace/routesStore.ts\n\nimport { DEFAULT_ROUTE_NAME, STANDARD_ROUTE_KEYS } from \"./constants\";\nimport { resolveForwardChain } from \"./forwardChain\";\nimport {\n assignConfigEntries,\n createEmptyConfig,\n sanitizeRoute,\n} from \"./helpers\";\nimport {\n createMatcher,\n createRouteTree,\n routeTreeToDefinitions,\n} from \"../../engine\";\n\nimport type { RouteConfig, RoutesDependencies } from \"./types\";\nimport type {\n CreateMatcherOptions,\n Matcher,\n RouteDefinition,\n RouteTree,\n} from \"../../engine\";\nimport type {\n DefaultDependencies,\n ForwardToCallback,\n GuardFn,\n Params,\n RouteConfigUpdate,\n RouterLogger,\n GuardFnFactory,\n Route,\n} from \"../../types\";\nimport type { RouteLifecycleNamespace } from \"../RouteLifecycleNamespace\";\n\n// =============================================================================\n// Interfaces\n// =============================================================================\n\nexport interface RoutesStore<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n> {\n /**\n * DERIVED VIEW, not stored state: reconstructed from `tree` on every access\n * via `routeTreeToDefinitions` (the lossless inverse cloneRouter already\n * relies on — the `~` absolute marker is restored, child order is the\n * definition order). The tree is the single source of truth, so a third\n * retained copy of the route table (~30 B/route) is not kept. Every reader\n * is a cold CRUD/plugin-registration path; the derive is O(N).\n *\n * The returned array is a FRESH snapshot each time — mutating it never\n * affects the store (pass an explicitly-mutated snapshot to\n * `commitTreeChanges` instead, as `remove` does).\n */\n readonly definitions: RouteDefinition[];\n readonly config: RouteConfig;\n tree: RouteTree;\n matcher: Matcher;\n /**\n * Per-route-name cache of URL (path) param names, derived from `matcher` and\n * read by `RoutesNamespace.getUrlParams` (powers `areStatesEqual` /\n * `isActiveRoute`). Cleared on every `matcher` rebuild so comparisons never\n * stay frozen to a route's pre-mutation param shape (#723).\n */\n readonly urlParamsCache: Map<string, string[]>;\n resolvedForwardMap: Record<string, string>;\n routeCustomFields: Record<string, Record<string, unknown>>;\n rootPath: string;\n readonly matcherOptions: CreateMatcherOptions | undefined;\n depsStore: RoutesDependencies<Dependencies> | undefined;\n lifecycleNamespace: RouteLifecycleNamespace<Dependencies> | undefined;\n readonly pendingCanActivate: Map<string, GuardFnFactory<Dependencies>>;\n readonly pendingCanDeactivate: Map<string, GuardFnFactory<Dependencies>>;\n}\n\n// =============================================================================\n// Tree operations\n// =============================================================================\n\nfunction rebuildTree(\n definitions: readonly RouteDefinition[],\n rootPath: string,\n matcherOptions: CreateMatcherOptions | undefined,\n): { tree: RouteTree; matcher: Matcher } {\n const tree = createRouteTree(DEFAULT_ROUTE_NAME, rootPath, definitions);\n const matcher = createMatcher(matcherOptions);\n\n matcher.registerTree(tree);\n\n return { tree, matcher };\n}\n\n/**\n * Rebuilds tree+matcher in place from `definitions` (defaults to the current\n * tree's own derived definitions — the same-table case, e.g. a rootPath\n * change).\n */\nexport function rebuildTreeInPlace<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n store: RoutesStore<Dependencies>,\n definitions: readonly RouteDefinition[] = store.definitions,\n): void {\n const result = rebuildTree(definitions, store.rootPath, store.matcherOptions);\n\n store.tree = result.tree;\n store.matcher = result.matcher;\n store.urlParamsCache.clear();\n}\n\nexport function commitTreeChanges<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n store: RoutesStore<Dependencies>,\n definitions: readonly RouteDefinition[],\n): void {\n rebuildTreeInPlace(store, definitions);\n store.resolvedForwardMap = refreshForwardMap(store.config);\n}\n\n// =============================================================================\n// Store reset\n// =============================================================================\n\n/**\n * Clears all routes and resets config.\n * Does NOT clear lifecycle handlers or state — caller handles that.\n */\nexport function resetStore<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(store: RoutesStore<Dependencies>): void {\n clearRouteData(store);\n rebuildTreeInPlace(store, []);\n}\n\n/**\n * Clears route data without rebuilding the tree.\n * Used by replace() to avoid double rebuild (clearRouteData + commitTreeChanges).\n * `definitions` needs no clearing — it is derived from the tree, which the\n * caller rebuilds (resetStore → empty, replace → the new artifacts).\n */\nexport function clearRouteData<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(store: RoutesStore<Dependencies>): void {\n Object.assign(store.config, createEmptyConfig());\n\n store.resolvedForwardMap = Object.create(null) as Record<string, string>;\n store.routeCustomFields = Object.create(null) as Record<\n string,\n Record<string, unknown>\n >;\n}\n\n// =============================================================================\n// Forward map\n// =============================================================================\n\nexport function refreshForwardMap(config: RouteConfig): Record<string, string> {\n const map = Object.create(null) as Record<string, string>;\n\n for (const fromRoute of Object.keys(config.forwardMap)) {\n map[fromRoute] = resolveForwardChain(fromRoute, config.forwardMap);\n }\n\n return map;\n}\n\n// =============================================================================\n// Route handler registration\n// =============================================================================\n\n/**\n * Throws if `forwardTo` is an async function (native or transpiled). Async\n * forwardTo callbacks break the synchronous matchPath/buildPath contract.\n * Runs inside `registerForwardTo` (the add/replace build path, before any store\n * mutation) AND inside `getRoutesApi`'s `updateForwardTo` (the update path), so\n * `update(name, { forwardTo: async })` is rejected at registration with the same\n * actionable error instead of deferring a generic TypeError to navigation (#967).\n */\nexport function assertForwardToNotAsync(\n forwardTo: unknown,\n fullName: string,\n): void {\n if (typeof forwardTo !== \"function\") {\n return;\n }\n\n const isNativeAsync =\n (forwardTo as { constructor: { name: string } }).constructor.name ===\n \"AsyncFunction\";\n const isTranspiledAsync = (forwardTo as { toString: () => string })\n .toString()\n .includes(\"__awaiter\");\n\n if (isNativeAsync || isTranspiledAsync) {\n throw new TypeError(\n `forwardTo callback cannot be async for route \"${fullName}\". ` +\n `Async functions break matchPath/buildPath.`,\n );\n }\n}\n\nfunction registerForwardTo<Dependencies extends DefaultDependencies>(\n route: Route<Dependencies>,\n fullName: string,\n config: RouteConfig,\n logger: RouterLogger,\n): void {\n if (route.canActivate) {\n /* v8 ignore next -- @preserve: edge case, both string and function tested separately */\n const forwardTarget =\n typeof route.forwardTo === \"string\" ? route.forwardTo : \"[dynamic]\";\n\n logger.warn(\n \"real-router\",\n `Route \"${fullName}\" has both forwardTo and canActivate. ` +\n `canActivate will be ignored because forwardTo creates a redirect (industry standard). ` +\n `Move canActivate to the target route \"${forwardTarget}\".`,\n );\n }\n\n if (route.canDeactivate) {\n /* v8 ignore next -- @preserve: edge case, both string and function tested separately */\n const forwardTarget =\n typeof route.forwardTo === \"string\" ? route.forwardTo : \"[dynamic]\";\n\n logger.warn(\n \"real-router\",\n `Route \"${fullName}\" has both forwardTo and canDeactivate. ` +\n `canDeactivate will be ignored because forwardTo creates a redirect (industry standard). ` +\n `Move canDeactivate to the target route \"${forwardTarget}\".`,\n );\n }\n\n assertForwardToNotAsync(route.forwardTo, fullName);\n\n // forwardTo is guaranteed to exist at this point\n if (typeof route.forwardTo === \"string\") {\n config.forwardMap[fullName] = route.forwardTo;\n } else {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n config.forwardFnMap[fullName] = route.forwardTo!;\n }\n}\n\nfunction registerSingleRouteHandlers<Dependencies extends DefaultDependencies>(\n route: Route<Dependencies>,\n fullName: string,\n config: RouteConfig,\n routeCustomFields: Record<string, Record<string, unknown>>,\n pendingCanActivate: Map<string, GuardFnFactory<Dependencies>>,\n pendingCanDeactivate: Map<string, GuardFnFactory<Dependencies>>,\n logger: RouterLogger,\n): void {\n const customFields = Object.fromEntries(\n Object.entries(route).filter(([key]) => !STANDARD_ROUTE_KEYS.has(key)),\n );\n\n if (Object.keys(customFields).length > 0) {\n routeCustomFields[fullName] = customFields;\n }\n\n // Guards are collected here and registered into the lifecycle later — by\n // `adoptRouteArtifacts` (add/replace) or `RoutesNamespace.flushPendingGuards`\n // (initial routes, the final step of the Router constructor — #1331) — so\n // the build stays a pure, side-effect-free preparation step.\n if (route.canActivate) {\n pendingCanActivate.set(fullName, route.canActivate);\n }\n\n if (route.canDeactivate) {\n pendingCanDeactivate.set(fullName, route.canDeactivate);\n }\n\n if (route.forwardTo) {\n registerForwardTo(route, fullName, config, logger);\n }\n\n if (route.decodeParams) {\n config.decoders[fullName] = (params: Params): Params =>\n route.decodeParams?.(params) ?? params;\n }\n\n if (route.encodeParams) {\n config.encoders[fullName] = (params: Params): Params =>\n route.encodeParams?.(params) ?? params;\n }\n\n if (route.defaultParams) {\n config.defaultParams[fullName] = route.defaultParams;\n }\n}\n\nfunction registerAllRouteHandlers<Dependencies extends DefaultDependencies>(\n routes: readonly Route<Dependencies>[],\n config: RouteConfig,\n routeCustomFields: Record<string, Record<string, unknown>>,\n pendingCanActivate: Map<string, GuardFnFactory<Dependencies>>,\n pendingCanDeactivate: Map<string, GuardFnFactory<Dependencies>>,\n logger: RouterLogger,\n parentName = \"\",\n): void {\n for (const route of routes) {\n const fullName = parentName ? `${parentName}.${route.name}` : route.name;\n\n registerSingleRouteHandlers(\n route,\n fullName,\n config,\n routeCustomFields,\n pendingCanActivate,\n pendingCanDeactivate,\n logger,\n );\n\n if (route.children) {\n registerAllRouteHandlers(\n route.children,\n config,\n routeCustomFields,\n pendingCanActivate,\n pendingCanDeactivate,\n logger,\n fullName,\n );\n }\n }\n}\n\n// =============================================================================\n// Prepare-then-commit (issue #698)\n//\n// add()/replace() build the complete new store state into LOCAL structures, and\n// only swap it into the store once every core-level error has surfaced from the\n// build itself (async/circular forwardTo throw in registerAllRouteHandlers /\n// refreshForwardMap; invalid path constraint throws in rebuildTree). The store\n// is mutated only by `adoptRouteArtifacts`, which compiles every prepared guard\n// factory BEFORE the swap (#956): a factory that throws on compile (or returns a\n// non-function) aborts there, with the store still untouched. So all error\n// classes — core-level build errors AND malformed guard factories — surface\n// before any mutation, leaving the existing routes intact (full atomicity). The\n// silent-corruption cases route-tree never throws on (duplicate name vs an\n// existing route, a name duplicated within the batch, missing parent) are caught\n// up front by `assertAddable`.\n// =============================================================================\n\n/**\n * The fully-built, ready-to-swap result of preparing a route mutation. Holds\n * everything `adoptRouteArtifacts` assigns into the store.\n */\ninterface RouteArtifacts<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n> {\n readonly config: RouteConfig;\n readonly routeCustomFields: Record<string, Record<string, unknown>>;\n readonly pendingCanActivate: Map<string, GuardFnFactory<Dependencies>>;\n readonly pendingCanDeactivate: Map<string, GuardFnFactory<Dependencies>>;\n readonly tree: RouteTree;\n readonly matcher: Matcher;\n readonly resolvedForwardMap: Record<string, string>;\n}\n\n/** Null-proto shallow clone of a RouteConfig (preserves every sub-map's contents). */\nfunction cloneConfig(config: RouteConfig): RouteConfig {\n const clone = createEmptyConfig();\n\n assignConfigEntries(clone, config);\n\n return clone;\n}\n\n/**\n * Returns a new definitions array with `added` inserted, without mutating the\n * input. For a top-level add the existing definitions are shallow-copied and\n * `added` appended. For a parented add the spine down to the parent is cloned\n * (siblings/other branches are shared by reference) and `added` appended to the\n * parent's children. Caller guarantees the parent path exists (see assertAddable).\n */\nfunction insertAddedDefinitions(\n definitions: readonly RouteDefinition[],\n added: RouteDefinition[],\n parentSegments: readonly string[],\n): RouteDefinition[] {\n if (parentSegments.length === 0) {\n return [...definitions, ...added];\n }\n\n const [head, ...rest] = parentSegments;\n\n return definitions.map((def) => {\n if (def.name !== head) {\n return def;\n }\n\n const children = def.children ?? [];\n\n return {\n ...def,\n children:\n rest.length === 0\n ? [...children, ...added]\n : insertAddedDefinitions(children, added, rest),\n };\n });\n}\n\n/** Depth-first walk yielding each route's full dotted name (no side effects). */\nfunction walkRouteNames<Dependencies extends DefaultDependencies>(\n routes: readonly Route<Dependencies>[],\n parentName: string,\n onName: (fullName: string) => void,\n): void {\n for (const route of routes) {\n const fullName = parentName ? `${parentName}.${route.name}` : route.name;\n\n onName(fullName);\n\n if (route.children) {\n walkRouteNames(route.children, fullName, onName);\n }\n }\n}\n\n/**\n * Rejects a route name duplicated WITHIN a single batch — the silent-overwrite\n * case route-tree stays last-wins on (#953 for `add`, #968 for `replace`). Walks\n * the same depth-first dotted names, but tracks them in a local Set: a name seen\n * twice in one array means the caller's second route would silently shadow the\n * first (`matchPath` for the first route's path becomes unreachable). Mirrors\n * validation-plugin's batch-dup message (route-tree `checkBatchNameDuplicate`)\n * so the no-plugin error matches the with-plugin one. `methodName` is \"addRoute\"\n * for both add and replace — the plugin reports \"addRoute\" for replace batches\n * too, so this keeps with/without-plugin parity.\n */\nexport function assertNoDuplicateNamesInBatch<\n Dependencies extends DefaultDependencies,\n>(\n routes: readonly Route<Dependencies>[],\n parentName: string,\n methodName: string,\n): void {\n const seen = new Set<string>();\n\n walkRouteNames(routes, parentName, (fullName) => {\n if (seen.has(fullName)) {\n throw new Error(\n `[router.${methodName}] Duplicate route \"${fullName}\" in batch`,\n );\n }\n\n seen.add(fullName);\n });\n}\n\nconst INTERNAL_ROUTE_PREFIX = \"@@\";\n\n/**\n * Rejects a single (bare) route name that uses the reserved \"@@\" prefix —\n * internal / system names such as UNKNOWN_ROUTE (`\"@@router/UNKNOWN_ROUTE\"`).\n * Mutating such a name would let a real URL `matchPath` to a state with\n * `name === UNKNOWN_ROUTE`, silently conflating a genuine route with \"not\n * found\". This always-on guard protected all four mutators (#238) until the\n * validation-extraction (`d1ebff80`) demoted it to the opt-in\n * validation-plugin; only `add` was restored (#954), so `remove`/`update`\n * regained it via this helper (#1047). Mirrors validation-plugin's\n * `throwIfInternalRoute` message so the no-plugin error matches the with-plugin\n * one.\n */\nexport function assertNoInternalRouteName(\n name: string,\n methodName: string,\n): void {\n if (name.startsWith(INTERNAL_ROUTE_PREFIX)) {\n throw new Error(\n `[router.${methodName}] Route name \"${name}\" uses the reserved \"${INTERNAL_ROUTE_PREFIX}\" prefix. Routes with this prefix are internal and cannot be modified through the public API.`,\n );\n }\n}\n\n/**\n * Batch counterpart to {@link assertNoInternalRouteName}: rejects any route in\n * the batch (recursing children) whose BARE leaf name uses the reserved \"@@\"\n * prefix (the prefix is on the leaf, not the dotted fullName). Used by `add`\n * (#954) and `replace` (#1047).\n */\nexport function assertNoInternalNamesInBatch<\n Dependencies extends DefaultDependencies,\n>(routes: readonly Route<Dependencies>[], methodName: string): void {\n for (const route of routes) {\n assertNoInternalRouteName(route.name, methodName);\n\n if (route.children) {\n assertNoInternalNamesInBatch(route.children, methodName);\n }\n }\n}\n\n/**\n * Rejects two routes that share the same `path` at the same parent level WITHIN\n * a single `add` batch (#955). The matcher resolves a path collision last-wins,\n * so the earlier route stays addressable by name (`has` / `buildPath`) but is\n * unreachable by URL (`matchPath` returns the later route) — a silent shadow.\n * Paths only collide among siblings, so seen paths are tracked per parent\n * fullName. Mirrors validation-plugin's message (route-tree\n * `checkBatchPathDuplicate`) so the no-plugin error matches the with-plugin one.\n * Scoped to the batch (not the existing tree) per #955 — the in-batch case the\n * issue describes.\n */\nexport function assertNoDuplicatePathsInBatch<\n Dependencies extends DefaultDependencies,\n>(\n routes: readonly Route<Dependencies>[],\n parentName: string,\n methodName: string,\n): void {\n const seenByParent = new Map<string, Set<string>>();\n\n const walk = (\n siblings: readonly Route<Dependencies>[],\n parent: string,\n ): void => {\n for (const route of siblings) {\n const paths = seenByParent.get(parent);\n\n if (paths?.has(route.path)) {\n throw new Error(\n `[router.${methodName}] Path \"${route.path}\" is already defined`,\n );\n }\n\n if (paths) {\n paths.add(route.path);\n } else {\n seenByParent.set(parent, new Set([route.path]));\n }\n\n if (route.children) {\n walk(route.children, parent ? `${parent}.${route.name}` : route.name);\n }\n }\n };\n\n walk(routes, parentName);\n}\n\n/**\n * Up-front guard for `add` against the corruptions route-tree stays silent on: a\n * missing `parent`, a name that collides with an EXISTING route, a name\n * duplicated WITHIN the batch, a reserved \"@@\"-prefixed name (which would shadow\n * an internal/system route name), and a path duplicated among siblings WITHIN\n * the batch (any of which would otherwise be silently overwritten/shadowed).\n * Throws before any build.\n */\nexport function assertAddable<Dependencies extends DefaultDependencies>(\n store: RoutesStore<Dependencies>,\n routes: readonly Route<Dependencies>[],\n parentName: string | undefined,\n): void {\n assertNoInternalNamesInBatch(routes, \"addRoute\");\n\n if (parentName !== undefined && !store.matcher.hasRoute(parentName)) {\n throw new Error(\n `[router.addRoute] Parent route \"${parentName}\" does not exist`,\n );\n }\n\n walkRouteNames(routes, parentName ?? \"\", (fullName) => {\n if (store.matcher.hasRoute(fullName)) {\n throw new Error(`[router.addRoute] Route \"${fullName}\" already exists`);\n }\n });\n\n assertNoDuplicateNamesInBatch(routes, parentName ?? \"\", \"addRoute\");\n assertNoDuplicatePathsInBatch(routes, parentName ?? \"\", \"addRoute\");\n}\n\n/**\n * Builds RouteArtifacts from a final definitions array and the routes whose\n * handlers (config + guards) populate `config`/`routeCustomFields`. Guards are\n * collected into the returned pending maps (depsStore is intentionally omitted\n * so nothing compiles or touches the lifecycle here). THROWS on async/circular\n * forwardTo and invalid path constraint — before the caller mutates the store.\n *\n * Takes a single args object: the positional list hit 8 parameters (S107) when\n * the per-router `logger` (#724) joined it, and named fields read better at the\n * two call sites anyway.\n */\nfunction buildArtifacts<Dependencies extends DefaultDependencies>({\n definitions,\n routesForHandlers,\n config,\n routeCustomFields,\n handlerParentName,\n rootPath,\n matcherOptions,\n logger,\n}: {\n definitions: readonly RouteDefinition[];\n routesForHandlers: readonly Route<Dependencies>[];\n config: RouteConfig;\n routeCustomFields: Record<string, Record<string, unknown>>;\n handlerParentName: string;\n rootPath: string;\n matcherOptions: CreateMatcherOptions | undefined;\n logger: RouterLogger;\n}): RouteArtifacts<Dependencies> {\n const pendingCanActivate = new Map<string, GuardFnFactory<Dependencies>>();\n const pendingCanDeactivate = new Map<string, GuardFnFactory<Dependencies>>();\n\n registerAllRouteHandlers(\n routesForHandlers,\n config,\n routeCustomFields,\n pendingCanActivate,\n pendingCanDeactivate,\n logger,\n handlerParentName,\n );\n\n const resolvedForwardMap = refreshForwardMap(config);\n const { tree, matcher } = rebuildTree(definitions, rootPath, matcherOptions);\n\n return {\n config,\n routeCustomFields,\n pendingCanActivate,\n pendingCanDeactivate,\n tree,\n matcher,\n resolvedForwardMap,\n };\n}\n\n/** Builds the merged artifacts for an incremental `add` (existing ∪ new). */\nexport function buildAddArtifacts<Dependencies extends DefaultDependencies>(\n store: RoutesStore<Dependencies>,\n routes: readonly Route<Dependencies>[],\n parentName: string | undefined,\n logger: RouterLogger,\n): RouteArtifacts<Dependencies> {\n const definitions = insertAddedDefinitions(\n store.definitions,\n routes.map((route) => sanitizeRoute(route)),\n parentName === undefined ? [] : parentName.split(\".\"),\n );\n\n return buildArtifacts({\n definitions,\n routesForHandlers: routes,\n config: cloneConfig(store.config),\n routeCustomFields: Object.assign(\n Object.create(null) as Record<string, Record<string, unknown>>,\n store.routeCustomFields,\n ),\n handlerParentName: parentName ?? \"\",\n rootPath: store.rootPath,\n matcherOptions: store.matcherOptions,\n logger,\n });\n}\n\n/** Builds the fresh artifacts for a full `replace` (standalone new set). */\nexport function buildReplaceArtifacts<Dependencies extends DefaultDependencies>(\n routes: readonly Route<Dependencies>[],\n rootPath: string,\n matcherOptions: CreateMatcherOptions | undefined,\n logger: RouterLogger,\n): RouteArtifacts<Dependencies> {\n return buildArtifacts({\n definitions: routes.map((route) => sanitizeRoute(route)),\n routesForHandlers: routes,\n config: createEmptyConfig(),\n routeCustomFields: Object.create(null) as Record<\n string,\n Record<string, unknown>\n >,\n handlerParentName: \"\",\n rootPath,\n matcherOptions,\n logger,\n });\n}\n\n/**\n * Compiles every pending guard factory up front, returning\n * `[name, factory, compiledFn]` triples for installation. THROWS from `compile`\n * on the first factory that throws on compile or returns a non-function — the\n * pre-swap validation that makes `adoptRouteArtifacts` atomic for malformed\n * guards (#956). Compiling here (not at install) means a factory with\n * compile-time side effects runs exactly once.\n */\nfunction compilePendingGuards<Dependencies extends DefaultDependencies>(\n pending: Map<string, GuardFnFactory<Dependencies>>,\n compile: (\n handler: GuardFnFactory<Dependencies>,\n methodName: string,\n ) => GuardFn,\n methodName: string,\n): [string, GuardFnFactory<Dependencies>, GuardFn][] {\n const compiled: [string, GuardFnFactory<Dependencies>, GuardFn][] = [];\n\n for (const [name, factory] of pending) {\n compiled.push([name, factory, compile(factory, methodName)]);\n }\n\n return compiled;\n}\n\n/** Pre-compiled guard triples for {@link adoptRouteArtifacts} install. */\nexport interface CompiledArtifactGuards<\n Dependencies extends DefaultDependencies,\n> {\n activate: [string, GuardFnFactory<Dependencies>, GuardFn][];\n deactivate: [string, GuardFnFactory<Dependencies>, GuardFn][];\n}\n\n/**\n * Compiles an artifacts' pending guard factories up front (#956), THROWING on\n * the first factory that throws on compile or returns a non-function.\n *\n * `replaceRoutes` calls this in its PREPARE phase — **before**\n * `clearDefinitionGuards()` — and hands the result to `adoptRouteArtifacts`, so\n * a compile-throw aborts with BOTH the tree AND the old definition guards intact\n * (#1193, mirroring #1046's handler-limit hoist). `add` has no clear step, so\n * `adoptRouteArtifacts` compiles inline for it.\n */\nexport function compileArtifactGuards<Dependencies extends DefaultDependencies>(\n artifacts: RouteArtifacts<Dependencies>,\n deps: RoutesDependencies<Dependencies>,\n): CompiledArtifactGuards<Dependencies> {\n return {\n activate: compilePendingGuards(\n artifacts.pendingCanActivate,\n deps.compileGuard,\n \"canActivate\",\n ),\n deactivate: compilePendingGuards(\n artifacts.pendingCanDeactivate,\n deps.compileGuard,\n \"canDeactivate\",\n ),\n };\n}\n\n/**\n * Commits prepared artifacts into the store in place. Every pending guard\n * factory is compiled BEFORE the tree/config swap (#956): a factory that throws\n * on compile (or returns a non-function) aborts here with the store untouched,\n * so `add`/`replace` are atomic for malformed guards too — not just core build\n * errors. The tree/config assignments are pure and cannot throw; the\n * pre-compiled guards are then installed without re-compiling (the factory ran\n * once, at the pre-compile above). `depsStore` is always set on a wired router,\n * which is the only path that reaches `add`/`replace`.\n */\nexport function adoptRouteArtifacts<Dependencies extends DefaultDependencies>(\n store: RoutesStore<Dependencies>,\n artifacts: RouteArtifacts<Dependencies>,\n precompiled?: CompiledArtifactGuards<Dependencies>,\n): void {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- depsStore is set once the router is wired; add/replace only run on a wired router (constructor-time registration uses createRoutesStore)\n const deps = store.depsStore!;\n\n // Pre-swap compile: surfaces a malformed guard factory before any mutation.\n // `replace()` pre-compiles in its PREPARE phase (BEFORE clearDefinitionGuards)\n // and passes the result here, so a compile-throw never erases the old\n // definition guards (#1193); `add` has no clear step and compiles inline.\n const { activate: compiledActivate, deactivate: compiledDeactivate } =\n precompiled ?? compileArtifactGuards(artifacts, deps);\n\n // Atomic swap — pure assignments, cannot throw. (`definitions` is derived\n // from `tree`, so swapping the tree IS the definitions swap.)\n Object.assign(store.config, artifacts.config);\n store.routeCustomFields = artifacts.routeCustomFields;\n store.tree = artifacts.tree;\n store.matcher = artifacts.matcher;\n store.urlParamsCache.clear();\n store.resolvedForwardMap = artifacts.resolvedForwardMap;\n\n // Install pre-compiled guards — no re-compile, no throw.\n for (const [name, factory, fn] of compiledActivate) {\n deps.addActivateGuard(name, factory, fn);\n }\n\n for (const [name, factory, fn] of compiledDeactivate) {\n deps.addDeactivateGuard(name, factory, fn);\n }\n}\n\n/**\n * COMMIT core for `update()` — the field-patch counterpart to\n * {@link adoptRouteArtifacts} (add/replace) / {@link commitTreeChanges} (remove)\n * / {@link resetStore} (clear), co-located here so all four route-CRUD commit\n * cores live in one file. Stays **NO_TREE_REBUILD**: an O(1) per-field patch\n * that never rebuilds the tree/matcher (so it must NOT funnel through\n * `adoptRouteArtifacts`).\n *\n * Prepare-then-commit (#951): every throwing step runs in PREPARE, before any\n * store write, so a rejected update leaves the route's prior config fully intact\n * — an async/cyclic `forwardTo` (#967), a guard factory that throws on compile\n * (#956 seam), a throwing custom-field getter, and the #961 handler-limit\n * pre-flight (#1046). Returns the structural fields for the caller's conditional\n * TREE_CHANGED emit, computed from the single destructure here so core invokes\n * each user getter once (#797 / #952 `null`-clears-definition-only preserved).\n */\nexport function commitRouteUpdate<Dependencies extends DefaultDependencies>(\n store: RoutesStore<Dependencies>,\n lifecycle: RouteLifecycleNamespace<Dependencies>,\n name: string,\n updates: RouteConfigUpdate<Dependencies>,\n): {\n forwardTo?: string | ForwardToCallback<Dependencies> | null | undefined;\n defaultParams?: Params | null | undefined;\n decodeParams?: ((params: Params) => Params) | null | undefined;\n encodeParams?: ((params: Params) => Params) | null | undefined;\n} {\n const {\n forwardTo,\n defaultParams,\n decodeParams,\n encodeParams,\n canActivate,\n canDeactivate,\n } = updates;\n\n // ===== PREPARE — compute every change into LOCALS. Any throw here aborts\n // before a single store write, so the whole field set is applied\n // all-or-nothing (#951).\n const forwardToPlan =\n forwardTo === undefined\n ? undefined\n : prepareForwardTo(name, forwardTo, store.config);\n\n const nextCustomFields = prepareCustomFields(store, name, updates);\n\n // Guard factories are compiled NOW (a throwing factory surfaces in PREPARE);\n // the precompiled function is installed in COMMIT without re-invoking the\n // factory, so a factory side effect runs exactly once (reuses the #956\n // compile-then-install seam). Compiled after the other prepares so a throw\n // upstream skips invoking the factory at all.\n const activateFn =\n canActivate === undefined || canActivate === null\n ? undefined\n : lifecycle.compileGuardFactory(canActivate, \"canActivate\");\n const deactivateFn =\n canDeactivate === undefined || canDeactivate === null\n ? undefined\n : lifecycle.compileGuardFactory(canDeactivate, \"canDeactivate\");\n\n // Pre-flight the #961 handler-limit before the COMMIT writes, so an at-limit\n // update that adds a NEW guard slot aborts before forwardTo / scalar config\n // land (#1046, #951). A slot is new only when `name` does not already hold a\n // guard of that type — an overwrite does not count.\n lifecycle.preflightHandlerLimit(\n activateFn === undefined ? [] : [name],\n deactivateFn === undefined ? [] : [name],\n false,\n );\n\n // ===== COMMIT — pure writes from here; nothing below throws.\n // Custom (plugin-defined) fields. Consumers read these lazily via\n // getRouteConfig (lifecycle hooks, preload, searchSchema), so no TREE_CHANGED\n // is needed — the next read sees the new value; the caller's emit stays\n // structural-only by design (О-7).\n if (nextCustomFields !== undefined) {\n if (Object.keys(nextCustomFields).length > 0) {\n store.routeCustomFields[name] = nextCustomFields;\n } else {\n delete store.routeCustomFields[name];\n }\n }\n\n if (forwardToPlan !== undefined) {\n store.config.forwardMap = forwardToPlan.forwardMap;\n store.config.forwardFnMap = forwardToPlan.forwardFnMap;\n store.resolvedForwardMap = forwardToPlan.resolved;\n }\n\n commitScalarConfig(store, name, {\n defaultParams,\n decodeParams,\n encodeParams,\n });\n\n // Install the guards from their PREPARE-phase precompiled functions; a `null`\n // clears the definition-origin guard only (#952). See commitGuardUpdate.\n commitGuardUpdate(lifecycle, \"activate\", name, canActivate, activateFn);\n commitGuardUpdate(lifecycle, \"deactivate\", name, canDeactivate, deactivateFn);\n\n return { forwardTo, defaultParams, decodeParams, encodeParams };\n}\n\n/**\n * PREPARE step for a `forwardTo` update (#951 atomicity): computes the new\n * forward maps and the resolved forward chain into LOCALS and returns them\n * WITHOUT touching the store. A throw here — an async `forwardTo` (#967) or a\n * cycle surfaced by `refreshForwardMap` — aborts `update()` before any field is\n * committed. The caller writes the returned bundle into the store in its COMMIT\n * phase. (Mirrors the build-then-swap shape of #698, but the swap is deferred to\n * the caller so it can be sequenced with the other prepared fields.)\n */\nfunction prepareForwardTo<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n name: string,\n forwardTo: string | ForwardToCallback<Dependencies> | null,\n config: RouteConfig,\n): {\n forwardMap: RouteConfig[\"forwardMap\"];\n forwardFnMap: RouteConfig[\"forwardFnMap\"];\n resolved: Record<string, string>;\n} {\n // #967: reject an async forwardTo at update time — parity with add/replace\n // (registerForwardTo runs the same check on the build path). A no-op for\n // string/null. Without this the async callback is stored silently and\n // surfaces later as a generic \"must return a string, got object\" TypeError\n // from #resolveDynamicForward at navigation. Runs first, before any clone.\n assertForwardToNotAsync(forwardTo, name);\n\n const forwardMap = Object.assign(\n Object.create(null) as RouteConfig[\"forwardMap\"],\n config.forwardMap,\n );\n const forwardFnMap = Object.assign(\n Object.create(null) as RouteConfig[\"forwardFnMap\"],\n config.forwardFnMap,\n );\n\n if (forwardTo === null) {\n delete forwardMap[name];\n delete forwardFnMap[name];\n } else if (typeof forwardTo === \"string\") {\n delete forwardFnMap[name];\n forwardMap[name] = forwardTo;\n } else {\n delete forwardMap[name];\n forwardFnMap[name] = forwardTo;\n }\n\n const resolved = refreshForwardMap({ ...config, forwardMap });\n\n return { forwardMap, forwardFnMap, resolved };\n}\n\n/**\n * PREPARE step for a route's plugin-defined **custom fields** (#951) — the\n * `update` counterpart to how `add`/`replace` register them\n * (`registerSingleRouteHandlers`). A custom field is any patch key not in\n * {@link STANDARD_ROUTE_KEYS}.\n *\n * Computes the merged record and RETURNS it for the caller to commit;\n * `undefined` means no custom-field key was present, so the caller leaves the\n * store untouched. Semantics mirror the scalar fields in\n * {@link commitScalarConfig}: shallow-merge by patch key, `null` removes a\n * single field, `undefined` is a no-op (leaves the field untouched). When the\n * merge empties the record, the caller drops the whole entry so `getRouteConfig`\n * returns `undefined` — symmetric with `add`, which only stores a record when at\n * least one custom field exists.\n *\n * Reading the custom-field getters HERE (in PREPARE, not at commit) is what lets\n * a throwing getter abort the whole update before any field is written. The\n * merged record is a **fresh object**, never mutated in place: `cloneRouter`\n * shares per-route custom-field records by reference (`Object.assign`), so\n * replacing the reference keeps a clone isolated from post-clone updates on the\n * source.\n */\nfunction prepareCustomFields<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n store: RoutesStore<Dependencies>,\n name: string,\n updates: RouteConfigUpdate<Dependencies>,\n): Record<string, unknown> | undefined {\n let next: Record<string, unknown> | undefined;\n\n // `Object.keys` (not `Object.entries`): a value is read only AFTER the\n // standard-key guard, so structural-field getters — already read once by\n // `update`'s destructuring — are not re-invoked. `Object.entries` would read\n // every value eagerly, double-invoking a `defaultParams`/`forwardTo` getter\n // and breaking the \"user getter called once\" invariant.\n // eslint-disable-next-line unicorn/prefer-object-iterable-methods -- see above\n for (const key of Object.keys(updates)) {\n if (STANDARD_ROUTE_KEYS.has(key)) {\n continue;\n }\n\n const value = (updates as Record<string, unknown>)[key];\n\n // `undefined` mirrors the structural path: leave the field untouched.\n if (value === undefined) {\n continue;\n }\n\n // Clone-on-first-write — keeps clones (which alias this record) isolated.\n next ??= { ...store.routeCustomFields[name] };\n\n if (value === null) {\n delete next[key];\n } else {\n next[key] = value;\n }\n }\n\n return next;\n}\n\n/**\n * COMMIT step for the scalar config fields of an update (#951): writes\n * `defaultParams` / `decodeParams` / `encodeParams` in place. These assignments\n * are pure and never throw, so they run in the COMMIT phase after every throwing\n * field has been validated in PREPARE. `forwardTo` is handled separately — it\n * has its own throwing prepare step ({@link prepareForwardTo}).\n */\nfunction commitScalarConfig<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n store: RoutesStore<Dependencies>,\n name: string,\n updates: {\n defaultParams?: Params | null | undefined;\n decodeParams?: ((params: Params) => Params) | null | undefined;\n encodeParams?: ((params: Params) => Params) | null | undefined;\n },\n): void {\n if (updates.defaultParams !== undefined) {\n if (updates.defaultParams === null) {\n delete store.config.defaultParams[name];\n } else {\n store.config.defaultParams[name] = updates.defaultParams;\n }\n }\n\n if (updates.decodeParams !== undefined) {\n if (updates.decodeParams === null) {\n delete store.config.decoders[name];\n } else {\n const decoder = updates.decodeParams;\n\n store.config.decoders[name] = (params: Params): Params =>\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- runtime fallback if user-provided decoder violates its return type\n decoder(params) ?? params;\n }\n }\n\n if (updates.encodeParams !== undefined) {\n if (updates.encodeParams === null) {\n delete store.config.encoders[name];\n } else {\n const encoder = updates.encodeParams;\n\n store.config.encoders[name] = (params: Params): Params =>\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- runtime fallback if user-provided encoder violates its return type\n encoder(params) ?? params;\n }\n }\n}\n\n/**\n * COMMIT step for one guard field of an update (#951). `undefined` is a no-op;\n * `null` clears the DEFINITION-origin guard only, preserving an external guard\n * (#952); a factory installs together with its PREPARE-phase `precompiledFn`\n * (no re-compile — #956 seam). Extracted from `update()` so its prepare/commit\n * orchestration stays within the cognitive-complexity budget.\n */\nfunction commitGuardUpdate<Dependencies extends DefaultDependencies>(\n lifecycle: RouteLifecycleNamespace<Dependencies>,\n kind: \"activate\" | \"deactivate\",\n name: string,\n value: GuardFnFactory<Dependencies> | null | undefined,\n precompiledFn: GuardFn | undefined,\n): void {\n if (value === undefined) {\n return;\n }\n\n if (kind === \"activate\") {\n if (value === null) {\n lifecycle.clearCanActivate(name, \"definition\");\n } else {\n lifecycle.addCanActivate(name, value, true, precompiledFn);\n }\n } else if (value === null) {\n lifecycle.clearCanDeactivate(name, \"definition\");\n } else {\n lifecycle.addCanDeactivate(name, value, true, precompiledFn);\n }\n}\n\n// =============================================================================\n// Factory\n// =============================================================================\n\nexport function createRoutesStore<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n routes: Route<Dependencies>[],\n matcherOptions: CreateMatcherOptions | undefined,\n logger: RouterLogger,\n): RoutesStore<Dependencies> {\n // Initial routes are a standalone set at rootPath \"\" — same build the\n // prepare-then-commit `replace` path uses. Guards land in the pending maps,\n // flushed by `flushPendingGuards()` at the end of the Router constructor\n // (#1331); `setDependencies` itself is a pure assignment.\n //\n // Reject the silent-corruption cases BEFORE building, giving the constructor\n // parity with `add()` / `replace()` — the third and last route-population\n // entry point (#1351): within-batch duplicate names (#953/#968) and reserved\n // \"@@\" names (#954). Without these the constructor silently last-wins a\n // duplicate-name sibling (the first route is dropped → its deep-link 404s)\n // while add/replace throw. `methodName` is \"addRoute\" so all three entry\n // points surface the identical bare-core error. (Duplicate PATHS are already\n // rejected downstream by the path-matcher backstop #1153, so they are not\n // re-checked here.)\n assertNoInternalNamesInBatch(routes, \"addRoute\");\n assertNoDuplicateNamesInBatch(routes, \"\", \"addRoute\");\n\n const artifacts = buildReplaceArtifacts(routes, \"\", matcherOptions, logger);\n\n const store: RoutesStore<Dependencies> = {\n // Deferred access: the getter runs only after `store` is initialized.\n get definitions() {\n return routeTreeToDefinitions(store.tree);\n },\n config: artifacts.config,\n tree: artifacts.tree,\n matcher: artifacts.matcher,\n urlParamsCache: new Map(),\n resolvedForwardMap: artifacts.resolvedForwardMap,\n routeCustomFields: artifacts.routeCustomFields,\n rootPath: \"\",\n matcherOptions,\n depsStore: undefined,\n lifecycleNamespace: undefined,\n pendingCanActivate: artifacts.pendingCanActivate,\n pendingCanDeactivate: artifacts.pendingCanDeactivate,\n };\n\n return store;\n}\n","// packages/core/src/transitionPath.ts\n\nimport { getStateMetaParams } from \"./stateMetaStore\";\n\nimport type { State } from \"./types\";\n\n/**\n * Parameters extracted from a route segment.\n * Maps parameter names to their string values.\n */\ntype PrimitiveParam = string | number | boolean;\n\n/**\n * Represents a transition path between two router states.\n * Contains information about which route segments need to be activated/deactivated.\n */\nexport interface TransitionPath {\n /** The common ancestor route segment where paths diverge */\n intersection: string;\n /** Route segments that need to be deactivated (in reverse order) */\n toDeactivate: string[];\n /** Route segments that need to be activated (in order) */\n toActivate: string[];\n}\n\n// Constants for better maintainability\nconst ROUTE_SEGMENT_SEPARATOR = \".\";\nconst EMPTY_INTERSECTION = \"\";\nconst DEFAULT_ROUTE_NAME = \"\";\nconst FROZEN_EMPTY_ARRAY: string[] = [];\n\nObject.freeze(FROZEN_EMPTY_ARRAY);\n\n/**\n * Handles conversion of route names with many segments (5+).\n * Internal helper for nameToIDs function.\n *\n * Uses optimized hybrid approach: split to get segments, then slice original\n * string to build cumulative paths. This approach is 65-81% faster than\n * string concatenation for typical cases (5-10 segments).\n *\n * @param name - Route name with 5 or more segments\n * @returns Array of cumulative segment IDs\n * @throws {Error} If route depth exceeds maximum allowed\n * @internal\n */\nfunction nameToIDsGeneral(name: string): string[] {\n // We know there are at least 5 segments at this point (after fast paths)\n const segments = name.split(ROUTE_SEGMENT_SEPARATOR);\n const segmentCount = segments.length;\n\n // First segment is always just itself\n const ids: string[] = [segments[0]];\n\n // Calculate cumulative lengths and slice from original string\n // This avoids repeated string concatenation (O(k²) → O(k))\n let cumulativeLength = segments[0].length;\n\n for (let i = 1; i < segmentCount - 1; i++) {\n cumulativeLength += 1 + segments[i].length; // +1 for dot separator\n ids.push(name.slice(0, cumulativeLength));\n }\n\n // Last segment is always the full route name\n ids.push(name);\n\n return ids;\n}\n\nconst PRIMITIVE_TYPES: ReadonlySet<string> = new Set([\n \"string\",\n \"number\",\n \"boolean\",\n]);\n\nfunction isPrimitive(value: unknown): value is PrimitiveParam {\n return PRIMITIVE_TYPES.has(typeof value);\n}\n\n/**\n * Compares segment parameters between two states without creating intermediate objects.\n * Returns true if all primitive params for the given segment are equal in both states.\n */\nfunction segmentParamsEqual(\n name: string,\n toMetaParams: Record<string, unknown>,\n toState: State,\n fromState: State,\n): boolean {\n const keys = toMetaParams[name];\n\n if (!keys || typeof keys !== \"object\") {\n return true;\n }\n\n for (const key of Object.keys(keys)) {\n const toVal = toState.params[key];\n const fromVal = fromState.params[key];\n\n if (\n isPrimitive(toVal) &&\n isPrimitive(fromVal) &&\n String(toVal) !== String(fromVal)\n ) {\n return false;\n }\n }\n\n return true;\n}\n\n/**\n * Finds the point where two state paths diverge based on segments and parameters.\n * Compares both segment names and their parameters to find the first difference.\n *\n * @param toMetaParams - Cached meta.params from toState (avoids per-segment WeakMap lookup)\n * @param toState - Target state\n * @param fromState - Source state\n * @param toStateIds - Segment IDs for target state\n * @param fromStateIds - Segment IDs for source state\n * @param maxI - Maximum index to check (minimum of both arrays)\n * @returns Index of first difference, or maxI if all checked segments match\n */\nfunction pointOfDifference(\n toMetaParams: Record<string, unknown>,\n toState: State,\n fromState: State,\n toStateIds: string[],\n fromStateIds: string[],\n maxI: number,\n): number {\n for (let i = 0; i < maxI; i++) {\n const toSegment = toStateIds[i];\n const fromSegment = fromStateIds[i];\n\n // Different segment names - immediate difference\n if (toSegment !== fromSegment) {\n return i;\n }\n\n if (!segmentParamsEqual(toSegment, toMetaParams, toState, fromState)) {\n return i;\n }\n }\n\n return maxI;\n}\n\n/**\n * Converts a route name to an array of hierarchical segment identifiers.\n * Each segment ID includes all parent segments in the path.\n *\n * @param name - Route name in dot notation (e.g., 'users.profile.edit')\n * @returns Array of cumulative segment IDs\n * @throws {Error} If route depth exceeds maximum allowed depth\n *\n * @example\n * // Simple route\n * nameToIDs('users');\n * // Returns: ['users']\n *\n * @example\n * // Nested route\n * nameToIDs('users.profile.edit');\n * // Returns: ['users', 'users.profile', 'users.profile.edit']\n *\n * @example\n * // Empty string (root route)\n * nameToIDs('');\n * // Returns: ['']\n *\n * @remarks\n * Input parameter is NOT validated in this function for performance reasons.\n * Validation significantly slows down nameToIDs execution.\n * The input should be validated by the function/method that calls nameToIDs.\n */\n// Module-global cache (shared across all router instances): bounded in practice by\n// the app's route-name vocabulary, which is stable across cloneRouter() requests, so\n// it does not grow per request. Intentionally NOT cleared on dispose() — it is not\n// per-router, so one router's teardown must not evict entries other routers rely on.\nconst nameToIDsCache = new Map<string, string[]>();\n\nexport function nameToIDs(name: string): string[] {\n const cached = nameToIDsCache.get(name);\n\n // Stryker disable next-line BlockStatement: equivalent — dropping the cache-hit early return recomputes the identical frozen id chain (the cache is a perf optimization, not a correctness gate).\n if (cached) {\n return cached;\n }\n\n const result = computeNameToIDs(name);\n\n Object.freeze(result);\n nameToIDsCache.set(name, result);\n\n return result;\n}\n\nfunction computeNameToIDs(name: string): string[] {\n if (!name) {\n return [DEFAULT_ROUTE_NAME];\n }\n\n const firstDot = name.indexOf(ROUTE_SEGMENT_SEPARATOR);\n\n if (firstDot === -1) {\n return [name];\n }\n\n const secondDot = name.indexOf(ROUTE_SEGMENT_SEPARATOR, firstDot + 1);\n\n if (secondDot === -1) {\n return [name.slice(0, firstDot), name];\n }\n\n const thirdDot = name.indexOf(ROUTE_SEGMENT_SEPARATOR, secondDot + 1);\n\n // Stryker disable next-line UnaryOperator,BlockStatement: equivalent — inverting/emptying the 3-segment fast path routes the name through nameToIDsGeneral (below), which yields the identical id chain (same rationale as the L242 ArithmeticOperator disable). The ConditionalExpression/EqualityOperator siblings stay live (→true and !== are killed).\n if (thirdDot === -1) {\n return [name.slice(0, firstDot), name.slice(0, secondDot), name];\n }\n\n // Stryker disable next-line ArithmeticOperator: equivalent — `thirdDot - 1` makes fourthDot non-(-1), routing 5+ segment names through nameToIDsGeneral, which yields the identical id chain.\n const fourthDot = name.indexOf(ROUTE_SEGMENT_SEPARATOR, thirdDot + 1);\n\n // Stryker disable next-line UnaryOperator,BlockStatement: equivalent — inverting/emptying the 4-segment fast path routes the name through nameToIDsGeneral (below), which yields the identical id chain (same rationale as the L242 ArithmeticOperator disable). The ConditionalExpression/EqualityOperator siblings stay live (→true and !== are killed).\n if (fourthDot === -1) {\n return [\n name.slice(0, firstDot),\n name.slice(0, secondDot),\n name.slice(0, thirdDot),\n name,\n ];\n }\n\n return nameToIDsGeneral(name);\n}\n\n/**\n * Calculates the transition path between two router states.\n * Determines which route segments need to be deactivated and activated\n * to transition from one state to another.\n *\n * @param toState - Target state to transition to\n * @param fromState - Current state to transition from (optional)\n * @returns Transition path with intersection and segments to activate/deactivate\n *\n * @throws {TypeError} When toState is null or undefined\n * @throws {TypeError} When toState is not an object\n * @throws {TypeError} When toState.name is missing or not a string\n * @throws {TypeError} When toState.params is missing or not an object\n * @throws {TypeError} When toState.path is missing or not a string\n * @throws {TypeError} When toState.name contains invalid route format:\n * - Contains only whitespace (e.g., \" \")\n * - Has consecutive dots (e.g., \"users..profile\")\n * - Has leading/trailing dots (e.g., \".users\" or \"users.\")\n * - Segments don't match pattern [a-zA-Z_][a-zA-Z0-9_-]* (e.g., \"users.123\")\n * - Contains spaces or special characters (e.g., \"users profile\")\n * - Exceeds maximum length (8192 characters)\n * @throws {TypeError} When fromState is provided and has any of the validation errors listed above for toState\n *\n * @example\n * // ✅ Valid calls\n * getTransitionPath({ name: 'users.profile', params: {}, path: '/users/profile' });\n * getTransitionPath(toState, fromState);\n * getTransitionPath({ name: '', params: {}, path: '/' }); // root route\n *\n * @example\n * // ❌ Invalid calls that throw TypeError\n * getTransitionPath(null); // toState is null\n * getTransitionPath(undefined); // toState is undefined\n * getTransitionPath({}); // missing required fields\n * getTransitionPath({ name: 123, params: {}, path: '/' }); // name not a string\n * getTransitionPath({ name: 'home', path: '/' }); // missing params\n * getTransitionPath({ name: 'users..profile', params: {}, path: '/' }); // consecutive dots\n * getTransitionPath({ name: '.users', params: {}, path: '/' }); // leading dot\n * getTransitionPath({ name: 'users.', params: {}, path: '/' }); // trailing dot\n * getTransitionPath({ name: 'users profile', params: {}, path: '/' }); // contains space\n * getTransitionPath({ name: 'users.123', params: {}, path: '/' }); // segment starts with number\n * getTransitionPath(validToState, { name: 'invalid..route', params: {}, path: '/' }); // fromState invalid\n *\n * @example\n * // Full activation (no fromState)\n * getTransitionPath(makeState('users.profile'));\n * // Returns: {\n * // intersection: '',\n * // toActivate: ['users', 'users.profile'],\n * // toDeactivate: []\n * // }\n *\n * @example\n * // Partial transition with common ancestor\n * getTransitionPath(\n * makeState('users.profile'),\n * makeState('users.list')\n * );\n * // Returns: {\n * // intersection: 'users',\n * // toActivate: ['users.profile'],\n * // toDeactivate: ['users.list']\n * // }\n *\n * @example\n * // Complete route change\n * getTransitionPath(\n * makeState('admin.dashboard'),\n * makeState('users.profile')\n * );\n * // Returns: {\n * // intersection: '',\n * // toActivate: ['admin', 'admin.dashboard'],\n * // toDeactivate: ['users.profile', 'users']\n * // }\n */\n// Single-entry cache: shouldUpdateNode calls getTransitionPath N times per\n// navigation with the same state objects (once per subscribed node).\n// Cache by reference eliminates N-1 redundant computations.\n// Module-global (≤2 State refs); not cleared on dispose — negligible, not per-router.\nlet cached1To: State | undefined;\nlet cached1From: State | undefined;\nlet cached1Result: TransitionPath | null = null;\n\nlet cached2To: State | undefined;\nlet cached2From: State | undefined;\nlet cached2Result: TransitionPath | null = null;\n\nfunction computeTransitionPath(\n toState: State,\n fromState?: State,\n): TransitionPath {\n // ===== FAST PATH 1: Initial navigation (no fromState) =====\n // This is the best performing case in benchmarks (5M ops/sec)\n if (!fromState) {\n return {\n intersection: EMPTY_INTERSECTION,\n toActivate: nameToIDs(toState.name),\n toDeactivate: FROZEN_EMPTY_ARRAY,\n };\n }\n\n // ===== FAST PATH 3: Missing meta requires full reload =====\n // Single WeakMap lookup per state, reused in pointOfDifference/segmentParamsEqual\n const toMetaParams = getStateMetaParams(toState);\n const fromMetaParams = getStateMetaParams(fromState);\n\n if (!toMetaParams && !fromMetaParams) {\n // FAST PATH 3 (both states meta-less). Consumers that land here read the\n // result order-INSENSITIVELY, so the from-chain is returned as-is\n // (root→leaf, no reverse needed):\n // • `shouldUpdateNode` reads `toDeactivate` by MEMBERSHIP (`.includes`).\n // • Externally-supplied meta-less states (e.g. a plugin passing a raw\n // `{name, params, path}` to `navigateToState`) land here. Since #1170,\n // `navigateToState` carries the source's WeakMap meta across its writable\n // shell, so start()/popstate states are NOT meta-less. A `replace()`\n // survivor stays meta-less but is benign: the next transition's `toState`\n // always carries meta (buildNavigateState), so this both-meta-less path\n // is not reached from it.\n // (`canNavigateTo` no longer reaches this path — since #970 it builds its\n // toState WITH meta, mirroring buildNavigateState.)\n // The navigate pipeline always carries meta (buildNavigateState) → STANDARD\n // PATH below, which trims the shared ancestor and reverses correctly.\n return {\n intersection: EMPTY_INTERSECTION,\n toActivate: nameToIDs(toState.name),\n toDeactivate: nameToIDs(fromState.name),\n };\n }\n\n // ===== STANDARD PATH: Routes with parameters =====\n const toStateIds = nameToIDs(toState.name);\n const fromStateIds = nameToIDs(fromState.name);\n // Stryker disable next-line MethodExpression: equivalent — Math.max reads one index past the shorter id array; that slot is undefined, so the `toSegment !== fromSegment` check in pointOfDifference returns the same divergence index Math.min would stop at.\n const maxI = Math.min(fromStateIds.length, toStateIds.length);\n\n const i = pointOfDifference(\n (toMetaParams ?? fromMetaParams) as Record<string, unknown>,\n toState,\n fromState,\n toStateIds,\n fromStateIds,\n maxI,\n );\n\n // Optimization: Build deactivation list in reverse order directly\n // instead of slice(i).toReversed() which creates 2 arrays\n let toDeactivate: string[];\n\n if (i >= fromStateIds.length) {\n toDeactivate = FROZEN_EMPTY_ARRAY;\n } else if (i === 0 && fromStateIds.length === 1) {\n // Single-segment route: reversed = original, reuse cached frozen array\n toDeactivate = fromStateIds;\n } else {\n toDeactivate = [];\n\n for (let j = fromStateIds.length - 1; j >= i; j--) {\n toDeactivate.push(fromStateIds[j]);\n }\n }\n\n // Build activation list — reuse cached frozen array when using full list\n const toActivate = i === 0 ? toStateIds : toStateIds.slice(i);\n\n // Determine intersection point (common ancestor)\n const intersection = i > 0 ? fromStateIds[i - 1] : EMPTY_INTERSECTION;\n\n return {\n intersection,\n toDeactivate,\n toActivate,\n };\n}\n\nexport function getTransitionPath(\n toState: State,\n fromState?: State,\n): TransitionPath {\n // Stryker disable BlockStatement: equivalent — both cache short-circuits below; emptying either early-return recomputes the identical TransitionPath (computeTransitionPath is deterministic for the same to/from states) and re-caches it. Restored right after.\n if (\n cached1Result !== null &&\n toState === cached1To &&\n fromState === cached1From\n ) {\n return cached1Result;\n }\n\n if (\n cached2Result !== null &&\n toState === cached2To &&\n fromState === cached2From\n ) {\n return cached2Result;\n }\n // Stryker restore BlockStatement\n\n const result = computeTransitionPath(toState, fromState);\n\n cached2To = cached1To;\n cached2From = cached1From;\n cached2Result = cached1Result;\n\n cached1To = toState;\n cached1From = fromState;\n cached1Result = result;\n\n return result;\n}\n","// packages/core/src/namespaces/RoutesNamespace/RoutesNamespace.ts\n\nimport { DEFAULT_ROUTE_NAME } from \"./constants\";\nimport {\n matchSourceTrailingSlash,\n paramsMatch,\n paramsMatchExcluding,\n stripQueryDefaults,\n} from \"./helpers\";\nimport {\n createRoutesStore,\n rebuildTreeInPlace,\n resetStore,\n} from \"./routesStore\";\nimport { constants, DEFAULT_TRANSITION } from \"../../constants\";\nimport { getTransitionPath } from \"../../transitionPath\";\n\nimport type { RoutesStore } from \"./routesStore\";\nimport type { RoutesDependencies } from \"./types\";\nimport type {\n CreateMatcherOptions,\n RouteParams,\n RouteTree,\n RouteTreeState,\n} from \"../../engine\";\nimport type {\n DefaultDependencies,\n ForwardToCallback,\n Options,\n Params,\n RouterLogger,\n State,\n Route,\n} from \"../../types\";\nimport type { RouteLifecycleNamespace } from \"../RouteLifecycleNamespace\";\n\nfunction collectUrlParamsArray(segments: readonly RouteTree[]): string[] {\n const params: string[] = [];\n\n for (const segment of segments) {\n for (const param of segment.paramMeta.urlParams) {\n params.push(param);\n }\n }\n\n return params;\n}\n\nfunction createRouteState<P extends RouteParams = RouteParams>(\n matchResult: {\n readonly segments: readonly { fullName: string }[];\n readonly params: Readonly<Record<string, unknown>>;\n readonly meta: Readonly<Record<string, Record<string, \"url\" | \"query\">>>;\n },\n name?: string,\n): RouteTreeState<P> {\n // The matcher yields ≥1 segment for every successful match, each carrying the\n // cumulative route name as `fullName`, so the last element is always present.\n // (Formerly `buildNameFromSegments` with a `?? \"\"` fallback — that branch was\n // unreachable defensive cruft propped up by a white-box test; inlined here.)\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- matcher invariant: a successful match is never empty\n const resolvedName = name ?? matchResult.segments.at(-1)!.fullName;\n\n return {\n name: resolvedName,\n params: matchResult.params as P,\n meta: matchResult.meta,\n };\n}\n\ninterface CachedBuildPathOpts {\n readonly trailingSlash?: \"always\" | \"never\" | undefined;\n readonly queryParamsMode?: \"default\" | \"strict\" | \"loose\" | undefined;\n}\n\n/**\n * Independent namespace for managing routes.\n *\n * Static methods handle validation (called by facade).\n * Instance methods handle storage and business logic.\n */\nexport class RoutesNamespace<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n> {\n readonly #store: RoutesStore<Dependencies>;\n #cachedBuildPathOpts: CachedBuildPathOpts | undefined;\n // Source `options` reference captured on the first #getBuildPathOptions call;\n // used only by the dev-build immutability assertion below (#957).\n #cachedOptionsSource: Options | undefined;\n\n get #deps(): RoutesDependencies<Dependencies> {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n return this.#store.depsStore!;\n }\n\n constructor(\n // No `= []` default: the sole caller (Router's ctor) always passes its own\n // already-defaulted `routes` — a namespace-level default would be dead code\n // and a default-before-required-params smell (S1788).\n routes: Route<Dependencies>[],\n matcherOptions: CreateMatcherOptions | undefined,\n logger: RouterLogger,\n ) {\n this.#store = createRoutesStore(routes, matcherOptions, logger);\n }\n\n /**\n * Creates a predicate function to check if a route node should be updated.\n * Note: Argument validation is done by facade (Router.ts) via validateShouldUpdateNodeArgs.\n */\n static shouldUpdateNode(\n nodeName: string,\n ): (toState: State, fromState?: State) => boolean {\n return (toState: State, fromState?: State): boolean => {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (!(toState && typeof toState === \"object\" && \"name\" in toState)) {\n throw new TypeError(\n \"[router.shouldUpdateNode] toState must be valid State object\",\n );\n }\n\n if (toState.transition.reload) {\n return true;\n }\n\n // Root node (DEFAULT_ROUTE_NAME === \"\") has no route-level identity — it\n // represents \"any route\". It must update on every transition so that\n // consumers subscribed via useRouteNode(\"\") (including RouteView at\n // the top of the tree) see every change. This matches the documented\n // contract in adapter docs: `useRouteNode(\"\")` — Root — ALL route\n // changes. See #519 for the missed transitions it was suffering from\n // (users → users.user had intersection=\"users\", leaving the root node\n // un-updated under a flat <Match segment=\"users.user\" exact> pattern).\n if (nodeName === DEFAULT_ROUTE_NAME) {\n return true;\n }\n\n const { intersection, toActivate, toDeactivate } = getTransitionPath(\n toState,\n fromState,\n );\n\n if (nodeName === intersection) {\n return true;\n }\n\n if (toActivate.includes(nodeName)) {\n return true;\n }\n\n return toDeactivate.includes(nodeName);\n };\n }\n\n // =========================================================================\n // Dependency injection\n // =========================================================================\n\n /**\n * Sets dependencies. Pure assignment — no side effects (#1331).\n *\n * The pending canActivate/canDeactivate factories from initial routes are\n * flushed separately by {@link flushPendingGuards}, called once wiring is\n * complete, so the order of the wire-* calls is unconstrained.\n */\n setDependencies(deps: RoutesDependencies<Dependencies>): void {\n this.#store.depsStore = deps;\n }\n\n /**\n * Registers the pending guard factories collected from initial route\n * definitions. Deferred out of {@link setDependencies} (#1331) so it runs on\n * a fully-built, fully-bound router: a guard factory that calls any\n * `router.*` method sees a ready instance instead of a half-assembled one.\n *\n * Invoked as the last step of the Router constructor. Idempotent after the\n * first call (the pending maps are cleared). Runtime `add()`/`replace()`\n * compile guards in their own PREPARE phase and never populate these maps.\n */\n flushPendingGuards(): void {\n const deps = this.#deps;\n\n for (const [routeName, handler] of this.#store.pendingCanActivate) {\n deps.addActivateGuard(routeName, handler);\n }\n\n this.#store.pendingCanActivate.clear();\n\n for (const [routeName, handler] of this.#store.pendingCanDeactivate) {\n deps.addDeactivateGuard(routeName, handler);\n }\n\n this.#store.pendingCanDeactivate.clear();\n }\n\n /**\n * Sets the lifecycle namespace reference.\n */\n setLifecycleNamespace(\n namespace: RouteLifecycleNamespace<Dependencies> | undefined,\n ): void {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n this.#store.lifecycleNamespace = namespace!;\n }\n\n // =========================================================================\n // Route tree operations\n // =========================================================================\n\n setRootPath(newRootPath: string): void {\n this.#store.rootPath = newRootPath;\n rebuildTreeInPlace(this.#store);\n }\n\n hasRoute(name: string): boolean {\n return this.#store.matcher.hasRoute(name);\n }\n\n clearRoutes(): void {\n resetStore(this.#store);\n }\n\n // =========================================================================\n // Path operations\n // =========================================================================\n\n /**\n * Builds a URL path for a route.\n * Note: Argument validation is done by facade (Router.ts) via validateBuildPathArgs.\n *\n * @param route - Route name\n * @param params - Route parameters\n * @param options - Router options\n */\n buildPath(route: string, params?: Params, options?: Options): string {\n if (route === constants.UNKNOWN_ROUTE) {\n return typeof params?.path === \"string\" ? params.path : \"\";\n }\n\n const paramsWithDefault = Object.hasOwn(\n this.#store.config.defaultParams,\n route,\n )\n ? { ...this.#store.config.defaultParams[route], ...params }\n : /* v8 ignore next -- @preserve: V8 can't track ?? branch in ternary; covered by buildPath tests without params */ (params ??\n {});\n\n const encodedParams =\n typeof this.#store.config.encoders[route] === \"function\"\n ? this.#store.config.encoders[route]({ ...paramsWithDefault })\n : paramsWithDefault;\n\n return this.#store.matcher.buildPath(\n route,\n encodedParams,\n this.#getBuildPathOptions(options),\n );\n }\n\n /**\n * Matches a URL path to a route in the tree.\n * Note: Argument validation is done by facade (Router.ts) via validateMatchPathArgs.\n */\n matchPath<P extends Params = Params>(\n path: string,\n options?: Options,\n ): State<P> | undefined {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- Router.ts always passes options\n const opts = options!;\n\n const matchResult = this.#store.matcher.match(path);\n\n if (!matchResult) {\n return undefined;\n }\n\n const routeState = createRouteState(matchResult);\n const { name, params, meta } = routeState;\n\n const decodedParams =\n typeof this.#store.config.decoders[name] === \"function\"\n ? this.#store.config.decoders[name](params)\n : params;\n\n const { name: routeName, params: routeParams } = this.#deps.forwardState<P>(\n name,\n decodedParams as P,\n );\n\n let builtPath = path;\n\n if (opts.rewritePathOnMatch) {\n const buildParams =\n typeof this.#store.config.encoders[routeName] === \"function\"\n ? this.#store.config.encoders[routeName]({\n ...(routeParams as Params),\n })\n : (routeParams as Record<string, unknown>);\n\n const ts = opts.trailingSlash;\n\n try {\n builtPath = this.#store.matcher.buildPath(routeName, buildParams, {\n trailingSlash: ts === \"never\" || ts === \"always\" ? ts : undefined,\n queryParamsMode: opts.queryParamsMode,\n });\n\n if (ts === \"preserve\") {\n builtPath = matchSourceTrailingSlash(path, builtPath);\n }\n } catch {\n // The match already succeeded (route found, params decoded); only the\n // post-match path rewrite threw — e.g. a custom encoder handed buildPath\n // a query value its codec cannot serialise. Keep the source path\n // un-rewritten rather than discard a valid match (#1157). Opposite of the\n // parse side (#737): there a throw means \"URL not understood\" → unmatched;\n // here the URL WAS matched and only re-canonicalisation failed.\n builtPath = path;\n }\n }\n\n return this.#deps.makeState<P>(routeName, routeParams, builtPath, meta);\n }\n\n /**\n * Applies forwardTo and returns resolved state with merged defaultParams.\n *\n * Merges params in order:\n * 1. Source route defaultParams\n * 2. Provided params\n * 3. Target route defaultParams (after resolving forwardTo)\n */\n forwardState<P extends Params = Params>(\n name: string,\n params: P,\n ): { name: string; params: P } {\n if (Object.hasOwn(this.#store.config.forwardFnMap, name)) {\n const paramsWithSourceDefaults = this.#mergeDefaultParams(name, params);\n const dynamicForward = this.#store.config.forwardFnMap[name];\n const resolved = this.#resolveDynamicForward(\n name,\n dynamicForward,\n params,\n );\n\n return {\n name: resolved,\n params: this.#mergeDefaultParams(resolved, paramsWithSourceDefaults),\n };\n }\n\n const staticForward = this.#store.resolvedForwardMap[name] ?? name;\n\n if (\n staticForward !== name &&\n Object.hasOwn(this.#store.config.forwardFnMap, staticForward)\n ) {\n const paramsWithSourceDefaults = this.#mergeDefaultParams(name, params);\n const targetDynamicForward =\n this.#store.config.forwardFnMap[staticForward];\n const resolved = this.#resolveDynamicForward(\n staticForward,\n targetDynamicForward,\n params,\n );\n\n return {\n name: resolved,\n params: this.#mergeDefaultParams(resolved, paramsWithSourceDefaults),\n };\n }\n\n if (staticForward !== name) {\n const paramsWithSourceDefaults = this.#mergeDefaultParams(name, params);\n\n return {\n name: staticForward,\n params: this.#mergeDefaultParams(\n staticForward,\n paramsWithSourceDefaults,\n ),\n };\n }\n\n return { name, params: this.#mergeDefaultParams(name, params) };\n }\n\n /**\n * Builds a RouteTreeState from already-resolved route name and params.\n * Called by Router.buildState after forwardState is applied at facade level.\n * This allows plugins to intercept forwardState.\n */\n buildStateResolved(\n resolvedName: string,\n resolvedParams: Params,\n ): RouteTreeState | undefined {\n const segments = this.#store.matcher.getSegmentsByName(resolvedName);\n\n if (!segments) {\n return undefined;\n }\n\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const meta = this.#store.matcher.getMetaByName(resolvedName)!;\n\n return createRouteState(\n { segments, params: resolvedParams, meta },\n resolvedName,\n );\n }\n\n // =========================================================================\n // Query operations\n // =========================================================================\n\n /**\n * Checks if a route is currently active.\n */\n isActiveRoute(\n name: string,\n params: Params = {},\n strictEquality = false,\n ignoreQueryParams = true,\n ): boolean {\n // Note: empty string check is handled by Router.ts facade\n const activeState = this.#deps.getState();\n\n if (!activeState) {\n return false;\n }\n\n const activeName = activeState.name;\n\n // Fast path: check if routes are related before expensive operations\n if (\n activeName !== name &&\n !activeName.startsWith(`${name}.`) &&\n !name.startsWith(`${activeName}.`)\n ) {\n return false;\n }\n\n const defaultParams = this.#store.config.defaultParams[name] as\n Params | undefined;\n\n // Exact match case\n if (strictEquality || activeName === name) {\n const effectiveParams = defaultParams\n ? { ...defaultParams, ...params }\n : params;\n\n const targetState: State = {\n name,\n params: effectiveParams,\n path: \"\",\n transition: DEFAULT_TRANSITION,\n context: {},\n };\n\n return this.#deps.areStatesEqual(\n targetState,\n activeState,\n ignoreQueryParams,\n );\n }\n\n // The fast path above lets through three relations: exact (handled in\n // the previous block), `activeName` descendant of `name`, and `name`\n // descendant of `activeName`. Only the first two count as \"active\" —\n // a link pointing DEEPER than the current state is a navigation option,\n // not an active state. Reject the descendant-of-active case explicitly.\n if (!activeName.startsWith(`${name}.`)) {\n return false;\n }\n\n // Hierarchical check: activeState is a descendant of target (name)\n const activeParams = activeState.params;\n\n if (!paramsMatch(params, activeParams)) {\n return false;\n }\n\n if (!defaultParams) {\n return true;\n }\n\n // Honor `ignoreQueryParams` symmetrically with the exact-match branch\n // above: query-only param differences (e.g. parent has\n // `defaultParams: { sort: \"asc\" }` while the active descendant is\n // `products.detail` with `params: { id: \"6\" }` and no sort) must not\n // disqualify an ancestor link from being active. Strip query-typed\n // keys of `name` from the defaults before comparison; URL-typed keys\n // (`:id`, `:role`, etc.) are still enforced.\n // `name` reaches this point only after the fast-path established a valid\n // hierarchical relation AND `defaultParams` is non-null — both imply the\n // matcher has registered the route. Since the #1414 skip-empty meta, a\n // fully-static route resolves to the shared EMPTY_ROUTE_META with no own\n // entry, so `getMetaByName(name)?.[name]` is undefined there — exactly the\n // \"nothing to strip\" signal stripQueryDefaults short-circuits on (a static\n // route cannot declare query params, hence cannot carry query defaults).\n const defaultsToCheck = ignoreQueryParams\n ? stripQueryDefaults(\n defaultParams,\n this.#store.matcher.getMetaByName(name)?.[name],\n )\n : defaultParams;\n\n return paramsMatchExcluding(defaultsToCheck, activeParams, params);\n }\n\n getMetaForState(\n name: string,\n ): Record<string, Record<string, \"url\" | \"query\">> | undefined {\n return this.#store.matcher.hasRoute(name)\n ? this.#store.matcher.getMetaByName(name)\n : undefined;\n }\n\n getUrlParams(name: string): string[] {\n const cached = this.#store.urlParamsCache.get(name);\n\n // Stryker disable next-line BlockStatement: equivalent — cache short-circuit; emptying the early-return recomputes the identical value (getUrlParams is deterministic per route name) and re-caches it. (ConditionalExpression stays live: `→true` returns undefined on a cache miss = killed.)\n if (cached !== undefined) {\n return cached;\n }\n\n const segments = this.#store.matcher.getSegmentsByName(name);\n const result = segments\n ? collectUrlParamsArray(segments as readonly RouteTree[])\n : [];\n\n this.#store.urlParamsCache.set(name, result);\n\n return result;\n }\n\n getStore(): RoutesStore<Dependencies> {\n return this.#store;\n }\n\n #mergeDefaultParams<P extends Params = Params>(\n routeName: string,\n params: P,\n ): P {\n if (Object.hasOwn(this.#store.config.defaultParams, routeName)) {\n return {\n ...this.#store.config.defaultParams[routeName],\n ...params,\n };\n }\n\n return params;\n }\n\n #getBuildPathOptions(options?: Options): CachedBuildPathOpts {\n // Stryker disable next-line BlockStatement: equivalent — cache short-circuit; emptying the early-return rebuilds the identical buildPath options (deterministic) and re-caches them. (ConditionalExpression stays live: `→false` always rebuilds but a real consumer test pins the cached identity.)\n if (this.#cachedBuildPathOpts) {\n /* v8 ignore next 5 -- @preserve: dev assertion guarding a future caller that passes per-call varying options; the sole caller (Router.buildPath, always via this.#options.get()) passes the same immutable, deep-frozen per-instance options, so this branch is unreachable through the public API by construction (#957) */\n if (options !== this.#cachedOptionsSource) {\n this.#deps.logger.warn(\n \"router.buildPath\",\n \"`options` differs from the cached source reference; router options are immutable per router instance, so the first-cached buildPath options are reused (#957).\",\n );\n }\n\n return this.#cachedBuildPathOpts;\n }\n\n this.#cachedOptionsSource = options;\n\n const ts = options?.trailingSlash;\n\n this.#cachedBuildPathOpts = Object.freeze({\n trailingSlash: ts === \"never\" || ts === \"always\" ? ts : undefined,\n queryParamsMode: options?.queryParamsMode,\n });\n\n return this.#cachedBuildPathOpts;\n }\n\n #resolveDynamicForward(\n startName: string,\n startFn: ForwardToCallback<Dependencies>,\n params: Params,\n ): string {\n const visited = new Set<string>([startName]);\n\n let current = startFn(this.#deps.getDependency, params);\n let depth = 0;\n const MAX_DEPTH = 100;\n\n if (typeof current !== \"string\") {\n throw new TypeError(\n `forwardTo callback must return a string, got ${typeof current}`,\n );\n }\n\n while (depth < MAX_DEPTH) {\n if (this.#store.matcher.getSegmentsByName(current) === undefined) {\n throw new Error(`Route \"${current}\" does not exist`);\n }\n\n if (visited.has(current)) {\n const chain = [...visited, current].join(\" → \");\n\n throw new Error(`Circular forwardTo: ${chain}`);\n }\n\n visited.add(current);\n\n if (Object.hasOwn(this.#store.config.forwardFnMap, current)) {\n const fn = this.#store.config.forwardFnMap[\n current\n ] as ForwardToCallback<Dependencies>;\n\n current = fn(this.#deps.getDependency, params);\n\n depth++;\n continue;\n }\n\n const staticForward = this.#store.config.forwardMap[current];\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (staticForward !== undefined) {\n current = staticForward;\n depth++;\n continue;\n }\n\n return current;\n }\n\n throw new Error(`forwardTo exceeds maximum depth of ${MAX_DEPTH}`);\n }\n}\n","// packages/core/src/RouterError.ts\n\nimport { errorCodes } from \"./constants\";\n\n// Pre-compute Set of error code values for O(1) lookup in setCode()\n// This avoids creating array and doing linear search on every setCode() call\nconst errorCodeValues = new Set(Object.values(errorCodes));\n\n// Reserved built-in properties - throw error if user tries to set these\nconst reservedProperties = new Set([\"code\", \"segment\", \"path\"]);\n\n// Reserved method names - silently ignore attempts to overwrite these\nconst reservedMethods = new Set([\n \"setCode\",\n \"setErrorInstance\",\n \"setAdditionalFields\",\n \"hasField\",\n \"getField\",\n \"toJSON\",\n]);\n\nexport class RouterError extends Error {\n [key: string]: unknown;\n\n // Using public properties to ensure structural compatibility\n // with RouterError interface in core-types\n readonly segment: string | undefined;\n readonly path: string | undefined;\n\n // Note: code appears to be writable but setCode() should be used\n // to properly update both code and message together\n code: string;\n\n /**\n * Creates a new RouterError instance.\n *\n * The options object accepts built-in fields (message, segment, path)\n * and any additional custom fields, which will all be attached to the error instance.\n *\n * @param code - The error code (e.g., \"ROUTE_NOT_FOUND\", \"CANNOT_ACTIVATE\")\n * @param options - Optional configuration object\n * @param options.message - Custom error message (defaults to code if not provided)\n * @param options.segment - The route segment where the error occurred\n * @param options.path - The full path where the error occurred\n *\n * @example\n * ```typescript\n * // Basic error\n * const err1 = new RouterError(\"ROUTE_NOT_FOUND\");\n *\n * // Error with custom message\n * const err2 = new RouterError(\"ERR\", { message: \"Something went wrong\" });\n *\n * // Error with context and custom fields\n * const err3 = new RouterError(\"CANNOT_ACTIVATE\", {\n * message: \"Insufficient permissions\",\n * segment: \"admin\",\n * path: \"/admin/users\",\n * userId: \"123\" // custom field\n * });\n * ```\n */\n constructor(\n code: string,\n {\n message,\n segment,\n path,\n ...rest\n }: {\n [key: string]: unknown;\n message?: string | undefined;\n segment?: string | undefined;\n path?: string | undefined;\n } = {},\n ) {\n super(message ?? code);\n\n // Subclasses don't auto-set `name`; without this `error.name` inherits\n // \"Error\", breaking `error.name === \"RouterError\"` checks at catch sites that\n // can't `instanceof` across bundle boundaries.\n this.name = \"RouterError\";\n\n this.code = code;\n this.segment = segment;\n this.path = path;\n\n // Assign custom fields, checking reserved properties and filtering out reserved method names\n // Issue #39: Throw for reserved properties to match setAdditionalFields behavior\n for (const [key, value] of Object.entries(rest)) {\n if (reservedProperties.has(key)) {\n throw new TypeError(\n `[RouterError] Cannot set reserved property \"${key}\"`,\n );\n }\n\n if (!reservedMethods.has(key)) {\n this[key] = value;\n }\n }\n }\n\n /**\n * Updates the error code and conditionally updates the message.\n *\n * If the current message is one of the standard error code values\n * (e.g., \"ROUTE_NOT_FOUND\", \"SAME_STATES\"), it will be replaced with the new code.\n * This allows keeping error messages in sync with codes when using standard error codes.\n *\n * If the message is custom (not a standard error code), it will be preserved.\n *\n * @param newCode - The new error code to set\n *\n * @example\n * // Message follows code (standard error code as message)\n * const err = new RouterError(\"ROUTE_NOT_FOUND\", { message: \"ROUTE_NOT_FOUND\" });\n * err.setCode(\"CUSTOM_ERROR\"); // message becomes \"CUSTOM_ERROR\"\n *\n * @example\n * // Custom message is preserved\n * const err = new RouterError(\"ERR\", { message: \"Custom error message\" });\n * err.setCode(\"NEW_CODE\"); // message stays \"Custom error message\"\n */\n setCode(newCode: string): void {\n this.code = newCode;\n\n // Only update message if it's a standard error code value (not a custom message)\n if (errorCodeValues.has(this.message)) {\n this.message = newCode;\n }\n }\n\n /**\n * Copies properties from another Error instance to this RouterError.\n *\n * This method updates the message, cause, and stack trace from the provided error.\n * Useful for wrapping native errors while preserving error context.\n *\n * @param err - The Error instance to copy properties from\n * @throws {TypeError} If err is null or undefined\n *\n * @example\n * ```typescript\n * const routerErr = new RouterError(\"TRANSITION_ERR\");\n * try {\n * // some operation that might fail\n * } catch (nativeErr) {\n * routerErr.setErrorInstance(nativeErr);\n * throw routerErr;\n * }\n * ```\n */\n setErrorInstance(err: Error): void {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (!err) {\n throw new TypeError(\n \"[RouterError.setErrorInstance] err parameter is required and must be an Error instance\",\n );\n }\n\n this.message = err.message;\n this.cause = err.cause;\n this.stack = err.stack ?? \"\";\n }\n\n /**\n * Adds custom fields to the error object.\n *\n * This method allows attaching arbitrary data to the error for debugging or logging purposes.\n * All fields become accessible as properties on the error instance and are included in JSON serialization.\n *\n * Reserved method names (setCode, setErrorInstance, setAdditionalFields, hasField, getField, toJSON)\n * are automatically filtered out to prevent accidental overwriting of class methods.\n *\n * @param fields - Object containing custom fields to add to the error\n *\n * @example\n * ```typescript\n * const err = new RouterError(\"CANNOT_ACTIVATE\");\n * err.setAdditionalFields({\n * userId: \"123\",\n * attemptedRoute: \"/admin\",\n * reason: \"insufficient permissions\"\n * });\n *\n * console.log(err.userId); // \"123\"\n * console.log(JSON.stringify(err)); // includes all custom fields\n * ```\n */\n setAdditionalFields(fields: Record<string, unknown>): void {\n // Assign fields, throwing for reserved properties, silently ignoring methods\n for (const [key, value] of Object.entries(fields)) {\n if (reservedProperties.has(key)) {\n throw new TypeError(\n `[RouterError.setAdditionalFields] Cannot set reserved property \"${key}\"`,\n );\n }\n\n if (!reservedMethods.has(key)) {\n this[key] = value;\n }\n }\n }\n\n /**\n * Checks if a custom field exists on the error object.\n *\n * This method checks for both custom fields added via setAdditionalFields()\n * and built-in fields (code, message, segment, etc.).\n *\n * @param key - The field name to check\n * @returns `true` if the field exists, `false` otherwise\n *\n * @example\n * ```typescript\n * const err = new RouterError(\"ERR\", { segment: \"users\" });\n * err.setAdditionalFields({ userId: \"123\" });\n *\n * err.hasField(\"userId\"); // true\n * err.hasField(\"segment\"); // true\n * err.hasField(\"unknown\"); // false\n * ```\n */\n hasField(key: string): boolean {\n return key in this;\n }\n\n /**\n * Retrieves a custom field value from the error object.\n *\n * This method can access both custom fields and built-in fields.\n * Returns `undefined` if the field doesn't exist.\n *\n * @param key - The field name to retrieve\n * @returns The field value, or `undefined` if it doesn't exist\n *\n * @example\n * ```typescript\n * const err = new RouterError(\"ERR\");\n * err.setAdditionalFields({ userId: \"123\", role: \"admin\" });\n *\n * err.getField(\"userId\"); // \"123\"\n * err.getField(\"role\"); // \"admin\"\n * err.getField(\"code\"); // \"ERR\" (built-in field)\n * err.getField(\"unknown\"); // undefined\n * ```\n */\n getField(key: string): unknown {\n return this[key];\n }\n\n /**\n * Serializes the error to a JSON-compatible object.\n *\n * This method is automatically called by JSON.stringify() and includes:\n * - Built-in fields: code, message, segment (if set), path (if set)\n * - All custom fields added via setAdditionalFields() or constructor\n * - Excludes: stack trace (for security/cleanliness)\n *\n * @returns A plain object representation of the error, suitable for JSON serialization\n *\n * @example\n * ```typescript\n * const err = new RouterError(\"ROUTE_NOT_FOUND\", {\n * message: \"Route not found\",\n * path: \"/admin/users/123\"\n * });\n * err.setAdditionalFields({ userId: \"123\" });\n *\n * JSON.stringify(err);\n * // {\n * // \"code\": \"ROUTE_NOT_FOUND\",\n * // \"message\": \"Route not found\",\n * // \"path\": \"/admin/users/123\",\n * // \"userId\": \"123\"\n * // }\n * ```\n */\n toJSON(): Record<string, unknown> {\n const result: Record<string, unknown> = {\n code: this.code,\n message: this.message,\n };\n\n if (this.segment !== undefined) {\n result.segment = this.segment;\n }\n if (this.path !== undefined) {\n result.path = this.path;\n }\n\n // add all public fields\n // Using Set.has() for O(1) lookup instead of Array.includes() O(n)\n // Overall complexity: O(n) instead of O(n*m)\n const excludeKeys = new Set([\n \"code\",\n \"message\",\n \"segment\",\n \"path\",\n \"stack\",\n // `name` is now an own enumerable prop (constructor sets it to\n // \"RouterError\"); it's class metadata, not a custom field — keep it out of\n // the serialized output (preserves toJSON shape).\n \"name\",\n ]);\n\n for (const key in this) {\n if (Object.hasOwn(this, key) && !excludeKeys.has(key)) {\n result[key] = this[key];\n }\n }\n\n return result;\n }\n}\n","// packages/core/src/namespaces/NavigationNamespace/constants.ts\n\nimport { errorCodes } from \"../../constants\";\nimport { RouterError } from \"../../RouterError\";\n\nimport type { State } from \"../../types\";\n\n// =============================================================================\n// Cached Errors & Rejected Promises (Performance Optimization)\n// =============================================================================\n// Pre-create error instances and rejected promises for sync error paths\n// in navigate(). Eliminates per-call allocations:\n// - new RouterError() — object + stack trace capture (~500ns-2μs)\n// - Promise.reject() — promise allocation\n// - .catch(handler) — derived promise from suppression\n//\n// Trade-off: All error instances share the same stack trace (points here).\n// This is acceptable because:\n// 1. These errors indicate expected conditions, not internal bugs\n// 2. Error code and message are sufficient for debugging\n// 3. The facade skips .catch() suppression for cached promises (zero alloc)\n// =============================================================================\n\nexport const CACHED_NOT_STARTED_ERROR = new RouterError(\n errorCodes.ROUTER_NOT_STARTED,\n);\n\nexport const CACHED_ROUTE_NOT_FOUND_ERROR = new RouterError(\n errorCodes.ROUTE_NOT_FOUND,\n);\n\nexport const CACHED_SAME_STATES_ERROR = new RouterError(errorCodes.SAME_STATES);\n\n// Pre-suppressed rejected promises — .catch() at module load prevents\n// unhandled rejection warnings. The facade skips additional .catch() calls\n// via the lastSyncRejected flag (zero derived-promise allocation).\nexport const CACHED_NOT_STARTED_REJECTION: Promise<State> = Promise.reject(\n CACHED_NOT_STARTED_ERROR,\n);\n\nexport const CACHED_ROUTE_NOT_FOUND_REJECTION: Promise<State> = Promise.reject(\n CACHED_ROUTE_NOT_FOUND_ERROR,\n);\n\nexport const CACHED_SAME_STATES_REJECTION: Promise<State> = Promise.reject(\n CACHED_SAME_STATES_ERROR,\n);\n\n// Suppress once at module load — prevents unhandled rejection events.\n// Subsequent .catch() / await by user code still works correctly:\n// a rejected promise stays rejected forever, each .catch() creates\n// its own derived promise and fires its handler.\nCACHED_NOT_STARTED_REJECTION.catch(() => {}); // NOSONAR -- intentional suppression, not a promise chain\nCACHED_ROUTE_NOT_FOUND_REJECTION.catch(() => {}); // NOSONAR\nCACHED_SAME_STATES_REJECTION.catch(() => {}); // NOSONAR\n","import { errorCodes, constants } from \"../../../constants\";\nimport { RouterError } from \"../../../RouterError\";\n\nimport type { NavigationOptions, State, TransitionMeta } from \"../../../types\";\nimport type { NavigationDependencies, NavigationContext } from \"../types\";\n\ntype MutableTransitionMeta = {\n -readonly [K in keyof TransitionMeta]: TransitionMeta[K];\n};\n\nfunction buildTransitionMeta(\n fromState: State | undefined,\n opts: NavigationOptions,\n toDeactivate: string[],\n toActivate: string[],\n intersection: string,\n): TransitionMeta {\n Object.freeze(toDeactivate);\n Object.freeze(toActivate);\n\n const segments = Object.freeze({\n deactivated: toDeactivate,\n activated: toActivate,\n intersection,\n });\n\n const meta: MutableTransitionMeta = {\n phase: \"activating\",\n reason: \"success\",\n segments,\n };\n\n if (fromState?.name !== undefined) {\n meta.from = fromState.name;\n }\n\n if (opts.reload !== undefined) {\n meta.reload = opts.reload;\n }\n\n if (opts.replace !== undefined) {\n meta.replace = opts.replace;\n }\n\n if (opts.redirected !== undefined) {\n meta.redirected = opts.redirected;\n }\n\n return Object.freeze(meta);\n}\n\nfunction stripSignal({\n signal: _,\n ...rest\n}: NavigationOptions): NavigationOptions {\n return rest;\n}\n\nexport function completeTransition(\n deps: NavigationDependencies,\n nav: NavigationContext,\n): State {\n const { toState, fromState, opts, toDeactivate, toActivate, intersection } =\n nav;\n\n if (\n toState.name !== constants.UNKNOWN_ROUTE &&\n !deps.hasRoute(toState.name)\n ) {\n const err = new RouterError(errorCodes.ROUTE_NOT_FOUND, {\n routeName: toState.name,\n });\n\n deps.sendTransitionFail(toState, fromState, err);\n\n throw err;\n }\n\n if (fromState) {\n for (const name of toDeactivate) {\n if (!toActivate.includes(name) && nav.canDeactivateFunctions.has(name)) {\n deps.clearCanDeactivate(name);\n }\n }\n }\n\n (toState as { transition: TransitionMeta }).transition = buildTransitionMeta(\n fromState,\n opts,\n toDeactivate,\n toActivate,\n intersection,\n );\n\n const finalState = Object.freeze(toState);\n\n deps.setState(finalState);\n\n const transitionOpts = opts.signal === undefined ? opts : stripSignal(opts);\n\n deps.sendTransitionDone(finalState, fromState, transitionOpts);\n\n return finalState;\n}\n","// packages/core/src/namespaces/NavigationNamespace/transition/errorHandling.ts\n\nimport { errorCodes } from \"../../../constants\";\nimport { RouterError } from \"../../../RouterError\";\n\nimport type { State } from \"../../../types\";\nimport type { NavigationDependencies } from \"../types\";\n\nexport function routeTransitionError(\n deps: NavigationDependencies,\n error: unknown,\n toState: State,\n fromState: State | undefined,\n): void {\n const routerError = error as RouterError;\n\n if (\n routerError.code === errorCodes.TRANSITION_CANCELLED ||\n routerError.code === errorCodes.ROUTE_NOT_FOUND\n ) {\n return;\n }\n\n deps.sendTransitionFail(toState, fromState, routerError);\n}\n\nexport function handleGuardError(\n error: unknown,\n errorCode: string,\n segment: string,\n): never {\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw new RouterError(errorCodes.TRANSITION_CANCELLED);\n }\n\n // A guard can also signal a quiet cancel by throwing\n // RouterError(TRANSITION_CANCELLED) directly — the same intent as a thrown\n // AbortError. Preserve it as-is instead of letting rethrowAsRouterError\n // overwrite the code with CANNOT_ACTIVATE / CANNOT_DEACTIVATE: that code\n // drives the downstream suppression (routeTransitionError early-returns,\n // fire-and-forget stays silent), so re-coding would surface the intended\n // quiet cancel as a reported transition error (#933).\n if (\n error instanceof RouterError &&\n error.code === errorCodes.TRANSITION_CANCELLED\n ) {\n throw error;\n }\n\n rethrowAsRouterError(error, errorCode, segment);\n}\n\n/**\n * Error metadata structure for transition errors.\n * Contains information extracted from caught exceptions.\n */\nexport interface SyncErrorMetadata {\n [key: string]: unknown;\n message?: string;\n stack?: string | undefined;\n cause?: unknown;\n segment?: string;\n}\n\n/**\n * Re-throws a caught error as a RouterError with the given error code.\n * If the error is already a RouterError, sets the code directly.\n * Otherwise wraps it with wrapSyncError metadata.\n */\nexport function rethrowAsRouterError(\n error: unknown,\n errorCode: string,\n segment: string,\n): never {\n if (error instanceof RouterError) {\n error.setCode(errorCode);\n\n throw error;\n }\n\n throw new RouterError(errorCode, wrapSyncError(error, segment));\n}\n\n// Own-enumerable keys that must never be copied from a thrown object onto the\n// RouterError metadata:\n// - `code` / `segment` / `path` are reserved — the RouterError constructor\n// throws a TypeError on them (#39).\n// - `then` would make the RouterError itself thenable, so a consumer that\n// awaits it (or passes it through Promise.resolve / returns it from an async\n// function) would have it assimilated as a Promise instead of treated as a\n// plain rejection reason (#947).\nconst reservedRouterErrorProps = new Set([\"code\", \"segment\", \"path\", \"then\"]);\n\n/**\n * Wraps a synchronously thrown value into structured error metadata.\n *\n * This helper extracts useful debugging information from various thrown values:\n * - Error instances: extracts message, stack, and cause (ES2022+)\n * - Plain objects: spreads properties into metadata\n * - Primitives (string, number, etc.): returns minimal metadata\n *\n * @param thrown - The value caught in a try-catch block\n * @param segment - Route segment name (for lifecycle hooks)\n * @returns Structured error metadata for RouterError\n */\nexport function wrapSyncError(\n thrown: unknown,\n segment: string,\n): SyncErrorMetadata {\n const base: SyncErrorMetadata = { segment };\n\n // Handle Error instances - extract all useful properties\n if (thrown instanceof Error) {\n return {\n ...base,\n message: thrown.message,\n stack: thrown.stack,\n // Error.cause requires ES2022+ - safely access if present\n ...(\"cause\" in thrown &&\n thrown.cause !== undefined && { cause: thrown.cause }),\n };\n }\n\n // Handle plain objects - spread properties into metadata, filtering reserved props\n if (thrown && typeof thrown === \"object\") {\n const filtered: Record<string, unknown> = {};\n\n for (const [key, value] of Object.entries(thrown)) {\n // Skip reserved / hazardous keys: #39 (constructor TypeError on code/\n // segment/path) and #947 (`then` would make the error thenable).\n if (!reservedRouterErrorProps.has(key)) {\n filtered[key] = value;\n }\n }\n\n return { ...base, ...filtered };\n }\n\n // Primitives (string, number, boolean, null, undefined, symbol, bigint)\n // Return base metadata only - the primitive value isn't useful as metadata\n return base;\n}\n","import { handleGuardError } from \"./errorHandling\";\nimport { errorCodes } from \"../../../constants\";\nimport { RouterError } from \"../../../RouterError\";\n\nimport type { GuardFn, State } from \"../../../types\";\n\nasync function resolveAsyncGuard(\n promise: Promise<boolean>,\n errorCode: string,\n segment: string,\n): Promise<void> {\n let result: boolean;\n\n try {\n result = await promise;\n } catch (error: unknown) {\n handleGuardError(error, errorCode, segment);\n\n return; // unreachable — handleGuardError returns never\n }\n\n if (!result) {\n throw new RouterError(errorCode, { segment });\n }\n}\n\nasync function resolveRemainingGuards( // NOSONAR -- params kept flat to avoid object allocation on hot path\n guards: Map<string, GuardFn>,\n segments: string[],\n errorCode: string,\n toState: State,\n fromState: State | undefined,\n signal: AbortSignal | undefined,\n isActive: () => boolean,\n startIndex: number,\n firstResult: Promise<boolean>,\n firstSegment: string,\n): Promise<void> {\n await resolveAsyncGuard(firstResult, errorCode, firstSegment);\n\n for (let i = startIndex; i < segments.length; i++) {\n // Stryker disable next-line BlockStatement: equivalent — emptying the body drops the async-tail cancellation throw, but supersession is independently enforced by the navigationId/isCurrentNav checks in NavigationNamespace, so the navigation still rejects with TRANSITION_CANCELLED (full suite green). The ConditionalExpression sibling stays live (its →true variant always-cancels and is killed by async-guard tests).\n if (!isActive()) {\n throw new RouterError(errorCodes.TRANSITION_CANCELLED);\n }\n\n const segment = segments[i];\n const guardFn = guards.get(segment);\n\n if (!guardFn) {\n continue;\n }\n\n // Stryker disable next-line BooleanLiteral: equivalent — guardResult is unconditionally reassigned by guardFn() on the next line, or the catch calls handleGuardError() (returns never), so the init value is never read.\n let guardResult: boolean | Promise<boolean> = false;\n\n try {\n guardResult = guardFn(toState, fromState, signal);\n } catch (error: unknown) {\n handleGuardError(error, errorCode, segment);\n }\n\n if (guardResult instanceof Promise) {\n await resolveAsyncGuard(guardResult, errorCode, segment);\n continue;\n }\n\n if (!guardResult) {\n throw new RouterError(errorCode, { segment });\n }\n }\n}\n\nasync function finishAsyncPipeline( // NOSONAR\n deactivateCompletion: Promise<void>,\n activateGuards: Map<string, GuardFn>,\n toActivate: string[],\n shouldActivate: boolean,\n toState: State,\n fromState: State | undefined,\n signal: AbortSignal,\n isActive: () => boolean,\n emitLeaveApprove: () => Promise<void> | undefined,\n): Promise<void> {\n await deactivateCompletion;\n\n if (!isActive()) {\n throw new RouterError(errorCodes.TRANSITION_CANCELLED);\n }\n\n const leaveResult = emitLeaveApprove();\n\n if (leaveResult !== undefined) {\n await leaveResult;\n\n // Stryker disable next-line BlockStatement: equivalent — defensive-redundancy: this post-leave cancellation re-check is already enforced upstream (navigationId / isCurrentNav supersession in NavigationNamespace), so emptying the throw is unobservable. Mirrors the disabled L42/L115/L228 cancel-checks.\n /* v8 ignore next 3 -- @preserve: V8 cannot track cancellation check through async leave continuation after Promise.allSettled */\n if (!isActive()) {\n throw new RouterError(errorCodes.TRANSITION_CANCELLED);\n }\n }\n\n if (shouldActivate) {\n const pending = runGuards(\n activateGuards,\n toActivate,\n errorCodes.CANNOT_ACTIVATE,\n toState,\n fromState,\n signal,\n isActive,\n );\n\n if (pending !== undefined) {\n await pending;\n }\n\n // Stryker disable next-line BlockStatement: equivalent — same async-tail cancellation throw as L42; dropping it is covered by the outer navigationId guard (full suite green). CE sibling stays live (→true killed by async-guard tests).\n if (!isActive()) {\n throw new RouterError(errorCodes.TRANSITION_CANCELLED);\n }\n }\n}\n\nexport function executeGuardPipeline( // NOSONAR\n deactivateGuards: Map<string, GuardFn>,\n activateGuards: Map<string, GuardFn>,\n toDeactivate: string[],\n toActivate: string[],\n shouldDeactivate: boolean,\n shouldActivate: boolean,\n toState: State,\n fromState: State | undefined,\n signal: AbortSignal,\n isActive: () => boolean,\n emitLeaveApprove: () => Promise<void> | undefined,\n): Promise<void> | undefined {\n if (shouldDeactivate) {\n const pending = runGuards(\n deactivateGuards,\n toDeactivate,\n errorCodes.CANNOT_DEACTIVATE,\n toState,\n fromState,\n signal,\n isActive,\n );\n\n if (pending !== undefined) {\n return finishAsyncPipeline(\n pending,\n activateGuards,\n toActivate,\n shouldActivate,\n toState,\n fromState,\n signal,\n isActive,\n emitLeaveApprove,\n );\n }\n }\n\n if (!isActive()) {\n throw new RouterError(errorCodes.TRANSITION_CANCELLED);\n }\n\n const leaveResult = emitLeaveApprove();\n\n if (leaveResult !== undefined) {\n return finishAfterAsyncLeave(\n leaveResult,\n /* v8 ignore next -- @preserve: false-branch unreachable — navigateToNotFound bypasses guards pipeline */\n shouldActivate ? activateGuards : undefined,\n toActivate,\n toState,\n fromState,\n signal,\n isActive,\n );\n }\n\n if (shouldActivate) {\n return runGuards(\n activateGuards,\n toActivate,\n errorCodes.CANNOT_ACTIVATE,\n toState,\n fromState,\n signal,\n isActive,\n );\n }\n\n return undefined;\n}\n\nasync function finishAfterAsyncLeave(\n leaveCompletion: Promise<void>,\n activateGuards: Map<string, GuardFn> | undefined,\n toActivate: string[],\n toState: State,\n fromState: State | undefined,\n signal: AbortSignal,\n isActive: () => boolean,\n): Promise<void> {\n await leaveCompletion;\n\n // Stryker disable next-line BlockStatement: equivalent — defensive-redundancy: same post-leave cancellation re-check as guardPhase's other `!isActive()` guards; supersession is enforced upstream (navigationId / isCurrentNav), so emptying the throw is unobservable.\n /* v8 ignore next 3 -- @preserve: unreachable after #663 — signal abort\n mid-leave rejects via settleLeavePromises, so `await leaveCompletion`\n throws directly instead of completing with a stale isActive() */\n if (!isActive()) {\n throw new RouterError(errorCodes.TRANSITION_CANCELLED);\n }\n\n /* v8 ignore next -- @preserve: false-branch unreachable — navigateToNotFound bypasses guards pipeline */\n if (activateGuards !== undefined) {\n const pending = runGuards(\n activateGuards,\n toActivate,\n errorCodes.CANNOT_ACTIVATE,\n toState,\n fromState,\n signal,\n isActive,\n );\n\n if (pending !== undefined) {\n await pending;\n }\n\n // Stryker disable next-line BlockStatement: equivalent — same async-tail cancellation throw as L42; dropping it is covered by the outer navigationId guard (full suite green). CE sibling stays live (→true killed by async-guard tests).\n if (!isActive()) {\n throw new RouterError(errorCodes.TRANSITION_CANCELLED);\n }\n }\n}\n\nfunction runGuards(\n guards: Map<string, GuardFn>,\n segments: string[],\n errorCode: string,\n toState: State,\n fromState: State | undefined,\n signal: AbortSignal | undefined,\n isActive: () => boolean,\n): Promise<void> | undefined {\n for (const [i, segment] of segments.entries()) {\n if (!isActive()) {\n throw new RouterError(errorCodes.TRANSITION_CANCELLED);\n }\n\n const guardFn = guards.get(segment);\n\n if (!guardFn) {\n continue;\n }\n\n // Stryker disable next-line BooleanLiteral: equivalent — guardResult is unconditionally reassigned by guardFn() on the next line, or the catch calls handleGuardError() (returns never), so the init value is never read.\n let guardResult: boolean | Promise<boolean> = false;\n\n try {\n guardResult = guardFn(toState, fromState, signal);\n } catch (error: unknown) {\n handleGuardError(error, errorCode, segment);\n }\n\n if (guardResult instanceof Promise) {\n return resolveRemainingGuards(\n guards,\n segments,\n errorCode,\n toState,\n fromState,\n signal,\n isActive,\n i + 1,\n guardResult,\n segment,\n );\n }\n\n if (!guardResult) {\n throw new RouterError(errorCode, { segment });\n }\n }\n\n return undefined;\n}\n","import {\n CACHED_NOT_STARTED_REJECTION,\n CACHED_ROUTE_NOT_FOUND_ERROR,\n CACHED_ROUTE_NOT_FOUND_REJECTION,\n CACHED_SAME_STATES_ERROR,\n CACHED_SAME_STATES_REJECTION,\n} from \"./constants\";\nimport { completeTransition } from \"./transition/completeTransition\";\nimport { routeTransitionError } from \"./transition/errorHandling\";\nimport { executeGuardPipeline } from \"./transition/guardPhase\";\nimport { EMPTY_PARAMS, errorCodes, constants } from \"../../constants\";\nimport { RouterError } from \"../../RouterError\";\nimport { getStateMetaParams, setStateMetaParams } from \"../../stateMetaStore\";\nimport { getTransitionPath, nameToIDs } from \"../../transitionPath\";\n\nimport type { NavigationContext, NavigationDependencies } from \"./types\";\nimport type { TransitionPath } from \"../../transitionPath\";\nimport type {\n GuardFn,\n NavigationOptions,\n Params,\n State,\n TransitionMeta,\n} from \"../../types\";\n\nconst FROZEN_ACTIVATED: string[] = Object.freeze([\n constants.UNKNOWN_ROUTE,\n]) as unknown as string[];\nconst FROZEN_REPLACE_OPTS: NavigationOptions = Object.freeze({ replace: true });\n\nfunction forceReplaceFromUnknown(\n opts: NavigationOptions,\n fromState: State | undefined,\n): NavigationOptions {\n return fromState?.name === constants.UNKNOWN_ROUTE && !opts.replace\n ? { ...opts, replace: true }\n : opts;\n}\n\nfunction isSameNavigation(\n fromState: State | undefined,\n opts: NavigationOptions,\n toState: State,\n): boolean {\n return (\n !!fromState &&\n !opts.reload &&\n !opts.force &&\n fromState.path === toState.path\n );\n}\n\n/**\n * Independent namespace for managing navigation.\n *\n * Handles navigate(), navigateToDefault(), navigateToNotFound(), and transition state.\n *\n * Performance: navigate() uses optimistic sync execution — guards run synchronously\n * until one returns a Promise, then switches to async. This eliminates Promise/AbortController\n * overhead for the common case (no guards or sync guards).\n */\nexport class NavigationNamespace {\n // Stryker disable next-line BooleanLiteral: equivalent — reset to false at the top of every navigate()/navigateToState()/navigateToDefault(), so the initializer value is never observed.\n lastSyncResolved = false;\n lastSyncRejected = false;\n #deps!: NavigationDependencies;\n #currentController: AbortController | null = null;\n #navigationId = 0;\n\n // =========================================================================\n // Dependency injection\n // =========================================================================\n\n setDependencies(deps: NavigationDependencies): void {\n this.#deps = deps;\n }\n\n // =========================================================================\n // Instance methods\n // =========================================================================\n\n navigate(\n name: string,\n params: Params,\n opts: NavigationOptions,\n ): Promise<State> {\n this.lastSyncResolved = false;\n const deps = this.#deps;\n\n // Fast-path sync rejections: cached error + cached Promise.reject\n // No allocations, no throw/catch overhead, facade skips .catch() suppression\n if (!deps.canNavigate()) {\n // Stryker disable next-line BooleanLiteral: equivalent — #721 optimization flag, not a correctness gate. Not flagging the cached (pre-suppressed) rejection routes the facade to the else-branch, which re-attaches a harmless .catch; no observable difference.\n this.lastSyncRejected = true;\n\n return CACHED_NOT_STARTED_REJECTION;\n }\n\n let toState: State | undefined;\n\n try {\n toState = deps.buildNavigateState(name, params);\n } catch (error) {\n /* v8 ignore next 3 -- @preserve: reachable only via validator-driven\n throws from buildNavigateState (validateStateBuilderArgs) — covered\n in @real-router/validation-plugin's suite, not in core. */\n // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors -- preserve original throw shape from user-provided buildNavigateState\n return Promise.reject(error);\n }\n\n if (!toState) {\n deps.emitTransitionError(\n undefined,\n deps.getState(),\n CACHED_ROUTE_NOT_FOUND_ERROR,\n );\n // Stryker disable next-line BooleanLiteral: equivalent — #721 optimization flag (see L95): the cached rejection is suppressed by the facade else-branch regardless of the flag.\n this.lastSyncRejected = true;\n\n return CACHED_ROUTE_NOT_FOUND_REJECTION;\n }\n\n return this.#executeNavigation(toState, opts);\n }\n\n /**\n * Navigate to a fully-built `State` directly, skipping `buildNavigateState`\n * (forwardState + buildPath + meta lookup). Used by URL plugins after they\n * have already produced a `State` from a browser-initiated event via\n * `api.matchPath(url)` — see issue #525.\n *\n * Semantics vs. `navigate(name, params, opts)`:\n * - `forwardState` is NOT re-applied. matchPath already runs it; reapplying\n * is redundant in the idempotent case and can race in the dynamic case.\n * - `buildPath` is NOT re-run. The caller's `state.path` is used as-is —\n * so `trailingSlash:\"preserve\"` matchedState paths flow through unchanged\n * (closes #525 Q2). `buildPath` interceptors do NOT run; the URL the\n * user navigated to is the source of truth for this code path.\n * - All other pipeline steps run unchanged: SAME_STATES check, FSM\n * transition, guards, `subscribeLeave`, `completeTransition`,\n * plugin lifecycle hooks.\n */\n navigateToState(state: State, opts: NavigationOptions): Promise<State> {\n this.lastSyncResolved = false;\n const deps = this.#deps;\n\n if (!deps.canNavigate()) {\n // Stryker disable next-line BooleanLiteral: equivalent — #721 optimization flag (see L95): the cached rejection is suppressed by the facade else-branch regardless of the flag.\n this.lastSyncRejected = true;\n\n return CACHED_NOT_STARTED_REJECTION;\n }\n\n // Reject states whose route no longer exists (e.g. the route tree was\n // mutated between matchPath and navigateToState). UNKNOWN_ROUTE is\n // structurally legal — it is the navigateToNotFound output shape.\n if (state.name !== constants.UNKNOWN_ROUTE && !deps.hasRoute(state.name)) {\n const err = new RouterError(errorCodes.ROUTE_NOT_FOUND, {\n routeName: state.name,\n });\n\n deps.emitTransitionError(undefined, deps.getState(), err);\n\n // This is a FRESH reject (carries `routeName`), not one of the\n // pre-suppressed CACHED_*_REJECTION singletons. `lastSyncRejected`\n // contractually means \"I returned a pre-suppressed cached rejection —\n // skip your .catch()\", so leaving it unset lets the facade attach its\n // own suppression. Setting it here leaked an unhandledRejection on\n // fire-and-forget calls (#721).\n return Promise.reject(err);\n }\n\n // States from `matchPath` are deeply frozen (`freezeStateInPlace`).\n // `completeTransition` mutates `toState.transition` and `context` is\n // intentionally extensible for plugin claim writes, so we hand the\n // pipeline a writable shell — same shape `makeState(skipFreeze=true)`\n // produces. `params` stays referentially shared (already frozen).\n // `transition` is omitted so completeTransition can assign it.\n const writableState = {\n name: state.name,\n params: state.params,\n path: state.path,\n context: { ...state.context },\n } as State;\n\n // Carry the route-meta binding (#1170). `matchPath` / `makeState` attach it\n // to the frozen source `state` via a WeakMap keyed by object reference, so\n // the fresh writable shell would otherwise be meta-less. Without it, two\n // consecutive popstate navigations make both `toState` AND `fromState`\n // meta-less, dropping `getTransitionPath` to FAST PATH 3 (full chains):\n // ancestor guards re-run and browser-back can block where `navigate()`\n // succeeds.\n const meta = getStateMetaParams(state);\n\n if (meta !== undefined) {\n setStateMetaParams(writableState, meta);\n }\n\n return this.#executeNavigation(writableState, opts);\n }\n\n navigateToDefault(opts: NavigationOptions): Promise<State> {\n // Reset the sync-resolution flag on entry, mirroring navigate() and\n // navigateToState(). start() leaves `lastSyncResolved = true`, and the\n // early reject paths below return before delegating to navigate(), so a\n // stale `true` would make the facade take the \"already resolved\" branch\n // and skip .catch() suppression — leaking an unhandledRejection on\n // fire-and-forget calls (#721).\n this.lastSyncResolved = false;\n const deps = this.#deps;\n const options = deps.getOptions();\n\n if (!options.defaultRoute) {\n return Promise.reject(\n new RouterError(errorCodes.ROUTE_NOT_FOUND, {\n routeName: \"defaultRoute not configured\",\n }),\n );\n }\n\n let route: string;\n let params: Params;\n\n try {\n ({ route, params } = deps.resolveDefault());\n } catch (error) {\n // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors -- preserve original throw shape from user-provided resolveDefault callback\n return Promise.reject(error);\n }\n\n if (!route) {\n return Promise.reject(\n new RouterError(errorCodes.ROUTE_NOT_FOUND, {\n routeName: \"defaultRoute resolved to empty\",\n }),\n );\n }\n\n return this.navigate(route, params, opts);\n }\n\n navigateToNotFound(path: string): State {\n // #1186 — liveness gate. This internal commit primitive has no FSM\n // transition of its own, so without this check a `dispose()` that lands\n // while a start-interceptor is parked (FSM already DISPOSED) would let the\n // resuming pipeline commit an UNKNOWN_ROUTE state on the disposed router and\n // `start()` resolve. Symmetric with `navigateToState`'s `canNavigate()` gate\n // (the matched-route branch is already protected). `!isActive()` also covers\n // a merely-stopped (IDLE) router: the only reachable path to that is a direct\n // `router.navigateToNotFound()` on a stopped instance (internal callers run\n // during STARTING, which is active), so the ROUTER_DISPOSED code is slightly\n // broad there — fail-closed is deliberate (committing on a stopped router is\n // out of contract), and the disposed race is the case that matters.\n if (!this.#deps.isActive()) {\n throw new RouterError(errorCodes.ROUTER_DISPOSED);\n }\n\n this.#abortPreviousNavigation();\n\n const fromState = this.#deps.getState();\n const deactivated: string[] = fromState\n ? nameToIDs(fromState.name).toReversed()\n : [];\n\n Object.freeze(deactivated);\n\n const segments: TransitionMeta[\"segments\"] = {\n deactivated,\n activated: FROZEN_ACTIVATED,\n intersection: \"\",\n };\n\n Object.freeze(segments);\n\n const transitionMeta: TransitionMeta = {\n phase: \"activating\",\n ...(fromState && { from: fromState.name }),\n reason: \"success\",\n replace: true,\n segments,\n };\n\n Object.freeze(transitionMeta);\n\n const state: State = {\n name: constants.UNKNOWN_ROUTE,\n params: EMPTY_PARAMS,\n path,\n transition: transitionMeta,\n context: {},\n };\n\n Object.freeze(state);\n\n this.#deps.setState(state);\n this.#deps.emitTransitionSuccess(state, fromState, FROZEN_REPLACE_OPTS);\n\n return state;\n }\n\n /**\n * Aborts and releases the in-flight navigation's `AbortController` (waking the\n * parked async pipeline via `onInternalAbort`). This is the\n * **effect** of the FSM `CANCEL` action (`handleCancel` → injected\n * `deps.abortCurrentController`), not something cancellation sources call\n * directly — so \"FSM `CANCEL` ⟹ controller aborted\" holds in one place (RFC\n * navigation-cancellation-unification §5). `reason` (e.g. an external\n * `opts.signal`'s reason, #943) becomes the controller's `signal.reason`;\n * defaults to `TRANSITION_CANCELLED`.\n */\n abortCurrentController(reason?: unknown): void {\n this.#currentController?.abort(\n reason ?? new RouterError(errorCodes.TRANSITION_CANCELLED),\n );\n this.#currentController = null;\n }\n\n #executeNavigation(toState: State, opts: NavigationOptions): Promise<State> {\n const deps = this.#deps;\n let fromState: State | undefined;\n let transitionStarted = false;\n let controller: AbortController | null = null;\n\n try {\n fromState = deps.getState();\n opts = forceReplaceFromUnknown(opts, fromState);\n\n if (isSameNavigation(fromState, opts, toState)) {\n deps.emitTransitionError(toState, fromState, CACHED_SAME_STATES_ERROR);\n // Stryker disable next-line BooleanLiteral: equivalent — #721 optimization flag (see L95): the cached rejection is suppressed by the facade else-branch regardless of the flag.\n this.lastSyncRejected = true;\n\n return CACHED_SAME_STATES_REJECTION;\n }\n\n this.#abortPreviousNavigation(opts.signal);\n\n // Stryker disable next-line UpdateOperator: equivalent — `#navigationId` is only ever compared by identity (`!== myId`) to detect supersession; uniqueness per navigation is all that matters, so `--` (decreasing ids) is indistinguishable from `++`.\n const myId = ++this.#navigationId;\n\n // #1169 commit-gate — liveness snapshot captured BEFORE the pre-commit\n // listener windows. A listener's `stop()`/`dispose()` runs `clearAll()`,\n // which empties the listener lists, so the marker must be read now, not at\n // the commit site (that self-destruct was the QB/QE hole, RFC §5-bis).\n // `suspendable` is true only when a synchronous supersede is reachable — an\n // external `opts.signal`, `subscribeLeave` listeners, or a pre-commit\n // plugin listener (`onTransitionStart` / `onTransitionLeaveApprove`); the\n // pure synchronous navigate (none of these) is uncancellable and skips the\n // gate, keeping the #307 hot path perf-neutral.\n const suspendable =\n opts.signal !== undefined ||\n deps.hasLeaveListeners() ||\n deps.hasPreCommitListeners();\n\n deps.startTransition(toState, fromState);\n transitionStarted = true;\n\n // Post-`startTransition` supersession is now caught at the commit-gate\n // below (before `completeTransition`'s setState): a `stop()`/`dispose()`/\n // external-abort from the TRANSITION_START listener leaves the FSM in\n // IDLE/DISPOSED, which `!deps.isActive()` detects. (Async supersession is\n // additionally caught in `#finishAsyncNavigation` / the guard pipeline's\n // `isCurrentNav`; a reentrant navigate() is banned — REENTRANT_NAVIGATION.)\n\n const [canDeactivateFunctions, canActivateFunctions] =\n deps.getLifecycleFunctions();\n const isUnknownRoute = toState.name === constants.UNKNOWN_ROUTE;\n\n const transitionPath = getTransitionPath(toState, fromState);\n const { toDeactivate, toActivate, intersection } = transitionPath;\n\n const shouldDeactivate =\n fromState && !opts.forceDeactivate && toDeactivate.length > 0;\n const shouldActivate = !isUnknownRoute && toActivate.length > 0;\n const hasGuards =\n canDeactivateFunctions.size > 0 || canActivateFunctions.size > 0;\n\n const confirmedToState = toState;\n\n if (!hasGuards) {\n const asyncLeave = this.#handleNoGuardsLeave(\n confirmedToState,\n fromState,\n myId,\n opts,\n transitionPath,\n canDeactivateFunctions,\n );\n\n if (asyncLeave !== undefined) {\n return asyncLeave;\n }\n }\n\n // Stryker disable next-line ConditionalExpression: equivalent — running the guard pipeline on the no-guards path does not double-emit LEAVE_APPROVE (full suite green with `if (true)`); the BlockStatement mutant stays live (killed by guarded-route tests).\n // eslint-disable-next-line unicorn/prefer-else-if -- two exhaustive `if`s read clearer here than an else-if; merging cascades into no-negated-condition / no-unnecessary-condition in this hot guard-setup branch\n if (hasGuards) {\n controller = new AbortController();\n this.#currentController = controller;\n const isCurrentNav = () =>\n this.#navigationId === myId && deps.isActive();\n\n const signal = controller.signal;\n\n const emitLeaveApproveCallback = (): Promise<void> | undefined => {\n deps.sendLeaveApprove(confirmedToState, fromState);\n\n if (deps.hasLeaveListeners()) {\n return deps.awaitLeaveListeners(\n confirmedToState,\n fromState,\n signal,\n );\n }\n\n return undefined;\n };\n\n const guardCompletion = executeGuardPipeline(\n canDeactivateFunctions,\n canActivateFunctions,\n toDeactivate,\n toActivate,\n !!shouldDeactivate,\n shouldActivate,\n toState,\n fromState,\n signal,\n isCurrentNav,\n emitLeaveApproveCallback,\n );\n\n if (guardCompletion !== undefined) {\n return this.#finishAsyncNavigation(\n guardCompletion,\n {\n toState,\n fromState,\n opts,\n toDeactivate,\n toActivate,\n intersection,\n canDeactivateFunctions,\n },\n controller,\n myId,\n );\n }\n\n if (!isCurrentNav()) {\n throw new RouterError(errorCodes.TRANSITION_CANCELLED);\n }\n\n this.#cleanupController(controller, false);\n }\n\n // #1169 commit-gate — refuse to commit a navigation cancelled or\n // terminated during a listener window, BEFORE `completeTransition`'s\n // setState. The FSM table (D-full) already prevents the forceState\n // resurrection; this prevents the state commit that precedes the emit.\n // Gated on `suspendable` so the pure sync hot path pays nothing. A\n // `stop()`/`dispose()` from the listener lands the FSM in IDLE/DISPOSED\n // (caught by `!isActive()`); an external `opts.signal` abort is caught\n // directly. No `#navigationId` check: a reentrant navigate() (the only\n // thing that could bump it synchronously) is banned (REENTRANT_NAVIGATION,\n // §4), so on this sync path `#navigationId === myId` always holds — async\n // supersede is caught in `#finishAsyncNavigation`'s `isCurrentNav`.\n if (suspendable && (!deps.isActive() || opts.signal?.aborted === true)) {\n throw new RouterError(errorCodes.TRANSITION_CANCELLED);\n }\n\n const finalState = completeTransition(deps, {\n toState,\n fromState,\n opts,\n toDeactivate,\n toActivate,\n intersection,\n canDeactivateFunctions,\n });\n\n // Mark sync-resolution only AFTER completeTransition returns. It emits\n // TRANSITION_SUCCESS; listener throws are all isolated via onListenerError\n // (the emitter re-throws nothing now that re-entrant emits are coalesced,\n // #1033, and reentrant navigate/CRUD are banned), so the emit itself does\n // not throw. Should completeTransition throw for any other reason, setting\n // the flag optimistically BEFORE it would leave the flag stale-true, so the\n // facade would read lastSyncResolved and skip its suppressing `.catch()` —\n // the rejection would then leak as a Node unhandledRejection. Post-emit\n // placement keeps the flag false on a throw, so control falls to catch and\n // the facade attaches its `.catch`.\n // Stryker disable next-line BooleanLiteral: equivalent — flipping to false routes the facade to the else-branch, which attaches a harmless .catch to an already-resolved promise; there is no rejection to suppress.\n this.lastSyncResolved = true;\n\n return Promise.resolve(finalState);\n } catch (error) {\n this.#handleNavigateError(\n error,\n controller,\n transitionStarted,\n toState,\n fromState,\n );\n\n // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors -- preserve original throw shape from guards or transition pipeline\n return Promise.reject(error);\n }\n }\n\n async #finishAsyncNavigation(\n guardCompletion: Promise<void>,\n nav: NavigationContext,\n controller: AbortController,\n myId: number,\n ): Promise<State> {\n const deps = this.#deps;\n const isActive = () =>\n this.#navigationId === myId &&\n !controller.signal.aborted &&\n deps.isActive();\n\n const externalSignal = nav.opts.signal;\n let onExternalAbort: (() => void) | undefined;\n let onInternalAbort: (() => void) | undefined;\n let succeeded = false;\n let failureReason: unknown;\n\n // #1018: race the guard completion against the controller's abort so a\n // non-cooperative guard whose Promise never settles (and ignores `signal`)\n // cannot wedge navigate() forever. `abortRace` RESOLVES on abort, so the\n // post-race `isActive()` check below throws TRANSITION_CANCELLED — the same\n // path that already handles a guard which swallows the abort and resolves\n // `true`. `stop()`/`dispose()`/supersede all abort the controller. Mirrors\n // the leave-path protection `settleLeavePromises` (#663/#673).\n const abortRace = new Promise<void>((resolve) => {\n if (controller.signal.aborted) {\n resolve();\n\n return;\n }\n\n onInternalAbort = () => {\n resolve();\n };\n\n controller.signal.addEventListener(\"abort\", onInternalAbort, {\n once: true,\n });\n });\n\n // Consume `guardCompletion` when the abort wins the race: a slow or\n // never-settling guard that settles later then has no awaiter, which would\n // surface as an unhandled rejection without this catch.\n guardCompletion.catch(() => {\n /* settlement consumed — the race already decided the navigation */\n });\n\n try {\n if (externalSignal) {\n if (externalSignal.aborted) {\n throw new RouterError(errorCodes.TRANSITION_CANCELLED, {\n reason: externalSignal.reason,\n });\n }\n\n // Bridge an external `{ signal }` abort onto the internal controller.\n // It is NOT scoped to `controller.signal` (the old `{ signal }` option)\n // because success no longer aborts the controller (#722) — the listener\n // is detached explicitly in `finally` instead.\n onExternalAbort = () => {\n // (#1030): route the external abort through the FSM. The\n // `CANCEL` action aborts the internal controller (waking THIS pipeline)\n // with the external `reason` (#943 — surfaces via the leave signal) AND\n // returns the FSM to READY, atomically. No direct `controller.abort`\n // here — \"FSM CANCEL ⟹ controller aborted\" lives in one place\n // (handleCancel). onExternalAbort only fires while the navigation is in\n // flight (the listener is removed in `finally` once it settles), so the\n // FSM is always cancellable here.\n deps.cancelNavigation(externalSignal.reason);\n };\n // Stryker disable next-line ObjectLiteral: equivalent — `{ once: true }` is redundant: the per-navigation signal aborts at most once and is discarded unaborted on success, and the `finally` block explicitly removeEventListener's it.\n externalSignal.addEventListener(\"abort\", onExternalAbort, {\n // Stryker disable next-line BooleanLiteral: equivalent — `once` redundant (see ObjectLiteral above); the listener is explicitly removed in `finally`.\n once: true,\n });\n }\n\n await Promise.race([guardCompletion, abortRace]);\n\n if (!isActive()) {\n throw new RouterError(errorCodes.TRANSITION_CANCELLED);\n }\n\n const state = completeTransition(deps, nav);\n\n succeeded = true;\n\n return state;\n } catch (error) {\n failureReason = error;\n\n routeTransitionError(deps, error, nav.toState, nav.fromState);\n\n throw error;\n // NB: the `} finally {}` BlockStatement mutant SURVIVES but is EQUIVALENT —\n // emptying the finally only skips #cleanupController, which is unobservable\n // (defense-in-depth: on a CANCEL the FSM CANCEL action already aborted+nulled\n // the controller via abortCurrentController, RFC §5; the success-path\n // ref-release is proven unobservable — see #cleanupController's disable). It\n // cannot be inline-`Stryker disable`d: the catch `}` and finally `{` share one\n // line, so there is no comment position that targets the finally body. Left documented.\n } finally {\n // Stryker disable next-line ConditionalExpression,BlockStatement: equivalent — listener cleanup is redundant: the per-navigation signal is discarded on completion, so skipping the removeEventListener leaks nothing observable.\n if (onExternalAbort) {\n // Stryker disable next-line StringLiteral: equivalent — cleanup event name is redundant (listener is `{ once: true }` and the signal is discarded), so a wrong name removes nothing observable.\n externalSignal?.removeEventListener(\"abort\", onExternalAbort);\n }\n\n // Detach the abort-race listener before #cleanupController aborts the\n // controller below, so the cleanup abort cannot re-fire it. `undefined`\n // only when the controller was already aborted at setup (the early-resolve\n // branch above registered no listener).\n if (onInternalAbort) {\n controller.signal.removeEventListener(\"abort\", onInternalAbort);\n }\n\n // Success drops the controller without aborting (the subscribeLeave signal\n // must stay unaborted); cancel/error aborts it with the originating reason\n // so captured signals expose the real cause via `signal.reason` (#943).\n this.#cleanupController(controller, !succeeded, failureReason);\n }\n }\n\n #handleNavigateError(\n error: unknown,\n controller: AbortController | null,\n transitionStarted: boolean,\n toState: State | undefined,\n fromState: State | undefined,\n ): void {\n if (controller) {\n this.#cleanupController(controller, true, error);\n }\n\n if (transitionStarted && toState) {\n routeTransitionError(this.#deps, error, toState, fromState);\n }\n }\n\n #handleNoGuardsLeave(\n toState: State,\n fromState: State | undefined,\n myId: number,\n opts: NavigationOptions,\n transitionPath: TransitionPath,\n canDeactivateFunctions: Map<string, GuardFn>,\n ): Promise<State> | undefined {\n const deps = this.#deps;\n\n deps.sendLeaveApprove(toState, fromState);\n\n if (deps.hasLeaveListeners()) {\n const controller = new AbortController();\n\n // Track as the current navigation BEFORE listeners run so a reentrant\n // navigate() / stop() / dispose() from a sync listener aborts THIS leave\n // signal — parity with the guard path (#722). On success the controller is\n // released without aborting (see #cleanupController).\n this.#currentController = controller;\n\n let leaveResult: Promise<void> | undefined;\n\n try {\n leaveResult = deps.awaitLeaveListeners(\n toState,\n fromState,\n controller.signal,\n );\n } catch (error) {\n // A sync listener threw — the navigation fails; abort the leave signal\n // with the thrown value so a listener that captured the signal sees the\n // real cause via `signal.reason`, not a generic AbortError (#943).\n this.#cleanupController(controller, true, error);\n\n throw error;\n }\n\n if (leaveResult !== undefined) {\n return this.#finishAsyncNavigation(\n leaveResult,\n {\n toState,\n fromState,\n opts,\n toDeactivate: transitionPath.toDeactivate,\n toActivate: transitionPath.toActivate,\n intersection: transitionPath.intersection,\n canDeactivateFunctions,\n },\n controller,\n myId,\n );\n }\n\n // Sync listeners settled. A synchronous reentrant navigate() can no longer\n // supersede here (banned, RFC §4), so the leave always succeeds: release the\n // controller WITHOUT aborting (the subscribeLeave signal must stay live).\n this.#cleanupController(controller, false);\n\n return undefined;\n }\n\n // No leave listeners: nothing synchronous could have superseded this\n // navigation during the LEAVE_APPROVE emit (reentrant navigate is banned).\n return undefined;\n }\n\n /**\n * Release a navigation's AbortController. The same `controller.signal` is\n * handed to `subscribeLeave` listeners, so it must abort **only** when the\n * navigation is cancelled or errors — never on success (#722). On the success\n * path pass `cancelled = false`: the reference is dropped without aborting, so\n * a listener that captured the signal still sees `aborted === false`.\n *\n * On the failure/cancellation path (`cancelled = true`) pass the originating\n * `reason` so `signal.reason` carries router/error context (a `RouterError`,\n * or the value a sync leave listener threw) — consistent with the cancellation\n * abort `RouterError(TRANSITION_CANCELLED)`, not a generic `AbortError` (#943).\n * `abort()` is idempotent: a controller already aborted by a superseding\n * navigation keeps its first (also-meaningful) reason.\n */\n #cleanupController(\n controller: AbortController,\n cancelled: boolean,\n reason?: unknown,\n ): void {\n if (cancelled) {\n controller.abort(reason);\n }\n\n // Stryker disable next-line ConditionalExpression,EqualityOperator,BlockStatement: equivalent — controller identity-guard; cleanup correctness is enforced by #abortPreviousNavigation + the navigationId/isCurrentNav checks. Full suite stays green with `=== → !==` (nulls the wrong controller) and with the body removed (ref never nulled), so no mutant here is observable.\n if (this.#currentController === controller) {\n this.#currentController = null;\n }\n }\n\n #abortPreviousNavigation(externalSignal?: AbortSignal): void {\n if (this.#deps.isTransitioning()) {\n this.#deps.logger.warn(\n \"router.navigate\",\n \"Concurrent navigation detected on shared router instance. \" +\n \"For SSR, use cloneRouter() to create isolated instance per request.\",\n );\n // The FSM CANCEL action aborts the previous controller — no\n // direct controller.abort here (RFC navigation-cancellation-unification §5).\n this.#deps.cancelNavigation();\n }\n\n if (externalSignal?.aborted) {\n throw new RouterError(errorCodes.TRANSITION_CANCELLED, {\n reason: externalSignal.reason,\n });\n }\n }\n}\n","// packages/core/src/namespaces/RouterLifecycleNamespace/RouterLifecycleNamespace.ts\n\nimport { errorCodes } from \"../../constants\";\nimport { RouterError } from \"../../RouterError\";\n\nimport type { RouterLifecycleDependencies } from \"./types\";\nimport type { NavigationOptions, State } from \"../../types\";\n\nconst REPLACE_OPTS: NavigationOptions = Object.freeze({ replace: true });\n\n/**\n * Independent namespace for managing router lifecycle.\n *\n * Handles start() and stop(). Lifecycle state (isActive, isStarted) is managed\n * by RouterFSM in the facade (Router.ts).\n */\nexport class RouterLifecycleNamespace {\n #deps!: RouterLifecycleDependencies;\n\n // =========================================================================\n // Dependency injection\n // =========================================================================\n\n /**\n * Sets dependencies for lifecycle operations.\n * Must be called before using lifecycle methods.\n */\n setDependencies(deps: RouterLifecycleDependencies): void {\n this.#deps = deps;\n }\n\n // =========================================================================\n // Instance methods\n // =========================================================================\n\n /**\n * Starts the router with the given path.\n *\n * Guards (concurrent start, already started) are handled by the facade via\n * RouterFSM state checks before this method is called.\n */\n async start(startPath: string): Promise<State> {\n const deps = this.#deps;\n\n // #1185: this method is the start-interceptor target — it runs AFTER the\n // whole interceptor chain. A stop() during that window sent STOP\n // (STARTING → IDLE via the FSM table), so if the router is back at IDLE the\n // start was cancelled mid-window; reject instead of committing a state on a\n // stopped router (mirrors the guard phase, which cancels from\n // TRANSITION_STARTED). `isIdle()` is deliberate — a dispose() mid-window\n // leaves the FSM DISPOSED, which the navigateToState / navigateToNotFound\n // liveness gate rejects as ROUTER_DISPOSED (#1186), not conflated with a\n // cancel.\n if (deps.isIdle()) {\n throw new RouterError(errorCodes.TRANSITION_CANCELLED);\n }\n\n const options = deps.getOptions();\n\n // Invariant guard (#939): core is platform-agnostic, so the caller must\n // provide a string path. Without a browser-plugin start interceptor to\n // inject a location, a non-string `startPath` (e.g. `start(undefined)`)\n // would otherwise reach matchPath() and throw a cryptic, code-less\n // `TypeError: …codePointAt` deep inside path-matcher. This guard runs AFTER\n // the interceptor chain (browser-plugin substitutes the location upstream),\n // so it only fires when nothing supplied a path — turning the cryptic crash\n // into an actionable error. Symmetric with the subscribe / navigateToNotFound\n // type guards; the validator deliberately permits `undefined` at the facade\n // for exactly the browser-plugin-override case.\n if (typeof startPath !== \"string\") {\n throw new TypeError(\n `[router.start] path must be a string, got ${typeof startPath}`,\n );\n }\n\n const matchedState = deps.matchPath(startPath);\n\n if (!matchedState && !options.allowNotFound) {\n const err = new RouterError(errorCodes.ROUTE_NOT_FOUND, {\n path: startPath,\n });\n\n deps.emitTransitionError(undefined, undefined, err);\n\n throw err;\n }\n\n deps.completeStart();\n\n if (matchedState) {\n // navigateToState commits matchedState verbatim — same primitive URL\n // plugins use on popstate / navigate-event (#525). Keeps trailing-slash\n // and any other source-URL flavor that matchPath produced; skips the\n // redundant forwardState+buildPath round-trip in buildNavigateState.\n return deps.navigateToState(matchedState, REPLACE_OPTS);\n }\n\n return deps.navigateToNotFound(startPath);\n }\n\n /**\n * Stops the router and resets state.\n *\n * Called for READY / TRANSITION_STARTED and, since #1185, STARTING (a stop()\n * that cancels a parked start — state is still `undefined` there, so\n * `clearState()` is a no-op that keeps the reset symmetric). The facade\n * handles IDLE / DISPOSED.\n */\n stop(): void {\n this.#deps.clearState();\n }\n}\n","import type { FSMConfig, TransitionInfo, TransitionListener } from \"./types\";\n\n/**\n * Shared guard for the engine-wide invariant \"the state is declared in\n * `config.transitions`\". Applied at every state-entry-point (constructor\n * `initial` and `on`'s `from`) so an undeclared state fails loud with\n * an explicit error instead of bricking the FSM or dead-registering an action\n * (#885). Returns the state's transition map for the caller to reuse.\n */\nfunction requireDeclared<TStates extends string, TEvents extends string>(\n transitions: Record<TStates, Partial<Record<TEvents, TStates>>>,\n state: TStates,\n where: string,\n): Partial<Record<TEvents, TStates>> {\n const stateTransitions = transitions[state];\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- runtime guard for JS / cast / string-typed callers passing a state outside TStates\n if (stateTransitions === undefined) {\n throw new Error(\n `[FSM.${where}] state \"${state}\" is not declared in config.transitions`,\n );\n }\n\n return stateTransitions;\n}\n\n/**\n * Synchronous finite state machine engine.\n *\n * Reentrancy: `send()` inside `onTransition` listener is allowed but unbounded —\n * callers are responsible for preventing infinite loops.\n *\n * Exceptions: if a listener throws, the exception propagates to the caller.\n * State is already updated before listeners fire, so `getState()` reflects the\n * new state even if the exception escapes `send()`.\n */\nexport class FSM<\n TStates extends string,\n TEvents extends string,\n TContext,\n TPayloadMap extends Partial<Record<TEvents, unknown>> = Record<never, never>,\n> {\n #state: TStates;\n #currentTransitions: Partial<Record<TEvents, TStates>>;\n #listenerCount = 0;\n #actions: Map<TStates, Map<TEvents, (payload: unknown) => void>> | null =\n null;\n readonly #context: TContext;\n readonly #transitions: Record<TStates, Partial<Record<TEvents, TStates>>>;\n readonly #listeners: (TransitionListener<\n TStates,\n TEvents,\n TPayloadMap\n > | null)[] = [];\n\n constructor(config: FSMConfig<TStates, TEvents, TContext>) {\n this.#state = config.initial;\n this.#context = config.context;\n this.#transitions = config.transitions;\n this.#currentTransitions = requireDeclared(\n config.transitions,\n config.initial,\n \"constructor\",\n );\n\n // #1159: validate table closure — every declared transition target must\n // itself be a declared state. `send()` applies table values\n // (`this.#transitions[nextState]`) without re-checking, so a dangling\n // target would silently enter an undeclared state (violating Validity #1)\n // and brick `canSend()` (violating No-bricking #10). One cold-path\n // O(states×events) pass at construction fails loud instead — the fourth\n // state-entry-point, mirroring the `initial` / `on` guards. Explicit\n // `undefined` values are the declared \"no transition\" no-op (send() returns\n // the current state) and are skipped. Post-construction mutation of the\n // shared table stays a documented GIGO boundary (Edge #5).\n for (const state of Object.keys(config.transitions)) {\n const stateTransitions = config.transitions[state as TStates];\n\n for (const event of Object.keys(stateTransitions)) {\n const target = stateTransitions[event as TEvents];\n\n if (target !== undefined) {\n requireDeclared(config.transitions, target, \"constructor\");\n }\n }\n }\n }\n\n send<E extends TEvents>(\n event: E,\n ...args: E extends keyof TPayloadMap ? [TPayloadMap[E]] : [undefined?]\n ): TStates {\n const nextState = this.#currentTransitions[event];\n\n if (nextState === undefined) {\n return this.#state;\n }\n\n const from = this.#state;\n\n this.#state = nextState;\n this.#currentTransitions = this.#transitions[nextState];\n\n const payload = args[0] as TPayloadMap[TEvents] | undefined;\n\n if (this.#actions !== null) {\n const action = this.#actions.get(from)?.get(event);\n\n if (action !== undefined) {\n action(payload);\n }\n }\n\n // Stryker disable next-line ConditionalExpression: equivalent — count>0 is a perf gate to skip the dispatch loop; `true` always enters it, but with no live listener `#listeners` holds only null slots and the loop body guards `listener !== null`, so dispatch is a no-op either way. The EqualityOperator `<=0` sibling on this line stays killed (not silenced here).\n if (this.#listenerCount > 0) {\n // `info` is structurally a valid TransitionInfo, but the distributive\n // union can't be matched to one variant while `event`/`payload` are\n // generic here — erase through `unknown` (TS2352), same spirit as the\n // `args[0]` cast above.\n const info = {\n from,\n to: nextState,\n event,\n payload,\n } as unknown as TransitionInfo<TStates, TEvents, TPayloadMap>;\n\n for (const listener of this.#listeners) {\n if (listener !== null) {\n listener(info);\n }\n }\n }\n\n return this.#state;\n }\n\n canSend(event: TEvents): boolean {\n return this.#currentTransitions[event] !== undefined;\n }\n\n getState(): TStates {\n return this.#state;\n }\n\n getContext(): TContext {\n return this.#context;\n }\n\n on<E extends TEvents>(\n from: TStates,\n event: E,\n action: E extends keyof TPayloadMap\n ? (payload: TPayloadMap[E]) => void\n : () => void,\n ): () => void {\n requireDeclared(this.#transitions, from, \"on\");\n\n this.#actions ??= new Map();\n\n let stateActions = this.#actions.get(from);\n\n if (!stateActions) {\n stateActions = new Map();\n this.#actions.set(from, stateActions);\n }\n\n const capturedAction = action as (payload: unknown) => void;\n\n stateActions.set(event, capturedAction);\n\n return () => {\n // Stryker disable next-line OptionalChaining: equivalent — `#actions` is assigned (`??= new Map()` above) before this unsubscribe closure is created and returned, so it is never null when the closure runs; `?.` can't short-circuit and behaves identically to `.get`.\n const stateMap = this.#actions?.get(from);\n\n if (stateMap?.get(event) === capturedAction) {\n stateMap.delete(event);\n }\n };\n }\n\n onTransition(\n listener: (info: TransitionInfo<TStates, TEvents, TPayloadMap>) => void,\n ): () => void {\n const nullIndex = this.#listeners.indexOf(null);\n let index: number;\n\n if (nullIndex === -1) {\n index = this.#listeners.length;\n this.#listeners.push(listener);\n } else {\n this.#listeners[nullIndex] = listener;\n index = nullIndex;\n }\n\n this.#listenerCount++;\n let subscribed = true;\n\n return () => {\n if (!subscribed) {\n return;\n }\n\n subscribed = false;\n this.#listeners[index] = null;\n // Stryker disable next-line UpdateOperator: equivalent — #listenerCount feeds only the `> 0` loop gate; `++` inflates it but the loop then iterates already-nulled slots (no-op), and no public reader exposes the count, so the miscount is unobservable.\n this.#listenerCount--;\n };\n }\n}\n","// packages/core/src/routerFSM.ts\n\nimport { FSM } from \"./foundation/fsm\";\n\nimport type { FSMConfig } from \"./foundation/fsm\";\nimport type { NavigationOptions, State } from \"./types\";\n\n/**\n * Router FSM states.\n *\n * - IDLE: Router not started or stopped\n * - STARTING: Router is initializing\n * - READY: Router is ready for navigation\n * - TRANSITION_STARTED: Navigation in progress (before deactivation guards)\n * - LEAVE_APPROVED: Deactivation guards passed, activation guards pending\n * - DISPOSED: Router has been disposed (R2+)\n */\nexport const routerStates = {\n IDLE: \"IDLE\",\n STARTING: \"STARTING\",\n READY: \"READY\",\n TRANSITION_STARTED: \"TRANSITION_STARTED\",\n LEAVE_APPROVED: \"LEAVE_APPROVED\",\n DISPOSED: \"DISPOSED\",\n} as const;\n\nexport type RouterState = (typeof routerStates)[keyof typeof routerStates];\n\n/**\n * Router FSM events.\n *\n * - START: Begin router initialization\n * - STARTED: Router initialization complete\n * - NAVIGATE: Begin navigation\n * - COMPLETE: Navigation completed successfully\n * - FAIL: Navigation or initialization failed\n * - CANCEL: Navigation cancelled\n * - STOP: Stop router\n * - DISPOSE: Dispose router (R2+)\n */\nexport const routerEvents = {\n START: \"START\",\n STARTED: \"STARTED\",\n NAVIGATE: \"NAVIGATE\",\n LEAVE_APPROVE: \"LEAVE_APPROVE\",\n COMPLETE: \"COMPLETE\",\n FAIL: \"FAIL\",\n CANCEL: \"CANCEL\",\n STOP: \"STOP\",\n DISPOSE: \"DISPOSE\",\n} as const;\n\nexport type RouterEvent = (typeof routerEvents)[keyof typeof routerEvents];\n\n/**\n * Per-event payloads for the router FSM (#1169 commit-gate). The three hot\n * navigation transitions carry their transition states so the FSM action\n * dispatched by `send()` emits the matching transition event — i.e. events are\n * literal consequences of FSM transitions (no `forceState` + manual emit). See\n * `EventBusNamespace.#setupFSMActions`.\n */\nexport interface RouterPayloads {\n NAVIGATE: { toState: State; fromState?: State | undefined };\n LEAVE_APPROVE: { toState: State; fromState?: State | undefined };\n COMPLETE: {\n toState: State;\n fromState?: State | undefined;\n opts?: NavigationOptions | undefined;\n };\n}\n\n/**\n * Router FSM configuration.\n *\n * Transitions:\n * - IDLE → STARTING (START), DISPOSED (DISPOSE)\n * - STARTING → READY (STARTED), IDLE (FAIL, STOP), DISPOSED (DISPOSE)\n * - READY → TRANSITION_STARTED (NAVIGATE), READY (FAIL, self-loop for early validation errors), IDLE (STOP), DISPOSED (DISPOSE)\n * - TRANSITION_STARTED → LEAVE_APPROVED (LEAVE_APPROVE), TRANSITION_STARTED (NAVIGATE, self-loop), READY (CANCEL, FAIL), DISPOSED (DISPOSE)\n * - LEAVE_APPROVED → READY (COMPLETE, CANCEL, FAIL), TRANSITION_STARTED (NAVIGATE), DISPOSED (DISPOSE)\n * - DISPOSED → (no transitions)\n *\n * DISPOSE is wired from every non-DISPOSED state so `router.dispose()` always\n * settles the FSM at DISPOSED. The facade orchestrates cleanup through IDLE\n * for healthy flows; the direct transitions guarantee the FSM is not left\n * stuck if cleanup is skipped (e.g. dispose mid-STARTING when the start\n * pipeline threw before STARTED/FAIL).\n */\nconst routerFSMConfig: FSMConfig<RouterState, RouterEvent, null> = {\n initial: routerStates.IDLE,\n context: null,\n transitions: {\n [routerStates.IDLE]: {\n [routerEvents.START]: routerStates.STARTING,\n [routerEvents.DISPOSE]: routerStates.DISPOSED,\n },\n [routerStates.STARTING]: {\n [routerEvents.STARTED]: routerStates.READY,\n [routerEvents.FAIL]: routerStates.IDLE,\n [routerEvents.STOP]: routerStates.IDLE,\n [routerEvents.DISPOSE]: routerStates.DISPOSED,\n },\n [routerStates.READY]: {\n [routerEvents.NAVIGATE]: routerStates.TRANSITION_STARTED,\n [routerEvents.FAIL]: routerStates.READY,\n [routerEvents.STOP]: routerStates.IDLE,\n [routerEvents.DISPOSE]: routerStates.DISPOSED,\n },\n [routerStates.TRANSITION_STARTED]: {\n [routerEvents.NAVIGATE]: routerStates.TRANSITION_STARTED,\n [routerEvents.LEAVE_APPROVE]: routerStates.LEAVE_APPROVED,\n [routerEvents.CANCEL]: routerStates.READY,\n [routerEvents.FAIL]: routerStates.READY,\n [routerEvents.DISPOSE]: routerStates.DISPOSED,\n },\n [routerStates.LEAVE_APPROVED]: {\n [routerEvents.NAVIGATE]: routerStates.TRANSITION_STARTED,\n [routerEvents.COMPLETE]: routerStates.READY,\n [routerEvents.CANCEL]: routerStates.READY,\n [routerEvents.FAIL]: routerStates.READY,\n [routerEvents.DISPOSE]: routerStates.DISPOSED,\n },\n [routerStates.DISPOSED]: {},\n },\n};\n\n/**\n * Factory function to create a router FSM instance.\n *\n * @returns FSM instance with initial state \"IDLE\"\n */\nexport function createRouterFSM(): FSM<\n RouterState,\n RouterEvent,\n null,\n RouterPayloads\n> {\n return new FSM<RouterState, RouterEvent, null, RouterPayloads>(\n routerFSMConfig,\n );\n}\n","// packages/core/src/namespaces/EventBusNamespace/EventBusNamespace.ts\n\nimport { errorCodes, events } from \"../../constants\";\nimport { RouterError } from \"../../RouterError\";\nimport { routerEvents, routerStates } from \"../../routerFSM\";\n\nimport type { EventBusOptions } from \"./types\";\nimport type { EventEmitter } from \"../../foundation/event-emitter\";\nimport type { FSM } from \"../../foundation/fsm\";\nimport type { RouterEvent, RouterPayloads, RouterState } from \"../../routerFSM\";\nimport type {\n EventName,\n LeaveFn,\n LeaveState,\n NavigationOptions,\n Plugin,\n State,\n SubscribeFn,\n TreeChangedEvent,\n Unsubscribe,\n EventMethodMap,\n} from \"../../types\";\nimport type { RouterEventMap } from \"../../types/internal\";\nimport type { RouterValidator } from \"../../types/RouterValidator\";\n\n/**\n * Internal-only event key for route-tree mutations. Lives on the same\n * `EventEmitter` as the 7 transition events but never enters the public\n * `EventName` union — reachable only through\n * `getRoutesApi(router).subscribeChanges()`.\n */\nconst TREE_CHANGED = \"TREE_CHANGED\";\n\nfunction ensureError(value: unknown): Error {\n return value instanceof Error ? value : new Error(String(value));\n}\n\nfunction settleLeavePromises(\n promises: Promise<void>[],\n firstSyncError: unknown,\n signal: AbortSignal,\n): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n // #1197 — canonicalize the abort outcome: reject with a\n // `RouterError(TRANSITION_CANCELLED)` carrying the external reason, so the\n // no-guards leave path classifies identically to the guard path (no raw\n // reject, no spurious TRANSITION_ERROR). Internal sources\n // (supersede/stop/dispose) already abort with such a RouterError — thread it\n // through unchanged so the #943 reason is preserved.\n const onAbort = (): void => {\n const reason: unknown = signal.reason;\n\n reject(\n reason instanceof RouterError &&\n reason.code === errorCodes.TRANSITION_CANCELLED\n ? reason\n : new RouterError(errorCodes.TRANSITION_CANCELLED, { reason }),\n );\n };\n\n if (signal.aborted) {\n onAbort();\n\n return;\n }\n\n // Stryker disable next-line ObjectLiteral,BooleanLiteral: equivalent — `{ once: true }` is redundant: onAbort fires at most once (a signal aborts once) and the success path explicitly removeEventListener's it, so dropping `once` is unobservable. StringLiteral sibling stays live (the \"abort\" event name is killed).\n signal.addEventListener(\"abort\", onAbort, { once: true });\n\n void Promise.allSettled(promises).then((results) => {\n // Stryker disable next-line StringLiteral: equivalent — this cleanup name is redundant: onAbort is registered with `{ once: true }` and the per-navigation signal is discarded unaborted on success, so failing to remove the listener leaks nothing observable.\n signal.removeEventListener(\"abort\", onAbort);\n\n // Stryker disable next-line BlockStatement: equivalent — emptying the post-allSettled abort-race early-return falls through to resolve()/reject(), but the abort handler already settled the promise, so the extra settle is a no-op. CE sibling stays live (→true hangs the pipeline = killed via timeout).\n if (signal.aborted) {\n // Race lost to abort — the abort handler already rejected; do nothing\n return;\n }\n\n if (firstSyncError !== undefined) {\n reject(ensureError(firstSyncError));\n\n return;\n }\n\n const rejected = results.find(\n (result): result is PromiseRejectedResult =>\n result.status === \"rejected\",\n );\n\n if (rejected !== undefined) {\n reject(ensureError(rejected.reason));\n\n return;\n }\n\n resolve();\n });\n });\n}\n\nexport class EventBusNamespace {\n readonly #fsm: FSM<RouterState, RouterEvent, null, RouterPayloads>;\n readonly #emitter: EventEmitter<RouterEventMap>;\n // Effect of the FSM CANCEL action: aborts the in-flight navigation's\n // controller. Wired to NavigationNamespace.\n readonly #abortController: (reason?: unknown) => void;\n // Lazy accessor for the opt-in RouterValidator (wired by wireNamespaces).\n // Returns `null` until validation-plugin is registered — so the proactive\n // listener-count threshold (#1188) costs the no-plugin path nothing.\n #getValidator: (() => RouterValidator | null) | undefined;\n readonly #leaveListeners: LeaveFn[] = [];\n\n // Depth of the synchronous transition-dispatch window — elevated while a\n // transition event is being emitted (`emitTransition*`) or a `subscribeLeave`\n // listener batch runs. `isProcessing()` reads it so the navigation facade can\n // reject a synchronous reentrant navigate() from inside a transition listener\n // with REENTRANT_NAVIGATION (RFC navigation-cancellation-unification §4). A\n // counter (not a boolean) tolerates legitimately-nested transition emits. The\n // ceiling-bounded \"allow reentrant navigate, throw RecursionDepthError at\n // `maxEventDepth`\" behaviour (#935/#945) is gone: such a navigate now throws\n // REENTRANT_NAVIGATION at depth 1, before it can recurse. (The emitter's old\n // `maxEventDepth` depth-bound is gone too — re-entrant emits are coalesced to a\n // no-op at the emitter, #1033 — so no event can re-enter its own dispatch.)\n #dispatchDepth = 0;\n\n #currentToState: State | undefined;\n #pendingToState: State | undefined;\n #pendingFromState: State | undefined;\n #pendingError: unknown;\n // Abort reason for the pending CANCEL — read by handleCancel, set by sendCancel.\n #pendingCancelReason: unknown;\n\n constructor(options: EventBusOptions) {\n this.#fsm = options.routerFSM;\n this.#emitter = options.emitter;\n this.#abortController = options.abortController;\n this.#currentToState = undefined;\n this.#setupFSMActions();\n }\n\n static validateSubscribeListener(listener: unknown): void {\n if (typeof listener !== \"function\") {\n throw new TypeError(\n \"[router.subscribe] Expected a function. \" +\n \"For Observable pattern use observable(router) from @real-router/rx\",\n );\n }\n }\n\n /**\n * Validates the `subscribeLeave` listener. Unlike\n * {@link validateSubscribeListener}, the error carries **no**\n * `@real-router/rx` hint — rx exposes the Observable pattern for *success*\n * transitions (`observable(router)`, `state$`, `events$`), not for leave\n * events, so steering leave-listener misuse toward rx would mislead. The\n * asymmetry is intentional (mirrored in `core/CLAUDE.md`).\n */\n static validateSubscribeLeaveListener(listener: unknown): void {\n if (typeof listener !== \"function\") {\n throw new TypeError(\"[router.subscribeLeave] Expected a function\");\n }\n }\n\n emitRouterStart(): void {\n this.#emitter.emit(events.ROUTER_START);\n }\n\n emitRouterStop(): void {\n this.#emitter.emit(events.ROUTER_STOP);\n }\n\n emitTransitionStart(toState: State, fromState?: State): void {\n this.#dispatchDepth++;\n try {\n this.#emitter.emit(events.TRANSITION_START, toState, fromState);\n } finally {\n this.#dispatchDepth--;\n }\n }\n\n emitTransitionSuccess(\n toState: State,\n fromState?: State,\n opts?: NavigationOptions,\n ): void {\n this.#dispatchDepth++;\n try {\n this.#emitter.emit(events.TRANSITION_SUCCESS, toState, fromState, opts);\n } finally {\n this.#dispatchDepth--;\n }\n }\n\n emitTransitionError(\n toState?: State,\n fromState?: State,\n error?: RouterError,\n ): void {\n this.#dispatchDepth++;\n try {\n this.#emitter.emit(events.TRANSITION_ERROR, toState, fromState, error);\n } finally {\n this.#dispatchDepth--;\n }\n }\n\n emitTransitionCancel(toState: State, fromState?: State): void {\n this.#dispatchDepth++;\n try {\n this.#emitter.emit(events.TRANSITION_CANCEL, toState, fromState);\n } finally {\n this.#dispatchDepth--;\n }\n }\n\n emitTransitionLeaveApprove(toState: State, fromState?: State): void {\n this.#dispatchDepth++;\n try {\n this.#emitter.emit(events.TRANSITION_LEAVE_APPROVE, toState, fromState);\n } finally {\n this.#dispatchDepth--;\n }\n }\n\n /**\n * True while a transition event is being dispatched synchronously — an\n * `emitTransition*` call or a `subscribeLeave` listener batch is on the stack.\n * The navigation facade reads this to reject a synchronous reentrant\n * navigate() from inside a transition listener (RFC §4).\n */\n isProcessing(): boolean {\n return this.#dispatchDepth > 0;\n }\n\n /**\n * Emits the internal `TREE_CHANGED` event after a structural route-tree\n * mutation. Reuses the shared `EventEmitter` — so re-entrancy coalescing\n * (#1033) and per-listener error isolation (`onListenerError`) apply\n * automatically.\n */\n emitTreeChanged(event: TreeChangedEvent): void {\n this.#emitter.emit(TREE_CHANGED, event);\n }\n\n /**\n * True while a `TREE_CHANGED` event is being dispatched synchronously.\n * Delegates to the emitter's own in-flight tracking (#1034) — `getRoutesApi`\n * reads this to reject reentrant route-CRUD from a `subscribeChanges` handler\n * (#1032).\n */\n isEmittingTreeChanged(): boolean {\n return this.#emitter.isDispatching(TREE_CHANGED);\n }\n\n /**\n * Subscribes to `TREE_CHANGED`. **Lenient** duplicate semantics (mirrors\n * {@link subscribe}): each call wraps the handler in a fresh closure, so N\n * registrations of the same reference produce N independent subscriptions.\n */\n subscribeTreeChanged(\n handler: (event: TreeChangedEvent) => void,\n ): Unsubscribe {\n // Same disposed-state enforcement as subscribe()/subscribeLeave() (#946),\n // completing the guard across all three subscription primitives — extended\n // here to the internal route-tree channel (#982). A `subscribeChanges`\n // reference bound before dispose() (`const s = routes.subscribeChanges\n // .bind(routes)`) reaches this method via the getRoutesApi delegate, which\n // — unlike its add/remove/update siblings — does not itself check\n // isDisposed(). Without this guard, `emitter.on` would re-register a\n // TREE_CHANGED listener that can never fire (clearAll already ran, the FSM\n // is DISPOSED, the route tree is torn down, no future emit) — a silent\n // no-op, the internal-channel counterpart of the #946 hazard.\n if (this.isDisposed()) {\n throw new RouterError(errorCodes.ROUTER_DISPOSED);\n }\n\n return this.#emitter.on(TREE_CHANGED, (event: TreeChangedEvent) => {\n handler(event);\n });\n }\n\n /** Number of active `TREE_CHANGED` listeners (drives conditional emit). */\n treeChangedListenerCount(): number {\n return this.#emitter.listenerCount(TREE_CHANGED);\n }\n\n sendStart(): void {\n this.#fsm.send(routerEvents.START);\n }\n\n sendStop(): void {\n this.#fsm.send(routerEvents.STOP);\n }\n\n sendDispose(): void {\n this.#fsm.send(routerEvents.DISPOSE);\n }\n\n sendStarted(): void {\n this.#fsm.send(routerEvents.STARTED);\n }\n\n sendNavigate(toState: State, fromState?: State): void {\n this.#currentToState = toState;\n // Table-driven: the FSM action emits TRANSITION_START (#1169 D-full). A\n // NAVIGATE that the table rejects is a no-op — the FSM never leaves an\n // invalid state and no event fires.\n this.#fsm.send(routerEvents.NAVIGATE, { toState, fromState });\n }\n\n sendComplete(\n state: State,\n fromState?: State,\n opts: NavigationOptions = {},\n ): void {\n // Table-driven: the FSM action emits TRANSITION_SUCCESS (#1169 D-full).\n // COMPLETE from IDLE/DISPOSED (a listener stopped/disposed mid-transition)\n // is a table no-op — no resurrection, no phantom success emit.\n this.#fsm.send(routerEvents.COMPLETE, { toState: state, fromState, opts });\n\n // Nav committed — clear so a later stop()/dispose() cannot cancel a finished\n // navigation. Unconditional now that synchronous reentrant navigate is banned\n // (RFC §4): nothing can replace #currentToState during the emit above, so the\n // #308 reentrant-preserve guard is no longer needed.\n this.#currentToState = undefined;\n }\n\n sendLeaveApprove(toState: State, fromState?: State): void {\n // Table-driven: the FSM action emits TRANSITION_LEAVE_APPROVE (#1169 D-full).\n // LEAVE_APPROVE from IDLE/DISPOSED is a table no-op — no resurrection.\n this.#fsm.send(routerEvents.LEAVE_APPROVE, { toState, fromState });\n }\n\n sendFail(toState?: State, fromState?: State, error?: unknown): void {\n this.#pendingToState = toState;\n this.#pendingFromState = fromState;\n this.#pendingError = error;\n this.#fsm.send(routerEvents.FAIL);\n\n // Nav failed — clear (unconditional; synchronous reentrant navigate is\n // banned (RFC §4), so nothing replaces #currentToState during the emit).\n this.#currentToState = undefined;\n }\n\n /**\n * Surfaces a `TRANSITION_ERROR` for callers that do **not** know — or do not\n * control — the current FSM state: the plugin-facing `emitTransitionError`\n * primitive (`getPluginApi`), the dispose chain, and validator / same-state\n * rejections. It is the state-agnostic counterpart to {@link sendFail}.\n *\n * **What \"Safe\" means here.** The error event is never *dropped*, whatever the\n * FSM state — it does **not** mean the method catches every error. Errors\n * thrown *inside* a `TRANSITION_ERROR` listener are isolated by the\n * `EventEmitter`'s per-listener `onListenerError` sink, not by this method.\n *\n * **Why it branches on its own FSM state.** When the FSM is settled in `READY`\n * (no transition in flight) it routes through the FSM `FAIL` action via\n * {@link sendFail}, so the error rides the normal FSM-driven emit. Otherwise —\n * the router may be starting, mid-transition, or torn down — it emits\n * `TRANSITION_ERROR` directly: a fire-and-forget error report from an unknown\n * state must not drive a second FSM transition that could collide with an\n * in-flight one. Both branches guarantee the event reaches subscribers.\n */\n sendFailSafe(toState?: State, fromState?: State, error?: unknown): void {\n if (this.isReady()) {\n this.sendFail(toState, fromState, error);\n } else {\n this.emitTransitionError(toState, fromState, error as RouterError);\n }\n }\n\n sendCancel(toState: State, fromState?: State, reason?: unknown): void {\n this.#pendingToState = toState;\n this.#pendingFromState = fromState;\n this.#pendingCancelReason = reason;\n this.#fsm.send(routerEvents.CANCEL);\n\n // Nav cancelled — clear (unconditional; synchronous reentrant navigate is\n // banned (RFC §4), so nothing replaces #currentToState during the emit).\n this.#currentToState = undefined;\n }\n\n canBeginTransition(): boolean {\n return this.#fsm.canSend(routerEvents.NAVIGATE);\n }\n\n canStart(): boolean {\n return this.#fsm.canSend(routerEvents.START);\n }\n\n canCancel(): boolean {\n return this.#fsm.canSend(routerEvents.CANCEL);\n }\n\n isActive(): boolean {\n const fsmState = this.#fsm.getState();\n\n return fsmState !== routerStates.IDLE && fsmState !== routerStates.DISPOSED;\n }\n\n isDisposed(): boolean {\n return this.#fsm.getState() === routerStates.DISPOSED;\n }\n\n isTransitioning(): boolean {\n const state = this.#fsm.getState();\n\n return (\n state === routerStates.TRANSITION_STARTED ||\n state === routerStates.LEAVE_APPROVED\n );\n }\n\n isLeaveApproved(): boolean {\n return this.#fsm.getState() === routerStates.LEAVE_APPROVED;\n }\n\n isReady(): boolean {\n return this.#fsm.getState() === routerStates.READY;\n }\n\n isStarting(): boolean {\n return this.#fsm.getState() === routerStates.STARTING;\n }\n\n isIdle(): boolean {\n return this.#fsm.getState() === routerStates.IDLE;\n }\n\n /**\n * Plugin-author API for subscribing to internal router events.\n *\n * @remarks\n *\n * **Duplicate-registration semantics — strict (throws).** Passing the same\n * callback reference twice for the same event throws\n * `Error(\"Duplicate listener for ...\")` from the underlying `EventEmitter`.\n * This is loud-on-misuse by design: plugin code is expected to register\n * each callback once. The contract differs from {@link subscribe} /\n * {@link subscribeLeave}, which are end-user surfaces and silently accept\n * duplicates.\n */\n addEventListener<E extends EventName>(\n eventName: E,\n cb: Plugin[EventMethodMap[E]],\n ): Unsubscribe {\n this.#checkListenerThreshold(eventName, \"addEventListener\");\n\n return this.#emitter.on(\n eventName,\n cb as (...args: RouterEventMap[typeof eventName]) => void,\n );\n }\n\n /**\n * End-user / UI-binding API for subscribing to successful transitions.\n *\n * @remarks\n *\n * **Duplicate-registration semantics — independent.** Each call wraps\n * `listener` in a fresh closure and registers it as a distinct internal\n * slot. `router.subscribe(fn)` twice produces **two** active subscriptions;\n * `fn` fires twice per `TRANSITION_SUCCESS`. The returned `Unsubscribe` is\n * paired with its specific call — invoking it removes exactly that\n * registration.\n *\n * This contract differs from {@link addEventListener} (plugin API, throws\n * on duplicate). End-user code that wants idempotent registration must\n * gate itself, e.g. `if (!unsub) unsub = router.subscribe(fn);`.\n */\n subscribe(listener: SubscribeFn): Unsubscribe {\n // Enforce the disposed state HERE, not only on the facade. A reference\n // bound before dispose() (`const s = router.subscribe.bind(router)`)\n // bypasses the facade's #markDisposed swap and reaches this method\n // directly. Without this guard, `emitter.on` would silently re-register a\n // listener that can never fire (clearAll already ran, FSM is DISPOSED, no\n // future emit) — a silent no-op / stuck-UI hazard (#946).\n if (this.isDisposed()) {\n throw new RouterError(errorCodes.ROUTER_DISPOSED);\n }\n\n this.#checkListenerThreshold(events.TRANSITION_SUCCESS, \"subscribe\");\n\n // `subscribe` is fire-and-forget; the listener's return value is ignored at\n // the type level (`SubscribeFn` is `=> void`). A void-typed async listener\n // still returns a rejecting Promise at runtime, so return it to the emitter,\n // whose central isolation routes the rejection to the same `onListenerError`\n // sink a sync throw flows through — instead of leaking an `unhandledRejection`\n // (fatal under `--unhandled-rejections=strict`, the Node 22+ default). This\n // is #944, now folded into the emitter's central #1412 async isolation — the\n // former per-site `.catch` here is redundant (symmetric with `subscribeLeave`,\n // which isolates via `Promise.allSettled`).\n return this.#emitter.on(\n events.TRANSITION_SUCCESS,\n (toState: State, fromState?: State) => {\n // Return the listener's runtime value to the emitter so its central\n // isolation catches an async listener's rejection (#944/#1412). Read the\n // void-typed result into `unknown` first — a returned `void` expression\n // is forbidden, but returning the read `unknown` is fine.\n // eslint-disable-next-line @typescript-eslint/no-confusing-void-expression -- read the void-typed listener's runtime value so it can be returned for central async isolation (#944/#1412)\n const result: unknown = listener({\n route: toState,\n previousRoute: fromState,\n });\n\n return result;\n },\n );\n }\n\n /**\n * End-user / UI-binding API for subscribing to **approved** route departures\n * (`LEAVE_APPROVED` phase): all `canDeactivate` guards have passed, but the\n * departure is **tentative, not committed** — an activation (`canActivate`)\n * guard can still reject (or the target route be removed mid-transition),\n * leaving the user on the current route (#932). Treat the leave as tentative\n * for non-idempotent side-effects and use the payload `signal` (which aborts\n * with the failure reason, #943) to roll back when the navigation does not\n * commit. Async listeners block the activation phase.\n *\n * @remarks\n *\n * **Duplicate-registration semantics — independent.** Each call pushes\n * `listener` onto the internal array; `router.subscribeLeave(fn)` twice\n * produces two entries and `fn` fires twice per leave. Each returned\n * `Unsubscribe` is **idempotent** (a `removed` flag, #1349) and removes\n * exactly ONE entry — the first still matching the reference (`indexOf`\n * semantic). So a repeated call of one unsubscribe is a true no-op and does\n * **not** touch the other registration; N subscribes + M *distinct*\n * unsubscribes leave N − M entries. Which physical entry survives is\n * irrelevant — the reference is the same.\n *\n * Contract differs from {@link addEventListener} (throws on duplicate).\n * For idempotent *registration* (one active subscription), gate at the call site.\n */\n subscribeLeave(listener: LeaveFn): Unsubscribe {\n // Same disposed-state enforcement as subscribe() (#946): a pre-bound\n // reference would otherwise push onto #leaveListeners after dispose() and\n // silently never fire (FSM is DISPOSED, no LEAVE_APPROVE emit).\n if (this.isDisposed()) {\n throw new RouterError(errorCodes.ROUTER_DISPOSED);\n }\n\n this.#leaveListeners.push(listener);\n\n // Idempotency flag (#1349), mirroring extendRouter / addInterceptor (#1198).\n // Without it, a double call would `indexOf(listener)` again and splice a\n // DUPLICATE registration of the same fn — silently deactivating another\n // subscriber whose own unsubscribe was never called. The `Unsubscribe`\n // contract names subscribeLeave as idempotent. (Unlike addInterceptor, the\n // `idx !== -1` guard stays: `dispose()` empties `#leaveListeners` via\n // `clearAll`, so an unsubscribe called after dispose reaches this with\n // idx === -1.)\n let removed = false;\n\n return () => {\n if (removed) {\n return;\n }\n\n removed = true;\n\n const idx = this.#leaveListeners.indexOf(listener);\n\n if (idx !== -1) {\n this.#leaveListeners.splice(idx, 1);\n }\n };\n }\n\n hasLeaveListeners(): boolean {\n return this.#leaveListeners.length > 0;\n }\n\n /**\n * True when a plugin listener runs in a PRE-COMMIT transition window where a\n * synchronous `stop()`/`dispose()` can supersede the in-flight navigation\n * before it commits — i.e. `onTransitionStart` (TRANSITION_START) or\n * `onTransitionLeaveApprove` (TRANSITION_LEAVE_APPROVE). (`subscribeLeave` is\n * tracked separately via `hasLeaveListeners`; `onTransitionSuccess` fires\n * post-commit and cannot cancel.) Read into the pre-`startTransition` liveness\n * snapshot so the commit-gate (#1169) fires for these windows too.\n */\n hasPreCommitListeners(): boolean {\n return (\n this.#emitter.listenerCount(events.TRANSITION_START) > 0 ||\n this.#emitter.listenerCount(events.TRANSITION_LEAVE_APPROVE) > 0\n );\n }\n\n awaitLeaveListeners(\n toState: State,\n fromState: State | undefined,\n signal: AbortSignal,\n ): Promise<void> | undefined {\n if (fromState === undefined) {\n return undefined;\n }\n\n // Freeze the payload wrapper so listeners cannot mutate it (`payload.route`\n // is already deep-frozen via the State immutability invariant; this closes\n // the wrapper-mutation gap surfaced by audit `probe-05-payload-frozen`).\n const leaveState: LeaveState = Object.freeze({\n route: fromState,\n nextRoute: toState,\n signal,\n });\n\n let promises: Promise<void>[] | undefined;\n let firstSyncError: unknown;\n\n // Snapshot before iteration — a listener that reentrantly calls\n // `subscribeLeave(newFn)` or its own `unsubscribe()` must not affect the\n // current emit cycle. Symmetric with the EventEmitter snapshot invariant\n // (PR #666 / #659).\n const snapshot = [...this.#leaveListeners];\n\n // Elevated across the SYNC leave-listener dispatch: `isProcessing()` reads\n // it, so a sync subscribeLeave listener that calls navigate() is rejected\n // with REENTRANT_NAVIGATION at the facade (RFC §4). The `finally` restores it\n // before any async tail (returned promise) runs, so a DEFERRED navigate from\n // an async listener (after its first `await`) sees depth 0 and is allowed.\n this.#dispatchDepth++;\n\n try {\n for (const listener of snapshot) {\n try {\n const result = listener(leaveState);\n\n if (result !== undefined && typeof result.then === \"function\") {\n promises ??= [];\n promises.push(result);\n }\n } catch (error: unknown) {\n if (firstSyncError === undefined) {\n firstSyncError = error;\n }\n }\n }\n } finally {\n this.#dispatchDepth--;\n }\n\n if (promises === undefined) {\n if (firstSyncError !== undefined) {\n throw ensureError(firstSyncError);\n }\n\n return undefined;\n }\n\n return settleLeavePromises(promises, firstSyncError, signal);\n }\n\n clearAll(): void {\n this.#emitter.clearAll();\n this.#leaveListeners.length = 0;\n }\n\n setLimits(limits: { maxListeners: number; warnListeners: number }): void {\n this.#emitter.setLimits(limits);\n }\n\n /**\n * Injects the lazy validator accessor (wireNamespaces), mirroring\n * `PluginsNamespace` / `RouteLifecycleNamespace`. The closure reads the live\n * `RouterInternals.validator`, so a validation-plugin registered AFTER wiring\n * is still observed on the next `subscribe` / `addEventListener`.\n */\n setValidatorAccessor(getValidator: () => RouterValidator | null): void {\n this.#getValidator = getValidator;\n }\n\n // Single guarded entry point for routing a cancel into the FSM `CANCEL` action\n // — used by every source: stop/dispose (RouterLifecycle) pass no reason;\n // supersede / external `opts.signal` (via the wiring `cancelNavigation` dep)\n // pass the abort reason (#943). `canCancel()` makes it a no-op outside a\n // cancellable FSM state (#1034: was a second, unguarded `cancelNavigation` path).\n sendCancelIfPossible(fromState: State | undefined, reason?: unknown): void {\n const toState = this.#currentToState;\n\n if (!this.canCancel() || toState === undefined) {\n return;\n }\n\n this.sendCancel(toState, fromState, reason);\n }\n\n /**\n * Proactive listener-count threshold (#1188) — mirrors the plugins /\n * lifecycle / dependencies counters. Opt-in: the emitter's per-event count is\n * read ONLY when the validator is installed, so the bare-core hot path pays\n * nothing. `count` is the POST-add size (`listenerCount + 1`), matching\n * `RouteLifecycleNamespace`'s `count + 1`, so warn/error fire exactly when the\n * new listener reaches the threshold. Core keeps the emitter's bare-`Error`\n * hard cap; this only surfaces an actionable signal well before it.\n */\n #checkListenerThreshold(eventName: EventName, methodName: string): void {\n const validator = this.#getValidator?.();\n\n if (validator) {\n validator.eventBus.validateCountThresholds(\n this.#emitter.listenerCount(eventName) + 1,\n eventName,\n methodName,\n );\n }\n }\n\n #emitPendingError(): void {\n this.emitTransitionError(\n this.#pendingToState,\n this.#pendingFromState,\n this.#pendingError as RouterError | undefined,\n );\n\n // Clear the pending payload once this FAIL action has consumed it. `#pending*`\n // is only meaningful in the window between the sendFail()/sendFailSafe() that\n // sets it and this emit; keeping it afterwards pins a stale State/RouterError\n // on the instance and leaves an implicit \"valid only in this window\" coupling\n // (#949). Hygiene only — every consumer overwrites the fields before\n // re-reading (handleCancel reads what its own sendCancel just set), so there\n // is no observable behaviour change.\n this.#pendingToState = undefined;\n this.#pendingFromState = undefined;\n this.#pendingError = undefined;\n }\n\n #setupFSMActions(): void {\n const fsm = this.#fsm;\n\n fsm.on(routerStates.STARTING, routerEvents.STARTED, () => {\n this.emitRouterStart();\n });\n\n fsm.on(routerStates.READY, routerEvents.STOP, () => {\n this.emitRouterStop();\n });\n\n // NAVIGATE / LEAVE_APPROVE / COMPLETE emit their transition event as the FSM\n // action (payload = the transition states), so `send()` from an invalid\n // state (e.g. COMPLETE from IDLE after a listener's stop()/dispose()) is a\n // table no-op that emits nothing — the FSM table is the sole authority over\n // state, no `forceState` resurrection (#1169 D-full). NAVIGATE fires from\n // READY plus the TRANSITION_STARTED / LEAVE_APPROVED self-loops (supersede).\n const emitNavigate = (payload: RouterPayloads[\"NAVIGATE\"]): void => {\n this.emitTransitionStart(payload.toState, payload.fromState);\n };\n\n fsm.on(routerStates.READY, routerEvents.NAVIGATE, emitNavigate);\n fsm.on(\n routerStates.TRANSITION_STARTED,\n routerEvents.NAVIGATE,\n emitNavigate,\n );\n fsm.on(routerStates.LEAVE_APPROVED, routerEvents.NAVIGATE, emitNavigate);\n\n fsm.on(\n routerStates.TRANSITION_STARTED,\n routerEvents.LEAVE_APPROVE,\n (payload) => {\n this.emitTransitionLeaveApprove(payload.toState, payload.fromState);\n },\n );\n\n fsm.on(routerStates.LEAVE_APPROVED, routerEvents.COMPLETE, (payload) => {\n this.emitTransitionSuccess(\n payload.toState,\n payload.fromState,\n payload.opts,\n );\n });\n\n const handleCancel = () => {\n const toState = this.#pendingToState;\n const reason = this.#pendingCancelReason;\n\n this.#pendingCancelReason = undefined;\n\n // (RFC navigation-cancellation-unification §5): the FSM CANCEL\n // action OWNS the abort. Aborting the in-flight controller wakes the parked\n // async pipeline (#1018) and rejects navigate() — the post-race local\n // isActive() sees signal.aborted; `reason` surfaces as the leave signal's\n // reason (#943). Order: (i) abort (wake) then (ii) emit TRANSITION_CANCEL.\n // No cycle: onInternalAbort is wake-only, it does not re-enter cancel.\n this.#abortController(reason);\n\n /* v8 ignore next -- @preserve: #pendingToState guaranteed set by sendCancel before send() */\n if (toState === undefined) {\n return;\n }\n\n this.emitTransitionCancel(toState, this.#pendingFromState);\n };\n\n fsm.on(routerStates.TRANSITION_STARTED, routerEvents.CANCEL, handleCancel);\n fsm.on(routerStates.LEAVE_APPROVED, routerEvents.CANCEL, handleCancel);\n\n fsm.on(routerStates.LEAVE_APPROVED, routerEvents.FAIL, () => {\n this.#emitPendingError();\n });\n\n fsm.on(routerStates.STARTING, routerEvents.FAIL, () => {\n this.#emitPendingError();\n });\n\n fsm.on(routerStates.READY, routerEvents.FAIL, () => {\n this.#emitPendingError();\n });\n\n fsm.on(routerStates.TRANSITION_STARTED, routerEvents.FAIL, () => {\n this.#emitPendingError();\n });\n }\n}\n","// packages/core/src/namespaces/RouterLifecycleNamespace/constants.ts\n\nimport { errorCodes } from \"../../constants\";\nimport { RouterError } from \"../../RouterError\";\n\n// =============================================================================\n// Cached Errors (Performance Optimization)\n// =============================================================================\n// Pre-create error instances to avoid object allocation on hot paths.\n// Error creation involves: new object, stack trace capture (~500ns-2μs).\n// Cached errors skip this overhead entirely.\n//\n// Trade-off: All error instances share the same stack trace (points here).\n// This is acceptable because:\n// 1. These errors indicate user misconfiguration, not internal bugs\n// 2. Error code and message are sufficient for debugging\n// 3. Performance gain (~80% for error paths) outweighs stack trace loss\n// =============================================================================\n\n/**\n * Cached error for start() called when router is already started/starting.\n */\nexport const CACHED_ALREADY_STARTED_ERROR = new RouterError(\n errorCodes.ROUTER_ALREADY_STARTED,\n);\n","// packages/core/src/wiring/wireNamespaces.ts\n\nimport { normalizeParams } from \"../helpers\";\nimport { getInternals } from \"../internals\";\nimport { resolveOption } from \"../namespaces/OptionsNamespace\";\n\nimport type { NamespaceBag } from \"./types\";\nimport type { NavigationDependencies } from \"../namespaces/NavigationNamespace\";\nimport type { PluginsDependencies } from \"../namespaces/PluginsNamespace\";\nimport type { RouteLifecycleDependencies } from \"../namespaces/RouteLifecycleNamespace\";\nimport type { RouterLifecycleDependencies } from \"../namespaces/RouterLifecycleNamespace\";\nimport type { RoutesDependencies } from \"../namespaces/RoutesNamespace\";\nimport type { Router } from \"../Router\";\nimport type { DefaultDependencies, Params } from \"../types\";\nimport type { RouterValidator } from \"../types/RouterValidator\";\n\n/**\n * Compiles a guard/plugin factory against the router + a cached `getDependency`\n * accessor. One generic function serves both RouteLifecycle (`GuardFnFactory →\n * GuardFn`) and Plugins (`PluginFactory → Plugin`).\n */\ntype CompileFactory<Dependencies extends DefaultDependencies> = <T>(\n factory: (\n router: Router<Dependencies>,\n getDependency: <K extends keyof Dependencies>(name: K) => Dependencies[K],\n ) => T,\n) => T;\n\n/**\n * Wires the inter-namespace dependencies of a freshly-constructed router.\n *\n * Replaces the former `RouterWiringBuilder` class + `wireRouter` director\n * (#1334): a single call-site with a fixed sequence and nothing to build is a\n * pack of procedures over a shared bag, not a builder — plain functions over a\n * `NamespaceBag` drop the triple-repeated field list and the builder instance.\n *\n * Call order is arbitrary (#1331): no `wire*` function runs user code or\n * eagerly reads another namespace's deps. (`wireLimits` is the one eager\n * *write* — it hands the frozen limits object to dependenciesStore/eventBus;\n * the rest only store deps-closures.) Initial-route guard factories are\n * flushed afterwards, from the constructor's `flushPendingGuards()` call.\n */\nexport function wireNamespaces<Dependencies extends DefaultDependencies>(\n ns: NamespaceBag<Dependencies>,\n): void {\n // One shared factory for both guard and plugin compilation (#1334); the\n // `getDependency` closure is allocated once here, not per compile call.\n const compileFactory = createCompileFactory(ns);\n\n // Shared by RouteLifecycle and Plugins — one allocation. Internals are\n // registered before wiring (#1331), so this never throws; returns null until\n // validation-plugin installs the validator.\n const getValidator = (): RouterValidator | null =>\n getInternals(ns.router).validator;\n\n wireLimits(ns);\n wireEventBus(ns, getValidator);\n wireRouteLifecycle(ns, compileFactory, getValidator);\n wireRoutes(ns);\n wirePlugins(ns, compileFactory, getValidator);\n wireNavigation(ns);\n wireRouterLifecycle(ns);\n wireState(ns);\n}\n\nfunction createCompileFactory<Dependencies extends DefaultDependencies>(\n ns: NamespaceBag<Dependencies>,\n): CompileFactory<Dependencies> {\n const { router, dependenciesStore } = ns;\n\n const getDependency = <K extends keyof Dependencies>(\n name: K,\n ): Dependencies[K] => dependenciesStore.dependencies[name] as Dependencies[K];\n\n return <T>(\n factory: (\n router: Router<Dependencies>,\n getDependency: <K extends keyof Dependencies>(name: K) => Dependencies[K],\n ) => T,\n ): T => factory(router, getDependency);\n}\n\nfunction wireLimits<Dependencies extends DefaultDependencies>(\n ns: NamespaceBag<Dependencies>,\n): void {\n ns.dependenciesStore.limits = ns.limits;\n ns.eventBus.setLimits({\n maxListeners: ns.limits.maxListeners,\n warnListeners: ns.limits.warnListeners,\n });\n}\n\n/**\n * Hands EventBusNamespace the shared lazy validator accessor so `subscribe` /\n * `addEventListener` can run the opt-in listener-count threshold (#1188) — the\n * emitter-side parallel to the plugins / lifecycle / dependencies counters.\n */\nfunction wireEventBus<Dependencies extends DefaultDependencies>(\n ns: NamespaceBag<Dependencies>,\n getValidator: () => RouterValidator | null,\n): void {\n ns.eventBus.setValidatorAccessor(getValidator);\n}\n\nfunction wireRouteLifecycle<Dependencies extends DefaultDependencies>(\n ns: NamespaceBag<Dependencies>,\n compileFactory: CompileFactory<Dependencies>,\n getValidator: () => RouterValidator | null,\n): void {\n const deps: RouteLifecycleDependencies<Dependencies> = {\n logger: getInternals(ns.router).logger,\n compileFactory,\n getValidator,\n };\n\n ns.routeLifecycle.setDependencies(deps);\n}\n\nfunction wireRoutes<Dependencies extends DefaultDependencies>(\n ns: NamespaceBag<Dependencies>,\n): void {\n const deps: RoutesDependencies<Dependencies> = {\n logger: getInternals(ns.router).logger,\n addActivateGuard: (name, handler, precompiledFn) => {\n ns.routeLifecycle.addCanActivate(name, handler, true, precompiledFn);\n },\n addDeactivateGuard: (name, handler, precompiledFn) => {\n ns.routeLifecycle.addCanDeactivate(name, handler, true, precompiledFn);\n },\n compileGuard: (handler, methodName) =>\n ns.routeLifecycle.compileGuardFactory(handler, methodName),\n makeState: (name, params, path, meta) =>\n ns.state.makeState(name, params, path, meta),\n getState: () => ns.state.get(),\n areStatesEqual: (state1, state2, ignoreQueryParams) =>\n ns.state.areStatesEqual(state1, state2, ignoreQueryParams),\n getDependency: (name) =>\n ns.dependenciesStore.dependencies[name] as Dependencies[typeof name],\n forwardState: <P extends Params = Params>(name: string, params: P) => {\n const ctx = getInternals(ns.router);\n\n ctx.validator?.routes.validateStateBuilderArgs(\n name,\n params,\n \"forwardState\",\n );\n\n return ctx.forwardState(name, params);\n },\n };\n\n ns.routes.setDependencies(deps);\n ns.routes.setLifecycleNamespace(ns.routeLifecycle);\n}\n\nfunction wirePlugins<Dependencies extends DefaultDependencies>(\n ns: NamespaceBag<Dependencies>,\n compileFactory: CompileFactory<Dependencies>,\n getValidator: () => RouterValidator | null,\n): void {\n const deps: PluginsDependencies<Dependencies> = {\n logger: getInternals(ns.router).logger,\n addEventListener: (eventName, cb) =>\n ns.eventBus.addEventListener(eventName, cb),\n canNavigate: () => ns.eventBus.canBeginTransition(),\n compileFactory,\n getValidator,\n };\n\n ns.plugins.setDependencies(deps);\n}\n\nfunction wireNavigation<Dependencies extends DefaultDependencies>(\n ns: NamespaceBag<Dependencies>,\n): void {\n const deps: NavigationDependencies = {\n logger: getInternals(ns.router).logger,\n getOptions: () => ns.options.get(),\n hasRoute: (name) => ns.routes.hasRoute(name),\n getState: () => ns.state.get(),\n setState: (state) => {\n ns.state.set(state);\n },\n buildNavigateState: (routeName, routeParams) => {\n const ctx = getInternals(ns.router);\n\n ctx.validator?.routes.validateStateBuilderArgs(\n routeName,\n routeParams,\n \"navigate\",\n );\n\n const forwarded = ctx.forwardState(routeName, routeParams);\n const name = forwarded.name;\n const params = normalizeParams(forwarded.params);\n const meta = ns.routes.getMetaForState(name);\n\n if (meta === undefined) {\n return;\n }\n\n const path = ctx.buildPath(name, params);\n\n return ns.state.makeState(name, params, path, meta, true);\n },\n resolveDefault: () => {\n const options = ns.options.get();\n const ctx = getInternals(ns.router);\n\n const route = resolveOption(\n options.defaultRoute,\n (name: string) =>\n ns.dependenciesStore.dependencies[name as keyof Dependencies],\n );\n const params = resolveOption(\n options.defaultParams,\n /* v8 ignore next -- @preserve: unreachable unless defaultParams is a callback that calls getDependency */\n (name: string) =>\n ns.dependenciesStore.dependencies[name as keyof Dependencies],\n );\n\n if (typeof options.defaultRoute === \"function\") {\n ctx.validator?.options.validateResolvedDefaultRoute(\n route,\n ctx.routeGetStore(),\n );\n }\n\n return { route, params };\n },\n startTransition: (toState, fromState) => {\n ns.eventBus.sendNavigate(toState, fromState);\n },\n cancelNavigation: (reason) => {\n ns.eventBus.sendCancelIfPossible(ns.state.get(), reason);\n },\n sendTransitionDone: (state, fromState, opts) => {\n ns.eventBus.sendComplete(state, fromState, opts);\n },\n sendTransitionFail: (toState, fromState, error) => {\n ns.eventBus.sendFail(toState, fromState, error);\n },\n emitTransitionError: (toState, fromState, error) => {\n ns.eventBus.sendFailSafe(toState, fromState, error);\n },\n emitTransitionSuccess: (toState, fromState, opts) => {\n ns.eventBus.emitTransitionSuccess(toState, fromState, opts);\n },\n sendLeaveApprove: (toState, fromState) => {\n ns.eventBus.sendLeaveApprove(toState, fromState);\n },\n canNavigate: () => ns.eventBus.canBeginTransition(),\n getLifecycleFunctions: () => ns.routeLifecycle.getFunctions(),\n isActive: () => ns.router.isActive(),\n isTransitioning: () => ns.eventBus.isTransitioning(),\n // Post-leave auto-cleanup unregisters only the EXTERNAL (component-managed)\n // guard; a route-config (definition) guard survives for re-entry (#1171).\n clearCanDeactivate: (name: string) => {\n ns.routeLifecycle.clearCanDeactivate(name, \"external\");\n },\n hasLeaveListeners: () => ns.eventBus.hasLeaveListeners(),\n hasPreCommitListeners: () => ns.eventBus.hasPreCommitListeners(),\n awaitLeaveListeners: (toState, fromState, signal) =>\n ns.eventBus.awaitLeaveListeners(toState, fromState, signal),\n };\n\n ns.navigation.setDependencies(deps);\n}\n\nfunction wireRouterLifecycle<Dependencies extends DefaultDependencies>(\n ns: NamespaceBag<Dependencies>,\n): void {\n const deps: RouterLifecycleDependencies = {\n getOptions: () => ns.options.get(),\n navigateToState: (state, opts) =>\n ns.navigation.navigateToState(state, opts),\n navigateToNotFound: (path) => ns.navigation.navigateToNotFound(path),\n clearState: () => {\n ns.state.set(undefined);\n },\n matchPath: (path) => ns.routes.matchPath(path, ns.options.get()),\n completeStart: () => {\n ns.eventBus.sendStarted();\n },\n isIdle: () => ns.eventBus.isIdle(),\n emitTransitionError: (toState, fromState, error) => {\n ns.eventBus.sendFail(toState, fromState, error);\n },\n };\n\n ns.lifecycle.setDependencies(deps);\n}\n\nfunction wireState<Dependencies extends DefaultDependencies>(\n ns: NamespaceBag<Dependencies>,\n): void {\n ns.state.setDependencies({\n getDefaultParams: () => ns.routes.getStore().config.defaultParams,\n buildPath: (name, params) => {\n const ctx = getInternals(ns.router);\n\n return ctx.buildPath(name, params);\n },\n getUrlParams: (name) => ns.routes.getUrlParams(name),\n });\n}\n","// packages/core/src/Router.ts\n\n/**\n * Router class - facade with integrated namespaces.\n *\n * All functionality is now provided by namespace classes.\n */\n\nimport { EMPTY_PARAMS, errorCodes } from \"./constants\";\nimport { EventEmitter } from \"./foundation/event-emitter\";\nimport { RouterLogger } from \"./foundation/logger\";\nimport {\n assertLoggerConfig,\n guardDependencies,\n guardRouteStructure,\n} from \"./guards\";\nimport { createLimits, normalizeParams } from \"./helpers\";\nimport {\n createBinaryInterceptable,\n createInterceptable,\n getInternals,\n registerInternals,\n} from \"./internals\";\nimport {\n EventBusNamespace,\n NavigationNamespace,\n OptionsNamespace,\n PluginsNamespace,\n RouteLifecycleNamespace,\n RouterLifecycleNamespace,\n RoutesNamespace,\n StateNamespace,\n createDependenciesStore,\n} from \"./namespaces\";\nimport { CACHED_ALREADY_STARTED_ERROR } from \"./namespaces/RouterLifecycleNamespace/constants\";\nimport { RouterError } from \"./RouterError\";\nimport { createRouterFSM } from \"./routerFSM\";\nimport { getTransitionPath } from \"./transitionPath\";\nimport { wireNamespaces } from \"./wiring\";\n\nimport type { CreateMatcherOptions } from \"./engine\";\nimport type { RouterInternals } from \"./internals\";\nimport type { DependenciesStore } from \"./namespaces\";\nimport type {\n DefaultDependencies,\n LeaveFn,\n NavigationOptions,\n Options,\n Params,\n Router as RouterInterface,\n State,\n SubscribeFn,\n Unsubscribe,\n PluginFactory,\n Route,\n} from \"./types\";\nimport type { Limits, RouterEventMap } from \"./types/internal\";\n\nconst EMPTY_OPTS: Readonly<NavigationOptions> = Object.freeze({});\n\n// Module-level so #isExpectedRejection allocates nothing per navigate()/start() call.\n// These are expected navigation outcomes owned by the caller, not internal\n// bugs — the safety net stays silent for them and lets awaiting callers see\n// the rejection. CANNOT_ACTIVATE / CANNOT_DEACTIVATE belong here: a guard\n// blocking (or a plugin's guard-blocked back()/forward()) is a normal result,\n// so a fire-and-forget call must not emit a spurious \"Unexpected navigation\n// error\" (#721).\nconst SUPPRESSED_ERROR_CODES: ReadonlySet<string> = new Set([\n errorCodes.SAME_STATES,\n errorCodes.TRANSITION_CANCELLED,\n errorCodes.ROUTER_NOT_STARTED,\n errorCodes.ROUTE_NOT_FOUND,\n errorCodes.CANNOT_ACTIVATE,\n errorCodes.CANNOT_DEACTIVATE,\n]);\n\n/**\n * Router class with integrated namespace architecture.\n *\n * All functionality is provided by namespace classes:\n * - OptionsNamespace: getOptions (immutable)\n * - DependenciesStore: get/set/remove dependencies\n * - EventEmitter: subscribe\n * - StateNamespace: state storage (getState, setState, getPreviousState)\n * - RoutesNamespace: route tree operations\n * - RouteLifecycleNamespace: canActivate/canDeactivate guards\n * - PluginsNamespace: plugin lifecycle\n * - NavigationNamespace: navigate\n * - RouterLifecycleNamespace: start, stop, isStarted\n *\n * @internal This class implementation is internal. Use createRouter() instead.\n */\nexport class Router<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n> implements RouterInterface<Dependencies> {\n [key: string]: unknown;\n\n // ============================================================================\n // Namespaces\n // ============================================================================\n\n readonly #options: OptionsNamespace;\n readonly #limits: Limits;\n readonly #dependenciesStore: DependenciesStore<Dependencies>;\n readonly #state: StateNamespace;\n readonly #routes: RoutesNamespace<Dependencies>;\n readonly #routeLifecycle: RouteLifecycleNamespace<Dependencies>;\n readonly #plugins: PluginsNamespace<Dependencies>;\n readonly #navigation: NavigationNamespace;\n readonly #lifecycle: RouterLifecycleNamespace;\n\n readonly #eventBus: EventBusNamespace;\n\n /**\n * Per-instance suppressors for fire-and-forget navigate / start. They log\n * through THIS router's logger (built in the constructor) — so, unlike the\n * former static \"one allocation per class\" closures, they are two closures per\n * instance. Trade accepted with the per-router logger (#724): the logger is\n * per-instance, so its suppressors must be too. The #931 category split is\n * preserved — navigate failures log under \"router.navigate\", start failures\n * under \"router.start\" (a start interceptor throwing a plain Error after\n * next() committed, #763, or a cryptic path TypeError — neither a suppressed\n * RouterError). The log line IS reachable: a subscribeLeave listener that\n * throws rejects navigate() with the original NON-suppressed error, and a\n * Symbol path-param's stringify TypeError is likewise non-suppressed.\n */\n readonly #onSuppressedNavigateError: (error: unknown) => void;\n readonly #onSuppressedStartError: (error: unknown) => void;\n\n // ============================================================================\n // Constructor\n // ============================================================================\n\n /**\n * @param routes - Route definitions\n * @param options - Router options\n * @param dependencies - DI dependencies\n */\n constructor(\n routes: Route<Dependencies>[] = [],\n options: Partial<Options> = {},\n dependencies: Dependencies = {} as Dependencies,\n ) {\n // Extract the logger config WITHOUT mutating the caller's `options` object\n // (#724). `routerOptions` is the logger-stripped view handed to the options\n // pipeline so `logger` never lands in the frozen router options.\n const { logger: loggerConfig, ...routerOptions } = options;\n\n if (loggerConfig) {\n assertLoggerConfig(loggerConfig);\n }\n\n // Per-router logger instance — replaces the former process-global singleton\n // whose configure() leaked across every router in the process, last\n // createRouter winning (#724). Stored on ctx (registerInternals below), so\n // the facade reads getInternals(this).logger; namespaces receive it via\n // their deps at wiring; plugins reach it through getPluginApi(router).logger.\n const logger = new RouterLogger(loggerConfig);\n\n // Per-instance fire-and-forget suppressors (see field declarations): they\n // log through THIS router's logger, so they are built here, not static.\n this.#onSuppressedNavigateError = (error: unknown): void => {\n if (Router.#isExpectedRejection(error)) {\n return;\n }\n\n logger.error(\"router.navigate\", \"Unexpected navigation error\", error);\n };\n this.#onSuppressedStartError = (error: unknown): void => {\n if (Router.#isExpectedRejection(error)) {\n return;\n }\n\n logger.error(\"router.start\", \"Unexpected start error\", error);\n };\n\n // =========================================================================\n // Validate inputs before creating namespaces\n // =========================================================================\n\n // Always validate the caller's options (catches non-object / array inputs)\n OptionsNamespace.validateOptionsIsObject(options);\n\n // Unconditional guard-level validation before creating namespaces\n guardDependencies(dependencies);\n\n // Stryker disable next-line EqualityOperator: equivalent — `>= 0` is always true, but `guardRouteStructure([])` on an empty array is a no-op, so validating an empty list behaves identically to skipping it. (ConditionalExpression stays live: `→false` skips validation of a real route list and is killable.)\n if (routes.length > 0) {\n guardRouteStructure(routes);\n }\n\n // =========================================================================\n // Create Namespaces\n // =========================================================================\n\n this.#options = new OptionsNamespace(routerOptions);\n this.#limits = createLimits(routerOptions.limits);\n this.#dependenciesStore =\n createDependenciesStore<Dependencies>(dependencies);\n this.#state = new StateNamespace();\n this.#routes = new RoutesNamespace<Dependencies>(\n routes,\n deriveMatcherOptions(this.#options.get()),\n logger,\n );\n this.#routeLifecycle = new RouteLifecycleNamespace<Dependencies>();\n this.#plugins = new PluginsNamespace<Dependencies>();\n this.#navigation = new NavigationNamespace();\n this.#lifecycle = new RouterLifecycleNamespace();\n\n // =========================================================================\n // Initialize EventBus\n // =========================================================================\n\n const routerFSM = createRouterFSM();\n\n const emitter = new EventEmitter<RouterEventMap>({\n // Shared per-listener error sink: EventEmitter reports synchronous listener\n // throws here, and EventBusNamespace.subscribe routes an async listener's\n // rejected Promise through the SAME sink (#944) — both land in one place.\n onListenerError: (eventName, error) => {\n logger.error(\"Router\", `Error in listener for ${eventName}:`, error);\n },\n onListenerWarn: (eventName, count) => {\n logger.warn(\n \"router.addEventListener\",\n `Event \"${eventName}\" has ${count} listeners — possible memory leak`,\n );\n },\n });\n\n this.#eventBus = new EventBusNamespace({\n routerFSM,\n emitter,\n // The FSM CANCEL action aborts the in-flight\n // navigation controller via this injected effect — \"FSM CANCEL ⟹\n // controller aborted\" in one place. `#navigation` is constructed above.\n abortController: (reason) => {\n this.#navigation.abortCurrentController(reason);\n },\n });\n\n // =========================================================================\n // Register Internals (WeakMap for plugin/infrastructure access)\n // =========================================================================\n // Registered BEFORE wiring (#1331) so every namespace's deps-closure sees a\n // router already present in the internals registry — `getInternals(router)`\n // never throws during wiring, and guard factories flushed at the end of the\n // constructor see a fully-registered instance.\n\n const interceptorsMap: RouterInternals[\"interceptors\"] = new Map();\n\n registerInternals(this, {\n logger,\n makeState: (name, params, path, meta) =>\n this.#state.makeState(name, params, path, meta),\n // `as unknown as` is required: createBinaryInterceptable returns a\n // non-generic `(a: A, b: B) => R`, but RouterInternals[\"forwardState\"]\n // is declared with a generic parameter `<P extends Params = Params>`,\n // which tsc will not infer from the non-generic source. Sonar S4325\n // misclassifies this as a redundant cast.\n forwardState: createBinaryInterceptable(\n \"forwardState\",\n (name: string, params: Params) =>\n this.#routes.forwardState(name, params),\n interceptorsMap,\n ) as unknown as RouterInternals[\"forwardState\"],\n buildStateResolved: (name, params) =>\n this.#routes.buildStateResolved(name, params),\n matchPath: (path, matchOptions) =>\n this.#routes.matchPath(path, matchOptions),\n getOptions: () => this.#options.get(),\n addEventListener: (eventName, cb) =>\n this.#eventBus.addEventListener(eventName, cb),\n treeChanged: {\n emit: (event) => {\n this.#eventBus.emitTreeChanged(event);\n },\n subscribe: (handler) => this.#eventBus.subscribeTreeChanged(handler),\n listenerCount: () => this.#eventBus.treeChangedListenerCount(),\n isEmitting: () => this.#eventBus.isEmittingTreeChanged(),\n },\n buildPath: createBinaryInterceptable(\n \"buildPath\",\n (route: string, params?: Params) =>\n this.#routes.buildPath(\n route,\n params ?? EMPTY_PARAMS,\n this.#options.get(),\n ),\n interceptorsMap,\n ),\n emitTransitionError: (error) => {\n this.#eventBus.sendFailSafe(undefined, this.#state.get(), error);\n },\n emitTransitionSuccess: (toState, fromState, opts) => {\n this.#eventBus.emitTransitionSuccess(toState, fromState, opts);\n },\n navigateToNotFound: (path) => this.#navigation.navigateToNotFound(path),\n start: createInterceptable(\n \"start\",\n (path: string) => {\n return this.#lifecycle.start(path);\n },\n interceptorsMap,\n ),\n navigateToState: (state, navOpts) => {\n // Plugin-only navigation primitive (#525). Mirrors the same\n // unhandled-rejection suppression and lastSync* bookkeeping used by\n // the public Router.navigate facade so plugin call-sites can\n // fire-and-forget the returned promise (popstate handlers do).\n this.#assertNotReentrant();\n\n const promiseState = this.#navigation.navigateToState(\n state,\n navOpts ?? EMPTY_OPTS,\n );\n\n if (this.#navigation.lastSyncResolved) {\n this.#navigation.lastSyncResolved = false;\n } else if (this.#navigation.lastSyncRejected) {\n this.#navigation.lastSyncRejected = false;\n } else {\n this.#suppressUnhandledRejection(promiseState);\n }\n\n return promiseState;\n },\n interceptors: interceptorsMap,\n setRootPath: (rootPath) => {\n this.#routes.setRootPath(rootPath);\n },\n getRootPath: () => this.#routes.getStore().rootPath,\n getTree: () => this.#routes.getStore().tree,\n isDisposed: () => this.#eventBus.isDisposed(),\n validator: null,\n // Dependencies (issue #172)\n dependenciesGetStore: () => this.#dependenciesStore,\n // Clone support (issue #173)\n getCloneState: () => ({\n options: { ...this.#options.get() },\n dependencies: { ...this.#dependenciesStore.dependencies },\n pluginFactories: this.#plugins.getAll(),\n // `logger` is a const in this constructor's scope (a RouterLogger class\n // instance), so getConfig() yields the resolved config a clone inherits\n // — frozen options don't carry `logger`, so cloneRouter reads it here.\n loggerConfig: logger.getConfig(),\n }),\n routeGetStore: () => this.#routes.getStore(),\n // Cross-namespace state (issue #174)\n getStateName: () => this.#state.get()?.name,\n isTransitioning: () => this.#eventBus.isTransitioning(),\n clearState: () => {\n this.#state.set(undefined);\n },\n setState: (state) => {\n this.#state.set(state);\n },\n routerExtensions: [],\n contextClaimRecords: new Set(),\n hydrationState: null,\n });\n\n // =========================================================================\n // Wire Dependencies\n // =========================================================================\n\n wireNamespaces<Dependencies>({\n router: this,\n options: this.#options,\n limits: this.#limits,\n dependenciesStore: this.#dependenciesStore,\n state: this.#state,\n routes: this.#routes,\n routeLifecycle: this.#routeLifecycle,\n plugins: this.#plugins,\n navigation: this.#navigation,\n lifecycle: this.#lifecycle,\n eventBus: this.#eventBus,\n });\n\n // =========================================================================\n // Bind Public Methods\n // =========================================================================\n // All public methods that access private fields must be bound to preserve\n // `this` context when methods are extracted as references.\n // See: https://github.com/tc39/proposal-bind-operator\n // =========================================================================\n\n // Path & State Building\n this.isActiveRoute = this.isActiveRoute.bind(this);\n this.buildPath = this.buildPath.bind(this);\n\n // State Management\n this.getState = this.getState.bind(this);\n this.getPreviousState = this.getPreviousState.bind(this);\n this.areStatesEqual = this.areStatesEqual.bind(this);\n this.shouldUpdateNode = this.shouldUpdateNode.bind(this);\n\n // Router Lifecycle\n this.isActive = this.isActive.bind(this);\n this.start = this.start.bind(this);\n this.stop = this.stop.bind(this);\n this.dispose = this.dispose.bind(this);\n\n // Route Lifecycle (Guards)\n this.canNavigateTo = this.canNavigateTo.bind(this);\n\n // Plugins\n this.usePlugin = this.usePlugin.bind(this);\n\n // Navigation\n this.navigate = this.navigate.bind(this);\n this.navigateToDefault = this.navigateToDefault.bind(this);\n this.navigateToNotFound = this.navigateToNotFound.bind(this);\n\n // Subscription\n this.subscribe = this.subscribe.bind(this);\n this.subscribeLeave = this.subscribeLeave.bind(this);\n this.isLeaveApproved = this.isLeaveApproved.bind(this);\n\n // =========================================================================\n // Flush initial-route guard factories\n // =========================================================================\n // Deferred out of wiring (#1331): the pending canActivate/canDeactivate\n // factories from initial route definitions are compiled and executed HERE,\n // on the fully-built and bound router — a factory calling read-only methods\n // (`buildPath()`, `isActiveRoute()`, `getState()`) no longer hits a\n // half-assembled instance. Side-effectful calls (`navigate`, `usePlugin`,\n // route-CRUD) stay OUT OF CONTRACT: factories re-execute outside the\n // constructor (cloneRouter re-compiles definition guards per clone;\n // #recompileSlot re-runs a factory after a definition-only clear), so any\n // side effect would duplicate per re-execution — see CLAUDE.md. Runtime\n // add()/replace() compile guards in their own PREPARE phase and never touch\n // these pending maps.\n //\n // Fail-closed on a factory throw: by this point a router reference leaked\n // from an earlier factory is fully operational, while later guards would\n // stay silently unregistered — a fail-open guard bypass. Disposing before\n // the rethrow turns any leaked reference into a ROUTER_DISPOSED-throwing\n // husk (pre-#1331 such a reference was inert because getInternals threw).\n try {\n this.#routes.flushPendingGuards();\n } catch (error) {\n this.dispose();\n\n throw error;\n }\n }\n\n // ============================================================================\n // Path & State Building\n // ============================================================================\n\n isActiveRoute(\n name: string,\n params?: Params,\n strictEquality?: boolean,\n ignoreQueryParams?: boolean,\n ): boolean {\n getInternals(this).validator?.routes.validateIsActiveRouteArgs(\n name,\n params,\n strictEquality,\n ignoreQueryParams,\n );\n\n getInternals(this).validator?.routes.validateRouteName(\n name,\n \"isActiveRoute\",\n );\n\n // Empty string is special case - warn and return false (root node is not a parent)\n if (name === \"\") {\n getInternals(this).logger.warn(\n \"real-router\",\n 'isActiveRoute(\"\") called with empty string. Root node is not considered a parent of any route.',\n );\n\n return false;\n }\n\n return this.#routes.isActiveRoute(\n name,\n params,\n strictEquality,\n ignoreQueryParams,\n );\n }\n\n buildPath(route: string, params?: Params): string {\n const ctx = getInternals(this);\n\n ctx.validator?.routes.validateBuildPathArgs(route);\n ctx.validator?.navigation.validateParams(params, \"buildPath\");\n\n return ctx.buildPath(route, normalizeParams(params));\n }\n\n // ============================================================================\n // State Management (delegated to StateNamespace)\n // ============================================================================\n\n getState<P extends Params = Params>(): State<P> | undefined {\n return this.#state.get<P>();\n }\n\n getPreviousState(): State | undefined {\n return this.#state.getPrevious();\n }\n\n areStatesEqual(\n state1: State | undefined,\n state2: State | undefined,\n ignoreQueryParams = true,\n ): boolean {\n getInternals(this).validator?.state.validateAreStatesEqualArgs(\n state1,\n state2,\n ignoreQueryParams,\n );\n\n return this.#state.areStatesEqual(state1, state2, ignoreQueryParams);\n }\n\n shouldUpdateNode(\n nodeName: string,\n ): (toState: State, fromState?: State) => boolean {\n getInternals(this).validator?.routes.validateShouldUpdateNodeArgs(nodeName);\n\n return RoutesNamespace.shouldUpdateNode(nodeName);\n }\n\n // ============================================================================\n // Router Lifecycle\n // ============================================================================\n\n isActive(): boolean {\n return this.#eventBus.isActive();\n }\n\n start(startPath: string): Promise<State> {\n if (!this.#eventBus.canStart()) {\n return Promise.reject(CACHED_ALREADY_STARTED_ERROR);\n }\n\n getInternals(this).validator?.navigation.validateStartArgs(startPath);\n\n // FSM bookkeeping is split across the facade and RouterLifecycleNamespace by\n // design, NOT a missed consolidation (#940): `sendStart()` runs HERE, before\n // the interceptor chain, so the STARTING window spans the whole start\n // pipeline. A pre-`next()` interceptor throw then unwinds via STARTING →\n // `sendFail`, which emits TRANSITION_ERROR from STARTING (EventBusNamespace\n // FAIL action) for `onTransitionError` plugins. Moving `sendStart()` into the\n // namespace (the interceptor *target*) would skip STARTING on a pre-`next()`\n // throw — the namespace is never reached — silently dropping that\n // TRANSITION_ERROR: a #668 regression. The commit (`completeStart`) lives in\n // the namespace; recovery needs facade state (`#state`, `#lifecycle`), so it\n // stays here in `#unwindFailedStart`.\n this.#eventBus.sendStart();\n\n // Convert sync interceptor throws to rejections so the recovery path is\n // reachable; otherwise the throw escapes synchronously, the FSM is left in\n // STARTING, and the router is permanently bricked (#668).\n let internalStart: Promise<State>;\n\n try {\n const chainResult: unknown = getInternals(this).start(startPath);\n\n // A `start` interceptor that returns without calling next() yields a\n // non-thenable (typically undefined); the `.catch` below would then throw\n // a cryptic `TypeError: ...reading 'catch'` and leave the FSM stuck in\n // STARTING. Reject with an actionable message so recovery unwinds via\n // #unwindFailedStart — the same deferred-crash class as the #939\n // start-path guard (#1411).\n internalStart =\n typeof (chainResult as { then?: unknown } | null | undefined)?.then ===\n \"function\"\n ? (chainResult as Promise<State>)\n : Promise.reject(\n new TypeError(\n \"[router.start] a `start` interceptor returned without calling next(). Every start interceptor must return `next(path)`.\",\n ),\n );\n } catch (syncError: unknown) {\n // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors -- preserve original throw shape from user-provided start interceptor\n internalStart = Promise.reject(syncError);\n }\n\n const promiseState = internalStart.catch((error: unknown) =>\n this.#unwindFailedStart(error),\n );\n\n this.#suppressUnhandledRejection(\n promiseState,\n this.#onSuppressedStartError,\n );\n\n return promiseState;\n }\n\n stop(): this {\n // SendCancelIfPossible → FSM CANCEL → the CANCEL action\n // aborts the in-flight controller (waking the pipeline). No separate abort.\n this.#eventBus.sendCancelIfPossible(this.#state.get());\n\n // `isStarting()` is included (#1185): a stop() while `start()` is parked in\n // an async start-interceptor (FSM STARTING, before `next()`) must cancel the\n // start, not silently no-op. `sendStop()` takes STARTING → IDLE (FSM table),\n // and `RouterLifecycleNamespace.start` re-checks `isIdle()` after the\n // interceptor chain and rejects with TRANSITION_CANCELLED — mirroring the\n // guard-phase behavior (which already cancels from TRANSITION_STARTED).\n if (\n !this.#eventBus.isReady() &&\n !this.#eventBus.isTransitioning() &&\n !this.#eventBus.isStarting()\n ) {\n return this;\n }\n\n this.#lifecycle.stop();\n this.#eventBus.sendStop();\n\n return this;\n }\n\n dispose(): void {\n // Stryker disable next-line BlockStatement: equivalent — emptying the early-return re-runs the dispose body on a 2nd call, but it is fully idempotent (FSM `send(DISPOSE)` no-ops from DISPOSED, `disposeAll()` already cleared `#unsubscribes`, every clear is idempotent). (ConditionalExpression stays live: `→true` always-returns and never disposes = killed.)\n if (this.#eventBus.isDisposed()) {\n return;\n }\n\n // the FSM CANCEL action aborts the in-flight controller.\n this.#eventBus.sendCancelIfPossible(this.#state.get());\n\n if (this.#eventBus.isReady() || this.#eventBus.isTransitioning()) {\n this.#lifecycle.stop();\n this.#eventBus.sendStop();\n }\n\n this.#eventBus.sendDispose();\n this.#eventBus.clearAll();\n\n this.#plugins.disposeAll();\n\n // Safety net: clean up extensions plugins failed to remove in teardown\n const ctx = getInternals(this);\n\n for (const extension of ctx.routerExtensions) {\n for (const key of extension.keys) {\n delete (this as Record<string, unknown>)[key];\n }\n }\n\n ctx.routerExtensions.length = 0;\n\n // Safety net: release context namespace claims plugins failed to release in teardown\n ctx.contextClaimRecords.clear();\n\n // Safety net: drop interceptors plugins failed to remove in teardown (#1199).\n // The third per-plugin registration channel — symmetric with routerExtensions\n // / contextClaimRecords above. `buildPath` is not method-swapped by dispose\n // and reads this Map live, so a leaked interceptor would otherwise still run\n // on the disposed router.\n ctx.interceptors.clear();\n\n this.#routes.clearRoutes();\n this.#routeLifecycle.clearAll();\n this.#state.reset();\n this.#dependenciesStore.dependencies = Object.create(\n null,\n ) as Partial<Dependencies>;\n\n this.#markDisposed();\n }\n\n // ============================================================================\n // Route Lifecycle (Guards)\n // ============================================================================\n\n canNavigateTo(name: string, params?: Params): boolean {\n const ctx = getInternals(this);\n\n ctx.validator?.routes.validateRouteName(name, \"canNavigateTo\");\n ctx.validator?.navigation.validateParams(params, \"canNavigateTo\");\n\n if (!this.#routes.hasRoute(name)) {\n return false;\n }\n\n const { name: resolvedName, params: resolvedParams } = ctx.forwardState(\n name,\n params ?? {},\n );\n\n // Build `toState` exactly as `buildNavigateState` does — WITH route-meta and\n // normalized params — so `getTransitionPath` takes its STANDARD PATH and\n // trims the shared ancestor, mirroring navigate's guard set (#970). A\n // meta-less `toState` makes both sides meta-less (the committed `getState()`\n // carries no meta after a path-matched `start()`), so `getTransitionPath`\n // takes FAST PATH 3 and (de)activates the WHOLE chain incl. shared ancestors\n // → false-negative (\"Link disabled though the click would succeed\").\n // `normalizeParams` also aligns the params guards observe with navigate's.\n // `skipFreeze` (5th arg) mirrors the navigate guard phase, where guards see\n // an unfrozen, transition-less `toState` (freeze happens later in\n // `completeTransition`).\n //\n // A capability predicate must answer, not throw: if the target path can't be\n // built from these params (e.g. a required path param is missing), the route\n // is simply unreachable with this input — return `false` rather than letting\n // `buildPath` throw (#725).\n let toState: State;\n\n try {\n const normalizedParams = normalizeParams(resolvedParams);\n const meta = this.#routes.getMetaForState(resolvedName);\n const path = ctx.buildPath(resolvedName, normalizedParams);\n\n toState = this.#state.makeState(\n resolvedName,\n normalizedParams,\n path,\n meta,\n true,\n );\n } catch {\n return false;\n }\n\n const fromState = this.#state.get();\n\n const { toDeactivate, toActivate } = getTransitionPath(toState, fromState);\n\n return this.#routeLifecycle.canNavigateTo(\n toDeactivate,\n toActivate,\n toState,\n fromState,\n );\n }\n\n // ============================================================================\n // Plugins\n // ============================================================================\n\n usePlugin(\n ...plugins: (PluginFactory<Dependencies> | false | null | undefined)[]\n ): Unsubscribe {\n // Post-dispose guard, mirroring #946 for subscribe/subscribeLeave. A\n // reference captured before dispose() (`const up = router.usePlugin`)\n // bypasses the #markDisposed method swap, so the swap alone is not enough:\n // without this, the factory would run on a disposed router (real side\n // effects), listeners would land in the cleared emitter, and teardown would\n // never fire — a silent zombie plugin (#1196).\n if (this.#eventBus.isDisposed()) {\n throw new RouterError(errorCodes.ROUTER_DISPOSED);\n }\n\n const filtered = plugins.filter(Boolean) as PluginFactory<Dependencies>[];\n\n if (filtered.length === 0) {\n return () => {};\n }\n\n const ctx = getInternals(this);\n\n ctx.validator?.plugins.validatePluginLimit(\n this.#plugins.count(),\n this.#limits,\n );\n for (const plugin of filtered) {\n // `getAll()` sits inside the optional-chain argument on purpose: with no\n // validator installed (production default) the `?.` short-circuits and the\n // array is never allocated. Hoisting it out would either allocate on the\n // no-validator hot path or push the dev-only branch out of coverage.\n ctx.validator?.plugins.validateNoDuplicatePlugins(\n plugin,\n this.#plugins.getAll(),\n );\n }\n\n return this.#plugins.use(...filtered);\n }\n\n // ============================================================================\n // Subscription (backed by EventEmitter)\n // ============================================================================\n\n subscribe(listener: SubscribeFn): Unsubscribe {\n EventBusNamespace.validateSubscribeListener(listener);\n\n return this.#eventBus.subscribe(listener);\n }\n\n subscribeLeave(listener: LeaveFn): Unsubscribe {\n EventBusNamespace.validateSubscribeLeaveListener(listener);\n\n return this.#eventBus.subscribeLeave(listener);\n }\n\n isLeaveApproved(): boolean {\n return this.#eventBus.isLeaveApproved();\n }\n\n // ============================================================================\n // Navigation\n // ============================================================================\n\n navigate(\n routeName: string,\n routeParams?: Params,\n options?: NavigationOptions,\n ): Promise<State> {\n this.#assertNotReentrant();\n\n const ctx = getInternals(this);\n\n ctx.validator?.navigation.validateNavigateArgs(routeName);\n ctx.validator?.navigation.validateParams(routeParams, \"navigate\");\n\n const opts = options ?? EMPTY_OPTS;\n\n ctx.validator?.navigation.validateNavigationOptions(opts, \"navigate\");\n\n const promiseState = this.#navigation.navigate(\n routeName,\n routeParams ?? EMPTY_PARAMS,\n opts,\n );\n\n if (this.#navigation.lastSyncResolved) {\n this.#navigation.lastSyncResolved = false;\n } else if (this.#navigation.lastSyncRejected) {\n // Cached rejection — already pre-suppressed at module load, skip .catch()\n this.#navigation.lastSyncRejected = false;\n } else {\n this.#suppressUnhandledRejection(promiseState);\n }\n\n return promiseState;\n }\n\n navigateToDefault(options?: NavigationOptions): Promise<State> {\n this.#assertNotReentrant();\n\n const ctx = getInternals(this);\n\n ctx.validator?.navigation.validateNavigateToDefaultArgs(options);\n\n const opts = options ?? EMPTY_OPTS;\n\n ctx.validator?.navigation.validateNavigationOptions(\n opts,\n \"navigateToDefault\",\n );\n\n const promiseState = this.#navigation.navigateToDefault(opts);\n\n if (this.#navigation.lastSyncResolved) {\n this.#navigation.lastSyncResolved = false;\n } else if (this.#navigation.lastSyncRejected) {\n this.#navigation.lastSyncRejected = false;\n } else {\n this.#suppressUnhandledRejection(promiseState);\n }\n\n return promiseState;\n }\n\n navigateToNotFound(path?: string): State {\n this.#assertNotReentrant();\n\n if (!this.#eventBus.isActive()) {\n throw new RouterError(errorCodes.ROUTER_NOT_STARTED);\n }\n\n if (path !== undefined && typeof path !== \"string\") {\n throw new TypeError(\n `[router.navigateToNotFound] path must be a string, got ${typeof path}`,\n );\n }\n\n if (path !== undefined) {\n return this.#navigation.navigateToNotFound(path);\n }\n\n // #1172: a path-less call derives the default path from the committed state.\n // During the two-phase start window the router is active (`isActive()` true)\n // while `getState()` is still undefined, so throw an actionable RouterError\n // instead of a cryptic `TypeError` from dereferencing the absent state —\n // same class as the #939 always-on invariant guards.\n const current = this.#state.get();\n\n if (current === undefined) {\n throw new RouterError(errorCodes.ROUTER_NOT_STARTED, {\n message:\n \"[router.navigateToNotFound] cannot derive the path before the start navigation commits — pass an explicit path\",\n });\n }\n\n return this.#navigation.navigateToNotFound(current.path);\n }\n\n /**\n * Classifies a fire-and-forget rejection as an EXPECTED outcome that must\n * stay silent (no log). Shared by the navigate and start suppressors so the\n * suppression contract lives in one place.\n *\n * A suppressed RouterError code is a normal caller-owned navigation result\n * (a guard block, SAME_STATES, ROUTER_NOT_STARTED, …) — see\n * SUPPRESSED_ERROR_CODES (#721).\n *\n * The #945 RecursionDepthError carve-out is gone: a reentrant navigate() from a\n * listener can no longer self-feed — it throws REENTRANT_NAVIGATION\n * synchronously at the facade (RFC navigation-cancellation-unification §4), so\n * navigate()'s promise never rejects with a recursion error (re-entrant emits\n * are coalesced at the emitter, #1033; reentrant route-CRUD throws\n * REENTRANT_TREE_MUTATION to the CRUD caller, #1032 — not through a navigate\n * promise).\n */\n static #isExpectedRejection(error: unknown): boolean {\n return (\n error instanceof RouterError && SUPPRESSED_ERROR_CODES.has(error.code)\n );\n }\n\n /**\n * Fire-and-forget safety: prevents unhandled rejection warnings when\n * navigate/navigateToDefault/start is called without await. Expected errors\n * are silently suppressed; unexpected ones are logged under `onSuppressed`'s\n * category — navigate by default; start() passes #onSuppressedStartError so\n * its failures are logged as \"router.start\", not \"router.navigate\" (#931).\n */\n #suppressUnhandledRejection(\n promise: Promise<State>,\n onSuppressed: (error: unknown) => void = this.#onSuppressedNavigateError,\n ): void {\n promise.catch(onSuppressed);\n }\n\n /**\n * Rejects a synchronous reentrant navigation — `navigate` /\n * `navigateToDefault` / `navigateToState` / `navigateToNotFound` called from\n * inside a transition-event listener while a transition is being dispatched\n * (RFC navigation-cancellation-unification §4). Throws synchronously: inside a\n * listener the emit's `onListenerError` isolation surfaces it (visible,\n * non-fatal); a DEFERRED (async / microtask) navigate from a listener runs\n * after dispatch settles and is allowed. Always-on core invariant guard (not\n * validator-gated).\n */\n #assertNotReentrant(): void {\n if (this.#eventBus.isProcessing()) {\n throw new RouterError(errorCodes.REENTRANT_NAVIGATION);\n }\n }\n\n /**\n * Settles the FSM after a failed start pipeline, then re-throws so the\n * rejection still surfaces to the caller. Three cases, by what the pipeline\n * reached before throwing:\n *\n * - **Pre-commit, READY** (`isReady()` and no committed state): an interceptor\n * threw after `completeStart()` reached READY but before any state committed\n * (e.g. an activation guard blocked the start navigation) — return READY →\n * IDLE via `stop()` so the router is reusable.\n * - **Pre-commit, STARTING** (`isStarting()`): the pipeline threw before\n * `completeStart()` — a sync interceptor throw before `next()`, or a throw\n * inside the namespace before commit — so unwind STARTING → IDLE via\n * `sendFail`, which also emits TRANSITION_ERROR from STARTING (#668).\n * - **Post-commit, READY with committed state** (neither branch fires): a\n * loader/interceptor threw AFTER `navigateToState` committed and emitted\n * TRANSITION_SUCCESS (the SSR/RSC loader window). Keep the committed state —\n * rolling back would retract an observed success (\"phantom success\", #763);\n * the error still surfaces via the re-throw.\n */\n #unwindFailedStart(error: unknown): never {\n if (this.#eventBus.isReady() && this.#state.get() === undefined) {\n this.#lifecycle.stop();\n this.#eventBus.sendStop();\n } else if (this.#eventBus.isStarting()) {\n this.#eventBus.sendFail(undefined, undefined, error);\n }\n\n throw error;\n }\n\n #markDisposed(): void {\n this.navigate = throwDisposed;\n this.navigateToDefault = throwDisposed;\n this.navigateToNotFound = throwDisposed;\n this.start = throwDisposed;\n this.stop = throwDisposed;\n this.usePlugin = throwDisposed;\n\n this.subscribe = throwDisposed;\n this.subscribeLeave = throwDisposed;\n this.canNavigateTo = throwDisposed;\n }\n}\n\nfunction throwDisposed(): never {\n throw new RouterError(errorCodes.ROUTER_DISPOSED);\n}\n\n/**\n * Derives CreateMatcherOptions from router Options.\n * Maps core option names to matcher option names.\n */\nfunction deriveMatcherOptions(\n options: Readonly<Options>,\n): CreateMatcherOptions {\n return {\n strictTrailingSlash: options.trailingSlash === \"strict\",\n caseSensitive: options.caseSensitive,\n strictQueryParams: options.queryParamsMode === \"strict\",\n urlParamsEncoding: options.urlParamsEncoding,\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n queryParams: options.queryParams!,\n };\n}\n"],"mappings":"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"}