@real-router/core 0.79.0 → 0.80.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (338) hide show
  1. package/dist/cjs/Router-DGUed2F4.js +2 -0
  2. package/dist/cjs/{Router-BRuC-daj.js.map → Router-DGUed2F4.js.map} +1 -1
  3. package/dist/cjs/{Router-BG5yNYUG.d.ts → Router.d.ts} +5 -4
  4. package/dist/cjs/Router.d.ts.map +1 -0
  5. package/dist/cjs/Router.js +2 -0
  6. package/dist/cjs/Router.js.map +1 -0
  7. package/dist/cjs/RouterError.d.ts +182 -0
  8. package/dist/cjs/RouterError.d.ts.map +1 -0
  9. package/dist/cjs/RouterError.js +2 -0
  10. package/dist/cjs/RouterError.js.map +1 -0
  11. package/dist/cjs/api/cloneRouter.d.ts +86 -0
  12. package/dist/cjs/api/cloneRouter.d.ts.map +1 -0
  13. package/dist/cjs/api/cloneRouter.js +2 -0
  14. package/dist/cjs/api/cloneRouter.js.map +1 -0
  15. package/dist/cjs/api/getDependenciesApi.d.ts +7 -0
  16. package/dist/cjs/api/getDependenciesApi.d.ts.map +1 -0
  17. package/dist/cjs/api/getDependenciesApi.js +2 -0
  18. package/dist/cjs/api/getDependenciesApi.js.map +1 -0
  19. package/dist/cjs/api/getLifecycleApi.d.ts +7 -0
  20. package/dist/cjs/api/getLifecycleApi.d.ts.map +1 -0
  21. package/dist/cjs/api/getLifecycleApi.js +2 -0
  22. package/dist/cjs/api/getLifecycleApi.js.map +1 -0
  23. package/dist/cjs/api/getPluginApi.d.ts +7 -0
  24. package/dist/cjs/api/getPluginApi.d.ts.map +1 -0
  25. package/dist/cjs/api/getPluginApi.js +2 -0
  26. package/dist/cjs/api/getPluginApi.js.map +1 -0
  27. package/dist/cjs/api/getRoutesApi.d.ts +7 -0
  28. package/dist/cjs/api/getRoutesApi.d.ts.map +1 -0
  29. package/dist/cjs/api/getRoutesApi.js +2 -0
  30. package/dist/cjs/api/getRoutesApi.js.map +1 -0
  31. package/dist/cjs/api/helpers.js +2 -0
  32. package/dist/cjs/api/helpers.js.map +1 -0
  33. package/dist/cjs/api/types.d.ts +9 -0
  34. package/dist/cjs/api/types.d.ts.map +1 -0
  35. package/dist/cjs/api.d.ts +8 -105
  36. package/dist/cjs/api.js +1 -2
  37. package/dist/cjs/api.js.map +1 -1
  38. package/dist/cjs/buildParamMeta-DniluAIV.js.map +1 -1
  39. package/dist/cjs/constants.d.ts +25 -0
  40. package/dist/cjs/constants.d.ts.map +1 -0
  41. package/dist/cjs/constants.js +2 -0
  42. package/dist/cjs/constants.js.map +1 -0
  43. package/dist/cjs/createRouter.d.ts +23 -0
  44. package/dist/cjs/createRouter.d.ts.map +1 -0
  45. package/dist/cjs/createRouter.js +2 -0
  46. package/dist/cjs/createRouter.js.map +1 -0
  47. package/dist/cjs/engine/builder/buildTree.js +2 -0
  48. package/dist/cjs/engine/builder/buildTree.js.map +1 -0
  49. package/dist/cjs/engine/builder/computeCaches.js +2 -0
  50. package/dist/cjs/engine/builder/computeCaches.js.map +1 -0
  51. package/dist/cjs/engine/builder/createRouteTree.js +2 -0
  52. package/dist/cjs/engine/builder/createRouteTree.js.map +1 -0
  53. package/dist/cjs/engine/builder/types.d.ts +48 -0
  54. package/dist/cjs/engine/builder/types.d.ts.map +1 -0
  55. package/dist/cjs/engine/createMatcher.d.ts +29 -0
  56. package/dist/cjs/engine/createMatcher.d.ts.map +1 -0
  57. package/dist/cjs/engine/createMatcher.js +2 -0
  58. package/dist/cjs/engine/createMatcher.js.map +1 -0
  59. package/dist/cjs/engine/operations/routeTreeToDefinitions.js +2 -0
  60. package/dist/cjs/engine/operations/routeTreeToDefinitions.js.map +1 -0
  61. package/dist/cjs/engine/path-matcher/SegmentMatcher.d.ts +16 -0
  62. package/dist/cjs/engine/path-matcher/SegmentMatcher.d.ts.map +1 -0
  63. package/dist/cjs/engine/path-matcher/SegmentMatcher.js +2 -0
  64. package/dist/cjs/engine/path-matcher/SegmentMatcher.js.map +1 -0
  65. package/dist/cjs/engine/path-matcher/buildParamMeta.js +2 -0
  66. package/dist/cjs/engine/path-matcher/buildParamMeta.js.map +1 -0
  67. package/dist/cjs/engine/path-matcher/encoding.js +2 -0
  68. package/dist/cjs/engine/path-matcher/encoding.js.map +1 -0
  69. package/dist/cjs/engine/path-matcher/parseSegment.js +2 -0
  70. package/dist/cjs/engine/path-matcher/parseSegment.js.map +1 -0
  71. package/dist/cjs/engine/path-matcher/pathUtils.js +2 -0
  72. package/dist/cjs/engine/path-matcher/pathUtils.js.map +1 -0
  73. package/dist/cjs/engine/path-matcher/percentEncoding.js +2 -0
  74. package/dist/cjs/engine/path-matcher/percentEncoding.js.map +1 -0
  75. package/dist/cjs/engine/path-matcher/registration/buildParts.js +2 -0
  76. package/dist/cjs/engine/path-matcher/registration/buildParts.js.map +1 -0
  77. package/dist/cjs/engine/path-matcher/registration/context.js +2 -0
  78. package/dist/cjs/engine/path-matcher/registration/context.js.map +1 -0
  79. package/dist/cjs/engine/path-matcher/registration/errors.js +2 -0
  80. package/dist/cjs/engine/path-matcher/registration/errors.js.map +1 -0
  81. package/dist/cjs/engine/path-matcher/registration/index.js +2 -0
  82. package/dist/cjs/engine/path-matcher/registration/index.js.map +1 -0
  83. package/dist/cjs/engine/path-matcher/registration/trie.js +2 -0
  84. package/dist/cjs/engine/path-matcher/registration/trie.js.map +1 -0
  85. package/dist/cjs/engine/path-matcher/registration/trieNodes.js +2 -0
  86. package/dist/cjs/engine/path-matcher/registration/trieNodes.js.map +1 -0
  87. package/dist/cjs/{types-DaXCTxi1.d.ts → engine/path-matcher/types.d.ts} +2 -47
  88. package/dist/cjs/engine/path-matcher/types.d.ts.map +1 -0
  89. package/dist/cjs/engine/search-params/decode.js +2 -0
  90. package/dist/cjs/engine/search-params/decode.js.map +1 -0
  91. package/dist/cjs/engine/search-params/encode.js +2 -0
  92. package/dist/cjs/engine/search-params/encode.js.map +1 -0
  93. package/dist/cjs/engine/search-params/searchParams.js +2 -0
  94. package/dist/cjs/engine/search-params/searchParams.js.map +1 -0
  95. package/dist/cjs/engine/search-params/strategies/array.js +2 -0
  96. package/dist/cjs/engine/search-params/strategies/array.js.map +1 -0
  97. package/dist/cjs/engine/search-params/strategies/boolean.js +2 -0
  98. package/dist/cjs/engine/search-params/strategies/boolean.js.map +1 -0
  99. package/dist/cjs/engine/search-params/strategies/index.js +2 -0
  100. package/dist/cjs/engine/search-params/strategies/index.js.map +1 -0
  101. package/dist/cjs/engine/search-params/strategies/null.js +2 -0
  102. package/dist/cjs/engine/search-params/strategies/null.js.map +1 -0
  103. package/dist/cjs/engine/search-params/strategies/number.js +2 -0
  104. package/dist/cjs/engine/search-params/strategies/number.js.map +1 -0
  105. package/dist/cjs/engine/search-params/types.d.ts +72 -0
  106. package/dist/cjs/engine/search-params/types.d.ts.map +1 -0
  107. package/dist/cjs/engine/search-params/utils.js +2 -0
  108. package/dist/cjs/engine/search-params/utils.js.map +1 -0
  109. package/dist/cjs/engine/validation/route-batch.d.ts +38 -0
  110. package/dist/cjs/engine/validation/route-batch.d.ts.map +1 -0
  111. package/dist/cjs/engine/validation/route-batch.js +2 -0
  112. package/dist/cjs/engine/validation/route-batch.js.map +1 -0
  113. package/dist/cjs/engine/validation/routes.js +2 -0
  114. package/dist/cjs/engine/validation/routes.js.map +1 -0
  115. package/dist/cjs/foundation/event-emitter/EventEmitter.js +2 -0
  116. package/dist/cjs/foundation/event-emitter/EventEmitter.js.map +1 -0
  117. package/dist/cjs/foundation/fsm/fsm.js +2 -0
  118. package/dist/cjs/foundation/fsm/fsm.js.map +1 -0
  119. package/dist/cjs/foundation/logger/RouterLogger.js +2 -0
  120. package/dist/cjs/foundation/logger/RouterLogger.js.map +1 -0
  121. package/dist/cjs/foundation/logger/constants.js +2 -0
  122. package/dist/cjs/foundation/logger/constants.js.map +1 -0
  123. package/dist/cjs/getNavigator.d.ts +6 -0
  124. package/dist/cjs/getNavigator.d.ts.map +1 -0
  125. package/dist/cjs/getNavigator.js +2 -0
  126. package/dist/cjs/getNavigator.js.map +1 -0
  127. package/dist/cjs/guards.js +2 -0
  128. package/dist/cjs/guards.js.map +1 -0
  129. package/dist/cjs/helpers.js +2 -0
  130. package/dist/cjs/helpers.js.map +1 -0
  131. package/dist/cjs/index.d.ts +17 -239
  132. package/dist/cjs/index.js +1 -2
  133. package/dist/cjs/internals.d.ts +98 -0
  134. package/dist/cjs/internals.d.ts.map +1 -0
  135. package/dist/cjs/internals.js +2 -0
  136. package/dist/cjs/internals.js.map +1 -0
  137. package/dist/cjs/namespaces/DependenciesNamespace/dependenciesStore.d.ts +10 -0
  138. package/dist/cjs/namespaces/DependenciesNamespace/dependenciesStore.d.ts.map +1 -0
  139. package/dist/cjs/namespaces/DependenciesNamespace/dependenciesStore.js +2 -0
  140. package/dist/cjs/namespaces/DependenciesNamespace/dependenciesStore.js.map +1 -0
  141. package/dist/cjs/namespaces/EventBusNamespace/EventBusNamespace.js +2 -0
  142. package/dist/cjs/namespaces/EventBusNamespace/EventBusNamespace.js.map +1 -0
  143. package/dist/cjs/namespaces/NavigationNamespace/NavigationNamespace.js +2 -0
  144. package/dist/cjs/namespaces/NavigationNamespace/NavigationNamespace.js.map +1 -0
  145. package/dist/cjs/namespaces/NavigationNamespace/constants.js +2 -0
  146. package/dist/cjs/namespaces/NavigationNamespace/constants.js.map +1 -0
  147. package/dist/cjs/namespaces/NavigationNamespace/transition/completeTransition.js +2 -0
  148. package/dist/cjs/namespaces/NavigationNamespace/transition/completeTransition.js.map +1 -0
  149. package/dist/cjs/namespaces/NavigationNamespace/transition/errorHandling.js +2 -0
  150. package/dist/cjs/namespaces/NavigationNamespace/transition/errorHandling.js.map +1 -0
  151. package/dist/cjs/namespaces/NavigationNamespace/transition/guardPhase.js +2 -0
  152. package/dist/cjs/namespaces/NavigationNamespace/transition/guardPhase.js.map +1 -0
  153. package/dist/cjs/namespaces/OptionsNamespace/OptionsNamespace.js +2 -0
  154. package/dist/cjs/namespaces/OptionsNamespace/OptionsNamespace.js.map +1 -0
  155. package/dist/cjs/namespaces/OptionsNamespace/constants.js +2 -0
  156. package/dist/cjs/namespaces/OptionsNamespace/constants.js.map +1 -0
  157. package/dist/cjs/namespaces/OptionsNamespace/helpers.js +2 -0
  158. package/dist/cjs/namespaces/OptionsNamespace/helpers.js.map +1 -0
  159. package/dist/cjs/namespaces/OptionsNamespace/validators.js +2 -0
  160. package/dist/cjs/namespaces/OptionsNamespace/validators.js.map +1 -0
  161. package/dist/cjs/namespaces/PluginsNamespace/PluginsNamespace.js +2 -0
  162. package/dist/cjs/namespaces/PluginsNamespace/PluginsNamespace.js.map +1 -0
  163. package/dist/cjs/namespaces/PluginsNamespace/constants.js +2 -0
  164. package/dist/cjs/namespaces/PluginsNamespace/constants.js.map +1 -0
  165. package/dist/cjs/namespaces/PluginsNamespace/validators.js +2 -0
  166. package/dist/cjs/namespaces/PluginsNamespace/validators.js.map +1 -0
  167. package/dist/cjs/namespaces/RouteLifecycleNamespace/RouteLifecycleNamespace.d.ts +174 -0
  168. package/dist/cjs/namespaces/RouteLifecycleNamespace/RouteLifecycleNamespace.d.ts.map +1 -0
  169. package/dist/cjs/namespaces/RouteLifecycleNamespace/RouteLifecycleNamespace.js +2 -0
  170. package/dist/cjs/namespaces/RouteLifecycleNamespace/RouteLifecycleNamespace.js.map +1 -0
  171. package/dist/cjs/namespaces/RouteLifecycleNamespace/types.d.ts +17 -0
  172. package/dist/cjs/namespaces/RouteLifecycleNamespace/types.d.ts.map +1 -0
  173. package/dist/cjs/namespaces/RouterLifecycleNamespace/RouterLifecycleNamespace.js +2 -0
  174. package/dist/cjs/namespaces/RouterLifecycleNamespace/RouterLifecycleNamespace.js.map +1 -0
  175. package/dist/cjs/namespaces/RouterLifecycleNamespace/constants.js +2 -0
  176. package/dist/cjs/namespaces/RouterLifecycleNamespace/constants.js.map +1 -0
  177. package/dist/cjs/namespaces/RoutesNamespace/RoutesNamespace.js +2 -0
  178. package/dist/cjs/namespaces/RoutesNamespace/RoutesNamespace.js.map +1 -0
  179. package/dist/cjs/namespaces/RoutesNamespace/constants.js +2 -0
  180. package/dist/cjs/namespaces/RoutesNamespace/constants.js.map +1 -0
  181. package/dist/cjs/namespaces/RoutesNamespace/forwardChain.d.ts +5 -0
  182. package/dist/cjs/namespaces/RoutesNamespace/forwardChain.d.ts.map +1 -0
  183. package/dist/cjs/namespaces/RoutesNamespace/forwardChain.js +2 -0
  184. package/dist/cjs/namespaces/RoutesNamespace/forwardChain.js.map +1 -0
  185. package/dist/cjs/namespaces/RoutesNamespace/helpers.js +2 -0
  186. package/dist/cjs/namespaces/RoutesNamespace/helpers.js.map +1 -0
  187. package/dist/cjs/namespaces/RoutesNamespace/routeGuards.js +2 -0
  188. package/dist/cjs/namespaces/RoutesNamespace/routeGuards.js.map +1 -0
  189. package/dist/cjs/namespaces/RoutesNamespace/routesStore.d.ts +42 -0
  190. package/dist/cjs/namespaces/RoutesNamespace/routesStore.d.ts.map +1 -0
  191. package/dist/cjs/namespaces/RoutesNamespace/routesStore.js +2 -0
  192. package/dist/cjs/namespaces/RoutesNamespace/routesStore.js.map +1 -0
  193. package/dist/cjs/namespaces/RoutesNamespace/types.d.ts +57 -0
  194. package/dist/cjs/namespaces/RoutesNamespace/types.d.ts.map +1 -0
  195. package/dist/cjs/namespaces/StateNamespace/StateNamespace.js +2 -0
  196. package/dist/cjs/namespaces/StateNamespace/StateNamespace.js.map +1 -0
  197. package/dist/cjs/namespaces/StateNamespace/helpers.js +2 -0
  198. package/dist/cjs/namespaces/StateNamespace/helpers.js.map +1 -0
  199. package/dist/cjs/routerFSM.js +2 -0
  200. package/dist/cjs/routerFSM.js.map +1 -0
  201. package/dist/cjs/stateMetaStore.js +2 -0
  202. package/dist/cjs/stateMetaStore.js.map +1 -0
  203. package/dist/cjs/transitionPath.js +2 -0
  204. package/dist/cjs/transitionPath.js.map +1 -0
  205. package/dist/cjs/{RouterValidator-CZ8kPkrI.d.ts → types/RouterValidator.d.ts} +2 -2
  206. package/dist/cjs/types/RouterValidator.d.ts.map +1 -0
  207. package/dist/cjs/types/api.d.ts +184 -0
  208. package/dist/cjs/types/api.d.ts.map +1 -0
  209. package/dist/cjs/types/base.d.ts +95 -0
  210. package/dist/cjs/types/base.d.ts.map +1 -0
  211. package/dist/cjs/types/constants.d.ts +72 -0
  212. package/dist/cjs/types/constants.d.ts.map +1 -0
  213. package/dist/cjs/types/internal.d.ts +9 -0
  214. package/dist/cjs/types/internal.d.ts.map +1 -0
  215. package/dist/cjs/types/limits.d.ts +45 -0
  216. package/dist/cjs/types/limits.d.ts.map +1 -0
  217. package/dist/cjs/types/route-node-types.d.ts +46 -0
  218. package/dist/cjs/types/route-node-types.d.ts.map +1 -0
  219. package/dist/cjs/types/router.d.ts +313 -0
  220. package/dist/cjs/types/router.d.ts.map +1 -0
  221. package/dist/cjs/types/tree-changed.d.ts +55 -0
  222. package/dist/cjs/types/tree-changed.d.ts.map +1 -0
  223. package/dist/cjs/types.d.ts +208 -2
  224. package/dist/cjs/types.d.ts.map +1 -0
  225. package/dist/cjs/validation.d.ts +6 -518
  226. package/dist/cjs/validation.js +1 -2
  227. package/dist/cjs/wiring/wireNamespaces.js +2 -0
  228. package/dist/cjs/wiring/wireNamespaces.js.map +1 -0
  229. package/dist/esm/Router-Bk6PfSE8.mjs +2 -0
  230. package/dist/esm/{Router-DikzugaD.mjs.map → Router-Bk6PfSE8.mjs.map} +1 -1
  231. package/dist/esm/{Router-Cy75qbgU.d.mts → Router.d.mts} +5 -4
  232. package/dist/esm/Router.d.mts.map +1 -0
  233. package/dist/esm/RouterError.d.mts +182 -0
  234. package/dist/esm/RouterError.d.mts.map +1 -0
  235. package/dist/esm/api/cloneRouter.d.mts +86 -0
  236. package/dist/esm/api/cloneRouter.d.mts.map +1 -0
  237. package/dist/esm/api/getDependenciesApi.d.mts +7 -0
  238. package/dist/esm/api/getDependenciesApi.d.mts.map +1 -0
  239. package/dist/esm/api/getLifecycleApi.d.mts +7 -0
  240. package/dist/esm/api/getLifecycleApi.d.mts.map +1 -0
  241. package/dist/esm/api/getPluginApi.d.mts +7 -0
  242. package/dist/esm/api/getPluginApi.d.mts.map +1 -0
  243. package/dist/esm/api/getRoutesApi.d.mts +7 -0
  244. package/dist/esm/api/getRoutesApi.d.mts.map +1 -0
  245. package/dist/esm/api/types.d.mts +9 -0
  246. package/dist/esm/api/types.d.mts.map +1 -0
  247. package/dist/esm/api.d.mts +8 -105
  248. package/dist/esm/api.mjs +1 -1
  249. package/dist/esm/api.mjs.map +1 -1
  250. package/dist/esm/buildParamMeta-bOLhLF9h.mjs.map +1 -1
  251. package/dist/esm/constants.d.mts +25 -0
  252. package/dist/esm/constants.d.mts.map +1 -0
  253. package/dist/esm/createRouter.d.mts +23 -0
  254. package/dist/esm/createRouter.d.mts.map +1 -0
  255. package/dist/esm/engine/builder/types.d.mts +48 -0
  256. package/dist/esm/engine/builder/types.d.mts.map +1 -0
  257. package/dist/esm/engine/createMatcher.d.mts +29 -0
  258. package/dist/esm/engine/createMatcher.d.mts.map +1 -0
  259. package/dist/esm/engine/path-matcher/SegmentMatcher.d.mts +16 -0
  260. package/dist/esm/engine/path-matcher/SegmentMatcher.d.mts.map +1 -0
  261. package/dist/esm/{types-DaXCTxi1.d.mts → engine/path-matcher/types.d.mts} +2 -47
  262. package/dist/esm/engine/path-matcher/types.d.mts.map +1 -0
  263. package/dist/esm/engine/search-params/types.d.mts +72 -0
  264. package/dist/esm/engine/search-params/types.d.mts.map +1 -0
  265. package/dist/esm/engine/validation/route-batch.d.mts +38 -0
  266. package/dist/esm/engine/validation/route-batch.d.mts.map +1 -0
  267. package/dist/esm/getNavigator.d.mts +6 -0
  268. package/dist/esm/getNavigator.d.mts.map +1 -0
  269. package/dist/esm/index.d.mts +17 -239
  270. package/dist/esm/index.mjs +1 -1
  271. package/dist/esm/internals.d.mts +98 -0
  272. package/dist/esm/internals.d.mts.map +1 -0
  273. package/dist/esm/namespaces/DependenciesNamespace/dependenciesStore.d.mts +10 -0
  274. package/dist/esm/namespaces/DependenciesNamespace/dependenciesStore.d.mts.map +1 -0
  275. package/dist/esm/namespaces/RouteLifecycleNamespace/RouteLifecycleNamespace.d.mts +174 -0
  276. package/dist/esm/namespaces/RouteLifecycleNamespace/RouteLifecycleNamespace.d.mts.map +1 -0
  277. package/dist/esm/namespaces/RouteLifecycleNamespace/types.d.mts +17 -0
  278. package/dist/esm/namespaces/RouteLifecycleNamespace/types.d.mts.map +1 -0
  279. package/dist/esm/namespaces/RoutesNamespace/forwardChain.d.mts +5 -0
  280. package/dist/esm/namespaces/RoutesNamespace/forwardChain.d.mts.map +1 -0
  281. package/dist/esm/namespaces/RoutesNamespace/routesStore.d.mts +42 -0
  282. package/dist/esm/namespaces/RoutesNamespace/routesStore.d.mts.map +1 -0
  283. package/dist/esm/namespaces/RoutesNamespace/types.d.mts +57 -0
  284. package/dist/esm/namespaces/RoutesNamespace/types.d.mts.map +1 -0
  285. package/dist/esm/{RouterValidator-CZ8kPkrI.d.mts → types/RouterValidator.d.mts} +2 -2
  286. package/dist/esm/types/RouterValidator.d.mts.map +1 -0
  287. package/dist/esm/types/api.d.mts +184 -0
  288. package/dist/esm/types/api.d.mts.map +1 -0
  289. package/dist/esm/types/base.d.mts +95 -0
  290. package/dist/esm/types/base.d.mts.map +1 -0
  291. package/dist/esm/types/constants.d.mts +72 -0
  292. package/dist/esm/types/constants.d.mts.map +1 -0
  293. package/dist/esm/types/internal.d.mts +9 -0
  294. package/dist/esm/types/internal.d.mts.map +1 -0
  295. package/dist/esm/types/limits.d.mts +45 -0
  296. package/dist/esm/types/limits.d.mts.map +1 -0
  297. package/dist/esm/types/route-node-types.d.mts +46 -0
  298. package/dist/esm/types/route-node-types.d.mts.map +1 -0
  299. package/dist/esm/types/router.d.mts +313 -0
  300. package/dist/esm/types/router.d.mts.map +1 -0
  301. package/dist/esm/types/tree-changed.d.mts +55 -0
  302. package/dist/esm/types/tree-changed.d.mts.map +1 -0
  303. package/dist/esm/types.d.mts +208 -2
  304. package/dist/esm/types.d.mts.map +1 -0
  305. package/dist/esm/validation.d.mts +6 -518
  306. package/package.json +5 -14
  307. package/dist/cjs/Router-BG5yNYUG.d.ts.map +0 -1
  308. package/dist/cjs/Router-BRuC-daj.js +0 -2
  309. package/dist/cjs/RouterValidator-CZ8kPkrI.d.ts.map +0 -1
  310. package/dist/cjs/api.d.ts.map +0 -1
  311. package/dist/cjs/cloneRouter-3AOUeQ8M.js +0 -2
  312. package/dist/cjs/cloneRouter-3AOUeQ8M.js.map +0 -1
  313. package/dist/cjs/index-CAnGQpnz.d.ts +0 -974
  314. package/dist/cjs/index-CAnGQpnz.d.ts.map +0 -1
  315. package/dist/cjs/index-DDrryY2U.d.ts +0 -309
  316. package/dist/cjs/index-DDrryY2U.d.ts.map +0 -1
  317. package/dist/cjs/index.d.ts.map +0 -1
  318. package/dist/cjs/types-DaXCTxi1.d.ts.map +0 -1
  319. package/dist/cjs/utils.d.ts +0 -2
  320. package/dist/cjs/utils.js +0 -2
  321. package/dist/cjs/utils.js.map +0 -1
  322. package/dist/cjs/validation.d.ts.map +0 -1
  323. package/dist/esm/Router-Cy75qbgU.d.mts.map +0 -1
  324. package/dist/esm/Router-DikzugaD.mjs +0 -2
  325. package/dist/esm/RouterValidator-CZ8kPkrI.d.mts.map +0 -1
  326. package/dist/esm/api.d.mts.map +0 -1
  327. package/dist/esm/cloneRouter-CAh5vA3l.mjs +0 -2
  328. package/dist/esm/cloneRouter-CAh5vA3l.mjs.map +0 -1
  329. package/dist/esm/index-CAnGQpnz.d.mts +0 -974
  330. package/dist/esm/index-CAnGQpnz.d.mts.map +0 -1
  331. package/dist/esm/index-DnSU6huX.d.mts +0 -309
  332. package/dist/esm/index-DnSU6huX.d.mts.map +0 -1
  333. package/dist/esm/index.d.mts.map +0 -1
  334. package/dist/esm/types-DaXCTxi1.d.mts.map +0 -1
  335. package/dist/esm/utils.d.mts +0 -2
  336. package/dist/esm/utils.mjs +0 -2
  337. package/dist/esm/utils.mjs.map +0 -1
  338. package/dist/esm/validation.d.mts.map +0 -1
