@askrjs/askr 0.0.35 → 0.0.36

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 (146) hide show
  1. package/dist/bench/components/benchmark-row.js.map +1 -1
  2. package/dist/bench/components/benchmark-table.js.map +1 -1
  3. package/dist/benchmark.js +2 -2
  4. package/dist/benchmark.js.map +1 -1
  5. package/dist/bin/askr-ssg.js.map +1 -1
  6. package/dist/boot/index.d.ts.map +1 -1
  7. package/dist/boot/index.js +9 -29
  8. package/dist/boot/index.js.map +1 -1
  9. package/dist/common/control.js.map +1 -1
  10. package/dist/common/env.js.map +1 -1
  11. package/dist/common/ssr-errors.js.map +1 -1
  12. package/dist/common/vnode.js.map +1 -1
  13. package/dist/components/error-boundary.js.map +1 -1
  14. package/dist/components/link.js.map +1 -1
  15. package/dist/control/case.js.map +1 -1
  16. package/dist/control/for.js.map +1 -1
  17. package/dist/control/shared.js.map +1 -1
  18. package/dist/control/show.js.map +1 -1
  19. package/dist/data/index.d.ts.map +1 -1
  20. package/dist/data/index.js +165 -23
  21. package/dist/data/index.js.map +1 -1
  22. package/dist/dev/invariant.js.map +1 -1
  23. package/dist/dev/logger.js.map +1 -1
  24. package/dist/foundations/icon/icon.js.map +1 -1
  25. package/dist/foundations/interactions/dismissable.js.map +1 -1
  26. package/dist/foundations/interactions/focusable.js.map +1 -1
  27. package/dist/foundations/interactions/hoverable.js.map +1 -1
  28. package/dist/foundations/interactions/interaction-policy.js.map +1 -1
  29. package/dist/foundations/interactions/pressable.js.map +1 -1
  30. package/dist/foundations/interactions/roving-focus.js.map +1 -1
  31. package/dist/foundations/state/controllable.js.map +1 -1
  32. package/dist/foundations/structures/collection.js.map +1 -1
  33. package/dist/foundations/structures/layer.js.map +1 -1
  34. package/dist/foundations/structures/layout.js.map +1 -1
  35. package/dist/foundations/structures/portal.js.map +1 -1
  36. package/dist/foundations/structures/presence.js.map +1 -1
  37. package/dist/foundations/structures/slot.js.map +1 -1
  38. package/dist/foundations/utilities/aria.js.map +1 -1
  39. package/dist/foundations/utilities/compose-handlers.js.map +1 -1
  40. package/dist/foundations/utilities/compose-ref.js.map +1 -1
  41. package/dist/foundations/utilities/merge-props.js.map +1 -1
  42. package/dist/foundations/utilities/use-id.js.map +1 -1
  43. package/dist/fx/fx.js.map +1 -1
  44. package/dist/fx/timing.js.map +1 -1
  45. package/dist/index.d.ts +2 -2
  46. package/dist/index.js +2 -2
  47. package/dist/index.js.map +1 -1
  48. package/dist/jsx/utils.js.map +1 -1
  49. package/dist/jsx-dev-runtime.js.map +1 -1
  50. package/dist/jsx-runtime.js.map +1 -1
  51. package/dist/renderer/children.js.map +1 -1
  52. package/dist/renderer/cleanup.js.map +1 -1
  53. package/dist/renderer/dom.js +128 -53
  54. package/dist/renderer/dom.js.map +1 -1
  55. package/dist/renderer/evaluate.js +7 -0
  56. package/dist/renderer/evaluate.js.map +1 -1
  57. package/dist/renderer/fastpath.js.map +1 -1
  58. package/dist/renderer/index.js.map +1 -1
  59. package/dist/renderer/keyed.js +1 -1
  60. package/dist/renderer/keyed.js.map +1 -1
  61. package/dist/renderer/reconcile.js.map +1 -1
  62. package/dist/renderer/utils.js.map +1 -1
  63. package/dist/router/index.d.ts +2 -2
  64. package/dist/router/index.js +2 -2
  65. package/dist/router/match.js.map +1 -1
  66. package/dist/router/navigate.d.ts.map +1 -1
  67. package/dist/router/navigate.js +4 -2
  68. package/dist/router/navigate.js.map +1 -1
  69. package/dist/router/policy.js.map +1 -1
  70. package/dist/router/route-context.js.map +1 -1
  71. package/dist/router/route.d.ts +1 -1
  72. package/dist/router/route.d.ts.map +1 -1
  73. package/dist/router/route.js +3 -34
  74. package/dist/router/route.js.map +1 -1
  75. package/dist/runtime/child-scope.d.ts +1 -0
  76. package/dist/runtime/child-scope.d.ts.map +1 -1
  77. package/dist/runtime/child-scope.js +2 -0
  78. package/dist/runtime/child-scope.js.map +1 -1
  79. package/dist/runtime/component.js +1 -1
  80. package/dist/runtime/component.js.map +1 -1
  81. package/dist/runtime/context.js.map +1 -1
  82. package/dist/runtime/control.js.map +1 -1
  83. package/dist/runtime/derive.d.ts +2 -1
  84. package/dist/runtime/derive.d.ts.map +1 -1
  85. package/dist/runtime/derive.js +15 -10
  86. package/dist/runtime/derive.js.map +1 -1
  87. package/dist/runtime/dev-namespace.js.map +1 -1
  88. package/dist/runtime/effect.d.ts.map +1 -1
  89. package/dist/runtime/effect.js +34 -9
  90. package/dist/runtime/effect.js.map +1 -1
  91. package/dist/runtime/events.js.map +1 -1
  92. package/dist/runtime/execution-model.js.map +1 -1
  93. package/dist/runtime/fastlane.js.map +1 -1
  94. package/dist/runtime/for-bench.d.ts +1 -1
  95. package/dist/runtime/for-bench.d.ts.map +1 -1
  96. package/dist/runtime/for-bench.js +15 -14
  97. package/dist/runtime/for-bench.js.map +1 -1
  98. package/dist/runtime/for.d.ts +1 -0
  99. package/dist/runtime/for.d.ts.map +1 -1
  100. package/dist/runtime/for.js +63 -16
  101. package/dist/runtime/for.js.map +1 -1
  102. package/dist/runtime/operations.d.ts.map +1 -1
  103. package/dist/runtime/operations.js +9 -7
  104. package/dist/runtime/operations.js.map +1 -1
  105. package/dist/runtime/perf-metrics.js +8 -2
  106. package/dist/runtime/perf-metrics.js.map +1 -1
  107. package/dist/runtime/readable.js.map +1 -1
  108. package/dist/runtime/resource-cell.js +3 -2
  109. package/dist/runtime/resource-cell.js.map +1 -1
  110. package/dist/runtime/scheduler.d.ts +0 -1
  111. package/dist/runtime/scheduler.d.ts.map +1 -1
  112. package/dist/runtime/scheduler.js +6 -19
  113. package/dist/runtime/scheduler.js.map +1 -1
  114. package/dist/runtime/selector.d.ts.map +1 -1
  115. package/dist/runtime/selector.js +6 -4
  116. package/dist/runtime/selector.js.map +1 -1
  117. package/dist/runtime/snapshot-source.d.ts +10 -0
  118. package/dist/runtime/snapshot-source.d.ts.map +1 -0
  119. package/dist/runtime/snapshot-source.js +17 -0
  120. package/dist/runtime/snapshot-source.js.map +1 -0
  121. package/dist/runtime/ssr-bridge.js.map +1 -1
  122. package/dist/runtime/state.d.ts.map +1 -1
  123. package/dist/runtime/state.js +1 -5
  124. package/dist/runtime/state.js.map +1 -1
  125. package/dist/ssg/batch-render.js.map +1 -1
  126. package/dist/ssg/create-static-gen.js.map +1 -1
  127. package/dist/ssg/generate-metadata.js.map +1 -1
  128. package/dist/ssg/incremental-manifest.js.map +1 -1
  129. package/dist/ssg/resolve-ssg-data.js.map +1 -1
  130. package/dist/ssg/route-utils.js.map +1 -1
  131. package/dist/ssg/write-static-files.js.map +1 -1
  132. package/dist/ssr/attrs.js.map +1 -1
  133. package/dist/ssr/context.d.ts +1 -0
  134. package/dist/ssr/context.d.ts.map +1 -1
  135. package/dist/ssr/context.js +3 -0
  136. package/dist/ssr/context.js.map +1 -1
  137. package/dist/ssr/escape.js +56 -16
  138. package/dist/ssr/escape.js.map +1 -1
  139. package/dist/ssr/index.d.ts.map +1 -1
  140. package/dist/ssr/index.js +15 -3
  141. package/dist/ssr/index.js.map +1 -1
  142. package/dist/ssr/render-keys.js.map +1 -1
  143. package/dist/ssr/sink.js.map +1 -1
  144. package/dist/ssr/stream-render.js +14 -2
  145. package/dist/ssr/stream-render.js.map +1 -1
  146. package/package.json +8 -6
