@real-router/core 0.84.0 → 0.85.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. package/dist/cjs/{Router-CUtHMhRV.js → Router-BOyV6_me.js} +2 -2
  2. package/dist/cjs/{Router-CUtHMhRV.js.map → Router-BOyV6_me.js.map} +1 -1
  3. package/dist/cjs/api.js +1 -1
  4. package/dist/cjs/index.js +1 -1
  5. package/dist/esm/Router-A9Vko7z1.mjs +2 -0
  6. package/dist/esm/{Router-sWkNlsWW.mjs.map → Router-A9Vko7z1.mjs.map} +1 -1
  7. package/dist/esm/api.mjs +1 -1
  8. package/dist/esm/index.mjs +1 -1
  9. package/package.json +2 -2
  10. package/dist/cjs/Router.js +0 -2
  11. package/dist/cjs/Router.js.map +0 -1
  12. package/dist/cjs/RouterError.js +0 -2
  13. package/dist/cjs/RouterError.js.map +0 -1
  14. package/dist/cjs/api/cloneRouter.js +0 -2
  15. package/dist/cjs/api/cloneRouter.js.map +0 -1
  16. package/dist/cjs/api/getDependenciesApi.js +0 -2
  17. package/dist/cjs/api/getDependenciesApi.js.map +0 -1
  18. package/dist/cjs/api/getLifecycleApi.js +0 -2
  19. package/dist/cjs/api/getLifecycleApi.js.map +0 -1
  20. package/dist/cjs/api/getPluginApi.js +0 -2
  21. package/dist/cjs/api/getPluginApi.js.map +0 -1
  22. package/dist/cjs/api/getRoutesApi.js +0 -2
  23. package/dist/cjs/api/getRoutesApi.js.map +0 -1
  24. package/dist/cjs/api/helpers.js +0 -2
  25. package/dist/cjs/api/helpers.js.map +0 -1
  26. package/dist/cjs/channels/defaults.js +0 -2
  27. package/dist/cjs/channels/defaults.js.map +0 -1
  28. package/dist/cjs/channels/guard.js +0 -2
  29. package/dist/cjs/channels/guard.js.map +0 -1
  30. package/dist/cjs/channels/modeGate.js +0 -2
  31. package/dist/cjs/channels/modeGate.js.map +0 -1
  32. package/dist/cjs/constants.js +0 -2
  33. package/dist/cjs/constants.js.map +0 -1
  34. package/dist/cjs/createRouter.js +0 -2
  35. package/dist/cjs/createRouter.js.map +0 -1
  36. package/dist/cjs/engine/builder/buildTree.js +0 -2
  37. package/dist/cjs/engine/builder/buildTree.js.map +0 -1
  38. package/dist/cjs/engine/builder/computeCaches.js +0 -2
  39. package/dist/cjs/engine/builder/computeCaches.js.map +0 -1
  40. package/dist/cjs/engine/builder/createRouteTree.js +0 -2
  41. package/dist/cjs/engine/builder/createRouteTree.js.map +0 -1
  42. package/dist/cjs/engine/createMatcher.js +0 -2
  43. package/dist/cjs/engine/createMatcher.js.map +0 -1
  44. package/dist/cjs/engine/operations/routeTreeToDefinitions.js +0 -2
  45. package/dist/cjs/engine/operations/routeTreeToDefinitions.js.map +0 -1
  46. package/dist/cjs/engine/path-matcher/SegmentMatcher.js +0 -2
  47. package/dist/cjs/engine/path-matcher/SegmentMatcher.js.map +0 -1
  48. package/dist/cjs/engine/path-matcher/buildParamMeta.js +0 -2
  49. package/dist/cjs/engine/path-matcher/buildParamMeta.js.map +0 -1
  50. package/dist/cjs/engine/path-matcher/encoding.js +0 -2
  51. package/dist/cjs/engine/path-matcher/encoding.js.map +0 -1
  52. package/dist/cjs/engine/path-matcher/parseSegment.js +0 -2
  53. package/dist/cjs/engine/path-matcher/parseSegment.js.map +0 -1
  54. package/dist/cjs/engine/path-matcher/pathUtils.js +0 -2
  55. package/dist/cjs/engine/path-matcher/pathUtils.js.map +0 -1
  56. package/dist/cjs/engine/path-matcher/percentEncoding.js +0 -2
  57. package/dist/cjs/engine/path-matcher/percentEncoding.js.map +0 -1
  58. package/dist/cjs/engine/path-matcher/registration/buildParts.js +0 -2
  59. package/dist/cjs/engine/path-matcher/registration/buildParts.js.map +0 -1
  60. package/dist/cjs/engine/path-matcher/registration/context.js +0 -2
  61. package/dist/cjs/engine/path-matcher/registration/context.js.map +0 -1
  62. package/dist/cjs/engine/path-matcher/registration/errors.js +0 -2
  63. package/dist/cjs/engine/path-matcher/registration/errors.js.map +0 -1
  64. package/dist/cjs/engine/path-matcher/registration/index.js +0 -2
  65. package/dist/cjs/engine/path-matcher/registration/index.js.map +0 -1
  66. package/dist/cjs/engine/path-matcher/registration/trie.js +0 -2
  67. package/dist/cjs/engine/path-matcher/registration/trie.js.map +0 -1
  68. package/dist/cjs/engine/path-matcher/registration/trieNodes.js +0 -2
  69. package/dist/cjs/engine/path-matcher/registration/trieNodes.js.map +0 -1
  70. package/dist/cjs/engine/search-params/decode.js +0 -2
  71. package/dist/cjs/engine/search-params/decode.js.map +0 -1
  72. package/dist/cjs/engine/search-params/encode.js +0 -2
  73. package/dist/cjs/engine/search-params/encode.js.map +0 -1
  74. package/dist/cjs/engine/search-params/searchParams.js +0 -2
  75. package/dist/cjs/engine/search-params/searchParams.js.map +0 -1
  76. package/dist/cjs/engine/search-params/strategies/array.js +0 -2
  77. package/dist/cjs/engine/search-params/strategies/array.js.map +0 -1
  78. package/dist/cjs/engine/search-params/strategies/boolean.js +0 -2
  79. package/dist/cjs/engine/search-params/strategies/boolean.js.map +0 -1
  80. package/dist/cjs/engine/search-params/strategies/index.js +0 -2
  81. package/dist/cjs/engine/search-params/strategies/index.js.map +0 -1
  82. package/dist/cjs/engine/search-params/strategies/null.js +0 -2
  83. package/dist/cjs/engine/search-params/strategies/null.js.map +0 -1
  84. package/dist/cjs/engine/search-params/strategies/number.js +0 -2
  85. package/dist/cjs/engine/search-params/strategies/number.js.map +0 -1
  86. package/dist/cjs/engine/search-params/utils.js +0 -2
  87. package/dist/cjs/engine/search-params/utils.js.map +0 -1
  88. package/dist/cjs/engine/validation/route-batch.js +0 -2
  89. package/dist/cjs/engine/validation/route-batch.js.map +0 -1
  90. package/dist/cjs/engine/validation/routes.js +0 -2
  91. package/dist/cjs/engine/validation/routes.js.map +0 -1
  92. package/dist/cjs/getNavigator.js +0 -2
  93. package/dist/cjs/getNavigator.js.map +0 -1
  94. package/dist/cjs/guards.js +0 -2
  95. package/dist/cjs/guards.js.map +0 -1
  96. package/dist/cjs/helpers.js +0 -2
  97. package/dist/cjs/helpers.js.map +0 -1
  98. package/dist/cjs/internals.js +0 -2
  99. package/dist/cjs/internals.js.map +0 -1
  100. package/dist/cjs/limits.js +0 -2
  101. package/dist/cjs/limits.js.map +0 -1
  102. package/dist/cjs/namespaces/DependenciesNamespace/dependenciesStore.js +0 -2
  103. package/dist/cjs/namespaces/DependenciesNamespace/dependenciesStore.js.map +0 -1
  104. package/dist/cjs/namespaces/EventBusNamespace/EventBusNamespace.js +0 -2
  105. package/dist/cjs/namespaces/EventBusNamespace/EventBusNamespace.js.map +0 -1
  106. package/dist/cjs/namespaces/NavigationNamespace/InFlightNavigation.js +0 -2
  107. package/dist/cjs/namespaces/NavigationNamespace/InFlightNavigation.js.map +0 -1
  108. package/dist/cjs/namespaces/NavigationNamespace/NavigationNamespace.js +0 -2
  109. package/dist/cjs/namespaces/NavigationNamespace/NavigationNamespace.js.map +0 -1
  110. package/dist/cjs/namespaces/NavigationNamespace/constants.js +0 -2
  111. package/dist/cjs/namespaces/NavigationNamespace/constants.js.map +0 -1
  112. package/dist/cjs/namespaces/NavigationNamespace/transition/completeTransition.js +0 -2
  113. package/dist/cjs/namespaces/NavigationNamespace/transition/completeTransition.js.map +0 -1
  114. package/dist/cjs/namespaces/NavigationNamespace/transition/errorHandling.js +0 -2
  115. package/dist/cjs/namespaces/NavigationNamespace/transition/errorHandling.js.map +0 -1
  116. package/dist/cjs/namespaces/NavigationNamespace/transition/executeNavigation.js +0 -2
  117. package/dist/cjs/namespaces/NavigationNamespace/transition/executeNavigation.js.map +0 -1
  118. package/dist/cjs/namespaces/NavigationNamespace/transition/guardPhase.js +0 -2
  119. package/dist/cjs/namespaces/NavigationNamespace/transition/guardPhase.js.map +0 -1
  120. package/dist/cjs/namespaces/NavigationNamespace/transition/navigateToNotFound.js +0 -2
  121. package/dist/cjs/namespaces/NavigationNamespace/transition/navigateToNotFound.js.map +0 -1
  122. package/dist/cjs/namespaces/OptionsNamespace/OptionsNamespace.js +0 -2
  123. package/dist/cjs/namespaces/OptionsNamespace/OptionsNamespace.js.map +0 -1
  124. package/dist/cjs/namespaces/OptionsNamespace/constants.js +0 -2
  125. package/dist/cjs/namespaces/OptionsNamespace/constants.js.map +0 -1
  126. package/dist/cjs/namespaces/OptionsNamespace/helpers.js +0 -2
  127. package/dist/cjs/namespaces/OptionsNamespace/helpers.js.map +0 -1
  128. package/dist/cjs/namespaces/OptionsNamespace/validators.js +0 -2
  129. package/dist/cjs/namespaces/OptionsNamespace/validators.js.map +0 -1
  130. package/dist/cjs/namespaces/PluginsNamespace/PluginsNamespace.js +0 -2
  131. package/dist/cjs/namespaces/PluginsNamespace/PluginsNamespace.js.map +0 -1
  132. package/dist/cjs/namespaces/PluginsNamespace/constants.js +0 -2
  133. package/dist/cjs/namespaces/PluginsNamespace/constants.js.map +0 -1
  134. package/dist/cjs/namespaces/PluginsNamespace/validators.js +0 -2
  135. package/dist/cjs/namespaces/PluginsNamespace/validators.js.map +0 -1
  136. package/dist/cjs/namespaces/RouteLifecycleNamespace/RouteLifecycleNamespace.js +0 -2
  137. package/dist/cjs/namespaces/RouteLifecycleNamespace/RouteLifecycleNamespace.js.map +0 -1
  138. package/dist/cjs/namespaces/RouterLifecycleNamespace/RouterLifecycleNamespace.js +0 -2
  139. package/dist/cjs/namespaces/RouterLifecycleNamespace/RouterLifecycleNamespace.js.map +0 -1
  140. package/dist/cjs/namespaces/RouterLifecycleNamespace/constants.js +0 -2
  141. package/dist/cjs/namespaces/RouterLifecycleNamespace/constants.js.map +0 -1
  142. package/dist/cjs/namespaces/RoutesNamespace/RoutesNamespace.js +0 -2
  143. package/dist/cjs/namespaces/RoutesNamespace/RoutesNamespace.js.map +0 -1
  144. package/dist/cjs/namespaces/RoutesNamespace/constants.js +0 -2
  145. package/dist/cjs/namespaces/RoutesNamespace/constants.js.map +0 -1
  146. package/dist/cjs/namespaces/RoutesNamespace/forwardChain.js +0 -2
  147. package/dist/cjs/namespaces/RoutesNamespace/forwardChain.js.map +0 -1
  148. package/dist/cjs/namespaces/RoutesNamespace/helpers.js +0 -2
  149. package/dist/cjs/namespaces/RoutesNamespace/helpers.js.map +0 -1
  150. package/dist/cjs/namespaces/RoutesNamespace/routeGuards.js +0 -2
  151. package/dist/cjs/namespaces/RoutesNamespace/routeGuards.js.map +0 -1
  152. package/dist/cjs/namespaces/RoutesNamespace/routesStore.js +0 -2
  153. package/dist/cjs/namespaces/RoutesNamespace/routesStore.js.map +0 -1
  154. package/dist/cjs/namespaces/StateNamespace/StateNamespace.js +0 -2
  155. package/dist/cjs/namespaces/StateNamespace/StateNamespace.js.map +0 -1
  156. package/dist/cjs/pipeline/buildURL.js +0 -2
  157. package/dist/cjs/pipeline/buildURL.js.map +0 -1
  158. package/dist/cjs/pipeline/canonicalize.js +0 -2
  159. package/dist/cjs/pipeline/canonicalize.js.map +0 -1
  160. package/dist/cjs/pipeline/materialize.js +0 -2
  161. package/dist/cjs/pipeline/materialize.js.map +0 -1
  162. package/dist/cjs/routerFSM.js +0 -2
  163. package/dist/cjs/routerFSM.js.map +0 -1
  164. package/dist/cjs/transitionPath.js +0 -2
  165. package/dist/cjs/transitionPath.js.map +0 -1
  166. package/dist/cjs/utils/event-emitter/EventEmitter.js +0 -2
  167. package/dist/cjs/utils/event-emitter/EventEmitter.js.map +0 -1
  168. package/dist/cjs/utils/fsm/fsm.js +0 -2
  169. package/dist/cjs/utils/fsm/fsm.js.map +0 -1
  170. package/dist/cjs/utils/logger/RouterLogger.js +0 -2
  171. package/dist/cjs/utils/logger/RouterLogger.js.map +0 -1
  172. package/dist/cjs/utils/logger/constants.js +0 -2
  173. package/dist/cjs/utils/logger/constants.js.map +0 -1
  174. package/dist/cjs/wiring/wireNamespaces.js +0 -2
  175. package/dist/cjs/wiring/wireNamespaces.js.map +0 -1
  176. package/dist/esm/Router-sWkNlsWW.mjs +0 -2
