@real-router/core 0.78.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 (378) hide show
  1. package/README.md +1 -1
  2. package/dist/cjs/Router-DGUed2F4.js +2 -0
  3. package/dist/cjs/Router-DGUed2F4.js.map +1 -0
  4. package/dist/cjs/{Router-BG5yNYUG.d.ts → Router.d.ts} +5 -4
  5. package/dist/cjs/Router.d.ts.map +1 -0
  6. package/dist/cjs/Router.js +2 -0
  7. package/dist/cjs/Router.js.map +1 -0
  8. package/dist/cjs/RouterError.d.ts +182 -0
  9. package/dist/cjs/RouterError.d.ts.map +1 -0
  10. package/dist/cjs/RouterError.js +2 -0
  11. package/dist/cjs/RouterError.js.map +1 -0
  12. package/dist/cjs/api/cloneRouter.d.ts +86 -0
  13. package/dist/cjs/api/cloneRouter.d.ts.map +1 -0
  14. package/dist/cjs/api/cloneRouter.js +2 -0
  15. package/dist/cjs/api/cloneRouter.js.map +1 -0
  16. package/dist/cjs/api/getDependenciesApi.d.ts +7 -0
  17. package/dist/cjs/api/getDependenciesApi.d.ts.map +1 -0
  18. package/dist/cjs/api/getDependenciesApi.js +2 -0
  19. package/dist/cjs/api/getDependenciesApi.js.map +1 -0
  20. package/dist/cjs/api/getLifecycleApi.d.ts +7 -0
  21. package/dist/cjs/api/getLifecycleApi.d.ts.map +1 -0
  22. package/dist/cjs/api/getLifecycleApi.js +2 -0
  23. package/dist/cjs/api/getLifecycleApi.js.map +1 -0
  24. package/dist/cjs/api/getPluginApi.d.ts +7 -0
  25. package/dist/cjs/api/getPluginApi.d.ts.map +1 -0
  26. package/dist/cjs/api/getPluginApi.js +2 -0
  27. package/dist/cjs/api/getPluginApi.js.map +1 -0
  28. package/dist/cjs/api/getRoutesApi.d.ts +7 -0
  29. package/dist/cjs/api/getRoutesApi.d.ts.map +1 -0
  30. package/dist/cjs/api/getRoutesApi.js +2 -0
  31. package/dist/cjs/api/getRoutesApi.js.map +1 -0
  32. package/dist/cjs/api/helpers.js +2 -0
  33. package/dist/cjs/api/helpers.js.map +1 -0
  34. package/dist/cjs/api/types.d.ts +9 -0
  35. package/dist/cjs/api/types.d.ts.map +1 -0
  36. package/dist/cjs/api.d.ts +8 -105
  37. package/dist/cjs/api.js +1 -1
  38. package/dist/cjs/api.js.map +1 -1
  39. package/dist/cjs/buildParamMeta-DniluAIV.js +2 -0
  40. package/dist/cjs/buildParamMeta-DniluAIV.js.map +1 -0
  41. package/dist/cjs/{cloneRouter-a6LJt-zh.js → cloneRouter-Ch25z3Z2.js} +2 -2
  42. package/dist/cjs/{cloneRouter-a6LJt-zh.js.map → cloneRouter-Ch25z3Z2.js.map} +1 -1
  43. package/dist/cjs/constants.d.ts +25 -0
  44. package/dist/cjs/constants.d.ts.map +1 -0
  45. package/dist/cjs/constants.js +2 -0
  46. package/dist/cjs/constants.js.map +1 -0
  47. package/dist/cjs/createRouter.d.ts +23 -0
  48. package/dist/cjs/createRouter.d.ts.map +1 -0
  49. package/dist/cjs/createRouter.js +2 -0
  50. package/dist/cjs/createRouter.js.map +1 -0
  51. package/dist/cjs/engine/builder/buildTree.js +2 -0
  52. package/dist/cjs/engine/builder/buildTree.js.map +1 -0
  53. package/dist/cjs/engine/builder/computeCaches.js +2 -0
  54. package/dist/cjs/engine/builder/computeCaches.js.map +1 -0
  55. package/dist/cjs/engine/builder/createRouteTree.js +2 -0
  56. package/dist/cjs/engine/builder/createRouteTree.js.map +1 -0
  57. package/dist/cjs/engine/builder/types.d.ts +48 -0
  58. package/dist/cjs/engine/builder/types.d.ts.map +1 -0
  59. package/dist/cjs/engine/createMatcher.d.ts +29 -0
  60. package/dist/cjs/engine/createMatcher.d.ts.map +1 -0
  61. package/dist/cjs/engine/createMatcher.js +2 -0
  62. package/dist/cjs/engine/createMatcher.js.map +1 -0
  63. package/dist/cjs/engine/operations/routeTreeToDefinitions.js +2 -0
  64. package/dist/cjs/engine/operations/routeTreeToDefinitions.js.map +1 -0
  65. package/dist/cjs/engine/path-matcher/SegmentMatcher.d.ts +16 -0
  66. package/dist/cjs/engine/path-matcher/SegmentMatcher.d.ts.map +1 -0
  67. package/dist/cjs/engine/path-matcher/SegmentMatcher.js +2 -0
  68. package/dist/cjs/engine/path-matcher/SegmentMatcher.js.map +1 -0
  69. package/dist/cjs/engine/path-matcher/buildParamMeta.js +2 -0
  70. package/dist/cjs/engine/path-matcher/buildParamMeta.js.map +1 -0
  71. package/dist/cjs/engine/path-matcher/encoding.js +2 -0
  72. package/dist/cjs/engine/path-matcher/encoding.js.map +1 -0
  73. package/dist/cjs/engine/path-matcher/parseSegment.js +2 -0
  74. package/dist/cjs/engine/path-matcher/parseSegment.js.map +1 -0
  75. package/dist/cjs/engine/path-matcher/pathUtils.js +2 -0
  76. package/dist/cjs/engine/path-matcher/pathUtils.js.map +1 -0
  77. package/dist/cjs/engine/path-matcher/percentEncoding.js +2 -0
  78. package/dist/cjs/engine/path-matcher/percentEncoding.js.map +1 -0
  79. package/dist/cjs/engine/path-matcher/registration/buildParts.js +2 -0
  80. package/dist/cjs/engine/path-matcher/registration/buildParts.js.map +1 -0
  81. package/dist/cjs/engine/path-matcher/registration/context.js +2 -0
  82. package/dist/cjs/engine/path-matcher/registration/context.js.map +1 -0
  83. package/dist/cjs/engine/path-matcher/registration/errors.js +2 -0
  84. package/dist/cjs/engine/path-matcher/registration/errors.js.map +1 -0
  85. package/dist/cjs/engine/path-matcher/registration/index.js +2 -0
  86. package/dist/cjs/engine/path-matcher/registration/index.js.map +1 -0
  87. package/dist/cjs/engine/path-matcher/registration/trie.js +2 -0
  88. package/dist/cjs/engine/path-matcher/registration/trie.js.map +1 -0
  89. package/dist/cjs/engine/path-matcher/registration/trieNodes.js +2 -0
  90. package/dist/cjs/engine/path-matcher/registration/trieNodes.js.map +1 -0
  91. package/dist/cjs/{types-xg2zHVF6.d.ts → engine/path-matcher/types.d.ts} +2 -80
  92. package/dist/cjs/engine/path-matcher/types.d.ts.map +1 -0
  93. package/dist/cjs/engine/search-params/decode.js +2 -0
  94. package/dist/cjs/engine/search-params/decode.js.map +1 -0
  95. package/dist/cjs/engine/search-params/encode.js +2 -0
  96. package/dist/cjs/engine/search-params/encode.js.map +1 -0
  97. package/dist/cjs/engine/search-params/searchParams.js +2 -0
  98. package/dist/cjs/engine/search-params/searchParams.js.map +1 -0
  99. package/dist/cjs/engine/search-params/strategies/array.js +2 -0
  100. package/dist/cjs/engine/search-params/strategies/array.js.map +1 -0
  101. package/dist/cjs/engine/search-params/strategies/boolean.js +2 -0
  102. package/dist/cjs/engine/search-params/strategies/boolean.js.map +1 -0
  103. package/dist/cjs/engine/search-params/strategies/index.js +2 -0
  104. package/dist/cjs/engine/search-params/strategies/index.js.map +1 -0
  105. package/dist/cjs/engine/search-params/strategies/null.js +2 -0
  106. package/dist/cjs/engine/search-params/strategies/null.js.map +1 -0
  107. package/dist/cjs/engine/search-params/strategies/number.js +2 -0
  108. package/dist/cjs/engine/search-params/strategies/number.js.map +1 -0
  109. package/dist/cjs/engine/search-params/types.d.ts +72 -0
  110. package/dist/cjs/engine/search-params/types.d.ts.map +1 -0
  111. package/dist/cjs/engine/search-params/utils.js +2 -0
  112. package/dist/cjs/engine/search-params/utils.js.map +1 -0
  113. package/dist/cjs/engine/validation/route-batch.d.ts +38 -0
  114. package/dist/cjs/engine/validation/route-batch.d.ts.map +1 -0
  115. package/dist/cjs/engine/validation/route-batch.js +2 -0
  116. package/dist/cjs/engine/validation/route-batch.js.map +1 -0
  117. package/dist/cjs/engine/validation/routes.js +2 -0
  118. package/dist/cjs/engine/validation/routes.js.map +1 -0
  119. package/dist/cjs/foundation/event-emitter/EventEmitter.js +2 -0
  120. package/dist/cjs/foundation/event-emitter/EventEmitter.js.map +1 -0
  121. package/dist/cjs/foundation/fsm/fsm.js +2 -0
  122. package/dist/cjs/foundation/fsm/fsm.js.map +1 -0
  123. package/dist/cjs/foundation/logger/RouterLogger.js +2 -0
  124. package/dist/cjs/foundation/logger/RouterLogger.js.map +1 -0
  125. package/dist/cjs/foundation/logger/constants.js +2 -0
  126. package/dist/cjs/foundation/logger/constants.js.map +1 -0
  127. package/dist/cjs/getNavigator.d.ts +6 -0
  128. package/dist/cjs/getNavigator.d.ts.map +1 -0
  129. package/dist/cjs/getNavigator.js +2 -0
  130. package/dist/cjs/getNavigator.js.map +1 -0
  131. package/dist/cjs/guards.js +2 -0
  132. package/dist/cjs/guards.js.map +1 -0
  133. package/dist/cjs/helpers.js +2 -0
  134. package/dist/cjs/helpers.js.map +1 -0
  135. package/dist/cjs/index.d.ts +17 -239
  136. package/dist/cjs/index.js +1 -1
  137. package/dist/cjs/internals.d.ts +99 -0
  138. package/dist/cjs/internals.d.ts.map +1 -0
  139. package/dist/cjs/internals.js +2 -0
  140. package/dist/cjs/internals.js.map +1 -0
  141. package/dist/cjs/namespaces/DependenciesNamespace/dependenciesStore.d.ts +10 -0
  142. package/dist/cjs/namespaces/DependenciesNamespace/dependenciesStore.d.ts.map +1 -0
  143. package/dist/cjs/namespaces/DependenciesNamespace/dependenciesStore.js +2 -0
  144. package/dist/cjs/namespaces/DependenciesNamespace/dependenciesStore.js.map +1 -0
  145. package/dist/cjs/namespaces/EventBusNamespace/EventBusNamespace.js +2 -0
  146. package/dist/cjs/namespaces/EventBusNamespace/EventBusNamespace.js.map +1 -0
  147. package/dist/cjs/namespaces/NavigationNamespace/NavigationNamespace.js +2 -0
  148. package/dist/cjs/namespaces/NavigationNamespace/NavigationNamespace.js.map +1 -0
  149. package/dist/cjs/namespaces/NavigationNamespace/constants.js +2 -0
  150. package/dist/cjs/namespaces/NavigationNamespace/constants.js.map +1 -0
  151. package/dist/cjs/namespaces/NavigationNamespace/transition/completeTransition.js +2 -0
  152. package/dist/cjs/namespaces/NavigationNamespace/transition/completeTransition.js.map +1 -0
  153. package/dist/cjs/namespaces/NavigationNamespace/transition/errorHandling.js +2 -0
  154. package/dist/cjs/namespaces/NavigationNamespace/transition/errorHandling.js.map +1 -0
  155. package/dist/cjs/namespaces/NavigationNamespace/transition/guardPhase.js +2 -0
  156. package/dist/cjs/namespaces/NavigationNamespace/transition/guardPhase.js.map +1 -0
  157. package/dist/cjs/namespaces/OptionsNamespace/OptionsNamespace.js +2 -0
  158. package/dist/cjs/namespaces/OptionsNamespace/OptionsNamespace.js.map +1 -0
  159. package/dist/cjs/namespaces/OptionsNamespace/constants.js +2 -0
  160. package/dist/cjs/namespaces/OptionsNamespace/constants.js.map +1 -0
  161. package/dist/cjs/namespaces/OptionsNamespace/helpers.js +2 -0
  162. package/dist/cjs/namespaces/OptionsNamespace/helpers.js.map +1 -0
  163. package/dist/cjs/namespaces/OptionsNamespace/validators.js +2 -0
  164. package/dist/cjs/namespaces/OptionsNamespace/validators.js.map +1 -0
  165. package/dist/cjs/namespaces/PluginsNamespace/PluginsNamespace.js +2 -0
  166. package/dist/cjs/namespaces/PluginsNamespace/PluginsNamespace.js.map +1 -0
  167. package/dist/cjs/namespaces/PluginsNamespace/constants.js +2 -0
  168. package/dist/cjs/namespaces/PluginsNamespace/constants.js.map +1 -0
  169. package/dist/cjs/namespaces/PluginsNamespace/validators.js +2 -0
  170. package/dist/cjs/namespaces/PluginsNamespace/validators.js.map +1 -0
  171. package/dist/cjs/namespaces/RouteLifecycleNamespace/RouteLifecycleNamespace.d.ts +174 -0
  172. package/dist/cjs/namespaces/RouteLifecycleNamespace/RouteLifecycleNamespace.d.ts.map +1 -0
  173. package/dist/cjs/namespaces/RouteLifecycleNamespace/RouteLifecycleNamespace.js +2 -0
  174. package/dist/cjs/namespaces/RouteLifecycleNamespace/RouteLifecycleNamespace.js.map +1 -0
  175. package/dist/cjs/namespaces/RouteLifecycleNamespace/types.d.ts +17 -0
  176. package/dist/cjs/namespaces/RouteLifecycleNamespace/types.d.ts.map +1 -0
  177. package/dist/cjs/namespaces/RouterLifecycleNamespace/RouterLifecycleNamespace.js +2 -0
  178. package/dist/cjs/namespaces/RouterLifecycleNamespace/RouterLifecycleNamespace.js.map +1 -0
  179. package/dist/cjs/namespaces/RouterLifecycleNamespace/constants.js +2 -0
  180. package/dist/cjs/namespaces/RouterLifecycleNamespace/constants.js.map +1 -0
  181. package/dist/cjs/namespaces/RoutesNamespace/RoutesNamespace.js +2 -0
  182. package/dist/cjs/namespaces/RoutesNamespace/RoutesNamespace.js.map +1 -0
  183. package/dist/cjs/namespaces/RoutesNamespace/constants.js +2 -0
  184. package/dist/cjs/namespaces/RoutesNamespace/constants.js.map +1 -0
  185. package/dist/cjs/namespaces/RoutesNamespace/forwardChain.d.ts +5 -0
  186. package/dist/cjs/namespaces/RoutesNamespace/forwardChain.d.ts.map +1 -0
  187. package/dist/cjs/namespaces/RoutesNamespace/forwardChain.js +2 -0
  188. package/dist/cjs/namespaces/RoutesNamespace/forwardChain.js.map +1 -0
  189. package/dist/cjs/namespaces/RoutesNamespace/helpers.js +2 -0
  190. package/dist/cjs/namespaces/RoutesNamespace/helpers.js.map +1 -0
  191. package/dist/cjs/namespaces/RoutesNamespace/routeGuards.js +2 -0
  192. package/dist/cjs/namespaces/RoutesNamespace/routeGuards.js.map +1 -0
  193. package/dist/cjs/namespaces/RoutesNamespace/routesStore.d.ts +42 -0
  194. package/dist/cjs/namespaces/RoutesNamespace/routesStore.d.ts.map +1 -0
  195. package/dist/cjs/namespaces/RoutesNamespace/routesStore.js +2 -0
  196. package/dist/cjs/namespaces/RoutesNamespace/routesStore.js.map +1 -0
  197. package/dist/cjs/namespaces/RoutesNamespace/types.d.ts +57 -0
  198. package/dist/cjs/namespaces/RoutesNamespace/types.d.ts.map +1 -0
  199. package/dist/cjs/namespaces/StateNamespace/StateNamespace.js +2 -0
  200. package/dist/cjs/namespaces/StateNamespace/StateNamespace.js.map +1 -0
  201. package/dist/cjs/namespaces/StateNamespace/helpers.js +2 -0
  202. package/dist/cjs/namespaces/StateNamespace/helpers.js.map +1 -0
  203. package/dist/cjs/routerFSM.js +2 -0
  204. package/dist/cjs/routerFSM.js.map +1 -0
  205. package/dist/cjs/stateMetaStore.js +2 -0
  206. package/dist/cjs/stateMetaStore.js.map +1 -0
  207. package/dist/cjs/transitionPath.js +2 -0
  208. package/dist/cjs/transitionPath.js.map +1 -0
  209. package/dist/cjs/{RouterValidator-CZ8kPkrI.d.ts → types/RouterValidator.d.ts} +2 -2
  210. package/dist/cjs/types/RouterValidator.d.ts.map +1 -0
  211. package/dist/cjs/types/api.d.ts +184 -0
  212. package/dist/cjs/types/api.d.ts.map +1 -0
  213. package/dist/cjs/types/base.d.ts +86 -0
  214. package/dist/cjs/types/base.d.ts.map +1 -0
  215. package/dist/cjs/types/constants.d.ts +72 -0
  216. package/dist/cjs/types/constants.d.ts.map +1 -0
  217. package/dist/cjs/types/internal.d.ts +9 -0
  218. package/dist/cjs/types/internal.d.ts.map +1 -0
  219. package/dist/cjs/types/limits.d.ts +45 -0
  220. package/dist/cjs/types/limits.d.ts.map +1 -0
  221. package/dist/cjs/types/route-node-types.d.ts +46 -0
  222. package/dist/cjs/types/route-node-types.d.ts.map +1 -0
  223. package/dist/cjs/types/router.d.ts +313 -0
  224. package/dist/cjs/types/router.d.ts.map +1 -0
  225. package/dist/cjs/types/tree-changed.d.ts +55 -0
  226. package/dist/cjs/types/tree-changed.d.ts.map +1 -0
  227. package/dist/cjs/types.d.ts +208 -2
  228. package/dist/cjs/types.d.ts.map +1 -0
  229. package/dist/cjs/utils/createRequestScope.d.ts +111 -0
  230. package/dist/cjs/utils/createRequestScope.d.ts.map +1 -0
  231. package/dist/cjs/utils/createRequestScope.js +2 -0
  232. package/dist/cjs/utils/createRequestScope.js.map +1 -0
  233. package/dist/cjs/utils/getStaticPaths.d.ts +7 -0
  234. package/dist/cjs/utils/getStaticPaths.d.ts.map +1 -0
  235. package/dist/cjs/utils/getStaticPaths.js +2 -0
  236. package/dist/cjs/utils/getStaticPaths.js.map +1 -0
  237. package/dist/cjs/utils/hydrateRouter.d.ts +69 -0
  238. package/dist/cjs/utils/hydrateRouter.d.ts.map +1 -0
  239. package/dist/cjs/utils/hydrateRouter.js +2 -0
  240. package/dist/cjs/utils/hydrateRouter.js.map +1 -0
  241. package/dist/cjs/utils/serializeRouterState.d.ts +84 -0
  242. package/dist/cjs/utils/serializeRouterState.d.ts.map +1 -0
  243. package/dist/cjs/utils/serializeRouterState.js +2 -0
  244. package/dist/cjs/utils/serializeRouterState.js.map +1 -0
  245. package/dist/cjs/utils/serializeState.d.ts +50 -0
  246. package/dist/cjs/utils/serializeState.d.ts.map +1 -0
  247. package/dist/cjs/utils/serializeState.js +2 -0
  248. package/dist/cjs/utils/serializeState.js.map +1 -0
  249. package/dist/cjs/utils.d.ts +5 -1
  250. package/dist/cjs/utils.js +1 -1
  251. package/dist/cjs/validation.d.ts +6 -518
  252. package/dist/cjs/validation.js +1 -1
  253. package/dist/cjs/validation.js.map +1 -1
  254. package/dist/cjs/wiring/wireNamespaces.js +2 -0
  255. package/dist/cjs/wiring/wireNamespaces.js.map +1 -0
  256. package/dist/esm/Router-Bk6PfSE8.mjs +2 -0
  257. package/dist/esm/Router-Bk6PfSE8.mjs.map +1 -0
  258. package/dist/esm/{Router-Cy75qbgU.d.mts → Router.d.mts} +5 -4
  259. package/dist/esm/Router.d.mts.map +1 -0
  260. package/dist/esm/RouterError.d.mts +182 -0
  261. package/dist/esm/RouterError.d.mts.map +1 -0
  262. package/dist/esm/api/cloneRouter.d.mts +86 -0
  263. package/dist/esm/api/cloneRouter.d.mts.map +1 -0
  264. package/dist/esm/api/getDependenciesApi.d.mts +7 -0
  265. package/dist/esm/api/getDependenciesApi.d.mts.map +1 -0
  266. package/dist/esm/api/getLifecycleApi.d.mts +7 -0
  267. package/dist/esm/api/getLifecycleApi.d.mts.map +1 -0
  268. package/dist/esm/api/getPluginApi.d.mts +7 -0
  269. package/dist/esm/api/getPluginApi.d.mts.map +1 -0
  270. package/dist/esm/api/getRoutesApi.d.mts +7 -0
  271. package/dist/esm/api/getRoutesApi.d.mts.map +1 -0
  272. package/dist/esm/api/types.d.mts +9 -0
  273. package/dist/esm/api/types.d.mts.map +1 -0
  274. package/dist/esm/api.d.mts +8 -105
  275. package/dist/esm/api.mjs +1 -1
  276. package/dist/esm/api.mjs.map +1 -1
  277. package/dist/esm/buildParamMeta-bOLhLF9h.mjs +2 -0
  278. package/dist/esm/buildParamMeta-bOLhLF9h.mjs.map +1 -0
  279. package/dist/esm/{cloneRouter-CqS4kTs8.mjs → cloneRouter-D4rnqmi9.mjs} +2 -2
  280. package/dist/esm/{cloneRouter-CqS4kTs8.mjs.map → cloneRouter-D4rnqmi9.mjs.map} +1 -1
  281. package/dist/esm/constants.d.mts +25 -0
  282. package/dist/esm/constants.d.mts.map +1 -0
  283. package/dist/esm/createRouter.d.mts +23 -0
  284. package/dist/esm/createRouter.d.mts.map +1 -0
  285. package/dist/esm/engine/builder/types.d.mts +48 -0
  286. package/dist/esm/engine/builder/types.d.mts.map +1 -0
  287. package/dist/esm/engine/createMatcher.d.mts +29 -0
  288. package/dist/esm/engine/createMatcher.d.mts.map +1 -0
  289. package/dist/esm/engine/path-matcher/SegmentMatcher.d.mts +16 -0
  290. package/dist/esm/engine/path-matcher/SegmentMatcher.d.mts.map +1 -0
  291. package/dist/esm/{types-xg2zHVF6.d.mts → engine/path-matcher/types.d.mts} +2 -80
  292. package/dist/esm/engine/path-matcher/types.d.mts.map +1 -0
  293. package/dist/esm/engine/search-params/types.d.mts +72 -0
  294. package/dist/esm/engine/search-params/types.d.mts.map +1 -0
  295. package/dist/esm/engine/validation/route-batch.d.mts +38 -0
  296. package/dist/esm/engine/validation/route-batch.d.mts.map +1 -0
  297. package/dist/esm/getNavigator.d.mts +6 -0
  298. package/dist/esm/getNavigator.d.mts.map +1 -0
  299. package/dist/esm/index.d.mts +17 -239
  300. package/dist/esm/index.mjs +1 -1
  301. package/dist/esm/internals.d.mts +99 -0
  302. package/dist/esm/internals.d.mts.map +1 -0
  303. package/dist/esm/namespaces/DependenciesNamespace/dependenciesStore.d.mts +10 -0
  304. package/dist/esm/namespaces/DependenciesNamespace/dependenciesStore.d.mts.map +1 -0
  305. package/dist/esm/namespaces/RouteLifecycleNamespace/RouteLifecycleNamespace.d.mts +174 -0
  306. package/dist/esm/namespaces/RouteLifecycleNamespace/RouteLifecycleNamespace.d.mts.map +1 -0
  307. package/dist/esm/namespaces/RouteLifecycleNamespace/types.d.mts +17 -0
  308. package/dist/esm/namespaces/RouteLifecycleNamespace/types.d.mts.map +1 -0
  309. package/dist/esm/namespaces/RoutesNamespace/forwardChain.d.mts +5 -0
  310. package/dist/esm/namespaces/RoutesNamespace/forwardChain.d.mts.map +1 -0
  311. package/dist/esm/namespaces/RoutesNamespace/routesStore.d.mts +42 -0
  312. package/dist/esm/namespaces/RoutesNamespace/routesStore.d.mts.map +1 -0
  313. package/dist/esm/namespaces/RoutesNamespace/types.d.mts +57 -0
  314. package/dist/esm/namespaces/RoutesNamespace/types.d.mts.map +1 -0
  315. package/dist/esm/{RouterValidator-CZ8kPkrI.d.mts → types/RouterValidator.d.mts} +2 -2
  316. package/dist/esm/types/RouterValidator.d.mts.map +1 -0
  317. package/dist/esm/types/api.d.mts +184 -0
  318. package/dist/esm/types/api.d.mts.map +1 -0
  319. package/dist/esm/types/base.d.mts +86 -0
  320. package/dist/esm/types/base.d.mts.map +1 -0
  321. package/dist/esm/types/constants.d.mts +72 -0
  322. package/dist/esm/types/constants.d.mts.map +1 -0
  323. package/dist/esm/types/internal.d.mts +9 -0
  324. package/dist/esm/types/internal.d.mts.map +1 -0
  325. package/dist/esm/types/limits.d.mts +45 -0
  326. package/dist/esm/types/limits.d.mts.map +1 -0
  327. package/dist/esm/types/route-node-types.d.mts +46 -0
  328. package/dist/esm/types/route-node-types.d.mts.map +1 -0
  329. package/dist/esm/types/router.d.mts +313 -0
  330. package/dist/esm/types/router.d.mts.map +1 -0
  331. package/dist/esm/types/tree-changed.d.mts +55 -0
  332. package/dist/esm/types/tree-changed.d.mts.map +1 -0
  333. package/dist/esm/types.d.mts +208 -2
  334. package/dist/esm/types.d.mts.map +1 -0
  335. package/dist/esm/utils/createRequestScope.d.mts +111 -0
  336. package/dist/esm/utils/createRequestScope.d.mts.map +1 -0
  337. package/dist/esm/utils/getStaticPaths.d.mts +7 -0
  338. package/dist/esm/utils/getStaticPaths.d.mts.map +1 -0
  339. package/dist/esm/utils/hydrateRouter.d.mts +69 -0
  340. package/dist/esm/utils/hydrateRouter.d.mts.map +1 -0
  341. package/dist/esm/utils/serializeRouterState.d.mts +84 -0
  342. package/dist/esm/utils/serializeRouterState.d.mts.map +1 -0
  343. package/dist/esm/utils/serializeState.d.mts +50 -0
  344. package/dist/esm/utils/serializeState.d.mts.map +1 -0
  345. package/dist/esm/utils.d.mts +5 -1
  346. package/dist/esm/utils.mjs +1 -1
  347. package/dist/esm/validation.d.mts +6 -518
  348. package/dist/esm/validation.mjs +1 -1
  349. package/dist/esm/validation.mjs.map +1 -1
  350. package/package.json +2 -2
  351. package/dist/cjs/Router-BG5yNYUG.d.ts.map +0 -1
  352. package/dist/cjs/Router-D6NzRxBA.js +0 -2
  353. package/dist/cjs/Router-D6NzRxBA.js.map +0 -1
  354. package/dist/cjs/RouterValidator-CZ8kPkrI.d.ts.map +0 -1
  355. package/dist/cjs/api.d.ts.map +0 -1
  356. package/dist/cjs/buildParamMeta-CkG6cUIl.js +0 -2
  357. package/dist/cjs/buildParamMeta-CkG6cUIl.js.map +0 -1
  358. package/dist/cjs/index-CAnGQpnz.d.ts +0 -974
  359. package/dist/cjs/index-CAnGQpnz.d.ts.map +0 -1
  360. package/dist/cjs/index-DDrryY2U.d.ts +0 -309
  361. package/dist/cjs/index-DDrryY2U.d.ts.map +0 -1
  362. package/dist/cjs/index.d.ts.map +0 -1
  363. package/dist/cjs/types-xg2zHVF6.d.ts.map +0 -1
  364. package/dist/cjs/validation.d.ts.map +0 -1
  365. package/dist/esm/Router-Cy75qbgU.d.mts.map +0 -1
  366. package/dist/esm/Router-De0Gr9Ll.mjs +0 -2
  367. package/dist/esm/Router-De0Gr9Ll.mjs.map +0 -1
  368. package/dist/esm/RouterValidator-CZ8kPkrI.d.mts.map +0 -1
  369. package/dist/esm/api.d.mts.map +0 -1
  370. package/dist/esm/buildParamMeta-Bl0uORu6.mjs +0 -2
  371. package/dist/esm/buildParamMeta-Bl0uORu6.mjs.map +0 -1
  372. package/dist/esm/index-CAnGQpnz.d.mts +0 -974
  373. package/dist/esm/index-CAnGQpnz.d.mts.map +0 -1
  374. package/dist/esm/index-DnSU6huX.d.mts +0 -309
  375. package/dist/esm/index-DnSU6huX.d.mts.map +0 -1
  376. package/dist/esm/index.d.mts.map +0 -1
  377. package/dist/esm/types-xg2zHVF6.d.mts.map +0 -1
  378. package/dist/esm/validation.d.mts.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"array.js","names":["safeEncode"],"sources":["../../../../../src/engine/search-params/strategies/array.ts"],"sourcesContent":["/**\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"],"mappings":"+BA0DM,EAAe,GAA2B,CAC9C,IAAM,EAAO,OAAO,EAEpB,GAAI,IAAS,UAAY,IAAS,UAAY,IAAS,UAGrD,MAAU,UACR,iFAAiF,GACnF,EAGF,OAAOA,EAAAA,WAAW,CAAkC,CACtD,EAQM,GACJ,EACA,EACA,EACA,IACW,CACX,IAAM,EAAM,GAAG,IAAO,IAChB,EAAkB,CAAC,EAEzB,IAAK,IAAM,KAAS,EAClB,GAAI,IAAU,KAAM,CAClB,IAAM,EAAU,EAAa,OAAO,CAAG,EAEnC,GACF,EAAM,KAAK,CAAO,CAEtB,MACE,EAAM,KAAK,GAAG,EAAI,GAAG,EAAY,CAAK,GAAG,EAI7C,OAAO,EAAM,KAAK,GAAG,CACvB,EAUa,EAAmC,CAC9C,aAAc,EAAM,EAAQ,IAC1B,EAAU,EAAM,EAAQ,GAAI,CAAY,CAC5C,EAMa,EAAuC,CAClD,aAAc,EAAM,EAAQ,IAC1B,EAAU,EAAM,EAAQ,KAAM,CAAY,CAC9C,EAMa,EAAoC,CAC/C,aAAc,EAAM,EAAQ,IAAiB,CAC3C,IAAM,EAAkB,CAAC,EAEzB,IAAK,GAAM,CAAC,EAAG,KAAU,EAAO,QAAQ,EAAG,CACzC,IAAM,EAAM,GAAG,EAAK,GAAG,EAAE,GAEzB,GAAI,IAAU,KAAM,CAClB,IAAM,EAAU,EAAa,OAAO,CAAG,EAEnC,GACF,EAAM,KAAK,CAAO,CAEtB,MACE,EAAM,KAAK,GAAG,EAAI,GAAG,EAAY,CAAK,GAAG,CAE7C,CAEA,OAAO,EAAM,KAAK,GAAG,CACvB,EAEA,QAAS,EACX,EAaa,EAAoC,CAC/C,aAAc,EAAM,IAAW,CAC7B,IAAM,EAAkB,CAAC,EAEzB,IAAK,IAAM,KAAS,EACd,IAAU,MACZ,EAAM,KAAK,EAAY,CAAK,CAAC,EAQjC,OAJI,EAAM,SAAW,EACZ,GAGF,GAAG,EAAK,GAAG,EAAM,KAAK,GAAG,GAClC,EAEA,YAAc,GAGP,EAAS,SAAS,GAAG,EAInB,EAAS,MAAM,GAAG,EAHhB,IAKb,EAKa,EAAsD,CACjE,KAAM,EACN,SAAU,EACV,MAAO,EACP,MAAO,CACT"}