@@ -89,50 +89,5 @@ interface ResolvedMatcherOptions {
89
89
  readonly buildQueryString: (params: Record<string, unknown>) => string;
90
90
  }
91
91
  //#endregion
92
- //#region src/engine/builder/types.d.ts
93
- /**
94
- * Definition of a route for configuration.
95
- */
96
- interface RouteDefinition {
97
- [key: string]: unknown;
98
- name: string;
99
- path: string;
100
- children?: RouteDefinition[] | undefined;
101
- }
102
- /**
103
- * Immutable route tree node.
104
- *
105
- * This is the core data structure of the new route-tree architecture.
106
- * It contains only data (no methods) and is created by the builder.
107
- *
108
- * All caches are pre-computed at build time:
109
- * - nonAbsoluteChildren: filtered children without absolute paths
110
- * - fullName: pre-computed "users.profile" instead of runtime join
111
- */
112
- interface RouteTree {
113
- /** Route segment name (e.g., "users" in "users.profile") */
114
- readonly name: string;
115
- /** Route path pattern (e.g., "/users/:id") */
116
- readonly path: string;
117
- /** Whether this route uses absolute path matching (path starts with "~") */
118
- readonly absolute: boolean;
119
- /** Child route nodes (Map for O(1) lookup by name) */
120
- readonly children: ReadonlyMap<string, RouteTree>;
121
- /** Parameter metadata extracted from path pattern (replaces parser dependency) */
122
- readonly paramMeta: ParamMeta;
123
- /** Parent node (null for root) */
124
- readonly parent: RouteTree | null;
125
- /** Children without absolute paths (for regular matching) */
126
- readonly nonAbsoluteChildren: readonly RouteTree[];
127
- /** Pre-computed full name (e.g., "users.profile") */
128
- readonly fullName: string;
129
- /**
130
- * Pre-computed parameter type map for this segment.
131
- * Cached to avoid recomputing on every navigation.
132
- * Maps param name → "url" | "query".
133
- */
134
- readonly paramTypeMap: Readonly<Record<string, "url" | "query">>;
135
- }
136
- //#endregion
137
- export { MatcherInputNode as a, MatchResult as i, RouteTree as n, ResolvedMatcherOptions as o, BuildPathOptions as r, SegmentMatcherOptions as s, RouteDefinition as t };
138
- //# sourceMappingURL=types-DaXCTxi1.d.ts.map
92
+ export { BuildPathOptions, MatchResult, MatcherInputNode, ParamMeta, ResolvedMatcherOptions, SegmentMatcherOptions, URLParamsEncodingType };
93
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","names":[],"sources":["../../../../src/engine/path-matcher/types.ts"],"mappings":";;;;;;;;;;;UAWiB;;;;;;WAMN;;;;;;WAOA;;;;;;WAOA;;;;;;WAOA,cAAc,SAAS;;;;;;WAOvB;;;;;;;;;;KAeC;UAMK;WACN;WACA;WACA;WACA;WACA,UAAU,oBAAoB;WAC9B,8BAA8B;WAC9B,WAAW;WACX,cAAc,SAAS;;;;;UAqCjB;WACN;WACA;;UA0BM;WACN,mBAAmB;WACnB,QAAQ,SAAS;WACjB,MAAM,SAAS,eAAe;;UAOxB;EACf;EACA;EACA;EACA,oBAAoB;EACpB,mBAAmB,wBAAwB;EAC3C,mBAAmB,QAAQ;;UAGZ;WACN;WACA;WACA;WACA,mBAAmB;WACnB,mBAAmB,wBAAwB;WAC3C,mBAAmB,QAAQ"}
@@ -0,0 +1,2 @@
1
+ const e=e=>{let t=e.indexOf(`%`),n=e.indexOf(`+`);if(t===-1&&n===-1)return e;let r=n===-1?e:e.replaceAll(`+`,` `);return t===-1?r:decodeURIComponent(r)},t=(t,n)=>{if(t===void 0)return n.boolean.decodeUndefined();let r=n.boolean.decodeRaw(t);if(r!==null)return r;let i=e(t),a=n.number.decode(i);return a===null?n.boolean.decodeValue(i):a};exports.decode=t,exports.decodeValue=e;
2
+ //# sourceMappingURL=decode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decode.js","names":[],"sources":["../../../../src/engine/search-params/decode.ts"],"sourcesContent":["/**\n * Decoding functions for search-params.\n *\n * Extracted from encode.ts for better separation of concerns.\n *\n * @module search-params/decode\n */\n\nimport type { ResolvedStrategies } from \"./strategies\";\nimport type { DecodeResult } from \"./types\";\n\n// =============================================================================\n// Value Decoding\n// =============================================================================\n\n/**\n * Decodes a URL-encoded value, handling + as space.\n * Optimized: only replaces + if present, skips decodeURIComponent if not needed.\n *\n * Uses indexOf for checking presence - more mutation-resistant than includes\n * because indexOf(\"x\") returns -1 when not found, while includes(\"\") always returns true.\n */\nexport const decodeValue = (value: string): string => {\n // Stryker disable next-line StringLiteral: equivalent — indexOf(\"%\") -> indexOf(\"\") returns 0, forcing the full decode path; decodeURIComponent is a no-op on a %-free string, so the result is identical (proven by injection). See the indexOf rationale above.\n const percentIdx = value.indexOf(\"%\");\n // Stryker disable next-line StringLiteral: equivalent — indexOf(\"+\") -> indexOf(\"\") returns 0, forcing replaceAll(\"+\",\" \") which is a no-op on a +-free string (proven by injection).\n const plusIdx = value.indexOf(\"+\");\n\n // Fast path: no encoding needed (common case - most values are simple)\n if (percentIdx === -1 && plusIdx === -1) {\n return value;\n }\n\n // Only replace + if present (avoid regex overhead)\n const withSpaces = plusIdx === -1 ? value : value.replaceAll(\"+\", \" \");\n\n // Only decode if % is present\n return percentIdx === -1 ? withSpaces : decodeURIComponent(withSpaces);\n};\n\n// =============================================================================\n// Main Decode\n// =============================================================================\n\n/**\n * Decodes a query parameter value using resolved strategies.\n *\n * @param value - Raw value from query string (undefined for key-only params)\n * @param strategies - Pre-resolved format strategies\n * @returns Decoded value (string, boolean, or null)\n */\nexport const decode = (\n value: string | undefined,\n strategies: ResolvedStrategies,\n): DecodeResult => {\n // Handle undefined (key-only params like ?flag)\n if (value === undefined) {\n return strategies.boolean.decodeUndefined();\n }\n\n // Check raw value for boolean formats (e.g., \"true\"/\"false\")\n const rawResult = strategies.boolean.decodeRaw(value);\n\n if (rawResult !== null) {\n return rawResult;\n }\n\n // Decode URI and check decoded value for boolean formats (e.g., ✓/✗)\n const decoded = decodeValue(value);\n\n const numberResult = strategies.number.decode(decoded);\n\n if (numberResult !== null) {\n return numberResult;\n }\n\n return strategies.boolean.decodeValue(decoded);\n};\n"],"mappings":"AAsBA,MAAa,EAAe,GAA0B,CAEpD,IAAM,EAAa,EAAM,QAAQ,GAAG,EAE9B,EAAU,EAAM,QAAQ,GAAG,EAGjC,GAAI,IAAe,IAAM,IAAY,GACnC,OAAO,EAIT,IAAM,EAAa,IAAY,GAAK,EAAQ,EAAM,WAAW,IAAK,GAAG,EAGrE,OAAO,IAAe,GAAK,EAAa,mBAAmB,CAAU,CACvE,EAaa,GACX,EACA,IACiB,CAEjB,GAAI,IAAU,IAAA,GACZ,OAAO,EAAW,QAAQ,gBAAgB,EAI5C,IAAM,EAAY,EAAW,QAAQ,UAAU,CAAK,EAEpD,GAAI,IAAc,KAChB,OAAO,EAIT,IAAM,EAAU,EAAY,CAAK,EAE3B,EAAe,EAAW,OAAO,OAAO,CAAO,EAMrD,OAJI,IAAiB,KAId,EAAW,QAAQ,YAAY,CAAO,EAHpC,CAIX"}
@@ -0,0 +1,2 @@
1
+ const e=require("./utils.js"),t=require("./strategies/index.js"),n={arrayFormat:`none`,booleanFormat:`auto`,nullFormat:`default`,numberFormat:`auto`},r={...n,strategies:t.DEFAULT_STRATEGIES},i=e=>{if(!e||e.arrayFormat===void 0&&e.booleanFormat===void 0&&e.nullFormat===void 0&&e.numberFormat===void 0)return r;let i=e.arrayFormat??n.arrayFormat,a=e.booleanFormat??n.booleanFormat,o=e.nullFormat??n.nullFormat,s=e.numberFormat??n.numberFormat;return{arrayFormat:i,booleanFormat:a,nullFormat:o,numberFormat:s,strategies:t.resolveStrategies(i,a,o,s)}},a=t=>e.safeEncode(t),o=(e,t,n)=>{let r=a(e);switch(typeof t){case`string`:case`number`:return`${r}=${a(t)}`;case`boolean`:return n.strategies.boolean.encode(r,t);case`object`:return t===null?n.strategies.null.encode(r):Array.isArray(t)?n.strategies.array.encodeArray(r,t,n.strategies.null):`${r}=${a(t)}`;default:return`${r}=${a(t)}`}};exports.DEFAULT_QUERY_PARAMS=n,exports.encode=o,exports.encodeValue=a,exports.makeOptions=i;
2
+ //# sourceMappingURL=encode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"encode.js","names":["DEFAULT_STRATEGIES","resolveStrategies","safeEncode"],"sources":["../../../../src/engine/search-params/encode.ts"],"sourcesContent":["/**\n * Encoding functions for search-params.\n *\n * Uses Strategy pattern for format-specific encoding.\n * Strategies are resolved once when options are created.\n *\n * @module search-params/encode\n */\n\nimport {\n DEFAULT_STRATEGIES,\n resolveStrategies,\n type ResolvedStrategies,\n} from \"./strategies\";\nimport { safeEncode } from \"./utils\";\n\nimport type { FinalOptions, Options } from \"./types\";\n\n// =============================================================================\n// Default Query Params\n// =============================================================================\n\n/**\n * Default query parameter options. Single source of truth for all packages.\n */\nexport const DEFAULT_QUERY_PARAMS: FinalOptions = {\n arrayFormat: \"none\",\n booleanFormat: \"auto\",\n nullFormat: \"default\",\n numberFormat: \"auto\",\n};\n\n// =============================================================================\n// Options with Strategies\n// =============================================================================\n\n/**\n * Extended options with pre-resolved strategies.\n */\nexport interface OptionsWithStrategies extends FinalOptions {\n readonly strategies: ResolvedStrategies;\n}\n\n/**\n * Cached default options with strategies - avoids allocation when no options passed.\n */\nconst DEFAULT_OPTIONS: OptionsWithStrategies = {\n ...DEFAULT_QUERY_PARAMS,\n strategies: DEFAULT_STRATEGIES,\n};\n\n/**\n * Creates options with defaults and pre-resolved strategies.\n * Returns cached DEFAULT_OPTIONS when no custom options are specified.\n */\nexport const makeOptions = (opts?: Options): OptionsWithStrategies => {\n if (\n !opts ||\n (opts.arrayFormat === undefined &&\n opts.booleanFormat === undefined &&\n opts.nullFormat === undefined &&\n opts.numberFormat === undefined)\n ) {\n return DEFAULT_OPTIONS;\n }\n\n // Avoid object spread - direct property assignment is faster\n const arrayFormat = opts.arrayFormat ?? DEFAULT_QUERY_PARAMS.arrayFormat;\n const booleanFormat =\n opts.booleanFormat ?? DEFAULT_QUERY_PARAMS.booleanFormat;\n const nullFormat = opts.nullFormat ?? DEFAULT_QUERY_PARAMS.nullFormat;\n const numberFormat = opts.numberFormat ?? DEFAULT_QUERY_PARAMS.numberFormat;\n\n return {\n arrayFormat,\n booleanFormat,\n nullFormat,\n numberFormat,\n strategies: resolveStrategies(\n arrayFormat,\n booleanFormat,\n nullFormat,\n numberFormat,\n ),\n };\n};\n\n// =============================================================================\n// Value Encoding\n// =============================================================================\n\n/**\n * Encodes a value for use in a URL query string.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument\nexport const encodeValue = (value: any): string => safeEncode(value);\n\n// =============================================================================\n// Main Encode\n// =============================================================================\n\n/**\n * Encodes a parameter name and value as a query string segment.\n *\n * Uses pre-resolved strategies for format-specific encoding.\n * Strategies handle all formats uniformly - no inline optimizations\n * to avoid equivalent mutants in mutation testing.\n *\n * @param name - Parameter name\n * @param value - Parameter value\n * @param opts - Options with pre-resolved strategies\n * @returns Query string segment (e.g., \"key=value\")\n */\nexport const encode = (\n name: string,\n value: unknown,\n opts: OptionsWithStrategies,\n): string => {\n const encodedName = encodeValue(name);\n\n // Handle each type using strategies\n switch (typeof value) {\n case \"string\":\n case \"number\": {\n return `${encodedName}=${encodeValue(value)}`;\n }\n case \"boolean\": {\n return opts.strategies.boolean.encode(encodedName, value);\n }\n case \"object\": {\n // Null check\n if (value === null) {\n return opts.strategies.null.encode(encodedName);\n }\n // Array check\n if (Array.isArray(value)) {\n return opts.strategies.array.encodeArray(\n encodedName,\n value,\n opts.strategies.null,\n );\n }\n\n // Fallback for other objects - treat as string\n return `${encodedName}=${encodeValue(value)}`;\n }\n default: {\n // Fallback for other types (undefined handled by caller)\n return `${encodedName}=${encodeValue(value)}`;\n }\n }\n};\n"],"mappings":"iEAyBa,EAAqC,CAChD,YAAa,OACb,cAAe,OACf,WAAY,UACZ,aAAc,MAChB,EAgBM,EAAyC,CAC7C,GAAG,EACH,WAAYA,EAAAA,kBACd,EAMa,EAAe,GAA0C,CACpE,GACE,CAAC,GACA,EAAK,cAAgB,IAAA,IACpB,EAAK,gBAAkB,IAAA,IACvB,EAAK,aAAe,IAAA,IACpB,EAAK,eAAiB,IAAA,GAExB,OAAO,EAIT,IAAM,EAAc,EAAK,aAAe,EAAqB,YACvD,EACJ,EAAK,eAAiB,EAAqB,cACvC,EAAa,EAAK,YAAc,EAAqB,WACrD,EAAe,EAAK,cAAgB,EAAqB,aAE/D,MAAO,CACL,cACA,gBACA,aACA,eACA,WAAYC,EAAAA,kBACV,EACA,EACA,EACA,CACF,CACF,CACF,EAUa,EAAe,GAAuBC,EAAAA,WAAW,CAAK,EAkBtD,GACX,EACA,EACA,IACW,CACX,IAAM,EAAc,EAAY,CAAI,EAGpC,OAAQ,OAAO,EAAf,CACE,IAAK,SACL,IAAK,SACH,MAAO,GAAG,EAAY,GAAG,EAAY,CAAK,IAE5C,IAAK,UACH,OAAO,EAAK,WAAW,QAAQ,OAAO,EAAa,CAAK,EAE1D,IAAK,SAeH,OAbI,IAAU,KACL,EAAK,WAAW,KAAK,OAAO,CAAW,EAG5C,MAAM,QAAQ,CAAK,EACd,EAAK,WAAW,MAAM,YAC3B,EACA,EACA,EAAK,WAAW,IAClB,EAIK,GAAG,EAAY,GAAG,EAAY,CAAK,IAE5C,QAEE,MAAO,GAAG,EAAY,GAAG,EAAY,CAAK,GAE9C,CACF"}
@@ -0,0 +1,2 @@
1
+ const e=require("./decode.js"),t=require("./encode.js");function n(e,t,n){t===`__proto__`?Object.defineProperty(e,t,{value:n,writable:!0,enumerable:!0,configurable:!0}):e[t]=n}function r(e,t,r,i){if(!Object.hasOwn(e,t)){n(e,t,i?[r]:r);return}let a=e[t];Array.isArray(a)?a.push(r):n(e,t,[a,r])}function i(t,n,r,i,a){return e.decode(i?t.slice(n+1,r):void 0,a)}function a(e,t,n){let r=t+1,i=0,a=!1;for(;r<n;){let t=e.codePointAt(r);if(t===93)return a?i:null;if(t!==void 0&&t>=48&&t<=57){i=i*10+(t-48),a=!0,r++;continue}return null}return null}function o(e,t,n){let{searchPart:r,nameEnd:o,nameSourceEnd:s,eqPos:c,end:l,hasValue:u,decodedName:d}=e,f=a(r,o,s);if(f===null)return!1;let p=i(r,c,l,u,t),m=n.get(d);return m===void 0?n.set(d,[[f,p]]):m.push([f,p]),!0}function s(t,n,a,s,c,l,u){let d=l!==-1&&l<a,f=d?l:a,p=f,m=!1;for(let e=n;e<f;e++)if(t.codePointAt(e)===91){p=e,m=!0;break}let h=e.decodeValue(t.slice(n,p));if(!(u!==void 0&&m&&o({searchPart:t,nameEnd:p,nameSourceEnd:f,eqPos:l,end:a,hasValue:d,decodedName:h},c,u))){if(!m&&d&&c.array.decodeValue){let n=t.slice(l+1,a),i=c.array.decodeValue(n);if(i){for(let t of i)r(s,h,e.decode(t,c),!0);return}}r(s,h,i(t,l,a,d,c),m)}}const c=(e,n)=>{if(e===``||e===`?`)return{};let r={};return l(e,r,t.makeOptions(n).strategies),r};function l(e,t,r){let i=r.array.indexed?new Map:void 0,a=0,o=e.length,c=-2;for(;a<o;){let n=e.indexOf(`&`,a);n===-1&&(n=o),n>a&&(c!==-1&&c<a&&(c=e.indexOf(`=`,a)),s(e,a,n,t,r,c,i)),a=n+1}if(i!==void 0)for(let[e,r]of i)r.sort((e,t)=>e[0]-t[0]),n(t,e,r.map(e=>e[1]))}const u=(e,n)=>{let r=Object.keys(e);if(r.length===0)return``;let i=t.makeOptions(n),a=[];for(let n of r){let r=e[n];if(r===void 0)continue;let o=t.encode(n,r,i);o&&a.push(o)}return a.join(`&`)};exports.build=u,exports.parseQuery=c;
2
+ //# sourceMappingURL=searchParams.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"searchParams.js","names":["decode","decodeValue","makeOptions","encode"],"sources":["../../../../src/engine/search-params/searchParams.ts"],"sourcesContent":["/**\n * Search Params - Query String Parsing and Building.\n *\n * Internalized from https://github.com/troch/search-params (MIT License)\n * for better code control and optimization.\n *\n * @module search-params/searchParams\n */\n\nimport { decode, decodeValue } from \"./decode\";\nimport { encode, makeOptions } from \"./encode\";\n\nimport type { ResolvedStrategies } from \"./strategies\";\nimport type { Options } from \"./types\";\n\n// =============================================================================\n// Internal Helpers\n// =============================================================================\n\n/**\n * Assigns a parameter as an own data property.\n *\n * Plain `params[name] = value` invokes the inherited `__proto__` accessor for the\n * literal key `\"__proto__\"`, so that key would mutate the prototype instead of\n * becoming a real entry; `defineProperty` writes a genuine own property.\n *\n * @internal\n */\nfunction assignParam(\n params: Record<string, unknown>,\n name: string,\n value: unknown,\n): void {\n if (name === \"__proto__\") {\n Object.defineProperty(params, name, {\n value,\n writable: true,\n enumerable: true,\n configurable: true,\n });\n } else {\n params[name] = value;\n }\n}\n\n/**\n * Adds a decoded value to params object, handling array accumulation.\n *\n * Collisions are detected via `Object.hasOwn`, not `params[name] !== undefined`:\n * a query key that shadows an `Object.prototype` member (`valueOf`, `constructor`,\n * `toString`, …) would otherwise read the inherited function and be mistaken for\n * a pre-existing value, corrupting the result into `[<fn>, value]`. (#855)\n *\n * @internal\n */\nfunction addToParams(\n params: Record<string, unknown>,\n decodedName: string,\n decodedValue: unknown,\n hasBrackets: boolean,\n): void {\n if (!Object.hasOwn(params, decodedName)) {\n assignParam(\n params,\n decodedName,\n hasBrackets ? [decodedValue] : decodedValue,\n );\n\n return;\n }\n\n const currentValue = params[decodedName];\n\n if (Array.isArray(currentValue)) {\n currentValue.push(decodedValue);\n } else {\n assignParam(params, decodedName, [currentValue, decodedValue]);\n }\n}\n\n/**\n * Decodes a parameter value through the resolved strategies.\n *\n * @internal\n */\nfunction decodeParamValue(\n searchPart: string,\n eqPos: number,\n end: number,\n hasValue: boolean,\n strategies: ResolvedStrategies,\n): unknown {\n const rawValue = hasValue ? searchPart.slice(eqPos + 1, end) : undefined;\n\n return decode(rawValue, strategies);\n}\n\n/**\n * Reads the non-negative integer index from a bracketed name (`a[12]`).\n *\n * `open` points at the `[`; digits up to the matching `]` form the index. Returns\n * `null` for `[]`, non-digit content (`a[x]`), or a missing `]` — those fall back\n * to insertion-order accumulation. (#856)\n *\n * @internal\n */\nfunction bracketIndex(\n searchPart: string,\n open: number,\n limit: number,\n): number | null {\n let i = open + 1;\n let value = 0;\n let hasDigit = false;\n\n while (i < limit) {\n const ch = searchPart.codePointAt(i);\n\n if (ch === 93) {\n // ']' — a numeric index only if at least one digit preceded it (\"[]\" → null)\n return hasDigit ? value : null;\n }\n\n if (ch !== undefined && ch >= 48 && ch <= 57) {\n value = value * 10 + (ch - 48);\n hasDigit = true;\n i++;\n\n continue;\n }\n\n return null; // non-digit inside brackets — not a numeric index\n }\n\n return null; // no closing ']' (incl. \"[\" at end) — malformed, fall back\n}\n\n/**\n * A single parsed query chunk: the source string plus the boundary offsets and\n * decoded name that `processParamChunk` computes once. Bundled into one\n * descriptor so the indexed-format collector reuses them without a long\n * parameter list (#856).\n *\n * @internal\n */\ninterface ParsedChunk {\n searchPart: string;\n /** Offset of `[` (bracket notation), or the name terminator. */\n nameEnd: number;\n /** Offset where the raw name ends (`=` for valued chunks, else `end`). */\n nameSourceEnd: number;\n /** Offset of `=`, or -1 when the chunk has no value. */\n eqPos: number;\n /** Offset one past the chunk. */\n end: number;\n hasValue: boolean;\n decodedName: string;\n}\n\n/**\n * Collects a bracketed chunk into the index-format group, to be sorted by index\n * after the full pass. Returns `false` when the bracket is not a numeric index\n * (`a[]`, `a[x]`, `a[`), so the caller falls back to insertion-order push. (#856)\n *\n * @internal\n */\nfunction collectIndexedChunk(\n chunk: ParsedChunk,\n strategies: ResolvedStrategies,\n indexedGroups: Map<string, [number, unknown][]>,\n): boolean {\n const {\n searchPart,\n nameEnd,\n nameSourceEnd,\n eqPos,\n end,\n hasValue,\n decodedName,\n } = chunk;\n const index = bracketIndex(searchPart, nameEnd, nameSourceEnd);\n\n if (index === null) {\n return false;\n }\n\n const value = decodeParamValue(searchPart, eqPos, end, hasValue, strategies);\n const group = indexedGroups.get(decodedName);\n\n if (group === undefined) {\n indexedGroups.set(decodedName, [[index, value]]);\n } else {\n group.push([index, value]);\n }\n\n // Stryker disable next-line BooleanLiteral: equivalent — returning false makes the caller ALSO push via insertion order, but indexedGroups was already populated above and parseIntoInternal overwrites the key with the index-sorted result, so the final params are identical (proven by injection).\n return true;\n}\n\n/**\n * Processes a single query parameter chunk and adds to params.\n *\n * `indexedGroups` is supplied only for `arrayFormat: \"index\"`: bracketed chunks\n * with a numeric index are collected there (to be sorted by index after the full\n * pass) instead of pushed in insertion order. (#856)\n *\n * @internal\n */\nfunction processParamChunk(\n searchPart: string,\n start: number,\n end: number,\n params: Record<string, unknown>,\n strategies: ResolvedStrategies,\n eqPos: number,\n indexedGroups?: Map<string, [number, unknown][]>,\n): void {\n // `eqPos` is the position of the next `=` at or after `start`, resolved once by\n // the caller's monotonic cursor (#1316) — never re-scanned here. `eqPos < end`\n // means it falls inside THIS chunk (so the chunk has a value); otherwise the\n // chunk is key-only.\n const hasValue = eqPos !== -1 && eqPos < end;\n\n const nameSourceEnd = hasValue ? eqPos : end;\n let nameEnd = nameSourceEnd;\n let hasBrackets = false;\n\n for (let i = start; i < nameSourceEnd; i++) {\n if (searchPart.codePointAt(i) !== 91) {\n continue;\n }\n\n // '['\n nameEnd = i;\n hasBrackets = true;\n\n break;\n }\n\n const decodedName = decodeValue(searchPart.slice(start, nameEnd));\n\n // Index array format: order by the bracket index, not insertion. A non-numeric\n // bracket (`a[]`, `a[x]`) returns false → falls through to insertion-order push.\n if (\n indexedGroups !== undefined &&\n hasBrackets &&\n collectIndexedChunk(\n { searchPart, nameEnd, nameSourceEnd, eqPos, end, hasValue, decodedName },\n strategies,\n indexedGroups,\n )\n ) {\n return;\n }\n\n // Comma array decode: split raw value before individual element decoding\n if (!hasBrackets && hasValue && strategies.array.decodeValue) {\n const rawValue = searchPart.slice(eqPos + 1, end);\n const parts = strategies.array.decodeValue(rawValue);\n\n if (parts) {\n for (const part of parts) {\n addToParams(params, decodedName, decode(part, strategies), true);\n }\n\n return;\n }\n }\n\n const decodedValue = decodeParamValue(\n searchPart,\n eqPos,\n end,\n hasValue,\n strategies,\n );\n\n addToParams(params, decodedName, decodedValue, hasBrackets);\n}\n\n// =============================================================================\n// Parse\n// =============================================================================\n\n/**\n * Parse an ALREADY-EXTRACTED query string (no path prefix, no leading \"?\") into\n * an object of parameters.\n *\n * The input must already be split at the first \"?\" — `SegmentMatcher.#preparePath`\n * does this before the DI call, so route-tree wires `parseQuery` (not a\n * path-accepting wrapper) as its query parser: re-splitting the input here would\n * break at a \"?\" *inside* a query value (legal per RFC 3986), silently dropping\n * the param (and unmatching the whole URL under `strictQueryParams`). (#1292)\n *\n * @example\n * ```typescript\n * parseQuery(\"page=1&sort=name\");\n * // => { page: 1, sort: \"name\" }\n *\n * parseQuery(\"items[]=a&items[]=b\", { arrayFormat: \"brackets\" });\n * // => { items: [\"a\", \"b\"] }\n * ```\n */\nexport const parseQuery = (\n search: string,\n opts?: Options,\n): Record<string, unknown> => {\n // Fast path: empty query string\n if (search === \"\" || search === \"?\") {\n return {};\n }\n\n // makeOptions(undefined) returns the cached DEFAULT_OPTIONS (auto) — the same\n // defaults `build` uses — so parseQuery(build(x)) === x even without options. (#744)\n const params: Record<string, unknown> = {};\n\n parseIntoInternal(search, params, makeOptions(opts).strategies);\n\n return params;\n};\n\n/**\n * Internal function to parse a query string into a target object.\n * The shared parse engine behind `parseQuery`.\n *\n * @internal\n */\nfunction parseIntoInternal(\n searchPart: string,\n params: Record<string, unknown>,\n strategies: ResolvedStrategies,\n): void {\n // `index` format orders by the bracket index; collect (index, value) pairs and\n // sort after the pass. `undefined` for every other format (no overhead). (#856)\n const indexedGroups = strategies.array.indexed\n ? new Map<string, [number, unknown][]>()\n : undefined;\n\n let start = 0;\n const length = searchPart.length;\n\n // Monotonic cursor for the next `=`. Its position only ever moves forward with\n // `start`, so the whole parse does a single amortised O(n) scan for `=` —\n // replacing `processParamChunk`'s former per-chunk `indexOf(\"=\", start)`, which\n // scanned to the end of the string on every key-only chunk and made `parse`\n // O(n²) on `\"a&a&…\"` (#1316). `-2` = not yet searched; `-1` = no `=` remains.\n let eqCache = -2;\n\n while (start < length) {\n let end = searchPart.indexOf(\"&\", start);\n\n if (end === -1) {\n end = length;\n }\n\n // Skip empty chunks — a `&&`, a leading `&`, or a trailing `&` produces a\n // zero-length span that carries no name and no value. Processing it would\n // decode the empty name to `\"\"` and the missing value to `null`, injecting a\n // junk `{ \"\": null }` param (and `[null, …]` on repeats) (#1156). An\n // intentional empty-key chunk always carries an `=` (`\"=1\"` → `end > start`),\n // so it is unaffected.\n if (end > start) {\n // Advance the cursor only when the cached `=` is behind the current chunk;\n // once it reports `-1` (no `=` left in the string) it is final.\n if (eqCache !== -1 && eqCache < start) {\n eqCache = searchPart.indexOf(\"=\", start);\n }\n\n processParamChunk(\n searchPart,\n start,\n end,\n params,\n strategies,\n eqCache,\n indexedGroups,\n );\n }\n\n start = end + 1;\n }\n\n if (indexedGroups !== undefined) {\n for (const [name, pairs] of indexedGroups) {\n // Stable sort by index (V8 sort is stable) → equal indices keep arrival order.\n pairs.sort((left, right) => left[0] - right[0]);\n assignParam(\n params,\n name,\n pairs.map((pair) => pair[1]),\n );\n }\n }\n}\n\n// =============================================================================\n// Build\n// =============================================================================\n\n/**\n * Build a querystring from an object of parameters.\n *\n * Note: Empty arrays produce an empty string, so `parseQuery(build({ items: [] }))`\n * will not contain the `items` key. This is expected behavior for all array\n * formats including `comma` — the key is erased uniformly (INVARIANTS Parse/Build #9).\n *\n * @example\n * ```typescript\n * build({ page: 1, sort: \"name\" });\n * // => \"page=1&sort=name\"\n *\n * build({ items: [\"a\", \"b\"] }, { arrayFormat: \"brackets\" });\n * // => \"items[]=a&items[]=b\"\n * ```\n */\nexport const build = (\n params: Record<string, unknown>,\n opts?: Options,\n): string => {\n // Fast path for empty params (common case)\n const keys = Object.keys(params);\n\n if (keys.length === 0) {\n return \"\";\n }\n\n const options = makeOptions(opts);\n\n // Optimized: single loop instead of filter().map().filter().join()\n // Avoids creating 3 intermediate arrays\n const parts: string[] = [];\n\n for (const key of keys) {\n const value = params[key];\n\n // Skip undefined values (not serialisable)\n if (value === undefined) {\n continue;\n }\n\n const encoded = encode(key, value, options);\n\n // Skip empty strings (e.g., from nullFormat: \"hidden\")\n if (encoded) {\n parts.push(encoded);\n }\n }\n\n return parts.join(\"&\");\n};\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ const e=require("../utils.js"),t=t=>{let n=typeof t;if(n!==`string`&&n!==`number`&&n!==`boolean`)throw TypeError(`[search-params] Array element must be a string, number, or boolean — received ${n}`);return e.safeEncode(t)},n=(e,n,r,i)=>{let a=`${e}${r}`,o=[];for(let e of n)if(e===null){let e=i.encode(a);e&&o.push(e)}else o.push(`${a}=${t(e)}`);return o.join(`&`)},r={encodeArray:(e,t,r)=>n(e,t,``,r)},i={encodeArray:(e,t,r)=>n(e,t,`[]`,r)},a={encodeArray:(e,n,r)=>{let i=[];for(let[a,o]of n.entries()){let n=`${e}[${a}]`;if(o===null){let e=r.encode(n);e&&i.push(e)}else i.push(`${n}=${t(o)}`)}return i.join(`&`)},indexed:!0},o={encodeArray:(e,n)=>{let r=[];for(let e of n)e!==null&&r.push(t(e));return r.length===0?``:`${e}=${r.join(`,`)}`},decodeValue:e=>e.includes(`,`)?e.split(`,`):null},s={none:r,brackets:i,index:a,comma:o};exports.arrayStrategies=s,exports.bracketsArrayStrategy=i,exports.commaArrayStrategy=o,exports.indexArrayStrategy=a,exports.noneArrayStrategy=r;
2
+ //# sourceMappingURL=array.js.map
@@ -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