@@ -1 +1 @@
1
- {"version":3,"file":"match.js","names":[],"sources":["../../src/router/match.ts"],"sourcesContent":["/**\n * Path matching, segment parsing, and specificity scoring.\n */\n\nimport type { ParsedSegment } from '../common/router';\n\n/**\n * Parse a route template path into typed segments.\n *\n * @example\n * parseSegments('/users/{id}') // [{kind:'static',value:'users'},{kind:'param',value:'id'}]\n * parseSegments('/*') // [{kind:'catchall',value:'*'}]\n * parseSegments('/posts/*') // [{kind:'static',value:'posts'},{kind:'wildcard',value:'*'}]\n */\nexport function parseSegments(path: string): ParsedSegment[] {\n const normalized =\n path.endsWith('/') && path !== '/' ? path.slice(0, -1) : path;\n\n const parts = normalized.split('/').filter(Boolean);\n\n // Bare catch-all: /*\n if (parts.length === 1 && parts[0] === '*') {\n return [{ kind: 'catchall', value: '*' }];\n }\n\n return parts.map((segment): ParsedSegment => {\n if (segment.startsWith('{') && segment.endsWith('}')) {\n return { kind: 'param', value: segment.slice(1, -1) };\n }\n if (segment === '*') {\n return { kind: 'wildcard', value: '*' };\n }\n return { kind: 'static', value: segment };\n });\n}\n\n/**\n * Compute a numeric specificity rank from a parsed segment list.\n *\n * Scoring: static = 3, param = 2, wildcard = 1, catchall = 0.\n * Higher rank wins when multiple routes match the same path.\n */\nexport function computeRank(segments: ParsedSegment[]): number {\n if (segments.length === 1 && segments[0].kind === 'catchall') return -1;\n let score = 0;\n for (const seg of segments) {\n if (seg.kind === 'static') score += 3;\n else if (seg.kind === 'param') score += 2;\n else if (seg.kind === 'wildcard') score += 1;\n // catchall contributes 0 per segment but is handled above\n }\n return score;\n}\n\n/** Reused frozen empty params object — returned for purely-static (no-capture) routes. */\nconst emptyParams: Record<string, string> = Object.freeze(\n Object.create(null) as Record<string, string>\n);\n\n/** Returned for every failed match — avoids per-call allocation. */\nconst noMatch: MatchResult = Object.freeze({\n matched: false,\n params: emptyParams,\n});\n\n/**\n * Match pre-split URL parts against pre-parsed route segments.\n *\n * This is the hot-path matcher used by `resolveRoute` and\n * `resolveRouteFromRoutes`. Callers split the URL path **once** and reuse\n * `urlParts` across all route comparisons in a single resolution call.\n *\n * - Returns a params object (possibly the shared `{}`) on match.\n * - Returns `null` on no match.\n * - Params are allocated lazily — purely-static routes return the frozen\n * empty sentinel without any heap allocation.\n */\nexport function matchSegments(\n urlParts: string[],\n segments: ParsedSegment[]\n): Record<string, string> | null {\n // catch-all /* — matches every URL at any depth\n if (segments.length === 1 && segments[0].kind === 'catchall') {\n return {\n '*':\n urlParts.length === 0\n ? '/'\n : urlParts.length === 1\n ? urlParts[0]\n : '/' + urlParts.join('/'),\n };\n }\n\n // non-catchall: part count must equal segment count\n if (urlParts.length !== segments.length) return null;\n\n // Walk segments; allocate the params object lazily on first capture\n let params: Record<string, string> | null = null;\n for (let i = 0; i < segments.length; i++) {\n const seg = segments[i];\n const part = urlParts[i];\n if (seg.kind === 'static') {\n if (seg.value !== part) return null;\n } else {\n if (params === null) params = {};\n if (seg.kind === 'param') {\n // Avoid decodeURIComponent when no percent-encoding is present\n params[seg.value] = part.includes('%')\n ? decodeURIComponent(part)\n : part;\n } else {\n // wildcard\n params['*'] = part;\n }\n }\n }\n return params ?? emptyParams;\n}\n\nexport interface MatchResult {\n matched: boolean;\n params: Record<string, string>;\n}\n\n/**\n * Match a path against a route pattern and extract params\n *\n * @example\n * match('/users/123', '/users/{id}')\n * // → { matched: true, params: { id: '123' } }\n *\n * match('/posts/hello-world/edit', '/posts/{slug}/{action}')\n * // → { matched: true, params: { slug: 'hello-world', action: 'edit' } }\n *\n * match('/users', '/posts/{id}')\n * // → { matched: false, params: {} }\n */\nexport function match(path: string, pattern: string): MatchResult {\n // Normalize trailing slashes\n const normalizedPath =\n path.endsWith('/') && path !== '/' ? path.slice(0, -1) : path;\n const normalizedPattern =\n pattern.endsWith('/') && pattern !== '/' ? pattern.slice(0, -1) : pattern;\n\n // Split into segments\n const pathSegments = normalizedPath.split('/').filter(Boolean);\n const patternSegments = normalizedPattern.split('/').filter(Boolean);\n\n // Support catch-all route: /* matches any path at any depth\n if (patternSegments.length === 1 && patternSegments[0] === '*') {\n // For multi-segment paths, preserve the leading slash\n // For single-segment paths, return just the segment\n return {\n matched: true,\n params: {\n '*': pathSegments.length > 1 ? normalizedPath : pathSegments[0],\n },\n };\n }\n\n // Check if lengths match (wildcard segments still need to match one segment)\n if (pathSegments.length !== patternSegments.length) {\n return { matched: false, params: {} };\n }\n\n const params: Record<string, string> = {};\n\n // Match each segment\n for (let i = 0; i < patternSegments.length; i++) {\n const patternSegment = patternSegments[i];\n const pathSegment = pathSegments[i];\n\n // Parameter: {paramName}\n if (patternSegment.startsWith('{') && patternSegment.endsWith('}')) {\n const paramName = patternSegment.slice(1, -1);\n params[paramName] = pathSegment.includes('%')\n ? decodeURIComponent(pathSegment)\n : pathSegment;\n } else if (patternSegment === '*') {\n // Wildcard: match single segment\n params['*'] = pathSegment;\n } else if (patternSegment !== pathSegment) {\n // Literal segment mismatch\n return noMatch;\n }\n }\n\n return { matched: true, params };\n}\n"],"mappings":";;;;;;;;;AAcA,SAAgB,cAAc,MAA+B;CAI3D,MAAM,SAFJ,KAAK,SAAS,IAAI,IAAI,SAAS,MAAM,KAAK,MAAM,GAAG,GAAG,GAAG,MAElC,MAAM,IAAI,CAAC,OAAO,QAAQ;AAGnD,KAAI,MAAM,WAAW,KAAK,MAAM,OAAO,IACrC,QAAO,CAAC;EAAE,MAAM;EAAY,OAAO;EAAK,CAAC;AAG3C,QAAO,MAAM,KAAK,YAA2B;AAC3C,MAAI,QAAQ,WAAW,IAAI,IAAI,QAAQ,SAAS,IAAI,CAClD,QAAO;GAAE,MAAM;GAAS,OAAO,QAAQ,MAAM,GAAG,GAAG;GAAE;AAEvD,MAAI,YAAY,IACd,QAAO;GAAE,MAAM;GAAY,OAAO;GAAK;AAEzC,SAAO;GAAE,MAAM;GAAU,OAAO;GAAS;GACzC;;;;;;;;AASJ,SAAgB,YAAY,UAAmC;AAC7D,KAAI,SAAS,WAAW,KAAK,SAAS,GAAG,SAAS,WAAY,QAAO;CACrE,IAAI,QAAQ;AACZ,MAAK,MAAM,OAAO,SAChB,KAAI,IAAI,SAAS,SAAU,UAAS;UAC3B,IAAI,SAAS,QAAS,UAAS;UAC/B,IAAI,SAAS,WAAY,UAAS;AAG7C,QAAO;;;AAIT,MAAM,cAAsC,OAAO,OACjD,OAAO,OAAO,KAAK,CACpB;AAG4B,OAAO,OAAO;CACzC,SAAS;CACT,QAAQ;CACT,CAAC;;;;;;;;;;;;;AAcF,SAAgB,cACd,UACA,UAC+B;AAE/B,KAAI,SAAS,WAAW,KAAK,SAAS,GAAG,SAAS,WAChD,QAAO,EACL,KACE,SAAS,WAAW,IAChB,MACA,SAAS,WAAW,IAClB,SAAS,KACT,MAAM,SAAS,KAAK,IAAI,EACjC;AAIH,KAAI,SAAS,WAAW,SAAS,OAAQ,QAAO;CAGhD,IAAI,SAAwC;AAC5C,MAAK,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;EACxC,MAAM,MAAM,SAAS;EACrB,MAAM,OAAO,SAAS;AACtB,MAAI,IAAI,SAAS,UACf;OAAI,IAAI,UAAU,KAAM,QAAO;SAC1B;AACL,OAAI,WAAW,KAAM,UAAS,EAAE;AAChC,OAAI,IAAI,SAAS,QAEf,QAAO,IAAI,SAAS,KAAK,SAAS,IAAI,GAClC,mBAAmB,KAAK,GACxB;OAGJ,QAAO,OAAO;;;AAIpB,QAAO,UAAU"}
1
+ {"version":3,"file":"match.js","names":[],"sources":["../../src/router/match.ts"],"sourcesContent":["/**\n * Path matching, segment parsing, and specificity scoring.\n */\n\nimport type { ParsedSegment } from '../common/router';\n\n/**\n * Parse a route template path into typed segments.\n *\n * @example\n * parseSegments('/users/{id}') // [{kind:'static',value:'users'},{kind:'param',value:'id'}]\n * parseSegments('/*') // [{kind:'catchall',value:'*'}]\n * parseSegments('/posts/*') // [{kind:'static',value:'posts'},{kind:'wildcard',value:'*'}]\n */\nexport function parseSegments(path: string): ParsedSegment[] {\n const normalized =\n path.endsWith('/') && path !== '/' ? path.slice(0, -1) : path;\n\n const parts = normalized.split('/').filter(Boolean);\n\n // Bare catch-all: /*\n if (parts.length === 1 && parts[0] === '*') {\n return [{ kind: 'catchall', value: '*' }];\n }\n\n return parts.map((segment): ParsedSegment => {\n if (segment.startsWith('{') && segment.endsWith('}')) {\n return { kind: 'param', value: segment.slice(1, -1) };\n }\n if (segment === '*') {\n return { kind: 'wildcard', value: '*' };\n }\n return { kind: 'static', value: segment };\n });\n}\n\n/**\n * Compute a numeric specificity rank from a parsed segment list.\n *\n * Scoring: static = 3, param = 2, wildcard = 1, catchall = 0.\n * Higher rank wins when multiple routes match the same path.\n */\nexport function computeRank(segments: ParsedSegment[]): number {\n if (segments.length === 1 && segments[0].kind === 'catchall') return -1;\n let score = 0;\n for (const seg of segments) {\n if (seg.kind === 'static') score += 3;\n else if (seg.kind === 'param') score += 2;\n else if (seg.kind === 'wildcard') score += 1;\n // catchall contributes 0 per segment but is handled above\n }\n return score;\n}\n\n/** Reused frozen empty params object — returned for purely-static (no-capture) routes. */\nconst emptyParams: Record<string, string> = Object.freeze(\n Object.create(null) as Record<string, string>\n);\n\n/** Returned for every failed match — avoids per-call allocation. */\nconst noMatch: MatchResult = Object.freeze({\n matched: false,\n params: emptyParams,\n});\n\n/**\n * Match pre-split URL parts against pre-parsed route segments.\n *\n * This is the hot-path matcher used by `resolveRoute` and\n * `resolveRouteFromRoutes`. Callers split the URL path **once** and reuse\n * `urlParts` across all route comparisons in a single resolution call.\n *\n * - Returns a params object (possibly the shared `{}`) on match.\n * - Returns `null` on no match.\n * - Params are allocated lazily — purely-static routes return the frozen\n * empty sentinel without any heap allocation.\n */\nexport function matchSegments(\n urlParts: string[],\n segments: ParsedSegment[]\n): Record<string, string> | null {\n // catch-all /* — matches every URL at any depth\n if (segments.length === 1 && segments[0].kind === 'catchall') {\n return {\n '*':\n urlParts.length === 0\n ? '/'\n : urlParts.length === 1\n ? urlParts[0]\n : '/' + urlParts.join('/'),\n };\n }\n\n // non-catchall: part count must equal segment count\n if (urlParts.length !== segments.length) return null;\n\n // Walk segments; allocate the params object lazily on first capture\n let params: Record<string, string> | null = null;\n for (let i = 0; i < segments.length; i++) {\n const seg = segments[i];\n const part = urlParts[i];\n if (seg.kind === 'static') {\n if (seg.value !== part) return null;\n } else {\n if (params === null) params = {};\n if (seg.kind === 'param') {\n // Avoid decodeURIComponent when no percent-encoding is present\n params[seg.value] = part.includes('%')\n ? decodeURIComponent(part)\n : part;\n } else {\n // wildcard\n params['*'] = part;\n }\n }\n }\n return params ?? emptyParams;\n}\n\nexport interface MatchResult {\n matched: boolean;\n params: Record<string, string>;\n}\n\n/**\n * Match a path against a route pattern and extract params\n *\n * @example\n * match('/users/123', '/users/{id}')\n * // → { matched: true, params: { id: '123' } }\n *\n * match('/posts/hello-world/edit', '/posts/{slug}/{action}')\n * // → { matched: true, params: { slug: 'hello-world', action: 'edit' } }\n *\n * match('/users', '/posts/{id}')\n * // → { matched: false, params: {} }\n */\nexport function match(path: string, pattern: string): MatchResult {\n // Normalize trailing slashes\n const normalizedPath =\n path.endsWith('/') && path !== '/' ? path.slice(0, -1) : path;\n const normalizedPattern =\n pattern.endsWith('/') && pattern !== '/' ? pattern.slice(0, -1) : pattern;\n\n // Split into segments\n const pathSegments = normalizedPath.split('/').filter(Boolean);\n const patternSegments = normalizedPattern.split('/').filter(Boolean);\n\n // Support catch-all route: /* matches any path at any depth\n if (patternSegments.length === 1 && patternSegments[0] === '*') {\n // For multi-segment paths, preserve the leading slash\n // For single-segment paths, return just the segment\n return {\n matched: true,\n params: {\n '*': pathSegments.length > 1 ? normalizedPath : pathSegments[0],\n },\n };\n }\n\n // Check if lengths match (wildcard segments still need to match one segment)\n if (pathSegments.length !== patternSegments.length) {\n return { matched: false, params: {} };\n }\n\n const params: Record<string, string> = {};\n\n // Match each segment\n for (let i = 0; i < patternSegments.length; i++) {\n const patternSegment = patternSegments[i];\n const pathSegment = pathSegments[i];\n\n // Parameter: {paramName}\n if (patternSegment.startsWith('{') && patternSegment.endsWith('}')) {\n const paramName = patternSegment.slice(1, -1);\n params[paramName] = pathSegment.includes('%')\n ? decodeURIComponent(pathSegment)\n : pathSegment;\n } else if (patternSegment === '*') {\n // Wildcard: match single segment\n params['*'] = pathSegment;\n } else if (patternSegment !== pathSegment) {\n // Literal segment mismatch\n return noMatch;\n }\n }\n\n return { matched: true, params };\n}\n"],"mappings":";;;;;;;;;AAcA,SAAgB,cAAc,MAA+B;CAI3D,MAAM,SAFJ,KAAK,SAAS,IAAI,IAAI,SAAS,MAAM,KAAK,MAAM,GAAG,GAAG,GAAG,MAElC,MAAM,IAAI,CAAC,OAAO,QAAQ;CAGnD,IAAI,MAAM,WAAW,KAAK,MAAM,OAAO,KACrC,OAAO,CAAC;EAAE,MAAM;EAAY,OAAO;EAAK,CAAC;CAG3C,OAAO,MAAM,KAAK,YAA2B;EAC3C,IAAI,QAAQ,WAAW,IAAI,IAAI,QAAQ,SAAS,IAAI,EAClD,OAAO;GAAE,MAAM;GAAS,OAAO,QAAQ,MAAM,GAAG,GAAG;GAAE;EAEvD,IAAI,YAAY,KACd,OAAO;GAAE,MAAM;GAAY,OAAO;GAAK;EAEzC,OAAO;GAAE,MAAM;GAAU,OAAO;GAAS;GACzC;;;;;;;;AASJ,SAAgB,YAAY,UAAmC;CAC7D,IAAI,SAAS,WAAW,KAAK,SAAS,GAAG,SAAS,YAAY,OAAO;CACrE,IAAI,QAAQ;CACZ,KAAK,MAAM,OAAO,UAChB,IAAI,IAAI,SAAS,UAAU,SAAS;MAC/B,IAAI,IAAI,SAAS,SAAS,SAAS;MACnC,IAAI,IAAI,SAAS,YAAY,SAAS;CAG7C,OAAO;;;AAIT,MAAM,cAAsC,OAAO,OACjD,OAAO,OAAO,KAAK,CACpB;AAG4B,OAAO,OAAO;CACzC,SAAS;CACT,QAAQ;CACT,CAAC;;;;;;;;;;;;;AAcF,SAAgB,cACd,UACA,UAC+B;CAE/B,IAAI,SAAS,WAAW,KAAK,SAAS,GAAG,SAAS,YAChD,OAAO,EACL,KACE,SAAS,WAAW,IAChB,MACA,SAAS,WAAW,IAClB,SAAS,KACT,MAAM,SAAS,KAAK,IAAI,EACjC;CAIH,IAAI,SAAS,WAAW,SAAS,QAAQ,OAAO;CAGhD,IAAI,SAAwC;CAC5C,KAAK,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;EACxC,MAAM,MAAM,SAAS;EACrB,MAAM,OAAO,SAAS;EACtB,IAAI,IAAI,SAAS,UACf;OAAI,IAAI,UAAU,MAAM,OAAO;SAC1B;GACL,IAAI,WAAW,MAAM,SAAS,EAAE;GAChC,IAAI,IAAI,SAAS,SAEf,OAAO,IAAI,SAAS,KAAK,SAAS,IAAI,GAClC,mBAAmB,KAAK,GACxB;QAGJ,OAAO,OAAO;;;CAIpB,OAAO,UAAU"}
@@ -1 +1 @@
1
- {"version":3,"file":"navigate.d.ts","names":[],"sources":["../../src/router/navigate.ts"],"mappings":";;;KA0BY,wBAAA;AAAA,KACA,qBAAA;AAAA,KAEA,wBAAA;EACV,UAAA,GAAa,wBAAA;EACb,OAAA,GAAU,qBAAA;AAAA;AAAA,KAqBA,eAAA;EACV,OAAA;EACA,MAAA,GAAS,wBAAA;AAAA;AAAA,iBAsIK,0BAAA,CACd,OAAA,aAAoB,wBAAA;;iBAkPN,mBAAA,CACd,QAAA,EAAU,iBAAA,EACV,IAAA;;;;;iBAsBc,QAAA,CAAS,IAAA,UAAc,OAAA,GAAS,eAAA;;AAnZhD;;iBAqegB,oBAAA,CAAA;;;;iBASA,iBAAA,CAAA"}
1
+ {"version":3,"file":"navigate.d.ts","names":[],"sources":["../../src/router/navigate.ts"],"mappings":";;;KA2BY,wBAAA;AAAA,KACA,qBAAA;AAAA,KAEA,wBAAA;EACV,UAAA,GAAa,wBAAA;EACb,OAAA,GAAU,qBAAA;AAAA;AAAA,KAqBA,eAAA;EACV,OAAA;EACA,MAAA,GAAS,wBAAA;AAAA;AAAA,iBAsIK,0BAAA,CACd,OAAA,aAAoB,wBAAA;;iBAkPN,mBAAA,CACd,QAAA,EAAU,iBAAA,EACV,IAAA;;;;;iBAqBc,QAAA,CAAS,IAAA,UAAc,OAAA,GAAS,eAAA;;AAlZhD;;iBAoegB,oBAAA,CAAA;;;;iBAYA,iBAAA,CAAA"}
@@ -12,6 +12,7 @@ import { DefaultPortal } from "../foundations/structures/portal.js";
12
12
  let currentInstance = null;