@@ -0,0 +1,2 @@
1
+ const e={encode:(e,t)=>`${e}=${t}`,decodeUndefined:()=>null,decodeRaw:()=>null,decodeValue:e=>e},t={encode:(e,t)=>`${e}=${t}`,decodeUndefined:()=>null,decodeRaw:e=>e===`true`||e!==`false`&&null,decodeValue:e=>e},n={encode:(e,t)=>t?e:`${e}=false`,decodeUndefined:()=>!0,decodeRaw:e=>e===`true`||e!==`false`&&null,decodeValue:e=>e},r={none:e,auto:t,"empty-true":n};exports.autoBooleanStrategy=t,exports.booleanStrategies=r,exports.emptyTrueBooleanStrategy=n,exports.noneBooleanStrategy=e;
2
+ //# sourceMappingURL=boolean.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"boolean.js","names":[],"sources":["../../../../../src/engine/search-params/strategies/boolean.ts"],"sourcesContent":["/**\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"],"mappings":"AA6DA,MAAa,EAAuC,CAClD,QAAS,EAAM,IAAU,GAAG,EAAK,GAAG,IACpC,oBAAuB,KACvB,cAAiB,KACjB,YAAc,GAAY,CAC5B,EAMa,EAAuC,CAClD,QAAS,EAAM,IAAU,GAAG,EAAK,GAAG,IACpC,oBAAuB,KACvB,UAAY,GACN,IAAQ,QAGR,IAAQ,SAIL,KAET,YAAc,GAAY,CAC5B,EAUa,EAA4C,CACvD,QAAS,EAAM,IAAW,EAAQ,EAAO,GAAG,EAAK,QACjD,oBAAuB,GACvB,UAAY,GACN,IAAQ,QAGR,IAAQ,SAIL,KAET,YAAc,GAAY,CAC5B,EAKa,EAA4D,CACvE,KAAM,EACN,KAAM,EACN,aAAc,CAChB"}
@@ -0,0 +1,2 @@
1
+ const e=require("./array.js"),t=require("./boolean.js"),n=require("./null.js"),r=require("./number.js"),i=(e,t,n,r)=>{if(e===void 0)throw TypeError(`[search-params] Unknown ${t} "${n}" — expected ${r}`);return e},a=(a,o,s,c)=>({boolean:i(t.booleanStrategies[o],`booleanFormat`,o,`"none" | "auto" | "empty-true"`),null:i(n.nullStrategies[s],`nullFormat`,s,`"default" | "hidden"`),number:i(r.numberStrategies[c],`numberFormat`,c,`"none" | "auto"`),array:i(e.arrayStrategies[a],`arrayFormat`,a,`"none" | "brackets" | "index" | "comma"`)}),o={boolean:t.booleanStrategies.auto,null:n.nullStrategies.default,number:r.numberStrategies.auto,array:e.arrayStrategies.none};exports.DEFAULT_STRATEGIES=o,exports.resolveStrategies=a;
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["booleanStrategies","nullStrategies","numberStrategies","arrayStrategies"],"sources":["../../../../../src/engine/search-params/strategies/index.ts"],"sourcesContent":["/**\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"],"mappings":"wGA4DM,GACJ,EACA,EACA,EACA,IACM,CACN,GAAI,IAAa,IAAA,GACf,MAAU,UACR,2BAA2B,EAAM,IAAI,EAAM,eAAe,GAC5D,EAGF,OAAO,CACT,EAEa,GACX,EACA,EACA,EACA,KACwB,CACxB,QAAS,EACPA,EAAAA,kBAAkB,GAClB,gBACA,EACA,gCACF,EACA,KAAM,EACJC,EAAAA,eAAe,GACf,aACA,EACA,sBACF,EACA,OAAQ,EACNC,EAAAA,iBAAiB,GACjB,eACA,EACA,iBACF,EACA,MAAO,EACLC,EAAAA,gBAAgB,GAChB,cACA,EACA,yCACF,CACF,GAUa,EAAyC,CACpD,QAASH,EAAAA,kBAAkB,KAC3B,KAAMC,EAAAA,eAAe,QACrB,OAAQC,EAAAA,iBAAiB,KACzB,MAAOC,EAAAA,gBAAgB,IACzB"}
@@ -0,0 +1,2 @@
1
+ const e={encode:e=>e},t={encode:()=>``},n={default:e,hidden:t};exports.defaultNullStrategy=e,exports.hiddenNullStrategy=t,exports.nullStrategies=n;
2
+ //# sourceMappingURL=null.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"null.js","names":[],"sources":["../../../../../src/engine/search-params/strategies/null.ts"],"sourcesContent":["/**\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"],"mappings":"AAqCA,MAAa,EAAoC,CAC/C,OAAS,GAAS,CACpB,EAMa,EAAmC,CAC9C,WAAc,EAChB,EAKa,EAAmD,CAC9D,QAAS,EACT,OAAQ,CACV"}
@@ -0,0 +1,2 @@
1
+ const e={decode:()=>null},t={decode:e=>{let t=e.length;if(t===0)return null;let n=+(e.codePointAt(0)===45);if(n===t||t-n>1&&e.codePointAt(n)===48&&e.codePointAt(n+1)!==46)return null;let r=!1;for(let i=n;i<t;i++){let a=e.codePointAt(i);if(!(a!==void 0&&a>=48&&a<=57)){if(a===46&&!r&&i!==n&&i!==t-1){r=!0;continue}return null}}let i=Number(e);return Object.is(i,-0)||!Number.isSafeInteger(i)&&!r?null:i}},n={auto:t,none:e};exports.autoNumberStrategy=t,exports.noneNumberStrategy=e,exports.numberStrategies=n;
2
+ //# sourceMappingURL=number.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"number.js","names":[],"sources":["../../../../../src/engine/search-params/strategies/number.ts"],"sourcesContent":["/**\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"],"mappings":"AA0BA,MAAa,EAAqC,CAChD,WAAc,IAChB,EAYa,EAAqC,CAChD,OAAS,GAAU,CACjB,IAAM,EAAS,EAAM,OAErB,GAAI,IAAW,EACb,OAAO,KAIT,IAAM,EAAQ,IAAM,YAAY,CAAC,IAAM,IASvC,GANI,IAAU,GAOZ,EAAS,EAAQ,GACjB,EAAM,YAAY,CAAK,IAAM,IAC7B,EAAM,YAAY,EAAQ,CAAC,IAAM,GAEjC,OAAO,KAGT,IAAI,EAAS,GAEb,IAAK,IAAI,EAAI,EAAO,EAAI,EAAQ,IAAK,CACnC,IAAM,EAAK,EAAM,YAAY,CAAC,EAE1B,SAAO,IAAA,IAAa,GAAM,IAAM,GAAM,IAI1C,IAAI,IAAO,IAAM,CAAC,GAAU,IAAM,GAAS,IAAM,EAAS,EAAG,CAC3D,EAAS,GAET,QACF,CAEA,OAAO,IAFP,CAGF,CAEA,IAAM,EAAM,OAAO,CAAK,EAaxB,OATI,OAAO,GAAG,EAAK,EAAE,GAKjB,CAAC,OAAO,cAAc,CAAG,GAAK,CAAC,EAC1B,KAGF,CACT,CACF,EASa,EAAyD,CACpE,KAAM,EACN,KAAM,CACR"}
@@ -0,0 +1,72 @@
1
+ //#region src/engine/search-params/types.d.ts
2
+ /**
3
+ * Search Params Type Definitions.
4
+ *
5
+ * Consolidated interfaces and type aliases for the search-params module.
6
+ *
7
+ * @module search-params/types
8
+ */
9
+ /**
10
+ * Array parameter encoding format.
11
+ *
12
+ * @remarks
13
+ * - `none` - repeated keys: `a=1&a=2`
14
+ * - `brackets` - bracket notation: `a[]=1&a[]=2`
15
+ * - `index` - indexed notation: `a[0]=1&a[1]=2`
16
+ * - `comma` - comma-separated: `a=1,2`
17
+ */
18
+ type ArrayFormat = "none" | "brackets" | "index" | "comma";
19
+ /**
20
+ * Boolean parameter encoding format.
21
+ *
22
+ * @remarks
23
+ * - `none` - no special handling
24
+ * - `auto` - auto-detect "true"/"false" strings as boolean values
25
+ * - `empty-true` - true values have no value: `?flag` instead of `?flag=true`
26
+ *
27
+ * @remarks
28
+ * Under `empty-true` the bare-key wire form (`?flag`) is reserved for `true`, so
29
+ * a `null` value with `nullFormat: "default"` (which also encodes to a bare key)
30
+ * is **not representable**: it round-trips back as `true`, not `null`. Both null
31
+ * and true collapse to the same token and only `true` decodes back. Pair
32
+ * `empty-true` with `nullFormat: "hidden"`, or avoid null query values.
33
+ */
34
+ type BooleanFormat = "none" | "auto" | "empty-true";
35
+ /**
36
+ * Null parameter encoding format.
37
+ *
38
+ * @remarks
39
+ * - `default` - key only: `?key` (collides with `booleanFormat: "empty-true"`,
40
+ * which reserves the bare-key form for `true` — null then decodes as `true`)
41
+ * - `hidden` - omit from query string
42
+ */
43
+ type NullFormat = "default" | "hidden";
44
+ /**
45
+ * Number parameter encoding format.
46
+ *
47
+ * @remarks
48
+ * - `none` - no special handling
49
+ * - `auto` - decode canonical decimal numbers (`/^-?(0|[1-9]\d*)(\.\d+)?$/`) to
50
+ * `Number()`. Recognizes negatives so the parsed type matches what
51
+ * `build`/`navigate` produce; rejects leading zeros (`"007"`), exponent
52
+ * notation, unsafe integers, and negative zero (`"-0"` — the grammar matches
53
+ * it, but an `Object.is` guard rejects it since `build(-0)` emits `"0"`, #898)
54
+ * — those keep their exact text as strings.
55
+ */
56
+ type NumberFormat = "none" | "auto";
57
+ /**
58
+ * Options for search-params parsing and building.
59
+ */
60
+ interface Options {
61
+ /** Array parameter encoding format. @default "none" */
62
+ arrayFormat?: ArrayFormat;
63
+ /** Boolean parameter encoding format. @default "auto" */
64
+ booleanFormat?: BooleanFormat;
65
+ /** Null parameter encoding format. @default "default" */
66
+ nullFormat?: NullFormat;
67
+ /** Number parameter encoding format. @default "auto" */
68
+ numberFormat?: NumberFormat;
69
+ }
70
+ //#endregion
71
+ export { ArrayFormat, BooleanFormat, NullFormat, NumberFormat, Options };
72
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","names":[],"sources":["../../../../src/engine/search-params/types.ts"],"mappings":";;;;;;;;;;;;;;;;;KAqBY;;;;;;;;;;;;;;;;KAiBA;;;;;;;;;KAUA;;;;;;;;;;;;;KAcA;;;;UASK;;EAEf,cAAc;;EAEd,gBAAgB;;EAEhB,aAAa;;EAEb,eAAe"}
@@ -0,0 +1,2 @@
1
+ const e=/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g,t=t=>{try{return encodeURIComponent(t)}catch(n){if(!(n instanceof URIError))throw n;return encodeURIComponent(String(t).replaceAll(e,`�`))}};exports.safeEncode=t;
2
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","names":[],"sources":["../../../../src/engine/search-params/utils.ts"],"sourcesContent":["/**\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"],"mappings":"AAoBA,MAAM,EACJ,0EAeW,EAAc,GAA6C,CACtE,GAAI,CACF,OAAO,mBAAmB,CAAK,CACjC,OAAS,EAAO,CAId,GAAI,EAAE,aAAiB,UACrB,MAAM,EAIR,OAAO,mBACL,OAAO,CAAK,CAAC,CAAC,WAAW,EAAoB,GAAG,CAClD,CACF,CACF"}
@@ -0,0 +1,38 @@
1
+ import { RouteDefinition, RouteTree } from "../builder/types.js";
2
+ //#region src/engine/validation/route-batch.d.ts
3
+ /**
4
+ * Validates route structure for add operations.
5
+ *
6
+ * Performs comprehensive validation including:
7
+ * - Type check (must be object)
8
+ * - Name validation
9
+ * - Path validation
10
+ * - Duplicate name detection (in tree and batch)
11
+ * - Duplicate path detection (in tree and batch)
12
+ * - Recursive children validation
13
+ *
14
+ * @param route - Route to validate
15
+ * @param methodName - Calling method for error context
16
+ * @param rootNode - Optional root node for duplicate checking
17
+ * @param parentName - Parent route name for building full path (used in recursion)
18
+ * @param seenNames - Set of names already seen in this batch
19
+ * @param seenPathsByParent - Map of paths by parent for path duplicate detection
20
+ * @throws {TypeError} If route structure is invalid
21
+ * @throws {Error} If route already exists (duplicate)
22
+ * @throws {Error} If path already defined (duplicate)
23
+ *
24
+ * @example
25
+ * ```typescript
26
+ * const seenNames = new Set<string>();
27
+ * const seenPaths = new Map<string, Set<string>>();
28
+ *
29
+ * // Validate routes before adding
30
+ * for (const route of routes) {
31
+ * validateRoute(route, "add", rootNode, "", seenNames, seenPaths);
32
+ * }
33
+ * ```
34
+ */
35
+ declare function validateRoute(route: unknown, methodName: string, rootNode?: RouteTree, parentName?: string, seenNames?: Set<string>, seenPathsByParent?: Map<string, Set<string>>): asserts route is RouteDefinition;
36
+ //#endregion
37
+ export { validateRoute };
38
+ //# sourceMappingURL=route-batch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route-batch.d.ts","names":[],"sources":["../../../../src/engine/validation/route-batch.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA0YgB,cACd,gBACA,oBACA,WAAW,WACX,qBACA,YAAY,aACZ,oBAAoB,YAAY,uBACvB,SAAS"}
@@ -0,0 +1,2 @@
1
+ const e=require("./routes.js"),t=/^[A-Z_a-z][\w-]*$/,n=/\S/,r=1e4;function i(e){if(e===null)return`null`;if(typeof e==`object`){try{let t=e.constructor;if(typeof t==`function`&&t.name!==`Object`)return t.name||`object`}catch{}return`object`}return typeof e}function a(e){for(let t of Object.keys(e)){let n=Object.getOwnPropertyDescriptor(e,t);if(n&&(n.get||n.set))return!0}return!1}function o(e,t){if(!e||typeof e!=`object`)throw TypeError(`[router.${t}] Route must be an object, got ${i(e)}`);let n=Object.getPrototypeOf(e);if(n!==Object.prototype&&n!==null)throw TypeError(`[router.${t}] Route must be a plain object, got ${i(e)}`);if(a(e))throw TypeError(`[router.${t}] Route must not have getters or setters`)}function s(e,t){if(e.encodeParams!==void 0&&typeof e.encodeParams!=`function`)throw TypeError(`[router.${t}] Route "${String(e.name)}" encodeParams must be a function`)}function c(e,t){if(e.decodeParams!==void 0&&typeof e.decodeParams!=`function`)throw TypeError(`[router.${t}] Route "${String(e.name)}" decodeParams must be a function`)}function l(e,a){if(typeof e.name!=`string`)throw TypeError(`[router.${a}] Route name must be a string, got ${i(e.name)}`);let o=e.name;if(o===``)throw TypeError(`[router.${a}] Route name cannot be empty`);if(!n.test(o))throw TypeError(`[router.${a}] Route name cannot contain only whitespace`);if(o.length>r)throw TypeError(`[router.${a}] Route name exceeds maximum length of ${r} characters`);if(!o.startsWith(`@@`)){if(o.includes(`.`))throw TypeError(`[router.${a}] Route name "${o}" cannot contain dots. Use children array or { parent } option in addRoute() instead.`);if(!t.test(o))throw TypeError(`[router.${a}] Invalid route name "${o}". Name must start with a letter or underscore, followed by letters, numbers, underscores, or hyphens.`)}}function u(e,t){if(!t.includes(`.`))return e.children.get(t);let n=e;for(let e of t.split(`.`))if(n=n.children.get(e),!n)return;return n}function d(e,t,n){if(u(e,t))throw Error(`[router.${n}] Route "${t}" already exists`)}function f(e,t,n){if(e.has(t))throw Error(`[router.${n}] Duplicate route "${t}" in batch`);e.add(t)}function p(e,t,n,r){let i=t===``?e:u(e,t);if(i){for(let e of i.children.values())if(e.path===n)throw Error(`[router.${r}] Path "${n}" is already defined`)}}function m(e,t,n,r){let i=e.get(t);if(i?.has(n))throw Error(`[router.${r}] Path "${n}" is already defined`);i?i.add(n):e.set(t,new Set([n]))}function h(t,n,r,a=``,u,g){o(t,n);let _=t;l(_,n),e.validateRoutePath(_.path,_.name,n,r),s(_,n),c(_,n);let v=_.name,y=a?`${a}.${v}`:v;r&&y&&d(r,y,n),u&&f(u,y,n);let b=_.path,x=a;if(r&&p(r,x,b,n),g&&m(g,x,b,n),_.children!==void 0){if(!Array.isArray(_.children))throw TypeError(`[router.${n}] Route "${v}" children must be an array, got ${i(_.children)}`);for(let e of _.children)h(e,n,r,y,u,g)}}exports.validateRoute=h;
2
+ //# sourceMappingURL=route-batch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route-batch.js","names":[],"sources":["../../../../src/engine/validation/route-batch.ts"],"sourcesContent":["/**\n * Batch route validation utilities.\n *\n * Provides validation for adding routes with cross-batch duplicate detection.\n * Used by router.addRoute() to ensure atomicity - all routes validated before any modification.\n */\n\nimport { validateRoutePath } from \"./routes\";\n\nimport type { RouteDefinition, RouteTree } from \"../types\";\n\n/**\n * Pattern for route name validation.\n * Each route name must start with letter/underscore, followed by alphanumeric/hyphen/underscore.\n * Dots are NOT allowed - use children array or { parent } option in addRoute() instead.\n * Rejects: names starting with numbers/hyphens, names containing dots.\n */\nconst ROUTE_NAME_PATTERN = /^[A-Z_a-z][\\w-]*$/;\n\n/**\n * Checks if string contains at least one non-whitespace character.\n */\nconst HAS_NON_WHITESPACE = /\\S/;\n\n/**\n * Maximum route name length to prevent DoS and performance issues.\n */\nconst MAX_ROUTE_NAME_LENGTH = 10_000;\n\n/**\n * Gets a human-readable description of a value's type.\n * Used for error messages to provide helpful debugging information.\n */\nfunction getTypeDescription(value: unknown): string {\n // Handle null explicitly (typeof null === \"object\")\n if (value === null) {\n return \"null\";\n }\n\n if (typeof value === \"object\") {\n // Read `constructor`/`.name` defensively: an adversarial own `constructor`\n // (null, a string, a number, …) is not a real constructor and must not crash\n // here nor yield a non-string (#903); a THROWING accessor — a `constructor`\n // getter, a function constructor with a throwing `.name` getter, or a Proxy\n // that throws on [[Get]] — must not crash either (#1052). Both fall back to\n // \"object\". (Byte-identical twin of type-guards' getTypeDescription —\n // route-tree has no type-guards dependency, so the hardening is duplicated.)\n try {\n const ctor: unknown = (value as { constructor?: unknown }).constructor;\n\n // Return constructor name for class instances\n if (typeof ctor === \"function\" && ctor.name !== \"Object\") {\n return ctor.name || \"object\"; // empty name (anonymous class) → \"object\"\n }\n } catch {\n // Throwing constructor/.name getter or Proxy [[Get]] → fall through (#1052).\n }\n\n // Plain object\n return \"object\";\n }\n\n // Primitive types (string, number, boolean, undefined, etc.)\n return typeof value;\n}\n\n/**\n * Checks if an object has getters or setters.\n * Used to detect potentially malicious route objects that could mutate during processing.\n *\n * @param obj - Object to check\n * @returns true if object has getters or setters\n */\nfunction hasGettersOrSetters(obj: Record<string, unknown>): boolean {\n for (const key of Object.keys(obj)) {\n const descriptor = Object.getOwnPropertyDescriptor(obj, key);\n\n if (descriptor && (descriptor.get || descriptor.set)) {\n return true;\n }\n }\n\n return false;\n}\n\n/**\n * Validates that route is a plain object without getters/setters.\n * Prevents malicious objects that could mutate during processing.\n *\n * @param route - Route to validate\n * @param methodName - Calling method for error context\n * @throws {TypeError} If route is not a plain object\n */\nfunction validateRouteType(\n route: unknown,\n methodName: string,\n): asserts route is Record<string, unknown> {\n if (!route || typeof route !== \"object\") {\n throw new TypeError(\n `[router.${methodName}] Route must be an object, got ${getTypeDescription(route)}`,\n );\n }\n\n // Check for plain object (prototype must be Object.prototype or null)\n const proto: object | null = Object.getPrototypeOf(route) as object | null;\n\n if (proto !== Object.prototype && proto !== null) {\n throw new TypeError(\n `[router.${methodName}] Route must be a plain object, got ${getTypeDescription(route)}`,\n );\n }\n\n // Check for getters/setters (could cause mutations during processing)\n if (hasGettersOrSetters(route as Record<string, unknown>)) {\n throw new TypeError(\n `[router.${methodName}] Route must not have getters or setters`,\n );\n }\n}\n\n/**\n * Validates that encodeParams is a function if provided.\n *\n * @param route - Route configuration\n * @param methodName - Calling method for error context\n * @throws {TypeError} If encodeParams is not a function\n */\nfunction validateEncodeParams(\n route: Record<string, unknown>,\n methodName: string,\n): void {\n if (\n route.encodeParams !== undefined &&\n typeof route.encodeParams !== \"function\"\n ) {\n throw new TypeError(\n `[router.${methodName}] Route \"${String(route.name)}\" encodeParams must be a function`,\n );\n }\n}\n\n/**\n * Validates that decodeParams is a function if provided.\n *\n * @param route - Route configuration\n * @param methodName - Calling method for error context\n * @throws {TypeError} If decodeParams is not a function\n */\nfunction validateDecodeParams(\n route: Record<string, unknown>,\n methodName: string,\n): void {\n if (\n route.decodeParams !== undefined &&\n typeof route.decodeParams !== \"function\"\n ) {\n throw new TypeError(\n `[router.${methodName}] Route \"${String(route.name)}\" decodeParams must be a function`,\n );\n }\n}\n\n/**\n * Validates that route name is a valid string matching the expected format.\n *\n * Route name rules:\n * - Must be a string\n * - Cannot be empty or whitespace-only\n * - Cannot contain dots (use children array or { parent } option instead)\n * - Must match [a-zA-Z_][a-zA-Z0-9_-]*\n *\n * @param route - Route configuration\n * @param methodName - Calling method for error context\n * @throws {TypeError} If name is missing, not a string, or invalid format\n */\nfunction validateRouteName(\n route: Record<string, unknown>,\n methodName: string,\n): asserts route is Record<string, unknown> & { name: string } {\n if (typeof route.name !== \"string\") {\n throw new TypeError(\n `[router.${methodName}] Route name must be a string, got ${getTypeDescription(route.name)}`,\n );\n }\n\n const name = route.name;\n\n // Empty string is not allowed for addRoute (unlike root node in type-guards)\n if (name === \"\") {\n throw new TypeError(`[router.${methodName}] Route name cannot be empty`);\n }\n\n // Whitespace-only strings are invalid\n if (!HAS_NON_WHITESPACE.test(name)) {\n throw new TypeError(\n `[router.${methodName}] Route name cannot contain only whitespace`,\n );\n }\n\n // Length check for technical safety\n if (name.length > MAX_ROUTE_NAME_LENGTH) {\n throw new TypeError(\n `[router.${methodName}] Route name exceeds maximum length of ${MAX_ROUTE_NAME_LENGTH} characters`,\n );\n }\n\n // System routes bypass pattern validation (e.g., @@router/UNKNOWN_ROUTE)\n if (name.startsWith(\"@@\")) {\n return;\n }\n\n // Dots are not allowed in route names\n if (name.includes(\".\")) {\n throw new TypeError(\n `[router.${methodName}] Route name \"${name}\" cannot contain dots. ` +\n `Use children array or { parent } option in addRoute() instead.`,\n );\n }\n\n // Validate route pattern (ASCII only: letters, numbers, underscores, hyphens)\n if (!ROUTE_NAME_PATTERN.test(name)) {\n throw new TypeError(\n `[router.${methodName}] Invalid route name \"${name}\". ` +\n `Name must start with a letter or underscore, ` +\n `followed by letters, numbers, underscores, or hyphens.`,\n );\n }\n}\n\n/**\n * Finds a node by its fullName in the tree.\n * Even though user-provided route names cannot contain dots,\n * fullName is computed during validation (e.g., \"parent.child\").\n *\n * @param rootNode - Root node to start from\n * @param fullName - Full route name (e.g., \"users.profile\")\n * @returns The resolved node, or undefined if not found\n */\nfunction findNodeByFullName(\n rootNode: RouteTree,\n fullName: string,\n): RouteTree | undefined {\n // Fast path: single-segment names don't need splitting\n // Stryker disable next-line ConditionalExpression,StringLiteral,BlockStatement: equivalent — the fast path is a pure optimization; for a dotless name the general path below yields the identical result (`name.split(\".\")` → `[name]`, a one-iteration `children.get(name)`). (BooleanLiteral stays live: dropping the `!` runs the fast path for a dotted name → `children.get(\"a.b\")` is undefined = killed.)\n if (!fullName.includes(\".\")) {\n return rootNode.children.get(fullName);\n }\n\n let current: RouteTree | undefined = rootNode;\n\n for (const segment of fullName.split(\".\")) {\n current = current.children.get(segment);\n\n if (!current) {\n return undefined;\n }\n }\n\n return current;\n}\n\n/**\n * Checks for duplicate route name in existing tree.\n *\n * @param rootNode - Root node to search in\n * @param fullName - Full route name (dot-notation)\n * @param methodName - Calling method for error context\n * @throws {Error} If route name already exists\n */\nfunction checkTreeNameDuplicate(\n rootNode: RouteTree,\n fullName: string,\n methodName: string,\n): void {\n if (findNodeByFullName(rootNode, fullName)) {\n throw new Error(\n `[router.${methodName}] Route \"${fullName}\" already exists`,\n );\n }\n}\n\n/**\n * Checks for duplicate route name in current batch.\n *\n * @param seenNames - Set of names already seen in batch\n * @param fullName - Full route name to check\n * @param methodName - Calling method for error context\n * @throws {Error} If duplicate name in batch\n */\nfunction checkBatchNameDuplicate(\n seenNames: Set<string>,\n fullName: string,\n methodName: string,\n): void {\n if (seenNames.has(fullName)) {\n throw new Error(\n `[router.${methodName}] Duplicate route \"${fullName}\" in batch`,\n );\n }\n\n seenNames.add(fullName);\n}\n\n/**\n * Checks for duplicate path in existing tree at same parent level.\n *\n * @param rootNode - Root node to search in\n * @param parentName - Parent route name (empty string for root level)\n * @param routePath - Path to check for duplicates\n * @throws {Error} If path already exists at this level\n */\nfunction checkTreePathDuplicate(\n rootNode: RouteTree,\n parentName: string,\n routePath: string,\n methodName: string,\n): void {\n const parentNode =\n parentName === \"\" ? rootNode : findNodeByFullName(rootNode, parentName);\n\n if (!parentNode) {\n return; // Parent doesn't exist, so no duplicate\n }\n\n for (const child of parentNode.children.values()) {\n if (child.path === routePath) {\n throw new Error(\n `[router.${methodName}] Path \"${routePath}\" is already defined`,\n );\n }\n }\n}\n\n/**\n * Checks for duplicate path in current batch at same parent level.\n *\n * @param seenPathsByParent - Map of paths by parent name\n * @param parentName - Parent route name (empty string for root level)\n * @param routePath - Path to check for duplicates\n * @throws {Error} If path already exists at this level in batch\n */\nfunction checkBatchPathDuplicate(\n seenPathsByParent: Map<string, Set<string>>,\n parentName: string,\n routePath: string,\n methodName: string,\n): void {\n const pathsAtLevel = seenPathsByParent.get(parentName);\n\n if (pathsAtLevel?.has(routePath)) {\n throw new Error(\n `[router.${methodName}] Path \"${routePath}\" is already defined`,\n );\n }\n\n if (pathsAtLevel) {\n pathsAtLevel.add(routePath);\n } else {\n seenPathsByParent.set(parentName, new Set([routePath]));\n }\n}\n\n/**\n * Validates route structure for add operations.\n *\n * Performs comprehensive validation including:\n * - Type check (must be object)\n * - Name validation\n * - Path validation\n * - Duplicate name detection (in tree and batch)\n * - Duplicate path detection (in tree and batch)\n * - Recursive children validation\n *\n * @param route - Route to validate\n * @param methodName - Calling method for error context\n * @param rootNode - Optional root node for duplicate checking\n * @param parentName - Parent route name for building full path (used in recursion)\n * @param seenNames - Set of names already seen in this batch\n * @param seenPathsByParent - Map of paths by parent for path duplicate detection\n * @throws {TypeError} If route structure is invalid\n * @throws {Error} If route already exists (duplicate)\n * @throws {Error} If path already defined (duplicate)\n *\n * @example\n * ```typescript\n * const seenNames = new Set<string>();\n * const seenPaths = new Map<string, Set<string>>();\n *\n * // Validate routes before adding\n * for (const route of routes) {\n * validateRoute(route, \"add\", rootNode, \"\", seenNames, seenPaths);\n * }\n * ```\n */\nexport function validateRoute(\n route: unknown,\n methodName: string,\n rootNode?: RouteTree,\n parentName = \"\",\n seenNames?: Set<string>,\n seenPathsByParent?: Map<string, Set<string>>,\n): asserts route is RouteDefinition {\n validateRouteType(route, methodName);\n\n const routeDef = route;\n\n // Validate that name is a non-empty string\n validateRouteName(routeDef, methodName);\n\n // Validate path structure\n validateRoutePath(routeDef.path, routeDef.name, methodName, rootNode);\n\n // Validate optional function properties\n validateEncodeParams(routeDef, methodName);\n validateDecodeParams(routeDef, methodName);\n\n const routeName = routeDef.name;\n const fullName = parentName ? `${parentName}.${routeName}` : routeName;\n\n // Check for duplicate name in existing tree\n if (rootNode && fullName) {\n checkTreeNameDuplicate(rootNode, fullName, methodName);\n }\n\n // Check for duplicate name in current batch\n if (seenNames) {\n checkBatchNameDuplicate(seenNames, fullName, methodName);\n }\n\n const routePath = routeDef.path;\n const pathCheckParent = parentName;\n\n // Check for duplicate path in existing tree\n if (rootNode) {\n checkTreePathDuplicate(rootNode, pathCheckParent, routePath, methodName);\n }\n\n // Check for duplicate path in current batch\n if (seenPathsByParent) {\n checkBatchPathDuplicate(\n seenPathsByParent,\n pathCheckParent,\n routePath,\n methodName,\n );\n }\n\n // Validate children recursively\n if (routeDef.children !== undefined) {\n if (!Array.isArray(routeDef.children)) {\n throw new TypeError(\n `[router.${methodName}] Route \"${routeName}\" children must be an array, got ${getTypeDescription(routeDef.children)}`,\n );\n }\n\n for (const child of routeDef.children) {\n validateRoute(\n child,\n methodName,\n rootNode,\n fullName,\n seenNames,\n seenPathsByParent,\n );\n }\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ const e=require("../path-matcher/parseSegment.js"),t=require("../path-matcher/buildParamMeta.js");function n(e,t){return TypeError(`[router.${e}] ${t}`)}function r(e,t,r,i){let a=new Set;for(let o of e){if(a.has(o))throw n(r,`Invalid path for route "${t}": duplicate parameter name ':${o}' in "${i}" (a param name must be unique within a route — the second binding would overwrite the first)`);a.add(o)}}function i(e,r,i,a,o){let s=new Set(e);for(let e of r){if(t.INVALID_QUERY_NAME_RGX.test(e))throw n(a,`Invalid path for route "${i}": invalid query-param name "${e}" in "${o}" (a query-param name cannot contain '<' or '>' — it would never round-trip; rename the query param)`);if(s.has(e))throw n(a,`Invalid path for route "${i}": "${e}" is declared as both a path param and a query param in "${o}" — buildPath would emit its value twice (rename one)`)}}function a(e){let t=!0,n=!1;for(let r of e){if(r===`/`){t=!0;continue}if(t&&=(n=r===`:`||r===`*`,!1),!n&&r.codePointAt(0)>=128)return!0}return!1}function o(e,t,n){switch(e){case`name-less`:case`trailing-marker`:return`Invalid path for route "${t}": parameter marker (':' or '*') without a name in "${n}"`;case`fused-marker`:return`Invalid path for route "${t}": parameter marker (':' or '*') must begin a segment, but "${n}" fuses one to a static prefix (use a boundary marker like "/a/:b")`}}function s(e,t){return e.code===`optional-removed`?`Invalid path for route "${t}": optional params are not supported — "${e.segment}". Declare two sibling routes instead: "${e.withoutSegment}" and "${e.requiredForm}" (the route hierarchy already expresses optionality)`:`Invalid path for route "${t}": regex constraints are not supported — '<' and '>' are reserved in path segments ("${e.segment}"). Match the segment as a plain string and validate the value in a guard (canActivate) or app code`}function c(c,l,u,d){if(typeof c!=`string`){let e;throw e=c===null?`null`:Array.isArray(c)?`array`:typeof c,n(u,`Route path must be a string, got ${e}`)}if(c===``)return;if(/\s/.test(c))throw n(u,`Invalid path for route "${l}": whitespace not allowed in "${c}"`);if(!/^([/?~]|[^/]+$)/.test(c))throw n(u,`Route "${l}" has invalid path format: "${c}". Path should start with '/', '~', '?' or be a relative segment.`);if(c.includes(`//`))throw n(u,`Invalid path for route "${l}": double slashes not allowed in "${c}"`);let{pathPattern:f,urlParams:p,queryParams:m}=t.buildParamMeta(c);r(p,l,u,c),i(p,m,l,u,c);let h=e.describeRemovedForm(f);if(h!==void 0)throw n(u,s(h,l));let g=e.findSegmentGrammarError(f);if(g!==void 0)throw n(u,o(g,l,c));if(a(f))throw n(u,`Invalid path for route "${l}": non-ASCII static segment in "${c}" — match compares static segments raw and rejects non-ASCII input, so this route would never match. Percent-encode it (e.g. '/caf%C3%A9') or use a param`);let _=d&&d.paramMeta.urlParams.length>0;if(c.startsWith(`~`)&&_)throw n(u,`Absolute path "${c}" cannot be used under parent route with URL parameters`)}exports.validateRoutePath=c;
2
+ //# sourceMappingURL=routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.js","names":["INVALID_QUERY_NAME_RGX","buildParamMeta","describeRemovedForm","findSegmentGrammarError"],"sources":["../../../../src/engine/validation/routes.ts"],"sourcesContent":["import {\n buildParamMeta,\n describeRemovedForm,\n findSegmentGrammarError,\n INVALID_QUERY_NAME_RGX,\n} from \"../path-matcher\";\n\nimport type { RemovedForm, SegmentErrorCode } from \"../path-matcher\";\nimport type { RouteTree } from \"../types\";\n\n/**\n * Creates a TypeError with consistent router error message format.\n *\n * @param methodName - Name of the method that triggered the error\n * @param message - Error message\n * @returns TypeError with formatted message\n * @internal\n */\nfunction createRouterError(methodName: string, message: string): TypeError {\n return new TypeError(`[router.${methodName}] ${message}`);\n}\n\n/**\n * Rejects a param name repeated within one route's own path (`/:id/:id`, a\n * param+splat clash `/:x/*x`, #1151). `buildParamMeta.urlParams` lists every\n * path-binding name — params AND splats — in order, keeping duplicates (`/:x/*x`\n * → `[\"x\", \"x\"]`), so a single pass over it catches both. The trie binds the\n * duplicates at different positions under one name, so match's later capture\n * silently overwrites the earlier and `rewritePathOnMatch` then rewrites the\n * user's URL from the single survivor. The #736 conflict guard only fires on\n * DIFFERENTLY-named params at one position, so this same-name case slips through.\n * path-matcher's `registerTree` backstop additionally catches CROSS-level dups (a\n * parent's param reused by a child), which this per-path gate cannot see.\n * Extracted so `validateRoutePath` stays within the cognitive-complexity budget.\n */\nfunction validateUniqueParamNames(\n urlParams: readonly string[],\n routeName: string,\n methodName: string,\n path: string,\n): void {\n const seen = new Set<string>();\n\n for (const name of urlParams) {\n if (seen.has(name)) {\n throw createRouterError(\n methodName,\n `Invalid path for route \"${routeName}\": duplicate parameter name ':${name}' in \"${path}\" (a param name must be unique within a route — the second binding would overwrite the first)`,\n );\n }\n\n seen.add(name);\n }\n}\n\n/**\n * #1242 §5.1/§5.3: rejects a malformed query-param declaration — a query name\n * carrying `<`/`>` (`/a?fil<ter` — a `<` in a plain query tail; never round-trips),\n * or one that collides with a path-param name (`/a/:tab?tab`, where buildPath emits\n * the value twice). Narrow to `<>`: a `=` in the declaration (`?tab=1`, §5.2) is\n * tolerated today and left as a separate call. (Under M1 a reverse-order typo\n * `/a/:b?<c>` is caught earlier as optional-removed — the `?<` keeps it in the\n * path, §3.3.) path-matcher's `registerTree` backstops both; this gate adds the\n * route-contextual message.\n */\nfunction validateQueryParamDeclarations(\n urlParams: readonly string[],\n queryParams: readonly string[],\n routeName: string,\n methodName: string,\n path: string,\n): void {\n const urlParamSet = new Set(urlParams);\n\n for (const name of queryParams) {\n if (INVALID_QUERY_NAME_RGX.test(name)) {\n throw createRouterError(\n methodName,\n `Invalid path for route \"${routeName}\": invalid query-param name \"${name}\" in \"${path}\" (a query-param name cannot contain '<' or '>' — it would never round-trip; rename the query param)`,\n );\n }\n\n if (urlParamSet.has(name)) {\n throw createRouterError(\n methodName,\n `Invalid path for route \"${routeName}\": \"${name}\" is declared as both a path param and a query param in \"${path}\" — buildPath would emit its value twice (rename one)`,\n );\n }\n }\n}\n\n/**\n * Reports whether a path has a raw non-ASCII code point (≥ U+0080) in a STATIC\n * segment (`/café`, `/меню`, #1154). match rejects any non-ASCII input byte\n * (`#scanPath`) and compares static trie keys raw, so such a route registers but\n * is unmatchable — `buildPath` emits `/café`, which its own `match` rejects. Only\n * static text is flagged: a marker-led segment (`:café`, a non-ASCII param NAME)\n * is skipped. A `for…of` code-point scan tracking segment start. Runs AFTER the\n * removed-form / grammar rejections, so no `<`/`>` (a former constraint) can\n * reach it — the 3-token grammar has no constraint body to skip (M1, #1516).\n */\nfunction hasNonAsciiStatic(path: string): boolean {\n let atSegmentStart = true;\n let segmentIsMarker = false;\n\n for (const char of path) {\n if (char === \"/\") {\n atSegmentStart = true;\n\n continue;\n }\n\n if (atSegmentStart) {\n segmentIsMarker = char === \":\" || char === \"*\";\n atSegmentStart = false;\n }\n\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- non-empty code point from for-of\n if (!segmentIsMarker && char.codePointAt(0)! >= 0x80) {\n return true;\n }\n }\n\n return false;\n}\n\n/**\n * Maps a SURVIVING per-segment grammar error code (from `findSegmentGrammarError`)\n * to the gate's route-contextual message. The removed-form codes (`optional-removed`\n * / `constraint-removed`) are NOT handled here — they carry a richer replacement\n * recipe built by `removedFormMessage` from `describeRemovedForm`.\n */\nfunction gateGrammarMessage(\n code: Exclude<SegmentErrorCode, \"optional-removed\" | \"constraint-removed\">,\n routeName: string,\n path: string,\n): string {\n switch (code) {\n case \"name-less\":\n case \"trailing-marker\": {\n return `Invalid path for route \"${routeName}\": parameter marker (':' or '*') without a name in \"${path}\"`;\n }\n case \"fused-marker\": {\n return `Invalid path for route \"${routeName}\": parameter marker (':' or '*') must begin a segment, but \"${path}\" fuses one to a static prefix (use a boundary marker like \"/a/:b\")`;\n }\n }\n}\n\n/**\n * Builds the route-contextual replacement recipe for a removed form (M1) — the\n * RICH tier (the matcher backstop uses a shorter, path-free recipe). For an\n * optional it names the offending segment and the two concrete sibling paths that\n * replace it (computed from the actual path by `describeRemovedForm`); for a\n * constraint it names the offending segment and points to a guard.\n */\nfunction removedFormMessage(removed: RemovedForm, routeName: string): string {\n if (removed.code === \"optional-removed\") {\n return `Invalid path for route \"${routeName}\": optional params are not supported — \"${removed.segment}\". Declare two sibling routes instead: \"${removed.withoutSegment}\" and \"${removed.requiredForm}\" (the route hierarchy already expresses optionality)`;\n }\n\n return `Invalid path for route \"${routeName}\": regex constraints are not supported — '<' and '>' are reserved in path segments (\"${removed.segment}\"). Match the segment as a plain string and validate the value in a guard (canActivate) or app code`;\n}\n\n/**\n * Validates route path format.\n * Throws a descriptive error if validation fails.\n *\n * Allows:\n * - Empty string (for grouping/root routes)\n * - Absolute paths: /path, ~path\n * - Query strings: ?query\n * - Relative segments: segment\n *\n * Prevents:\n * - Non-string values\n * - Double slashes (//)\n * - Absolute paths (~) under parameterized parent nodes\n *\n * @param path - Route path to validate\n * @param routeName - Name of route (for error messages)\n * @param methodName - Name of calling method for error messages\n * @param parentNode - Optional parent node for context validation\n * @throws {TypeError} If path is invalid\n *\n * @example\n * // Valid paths\n * validateRoutePath(\"\", \"home\", \"add\"); // ok (empty for grouping)\n * validateRoutePath(\"/users\", \"users\", \"add\"); // ok (absolute)\n * validateRoutePath(\"~dashboard\", \"dash\", \"add\"); // ok (absolute, if no parameterized parent)\n * validateRoutePath(\"?tab=1\", \"home\", \"add\"); // ok (query)\n * validateRoutePath(\"profile\", \"users.profile\", \"add\"); // ok (relative)\n *\n * @example\n * // Invalid paths (throws)\n * validateRoutePath(\"/users//list\", \"users.list\", \"add\"); // throws (double slash)\n * validateRoutePath(\"~dash\", \"dash\", \"add\", paramParent); // throws (~ under parameterized parent)\n */\n// A format-validation gate: a flat sequence of INDEPENDENT guard clauses (type,\n// whitespace, format, double-slash, dup-param, query-decl, the M1 removed-form\n// recipe, surviving grammar markers (name-less / fused / trailing), non-ASCII\n// static, absolute-under-param). Each is a simple early throw; extracting them\n// would only scatter one checklist.\n\nexport function validateRoutePath(\n path: unknown,\n routeName: string,\n methodName: string,\n parentNode?: RouteTree,\n): asserts path is string {\n // Type check\n if (typeof path !== \"string\") {\n // Get type description for error message\n let typeDesc: string;\n\n if (path === null) {\n typeDesc = \"null\";\n } else if (Array.isArray(path)) {\n typeDesc = \"array\";\n } else {\n typeDesc = typeof path;\n }\n\n throw createRouterError(\n methodName,\n `Route path must be a string, got ${typeDesc}`,\n );\n }\n\n // Empty path is valid (for grouping/root)\n if (path === \"\") {\n return;\n }\n\n // No whitespace characters (spaces, tabs, newlines)\n if (/\\s/.test(path)) {\n throw createRouterError(\n methodName,\n `Invalid path for route \"${routeName}\": whitespace not allowed in \"${path}\"`,\n );\n }\n\n // Valid path pattern\n if (!/^([/?~]|[^/]+$)/.test(path)) {\n throw createRouterError(\n methodName,\n `Route \"${routeName}\" has invalid path format: \"${path}\". Path should start with '/', '~', '?' or be a relative segment.`,\n );\n }\n\n // No double slashes\n if (path.includes(\"//\")) {\n throw createRouterError(\n methodName,\n `Invalid path for route \"${routeName}\": double slashes not allowed in \"${path}\"`,\n );\n }\n\n // The grammar checks below scan only the URL-path portion: `buildParamMeta`\n // strips the query the same way the trie does, so a `:`/`*` inside a query\n // declaration is not falsely flagged. (`buildParamMeta` is total — the 3-token\n // grammar has no constraint body to compile, so it never throws — M1, #1516.)\n const { pathPattern, urlParams, queryParams } = buildParamMeta(path);\n\n // Duplicate param name within this route's own path (`/:id/:id`, `/:x/*x`, #1151).\n validateUniqueParamNames(urlParams, routeName, methodName, path);\n\n // Malformed query-param declarations (#1242 §5.1/§5.2/§5.3): a query name with\n // `<>`, or one that collides with a path-param name.\n validateQueryParamDeclarations(\n urlParams,\n queryParams,\n routeName,\n methodName,\n path,\n );\n\n // Removed-form (M1) rejection first — a `:x?` optional or a `<re>` constraint —\n // with the RICH route-contextual replacement recipe (the offending segment plus,\n // for an optional, the two computed sibling paths). Returns undefined when the\n // path's first grammar error is a SURVIVING code, so the fall-through below runs.\n const removed = describeRemovedForm(pathPattern);\n\n if (removed !== undefined) {\n throw createRouterError(methodName, removedFormMessage(removed, routeName));\n }\n\n // Surviving per-segment grammar rejections via the canonical `parseSegment`\n // tokenizer: name-less (#858/#863), fused-marker (#1050), trailing-marker\n // (#1324). `findSegmentGrammarError` runs the same split+parse the matcher uses,\n // so the gate cannot drift. (Only removed-form codes reach `removed` above; the\n // first error here is therefore always a surviving code.)\n const grammarError = findSegmentGrammarError(pathPattern);\n\n if (grammarError !== undefined) {\n throw createRouterError(\n methodName,\n gateGrammarMessage(\n grammarError as Exclude<\n SegmentErrorCode,\n \"optional-removed\" | \"constraint-removed\"\n >,\n routeName,\n path,\n ),\n );\n }\n\n // Raw non-ASCII in a STATIC segment (`/café`, `/меню`, #1154). match rejects\n // non-ASCII input and compares static keys raw, so the route registers but never\n // matches. Reject with the percent-encode workaround (path-matcher backstops at\n // `registerTree`); a non-ASCII param NAME or constraint body is unaffected.\n if (hasNonAsciiStatic(pathPattern)) {\n throw createRouterError(\n methodName,\n `Invalid path for route \"${routeName}\": non-ASCII static segment in \"${path}\" — match compares static segments raw and rejects non-ASCII input, so this route would never match. Percent-encode it (e.g. '/caf%C3%A9') or use a param`,\n );\n }\n\n // Absolute paths under parameterized parents\n // Check if parent has URL parameters via paramTypeMap\n const hasUrlParams = parentNode && parentNode.paramMeta.urlParams.length > 0;\n\n if (path.startsWith(\"~\") && hasUrlParams) {\n throw createRouterError(\n methodName,\n `Absolute path \"${path}\" cannot be used under parent route with URL parameters`,\n );\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ const e={maxListeners:0,warnListeners:0};var t=class{#e=new Map;#t=new Set;#n=null;#r=e;#i;#a;constructor(e){e?.limits&&(this.#r=e.limits),this.#i=e?.onListenerError??null,this.#a=e?.onListenerWarn??null}static validateCallback(e,t){if(typeof e!=`function`)throw TypeError(`Expected callback to be a function for event ${t}`)}setLimits(e){this.#r=e}on(e,t){let n=this.#e.get(e),r=n?.size??0;if(n?.has(t))throw Error(`Duplicate listener for "${e}"`);let{maxListeners:i,warnListeners:a}=this.#r;if(i!==0&&r>=i)throw Error(`Listener limit (${i}) reached for "${e}"`);a!==0&&r===a&&this.#a!==null&&(this.#n??=new Set,this.#n.has(e)||(this.#a(e,a),this.#n.add(e)));let o=n;return o===void 0&&(o=new Set,this.#e.set(e,o)),o.add(t),()=>{this.off(e,t)}}off(e,t){let n=this.#e.get(e);n&&(n.delete(t),n.size===0&&(this.#e.delete(e),this.#n?.delete(e)))}emit(e,t,n,r,i){let a=this.#e.get(e);if(!a||a.size===0||this.#t.has(e))return;let o=arguments.length-1;this.#t.add(e);try{if(a.size===1){let[s]=a;this.#o(e,s,o,t,n,r,i)}else{let s=[...a];for(let a of s)this.#o(e,a,o,t,n,r,i)}}finally{this.#t.delete(e)}}clearAll(){this.#e.clear(),this.#n=null}listenerCount(e){return this.#e.get(e)?.size??0}isDispatching(e){return this.#t.has(e)}#o(e,t,n,r,i,a,o){try{let s=this.#s(t,n,r,i,a,o);s!=null&&typeof s.then==`function`&&Promise.resolve(s).catch(t=>{this.#i?.(e,t)})}catch(t){this.#i?.(e,t)}}#s(e,t,n,r,i,a){switch(t){case 0:return e();case 1:return e(n);case 2:return e(n,r);case 3:return e(n,r,i);default:return e(n,r,i,a)}}};exports.EventEmitter=t;
2
+ //# sourceMappingURL=EventEmitter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EventEmitter.js","names":["#callbacks","#dispatching","#onListenerError","#onListenerWarn","#limits","#warnedEvents","#invokeIsolated","#callListener"],"sources":["../../../../src/foundation/event-emitter/EventEmitter.ts"],"sourcesContent":["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"],"mappings":"AAMA,MAAM,EAAqC,CACzC,aAAc,EACd,cAAe,CACjB,EAWA,IAAa,EAAb,KAAuE,CACrE,GAAsB,IAAI,IAK1B,GAAwB,IAAI,IAC5B,GAAoC,KACpC,GAA8B,EAC9B,GAEA,GAEA,YAAY,EAA+B,CACrC,GAAS,SACX,KAAKI,GAAU,EAAQ,QAGzB,KAAKF,GAAmB,GAAS,iBAAmB,KACpD,KAAKC,GAAkB,GAAS,gBAAkB,IACpD,CAKA,OAAO,iBACL,EACA,EAEwB,CACxB,GAAI,OAAO,GAAO,WAChB,MAAU,UACR,gDAAgD,GAClD,CAEJ,CAKA,UAAU,EAAkC,CAC1C,KAAKC,GAAU,CACjB,CAaA,GACE,EACA,EACa,CACb,IAAM,EAAW,KAAKJ,GAAW,IAAI,CAAS,EACxC,EAAO,GAAU,MAAQ,EAE/B,GAAI,GAAU,IAAI,CAAE,EAClB,MAAU,MAAM,2BAA2B,EAAU,EAAE,EAGzD,GAAM,CAAE,eAAc,iBAAkB,KAAKI,GAI7C,GAAI,IAAiB,GAAK,GAAQ,EAChC,MAAU,MACR,mBAAmB,EAAa,iBAAiB,EAAU,EAC7D,EAUA,IAAkB,GAClB,IAAS,GACT,KAAKD,KAAoB,OAEzB,KAAKE,KAAkB,IAAI,IAEtB,KAAKA,GAAc,IAAI,CAAS,IACnC,KAAKF,GAAgB,EAAW,CAAa,EAC7C,KAAKE,GAAc,IAAI,CAAS,IAMpC,IAAI,EAAM,EASV,OAPI,IAAQ,IAAA,KACV,EAAM,IAAI,IACV,KAAKL,GAAW,IAAI,EAAW,CAAG,GAGpC,EAAI,IAAI,CAAE,MAEG,CACX,KAAK,IAAI,EAAW,CAAE,CACxB,CACF,CAKA,IACE,EACA,EACM,CACN,IAAM,EAAM,KAAKA,GAAW,IAAI,CAAS,EAEpC,IAIL,EAAI,OAAO,CAAE,EAET,EAAI,OAAS,IAIf,KAAKA,GAAW,OAAO,CAAS,EAChC,KAAKK,IAAe,OAAO,CAAS,GAExC,CAgBA,KACE,EACA,EACA,EACA,EACA,EACM,CACN,IAAM,EAAM,KAAKL,GAAW,IAAI,CAAS,EAOzC,GALI,CAAC,GAAO,EAAI,OAAS,GAKrB,KAAKC,GAAa,IAAI,CAAS,EACjC,OAIF,IAAM,EAAO,UAAU,OAAS,EAEhC,KAAKA,GAAa,IAAI,CAAS,EAE/B,GAAI,CAEF,GAAI,EAAI,OAAS,EAAG,CAClB,GAAM,CAAC,GAAM,EAEb,KAAKK,GAAgB,EAAW,EAAI,EAAM,EAAM,EAAM,EAAM,CAAI,CAClE,KAAO,CACL,IAAM,EAAY,CAAC,GAAG,CAAG,EAEzB,IAAK,IAAM,KAAM,EACf,KAAKA,GAAgB,EAAW,EAAI,EAAM,EAAM,EAAM,EAAM,CAAI,CAEpE,CACF,QAAU,CACR,KAAKL,GAAa,OAAO,CAAS,CACpC,CACF,CAaA,UAAiB,CACf,KAAKD,GAAW,MAAM,EACtB,KAAKK,GAAgB,IACvB,CAKA,cAAc,EAA6C,CACzD,OAAO,KAAKL,GAAW,IAAI,CAAS,CAAC,EAAE,MAAQ,CACjD,CAQA,cAAc,EAA8C,CAC1D,OAAO,KAAKC,GAAa,IAAI,CAAS,CACxC,CAUA,GACE,EACA,EACA,EACA,EACA,EACA,EACA,EACM,CACN,GAAI,CACF,IAAM,EAAS,KAAKM,GAAc,EAAI,EAAM,EAAM,EAAM,EAAM,CAAI,EAUhE,GAAW,MAEX,OAAQ,EAAgC,MAAS,YAEjD,QAAQ,QAAQ,CAA8B,CAAC,CAAC,MAC7C,GAAmB,CAClB,KAAKL,KAAmB,EAAW,CAAK,CAC1C,CACF,CAEJ,OAAS,EAAO,CACd,KAAKA,KAAmB,EAAW,CAAK,CAC1C,CACF,CAEA,GACE,EACA,EACA,EACA,EACA,EACA,EACS,CACT,OAAQ,EAAR,CACE,IAAK,GACH,OAAQ,EAAqB,EAE/B,IAAK,GACH,OAAQ,EAA+B,CAAI,EAE7C,IAAK,GACH,OAAQ,EAA2C,EAAM,CAAI,EAE/D,IAAK,GACH,OAAQ,EACN,EACA,EACA,CACF,EAEF,QACE,OACE,EACA,EAAM,EAAM,EAAM,CAAI,CAE5B,CACF,CAIF"}
@@ -0,0 +1,2 @@
1
+ function e(e,t,n){let r=e[t];if(r===void 0)throw Error(`[FSM.${n}] state "${t}" is not declared in config.transitions`);return r}var t=class{#e;#t;#n=0;#r=null;#i;#a;#o=[];constructor(t){this.#e=t.initial,this.#i=t.context,this.#a=t.transitions,this.#t=e(t.transitions,t.initial,`constructor`);for(let n of Object.keys(t.transitions)){let r=t.transitions[n];for(let n of Object.keys(r)){let i=r[n];i!==void 0&&e(t.transitions,i,`constructor`)}}}send(e,...t){let n=this.#t[e];if(n===void 0)return this.#e;let r=this.#e;this.#e=n,this.#t=this.#a[n];let i=t[0];if(this.#r!==null){let t=this.#r.get(r)?.get(e);t!==void 0&&t(i)}if(this.#n>0){let t={from:r,to:n,event:e,payload:i};for(let e of this.#o)e!==null&&e(t)}return this.#e}canSend(e){return this.#t[e]!==void 0}getState(){return this.#e}getContext(){return this.#i}on(t,n,r){e(this.#a,t,`on`),this.#r??=new Map;let i=this.#r.get(t);i||(i=new Map,this.#r.set(t,i));let a=r;return i.set(n,a),()=>{let e=this.#r?.get(t);e?.get(n)===a&&e.delete(n)}}onTransition(e){let t=this.#o.indexOf(null),n;t===-1?(n=this.#o.length,this.#o.push(e)):(this.#o[t]=e,n=t),this.#n++;let r=!0;return()=>{r&&(r=!1,this.#o[n]=null,this.#n--)}}};exports.FSM=t;
2
+ //# sourceMappingURL=fsm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fsm.js","names":["#context","#transitions","#listeners","#state","#currentTransitions","#actions","#listenerCount"],"sources":["../../../../src/foundation/fsm/fsm.ts"],"sourcesContent":["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"],"mappings":"AASA,SAAS,EACP,EACA,EACA,EACmC,CACnC,IAAM,EAAmB,EAAY,GAGrC,GAAI,IAAqB,IAAA,GACvB,MAAU,MACR,QAAQ,EAAM,WAAW,EAAM,wCACjC,EAGF,OAAO,CACT,CAYA,IAAa,EAAb,KAKE,CACA,GACA,GACA,GAAiB,EACjB,GACE,KACF,GACA,GACA,GAIc,CAAC,EAEf,YAAY,EAA+C,CACzD,KAAKG,GAAS,EAAO,QACrB,KAAKH,GAAW,EAAO,QACvB,KAAKC,GAAe,EAAO,YAC3B,KAAKG,GAAsB,EACzB,EAAO,YACP,EAAO,QACP,aACF,EAYA,IAAK,IAAM,KAAS,OAAO,KAAK,EAAO,WAAW,EAAG,CACnD,IAAM,EAAmB,EAAO,YAAY,GAE5C,IAAK,IAAM,KAAS,OAAO,KAAK,CAAgB,EAAG,CACjD,IAAM,EAAS,EAAiB,GAE5B,IAAW,IAAA,IACb,EAAgB,EAAO,YAAa,EAAQ,aAAa,CAE7D,CACF,CACF,CAEA,KACE,EACA,GAAG,EACM,CACT,IAAM,EAAY,KAAKA,GAAoB,GAE3C,GAAI,IAAc,IAAA,GAChB,OAAO,KAAKD,GAGd,IAAM,EAAO,KAAKA,GAElB,KAAKA,GAAS,EACd,KAAKC,GAAsB,KAAKH,GAAa,GAE7C,IAAM,EAAU,EAAK,GAErB,GAAI,KAAKI,KAAa,KAAM,CAC1B,IAAM,EAAS,KAAKA,GAAS,IAAI,CAAI,CAAC,EAAE,IAAI,CAAK,EAE7C,IAAW,IAAA,IACb,EAAO,CAAO,CAElB,CAGA,GAAI,KAAKC,GAAiB,EAAG,CAK3B,IAAM,EAAO,CACX,OACA,GAAI,EACJ,QACA,SACF,EAEA,IAAK,IAAM,KAAY,KAAKJ,GACtB,IAAa,MACf,EAAS,CAAI,CAGnB,CAEA,OAAO,KAAKC,EACd,CAEA,QAAQ,EAAyB,CAC/B,OAAO,KAAKC,GAAoB,KAAW,IAAA,EAC7C,CAEA,UAAoB,CAClB,OAAO,KAAKD,EACd,CAEA,YAAuB,CACrB,OAAO,KAAKH,EACd,CAEA,GACE,EACA,EACA,EAGY,CACZ,EAAgB,KAAKC,GAAc,EAAM,IAAI,EAE7C,KAAKI,KAAa,IAAI,IAEtB,IAAI,EAAe,KAAKA,GAAS,IAAI,CAAI,EAEpC,IACH,EAAe,IAAI,IACnB,KAAKA,GAAS,IAAI,EAAM,CAAY,GAGtC,IAAM,EAAiB,EAIvB,OAFA,EAAa,IAAI,EAAO,CAAc,MAEzB,CAEX,IAAM,EAAW,KAAKA,IAAU,IAAI,CAAI,EAEpC,GAAU,IAAI,CAAK,IAAM,GAC3B,EAAS,OAAO,CAAK,CAEzB,CACF,CAEA,aACE,EACY,CACZ,IAAM,EAAY,KAAKH,GAAW,QAAQ,IAAI,EAC1C,EAEA,IAAc,IAChB,EAAQ,KAAKA,GAAW,OACxB,KAAKA,GAAW,KAAK,CAAQ,IAE7B,KAAKA,GAAW,GAAa,EAC7B,EAAQ,GAGV,KAAKI,KACL,IAAI,EAAa,GAEjB,UAAa,CACN,IAIL,EAAa,GACb,KAAKJ,GAAW,GAAS,KAEzB,KAAKI,KACP,CACF,CACF"}
@@ -0,0 +1,2 @@
1
+ const e=require("./constants.js");var t=class{#e={level:`all`,callbackIgnoresLevel:!1};#t=0;#n=!1;constructor(e){e&&this.configure(e)}configure(t){let n=t.level;if(n!==void 0){if(!Object.hasOwn(e.LEVEL_CONFIGS,n))throw Error(`Invalid log level: "${n}". Valid levels are: ${Object.keys(e.LEVEL_CONFIGS).join(`, `)}`);this.#e.level=n,this.#t=e.LEVEL_CONFIGS[n]}Object.hasOwn(t,`callback`)&&(this.#e.callback=t.callback);let r=t.callbackIgnoresLevel;r!==void 0&&(this.#e.callbackIgnoresLevel=r)}getConfig(){return{level:this.#e.level,callback:this.#e.callback,callbackIgnoresLevel:this.#e.callbackIgnoresLevel}}log(e,t,...n){this.#r(`log`,e,t,n)}warn(e,t,...n){this.#r(`warn`,e,t,n)}error(e,t,...n){this.#r(`error`,e,t,n)}#r(t,n,r,i){if(this.#e.level===`none`&&!this.#e.callbackIgnoresLevel)return;let a=e.LOG_LEVELS[t]<this.#t;a||this.#i(t,n,r,i),this.#a(t,n,r,a,i)}#i(e,t,n,r){if(typeof console<`u`&&typeof console[e]==`function`){let i=t?`[${t}] ${n}`:n;console[e](i,...r)}}#a(e,t,n,r,i){if(!(!this.#e.callback||!this.#e.callbackIgnoresLevel&&r)&&!this.#n){this.#n=!0;try{let r=this.#e.callback(e,t,n,...i);r!=null&&typeof r.then==`function`&&Promise.resolve(r).catch(e=>{this.#o(`[Logger] Error in async callback:`,e)})}catch(e){this.#o(`[Logger] Error in callback:`,e)}finally{this.#n=!1}}}#o(e,t){typeof console<`u`&&typeof console.error==`function`&&console.error(e,t)}};exports.RouterLogger=t;
2
+ //# sourceMappingURL=RouterLogger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RouterLogger.js","names":["#config","LEVEL_CONFIGS","#currentThreshold","#writeLog","LOG_LEVELS","#writeToConsole","#invokeCallback","#inCallback","#reportError"],"sources":["../../../../src/foundation/logger/RouterLogger.ts"],"sourcesContent":["// 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"],"mappings":"kCA2CA,IAAa,EAAb,KAA0B,CAExB,GAAyC,CACvC,MAAO,MACP,qBAAsB,EACxB,EAGA,GAAoB,EAQpB,GAAc,GAWd,YAAY,EAAgC,CACtC,GACF,KAAK,UAAU,CAAM,CAEzB,CAwBA,UAAU,EAAqC,CAK7C,IAAM,EAAQ,EAAO,MAErB,GAAI,IAAU,IAAA,GAAW,CAEvB,GAAI,CAAC,OAAO,OAAOC,EAAAA,cAAe,CAAK,EACrC,MAAU,MACR,uBAAuB,EAAM,uBAAuB,OAAO,KAAKA,EAAAA,aAAa,CAAC,CAAC,KAAK,IAAI,GAC1F,EAGF,KAAKD,GAAQ,MAAQ,EACrB,KAAKE,GAAoBD,EAAAA,cAAc,EACzC,CACI,OAAO,OAAO,EAAQ,UAAU,IAClC,KAAKD,GAAQ,SAAW,EAAO,UAGjC,IAAM,EAAuB,EAAO,qBAEhC,IAAyB,IAAA,KAC3B,KAAKA,GAAQ,qBAAuB,EAExC,CAcA,WAA0B,CACxB,MAAO,CACL,MAAO,KAAKA,GAAQ,MACpB,SAAU,KAAKA,GAAQ,SACvB,qBAAsB,KAAKA,GAAQ,oBACrC,CACF,CAiBA,IAAI,EAAiB,EAAiB,GAAG,EAAuB,CAC9D,KAAKG,GAAU,MAAO,EAAS,EAAS,CAAI,CAC9C,CAkBA,KAAK,EAAiB,EAAiB,GAAG,EAAuB,CAC/D,KAAKA,GAAU,OAAQ,EAAS,EAAS,CAAI,CAC/C,CAkBA,MAAM,EAAiB,EAAiB,GAAG,EAAuB,CAChE,KAAKA,GAAU,QAAS,EAAS,EAAS,CAAI,CAChD,CAiBA,GACE,EACA,EACA,EACA,EACM,CAIN,GAAI,KAAKH,GAAQ,QAAU,QAAU,CAAC,KAAKA,GAAQ,qBACjD,OASF,IAAM,EAJoBI,EAAAA,WAAW,GAIS,KAAKF,GAG9C,GACH,KAAKG,GAAgB,EAAO,EAAS,EAAS,CAAI,EAIpD,KAAKC,GAAgB,EAAO,EAAS,EAAS,EAAmB,CAAI,CACvE,CAiBA,GACE,EACA,EACA,EACA,EACM,CAGN,GACE,OAAO,QAAY,KACnB,OAAO,QAAQ,IAAW,WAC1B,CAIA,IAAM,EAAmB,EAAU,IAAI,EAAQ,IAAI,IAAY,EAE/D,QAAQ,EAAM,CAAC,EAAkB,GAAG,CAAI,CAC1C,CACF,CAsBA,GACE,EACA,EACA,EACA,EACA,EACM,CAGJ,MAAC,KAAKN,GAAQ,UACb,CAAC,KAAKA,GAAQ,sBAAwB,IASrC,MAAKO,GAMT,MAAKA,GAAc,GACnB,GAAI,CASF,IAAM,EAAkB,KAAKP,GAAQ,SACnC,EACA,EACA,EACA,GAAG,CACL,EAGE,GAAW,MAEX,OAAQ,EAAgC,MAAS,YAEjD,QAAQ,QAAQ,CAA8B,CAAC,CAAC,MAC7C,GAAmB,CAClB,KAAKQ,GAAa,oCAAqC,CAAK,CAC9D,CACF,CAEJ,OAAS,EAAO,CAEd,KAAKA,GAAa,8BAA+B,CAAK,CACxD,QAAU,CACR,KAAKD,GAAc,EACrB,CAjCmB,CAkCrB,CAKA,GAAa,EAAiB,EAAsB,CAC9C,OAAO,QAAY,KAAe,OAAO,QAAQ,OAAU,YAC7D,QAAQ,MAAM,EAAS,CAAK,CAEhC,CACF"}
@@ -0,0 +1,2 @@
1
+ const e=Object.freeze({log:0,warn:1,error:2}),t=Object.freeze({all:0,"warn-error":1,"error-only":2,none:3});exports.LEVEL_CONFIGS=t,exports.LOG_LEVELS=e;
2
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.js","names":[],"sources":["../../../../src/foundation/logger/constants.ts"],"sourcesContent":["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"],"mappings":"AAyBA,MAAa,EAAuC,OAAO,OAAO,CAChE,IAAK,EACL,KAAM,EACN,MAAO,CACT,CAAC,EAyCY,EAAgD,OAAO,OAAO,CACzE,IAAK,EACL,aAAc,EACd,aAAc,EACd,KAAM,CACR,CAAC"}
@@ -0,0 +1,6 @@
1
+ import { DefaultDependencies, Navigator, Router } from "./types/router.js";
2
+ //#region src/getNavigator.d.ts
3
+ declare const getNavigator: <Dependencies extends DefaultDependencies = DefaultDependencies>(router: Router<Dependencies>) => Navigator;
4
+ //#endregion
5
+ export { getNavigator };
6
+ //# sourceMappingURL=getNavigator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getNavigator.d.ts","names":[],"sources":["../../src/getNavigator.ts"],"mappings":";;cAIa,eACX,qBAAqB,sBAAsB,qBAE3C,QAAQ,OAAO,kBACd"}
@@ -0,0 +1,2 @@
1
+ const e=new WeakMap,t=t=>{let n=e.get(t);return n||(n=Object.freeze({navigate:t.navigate,getState:t.getState,isActiveRoute:t.isActiveRoute,canNavigateTo:t.canNavigateTo,subscribe:t.subscribe,subscribeLeave:t.subscribeLeave,isLeaveApproved:t.isLeaveApproved}),e.set(t,n)),n};exports.getNavigator=t;
2
+ //# sourceMappingURL=getNavigator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getNavigator.js","names":[],"sources":["../../src/getNavigator.ts"],"sourcesContent":["import type { Navigator, DefaultDependencies, Router } from \"./types\";\n\nconst cache = new WeakMap<Router, Navigator>();\n\nexport const getNavigator = <\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n router: Router<Dependencies>,\n): Navigator => {\n let nav = cache.get(router);\n\n if (!nav) {\n nav = Object.freeze({\n navigate: router.navigate,\n getState: router.getState,\n isActiveRoute: router.isActiveRoute,\n canNavigateTo: router.canNavigateTo,\n subscribe: router.subscribe,\n subscribeLeave: router.subscribeLeave,\n isLeaveApproved: router.isLeaveApproved,\n } as Navigator);\n cache.set(router, nav);\n }\n\n return nav;\n};\n"],"mappings":"AAEA,MAAM,EAAQ,IAAI,QAEL,EAGX,GACc,CACd,IAAI,EAAM,EAAM,IAAI,CAAM,EAe1B,OAbK,IACH,EAAM,OAAO,OAAO,CAClB,SAAU,EAAO,SACjB,SAAU,EAAO,SACjB,cAAe,EAAO,cACtB,cAAe,EAAO,cACtB,UAAW,EAAO,UAClB,eAAgB,EAAO,eACvB,gBAAiB,EAAO,eAC1B,CAAc,EACd,EAAM,IAAI,EAAQ,CAAG,GAGhB,CACT"}
@@ -0,0 +1,2 @@
1
+ function e(e){if(!e||typeof e!=`object`||e.constructor!==Object)throw TypeError(`dependencies must be a plain object`);for(let t in e)if(Object.getOwnPropertyDescriptor(e,t)?.get)throw TypeError(`dependencies cannot contain getters: "${t}"`)}function t(e,n){for(let r of e){let e=r;if(typeof e!=`object`||!e||Array.isArray(e))throw TypeError(`route must be a non-array object`);n?.routes.guardRouteCallbacks(r),n?.routes.guardNoAsyncCallbacks(r);let i=r.children;i&&t(i,n)}}const n=new Set([`all`,`warn-error`,`error-only`,`none`]);function r(e){return typeof e==`string`&&n.has(e)}function i(e){return typeof e==`string`?`"${e}"`:typeof e==`object`?JSON.stringify(e):String(e)}function a(e){if(typeof e!=`object`)throw TypeError(`Logger config must be an object`);let t=e;for(let e of Object.keys(t))if(e!==`level`&&e!==`callback`&&e!==`callbackIgnoresLevel`)throw TypeError(`Unknown logger config property: "${e}"`);if(`level`in t&&t.level!==void 0&&!r(t.level))throw TypeError(`Invalid logger level: ${i(t.level)}. Expected: "all" | "warn-error" | "error-only" | "none"`);if(`callback`in t&&t.callback!==void 0&&typeof t.callback!=`function`)throw TypeError(`Logger callback must be a function, got ${typeof t.callback}`);if(`callbackIgnoresLevel`in t&&t.callbackIgnoresLevel!==void 0&&typeof t.callbackIgnoresLevel!=`boolean`)throw TypeError(`Logger callbackIgnoresLevel must be a boolean, got ${typeof t.callbackIgnoresLevel}`)}exports.assertLoggerConfig=a,exports.guardDependencies=e,exports.guardRouteStructure=t;
2
+ //# sourceMappingURL=guards.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guards.js","names":[],"sources":["../../src/guards.ts"],"sourcesContent":["// 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"],"mappings":"AASA,SAAgB,EAAkB,EAAqB,CACrD,GACE,CAAC,GACD,OAAO,GAAS,UACf,EAAkC,cAAgB,OAEnD,MAAU,UAAU,qCAAqC,EAE3D,IAAK,IAAM,KAAO,EAChB,GAAI,OAAO,yBAAyB,EAAM,CAAG,CAAC,EAAE,IAC9C,MAAU,UAAU,yCAAyC,EAAI,EAAE,CAGzE,CAGA,SAAgB,EACd,EACA,EACM,CAEN,IAAK,IAAM,KAAS,EAAQ,CAC1B,IAAM,EAAsB,EAE5B,GAEE,OAAO,GAAe,WADtB,GAEA,MAAM,QAAQ,CAAU,EAExB,MAAU,UAAU,kCAAkC,EAGxD,GAAW,OAAO,oBAAoB,CAAc,EACpD,GAAW,OAAO,sBAAsB,CAAc,EACtD,IAAM,EAAY,EAAgB,SAE9B,GACF,EAAoB,EAAU,CAAS,CAE3C,CACF,CAMA,MAAM,EAAmB,IAAI,IAAY,CACvC,MACA,aACA,aACA,MACF,CAAC,EAED,SAAS,EAAa,EAAyC,CAC7D,OAAO,OAAO,GAAU,UAAY,EAAiB,IAAI,CAAK,CAChE,CAEA,SAAS,EAAY,EAAwB,CAS3C,OARI,OAAO,GAAU,SACZ,IAAI,EAAM,GAEf,OAAO,GAAU,SACZ,KAAK,UAAU,CAAK,EAItB,OAAO,CAAK,CACrB,CAEA,SAAgB,EACd,EACgC,CAChC,GAAI,OAAO,GAAW,SACpB,MAAU,UAAU,iCAAiC,EAOvD,IAAM,EAAM,EAGZ,IAAK,IAAM,KAAO,OAAO,KAAK,CAAG,EAC/B,GACE,IAAQ,SACR,IAAQ,YACR,IAAQ,uBAER,MAAU,UAAU,oCAAoC,EAAI,EAAE,EAKlE,GAAI,UAAW,GAAO,EAAI,QAAU,IAAA,IAAa,CAAC,EAAa,EAAI,KAAK,EACtE,MAAU,UACR,yBAAyB,EAAY,EAAI,KAAK,EAAE,yDAClD,EAIF,GACE,aAAc,GACd,EAAI,WAAa,IAAA,IACjB,OAAO,EAAI,UAAa,WAExB,MAAU,UACR,2CAA2C,OAAO,EAAI,UACxD,EAIF,GACE,yBAA0B,GAC1B,EAAI,uBAAyB,IAAA,IAC7B,OAAO,EAAI,sBAAyB,UAEpC,MAAU,UACR,sDAAsD,OAAO,EAAI,sBACnE,CAEJ"}
@@ -0,0 +1,2 @@
1
+ const e=require("./constants.js");function t(e){return Object.freeze(e)}function n(t={}){return{...e.DEFAULT_LIMITS,...t}}function r(t){if(t===void 0)return t;let n;for(let e in t){if(!Object.hasOwn(t,e))continue;let r=t[e];r!==void 0&&(n??={},n[e]=r)}return n??e.EMPTY_PARAMS}exports.createLimits=n,exports.freezeStateInPlace=t,exports.normalizeParams=r;
2
+ //# sourceMappingURL=helpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers.js","names":["DEFAULT_LIMITS","EMPTY_PARAMS"],"sources":["../../src/helpers.ts"],"sourcesContent":["// 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"],"mappings":"kCA4BA,SAAgB,EAAoC,EAAa,CAK/D,OAAO,OAAO,OAAO,CAAK,CAC5B,CAMA,SAAgB,EAAa,EAAoC,CAAC,EAAW,CAC3E,MAAO,CAAE,GAAGA,EAAAA,eAAgB,GAAG,CAAW,CAC5C,CAoCA,SAAgB,EACd,EACoB,CACpB,GAAI,IAAW,IAAA,GACb,OAAO,EAGT,IAAI,EAEJ,IAAK,IAAM,KAAO,EAAQ,CACxB,GAAI,CAAC,OAAO,OAAO,EAAQ,CAAG,EAC5B,SAGF,IAAM,EAAQ,EAAO,GAEjB,IAAU,IAAA,KAEZ,IAAe,CAAC,EAChB,EAAW,GAAO,EAEtB,CAIA,OAAO,GAAcC,EAAAA,YACvB"}