@@ -1 +0,0 @@
1
- {"version":3,"file":"trieNodes.js","names":["parseSegment","createSegmentNode"],"sources":["../../../../../src/engine/path-matcher/registration/trieNodes.ts"],"sourcesContent":["// Trie node construction: param-name extraction + the param/splat child creators\n// (#736). Consumed by `trie` during insertion.\n\nimport { parseSegment } from \"../parseSegment\";\nimport { createSegmentNode } from \"../pathUtils\";\nimport { throwEmptyParamName, throwParamNameConflict } from \"./errors\";\n\nimport type { SegmentNode } from \"../types\";\n\n/**\n * Extracts the param name from a marker-led segment (`:name` / `*name`),\n * delegating the boundary to the canonical `parseSegment` tokenizer (#1324) so the\n * trie backstop, the route-tree gate, and `buildParamMeta` share ONE grammar and\n * cannot drift. `registerNode`'s per-segment grammar pre-pass has already rejected\n * every malformed form (name-less #858, trailing marker `:y*`/`:y:` #1324, fused\n * marker #1050, and the M1 removed optional/constraint forms), so a `:param` /\n * `*splat` name is guaranteed here — the error/`static` branches below are an\n * unreachable typed backstop. Single source for the param branch in\n * `processSegment` (the 3-token walk no longer forks, #1516).\n */\nexport function extractParamName(segment: string): string {\n const token = parseSegment(segment);\n\n // registerNode's per-segment grammar pass (Реш.2) rejects every malformed segment\n // — name-less (#858), trailing-marker (#1324), fused-marker (#1050), constraint\n // forms — before trie insertion, so a param|splat name is guaranteed here. The\n // error/`static` branches are unreachable, kept as a typed defensive backstop.\n // This ALSO relies on createNode's leading-`/` normalization (#1407): a\n // slash-less path (`a:`) let the trie's index-1 scan drop the leading char and\n // reach `:` here (a name-less marker) — normalization keeps that branch dead.\n /* v8 ignore start -- unreachable: registerNode's grammar pass rejects non-name segments first */\n if (\"error\" in token || token.kind === \"static\") {\n throwEmptyParamName();\n }\n /* v8 ignore stop */\n\n return token.name;\n}\n\n/**\n * Returns the param child of `node`, creating it on first use. A pre-existing\n * child with a *different* name is a #736 conflict (two routes binding the same\n * trie position under different names). With the 3-token grammar (M1) insertion is\n * a strict linear walk, so a single route never revisits a slot it created — the\n * former optional-omit `ownNodes` exception is gone with optional params.\n */\nexport function ensureParamChild(\n node: SegmentNode,\n paramName: string,\n): SegmentNode {\n if (!node.paramChild) {\n node.paramChild = { node: createSegmentNode(), name: paramName };\n } else if (node.paramChild.name !== paramName) {\n throwParamNameConflict(node.paramChild.name, paramName, \":\");\n }\n\n return node.paramChild.node;\n}\n\n/** Splat counterpart of {@link ensureParamChild}. */\nexport function ensureSplatChild(\n node: SegmentNode,\n splatName: string,\n): SegmentNode {\n if (!node.splatChild) {\n node.splatChild = { node: createSegmentNode(), name: splatName };\n } else if (node.splatChild.name !== splatName) {\n throwParamNameConflict(node.splatChild.name, splatName, \"*\");\n }\n\n return node.splatChild.node;\n}\n"],"mappings":"4FAoBA,SAAgB,EAAiB,EAAyB,CACxD,IAAM,EAAQA,EAAAA,aAAa,CAAO,EAelC,OALI,UAAW,GAAS,EAAM,OAAS,WACrC,EAAA,oBAAoB,EAIf,EAAM,IACf,CASA,SAAgB,EACd,EACA,EACa,CAOb,OANK,EAAK,WAEC,EAAK,WAAW,OAAS,GAClC,EAAA,uBAAuB,EAAK,WAAW,KAAM,EAAW,GAAG,EAF3D,EAAK,WAAa,CAAE,KAAMC,EAAAA,kBAAkB,EAAG,KAAM,CAAU,EAK1D,EAAK,WAAW,IACzB,CAGA,SAAgB,EACd,EACA,EACa,CAOb,OANK,EAAK,WAEC,EAAK,WAAW,OAAS,GAClC,EAAA,uBAAuB,EAAK,WAAW,KAAM,EAAW,GAAG,EAF3D,EAAK,WAAa,CAAE,KAAMA,EAAAA,kBAAkB,EAAG,KAAM,CAAU,EAK1D,EAAK,WAAW,IACzB"}
@@ -1,2 +0,0 @@
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
@@ -1 +0,0 @@
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"}
@@ -1,2 +0,0 @@
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
@@ -1 +0,0 @@
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"}
@@ -1,2 +0,0 @@
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
@@ -1 +0,0 @@
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":"wDA4BA,SAAS,EACP,EACA,EACA,EACM,CACF,IAAS,YACX,OAAO,eAAe,EAAQ,EAAM,CAClC,QACA,SAAU,GACV,WAAY,GACZ,aAAc,EAChB,CAAC,EAED,EAAO,GAAQ,CAEnB,CAYA,SAAS,EACP,EACA,EACA,EACA,EACM,CACN,GAAI,CAAC,OAAO,OAAO,EAAQ,CAAW,EAAG,CACvC,EACE,EACA,EACA,EAAc,CAAC,CAAY,EAAI,CACjC,EAEA,MACF,CAEA,IAAM,EAAe,EAAO,GAExB,MAAM,QAAQ,CAAY,EAC5B,EAAa,KAAK,CAAY,EAE9B,EAAY,EAAQ,EAAa,CAAC,EAAc,CAAY,CAAC,CAEjE,CAOA,SAAS,EACP,EACA,EACA,EACA,EACA,EACS,CAGT,OAAOA,EAAAA,OAFU,EAAW,EAAW,MAAM,EAAQ,EAAG,CAAG,EAAI,IAAA,GAEvC,CAAU,CACpC,CAWA,SAAS,EACP,EACA,EACA,EACe,CACf,IAAI,EAAI,EAAO,EACX,EAAQ,EACR,EAAW,GAEf,KAAO,EAAI,GAAO,CAChB,IAAM,EAAK,EAAW,YAAY,CAAC,EAEnC,GAAI,IAAO,GAET,OAAO,EAAW,EAAQ,KAG5B,GAAI,IAAO,IAAA,IAAa,GAAM,IAAM,GAAM,GAAI,CAC5C,EAAQ,EAAQ,IAAM,EAAK,IAC3B,EAAW,GACX,IAEA,QACF,CAEA,OAAO,IACT,CAEA,OAAO,IACT,CA+BA,SAAS,EACP,EACA,EACA,EACS,CACT,GAAM,CACJ,aACA,UACA,gBACA,QACA,MACA,WACA,eACE,EACE,EAAQ,EAAa,EAAY,EAAS,CAAa,EAE7D,GAAI,IAAU,KACZ,MAAO,GAGT,IAAM,EAAQ,EAAiB,EAAY,EAAO,EAAK,EAAU,CAAU,EACrE,EAAQ,EAAc,IAAI,CAAW,EAS3C,OAPI,IAAU,IAAA,GACZ,EAAc,IAAI,EAAa,CAAC,CAAC,EAAO,CAAK,CAAC,CAAC,EAE/C,EAAM,KAAK,CAAC,EAAO,CAAK,CAAC,EAIpB,EACT,CAWA,SAAS,EACP,EACA,EACA,EACA,EACA,EACA,EACA,EACM,CAKN,IAAM,EAAW,IAAU,IAAM,EAAQ,EAEnC,EAAgB,EAAW,EAAQ,EACrC,EAAU,EACV,EAAc,GAElB,IAAK,IAAI,EAAI,EAAO,EAAI,EAAe,IACjC,KAAW,YAAY,CAAC,IAAM,GAMlC,CADA,EAAU,EACV,EAAc,GAEd,KAFc,CAKhB,IAAM,EAAcC,EAAAA,YAAY,EAAW,MAAM,EAAO,CAAO,CAAC,EAK9D,SAAkB,IAAA,IAClB,GACA,EACE,CAAE,aAAY,UAAS,gBAAe,QAAO,MAAK,WAAU,aAAY,EACxE,EACA,CACF,GAMF,IAAI,CAAC,GAAe,GAAY,EAAW,MAAM,YAAa,CAC5D,IAAM,EAAW,EAAW,MAAM,EAAQ,EAAG,CAAG,EAC1C,EAAQ,EAAW,MAAM,YAAY,CAAQ,EAEnD,GAAI,EAAO,CACT,IAAK,IAAM,KAAQ,EACjB,EAAY,EAAQ,EAAaD,EAAAA,OAAO,EAAM,CAAU,EAAG,EAAI,EAGjE,MACF,CACF,CAUA,EAAY,EAAQ,EARC,EACnB,EACA,EACA,EACA,EACA,CAG0C,EAAG,CAAW,CAV1D,CAWF,CAyBA,MAAa,GACX,EACA,IAC4B,CAE5B,GAAI,IAAW,IAAM,IAAW,IAC9B,MAAO,CAAC,EAKV,IAAM,EAAkC,CAAC,EAIzC,OAFA,EAAkB,EAAQ,EAAQE,EAAAA,YAAY,CAAI,CAAC,CAAC,UAAU,EAEvD,CACT,EAQA,SAAS,EACP,EACA,EACA,EACM,CAGN,IAAM,EAAgB,EAAW,MAAM,QACnC,IAAI,IACJ,IAAA,GAEA,EAAQ,EACN,EAAS,EAAW,OAOtB,EAAU,GAEd,KAAO,EAAQ,GAAQ,CACrB,IAAI,EAAM,EAAW,QAAQ,IAAK,CAAK,EAEnC,IAAQ,KACV,EAAM,GASJ,EAAM,IAGJ,IAAY,IAAM,EAAU,IAC9B,EAAU,EAAW,QAAQ,IAAK,CAAK,GAGzC,EACE,EACA,EACA,EACA,EACA,EACA,EACA,CACF,GAGF,EAAQ,EAAM,CAChB,CAEA,GAAI,IAAkB,IAAA,GACpB,IAAK,GAAM,CAAC,EAAM,KAAU,EAE1B,EAAM,MAAM,EAAM,IAAU,EAAK,GAAK,EAAM,EAAE,EAC9C,EACE,EACA,EACA,EAAM,IAAK,GAAS,EAAK,EAAE,CAC7B,CAGN,CAsBA,MAAa,GACX,EACA,IACW,CAEX,IAAM,EAAO,OAAO,KAAK,CAAM,EAE/B,GAAI,EAAK,SAAW,EAClB,MAAO,GAGT,IAAM,EAAUA,EAAAA,YAAY,CAAI,EAI1B,EAAkB,CAAC,EAEzB,IAAK,IAAM,KAAO,EAAM,CACtB,IAAM,EAAQ,EAAO,GAGrB,GAAI,IAAU,IAAA,GACZ,SAGF,IAAM,EAAUC,EAAAA,OAAO,EAAK,EAAO,CAAO,EAGtC,GACF,EAAM,KAAK,CAAO,CAEtB,CAEA,OAAO,EAAM,KAAK,GAAG,CACvB"}
@@ -1,2 +0,0 @@
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
@@ -1 +0,0 @@
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"}
@@ -1,2 +0,0 @@
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
@@ -1 +0,0 @@
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"}
@@ -1,2 +0,0 @@
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
@@ -1 +0,0 @@
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"}
@@ -1,2 +0,0 @@
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
@@ -1 +0,0 @@
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"}
@@ -1,2 +0,0 @@
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 String(i)!==e||!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
@@ -1 +0,0 @@
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 // The stated criterion is round-trip stability, enforced directly rather than\n // through per-case guards: `build` prints a number with String(), so a value\n // may be coerced only when String() reproduces the exact text we parsed. This\n // subsumes the former negative-zero guard (String(-0) === \"0\" ≠ \"-0\", #898)\n // and closes the decimal family the safe-integer check below deliberately\n // exempts — trailing zeros (\"2.0\" → 2 → \"2\") and precision loss\n // (\"9007199254740993.5\" → 9007199254740994), both of which rebuilt a URL\n // different from the one that was matched. (#1565)\n if (String(num) !== value) {\n return null;\n }\n\n // Unsafe integers pass the text check (2**53 prints back exactly) but lose\n // precision in arithmetic, so they stay strings. Decimals are exempt: a\n // fractional value is never a \"safe integer\", and the check above already\n // proves its text round-trips.\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,EAsBxB,OAZI,OAAO,CAAG,IAAM,GAQhB,CAAC,OAAO,cAAc,CAAG,GAAK,CAAC,EAC1B,KAGF,CACT,CACF,EASa,EAAyD,CACpE,KAAM,EACN,KAAM,CACR"}
@@ -1,2 +0,0 @@
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
@@ -1 +0,0 @@
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"}
@@ -1,2 +0,0 @@
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
@@ -1 +0,0 @@
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":"+BAiBM,EAAqB,oBAKrB,EAAqB,KAKrB,EAAwB,IAM9B,SAAS,EAAmB,EAAwB,CAElD,GAAI,IAAU,KACZ,MAAO,OAGT,GAAI,OAAO,GAAU,SAAU,CAQ7B,GAAI,CACF,IAAM,EAAiB,EAAoC,YAG3D,GAAI,OAAO,GAAS,YAAc,EAAK,OAAS,SAC9C,OAAO,EAAK,MAAQ,QAExB,MAAQ,CAER,CAGA,MAAO,QACT,CAGA,OAAO,OAAO,CAChB,CASA,SAAS,EAAoB,EAAuC,CAClE,IAAK,IAAM,KAAO,OAAO,KAAK,CAAG,EAAG,CAClC,IAAM,EAAa,OAAO,yBAAyB,EAAK,CAAG,EAE3D,GAAI,IAAe,EAAW,KAAO,EAAW,KAC9C,MAAO,EAEX,CAEA,MAAO,EACT,CAUA,SAAS,EACP,EACA,EAC0C,CAC1C,GAAI,CAAC,GAAS,OAAO,GAAU,SAC7B,MAAU,UACR,WAAW,EAAW,iCAAiC,EAAmB,CAAK,GACjF,EAIF,IAAM,EAAuB,OAAO,eAAe,CAAK,EAExD,GAAI,IAAU,OAAO,WAAa,IAAU,KAC1C,MAAU,UACR,WAAW,EAAW,sCAAsC,EAAmB,CAAK,GACtF,EAIF,GAAI,EAAoB,CAAgC,EACtD,MAAU,UACR,WAAW,EAAW,yCACxB,CAEJ,CASA,SAAS,EACP,EACA,EACM,CACN,GACE,EAAM,eAAiB,IAAA,IACvB,OAAO,EAAM,cAAiB,WAE9B,MAAU,UACR,WAAW,EAAW,WAAW,OAAO,EAAM,IAAI,EAAE,kCACtD,CAEJ,CASA,SAAS,EACP,EACA,EACM,CACN,GACE,EAAM,eAAiB,IAAA,IACvB,OAAO,EAAM,cAAiB,WAE9B,MAAU,UACR,WAAW,EAAW,WAAW,OAAO,EAAM,IAAI,EAAE,kCACtD,CAEJ,CAeA,SAAS,EACP,EACA,EAC6D,CAC7D,GAAI,OAAO,EAAM,MAAS,SACxB,MAAU,UACR,WAAW,EAAW,qCAAqC,EAAmB,EAAM,IAAI,GAC1F,EAGF,IAAM,EAAO,EAAM,KAGnB,GAAI,IAAS,GACX,MAAU,UAAU,WAAW,EAAW,6BAA6B,EAIzE,GAAI,CAAC,EAAmB,KAAK,CAAI,EAC/B,MAAU,UACR,WAAW,EAAW,4CACxB,EAIF,GAAI,EAAK,OAAS,EAChB,MAAU,UACR,WAAW,EAAW,yCAAyC,EAAsB,YACvF,EAIE,MAAK,WAAW,IAAI,EAKxB,IAAI,EAAK,SAAS,GAAG,EACnB,MAAU,UACR,WAAW,EAAW,gBAAgB,EAAK,sFAE7C,EAIF,GAAI,CAAC,EAAmB,KAAK,CAAI,EAC/B,MAAU,UACR,WAAW,EAAW,wBAAwB,EAAK,uGAGrD,CATA,CAWJ,CAWA,SAAS,EACP,EACA,EACuB,CAGvB,GAAI,CAAC,EAAS,SAAS,GAAG,EACxB,OAAO,EAAS,SAAS,IAAI,CAAQ,EAGvC,IAAI,EAAiC,EAErC,IAAK,IAAM,KAAW,EAAS,MAAM,GAAG,EAGtC,GAFA,EAAU,EAAQ,SAAS,IAAI,CAAO,EAElC,CAAC,EACH,OAIJ,OAAO,CACT,CAUA,SAAS,EACP,EACA,EACA,EACM,CACN,GAAI,EAAmB,EAAU,CAAQ,EACvC,MAAU,MACR,WAAW,EAAW,WAAW,EAAS,iBAC5C,CAEJ,CAUA,SAAS,EACP,EACA,EACA,EACM,CACN,GAAI,EAAU,IAAI,CAAQ,EACxB,MAAU,MACR,WAAW,EAAW,qBAAqB,EAAS,WACtD,EAGF,EAAU,IAAI,CAAQ,CACxB,CAUA,SAAS,EACP,EACA,EACA,EACA,EACM,CACN,IAAM,EACJ,IAAe,GAAK,EAAW,EAAmB,EAAU,CAAU,EAEnE,KAIL,KAAK,IAAM,KAAS,EAAW,SAAS,OAAO,EAC7C,GAAI,EAAM,OAAS,EACjB,MAAU,MACR,WAAW,EAAW,UAAU,EAAU,qBAC5C,CAAA,CAGN,CAUA,SAAS,EACP,EACA,EACA,EACA,EACM,CACN,IAAM,EAAe,EAAkB,IAAI,CAAU,EAErD,GAAI,GAAc,IAAI,CAAS,EAC7B,MAAU,MACR,WAAW,EAAW,UAAU,EAAU,qBAC5C,EAGE,EACF,EAAa,IAAI,CAAS,EAE1B,EAAkB,IAAI,EAAY,IAAI,IAAI,CAAC,CAAS,CAAC,CAAC,CAE1D,CAkCA,SAAgB,EACd,EACA,EACA,EACA,EAAa,GACb,EACA,EACkC,CAClC,EAAkB,EAAO,CAAU,EAEnC,IAAM,EAAW,EAGjB,EAAkB,EAAU,CAAU,EAGtC,EAAA,kBAAkB,EAAS,KAAM,EAAS,KAAM,EAAY,CAAQ,EAGpE,EAAqB,EAAU,CAAU,EACzC,EAAqB,EAAU,CAAU,EAEzC,IAAM,EAAY,EAAS,KACrB,EAAW,EAAa,GAAG,EAAW,GAAG,IAAc,EAGzD,GAAY,GACd,EAAuB,EAAU,EAAU,CAAU,EAInD,GACF,EAAwB,EAAW,EAAU,CAAU,EAGzD,IAAM,EAAY,EAAS,KACrB,EAAkB,EAkBxB,GAfI,GACF,EAAuB,EAAU,EAAiB,EAAW,CAAU,EAIrE,GACF,EACE,EACA,EACA,EACA,CACF,EAIE,EAAS,WAAa,IAAA,GAAW,CACnC,GAAI,CAAC,MAAM,QAAQ,EAAS,QAAQ,EAClC,MAAU,UACR,WAAW,EAAW,WAAW,EAAU,mCAAmC,EAAmB,EAAS,QAAQ,GACpH,EAGF,IAAK,IAAM,KAAS,EAAS,SAC3B,EACE,EACA,EACA,EACA,EACA,EACA,CACF,CAEJ,CACF"}
@@ -1,2 +0,0 @@
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){for(let o of e)if(t.INVALID_QUERY_NAME_RGX.test(o))throw n(i,`Invalid path for route "${r}": invalid query-param name "${o}" in "${a}" (a query-param name cannot contain '<' or '>' — it would never round-trip; rename the query param)`)}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(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
@@ -1 +0,0 @@
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 queryParams: readonly string[],\n routeName: string,\n methodName: string,\n path: string,\n): void {\n // A path/query name collision (`/a/:tab?tab`) is legal under M2: `tab` lives\n // in both `state.params` and `state.search` as separate channels (RFC-4 M2 /\n // #1548), so the former \"declared as both\" rejection is gone. Only a query\n // name that can never round-trip (contains `<`/`>`) is still rejected.\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}\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): a query name with `<>`\n // (never round-trips). Name collisions with a path param are legal under M2\n // (separate params/search channels, #1548).\n validateQueryParamDeclarations(queryParams, routeName, methodName, path);\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":"kGAkBA,SAAS,EAAkB,EAAoB,EAA4B,CACzE,OAAW,UAAU,WAAW,EAAW,IAAI,GAAS,CAC1D,CAeA,SAAS,EACP,EACA,EACA,EACA,EACM,CACN,IAAM,EAAO,IAAI,IAEjB,IAAK,IAAM,KAAQ,EAAW,CAC5B,GAAI,EAAK,IAAI,CAAI,EACf,MAAM,EACJ,EACA,2BAA2B,EAAU,gCAAgC,EAAK,QAAQ,EAAK,8FACzF,EAGF,EAAK,IAAI,CAAI,CACf,CACF,CAYA,SAAS,EACP,EACA,EACA,EACA,EACM,CAKN,IAAK,IAAM,KAAQ,EACjB,GAAIA,EAAAA,uBAAuB,KAAK,CAAI,EAClC,MAAM,EACJ,EACA,2BAA2B,EAAU,+BAA+B,EAAK,QAAQ,EAAK,qGACxF,CAGN,CAYA,SAAS,EAAkB,EAAuB,CAChD,IAAI,EAAiB,GACjB,EAAkB,GAEtB,IAAK,IAAM,KAAQ,EAAM,CACvB,GAAI,IAAS,IAAK,CAChB,EAAiB,GAEjB,QACF,CAQA,GANA,AAEE,KADA,EAAkB,IAAS,KAAO,IAAS,IAC1B,IAIf,CAAC,GAAmB,EAAK,YAAY,CAAC,GAAM,IAC9C,MAAO,EAEX,CAEA,MAAO,EACT,CAQA,SAAS,EACP,EACA,EACA,EACQ,CACR,OAAQ,EAAR,CACE,IAAK,YACL,IAAK,kBACH,MAAO,2BAA2B,EAAU,sDAAsD,EAAK,GAEzG,IAAK,eACH,MAAO,2BAA2B,EAAU,8DAA8D,EAAK,oEAEnH,CACF,CASA,SAAS,EAAmB,EAAsB,EAA2B,CAK3E,OAJI,EAAQ,OAAS,mBACZ,2BAA2B,EAAU,0CAA0C,EAAQ,QAAQ,0CAA0C,EAAQ,eAAe,SAAS,EAAQ,aAAa,uDAGhM,2BAA2B,EAAU,uFAAuF,EAAQ,QAAQ,oGACrJ,CA0CA,SAAgB,EACd,EACA,EACA,EACA,EACwB,CAExB,GAAI,OAAO,GAAS,SAAU,CAE5B,IAAI,EAUJ,KARA,CAKE,EALE,IAAS,KACA,OACF,MAAM,QAAQ,CAAI,EAChB,QAEA,OAAO,EAGd,EACJ,EACA,oCAAoC,GACtC,CACF,CAGA,GAAI,IAAS,GACX,OAIF,GAAI,KAAK,KAAK,CAAI,EAChB,MAAM,EACJ,EACA,2BAA2B,EAAU,gCAAgC,EAAK,EAC5E,EAIF,GAAI,CAAC,kBAAkB,KAAK,CAAI,EAC9B,MAAM,EACJ,EACA,UAAU,EAAU,8BAA8B,EAAK,kEACzD,EAIF,GAAI,EAAK,SAAS,IAAI,EACpB,MAAM,EACJ,EACA,2BAA2B,EAAU,oCAAoC,EAAK,EAChF,EAOF,GAAM,CAAE,cAAa,YAAW,eAAgBC,EAAAA,eAAe,CAAI,EAGnE,EAAyB,EAAW,EAAW,EAAY,CAAI,EAK/D,EAA+B,EAAa,EAAW,EAAY,CAAI,EAMvE,IAAM,EAAUC,EAAAA,oBAAoB,CAAW,EAE/C,GAAI,IAAY,IAAA,GACd,MAAM,EAAkB,EAAY,EAAmB,EAAS,CAAS,CAAC,EAQ5E,IAAM,EAAeC,EAAAA,wBAAwB,CAAW,EAExD,GAAI,IAAiB,IAAA,GACnB,MAAM,EACJ,EACA,EACE,EAIA,EACA,CACF,CACF,EAOF,GAAI,EAAkB,CAAW,EAC/B,MAAM,EACJ,EACA,2BAA2B,EAAU,kCAAkC,EAAK,0JAC9E,EAKF,IAAM,EAAe,GAAc,EAAW,UAAU,UAAU,OAAS,EAE3E,GAAI,EAAK,WAAW,GAAG,GAAK,EAC1B,MAAM,EACJ,EACA,kBAAkB,EAAK,wDACzB,CAEJ"}
@@ -1,2 +0,0 @@
1
- const e=new WeakMap,t=t=>{let n=e.get(t);return n||(n=Object.freeze({navigate:t.navigate,getState:t.getState,isActiveRoute:t.isActiveRoute,canNavigateTo:t.canNavigateTo,subscribe:t.subscribe,subscribeLeave:t.subscribeLeave,isLeaveApproved:t.isLeaveApproved}),e.set(t,n)),n};exports.getNavigator=t;
2
- //# sourceMappingURL=getNavigator.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"getNavigator.js","names":[],"sources":["../../src/getNavigator.ts"],"sourcesContent":["import type { Navigator, DefaultDependencies, Router } from \"./types\";\n\nconst cache = new WeakMap<Router, Navigator>();\n\nexport const getNavigator = <\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n router: Router<Dependencies>,\n): Navigator => {\n let nav = cache.get(router);\n\n if (!nav) {\n nav = Object.freeze({\n navigate: router.navigate,\n getState: router.getState,\n isActiveRoute: router.isActiveRoute,\n canNavigateTo: router.canNavigateTo,\n subscribe: router.subscribe,\n subscribeLeave: router.subscribeLeave,\n isLeaveApproved: router.isLeaveApproved,\n } as Navigator);\n cache.set(router, nav);\n }\n\n return nav;\n};\n"],"mappings":"AAEA,MAAM,EAAQ,IAAI,QAEL,EAGX,GACc,CACd,IAAI,EAAM,EAAM,IAAI,CAAM,EAe1B,OAbK,IACH,EAAM,OAAO,OAAO,CAClB,SAAU,EAAO,SACjB,SAAU,EAAO,SACjB,cAAe,EAAO,cACtB,cAAe,EAAO,cACtB,UAAW,EAAO,UAClB,eAAgB,EAAO,eACvB,gBAAiB,EAAO,eAC1B,CAAc,EACd,EAAM,IAAI,EAAQ,CAAG,GAGhB,CACT"}
@@ -1,2 +0,0 @@
1
- function e(e){if(!e||typeof e!=`object`||e.constructor!==Object)throw TypeError(`dependencies must be a plain object`);for(let t in e)if(Object.getOwnPropertyDescriptor(e,t)?.get)throw TypeError(`dependencies cannot contain getters: "${t}"`)}function t(e,n){for(let r of e){let e=r;if(typeof e!=`object`||!e||Array.isArray(e))throw TypeError(`route must be a non-array object`);n?.routes.guardRouteCallbacks(r),n?.routes.guardNoAsyncCallbacks(r);let i=r.children;i&&t(i,n)}}const n=new Set([`all`,`warn-error`,`error-only`,`none`]);function r(e){return typeof e==`string`&&n.has(e)}function i(e){return typeof e==`string`?`"${e}"`:typeof e==`object`?JSON.stringify(e):String(e)}function a(e){if(typeof e!=`object`)throw TypeError(`Logger config must be an object`);let t=e;for(let e of Object.keys(t))if(e!==`level`&&e!==`callback`&&e!==`callbackIgnoresLevel`)throw TypeError(`Unknown logger config property: "${e}"`);if(`level`in t&&t.level!==void 0&&!r(t.level))throw TypeError(`Invalid logger level: ${i(t.level)}. Expected: "all" | "warn-error" | "error-only" | "none"`);if(`callback`in t&&t.callback!==void 0&&typeof t.callback!=`function`)throw TypeError(`Logger callback must be a function, got ${typeof t.callback}`);if(`callbackIgnoresLevel`in t&&t.callbackIgnoresLevel!==void 0&&typeof t.callbackIgnoresLevel!=`boolean`)throw TypeError(`Logger callbackIgnoresLevel must be a boolean, got ${typeof t.callbackIgnoresLevel}`)}exports.assertLoggerConfig=a,exports.guardDependencies=e,exports.guardRouteStructure=t;
2
- //# sourceMappingURL=guards.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"guards.js","names":[],"sources":["../../src/guards.ts"],"sourcesContent":["// packages/core/src/guards.ts\n\nimport type { LoggerConfig, LogLevelConfig, Route } from \"./types\";\nimport type { RouterValidator } from \"./types/RouterValidator\";\n\n// ============================================================================\n// Structural invariant guards (dependencies + route-tree shape)\n// ============================================================================\n\nexport function guardDependencies(deps: unknown): void {\n if (\n !deps ||\n typeof deps !== \"object\" ||\n (deps as { constructor: unknown }).constructor !== Object\n ) {\n throw new TypeError(\"dependencies must be a plain object\");\n }\n for (const key in deps as Record<string, unknown>) {\n if (Object.getOwnPropertyDescriptor(deps, key)?.get) {\n throw new TypeError(`dependencies cannot contain getters: \"${key}\"`);\n }\n }\n}\n\n/* eslint-disable @typescript-eslint/no-explicit-any -- accepts any Route type */\nexport function guardRouteStructure(\n routes: Route<any>[],\n validator?: RouterValidator | null,\n): void {\n /* eslint-enable @typescript-eslint/no-explicit-any */\n for (const route of routes) {\n const routeValue: unknown = route;\n\n if (\n routeValue === null ||\n typeof routeValue !== \"object\" ||\n Array.isArray(routeValue)\n ) {\n throw new TypeError(\"route must be a non-array object\");\n }\n\n validator?.routes.guardRouteCallbacks(route as Route);\n validator?.routes.guardNoAsyncCallbacks(route as Route);\n const children = (route as Route).children;\n\n if (children) {\n guardRouteStructure(children, validator);\n }\n }\n}\n\n// ============================================================================\n// Logger config assertion (RealRouter-specific)\n// ============================================================================\n\nconst VALID_LEVELS_SET = new Set<string>([\n \"all\",\n \"warn-error\",\n \"error-only\",\n \"none\",\n]);\n\nfunction isValidLevel(value: unknown): value is LogLevelConfig {\n return typeof value === \"string\" && VALID_LEVELS_SET.has(value);\n}\n\nfunction formatValue(value: unknown): string {\n if (typeof value === \"string\") {\n return `\"${value}\"`;\n }\n if (typeof value === \"object\") {\n return JSON.stringify(value);\n }\n\n // eslint-disable-next-line @typescript-eslint/no-base-to-string\n return String(value);\n}\n\nexport function assertLoggerConfig(\n config: unknown,\n): asserts config is LoggerConfig {\n if (typeof config !== \"object\") {\n throw new TypeError(\"Logger config must be an object\");\n }\n\n // `typeof null === \"object\"`, so TS still sees `object | null` here — but the\n // sole caller (Router's ctor) gates on `if (loggerConfig)`, so null/falsy never\n // arrives; treat it as the non-null object the gate guarantees.\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- gated caller never passes null\n const obj = config!;\n\n // Check for unknown properties\n for (const key of Object.keys(obj)) {\n if (\n key !== \"level\" &&\n key !== \"callback\" &&\n key !== \"callbackIgnoresLevel\"\n ) {\n throw new TypeError(`Unknown logger config property: \"${key}\"`);\n }\n }\n\n // Validate level if present\n if (\"level\" in obj && obj.level !== undefined && !isValidLevel(obj.level)) {\n throw new TypeError(\n `Invalid logger level: ${formatValue(obj.level)}. Expected: \"all\" | \"warn-error\" | \"error-only\" | \"none\"`,\n );\n }\n\n // Validate callback if present\n if (\n \"callback\" in obj &&\n obj.callback !== undefined &&\n typeof obj.callback !== \"function\"\n ) {\n throw new TypeError(\n `Logger callback must be a function, got ${typeof obj.callback}`,\n );\n }\n\n // Validate callbackIgnoresLevel if present (logger.configure does not type-check it)\n if (\n \"callbackIgnoresLevel\" in obj &&\n obj.callbackIgnoresLevel !== undefined &&\n typeof obj.callbackIgnoresLevel !== \"boolean\"\n ) {\n throw new TypeError(\n `Logger callbackIgnoresLevel must be a boolean, got ${typeof obj.callbackIgnoresLevel}`,\n );\n }\n}\n"],"mappings":"AASA,SAAgB,EAAkB,EAAqB,CACrD,GACE,CAAC,GACD,OAAO,GAAS,UACf,EAAkC,cAAgB,OAEnD,MAAU,UAAU,qCAAqC,EAE3D,IAAK,IAAM,KAAO,EAChB,GAAI,OAAO,yBAAyB,EAAM,CAAG,CAAC,EAAE,IAC9C,MAAU,UAAU,yCAAyC,EAAI,EAAE,CAGzE,CAGA,SAAgB,EACd,EACA,EACM,CAEN,IAAK,IAAM,KAAS,EAAQ,CAC1B,IAAM,EAAsB,EAE5B,GAEE,OAAO,GAAe,WADtB,GAEA,MAAM,QAAQ,CAAU,EAExB,MAAU,UAAU,kCAAkC,EAGxD,GAAW,OAAO,oBAAoB,CAAc,EACpD,GAAW,OAAO,sBAAsB,CAAc,EACtD,IAAM,EAAY,EAAgB,SAE9B,GACF,EAAoB,EAAU,CAAS,CAE3C,CACF,CAMA,MAAM,EAAmB,IAAI,IAAY,CACvC,MACA,aACA,aACA,MACF,CAAC,EAED,SAAS,EAAa,EAAyC,CAC7D,OAAO,OAAO,GAAU,UAAY,EAAiB,IAAI,CAAK,CAChE,CAEA,SAAS,EAAY,EAAwB,CAS3C,OARI,OAAO,GAAU,SACZ,IAAI,EAAM,GAEf,OAAO,GAAU,SACZ,KAAK,UAAU,CAAK,EAItB,OAAO,CAAK,CACrB,CAEA,SAAgB,EACd,EACgC,CAChC,GAAI,OAAO,GAAW,SACpB,MAAU,UAAU,iCAAiC,EAOvD,IAAM,EAAM,EAGZ,IAAK,IAAM,KAAO,OAAO,KAAK,CAAG,EAC/B,GACE,IAAQ,SACR,IAAQ,YACR,IAAQ,uBAER,MAAU,UAAU,oCAAoC,EAAI,EAAE,EAKlE,GAAI,UAAW,GAAO,EAAI,QAAU,IAAA,IAAa,CAAC,EAAa,EAAI,KAAK,EACtE,MAAU,UACR,yBAAyB,EAAY,EAAI,KAAK,EAAE,yDAClD,EAIF,GACE,aAAc,GACd,EAAI,WAAa,IAAA,IACjB,OAAO,EAAI,UAAa,WAExB,MAAU,UACR,2CAA2C,OAAO,EAAI,UACxD,EAIF,GACE,yBAA0B,GAC1B,EAAI,uBAAyB,IAAA,IAC7B,OAAO,EAAI,sBAAyB,UAEpC,MAAU,UACR,sDAAsD,OAAO,EAAI,sBACnE,CAEJ"}
@@ -1,2 +0,0 @@
1
- const e=require("./constants.js");function t(e,t){if(e===void 0)return n(t);let r={};for(let t in e)Object.hasOwn(e,t)&&e[t]!==void 0&&(r[t]=e[t]);if(t!==void 0)for(let e in t)Object.hasOwn(t,e)&&t[e]!==void 0&&(r[e]=t[e]);return r}function n(e){if(e===void 0)return;let t;for(let n in e)Object.hasOwn(e,n)&&e[n]===void 0&&(t??={...e},delete t[n]);return t??e}const r=new Set([`string`,`number`,`boolean`]);function i(e){return r.has(typeof e)}function a(e,t){if(e===t)return!0;if(Array.isArray(e)){if(!Array.isArray(t))return e.length===1&&a(e[0],t);if(e.length!==t.length)return!1;for(let n=0;n<e.length;n++)if(!a(e[n],t[n]))return!1;return!0}return Array.isArray(t)?t.length===1&&a(e,t[0]):i(e)&&i(t)&&String(e)===String(t)}function o(e){return Object.freeze(e)}function s(e,n,r,i=!1){if(e!==void 0)return Object.freeze(t(e,n));if(n===void 0||n===r)return r;if(i)return Object.freeze(n);let a=t(void 0,n);return Object.freeze(a===n?{...n}:a)}function c(t){if(t===void 0)return t;let n;for(let e in t){if(!Object.hasOwn(t,e))continue;let r=t[e];r!==void 0&&(n??={},n[e]=r)}return n??e.EMPTY_PARAMS}exports.areParamValuesEqual=a,exports.freezeStateShell=o,exports.mergeDefined=t,exports.mergeWithDefault=s,exports.normalizeParams=c;
2
- //# sourceMappingURL=helpers.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"helpers.js","names":["EMPTY_PARAMS"],"sources":["../../src/helpers.ts"],"sourcesContent":["// packages/core/src/helpers.ts\n\nimport { EMPTY_PARAMS } from \"./constants\";\n\nimport type { Params, State } from \"./types\";\n\n// =============================================================================\n// Default merge — `undefined` ≡ absence (#1550 / #1551)\n// =============================================================================\n\n/**\n * Merges a route default UNDER a value (the value wins), treating `undefined` as\n * **absence on both sides** (#1550 / #1551).\n *\n * A key survives only when its winning value is defined:\n * - `mergeDefined({ page: \"1\" }, { page: undefined })` → `{ page: \"1\" }` — an\n * explicit `undefined` from the caller does not outrank the default (this is\n * what the path channel always did via `normalizeParams`, and what the query\n * channel did not, #1550);\n * - `mergeDefined({ q: undefined }, undefined)` → `{}` — a default that itself\n * carries `undefined` behaves exactly like no default entry, instead of\n * leaking an `undefined`-valued own key into the frozen state (#1551).\n *\n * Because the rule lives in the merge rather than in a separately-ordered\n * \"normalize\" stage, it holds for every producer and cannot be reintroduced by\n * whichever side is merged last.\n *\n * Allocation contract: **may return the `value` argument itself** when there is\n * no default and nothing to strip (the hot path — callers pass an\n * already-normalized bag), so a caller that freezes or stores the result must\n * copy it first. `undefined` in ⇒ `undefined` out when there is no default, which\n * keeps the matcher's single-bag fallback (`search ?? params`) reachable.\n */\nexport function mergeDefined<T extends Record<string, unknown>>(\n defaultValue: T,\n value: T | undefined,\n): T;\n\nexport function mergeDefined<T extends Record<string, unknown>>(\n defaultValue: T | undefined,\n value: T,\n): T;\n\nexport function mergeDefined<T extends Record<string, unknown>>(\n defaultValue: T | undefined,\n value: T | undefined,\n): T | undefined;\n\nexport function mergeDefined<T extends Record<string, unknown>>(\n defaultValue: T | undefined,\n value: T | undefined,\n): T | undefined {\n if (defaultValue === undefined) {\n return stripUndefined(value);\n }\n\n const merged: Record<string, unknown> = {};\n\n for (const key in defaultValue) {\n if (Object.hasOwn(defaultValue, key) && defaultValue[key] !== undefined) {\n merged[key] = defaultValue[key];\n }\n }\n\n if (value !== undefined) {\n for (const key in value) {\n if (!Object.hasOwn(value, key)) {\n continue;\n }\n\n // `undefined` means \"I said nothing\", so the default keeps the slot.\n if (value[key] === undefined) {\n continue;\n }\n\n merged[key] = value[key];\n }\n }\n\n return merged as T;\n}\n\n/**\n * Drops `undefined`-valued own keys, returning the input **unchanged** when there\n * are none (no allocation on the common path). `undefined` in ⇒ `undefined` out —\n * unlike {@link normalizeParams}, which collapses an all-`undefined` bag to the\n * shared `EMPTY_PARAMS` singleton and is the path-channel entry guard.\n */\nfunction stripUndefined<T extends Record<string, unknown>>(\n value: T | undefined,\n): T | undefined {\n if (value === undefined) {\n return undefined;\n }\n\n let stripped: Record<string, unknown> | undefined;\n\n for (const key in value) {\n if (!(Object.hasOwn(value, key) && value[key] === undefined)) {\n continue;\n }\n\n stripped ??= { ...value };\n\n delete stripped[key];\n }\n\n return (stripped as T | undefined) ?? value;\n}\n\n// =============================================================================\n// Param value comparison (#1554)\n// =============================================================================\n\n/** The value types a channel prints into (and parses back from) a URL. */\nconst PRINTABLE_TYPES = new Set([\"string\", \"number\", \"boolean\"]);\n\n/** A value the two channels can carry across a URL round-trip. */\nfunction isPrintableScalar(value: unknown): value is string | number | boolean {\n return PRINTABLE_TYPES.has(typeof value);\n}\n\n/**\n * Compares two param / query values for equality **independently of where they\n * came from** (#1554).\n *\n * The two directions produce different value DOMAINS for the same location: the\n * URL direction parses (`?page=2` → `2`, `?a=1&a=2` → `[1, 2]`, a path slot is\n * always a string), the intent direction keeps whatever the caller supplied\n * (`{ page: \"2\" }` stays a string). Both build the SAME `state.path`, so a\n * `===`-based comparison reported a URL-derived state and an intent-derived\n * state on one location as UNEQUAL — an active link rendered inactive.\n *\n * The rule is therefore \"equal when both values print the same query string\":\n * - **scalars** (string / number / boolean) compare by their printed form, so\n * `2 ≡ \"2\"` and `true ≡ \"true\"`;\n * - **arrays** compare element-wise under the same rule, and a **singleton\n * array** compares against a bare scalar (`[\"1\"]` and `1` both print `?a=1`);\n * - everything else (`null`, `undefined`, objects) keeps strict semantics —\n * those print differently (`?a` vs `?a=` vs nothing at all), so tolerating\n * them would equate genuinely different URLs.\n *\n * Value normalization is deliberately NOT done: `state.search` keeps the mixed\n * domain (RFC-4 M2 / §10.14 decision (б)) and comparison is the single place\n * that knows the two domains describe the same location. Unifying the domain\n * itself belongs to the typed search-schema stage.\n */\nexport function areParamValuesEqual(val1: unknown, val2: unknown): boolean {\n if (val1 === val2) {\n return true;\n }\n\n if (Array.isArray(val1)) {\n // A singleton array prints exactly like its element (`[\"1\"]` and `1` both\n // print `?a=1`), so compare across the shape instead of rejecting on it.\n if (!Array.isArray(val2)) {\n return val1.length === 1 && areParamValuesEqual(val1[0], val2);\n }\n\n if (val1.length !== val2.length) {\n return false;\n }\n\n // eslint-disable-next-line unicorn/no-for-loop -- hot path: for-of entries() allocates iterator per recursive call\n for (let i = 0; i < val1.length; i++) {\n if (!areParamValuesEqual(val1[i], val2[i])) {\n return false;\n }\n }\n\n return true;\n }\n\n if (Array.isArray(val2)) {\n return val2.length === 1 && areParamValuesEqual(val1, val2[0]);\n }\n\n return (\n isPrintableScalar(val1) &&\n isPrintableScalar(val2) &&\n String(val1) === String(val2)\n );\n}\n\n// =============================================================================\n// State Helpers\n// =============================================================================\n\n/**\n * Freezes the State object's own level — the SHELL, not the state.\n *\n * Named for what it does after #1599: it used to be called\n * `freezeStateInPlace`, which promised a depth it has never delivered, and\n * `CLAUDE.md` described it as \"consolidated into one recursive traversal\" long\n * after the traversal was gone. It blocks reassignment of `name` / `params` /\n * `search` / `path` / `transition` / `context` and nothing more.\n *\n * **The depth is a POLICY, not this function's job: every object is frozen once,\n * where it is created.** That is deliberate and measured — re-freezing an\n * already-frozen object costs ~8 ns, so a recursive walk would pay per node for\n * work its producers already did. The four producers and what each owns:\n *\n * - `params` — {@link mergeWithDefault} on the slow path; on the fast one there is\n * no merge to freeze it, so `pipeline/materialize` does at the publication\n * boundary (#1598), before its own `skipFreeze` branch\n * - `search` — the `EMPTY_SEARCH` singleton, or `admittedSearch`\n * (`channels/modeGate.ts`) on its DROP branch, the one branch that builds a bag\n * the caller did not already freeze\n * - `transition` + nested — `buildTransitionMeta()` (or inline in\n * `navigateToNotFound()`)\n * - the shell — here\n *\n * `state.context` is **intentionally not frozen** — plugins write to it via\n * `claim.write(state, value)` after state creation.\n *\n * The whole matrix is pinned black-box in\n * `tests/functional/error/helpers.test.ts` (\"state immutability across every\n * producer\"), mutationally validated against all four sites. Before #1599 two of\n * them were unguarded: deleting the `canonicalize` freeze left the entire suite\n * green, and the mode gate's freeze was reachable only under a non-`loose` mode\n * with one key dropped AND one admitted.\n *\n * @internal\n */\nexport function freezeStateShell<T extends State>(state: T): T {\n // `Object.freeze` returns non-objects (incl. null/undefined) unchanged, so the\n // former `if (!state) return state` guard was redundant — callers also gate it\n // (`state ? freezeStateShell(state) : undefined`) and `T extends State` is\n // typed non-null.\n return Object.freeze(state);\n}\n\n/**\n * Merges a channel's route default UNDER a routed value (the value wins) and\n * freezes the result. Reuses the shared frozen `empty` singleton (EMPTY_PARAMS /\n * EMPTY_SEARCH, #1027) when there is neither a default nor a value — so the hot\n * path (no defaults, empty params) allocates zero objects. A defaulted channel\n * always spreads (a fresh frozen object); an undefined-default channel freezes a\n * copy of the value (never the caller's object).\n *\n * `undefined` is absence on BOTH sides (`mergeDefined`, #1550 / #1551): an\n * explicitly-`undefined` caller value leaves the default in place, and a default\n * that carries `undefined` behaves like no entry — so the frozen state never\n * exposes an `undefined`-valued own key on either channel.\n *\n * `valueIsOwned` says the caller minted `value` itself and nothing else holds a\n * reference — then the defensive copy is skipped and the bag is frozen in place\n * (#1589). Only `canonicalize`'s PATH channel may pass it, because only there is\n * the value the fresh object `normalizeParams` just returned. Passing it for a\n * bag that came from user code would freeze the caller's object.\n *\n * Lives here, not in a namespace, because the rule outlived its call count:\n * stage ③ (`applyDefaults`) had TWO callers when the pipeline landed\n * (`StateNamespace.makeState` and `pipeline/canonicalize`) and has ONE since\n * Phase 4 folded the first onto the second — but the chain fold in\n * `RoutesNamespace` still layers hop defaults through `mergeDefined` directly,\n * so a second copy of \"default under value\" would be a second source of truth\n * for the rule, the same drift trap #1550/#1551 closed\n * by collapsing the four merge sites onto `mergeDefined`.\n *\n * @internal\n */\nexport function mergeWithDefault(\n defaultValue: Record<string, unknown> | undefined,\n value: Record<string, unknown> | undefined,\n empty: Readonly<Record<string, never>>,\n valueIsOwned = false,\n): Readonly<Record<string, unknown>> {\n if (defaultValue !== undefined) {\n return Object.freeze(mergeDefined(defaultValue, value));\n }\n\n if (value === undefined || value === empty) {\n return empty;\n }\n\n // OWNED value: freeze in place. The copy below exists solely so the CALLER's\n // bag is never frozen out from under it — when the bag was minted one line\n // earlier by `normalizeParams` (which always returns a fresh object or the\n // frozen `empty` singleton, never its input) there is no caller to protect,\n // and `undefined` values are already stripped, so `mergeDefined`'s walk is\n // redundant too. Measured on #1589: without this the path channel is copied\n // TWICE per producer call — once to normalize, once to freeze — on `navigate`,\n // `buildPath`, `matchPath`, `isActiveRoute` and `canNavigateTo` alike.\n if (valueIsOwned) {\n return Object.freeze(value);\n }\n\n // `mergeDefined` returns the argument itself when there is nothing to strip,\n // so copy before freezing — the caller's bag must never be frozen.\n const defined = mergeDefined(undefined, value);\n\n return Object.freeze(defined === value ? { ...value } : defined);\n}\n\n// =============================================================================\n// Params Helpers\n// =============================================================================\n\n/**\n * Strips `undefined` values from a params object before handoff to the query\n * string engine and state storage.\n *\n * **Why this exists:** `router.navigate(name, { x: undefined })` must not put\n * `x` into the resulting URL (publicly documented contract). The underlying\n * query engine (`search-params`) already does this, but the contract belongs\n * to `@real-router/core` — this function guarantees it at the core boundary\n * so that:\n * - Plugin interceptors on `forwardState` that inject `undefined` values are\n * caught before they reach the engine\n * - `state.params` never contains `undefined` values (roundtrip consistent\n * with URL)\n * - The contract is verifiable at core's own test surface (doesn't depend on\n * engine behavior for regression detection)\n *\n * Single pass. When nothing survives (empty input, or every value `undefined`)\n * it returns the shared frozen `EMPTY_PARAMS` singleton, so the merge's\n * `value === empty` reuse branch (`mergeWithDefault`) fires and an empty-params\n * navigation allocates zero transient `{}` (#1027); a non-empty input returns a fresh\n * object. Either way reference identity is not preserved across calls, and the\n * result MUST be treated as read-only — callers must not mutate it (the empty\n * case is a shared frozen singleton).\n */\nexport function normalizeParams(params: Params): Params;\n\nexport function normalizeParams(params: undefined): undefined;\n\nexport function normalizeParams(params: Params | undefined): Params | undefined;\n\nexport function normalizeParams(\n params: Params | undefined,\n): Params | undefined {\n if (params === undefined) {\n return params;\n }\n\n let normalized: Params | undefined;\n\n for (const key in params) {\n if (!Object.hasOwn(params, key)) {\n continue;\n }\n\n const value = params[key];\n\n if (value !== undefined) {\n // Lazy allocation: an all-empty / all-undefined input costs zero objects.\n normalized ??= {};\n normalized[key] = value;\n }\n }\n\n // Reuse the shared singleton when nothing survived so the merge's\n // `value === empty` reuse branch fires (#1027).\n return normalized ?? EMPTY_PARAMS;\n}\n"],"mappings":"kCAgDA,SAAgB,EACd,EACA,EACe,CACf,GAAI,IAAiB,IAAA,GACnB,OAAO,EAAe,CAAK,EAG7B,IAAM,EAAkC,CAAC,EAEzC,IAAK,IAAM,KAAO,EACZ,OAAO,OAAO,EAAc,CAAG,GAAK,EAAa,KAAS,IAAA,KAC5D,EAAO,GAAO,EAAa,IAI/B,GAAI,IAAU,IAAA,GACZ,IAAK,IAAM,KAAO,EACX,OAAO,OAAO,EAAO,CAAG,GAKzB,EAAM,KAAS,IAAA,KAInB,EAAO,GAAO,EAAM,IAIxB,OAAO,CACT,CAQA,SAAS,EACP,EACe,CACf,GAAI,IAAU,IAAA,GACZ,OAGF,IAAI,EAEJ,IAAK,IAAM,KAAO,EACV,OAAO,OAAO,EAAO,CAAG,GAAK,EAAM,KAAS,IAAA,KAIlD,IAAa,CAAE,GAAG,CAAM,EAExB,OAAO,EAAS,IAGlB,OAAQ,GAA8B,CACxC,CAOA,MAAM,EAAkB,IAAI,IAAI,CAAC,SAAU,SAAU,SAAS,CAAC,EAG/D,SAAS,EAAkB,EAAoD,CAC7E,OAAO,EAAgB,IAAI,OAAO,CAAK,CACzC,CA2BA,SAAgB,EAAoB,EAAe,EAAwB,CACzE,GAAI,IAAS,EACX,MAAO,GAGT,GAAI,MAAM,QAAQ,CAAI,EAAG,CAGvB,GAAI,CAAC,MAAM,QAAQ,CAAI,EACrB,OAAO,EAAK,SAAW,GAAK,EAAoB,EAAK,GAAI,CAAI,EAG/D,GAAI,EAAK,SAAW,EAAK,OACvB,MAAO,GAIT,IAAK,IAAI,EAAI,EAAG,EAAI,EAAK,OAAQ,IAC/B,GAAI,CAAC,EAAoB,EAAK,GAAI,EAAK,EAAE,EACvC,MAAO,GAIX,MAAO,EACT,CAMA,OAJI,MAAM,QAAQ,CAAI,EACb,EAAK,SAAW,GAAK,EAAoB,EAAM,EAAK,EAAE,EAI7D,EAAkB,CAAI,GACtB,EAAkB,CAAI,GACtB,OAAO,CAAI,IAAM,OAAO,CAAI,CAEhC,CA0CA,SAAgB,EAAkC,EAAa,CAK7D,OAAO,OAAO,OAAO,CAAK,CAC5B,CAgCA,SAAgB,EACd,EACA,EACA,EACA,EAAe,GACoB,CACnC,GAAI,IAAiB,IAAA,GACnB,OAAO,OAAO,OAAO,EAAa,EAAc,CAAK,CAAC,EAGxD,GAAI,IAAU,IAAA,IAAa,IAAU,EACnC,OAAO,EAWT,GAAI,EACF,OAAO,OAAO,OAAO,CAAK,EAK5B,IAAM,EAAU,EAAa,IAAA,GAAW,CAAK,EAE7C,OAAO,OAAO,OAAO,IAAY,EAAQ,CAAE,GAAG,CAAM,EAAI,CAAO,CACjE,CAoCA,SAAgB,EACd,EACoB,CACpB,GAAI,IAAW,IAAA,GACb,OAAO,EAGT,IAAI,EAEJ,IAAK,IAAM,KAAO,EAAQ,CACxB,GAAI,CAAC,OAAO,OAAO,EAAQ,CAAG,EAC5B,SAGF,IAAM,EAAQ,EAAO,GAEjB,IAAU,IAAA,KAEZ,IAAe,CAAC,EAChB,EAAW,GAAO,EAEtB,CAIA,OAAO,GAAcA,EAAAA,YACvB"}
@@ -1,2 +0,0 @@
1
- const e=require("./channels/guard.js"),t=new WeakMap;function n(e){let n=t.get(e);if(!n)throw TypeError(`[real-router] Invalid router instance — not found in internals registry`);return n}function r(t,n,r,i){e.assertChannelCorrect(n,r,i,t.getQueryParams(r))}function i(e,n){t.set(e,n)}function a(e,t,n){let r=t;for(let t of e){let e=r;r=(...n)=>t(e,...n)}return r(...n)}function o(e,t,n){return((...r)=>{let i=n.get(e);return!i||i.length===0?t(...r):a(i,t,r)})}function s(e,t,n){return(r,i,o)=>{let s=n.get(e);return!s||s.length===0?t(r,i,o):a(s,t,[r,i,o])}}exports.createInterceptable=o,exports.createTernaryInterceptable=s,exports.getInternals=n,exports.registerInternals=i,exports.throwOnMisChanneledKey=r;
2
- //# sourceMappingURL=internals.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"internals.js","names":[],"sources":["../../src/internals.ts"],"sourcesContent":["import { assertChannelCorrect } from \"./channels\";\n\nimport type { RouteTree } from \"./engine\";\nimport type { DependenciesStore } from \"./namespaces\";\nimport type { RoutesStore } from \"./namespaces/RoutesNamespace\";\nimport type { RouteResolver } from \"./pipeline\";\nimport type { Router as RouterClass } from \"./Router\";\nimport type {\n AnyOptions,\n DefaultDependencies,\n EventName,\n LoggerConfig,\n NavigationOptions,\n Options,\n Params,\n Plugin,\n Router as RouterInterface,\n RouterLogger,\n RouteTreeState,\n SearchParams,\n SerializedRouterState,\n SimpleState,\n State,\n TreeChangedEvent,\n Unsubscribe,\n EventMethodMap,\n PluginFactory,\n} from \"./types\";\nimport type { RouterValidator } from \"./types/RouterValidator\";\n\nexport interface RouterInternals<\n D extends DefaultDependencies = DefaultDependencies,\n> {\n readonly makeState: <\n P extends Params = Params,\n S extends SearchParams = SearchParams,\n >(\n name: string,\n params?: P,\n search?: S,\n path?: string,\n ) => State<P, S>;\n\n /**\n * Per-segment param-source map for a route name (`{ segment: { param: \"url\" |\n * \"query\" } }`), read from the live matcher — the ownership channel for\n * `getTransitionPath` (RFC-4 M2 / #1548, replaced the removed per-State\n * `stateMetaStore` WeakMap). `undefined` when the name is not in the tree.\n */\n readonly getMetaForState: (\n name: string,\n ) => Record<string, Record<string, \"url\" | \"query\">> | undefined;\n\n /**\n * The route's DECLARED query-param names — the same registry the URL build\n * prints from (#1556), minus path slots. Feeds the always-on channel guard\n * (#1572); read here rather than re-derived, so classification cannot drift.\n */\n readonly getQueryParams: (name: string) => readonly string[];\n\n readonly forwardState: <\n P extends Params = Params,\n S extends SearchParams = SearchParams,\n >(\n routeName: string,\n routeParams: P,\n routeSearch?: S,\n ) => SimpleState<P, S>;\n\n readonly buildStateResolved: (\n resolvedName: string,\n resolvedParams: Params,\n ) => RouteTreeState | undefined;\n\n readonly matchPath: <P extends Params = Params>(\n path: string,\n options?: AnyOptions,\n ) => State<P> | undefined;\n\n readonly getOptions: () => Options<D>;\n\n readonly addEventListener: <E extends EventName>(\n eventName: E,\n cb: Plugin[EventMethodMap[E]],\n ) => Unsubscribe;\n\n /**\n * Route-tree mutation channel — internal access for the `getRoutesApi`\n * wrapper. A dedicated bridge is required because the public\n * `addEventListener<E extends EventName>` structurally rejects\n * `\"TREE_CHANGED\"` (it is not in the public `EventName` union), is strict on\n * duplicates, and exposes neither `emit` nor `listenerCount`.\n */\n readonly treeChanged: {\n readonly emit: (event: TreeChangedEvent) => void;\n readonly subscribe: (\n handler: (event: TreeChangedEvent) => void,\n ) => Unsubscribe;\n readonly listenerCount: () => number;\n /**\n * True while a `TREE_CHANGED` emit is on the stack — `getRoutesApi` reads it\n * to reject reentrant route-CRUD from a `subscribeChanges` handler (#1032).\n */\n readonly isEmitting: () => boolean;\n };\n\n readonly buildPath: (\n route: string,\n params?: Params,\n search?: SearchParams,\n ) => string;\n\n /**\n * The navigation pipeline's read-model, for entry points that live on this\n * plugin-facing surface rather than in a namespace. Resolved LAZILY: the port\n * is created during wiring, and `registerInternals` runs before that, so the\n * accessor is a closure rather than a value — the same shape the interceptable\n * methods above already use.\n */\n readonly port: () => RouteResolver;\n\n readonly emitTransitionError: (error: Error) => void;\n\n /**\n * Emits `TRANSITION_SUCCESS` directly (no FSM transition) — used by\n * `getRoutesApi().replace()` to notify `router.subscribe` listeners when a\n * structural replace revalidates the active state (#950). Mirrors the success\n * emission `completeTransition` / `navigateToNotFound` perform.\n */\n readonly emitTransitionSuccess: (\n toState: State,\n fromState: State | undefined,\n opts?: NavigationOptions,\n ) => void;\n\n /**\n * Commits the not-found (`UNKNOWN_ROUTE`) state for `path` and emits\n * `TRANSITION_SUCCESS` — the `NavigationNamespace.navigateToNotFound`\n * primitive. `replace()` uses it when a structural replace drops the active\n * route, so subscribers are notified instead of the state silently clearing\n * (#950).\n */\n readonly navigateToNotFound: (path: string) => State;\n\n readonly start: (path: string) => Promise<State>;\n\n /**\n * Plugin-only navigation entry point — delegates to\n * `NavigationNamespace.navigateToState` (`getPluginApi(router).navigateToState`).\n * Hidden from `Router`/`Navigator` to keep the userland surface minimal;\n * see `core-types/src/api.ts` for usage docs.\n */\n readonly navigateToState: (\n state: State,\n options?: NavigationOptions,\n ) => Promise<State>;\n\n /* eslint-disable @typescript-eslint/no-explicit-any -- heterogeneous map: stores different InterceptorFn<M> types under different keys */\n readonly interceptors: Map<\n string,\n ((next: (...args: any[]) => any, ...args: any[]) => any)[]\n >;\n /* eslint-enable @typescript-eslint/no-explicit-any */\n\n readonly setRootPath: (rootPath: string) => void;\n readonly getRootPath: () => string;\n\n readonly getTree: () => RouteTree;\n\n readonly isDisposed: () => boolean;\n\n validator: RouterValidator | null;\n\n // Per-router logger instance (built from `options.logger` in the Router\n // constructor). The facade reads it as `getInternals(this).logger`; namespaces\n // receive it via their deps at wiring; plugins reach it through\n // `getPluginApi(router).logger`. Replaces the former process-global singleton\n // from the standalone `@real-router/logger` package (now folded into\n // `utils/logger`), whose `configure()` leaked across routers (#724).\n readonly logger: RouterLogger;\n\n // Dependencies (issue #172)\n readonly dependenciesGetStore: () => DependenciesStore<D>;\n\n // Clone support (issue #173, consolidated #964). One accessor for the\n // source-side snapshot a clone carries over besides the route store, so a new\n // clone-relevant subsystem is wired in a single place instead of being spread\n // across separate methods.\n readonly getCloneState: () => {\n options: Options<D>;\n dependencies: Record<string, unknown>;\n pluginFactories: PluginFactory<D>[];\n // Resolved logger config of the base router, so a clone can build its OWN\n // logger inheriting the base's level/callback. Frozen `options` do NOT carry\n // `logger` (stripped in the constructor), so `options` above can't convey it;\n // cloneRouter merges a per-request override (traceId) over this snapshot.\n loggerConfig: LoggerConfig;\n };\n\n // Consolidated route data store (issue #174 Phase 2)\n readonly routeGetStore: () => RoutesStore<D>;\n\n // Cross-namespace state (issue #174)\n readonly getStateName: () => string | undefined;\n readonly isTransitioning: () => boolean;\n readonly clearState: () => void;\n readonly setState: (state: State) => void;\n readonly routerExtensions: { keys: string[] }[];\n readonly contextClaimRecords: Set<string>;\n\n /**\n * One-shot hydration scratchpad populated by `hydrateRouter` immediately\n * before delegating to `router.start(parsed.path)` and cleared in the\n * matching `finally`. SSR loader plugins read this slot directly via\n * `getInternals(router).hydrationState` to short-circuit their own loader\n * call when the server-resolved namespace value is already present in the\n * parsed state (#596). `null` outside of an active `hydrateRouter`\n * invocation.\n */\n hydrationState: SerializedRouterState | null;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- existential type: stores RouterInternals for all Dependencies types\nconst internals = new WeakMap<object, RouterInternals<any>>();\n\nexport function getInternals<D extends DefaultDependencies>(\n router: RouterInterface<D>,\n): RouterInternals<D> {\n const ctx = internals.get(router);\n\n if (!ctx) {\n throw new TypeError(\n \"[real-router] Invalid router instance — not found in internals registry\",\n );\n }\n\n return ctx as RouterInternals<D>;\n}\n\n/**\n * Channel guard, position P1 (#1572) — the caller's RAW `params` argument, at\n * the API boundary and BEFORE any interceptor runs, so what it reports is what\n * the CALLER wrote (a plugin's later injection is P2's population, not this one).\n *\n * THROWS. The warn-first step (#1572) announced the contract so every call site\n * could identify itself in the logs; this is the promotion it announced.\n *\n * A `TypeError`, synchronous, rather than a `RouterError` on a rejected promise:\n * this is an ARGUMENT-shape defect at the API boundary, caught before any\n * interceptor or transition exists — the same class as the `subscribe` /\n * `navigateToNotFound` / `start` guards beside it. Rejecting instead would let a\n * `.catch()` written for navigation failures swallow a programming error.\n *\n * P3 (`navigateToState`) keeps REJECTING — deliberately asymmetric, because it\n * takes a ready-made `State` from a popstate handler, where a new synchronous\n * throw would change an existing method's failure shape.\n *\n * The predicates (`buildPath` / `isActiveRoute` / `canNavigateTo`) are still NOT\n * instrumented: they run on every `<Link>` render, an answer there is read\n * immediately and corrupts nothing, and throwing inside a render in six adapters\n * is not a trade this guard is worth.\n *\n * ⚠ Not instrumented ≠ blind. `canNavigateTo` answers whether `navigate` WOULD\n * work, so it consults {@link findMisChanneledKey} directly and returns `false`\n * for a shape this function would have thrown on (#1576) — an answer, not a\n * throw, so the render-path trade above is untouched. `buildPath` /\n * `isActiveRoute` ask a different question and are unchanged.\n *\n * @internal\n */\nexport function throwOnMisChanneledKey<D extends DefaultDependencies>(\n ctx: RouterInternals<D>,\n method: string,\n routeName: string,\n params: Params | undefined,\n): void {\n assertChannelCorrect(\n method,\n routeName,\n params,\n ctx.getQueryParams(routeName),\n );\n}\n\nexport function registerInternals<D extends DefaultDependencies>(\n router: RouterClass<D>,\n ctx: RouterInternals<D>,\n): void {\n internals.set(router, ctx);\n}\n\n/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-argument -- internal chain execution: type safety enforced at public API boundary (PluginApi.addInterceptor) */\nfunction executeInterceptorChain<T>(\n interceptors: ((next: (...args: any[]) => any, ...args: any[]) => any)[],\n original: (...args: any[]) => T,\n args: any[],\n): T {\n let chain = original as (...args: any[]) => any;\n\n for (const interceptor of interceptors) {\n const prev = chain;\n\n chain = (...chainArgs: any[]) => interceptor(prev, ...chainArgs);\n }\n\n return chain(...args) as T;\n}\n\n/**\n * Variadic interceptor wrapper — wraps a function of any arity, returning the\n * same callable type `T`. Use {@link createTernaryInterceptable} instead when\n * the wrapped method takes exactly three args and the caller needs the precise\n * `(a, b, c) => r` signature preserved (the variadic form widens args to\n * `any[]`).\n */\nexport function createInterceptable<T extends (...args: any[]) => any>(\n name: string,\n original: T,\n interceptors: Map<\n string,\n ((next: (...args: any[]) => any, ...args: any[]) => any)[]\n >,\n): T {\n return ((...args: any[]) => {\n const chain = interceptors.get(name);\n\n if (!chain || chain.length === 0) {\n return original(...args);\n }\n\n return executeInterceptorChain(chain, original, args);\n }) as T;\n}\n\n/**\n * Three-argument interceptor wrapper — preserves the exact\n * `(a: A, b: B, c: C) => R` signature that the variadic\n * {@link createInterceptable} widens to `any[]`. Backs both search-aware\n * interceptables — `buildPath(route, params, search)` and\n * `forwardState(name, params, search)` (RFC-4 M2 / #1548). Every first-party\n * plugin registers the full three-argument form; a shorter-arity interceptor\n * from a third party remains type-valid (TS allows fewer params, and `next(a,\n * b)` leaves the third arg `undefined`).\n */\nexport function createTernaryInterceptable<A, B, C, R>(\n name: string,\n original: (a: A, b: B, c: C) => R,\n interceptors: Map<\n string,\n ((next: (...args: any[]) => any, ...args: any[]) => any)[]\n >,\n): (a: A, b: B, c: C) => R {\n return (arg1: A, arg2: B, arg3: C) => {\n const chain = interceptors.get(name);\n\n if (!chain || chain.length === 0) {\n return original(arg1, arg2, arg3);\n }\n\n return executeInterceptorChain(chain, original, [arg1, arg2, arg3]);\n };\n}\n/* eslint-enable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-argument */\n"],"mappings":"uCA+NM,EAAY,IAAI,QAEtB,SAAgB,EACd,EACoB,CACpB,IAAM,EAAM,EAAU,IAAI,CAAM,EAEhC,GAAI,CAAC,EACH,MAAU,UACR,yEACF,EAGF,OAAO,CACT,CAiCA,SAAgB,EACd,EACA,EACA,EACA,EACM,CACN,EAAA,qBACE,EACA,EACA,EACA,EAAI,eAAe,CAAS,CAC9B,CACF,CAEA,SAAgB,EACd,EACA,EACM,CACN,EAAU,IAAI,EAAQ,CAAG,CAC3B,CAGA,SAAS,EACP,EACA,EACA,EACG,CACH,IAAI,EAAQ,EAEZ,IAAK,IAAM,KAAe,EAAc,CACtC,IAAM,EAAO,EAEb,GAAS,GAAG,IAAqB,EAAY,EAAM,GAAG,CAAS,CACjE,CAEA,OAAO,EAAM,GAAG,CAAI,CACtB,CASA,SAAgB,EACd,EACA,EACA,EAIG,CACH,QAAS,GAAG,IAAgB,CAC1B,IAAM,EAAQ,EAAa,IAAI,CAAI,EAMnC,MAJI,CAAC,GAAS,EAAM,SAAW,EACtB,EAAS,GAAG,CAAI,EAGlB,EAAwB,EAAO,EAAU,CAAI,CACtD,EACF,CAYA,SAAgB,EACd,EACA,EACA,EAIyB,CACzB,OAAQ,EAAS,EAAS,IAAY,CACpC,IAAM,EAAQ,EAAa,IAAI,CAAI,EAMnC,MAJI,CAAC,GAAS,EAAM,SAAW,EACtB,EAAS,EAAM,EAAM,CAAI,EAG3B,EAAwB,EAAO,EAAU,CAAC,EAAM,EAAM,CAAI,CAAC,CACpE,CACF"}
@@ -1,2 +0,0 @@
1
- const e=require("./constants.js");function t(t={}){return{...e.DEFAULT_LIMITS,...t}}exports.createLimits=t;
2
- //# sourceMappingURL=limits.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"limits.js","names":["DEFAULT_LIMITS"],"sources":["../../src/limits.ts"],"sourcesContent":["// packages/core/src/limits.ts\n\nimport { DEFAULT_LIMITS } from \"./constants\";\n\nimport type { LimitsConfig } from \"./types\";\nimport type { Limits } from \"./types/internal\";\n\n/**\n * Merges user limits with the defaults; returns a frozen-by-type value.\n *\n * Its own module rather than a corner of `helpers.ts`, where it sat until the\n * applicability audit: resource limits share nothing with that file's subject —\n * path/query channels, the default merge, value comparison, the state shape —\n * and a reader scanning `helpers.ts` for the channel model had to step over it.\n * The two places it could have gone instead both cost more than they save:\n * `types/limits.ts` is a TYPES module (and is re-exported into the public\n * `@real-router/core/types` entry, where runtime code has no business, least of\n * all under the two-phase dts build the augmentation invariant depends on), and\n * `constants.ts` exports no functions at all.\n *\n * @internal\n */\nexport function createLimits(userLimits: Partial<LimitsConfig> = {}): Limits {\n return { ...DEFAULT_LIMITS, ...userLimits };\n}\n"],"mappings":"kCAsBA,SAAgB,EAAa,EAAoC,CAAC,EAAW,CAC3E,MAAO,CAAE,GAAGA,EAAAA,eAAgB,GAAG,CAAW,CAC5C"}
@@ -1,2 +0,0 @@
1
- const e=require("../../constants.js");function t(t={}){let n=Object.create(null);for(let e in t)t[e]!==void 0&&(n[e]=t[e]);return{dependencies:n,limits:e.DEFAULT_LIMITS}}exports.createDependenciesStore=t;
2
- //# sourceMappingURL=dependenciesStore.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"dependenciesStore.js","names":["DEFAULT_LIMITS"],"sources":["../../../../src/namespaces/DependenciesNamespace/dependenciesStore.ts"],"sourcesContent":["import { DEFAULT_LIMITS } from \"../../constants\";\n\nimport type { DefaultDependencies } from \"../../types\";\nimport type { Limits } from \"../../types/internal\";\n\nexport interface DependenciesStore<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n> {\n dependencies: Partial<Dependencies>;\n limits: Limits;\n}\n\nexport function createDependenciesStore<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n initialDependencies: Partial<Dependencies> = {},\n): DependenciesStore<Dependencies> {\n const dependencies = Object.create(null) as Partial<Dependencies>;\n\n for (const key in initialDependencies) {\n if (initialDependencies[key] !== undefined) {\n dependencies[key] = initialDependencies[key];\n }\n }\n\n return {\n dependencies,\n limits: DEFAULT_LIMITS,\n };\n}\n"],"mappings":"sCAYA,SAAgB,EAGd,EAA6C,CAAC,EACb,CACjC,IAAM,EAAe,OAAO,OAAO,IAAI,EAEvC,IAAK,IAAM,KAAO,EACZ,EAAoB,KAAS,IAAA,KAC/B,EAAa,GAAO,EAAoB,IAI5C,MAAO,CACL,eACA,OAAQA,EAAAA,cACV,CACF"}
@@ -1,2 +0,0 @@
1
- const e=require("../../constants.js"),t=require("../../RouterError.js"),n=require("../../routerFSM.js"),r=`TREE_CHANGED`;function i(e){return e instanceof Error?e:Error(String(e))}function a(n,r,a){return new Promise((o,s)=>{let c=()=>{let n=a.reason;s(n instanceof t.RouterError&&n.code===e.errorCodes.TRANSITION_CANCELLED?n:new t.RouterError(e.errorCodes.TRANSITION_CANCELLED,{reason:n}))};if(a.aborted){c();return}a.addEventListener(`abort`,c,{once:!0}),Promise.allSettled(n).then(e=>{if(a.removeEventListener(`abort`,c),a.aborted)return;if(r!==void 0){s(i(r));return}let t=e.find(e=>e.status===`rejected`);if(t!==void 0){s(i(t.reason));return}o()})})}var o=class{#e;#t;#n;#r;#i=[];#a=0;#o;#s;#c;#l;#u;constructor(e){this.#e=e.routerFSM,this.#t=e.emitter,this.#n=e.abortController,this.#o=void 0,this.#p()}static validateSubscribeListener(e){if(typeof e!=`function`)throw TypeError(`[router.subscribe] Expected a function. For Observable pattern use observable(router) from @real-router/rx`)}static validateSubscribeLeaveListener(e){if(typeof e!=`function`)throw TypeError(`[router.subscribeLeave] Expected a function`)}emitRouterStart(){this.#t.emit(e.events.ROUTER_START)}emitRouterStop(){this.#t.emit(e.events.ROUTER_STOP)}emitTransitionStart(t,n){this.#a++;try{this.#t.emit(e.events.TRANSITION_START,t,n)}finally{this.#a--}}emitTransitionSuccess(t,n,r){this.#a++;try{this.#t.emit(e.events.TRANSITION_SUCCESS,t,n,r)}finally{this.#a--}}emitTransitionError(t,n,r){this.#a++;try{this.#t.emit(e.events.TRANSITION_ERROR,t,n,r)}finally{this.#a--}}emitTransitionCancel(t,n){this.#a++;try{this.#t.emit(e.events.TRANSITION_CANCEL,t,n)}finally{this.#a--}}emitTransitionLeaveApprove(t,n){this.#a++;try{this.#t.emit(e.events.TRANSITION_LEAVE_APPROVE,t,n)}finally{this.#a--}}isProcessing(){return this.#a>0}emitTreeChanged(e){this.#t.emit(r,e)}isEmittingTreeChanged(){return this.#t.isDispatching(r)}subscribeTreeChanged(n){if(this.isDisposed())throw new t.RouterError(e.errorCodes.ROUTER_DISPOSED);return this.#t.on(r,e=>{n(e)})}treeChangedListenerCount(){return this.#t.listenerCount(r)}sendStart(){this.#e.send(n.routerEvents.START)}sendStop(){this.#e.send(n.routerEvents.STOP)}sendDispose(){this.#e.send(n.routerEvents.DISPOSE)}sendStarted(){this.#e.send(n.routerEvents.STARTED)}sendNavigate(e,t){this.#o=e,this.#e.send(n.routerEvents.NAVIGATE,{toState:e,fromState:t})}sendComplete(e,t,r={}){this.#e.send(n.routerEvents.COMPLETE,{toState:e,fromState:t,opts:r}),this.#o=void 0}sendLeaveApprove(e,t){this.#e.send(n.routerEvents.LEAVE_APPROVE,{toState:e,fromState:t})}sendFail(e,t,r){this.#s=e,this.#c=t,this.#l=r,this.#e.send(n.routerEvents.FAIL),this.#o=void 0}sendFailSafe(e,t,n){this.isReady()?this.sendFail(e,t,n):this.emitTransitionError(e,t,n)}sendCancel(e,t,r){this.#s=e,this.#c=t,this.#u=r,this.#e.send(n.routerEvents.CANCEL),this.#o=void 0}canBeginTransition(){return this.#e.canSend(n.routerEvents.NAVIGATE)}canStart(){return this.#e.canSend(n.routerEvents.START)}canCancel(){return this.#e.canSend(n.routerEvents.CANCEL)}isActive(){let e=this.#e.getState();return e!==n.routerStates.IDLE&&e!==n.routerStates.DISPOSED}isDisposed(){return this.#e.getState()===n.routerStates.DISPOSED}isTransitioning(){let e=this.#e.getState();return e===n.routerStates.TRANSITION_STARTED||e===n.routerStates.LEAVE_APPROVED}isLeaveApproved(){return this.#e.getState()===n.routerStates.LEAVE_APPROVED}isReady(){return this.#e.getState()===n.routerStates.READY}isStarting(){return this.#e.getState()===n.routerStates.STARTING}isIdle(){return this.#e.getState()===n.routerStates.IDLE}addEventListener(e,t){return this.#d(e,`addEventListener`),this.#t.on(e,t)}subscribe(n){if(this.isDisposed())throw new t.RouterError(e.errorCodes.ROUTER_DISPOSED);return this.#d(e.events.TRANSITION_SUCCESS,`subscribe`),this.#t.on(e.events.TRANSITION_SUCCESS,(e,t)=>n({route:e,previousRoute:t}))}subscribeLeave(n){if(this.isDisposed())throw new t.RouterError(e.errorCodes.ROUTER_DISPOSED);this.#i.push(n);let r=!1;return()=>{if(r)return;r=!0;let e=this.#i.indexOf(n);e!==-1&&this.#i.splice(e,1)}}hasLeaveListeners(){return this.#i.length>0}hasPreCommitListeners(){return this.#t.listenerCount(e.events.TRANSITION_START)>0||this.#t.listenerCount(e.events.TRANSITION_LEAVE_APPROVE)>0}awaitLeaveListeners(e,t,n){if(t===void 0)return;let r=Object.freeze({route:t,nextRoute:e,signal:n}),o,s,c=[...this.#i];this.#a++;try{for(let e of c)try{let t=e(r);t!==void 0&&typeof t.then==`function`&&(o??=[],o.push(t))}catch(e){s===void 0&&(s=e)}}finally{this.#a--}if(o===void 0){if(s!==void 0)throw i(s);return}return a(o,s,n)}clearAll(){this.#t.clearAll(),this.#i.length=0}setLimits(e){this.#t.setLimits(e)}setValidatorAccessor(e){this.#r=e}sendCancelIfPossible(e,t){let n=this.#o;!this.canCancel()||n===void 0||this.sendCancel(n,e,t)}#d(e,t){let n=this.#r?.();n&&n.eventBus.validateCountThresholds(this.#t.listenerCount(e)+1,e,t)}#f(){this.emitTransitionError(this.#s,this.#c,this.#l),this.#s=void 0,this.#c=void 0,this.#l=void 0}#p(){let e=this.#e;e.on(n.routerStates.STARTING,n.routerEvents.STARTED,()=>{this.emitRouterStart()}),e.on(n.routerStates.READY,n.routerEvents.STOP,()=>{this.emitRouterStop()});let t=e=>{this.emitTransitionStart(e.toState,e.fromState)};e.on(n.routerStates.READY,n.routerEvents.NAVIGATE,t),e.on(n.routerStates.TRANSITION_STARTED,n.routerEvents.NAVIGATE,t),e.on(n.routerStates.LEAVE_APPROVED,n.routerEvents.NAVIGATE,t),e.on(n.routerStates.TRANSITION_STARTED,n.routerEvents.LEAVE_APPROVE,e=>{this.emitTransitionLeaveApprove(e.toState,e.fromState)}),e.on(n.routerStates.LEAVE_APPROVED,n.routerEvents.COMPLETE,e=>{this.emitTransitionSuccess(e.toState,e.fromState,e.opts)});let r=()=>{let e=this.#s,t=this.#u;this.#u=void 0,this.#n(t),e!==void 0&&this.emitTransitionCancel(e,this.#c)};e.on(n.routerStates.TRANSITION_STARTED,n.routerEvents.CANCEL,r),e.on(n.routerStates.LEAVE_APPROVED,n.routerEvents.CANCEL,r),e.on(n.routerStates.LEAVE_APPROVED,n.routerEvents.FAIL,()=>{this.#f()}),e.on(n.routerStates.STARTING,n.routerEvents.FAIL,()=>{this.#f()}),e.on(n.routerStates.READY,n.routerEvents.FAIL,()=>{this.#f()}),e.on(n.routerStates.TRANSITION_STARTED,n.routerEvents.FAIL,()=>{this.#f()})}};exports.EventBusNamespace=o;
2
- //# sourceMappingURL=EventBusNamespace.js.map