13
13
  let currentPathname = "/";
14
14
  let currentHref = "/";
15
+ let navigationInitialized = false;
15
16
  const DEFAULT_SCROLL_RESTORATION = {
16
17
  enabled: true,
17
18
  navigation: "top",
@@ -227,7 +228,6 @@ function registerAppInstance(instance, path) {
227
228
  currentPathname = path;
228
229
  currentHref = getWindowHref();
229
230
  syncCurrentRouteSnapshot(window.location.pathname, window.location.search, window.location.hash);
230
- saveScrollPosition(currentHref);
231
231
  if (isProductionEnvironment()) lockRouteRegistration();
232
232
  }
233
233
  /**
@@ -283,7 +283,9 @@ function handlePopState(_event) {
283
283
  * Setup popstate listener for browser navigation
284
284
  */
285
285
  function initializeNavigation() {
286
- if (typeof window !== "undefined") window.addEventListener("popstate", handlePopState);
286
+ if (typeof window === "undefined" || navigationInitialized) return;
287
+ navigationInitialized = true;
288
+ window.addEventListener("popstate", handlePopState);
287
289
  }
288
290
  //#endregion
289
291
  export { configureScrollRestoration, initializeNavigation, navigate, registerAppInstance };
@@ -1 +1 @@
1
- {"version":3,"file":"navigate.js","names":[],"sources":["../../src/router/navigate.ts"],"sourcesContent":["/**\n * Client-side navigation with History API\n */\n\nimport {\n resolveRoute,\n resolveRouteRequest,\n lockRouteRegistration,\n syncCurrentRouteSnapshot,\n type ResolvedRoute,\n} from './route';\nimport { mountComponent, type ComponentInstance } from '../runtime/component';\nimport {\n isDevelopmentEnvironment,\n isProductionEnvironment,\n} from '../common/env';\nimport { logger } from '../dev/logger';\nimport type { RouteRenderResult, RouteRequestResult } from '../common/router';\nimport { Fragment, ELEMENT_TYPE } from '../jsx';\nimport { DefaultPortal } from '../foundations/structures/portal';\n\n// Global app state for navigation\nlet currentInstance: ComponentInstance | null = null;\nlet currentPathname = '/';\nlet currentHref = '/';\n\nexport type NavigationScrollBehavior = 'top' | 'preserve';\nexport type HistoryScrollBehavior = 'restore' | 'top' | 'preserve';\n\nexport type ScrollRestorationOptions = {\n navigation?: NavigationScrollBehavior;\n history?: HistoryScrollBehavior;\n};\n\ntype NormalizedScrollRestorationOptions = {\n enabled: boolean;\n navigation: NavigationScrollBehavior;\n history: HistoryScrollBehavior;\n};\n\nconst DEFAULT_SCROLL_RESTORATION: NormalizedScrollRestorationOptions = {\n enabled: true,\n navigation: 'top',\n history: 'restore',\n};\n\nlet scrollRestorationOptions: NormalizedScrollRestorationOptions = {\n ...DEFAULT_SCROLL_RESTORATION,\n};\n\nconst scrollPositions = new Map<string, { x: number; y: number }>();\n\nexport type NavigateOptions = {\n history?: 'push' | 'replace';\n scroll?: NavigationScrollBehavior;\n};\n\nfunction getWindowHref(): string {\n if (typeof window === 'undefined') {\n return currentHref;\n }\n\n return `${window.location.pathname}${window.location.search}${window.location.hash}`;\n}\n\nfunction normalizeScrollRestorationOptions(\n options?: boolean | ScrollRestorationOptions\n): NormalizedScrollRestorationOptions {\n if (options === false) {\n return {\n enabled: false,\n navigation: DEFAULT_SCROLL_RESTORATION.navigation,\n history: DEFAULT_SCROLL_RESTORATION.history,\n };\n }\n\n if (options === true || options === undefined) {\n return { ...DEFAULT_SCROLL_RESTORATION };\n }\n\n return {\n enabled: true,\n navigation: options.navigation ?? DEFAULT_SCROLL_RESTORATION.navigation,\n history: options.history ?? DEFAULT_SCROLL_RESTORATION.history,\n };\n}\n\nfunction readScrollPosition(): { x: number; y: number } {\n if (typeof window === 'undefined') {\n return { x: 0, y: 0 };\n }\n\n const x =\n typeof window.scrollX === 'number'\n ? window.scrollX\n : typeof window.pageXOffset === 'number'\n ? window.pageXOffset\n : 0;\n const y =\n typeof window.scrollY === 'number'\n ? window.scrollY\n : typeof window.pageYOffset === 'number'\n ? window.pageYOffset\n : 0;\n\n return { x, y };\n}\n\nfunction writeHistoryScrollPosition(\n href: string,\n position: { x: number; y: number }\n): void {\n if (typeof window === 'undefined' || getWindowHref() !== href) {\n return;\n }\n if (typeof window.history?.replaceState !== 'function') {\n return;\n }\n const state =\n window.history.state && typeof window.history.state === 'object'\n ? window.history.state\n : {};\n\n window.history.replaceState(\n {\n ...state,\n path: href,\n scroll: position,\n },\n '',\n href\n );\n}\n\nfunction saveScrollPosition(href: string): void {\n if (!scrollRestorationOptions.enabled || typeof window === 'undefined') {\n return;\n }\n\n const position = readScrollPosition();\n scrollPositions.set(href, position);\n writeHistoryScrollPosition(href, position);\n}\n\nfunction scrollToPosition(position: { x: number; y: number }): void {\n if (typeof window === 'undefined' || typeof window.scrollTo !== 'function') {\n return;\n }\n\n window.scrollTo(position.x, position.y);\n}\n\nfunction applyNavigationScroll(behavior: NavigationScrollBehavior): void {\n if (!scrollRestorationOptions.enabled || behavior === 'preserve') {\n return;\n }\n\n scrollToPosition({ x: 0, y: 0 });\n}\n\nfunction applyHistoryScroll(href: string, state: PopStateEvent['state']): void {\n if (!scrollRestorationOptions.enabled) {\n return;\n }\n\n if (scrollRestorationOptions.history === 'preserve') {\n return;\n }\n\n if (scrollRestorationOptions.history === 'top') {\n scrollToPosition({ x: 0, y: 0 });\n return;\n }\n\n const fromState =\n state && typeof state === 'object' && 'scroll' in state\n ? (state.scroll as { x?: unknown; y?: unknown })\n : undefined;\n const saved =\n fromState &&\n typeof fromState.x === 'number' &&\n typeof fromState.y === 'number'\n ? { x: fromState.x, y: fromState.y }\n : scrollPositions.get(href);\n\n scrollToPosition(saved ?? { x: 0, y: 0 });\n}\n\nexport function configureScrollRestoration(\n options?: boolean | ScrollRestorationOptions\n): void {\n scrollRestorationOptions = normalizeScrollRestorationOptions(options);\n\n if (typeof window === 'undefined') {\n return;\n }\n\n if ('scrollRestoration' in window.history) {\n try {\n window.history.scrollRestoration = scrollRestorationOptions.enabled\n ? 'manual'\n : 'auto';\n } catch {\n // Ignore environments that expose but do not allow setting scrollRestoration.\n }\n }\n}\n\nfunction parseTargetUrl(path: string): URL {\n const pathname = window.location.pathname || '/';\n const search = window.location.search || '';\n const hash = window.location.hash || '';\n const href =\n typeof window.location.href === 'string' ? window.location.href : '';\n const base =\n href &&\n href !== 'about:blank' &&\n !href.startsWith('about:') &&\n !href.startsWith('data:')\n ? href\n : `http://localhost${pathname}${search}${hash}`;\n\n return new URL(path, base);\n}\n\nfunction isRenderResult(\n result: RouteRequestResult\n): result is RouteRenderResult {\n return result !== null && result.kind === 'render';\n}\n\nfunction isPromise<T>(value: T | Promise<T>): value is Promise<T> {\n return value instanceof Promise;\n}\n\nfunction createDeniedResolvedRoute(status: number): ResolvedRoute {\n return {\n handler: () => ({\n type: 'div',\n props: {\n 'data-route-denied': String(status),\n },\n children: [String(status)],\n }),\n params: {},\n };\n}\n\nfunction bindResolvedRouteHandler(\n resolved: ResolvedRoute\n): ComponentInstance['fn'] {\n return () =>\n resolved.handler(resolved.params) as ReturnType<ComponentInstance['fn']>;\n}\n\nfunction wrapRootRouteHandler(\n componentFn: ComponentInstance['fn']\n): ComponentInstance['fn'] {\n const wrappedFn: ComponentInstance['fn'] = (props, ctx) => {\n const out = componentFn(props, ctx);\n const portalVNode = {\n $$typeof: ELEMENT_TYPE,\n type: DefaultPortal,\n props: {},\n key: '__default_portal',\n } as unknown;\n\n return {\n $$typeof: ELEMENT_TYPE,\n type: Fragment,\n props: {\n children:\n out === undefined || out === null\n ? [portalVNode]\n : [out, portalVNode],\n },\n } as ReturnType<ComponentInstance['fn']>;\n };\n\n Object.defineProperty(wrappedFn, 'name', {\n value: componentFn.name || 'Component',\n });\n\n return wrappedFn;\n}\n\nfunction remountResolvedRoute(\n resolved: ResolvedRoute,\n pathname: string,\n href: string\n): boolean {\n if (!currentInstance) {\n return false;\n }\n\n // The route handler IS the component function\n // It takes params as props and renders the route\n currentInstance.fn = wrapRootRouteHandler(bindResolvedRouteHandler(resolved));\n currentInstance.props = {};\n\n // Reset state to prevent leakage from previous route\n // Each route navigation starts completely fresh\n currentInstance.stateValues = [];\n currentInstance.expectedStateIndices = [];\n currentInstance.firstRenderComplete = false;\n currentInstance.stateIndexCheck = -1;\n // Increment generation to invalidate pending async evaluations from previous route\n currentInstance.evaluationGeneration++;\n currentInstance.notifyUpdate = null;\n currentInstance.mountOperations = [];\n currentInstance._placeholder = undefined;\n currentInstance.hasPendingUpdate = false;\n\n // Route-local async work should create a fresh abort controller lazily.\n currentInstance.abortController = null;\n\n // Re-execute component against the existing host so reconciliation can\n // preserve any shared layout DOM between sibling routes.\n mountComponent(currentInstance);\n currentPathname = pathname;\n currentHref = href;\n return true;\n}\n\nfunction rerenderResolvedRoute(pathname: string, href: string): boolean {\n if (!currentInstance) {\n return false;\n }\n\n const resolved = resolveRoute(pathname);\n if (!resolved) {\n return false;\n }\n\n currentPathname = pathname;\n currentHref = href;\n currentInstance._enqueueRun?.();\n return true;\n}\n\nfunction resolveNavigationTarget(path: string) {\n const target = parseTargetUrl(path);\n const pathname = target.pathname;\n const href = `${target.pathname}${target.search}${target.hash}`;\n const resolved = resolveRouteRequest(href);\n\n if (isPromise(resolved)) {\n return resolved.then((next) => ({\n href,\n pathname,\n resolved: next,\n }));\n }\n\n return {\n href,\n pathname,\n resolved,\n };\n}\n\nfunction applyNavigationTarget(\n path: string,\n options: NavigateOptions,\n target: {\n href: string;\n pathname: string;\n resolved: RouteRequestResult;\n }\n): void {\n const { href, pathname, resolved } = target;\n const previousPathname = currentPathname;\n\n saveScrollPosition(currentHref);\n\n if (!resolved) {\n if (isDevelopmentEnvironment()) {\n logger.warn(`No route found for path: ${path}`);\n }\n return;\n }\n\n if (resolved.kind === 'redirect') {\n const redirectTarget = parseTargetUrl(resolved.to);\n const redirectHref = `${redirectTarget.pathname}${redirectTarget.search}${redirectTarget.hash}`;\n if (redirectHref === href) {\n if (isDevelopmentEnvironment()) {\n logger.warn(\n `Navigation guard redirected to the same path: ${redirectHref}`\n );\n }\n return;\n }\n\n navigate(redirectHref, { history: 'replace' });\n return;\n }\n\n const nextResolved =\n resolved.kind === 'deny'\n ? createDeniedResolvedRoute(resolved.status)\n : {\n handler: resolved.handler,\n params: resolved.params,\n };\n\n const historyMethod =\n options.history === 'replace' ? 'replaceState' : 'pushState';\n window.history[historyMethod]({ path: href }, '', href);\n syncCurrentRouteSnapshot(\n window.location.pathname,\n window.location.search,\n window.location.hash\n );\n\n if (currentInstance) {\n if (pathname === currentPathname && isRenderResult(resolved)) {\n rerenderResolvedRoute(pathname, href);\n return;\n }\n\n remountResolvedRoute(nextResolved, pathname, href);\n if (pathname !== previousPathname) {\n applyNavigationScroll(\n options.scroll ?? scrollRestorationOptions.navigation\n );\n }\n }\n}\n\n/** Register the current app instance (called by createSPA/hydrateSPA). */\nexport function registerAppInstance(\n instance: ComponentInstance,\n path: string\n): void {\n currentInstance = instance;\n currentPathname = path;\n currentHref = getWindowHref();\n syncCurrentRouteSnapshot(\n window.location.pathname,\n window.location.search,\n window.location.hash\n );\n saveScrollPosition(currentHref);\n // Lock further route registrations after the app has started — but allow tests to register routes.\n // Enforce only in production to avoid breaking test infra which registers routes dynamically.\n if (isProductionEnvironment()) {\n lockRouteRegistration();\n }\n}\n\n/**\n * Navigate to a new path\n * Updates URL, resolves route, and re-mounts app with new handler\n */\nexport function navigate(path: string, options: NavigateOptions = {}): void {\n if (typeof window === 'undefined') {\n // SSR context\n return;\n }\n\n const target = resolveNavigationTarget(path);\n if (isPromise(target)) {\n void target.then((resolvedTarget) =>\n applyNavigationTarget(path, options, resolvedTarget)\n );\n return;\n }\n\n applyNavigationTarget(path, options, target);\n}\n\n/**\n * Handle browser back/forward buttons\n */\nfunction handlePopState(_event: PopStateEvent): void {\n const previousHref = currentHref;\n const pathname = window.location.pathname;\n const href = `${window.location.pathname}${window.location.search}${window.location.hash}`;\n\n saveScrollPosition(previousHref);\n\n if (!currentInstance) {\n return;\n }\n\n const resolved = resolveRouteRequest(href);\n\n const applyResolved = (resolved: RouteRequestResult) => {\n if (!resolved) {\n if (isDevelopmentEnvironment()) {\n logger.warn(`No route found for path: ${pathname}`);\n }\n return;\n }\n\n if (resolved.kind === 'redirect') {\n navigate(resolved.to, { history: 'replace' });\n return;\n }\n\n syncCurrentRouteSnapshot(\n window.location.pathname,\n window.location.search,\n window.location.hash\n );\n\n if (pathname === currentPathname && isRenderResult(resolved)) {\n rerenderResolvedRoute(pathname, href);\n return;\n }\n\n remountResolvedRoute(\n resolved.kind === 'deny'\n ? createDeniedResolvedRoute(resolved.status)\n : {\n handler: resolved.handler,\n params: resolved.params,\n },\n pathname,\n href\n );\n\n applyHistoryScroll(href, _event.state);\n };\n\n if (isPromise(resolved)) {\n void resolved.then((next) => applyResolved(next));\n return;\n }\n\n applyResolved(resolved);\n}\n\n/**\n * Setup popstate listener for browser navigation\n */\nexport function initializeNavigation(): void {\n if (typeof window !== 'undefined') {\n window.addEventListener('popstate', handlePopState);\n }\n}\n\n/**\n * Cleanup navigation listeners\n */\nexport function cleanupNavigation(): void {\n if (typeof window !== 'undefined') {\n window.removeEventListener('popstate', handlePopState);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"navigate.js","names":[],"sources":["../../src/router/navigate.ts"],"sourcesContent":["/**\n * Client-side navigation with History API\n */\n\nimport {\n resolveRoute,\n resolveRouteRequest,\n lockRouteRegistration,\n syncCurrentRouteSnapshot,\n type ResolvedRoute,\n} from './route';\nimport { mountComponent, type ComponentInstance } from '../runtime/component';\nimport {\n isDevelopmentEnvironment,\n isProductionEnvironment,\n} from '../common/env';\nimport { logger } from '../dev/logger';\nimport type { RouteRenderResult, RouteRequestResult } from '../common/router';\nimport { Fragment, ELEMENT_TYPE } from '../jsx';\nimport { DefaultPortal } from '../foundations/structures/portal';\n\n// Global app state for navigation\nlet currentInstance: ComponentInstance | null = null;\nlet currentPathname = '/';\nlet currentHref = '/';\nlet navigationInitialized = false;\n\nexport type NavigationScrollBehavior = 'top' | 'preserve';\nexport type HistoryScrollBehavior = 'restore' | 'top' | 'preserve';\n\nexport type ScrollRestorationOptions = {\n navigation?: NavigationScrollBehavior;\n history?: HistoryScrollBehavior;\n};\n\ntype NormalizedScrollRestorationOptions = {\n enabled: boolean;\n navigation: NavigationScrollBehavior;\n history: HistoryScrollBehavior;\n};\n\nconst DEFAULT_SCROLL_RESTORATION: NormalizedScrollRestorationOptions = {\n enabled: true,\n navigation: 'top',\n history: 'restore',\n};\n\nlet scrollRestorationOptions: NormalizedScrollRestorationOptions = {\n ...DEFAULT_SCROLL_RESTORATION,\n};\n\nconst scrollPositions = new Map<string, { x: number; y: number }>();\n\nexport type NavigateOptions = {\n history?: 'push' | 'replace';\n scroll?: NavigationScrollBehavior;\n};\n\nfunction getWindowHref(): string {\n if (typeof window === 'undefined') {\n return currentHref;\n }\n\n return `${window.location.pathname}${window.location.search}${window.location.hash}`;\n}\n\nfunction normalizeScrollRestorationOptions(\n options?: boolean | ScrollRestorationOptions\n): NormalizedScrollRestorationOptions {\n if (options === false) {\n return {\n enabled: false,\n navigation: DEFAULT_SCROLL_RESTORATION.navigation,\n history: DEFAULT_SCROLL_RESTORATION.history,\n };\n }\n\n if (options === true || options === undefined) {\n return { ...DEFAULT_SCROLL_RESTORATION };\n }\n\n return {\n enabled: true,\n navigation: options.navigation ?? DEFAULT_SCROLL_RESTORATION.navigation,\n history: options.history ?? DEFAULT_SCROLL_RESTORATION.history,\n };\n}\n\nfunction readScrollPosition(): { x: number; y: number } {\n if (typeof window === 'undefined') {\n return { x: 0, y: 0 };\n }\n\n const x =\n typeof window.scrollX === 'number'\n ? window.scrollX\n : typeof window.pageXOffset === 'number'\n ? window.pageXOffset\n : 0;\n const y =\n typeof window.scrollY === 'number'\n ? window.scrollY\n : typeof window.pageYOffset === 'number'\n ? window.pageYOffset\n : 0;\n\n return { x, y };\n}\n\nfunction writeHistoryScrollPosition(\n href: string,\n position: { x: number; y: number }\n): void {\n if (typeof window === 'undefined' || getWindowHref() !== href) {\n return;\n }\n if (typeof window.history?.replaceState !== 'function') {\n return;\n }\n const state =\n window.history.state && typeof window.history.state === 'object'\n ? window.history.state\n : {};\n\n window.history.replaceState(\n {\n ...state,\n path: href,\n scroll: position,\n },\n '',\n href\n );\n}\n\nfunction saveScrollPosition(href: string): void {\n if (!scrollRestorationOptions.enabled || typeof window === 'undefined') {\n return;\n }\n\n const position = readScrollPosition();\n scrollPositions.set(href, position);\n writeHistoryScrollPosition(href, position);\n}\n\nfunction scrollToPosition(position: { x: number; y: number }): void {\n if (typeof window === 'undefined' || typeof window.scrollTo !== 'function') {\n return;\n }\n\n window.scrollTo(position.x, position.y);\n}\n\nfunction applyNavigationScroll(behavior: NavigationScrollBehavior): void {\n if (!scrollRestorationOptions.enabled || behavior === 'preserve') {\n return;\n }\n\n scrollToPosition({ x: 0, y: 0 });\n}\n\nfunction applyHistoryScroll(href: string, state: PopStateEvent['state']): void {\n if (!scrollRestorationOptions.enabled) {\n return;\n }\n\n if (scrollRestorationOptions.history === 'preserve') {\n return;\n }\n\n if (scrollRestorationOptions.history === 'top') {\n scrollToPosition({ x: 0, y: 0 });\n return;\n }\n\n const fromState =\n state && typeof state === 'object' && 'scroll' in state\n ? (state.scroll as { x?: unknown; y?: unknown })\n : undefined;\n const saved =\n fromState &&\n typeof fromState.x === 'number' &&\n typeof fromState.y === 'number'\n ? { x: fromState.x, y: fromState.y }\n : scrollPositions.get(href);\n\n scrollToPosition(saved ?? { x: 0, y: 0 });\n}\n\nexport function configureScrollRestoration(\n options?: boolean | ScrollRestorationOptions\n): void {\n scrollRestorationOptions = normalizeScrollRestorationOptions(options);\n\n if (typeof window === 'undefined') {\n return;\n }\n\n if ('scrollRestoration' in window.history) {\n try {\n window.history.scrollRestoration = scrollRestorationOptions.enabled\n ? 'manual'\n : 'auto';\n } catch {\n // Ignore environments that expose but do not allow setting scrollRestoration.\n }\n }\n}\n\nfunction parseTargetUrl(path: string): URL {\n const pathname = window.location.pathname || '/';\n const search = window.location.search || '';\n const hash = window.location.hash || '';\n const href =\n typeof window.location.href === 'string' ? window.location.href : '';\n const base =\n href &&\n href !== 'about:blank' &&\n !href.startsWith('about:') &&\n !href.startsWith('data:')\n ? href\n : `http://localhost${pathname}${search}${hash}`;\n\n return new URL(path, base);\n}\n\nfunction isRenderResult(\n result: RouteRequestResult\n): result is RouteRenderResult {\n return result !== null && result.kind === 'render';\n}\n\nfunction isPromise<T>(value: T | Promise<T>): value is Promise<T> {\n return value instanceof Promise;\n}\n\nfunction createDeniedResolvedRoute(status: number): ResolvedRoute {\n return {\n handler: () => ({\n type: 'div',\n props: {\n 'data-route-denied': String(status),\n },\n children: [String(status)],\n }),\n params: {},\n };\n}\n\nfunction bindResolvedRouteHandler(\n resolved: ResolvedRoute\n): ComponentInstance['fn'] {\n return () =>\n resolved.handler(resolved.params) as ReturnType<ComponentInstance['fn']>;\n}\n\nfunction wrapRootRouteHandler(\n componentFn: ComponentInstance['fn']\n): ComponentInstance['fn'] {\n const wrappedFn: ComponentInstance['fn'] = (props, ctx) => {\n const out = componentFn(props, ctx);\n const portalVNode = {\n $$typeof: ELEMENT_TYPE,\n type: DefaultPortal,\n props: {},\n key: '__default_portal',\n } as unknown;\n\n return {\n $$typeof: ELEMENT_TYPE,\n type: Fragment,\n props: {\n children:\n out === undefined || out === null\n ? [portalVNode]\n : [out, portalVNode],\n },\n } as ReturnType<ComponentInstance['fn']>;\n };\n\n Object.defineProperty(wrappedFn, 'name', {\n value: componentFn.name || 'Component',\n });\n\n return wrappedFn;\n}\n\nfunction remountResolvedRoute(\n resolved: ResolvedRoute,\n pathname: string,\n href: string\n): boolean {\n if (!currentInstance) {\n return false;\n }\n\n // The route handler IS the component function\n // It takes params as props and renders the route\n currentInstance.fn = wrapRootRouteHandler(bindResolvedRouteHandler(resolved));\n currentInstance.props = {};\n\n // Reset state to prevent leakage from previous route\n // Each route navigation starts completely fresh\n currentInstance.stateValues = [];\n currentInstance.expectedStateIndices = [];\n currentInstance.firstRenderComplete = false;\n currentInstance.stateIndexCheck = -1;\n // Increment generation to invalidate pending async evaluations from previous route\n currentInstance.evaluationGeneration++;\n currentInstance.notifyUpdate = null;\n currentInstance.mountOperations = [];\n currentInstance._placeholder = undefined;\n currentInstance.hasPendingUpdate = false;\n\n // Route-local async work should create a fresh abort controller lazily.\n currentInstance.abortController = null;\n\n // Re-execute component against the existing host so reconciliation can\n // preserve any shared layout DOM between sibling routes.\n mountComponent(currentInstance);\n currentPathname = pathname;\n currentHref = href;\n return true;\n}\n\nfunction rerenderResolvedRoute(pathname: string, href: string): boolean {\n if (!currentInstance) {\n return false;\n }\n\n const resolved = resolveRoute(pathname);\n if (!resolved) {\n return false;\n }\n\n currentPathname = pathname;\n currentHref = href;\n currentInstance._enqueueRun?.();\n return true;\n}\n\nfunction resolveNavigationTarget(path: string) {\n const target = parseTargetUrl(path);\n const pathname = target.pathname;\n const href = `${target.pathname}${target.search}${target.hash}`;\n const resolved = resolveRouteRequest(href);\n\n if (isPromise(resolved)) {\n return resolved.then((next) => ({\n href,\n pathname,\n resolved: next,\n }));\n }\n\n return {\n href,\n pathname,\n resolved,\n };\n}\n\nfunction applyNavigationTarget(\n path: string,\n options: NavigateOptions,\n target: {\n href: string;\n pathname: string;\n resolved: RouteRequestResult;\n }\n): void {\n const { href, pathname, resolved } = target;\n const previousPathname = currentPathname;\n\n saveScrollPosition(currentHref);\n\n if (!resolved) {\n if (isDevelopmentEnvironment()) {\n logger.warn(`No route found for path: ${path}`);\n }\n return;\n }\n\n if (resolved.kind === 'redirect') {\n const redirectTarget = parseTargetUrl(resolved.to);\n const redirectHref = `${redirectTarget.pathname}${redirectTarget.search}${redirectTarget.hash}`;\n if (redirectHref === href) {\n if (isDevelopmentEnvironment()) {\n logger.warn(\n `Navigation guard redirected to the same path: ${redirectHref}`\n );\n }\n return;\n }\n\n navigate(redirectHref, { history: 'replace' });\n return;\n }\n\n const nextResolved =\n resolved.kind === 'deny'\n ? createDeniedResolvedRoute(resolved.status)\n : {\n handler: resolved.handler,\n params: resolved.params,\n };\n\n const historyMethod =\n options.history === 'replace' ? 'replaceState' : 'pushState';\n window.history[historyMethod]({ path: href }, '', href);\n syncCurrentRouteSnapshot(\n window.location.pathname,\n window.location.search,\n window.location.hash\n );\n\n if (currentInstance) {\n if (pathname === currentPathname && isRenderResult(resolved)) {\n rerenderResolvedRoute(pathname, href);\n return;\n }\n\n remountResolvedRoute(nextResolved, pathname, href);\n if (pathname !== previousPathname) {\n applyNavigationScroll(\n options.scroll ?? scrollRestorationOptions.navigation\n );\n }\n }\n}\n\n/** Register the current app instance (called by createSPA/hydrateSPA). */\nexport function registerAppInstance(\n instance: ComponentInstance,\n path: string\n): void {\n currentInstance = instance;\n currentPathname = path;\n currentHref = getWindowHref();\n syncCurrentRouteSnapshot(\n window.location.pathname,\n window.location.search,\n window.location.hash\n );\n // Lock further route registrations after the app has started — but allow tests to register routes.\n // Enforce only in production to avoid breaking test infra which registers routes dynamically.\n if (isProductionEnvironment()) {\n lockRouteRegistration();\n }\n}\n\n/**\n * Navigate to a new path\n * Updates URL, resolves route, and re-mounts app with new handler\n */\nexport function navigate(path: string, options: NavigateOptions = {}): void {\n if (typeof window === 'undefined') {\n // SSR context\n return;\n }\n\n const target = resolveNavigationTarget(path);\n if (isPromise(target)) {\n void target.then((resolvedTarget) =>\n applyNavigationTarget(path, options, resolvedTarget)\n );\n return;\n }\n\n applyNavigationTarget(path, options, target);\n}\n\n/**\n * Handle browser back/forward buttons\n */\nfunction handlePopState(_event: PopStateEvent): void {\n const previousHref = currentHref;\n const pathname = window.location.pathname;\n const href = `${window.location.pathname}${window.location.search}${window.location.hash}`;\n\n saveScrollPosition(previousHref);\n\n if (!currentInstance) {\n return;\n }\n\n const resolved = resolveRouteRequest(href);\n\n const applyResolved = (resolved: RouteRequestResult) => {\n if (!resolved) {\n if (isDevelopmentEnvironment()) {\n logger.warn(`No route found for path: ${pathname}`);\n }\n return;\n }\n\n if (resolved.kind === 'redirect') {\n navigate(resolved.to, { history: 'replace' });\n return;\n }\n\n syncCurrentRouteSnapshot(\n window.location.pathname,\n window.location.search,\n window.location.hash\n );\n\n if (pathname === currentPathname && isRenderResult(resolved)) {\n rerenderResolvedRoute(pathname, href);\n return;\n }\n\n remountResolvedRoute(\n resolved.kind === 'deny'\n ? createDeniedResolvedRoute(resolved.status)\n : {\n handler: resolved.handler,\n params: resolved.params,\n },\n pathname,\n href\n );\n\n applyHistoryScroll(href, _event.state);\n };\n\n if (isPromise(resolved)) {\n void resolved.then((next) => applyResolved(next));\n return;\n }\n\n applyResolved(resolved);\n}\n\n/**\n * Setup popstate listener for browser navigation\n */\nexport function initializeNavigation(): void {\n if (typeof window === 'undefined' || navigationInitialized) {\n return;\n }\n\n navigationInitialized = true;\n window.addEventListener('popstate', handlePopState);\n}\n\n/**\n * Cleanup navigation listeners\n */\nexport function cleanupNavigation(): void {\n if (typeof window === 'undefined' || !navigationInitialized) {\n return;\n }\n\n navigationInitialized = false;\n window.removeEventListener('popstate', handlePopState);\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"policy.js","names":[],"sources":["../../src/router/policy.ts"],"sourcesContent":["import type {\n AccessDecision,\n AccessDenyStatus,\n AccessRedirectStatus,\n RouteAuthOptions,\n RouteContext,\n RoutePolicy,\n} from '../common/router';\n\nconst ROUTE_AUTH_OPTIONS = Symbol.for('__ASKR_ROUTE_AUTH_OPTIONS__');\n\ntype InternalRouteContext = RouteContext & {\n [ROUTE_AUTH_OPTIONS]?: RouteAuthOptions;\n};\n\ntype RoutePathResolver =\n | string\n | ((context: RouteContext) => string | Promise<string>);\n\nfunction isPromise<T>(value: T | Promise<T>): value is Promise<T> {\n return value instanceof Promise;\n}\n\nexport function allow(): AccessDecision {\n return { kind: 'allow' };\n}\n\nexport function redirect(\n to: string,\n init: {\n status?: AccessRedirectStatus;\n replace?: boolean;\n } = {}\n): AccessDecision {\n return {\n kind: 'redirect',\n to,\n ...(init.status ? { status: init.status } : {}),\n ...(init.replace !== undefined ? { replace: init.replace } : {}),\n };\n}\n\nexport function deny(status: AccessDenyStatus): AccessDecision {\n return { kind: 'deny', status };\n}\n\nexport function unauthorized(): AccessDecision {\n return deny(401);\n}\n\nexport function forbidden(): AccessDecision {\n return deny(403);\n}\n\nexport function notFound(): AccessDecision {\n return deny(404);\n}\n\nexport function normalizeAccessDecision(\n result: AccessDecision\n): AccessDecision {\n return result;\n}\n\nexport function withRouteAuthOptions(\n context: RouteContext,\n auth: RouteAuthOptions | undefined\n): RouteContext {\n const internal = context as InternalRouteContext;\n\n if (!auth) {\n delete internal[ROUTE_AUTH_OPTIONS];\n return context;\n }\n\n internal[ROUTE_AUTH_OPTIONS] = auth;\n return context;\n}\n\nfunction getRouteAuthOptions(\n context: RouteContext\n): RouteAuthOptions | undefined {\n return (context as InternalRouteContext)[ROUTE_AUTH_OPTIONS];\n}\n\nfunction assertRouteAuthOptions(context: RouteContext): RouteAuthOptions {\n const auth = getRouteAuthOptions(context);\n if (!auth?.resolve) {\n throw new Error(\n 'Built-in auth policies require router auth configuration. ' +\n 'Provide `auth` to registerRoutes(...), createSPA(...), hydrateSPA(...), or SSR route resolution.'\n );\n }\n\n return auth;\n}\n\nfunction resolvePathSetting(\n value: RoutePathResolver | undefined,\n fallback: string,\n context: RouteContext\n): string | Promise<string> {\n if (!value) {\n return fallback;\n }\n\n if (typeof value === 'function') {\n return value(context);\n }\n\n return value;\n}\n\nfunction appendNextTarget(path: string, href: string): string {\n const resolved = new URL(path, 'http://localhost');\n resolved.searchParams.set('next', href);\n return `${resolved.pathname}${resolved.search}${resolved.hash}`;\n}\n\nfunction collectStringValues(value: unknown): string[] {\n if (typeof value === 'string') {\n return [value];\n }\n\n if (Array.isArray(value)) {\n return value.filter((entry): entry is string => typeof entry === 'string');\n }\n\n if (value instanceof Set) {\n return Array.from(value).filter(\n (entry): entry is string => typeof entry === 'string'\n );\n }\n\n return [];\n}\n\nfunction defaultHasRole(user: unknown, role: string): boolean {\n if (!user || typeof user !== 'object') {\n return false;\n }\n\n return collectStringValues((user as { roles?: unknown }).roles).includes(\n role\n );\n}\n\nfunction defaultHasPermission(user: unknown, permission: string): boolean {\n if (!user || typeof user !== 'object') {\n return false;\n }\n\n return collectStringValues(\n (user as { permissions?: unknown }).permissions\n ).includes(permission);\n}\n\nfunction resolveUnauthenticatedRedirect(\n context: RouteContext\n): AccessDecision | Promise<AccessDecision> {\n const auth = assertRouteAuthOptions(context);\n const loginPath = resolvePathSetting(auth.loginPath, '/login', context);\n if (isPromise(loginPath)) {\n return loginPath.then((nextPath) =>\n redirect(appendNextTarget(nextPath, context.href), {\n replace: context.mode === 'spa',\n })\n );\n }\n\n return redirect(appendNextTarget(loginPath, context.href), {\n replace: context.mode === 'spa',\n });\n}\n\nfunction resolveAuthenticatedRedirect(\n context: RouteContext\n): AccessDecision | Promise<AccessDecision> {\n const auth = assertRouteAuthOptions(context);\n const target = resolvePathSetting(auth.guestRedirectTo, '/', context);\n if (isPromise(target)) {\n return target.then((nextTarget) =>\n redirect(nextTarget, {\n replace: true,\n })\n );\n }\n\n return redirect(target, {\n replace: true,\n });\n}\n\nexport function requireAuth(): RoutePolicy {\n return (context) => {\n if (context.session) {\n return allow();\n }\n\n return resolveUnauthenticatedRedirect(context);\n };\n}\n\nexport function requireGuest(): RoutePolicy {\n return (context) => {\n if (!context.session) {\n return allow();\n }\n\n return resolveAuthenticatedRedirect(context);\n };\n}\n\nexport function requireRole(role: string): RoutePolicy {\n return (context) => {\n if (!context.user) {\n return resolveUnauthenticatedRedirect(context);\n }\n\n const auth = assertRouteAuthOptions(context);\n const hasRole = auth.hasRole\n ? auth.hasRole(context.user, role, context)\n : defaultHasRole(context.user, role);\n\n if (isPromise(hasRole)) {\n return hasRole.then((next) => (next ? allow() : forbidden()));\n }\n\n return hasRole ? allow() : forbidden();\n };\n}\n\nexport function requirePermission(permission: string): RoutePolicy {\n return (context) => {\n if (!context.user) {\n return resolveUnauthenticatedRedirect(context);\n }\n\n const auth = assertRouteAuthOptions(context);\n const hasPermission = auth.hasPermission\n ? auth.hasPermission(context.user, permission, context)\n : defaultHasPermission(context.user, permission);\n\n if (isPromise(hasPermission)) {\n return hasPermission.then((next) => (next ? allow() : forbidden()));\n }\n\n return hasPermission ? allow() : forbidden();\n };\n}\n\nexport async function evaluateRoutePolicy(\n policy: RoutePolicy,\n context: RouteContext\n): Promise<AccessDecision> {\n const result = await policy(context);\n return result;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"policy.js","names":[],"sources":["../../src/router/policy.ts"],"sourcesContent":["import type {\n AccessDecision,\n AccessDenyStatus,\n AccessRedirectStatus,\n RouteAuthOptions,\n RouteContext,\n RoutePolicy,\n} from '../common/router';\n\nconst ROUTE_AUTH_OPTIONS = Symbol.for('__ASKR_ROUTE_AUTH_OPTIONS__');\n\ntype InternalRouteContext = RouteContext & {\n [ROUTE_AUTH_OPTIONS]?: RouteAuthOptions;\n};\n\ntype RoutePathResolver =\n | string\n | ((context: RouteContext) => string | Promise<string>);\n\nfunction isPromise<T>(value: T | Promise<T>): value is Promise<T> {\n return value instanceof Promise;\n}\n\nexport function allow(): AccessDecision {\n return { kind: 'allow' };\n}\n\nexport function redirect(\n to: string,\n init: {\n status?: AccessRedirectStatus;\n replace?: boolean;\n } = {}\n): AccessDecision {\n return {\n kind: 'redirect',\n to,\n ...(init.status ? { status: init.status } : {}),\n ...(init.replace !== undefined ? { replace: init.replace } : {}),\n };\n}\n\nexport function deny(status: AccessDenyStatus): AccessDecision {\n return { kind: 'deny', status };\n}\n\nexport function unauthorized(): AccessDecision {\n return deny(401);\n}\n\nexport function forbidden(): AccessDecision {\n return deny(403);\n}\n\nexport function notFound(): AccessDecision {\n return deny(404);\n}\n\nexport function normalizeAccessDecision(\n result: AccessDecision\n): AccessDecision {\n return result;\n}\n\nexport function withRouteAuthOptions(\n context: RouteContext,\n auth: RouteAuthOptions | undefined\n): RouteContext {\n const internal = context as InternalRouteContext;\n\n if (!auth) {\n delete internal[ROUTE_AUTH_OPTIONS];\n return context;\n }\n\n internal[ROUTE_AUTH_OPTIONS] = auth;\n return context;\n}\n\nfunction getRouteAuthOptions(\n context: RouteContext\n): RouteAuthOptions | undefined {\n return (context as InternalRouteContext)[ROUTE_AUTH_OPTIONS];\n}\n\nfunction assertRouteAuthOptions(context: RouteContext): RouteAuthOptions {\n const auth = getRouteAuthOptions(context);\n if (!auth?.resolve) {\n throw new Error(\n 'Built-in auth policies require router auth configuration. ' +\n 'Provide `auth` to registerRoutes(...), createSPA(...), hydrateSPA(...), or SSR route resolution.'\n );\n }\n\n return auth;\n}\n\nfunction resolvePathSetting(\n value: RoutePathResolver | undefined,\n fallback: string,\n context: RouteContext\n): string | Promise<string> {\n if (!value) {\n return fallback;\n }\n\n if (typeof value === 'function') {\n return value(context);\n }\n\n return value;\n}\n\nfunction appendNextTarget(path: string, href: string): string {\n const resolved = new URL(path, 'http://localhost');\n resolved.searchParams.set('next', href);\n return `${resolved.pathname}${resolved.search}${resolved.hash}`;\n}\n\nfunction collectStringValues(value: unknown): string[] {\n if (typeof value === 'string') {\n return [value];\n }\n\n if (Array.isArray(value)) {\n return value.filter((entry): entry is string => typeof entry === 'string');\n }\n\n if (value instanceof Set) {\n return Array.from(value).filter(\n (entry): entry is string => typeof entry === 'string'\n );\n }\n\n return [];\n}\n\nfunction defaultHasRole(user: unknown, role: string): boolean {\n if (!user || typeof user !== 'object') {\n return false;\n }\n\n return collectStringValues((user as { roles?: unknown }).roles).includes(\n role\n );\n}\n\nfunction defaultHasPermission(user: unknown, permission: string): boolean {\n if (!user || typeof user !== 'object') {\n return false;\n }\n\n return collectStringValues(\n (user as { permissions?: unknown }).permissions\n ).includes(permission);\n}\n\nfunction resolveUnauthenticatedRedirect(\n context: RouteContext\n): AccessDecision | Promise<AccessDecision> {\n const auth = assertRouteAuthOptions(context);\n const loginPath = resolvePathSetting(auth.loginPath, '/login', context);\n if (isPromise(loginPath)) {\n return loginPath.then((nextPath) =>\n redirect(appendNextTarget(nextPath, context.href), {\n replace: context.mode === 'spa',\n })\n );\n }\n\n return redirect(appendNextTarget(loginPath, context.href), {\n replace: context.mode === 'spa',\n });\n}\n\nfunction resolveAuthenticatedRedirect(\n context: RouteContext\n): AccessDecision | Promise<AccessDecision> {\n const auth = assertRouteAuthOptions(context);\n const target = resolvePathSetting(auth.guestRedirectTo, '/', context);\n if (isPromise(target)) {\n return target.then((nextTarget) =>\n redirect(nextTarget, {\n replace: true,\n })\n );\n }\n\n return redirect(target, {\n replace: true,\n });\n}\n\nexport function requireAuth(): RoutePolicy {\n return (context) => {\n if (context.session) {\n return allow();\n }\n\n return resolveUnauthenticatedRedirect(context);\n };\n}\n\nexport function requireGuest(): RoutePolicy {\n return (context) => {\n if (!context.session) {\n return allow();\n }\n\n return resolveAuthenticatedRedirect(context);\n };\n}\n\nexport function requireRole(role: string): RoutePolicy {\n return (context) => {\n if (!context.user) {\n return resolveUnauthenticatedRedirect(context);\n }\n\n const auth = assertRouteAuthOptions(context);\n const hasRole = auth.hasRole\n ? auth.hasRole(context.user, role, context)\n : defaultHasRole(context.user, role);\n\n if (isPromise(hasRole)) {\n return hasRole.then((next) => (next ? allow() : forbidden()));\n }\n\n return hasRole ? allow() : forbidden();\n };\n}\n\nexport function requirePermission(permission: string): RoutePolicy {\n return (context) => {\n if (!context.user) {\n return resolveUnauthenticatedRedirect(context);\n }\n\n const auth = assertRouteAuthOptions(context);\n const hasPermission = auth.hasPermission\n ? auth.hasPermission(context.user, permission, context)\n : defaultHasPermission(context.user, permission);\n\n if (isPromise(hasPermission)) {\n return hasPermission.then((next) => (next ? allow() : forbidden()));\n }\n\n return hasPermission ? allow() : forbidden();\n };\n}\n\nexport async function evaluateRoutePolicy(\n policy: RoutePolicy,\n context: RouteContext\n): Promise<AccessDecision> {\n const result = await policy(context);\n return result;\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"route-context.js","names":[],"sources":["../../src/router/route-context.ts"],"sourcesContent":["import { withRouteAuthOptions } from './policy';\n\nimport type {\n RouteAuthOptions,\n RouteContext,\n RouteQuery,\n} from '../common/router';\n\nexport function parseLocation(url: string): {\n pathname: string;\n search: string;\n hash: string;\n} {\n try {\n const parsedUrl = new URL(url, 'http://localhost');\n return {\n pathname: parsedUrl.pathname,\n search: parsedUrl.search,\n hash: parsedUrl.hash,\n };\n } catch {\n return { pathname: '/', search: '', hash: '' };\n }\n}\n\nexport function deepFreeze<T>(obj: T): T {\n if (obj && typeof obj === 'object' && !Object.isFrozen(obj)) {\n Object.freeze(obj as Record<string, unknown>);\n for (const key of Object.keys(obj as Record<string, unknown>)) {\n const value = (obj as Record<string, unknown>)[key];\n if (value && typeof value === 'object') {\n deepFreeze(value);\n }\n }\n }\n\n return obj;\n}\n\nexport function makeQuery(search: string): RouteQuery {\n const searchParams = new URLSearchParams(search || '');\n const mapping = new Map<string, string[]>();\n\n for (const [key, value] of searchParams.entries()) {\n const existing = mapping.get(key);\n if (existing) {\n existing.push(value);\n } else {\n mapping.set(key, [value]);\n }\n }\n\n const query: RouteQuery = {\n get(key: string) {\n const values = mapping.get(key);\n return values ? values[0] : null;\n },\n getAll(key: string) {\n const values = mapping.get(key);\n return values ? [...values] : [];\n },\n has(key: string) {\n return mapping.has(key);\n },\n toJSON() {\n const output: Record<string, string | string[]> = {};\n for (const [key, values] of mapping.entries()) {\n output[key] = values.length > 1 ? [...values] : values[0];\n }\n return output;\n },\n };\n\n return deepFreeze(query);\n}\n\nexport function buildRouteContextBase(\n target: string,\n params: Record<string, string>,\n options: {\n mode: RouteContext['mode'];\n signal: AbortSignal;\n }\n): Omit<RouteContext, 'session' | 'user'> {\n const parsed = parseLocation(target);\n const href = `${parsed.pathname}${parsed.search}${parsed.hash}`;\n\n return {\n mode: options.mode,\n params,\n pathname: parsed.pathname,\n search: parsed.search,\n hash: parsed.hash,\n href,\n signal: options.signal,\n };\n}\n\nexport function buildRouteContext(\n target: string,\n params: Record<string, string>,\n options: {\n mode: RouteContext['mode'];\n signal: AbortSignal;\n auth?: RouteAuthOptions;\n session?: unknown;\n user?: unknown;\n }\n): RouteContext {\n const context: RouteContext = {\n ...buildRouteContextBase(target, params, options),\n session: options.session ?? null,\n user: options.user ?? null,\n };\n\n return withRouteAuthOptions(context, options.auth);\n}\n"],"mappings":";;AAQA,SAAgB,cAAc,KAI5B;AACA,KAAI;EACF,MAAM,YAAY,IAAI,IAAI,KAAK,mBAAmB;AAClD,SAAO;GACL,UAAU,UAAU;GACpB,QAAQ,UAAU;GAClB,MAAM,UAAU;GACjB;SACK;AACN,SAAO;GAAE,UAAU;GAAK,QAAQ;GAAI,MAAM;GAAI;;;AAIlD,SAAgB,WAAc,KAAW;AACvC,KAAI,OAAO,OAAO,QAAQ,YAAY,CAAC,OAAO,SAAS,IAAI,EAAE;AAC3D,SAAO,OAAO,IAA+B;AAC7C,OAAK,MAAM,OAAO,OAAO,KAAK,IAA+B,EAAE;GAC7D,MAAM,QAAS,IAAgC;AAC/C,OAAI,SAAS,OAAO,UAAU,SAC5B,YAAW,MAAM;;;AAKvB,QAAO;;AAGT,SAAgB,UAAU,QAA4B;CACpD,MAAM,eAAe,IAAI,gBAAgB,UAAU,GAAG;CACtD,MAAM,0BAAU,IAAI,KAAuB;AAE3C,MAAK,MAAM,CAAC,KAAK,UAAU,aAAa,SAAS,EAAE;EACjD,MAAM,WAAW,QAAQ,IAAI,IAAI;AACjC,MAAI,SACF,UAAS,KAAK,MAAM;MAEpB,SAAQ,IAAI,KAAK,CAAC,MAAM,CAAC;;AAyB7B,QAAO,WAAW;EApBhB,IAAI,KAAa;GACf,MAAM,SAAS,QAAQ,IAAI,IAAI;AAC/B,UAAO,SAAS,OAAO,KAAK;;EAE9B,OAAO,KAAa;GAClB,MAAM,SAAS,QAAQ,IAAI,IAAI;AAC/B,UAAO,SAAS,CAAC,GAAG,OAAO,GAAG,EAAE;;EAElC,IAAI,KAAa;AACf,UAAO,QAAQ,IAAI,IAAI;;EAEzB,SAAS;GACP,MAAM,SAA4C,EAAE;AACpD,QAAK,MAAM,CAAC,KAAK,WAAW,QAAQ,SAAS,CAC3C,QAAO,OAAO,OAAO,SAAS,IAAI,CAAC,GAAG,OAAO,GAAG,OAAO;AAEzD,UAAO;;EAIO,CAAM;;AAG1B,SAAgB,sBACd,QACA,QACA,SAIwC;CACxC,MAAM,SAAS,cAAc,OAAO;CACpC,MAAM,OAAO,GAAG,OAAO,WAAW,OAAO,SAAS,OAAO;AAEzD,QAAO;EACL,MAAM,QAAQ;EACd;EACA,UAAU,OAAO;EACjB,QAAQ,OAAO;EACf,MAAM,OAAO;EACb;EACA,QAAQ,QAAQ;EACjB;;AAGH,SAAgB,kBACd,QACA,QACA,SAOc;AAOd,QAAO,qBAAqB;EAL1B,GAAG,sBAAsB,QAAQ,QAAQ,QAAQ;EACjD,SAAS,QAAQ,WAAW;EAC5B,MAAM,QAAQ,QAAQ;EAGI,EAAS,QAAQ,KAAK"}
1
+ {"version":3,"file":"route-context.js","names":[],"sources":["../../src/router/route-context.ts"],"sourcesContent":["import { withRouteAuthOptions } from './policy';\n\nimport type {\n RouteAuthOptions,\n RouteContext,\n RouteQuery,\n} from '../common/router';\n\nexport function parseLocation(url: string): {\n pathname: string;\n search: string;\n hash: string;\n} {\n try {\n const parsedUrl = new URL(url, 'http://localhost');\n return {\n pathname: parsedUrl.pathname,\n search: parsedUrl.search,\n hash: parsedUrl.hash,\n };\n } catch {\n return { pathname: '/', search: '', hash: '' };\n }\n}\n\nexport function deepFreeze<T>(obj: T): T {\n if (obj && typeof obj === 'object' && !Object.isFrozen(obj)) {\n Object.freeze(obj as Record<string, unknown>);\n for (const key of Object.keys(obj as Record<string, unknown>)) {\n const value = (obj as Record<string, unknown>)[key];\n if (value && typeof value === 'object') {\n deepFreeze(value);\n }\n }\n }\n\n return obj;\n}\n\nexport function makeQuery(search: string): RouteQuery {\n const searchParams = new URLSearchParams(search || '');\n const mapping = new Map<string, string[]>();\n\n for (const [key, value] of searchParams.entries()) {\n const existing = mapping.get(key);\n if (existing) {\n existing.push(value);\n } else {\n mapping.set(key, [value]);\n }\n }\n\n const query: RouteQuery = {\n get(key: string) {\n const values = mapping.get(key);\n return values ? values[0] : null;\n },\n getAll(key: string) {\n const values = mapping.get(key);\n return values ? [...values] : [];\n },\n has(key: string) {\n return mapping.has(key);\n },\n toJSON() {\n const output: Record<string, string | string[]> = {};\n for (const [key, values] of mapping.entries()) {\n output[key] = values.length > 1 ? [...values] : values[0];\n }\n return output;\n },\n };\n\n return deepFreeze(query);\n}\n\nexport function buildRouteContextBase(\n target: string,\n params: Record<string, string>,\n options: {\n mode: RouteContext['mode'];\n signal: AbortSignal;\n }\n): Omit<RouteContext, 'session' | 'user'> {\n const parsed = parseLocation(target);\n const href = `${parsed.pathname}${parsed.search}${parsed.hash}`;\n\n return {\n mode: options.mode,\n params,\n pathname: parsed.pathname,\n search: parsed.search,\n hash: parsed.hash,\n href,\n signal: options.signal,\n };\n}\n\nexport function buildRouteContext(\n target: string,\n params: Record<string, string>,\n options: {\n mode: RouteContext['mode'];\n signal: AbortSignal;\n auth?: RouteAuthOptions;\n session?: unknown;\n user?: unknown;\n }\n): RouteContext {\n const context: RouteContext = {\n ...buildRouteContextBase(target, params, options),\n session: options.session ?? null,\n user: options.user ?? null,\n };\n\n return withRouteAuthOptions(context, options.auth);\n}\n"],"mappings":";;AAQA,SAAgB,cAAc,KAI5B;CACA,IAAI;EACF,MAAM,YAAY,IAAI,IAAI,KAAK,mBAAmB;EAClD,OAAO;GACL,UAAU,UAAU;GACpB,QAAQ,UAAU;GAClB,MAAM,UAAU;GACjB;SACK;EACN,OAAO;GAAE,UAAU;GAAK,QAAQ;GAAI,MAAM;GAAI;;;AAIlD,SAAgB,WAAc,KAAW;CACvC,IAAI,OAAO,OAAO,QAAQ,YAAY,CAAC,OAAO,SAAS,IAAI,EAAE;EAC3D,OAAO,OAAO,IAA+B;EAC7C,KAAK,MAAM,OAAO,OAAO,KAAK,IAA+B,EAAE;GAC7D,MAAM,QAAS,IAAgC;GAC/C,IAAI,SAAS,OAAO,UAAU,UAC5B,WAAW,MAAM;;;CAKvB,OAAO;;AAGT,SAAgB,UAAU,QAA4B;CACpD,MAAM,eAAe,IAAI,gBAAgB,UAAU,GAAG;CACtD,MAAM,0BAAU,IAAI,KAAuB;CAE3C,KAAK,MAAM,CAAC,KAAK,UAAU,aAAa,SAAS,EAAE;EACjD,MAAM,WAAW,QAAQ,IAAI,IAAI;EACjC,IAAI,UACF,SAAS,KAAK,MAAM;OAEpB,QAAQ,IAAI,KAAK,CAAC,MAAM,CAAC;;CAyB7B,OAAO,WAAW;EApBhB,IAAI,KAAa;GACf,MAAM,SAAS,QAAQ,IAAI,IAAI;GAC/B,OAAO,SAAS,OAAO,KAAK;;EAE9B,OAAO,KAAa;GAClB,MAAM,SAAS,QAAQ,IAAI,IAAI;GAC/B,OAAO,SAAS,CAAC,GAAG,OAAO,GAAG,EAAE;;EAElC,IAAI,KAAa;GACf,OAAO,QAAQ,IAAI,IAAI;;EAEzB,SAAS;GACP,MAAM,SAA4C,EAAE;GACpD,KAAK,MAAM,CAAC,KAAK,WAAW,QAAQ,SAAS,EAC3C,OAAO,OAAO,OAAO,SAAS,IAAI,CAAC,GAAG,OAAO,GAAG,OAAO;GAEzD,OAAO;;EAIO,CAAM;;AAG1B,SAAgB,sBACd,QACA,QACA,SAIwC;CACxC,MAAM,SAAS,cAAc,OAAO;CACpC,MAAM,OAAO,GAAG,OAAO,WAAW,OAAO,SAAS,OAAO;CAEzD,OAAO;EACL,MAAM,QAAQ;EACd;EACA,UAAU,OAAO;EACjB,QAAQ,OAAO;EACf,MAAM,OAAO;EACb;EACA,QAAQ,QAAQ;EACjB;;AAGH,SAAgB,kBACd,QACA,QACA,SAOc;CAOd,OAAO,qBAAqB;EAL1B,GAAG,sBAAsB,QAAQ,QAAQ,QAAQ;EACjD,SAAS,QAAQ,WAAW;EAC5B,MAAM,QAAQ,QAAQ;EAGI,EAAS,QAAQ,KAAK"}
@@ -125,5 +125,5 @@ declare function resolveRouteRequest(target: string, options?: RouteRequestOptio
125
125
  */
126
126
  declare function resolveRouteFromRoutes(pathname: string, routeList: readonly Route[]): ResolvedRoute | null;
127
127
  //#endregion
128
- export { Outlet, _applyManifest, _drainLazy, clearRoutes, currentRoute, fallback, getLoadedNamespaces, getManifest, getNamespaceRoutes, getRoutes, group, index, lazy, page, registerRoutes, resolveRouteRequest, route, setServerLocation, unloadNamespace };
128
+ export { Outlet, clearRoutes, currentRoute, fallback, getManifest, getRoutes, group, index, lazy, page, registerRoutes, route };
129
129
  //# sourceMappingURL=route.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"route.d.ts","names":[],"sources":["../../src/router/route.ts"],"mappings":";;;iBAoOgB,iBAAA,CAAkB,GAAA;AAAA,iBA8MlB,0BAAA,CACd,IAAA,EAAM,gBAAA;AAuFR;;;;AAAA,iBAJgB,qBAAA,CAAA;AAAA,iBAIA,8BAAA,CAAA;AAAA,iBAIA,gCAAA,CAAA;AAAA,iBAmIA,MAAA,CAAA;;;AAAhB;;;iBASgB,aAAA,CAAA,GAAiB,OAAA;;AAAjC;;;;;AAyBA;;;;;;;;;;;;;;;iBAAgB,IAAA,CACd,OAAA,QAAe,OAAA;EAAU,OAAA,EAAS,cAAA;AAAA,IAAmB,cAAA,IACpD,cAAA;;;;;iBAmCa,UAAA,CACd,iBAAA,GAAmB,QAAA,CAAS,OAAA,aAC3B,OAAA;AAAA,iBASa,KAAA,CAAM,OAAA,EAAS,kBAAA,EAAoB,EAAA,EAAI,eAAA;AAAA,iBAKvC,IAAA,CACd,IAAA,UACA,SAAA,EAAW,cAAA,EACX,EAAA,EAAI,eAAA;AAAA,iBAEU,IAAA,CACd,IAAA,UACA,SAAA,EAAW,cAAA,EACX,OAAA,EAAS,iBAAA,EACT,EAAA,EAAI,eAAA;AAAA,iBA2BU,KAAA,CAAM,SAAA,EAAW,cAAA,EAAgB,OAAA,GAAU,YAAA;AAAA,iBAI3C,QAAA,CAAS,SAAA,EAAW,cAAA;AAAA,iBAkUpB,cAAA,CACd,UAAA,EAAY,eAAA,EACZ,OAAA,GAAS,qBAAA;AAAA,iBAoEK,YAAA,CAAA,GAAgB,aAAA;AAAA,iBAsBhB,wBAAA,CACd,QAAA,UACA,MAAA,UACA,IAAA;AA9cF;;;;;;;;;;AAKA;;AALA,iBA+dgB,KAAA,CACd,IAAA,UACA,SAAA,EAAW,cAAA,EACX,OAAA,GAAU,YAAA;;;;;;;;;AAxdZ;;;iBA0hBgB,WAAA,CAAA,GAAe,aAAA;;;;;iBAWf,cAAA,CAAe,QAAA,EAAU,aAAA;;;;;iBA0BzB,SAAA,CAAA,GAAa,KAAA;;iBAKb,kBAAA,CAAmB,SAAA,WAAoB,KAAA;;iBAKvC,eAAA,CAAgB,SAAA;;iBA6BhB,WAAA,CAAA;;;;iBAiBA,mBAAA,CAAA;;;;AAplBhB;;;;;iBA+nBgB,YAAA,CAAa,QAAA,WAAmB,aAAA;AAAA,iBAuHhC,mBAAA,CACd,MAAA,UACA,OAAA,GAAS,mBAAA,GACR,kBAAA,GAAqB,OAAA,CAAQ,kBAAA;;;;;;;;;AAjXhC;;;;;AAsBA;iBA+ZgB,sBAAA,CACd,QAAA,UACA,SAAA,WAAoB,KAAA,KACnB,aAAA"}
1
+ {"version":3,"file":"route.d.ts","names":[],"sources":["../../src/router/route.ts"],"mappings":";;;iBAgOgB,iBAAA,CAAkB,GAAA;AAAA,iBA8MlB,0BAAA,CACd,IAAA,EAAM,gBAAA;AAuFR;;;;AAAA,iBAJgB,qBAAA,CAAA;AAAA,iBAIA,8BAAA,CAAA;AAAA,iBAIA,gCAAA,CAAA;AAAA,iBAmIA,MAAA,CAAA;;;AAAhB;;;iBASgB,aAAA,CAAA,GAAiB,OAAA;;AAAjC;;;;;AAyBA;;;;;;;;;;;;;;;iBAAgB,IAAA,CACd,OAAA,QAAe,OAAA;EAAU,OAAA,EAAS,cAAA;AAAA,IAAmB,cAAA,IACpD,cAAA;;;;;iBAmCa,UAAA,CACd,iBAAA,GAAmB,QAAA,CAAS,OAAA,aAC3B,OAAA;AAAA,iBASa,KAAA,CAAM,OAAA,EAAS,kBAAA,EAAoB,EAAA,EAAI,eAAA;AAAA,iBAKvC,IAAA,CACd,IAAA,UACA,SAAA,EAAW,cAAA,EACX,EAAA,EAAI,eAAA;AAAA,iBAEU,IAAA,CACd,IAAA,UACA,SAAA,EAAW,cAAA,EACX,OAAA,EAAS,iBAAA,EACT,EAAA,EAAI,eAAA;AAAA,iBA2BU,KAAA,CAAM,SAAA,EAAW,cAAA,EAAgB,OAAA,GAAU,YAAA;AAAA,iBAI3C,QAAA,CAAS,SAAA,EAAW,cAAA;AAAA,iBAkUpB,cAAA,CACd,UAAA,EAAY,eAAA,EACZ,OAAA,GAAS,qBAAA;AAAA,iBAoEK,YAAA,CAAA,GAAgB,aAAA;AAAA,iBAiBhB,wBAAA,CACd,QAAA,UACA,MAAA,UACA,IAAA;AAzcF;;;;;;;;;;AAKA;;AALA,iBA0dgB,KAAA,CACd,IAAA,UACA,SAAA,EAAW,cAAA,EACX,OAAA,GAAU,YAAA;;;;;;;;;AAndZ;;;iBAohBgB,WAAA,CAAA,GAAe,aAAA;;;;;iBAWf,cAAA,CAAe,QAAA,EAAU,aAAA;;;;;iBA0BzB,SAAA,CAAA,GAAa,KAAA;;iBAKb,kBAAA,CAAmB,SAAA,WAAoB,KAAA;;iBAKvC,eAAA,CAAgB,SAAA;;iBA6BhB,WAAA,CAAA;;;;iBAiBA,mBAAA,CAAA;;;;AA9kBhB;;;;;iBAynBgB,YAAA,CAAa,QAAA,WAAmB,aAAA;AAAA,iBAuHhC,mBAAA,CACd,MAAA,UACA,OAAA,GAAS,mBAAA,GACR,kBAAA,GAAqB,OAAA,CAAQ,kBAAA;;;;;;;;;AA3WhC;;;;;AAiBA;iBA8ZgB,sBAAA,CACd,QAAA,UACA,SAAA,WAAoB,KAAA,KACnB,aAAA"}
@@ -1,11 +1,11 @@
1
1
  import { defineContext, readContext } from "../runtime/context.js";
2
2
  import { markReactivePropsDirtySource, markReadableDerivedSubscribersDirty, notifyReadableReaders, recordReadableRead } from "../runtime/readable.js";
3
3
  import { getCurrentComponentInstance } from "../runtime/component.js";
4
+ import { getRenderContext } from "../ssr/context.js";
4
5
  import { computeRank, matchSegments, parseSegments } from "./match.js";
5
6
  import { requireAuth, requireGuest, requirePermission, requireRole } from "./policy.js";
6
7
  import { buildRouteContext, buildRouteContextBase, deepFreeze, makeQuery, parseLocation } from "./route-context.js";
7
8
  import { getExecutionModel } from "../runtime/execution-model.js";
8
- import { getRenderContext } from "../ssr/context.js";
9
9
  //#region src/router/route.ts
10
10
  /**
11
11
  * Route definition, registration, and matching.
@@ -547,10 +547,6 @@ function currentRoute() {
547
547
  const instance = getCurrentComponentInstance();
548
548
  if (!instance) throw new Error("currentRoute() can only be called during component render execution. Call currentRoute() from inside your component function.");
549
549
  if (typeof window === "undefined" || instance.ssr) return readCurrentRouteSnapshot();
550
- if (currentRouteSnapshot.path !== (window.location.pathname || "/")) {
551
- recordReadableRead(currentRouteSource);
552
- return readCurrentRouteSnapshot();
553
- }
554
550
  recordReadableRead(currentRouteSource);
555
551
  return readCurrentRouteSnapshot();
556
552
  }
@@ -558,8 +554,8 @@ function syncCurrentRouteSnapshot(pathname, search, hash) {
558
554
  setCurrentRouteSnapshot(pathname, search, hash);
559
555
  }
560
556
  function route(path, Component, options) {
561
- if (getExecutionModel() === "islands") throw new Error("Routes are not supported with islands. Use createSPA (client) or createSSR (server) instead.");
562
557
  if (typeof path === "undefined") throw new Error("route() is only for route registration. Use currentRoute() inside components.");
558
+ if (getExecutionModel() === "islands") throw new Error("Routes are not supported with islands. Use createSPA (client) or createSSR (server) instead.");
563
559
  const currentInst = getCurrentComponentInstance();
564
560
  if (currentInst && currentInst.ssr) throw new Error("route() cannot be called during SSR rendering. Register routes at module load time instead.");
565
561
  if (registrationLocked) throw new Error("Route registration is locked after app startup. Register routes at module load time before calling createSPA or createSSR.");
@@ -606,27 +602,6 @@ function _applyManifest(manifest) {
606
602
  function getRoutes() {
607
603
  return [...routes];
608
604
  }
609
- /** Get routes for a specific namespace. */
610
- function getNamespaceRoutes(namespace) {
611
- return routes.filter((r) => r.namespace === namespace);
612
- }
613
- /** Unload all routes from a namespace (for MFE unmounting). */
614
- function unloadNamespace(namespace) {
615
- const before = routes.length;
616
- for (let i = routes.length - 1; i >= 0; i--) if (routes[i].namespace === namespace) {
617
- const removed = routes[i];
618
- routes.splice(i, 1);
619
- const depth = getDepth(removed.path);
620
- const depthRoutes = routesByDepth.get(depth);
621
- if (depthRoutes) {
622
- const idx = depthRoutes.indexOf(removed);
623
- if (idx >= 0) depthRoutes.splice(idx, 1);
624
- }
625
- }
626
- for (let i = records.length - 1; i >= 0; i--) if (records[i].options.namespace === namespace) records.splice(i, 1);
627
- namespaces.delete(namespace);
628
- return before - routes.length;
629
- }
630
605
  /** Clear all registered routes and records (testing / boot reset). */
631
606
  function clearRoutes() {
632
607
  routes.length = 0;
@@ -641,12 +616,6 @@ function clearRoutes() {
641
616
  setHasRoutes(false);
642
617
  pendingLazy.clear();
643
618
  }
644
- /**
645
- * Get all loaded namespaces (MFE identifiers)
646
- */
647
- function getLoadedNamespaces() {
648
- return Array.from(namespaces);
649
- }
650
619
  function findBestScopedFallbackRecord(pathname, routeRecords) {
651
620
  let bestRecord = null;
652
621
  let bestParams = null;
@@ -784,6 +753,6 @@ function resolveRouteFromRoutes(pathname, routeList) {
784
753
  } : null;
785
754
  }
786
755
  //#endregion
787
- export { Outlet, _applyManifest, _drainLazy, _setActiveRouteAuthOptions, _snapshotLazy, clearRoutes, currentRoute, fallback, getLoadedNamespaces, getManifest, getNamespaceRoutes, getRoutes, group, index, lazy, lockRouteRegistration, page, registerRoutes, resolveRoute, resolveRouteFromRoutes, resolveRouteRequest, route, setServerLocation, syncCurrentRouteSnapshot, unloadNamespace };
756
+ export { Outlet, _applyManifest, _drainLazy, _setActiveRouteAuthOptions, _snapshotLazy, clearRoutes, currentRoute, fallback, getManifest, getRoutes, group, index, lazy, lockRouteRegistration, page, registerRoutes, resolveRoute, resolveRouteFromRoutes, resolveRouteRequest, route, setServerLocation, syncCurrentRouteSnapshot };
788
757
 
789
758
  //# sourceMappingURL=route.js.map