@askrjs/askr 0.0.39 → 0.0.40

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 (250) hide show
  1. package/dist/bench/components/benchmark-row.d.ts.map +1 -1
  2. package/dist/bench/components/benchmark-row.js.map +1 -1
  3. package/dist/bench/components/benchmark-table.js.map +1 -1
  4. package/dist/benchmark.d.ts.map +1 -1
  5. package/dist/benchmark.js +2 -2
  6. package/dist/benchmark.js.map +1 -1
  7. package/dist/bin/askr-ssg.d.ts.map +1 -1
  8. package/dist/bin/askr-ssg.js.map +1 -1
  9. package/dist/boot/index.d.ts +9 -6
  10. package/dist/boot/index.d.ts.map +1 -1
  11. package/dist/boot/index.js +210 -75
  12. package/dist/boot/index.js.map +1 -1
  13. package/dist/common/attr-names.js +17 -0
  14. package/dist/common/attr-names.js.map +1 -0
  15. package/dist/common/component.d.ts.map +1 -1
  16. package/dist/common/control.d.ts.map +1 -1
  17. package/dist/common/control.js.map +1 -1
  18. package/dist/common/env.js.map +1 -1
  19. package/dist/common/jsx.d.ts.map +1 -1
  20. package/dist/common/jsx.js.map +1 -1
  21. package/dist/common/promise.js +8 -0
  22. package/dist/common/promise.js.map +1 -0
  23. package/dist/common/props.d.ts +286 -1
  24. package/dist/common/props.d.ts.map +1 -1
  25. package/dist/common/router-internal.js +6 -0
  26. package/dist/common/router-internal.js.map +1 -0
  27. package/dist/common/router.d.ts +45 -36
  28. package/dist/common/router.d.ts.map +1 -1
  29. package/dist/common/ssr-errors.d.ts.map +1 -1
  30. package/dist/common/ssr-errors.js.map +1 -1
  31. package/dist/common/ssr.d.ts +1 -0
  32. package/dist/common/ssr.d.ts.map +1 -1
  33. package/dist/common/ssr.js +6 -0
  34. package/dist/common/ssr.js.map +1 -0
  35. package/dist/common/vnode.d.ts +2 -1
  36. package/dist/common/vnode.d.ts.map +1 -1
  37. package/dist/common/vnode.js.map +1 -1
  38. package/dist/components/error-boundary.d.ts +7 -4
  39. package/dist/components/error-boundary.d.ts.map +1 -1
  40. package/dist/components/error-boundary.js.map +1 -1
  41. package/dist/components/link.d.ts +2 -1
  42. package/dist/components/link.d.ts.map +1 -1
  43. package/dist/components/link.js +10 -1
  44. package/dist/components/link.js.map +1 -1
  45. package/dist/control/case.d.ts +4 -2
  46. package/dist/control/case.d.ts.map +1 -1
  47. package/dist/control/case.js.map +1 -1
  48. package/dist/control/for.d.ts +2 -1
  49. package/dist/control/for.d.ts.map +1 -1
  50. package/dist/control/for.js.map +1 -1
  51. package/dist/control/shared.d.ts +9 -0
  52. package/dist/control/shared.d.ts.map +1 -0
  53. package/dist/control/shared.js.map +1 -1
  54. package/dist/control/show.d.ts +4 -3
  55. package/dist/control/show.d.ts.map +1 -1
  56. package/dist/control/show.js.map +1 -1
  57. package/dist/data/index.d.ts +94 -14
  58. package/dist/data/index.d.ts.map +1 -1
  59. package/dist/data/index.js +65 -14
  60. package/dist/data/index.js.map +1 -1
  61. package/dist/dev/invariant.js.map +1 -1
  62. package/dist/dev/logger.js.map +1 -1
  63. package/dist/foundations/icon/icon.d.ts.map +1 -1
  64. package/dist/foundations/icon/icon.js.map +1 -1
  65. package/dist/foundations/icon/icon.types.d.ts.map +1 -1
  66. package/dist/foundations/interactions/dismissable.d.ts +9 -3
  67. package/dist/foundations/interactions/dismissable.d.ts.map +1 -1
  68. package/dist/foundations/interactions/dismissable.js.map +1 -1
  69. package/dist/foundations/interactions/focusable.d.ts.map +1 -1
  70. package/dist/foundations/interactions/focusable.js.map +1 -1
  71. package/dist/foundations/interactions/hoverable.d.ts.map +1 -1
  72. package/dist/foundations/interactions/hoverable.js.map +1 -1
  73. package/dist/foundations/interactions/interaction-policy.d.ts +1 -1
  74. package/dist/foundations/interactions/interaction-policy.d.ts.map +1 -1
  75. package/dist/foundations/interactions/interaction-policy.js +3 -3
  76. package/dist/foundations/interactions/interaction-policy.js.map +1 -1
  77. package/dist/foundations/interactions/pressable.d.ts.map +1 -1
  78. package/dist/foundations/interactions/pressable.js.map +1 -1
  79. package/dist/foundations/interactions/roving-focus.d.ts.map +1 -1
  80. package/dist/foundations/interactions/roving-focus.js +8 -8
  81. package/dist/foundations/interactions/roving-focus.js.map +1 -1
  82. package/dist/foundations/state/controllable.d.ts.map +1 -1
  83. package/dist/foundations/state/controllable.js.map +1 -1
  84. package/dist/foundations/structures/collection.d.ts.map +1 -1
  85. package/dist/foundations/structures/collection.js.map +1 -1
  86. package/dist/foundations/structures/layer.d.ts.map +1 -1
  87. package/dist/foundations/structures/layer.js.map +1 -1
  88. package/dist/foundations/structures/layout.d.ts +4 -2
  89. package/dist/foundations/structures/layout.d.ts.map +1 -1
  90. package/dist/foundations/structures/layout.js.map +1 -1
  91. package/dist/foundations/structures/portal.d.ts +9 -25
  92. package/dist/foundations/structures/portal.d.ts.map +1 -1
  93. package/dist/foundations/structures/portal.js +98 -17
  94. package/dist/foundations/structures/portal.js.map +1 -1
  95. package/dist/foundations/structures/presence.d.ts +4 -3
  96. package/dist/foundations/structures/presence.d.ts.map +1 -1
  97. package/dist/foundations/structures/presence.js +2 -2
  98. package/dist/foundations/structures/presence.js.map +1 -1
  99. package/dist/foundations/structures/slot.d.ts +2 -1
  100. package/dist/foundations/structures/slot.d.ts.map +1 -1
  101. package/dist/foundations/structures/slot.js.map +1 -1
  102. package/dist/foundations/utilities/aria.d.ts.map +1 -1
  103. package/dist/foundations/utilities/aria.js.map +1 -1
  104. package/dist/foundations/utilities/compose-handlers.d.ts.map +1 -1
  105. package/dist/foundations/utilities/compose-handlers.js.map +1 -1
  106. package/dist/foundations/utilities/compose-ref.d.ts.map +1 -1
  107. package/dist/foundations/utilities/compose-ref.js.map +1 -1
  108. package/dist/foundations/utilities/event-types.d.ts.map +1 -1
  109. package/dist/foundations/utilities/merge-props.js.map +1 -1
  110. package/dist/foundations/utilities/use-id.d.ts.map +1 -1
  111. package/dist/foundations/utilities/use-id.js.map +1 -1
  112. package/dist/fx/fx.d.ts.map +1 -1
  113. package/dist/fx/fx.js.map +1 -1
  114. package/dist/fx/noop.js.map +1 -1
  115. package/dist/fx/timing.d.ts +5 -4
  116. package/dist/fx/timing.d.ts.map +1 -1
  117. package/dist/fx/timing.js +7 -4
  118. package/dist/fx/timing.js.map +1 -1
  119. package/dist/index.js.map +1 -1
  120. package/dist/jsx/types.d.ts +3 -3
  121. package/dist/jsx/types.d.ts.map +1 -1
  122. package/dist/jsx/utils.d.ts.map +1 -1
  123. package/dist/jsx/utils.js.map +1 -1
  124. package/dist/jsx-dev-runtime.d.ts +6 -2
  125. package/dist/jsx-dev-runtime.d.ts.map +1 -1
  126. package/dist/jsx-dev-runtime.js.map +1 -1
  127. package/dist/jsx-runtime.d.ts +16 -6
  128. package/dist/jsx-runtime.d.ts.map +1 -1
  129. package/dist/jsx-runtime.js.map +1 -1
  130. package/dist/renderer/children.js.map +1 -1
  131. package/dist/renderer/cleanup.js +40 -13
  132. package/dist/renderer/cleanup.js.map +1 -1
  133. package/dist/renderer/dom.js +163 -63
  134. package/dist/renderer/dom.js.map +1 -1
  135. package/dist/renderer/evaluate.js +9 -6
  136. package/dist/renderer/evaluate.js.map +1 -1
  137. package/dist/renderer/fastpath.js.map +1 -1
  138. package/dist/renderer/for-commit.js +1 -0
  139. package/dist/renderer/for-commit.js.map +1 -1
  140. package/dist/renderer/index.js.map +1 -1
  141. package/dist/renderer/keyed.js.map +1 -1
  142. package/dist/renderer/reconcile.js +34 -0
  143. package/dist/renderer/reconcile.js.map +1 -1
  144. package/dist/renderer/utils.js +34 -3
  145. package/dist/renderer/utils.js.map +1 -1
  146. package/dist/router/index.d.ts +2 -2
  147. package/dist/router/match.js +16 -2
  148. package/dist/router/match.js.map +1 -1
  149. package/dist/router/navigate.d.ts +9 -1
  150. package/dist/router/navigate.d.ts.map +1 -1
  151. package/dist/router/navigate.js +235 -81
  152. package/dist/router/navigate.js.map +1 -1
  153. package/dist/router/policy.d.ts +7 -7
  154. package/dist/router/policy.d.ts.map +1 -1
  155. package/dist/router/policy.js +5 -7
  156. package/dist/router/policy.js.map +1 -1
  157. package/dist/router/route-context.js.map +1 -1
  158. package/dist/router/route.d.ts +20 -9
  159. package/dist/router/route.d.ts.map +1 -1
  160. package/dist/router/route.js +95 -29
  161. package/dist/router/route.js.map +1 -1
  162. package/dist/runtime/child-scope.d.ts.map +1 -1
  163. package/dist/runtime/child-scope.js +5 -1
  164. package/dist/runtime/child-scope.js.map +1 -1
  165. package/dist/runtime/component.d.ts +5 -2
  166. package/dist/runtime/component.d.ts.map +1 -1
  167. package/dist/runtime/component.js +85 -25
  168. package/dist/runtime/component.js.map +1 -1
  169. package/dist/runtime/context.d.ts +5 -2
  170. package/dist/runtime/context.d.ts.map +1 -1
  171. package/dist/runtime/context.js +5 -2
  172. package/dist/runtime/context.js.map +1 -1
  173. package/dist/runtime/control.d.ts.map +1 -1
  174. package/dist/runtime/control.js.map +1 -1
  175. package/dist/runtime/derive.d.ts.map +1 -1
  176. package/dist/runtime/derive.js +3 -1
  177. package/dist/runtime/derive.js.map +1 -1
  178. package/dist/runtime/dev-namespace.js.map +1 -1
  179. package/dist/runtime/effect.d.ts.map +1 -1
  180. package/dist/runtime/effect.js +17 -11
  181. package/dist/runtime/effect.js.map +1 -1
  182. package/dist/runtime/events.js +43 -1
  183. package/dist/runtime/events.js.map +1 -1
  184. package/dist/runtime/execution-model.js.map +1 -1
  185. package/dist/runtime/fastlane.js +2 -23
  186. package/dist/runtime/fastlane.js.map +1 -1
  187. package/dist/runtime/for-bench.d.ts.map +1 -1
  188. package/dist/runtime/for-bench.js.map +1 -1
  189. package/dist/runtime/for.d.ts +1 -1
  190. package/dist/runtime/for.d.ts.map +1 -1
  191. package/dist/runtime/for.js +36 -12
  192. package/dist/runtime/for.js.map +1 -1
  193. package/dist/runtime/operations.d.ts +3 -13
  194. package/dist/runtime/operations.d.ts.map +1 -1
  195. package/dist/runtime/operations.js +47 -39
  196. package/dist/runtime/operations.js.map +1 -1
  197. package/dist/runtime/perf-metrics.js.map +1 -1
  198. package/dist/runtime/readable.d.ts +1 -0
  199. package/dist/runtime/readable.d.ts.map +1 -1
  200. package/dist/runtime/readable.js +6 -1
  201. package/dist/runtime/readable.js.map +1 -1
  202. package/dist/runtime/resource-cell.js +13 -2
  203. package/dist/runtime/resource-cell.js.map +1 -1
  204. package/dist/runtime/scheduler.d.ts.map +1 -1
  205. package/dist/runtime/scheduler.js +8 -4
  206. package/dist/runtime/scheduler.js.map +1 -1
  207. package/dist/runtime/selector.d.ts.map +1 -1
  208. package/dist/runtime/selector.js +8 -2
  209. package/dist/runtime/selector.js.map +1 -1
  210. package/dist/runtime/snapshot-source.d.ts.map +1 -1
  211. package/dist/runtime/snapshot-source.js.map +1 -1
  212. package/dist/runtime/ssr-bridge.js.map +1 -1
  213. package/dist/runtime/state.d.ts +4 -3
  214. package/dist/runtime/state.d.ts.map +1 -1
  215. package/dist/runtime/state.js.map +1 -1
  216. package/dist/ssg/batch-render.js +4 -8
  217. package/dist/ssg/batch-render.js.map +1 -1
  218. package/dist/ssg/create-static-gen.d.ts +10 -2
  219. package/dist/ssg/create-static-gen.d.ts.map +1 -1
  220. package/dist/ssg/create-static-gen.js +5 -4
  221. package/dist/ssg/create-static-gen.js.map +1 -1
  222. package/dist/ssg/generate-metadata.js.map +1 -1
  223. package/dist/ssg/incremental-manifest.js.map +1 -1
  224. package/dist/ssg/resolve-ssg-data.js +54 -12
  225. package/dist/ssg/resolve-ssg-data.js.map +1 -1
  226. package/dist/ssg/route-utils.js.map +1 -1
  227. package/dist/ssg/types.d.ts +8 -7
  228. package/dist/ssg/types.d.ts.map +1 -1
  229. package/dist/ssg/write-static-files.js.map +1 -1
  230. package/dist/ssr/attrs.js +3 -2
  231. package/dist/ssr/attrs.js.map +1 -1
  232. package/dist/ssr/context.d.ts +1 -0
  233. package/dist/ssr/context.d.ts.map +1 -1
  234. package/dist/ssr/context.js +1 -0
  235. package/dist/ssr/context.js.map +1 -1
  236. package/dist/ssr/errors.d.ts.map +1 -1
  237. package/dist/ssr/escape.js.map +1 -1
  238. package/dist/ssr/index.d.ts +8 -12
  239. package/dist/ssr/index.d.ts.map +1 -1
  240. package/dist/ssr/index.js +55 -30
  241. package/dist/ssr/index.js.map +1 -1
  242. package/dist/ssr/render-keys.js +15 -4
  243. package/dist/ssr/render-keys.js.map +1 -1
  244. package/dist/ssr/render-resolved.js +33 -0
  245. package/dist/ssr/render-resolved.js.map +1 -0
  246. package/dist/ssr/sink.js.map +1 -1
  247. package/dist/ssr/types.d.ts.map +1 -1
  248. package/dist/ssr/verify-hydration.js +5 -6
  249. package/dist/ssr/verify-hydration.js.map +1 -1
  250. package/package.json +12 -4
@@ -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 AccessAllowDecision,\n AccessDenyDecision,\n AccessDenyStatus,\n AccessRedirectDecision,\n AccessRedirectStatus,\n RouteAuthOptions,\n RouteContext,\n RoutePolicy,\n} from '../common/router';\nimport { isPromiseLike } from '../common/promise';\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 | PromiseLike<string>);\n\nexport function allow(): AccessAllowDecision {\n return { kind: 'allow' };\n}\n\nexport function redirect(\n to: string,\n init: {\n status?: AccessRedirectStatus;\n replace?: boolean;\n } = {}\n): AccessRedirectDecision {\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): AccessDenyDecision {\n return { kind: 'deny', status };\n}\n\nexport function unauthorized(): AccessDenyDecision {\n return deny(401);\n}\n\nexport function forbidden(): AccessDenyDecision {\n return deny(403);\n}\n\nexport function notFound(): AccessDenyDecision {\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 | PromiseLike<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 (isPromiseLike<string>(loginPath)) {\n return Promise.resolve(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 (isPromiseLike<string>(target)) {\n return Promise.resolve(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 (isPromiseLike<boolean>(hasRole)) {\n return Promise.resolve(hasRole).then((next) =>\n next ? allow() : forbidden()\n );\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 (isPromiseLike<boolean>(hasPermission)) {\n return Promise.resolve(hasPermission).then((next) =>\n next ? allow() : forbidden()\n );\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;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"}
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,kBAAkB;EACjD,OAAO;GACL,UAAU,UAAU;GACpB,QAAQ,UAAU;GAClB,MAAM,UAAU;EAClB;CACF,QAAQ;EACN,OAAO;GAAE,UAAU;GAAK,QAAQ;GAAI,MAAM;EAAG;CAC/C;AACF;AAEA,SAAgB,WAAc,KAAW;CACvC,IAAI,OAAO,OAAO,QAAQ,YAAY,CAAC,OAAO,SAAS,GAAG,GAAG;EAC3D,OAAO,OAAO,GAA8B;EAC5C,KAAK,MAAM,OAAO,OAAO,KAAK,GAA8B,GAAG;GAC7D,MAAM,QAAS,IAAgC;GAC/C,IAAI,SAAS,OAAO,UAAU,UAC5B,WAAW,KAAK;EAEpB;CACF;CAEA,OAAO;AACT;AAEA,SAAgB,UAAU,QAA4B;CACpD,MAAM,eAAe,IAAI,gBAAgB,UAAU,EAAE;CACrD,MAAM,0BAAU,IAAI,IAAsB;CAE1C,KAAK,MAAM,CAAC,KAAK,UAAU,aAAa,QAAQ,GAAG;EACjD,MAAM,WAAW,QAAQ,IAAI,GAAG;EAChC,IAAI,UACF,SAAS,KAAK,KAAK;OAEnB,QAAQ,IAAI,KAAK,CAAC,KAAK,CAAC;CAE5B;CAuBA,OAAO,WAAW;EApBhB,IAAI,KAAa;GACf,MAAM,SAAS,QAAQ,IAAI,GAAG;GAC9B,OAAO,SAAS,OAAO,KAAK;EAC9B;EACA,OAAO,KAAa;GAClB,MAAM,SAAS,QAAQ,IAAI,GAAG;GAC9B,OAAO,SAAS,CAAC,GAAG,MAAM,IAAI,CAAC;EACjC;EACA,IAAI,KAAa;GACf,OAAO,QAAQ,IAAI,GAAG;EACxB;EACA,SAAS;GACP,MAAM,SAA4C,CAAC;GACnD,KAAK,MAAM,CAAC,KAAK,WAAW,QAAQ,QAAQ,GAC1C,OAAO,OAAO,OAAO,SAAS,IAAI,CAAC,GAAG,MAAM,IAAI,OAAO;GAEzD,OAAO;EACT;CAGgB,CAAK;AACzB;AAEA,SAAgB,sBACd,QACA,QACA,SAIwC;CACxC,MAAM,SAAS,cAAc,MAAM;CACnC,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;CAClB;AACF;AAEA,SAAgB,kBACd,QACA,QACA,SAOc;CAOd,OAAO,qBAAqB;EAL1B,GAAG,sBAAsB,QAAQ,QAAQ,OAAO;EAChD,SAAS,QAAQ,WAAW;EAC5B,MAAM,QAAQ,QAAQ;CAGI,GAAS,QAAQ,IAAI;AACnD"}
@@ -1,6 +1,14 @@
1
- import { AccessDecision, AccessDenyDecision, AccessRedirectDecision, GroupHelperOptions, LayoutScopeRecord, PageHelperOptions, PageScopeRecord, ParsedSegment, RegisterRoutesOptions, ResolvedRoute, Route, RouteAuthOptions, RouteComponent, RouteContext, RouteDefinition, RouteHandler, RouteManifest, RouteMatch, RouteOptions, RoutePolicy, RouteQuery, RouteRecord, RouteRenderResult, RouteRequestOptions, RouteRequestResult, RouteSnapshot } from "../common/router.js";
1
+ import { JSXElement } from "../common/jsx.js";
2
+ import { RenderableChild } from "../common/vnode.js";
3
+ import { AccessDecision, AccessDenyDecision, AccessRedirectDecision, GroupHelperOptions, LayoutScopeRecord, PageHelperOptions, PageScopeRecord, ParsedSegment, RegisterRoutesOptions, ResolvedRoute, Route, RouteAuthOptions, RouteComponent, RouteContext, RouteDefinition, RouteHandler, RouteManifest, RouteMatch, RouteOptions, RouteParams, RoutePathParams, RoutePolicy, RouteQuery, RouteRecord, RouteRenderResult, RouteRequestOptions, RouteRequestResult, RouteSnapshot } from "../common/router.js";
2
4
 
3
5
  //#region src/router/route.d.ts
6
+ type AnyRouteComponent = (...args: any[]) => RenderableChild;
7
+ type RouteComponentParam<TComponent extends AnyRouteComponent> = Parameters<TComponent> extends [] ? unknown : Parameters<TComponent>[0];
8
+ type CompatibleAbsoluteRouteComponent<Path extends string, TComponent extends AnyRouteComponent> = Parameters<TComponent> extends [] ? TComponent : RoutePathParams<Path> extends RouteComponentParam<TComponent> ? TComponent : never;
9
+ type CompatibleRelativeRouteComponent<Path extends string, TComponent extends AnyRouteComponent> = Parameters<TComponent> extends [] ? TComponent : RouteComponentParam<TComponent> extends Record<keyof RoutePathParams<Path>, string> ? TComponent : never;
10
+ type CompatibleRouteComponent<Path extends string, TComponent extends AnyRouteComponent> = Path extends `/${string}` ? CompatibleAbsoluteRouteComponent<Path, TComponent> : CompatibleRelativeRouteComponent<Path, TComponent>;
11
+ type RouteOptionsForComponent<Path extends string, TComponent extends AnyRouteComponent> = Parameters<TComponent> extends [] ? RouteOptions<RoutePathParams<Path>> : RouteComponentParam<TComponent> extends RouteParams ? RouteOptions<RouteComponentParam<TComponent>> : RouteOptions<RoutePathParams<Path>>;
4
12
  declare function setServerLocation(url: string | null): void;
5
13
  declare function _setActiveRouteAuthOptions(auth: RouteAuthOptions | undefined): void;
6
14
  /**
@@ -10,7 +18,7 @@ declare function _setActiveRouteAuthOptions(auth: RouteAuthOptions | undefined):
10
18
  declare function lockRouteRegistration(): void;
11
19
  declare function _lockRouteRegistrationForTests(): void;
12
20
  declare function _unlockRouteRegistrationForTests(): void;
13
- declare function Outlet(): unknown;
21
+ declare function Outlet(): JSXElement;
14
22
  /**
15
23
  * Snapshot the current in-flight lazy() imports.
16
24
  * Boot uses this before clearing route state so manifest-based startup can
@@ -38,21 +46,23 @@ declare function _snapshotLazy(): Promise<unknown>[];
38
46
  * export default function DashboardPage() { … }
39
47
  * ```
40
48
  */
41
- declare function lazy(factory: () => Promise<{
42
- default: RouteComponent;
43
- } | RouteComponent>): RouteComponent;
49
+ declare function lazy<TComponent extends AnyRouteComponent>(factory: () => Promise<{
50
+ default: TComponent;
51
+ } | TComponent>): TComponent;
44
52
  /**
45
53
  * Wait for all pending `lazy()` imports to settle.
46
54
  * Called automatically by `createSPA` / `hydrateSPA` before mounting.
47
55
  */
48
56
  declare function _drainLazy(additionalPending?: Iterable<Promise<unknown>>): Promise<void>;
49
57
  declare function group(options: GroupHelperOptions, fn: RouteDefinition): void;
50
- declare function page(path: string, Component: RouteComponent, fn: RouteDefinition): void;
51
- declare function page(path: string, Component: RouteComponent, options: PageHelperOptions, fn: RouteDefinition): void;
58
+ declare function page<const TPath extends string>(path: TPath, Component: RouteComponent<RoutePathParams<TPath>>, fn: RouteDefinition): void;
59
+ declare function page<const TPath extends string, TComponent extends AnyRouteComponent>(path: TPath, Component: CompatibleRouteComponent<TPath, TComponent>, fn: RouteDefinition): void;
60
+ declare function page<const TPath extends string>(path: TPath, Component: RouteComponent<RoutePathParams<TPath>>, options: PageHelperOptions, fn: RouteDefinition): void;
61
+ declare function page<const TPath extends string, TComponent extends AnyRouteComponent>(path: TPath, Component: CompatibleRouteComponent<TPath, TComponent>, options: PageHelperOptions, fn: RouteDefinition): void;
52
62
  declare function index(Component: RouteComponent, options?: RouteOptions): void;
53
63
  declare function fallback(Component: RouteComponent): void;
54
64
  declare function registerRoutes(definition: RouteDefinition, options?: RegisterRoutesOptions): void;
55
- declare function currentRoute(): RouteSnapshot;
65
+ declare function currentRoute<TParams extends RouteParams = RouteParams>(): RouteSnapshot<TParams>;
56
66
  declare function syncCurrentRouteSnapshot(pathname: string, search: string, hash: string): void;
57
67
  /**
58
68
  * Register a route.
@@ -66,7 +76,8 @@ declare function syncCurrentRouteSnapshot(pathname: string, search: string, hash
66
76
  * });
67
77
  * ```
68
78
  */
69
- declare function route(path: string, Component: RouteComponent, options?: RouteOptions): void;
79
+ declare function route<const TPath extends string>(path: TPath, Component: RouteComponent<RoutePathParams<TPath>>, options?: RouteOptions<RoutePathParams<TPath>>): void;
80
+ declare function route<const TPath extends string, TComponent extends AnyRouteComponent>(path: TPath, Component: CompatibleRouteComponent<TPath, TComponent>, options?: RouteOptionsForComponent<TPath, TComponent>): void;
70
81
  /**
71
82
  * Return the normalized route manifest built from registered route definitions.
72
83
  *
@@ -1 +1 @@
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
+ {"version":3,"file":"route.d.ts","names":[],"sources":["../../src/router/route.ts"],"mappings":"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"}
@@ -1,7 +1,10 @@
1
+ import { ELEMENT_TYPE, Fragment } from "../common/jsx.js";
1
2
  import { defineContext, readContext } from "../runtime/context.js";
2
3
  import { markReactivePropsDirtySource, markReadableDerivedSubscribersDirty, notifyReadableReaders, recordReadableRead } from "../runtime/readable.js";
4
+ import { isPromiseLike } from "../common/promise.js";
3
5
  import { getCurrentComponentInstance } from "../runtime/component.js";
4
- import { computeRank, matchSegments, parseSegments } from "./match.js";
6
+ import { ROUTE_ROOT_COMPONENT } from "../common/router-internal.js";
7
+ import { computeRank, matchSegments, parseSegments, splitPathSegments } from "./match.js";
5
8
  import { requireAuth, requireGuest, requirePermission, requireRole } from "./policy.js";
6
9
  import { buildRouteContext, buildRouteContextBase, deepFreeze, makeQuery, parseLocation } from "./route-context.js";
7
10
  import { getExecutionModel } from "../runtime/execution-model.js";
@@ -84,9 +87,6 @@ let serverLocation = null;
84
87
  function setServerLocation(url) {
85
88
  serverLocation = url;
86
89
  }
87
- function isPromise(value) {
88
- return value instanceof Promise;
89
- }
90
90
  function buildRouteSnapshot(pathname, search, hash) {
91
91
  const query = makeQuery(search);
92
92
  const matches = computeMatchesFromRoutes(pathname, getActiveRoutes());
@@ -109,12 +109,11 @@ function matchFallbackPrefix(pathname, fallbackPrefix) {
109
109
  const normalizedPath = pathname.endsWith("/") && pathname !== "/" ? pathname.slice(0, -1) : pathname;
110
110
  const normalizedPrefix = fallbackPrefix.endsWith("/") && fallbackPrefix !== "/" ? fallbackPrefix.slice(0, -1) : fallbackPrefix;
111
111
  if (normalizedPrefix === "/") {
112
- const urlParts = normalizedPath === "/" ? [] : normalizedPath.split("/").filter(Boolean);
112
+ const urlParts = splitPathSegments(normalizedPath);
113
113
  return { "*": urlParts.length === 0 ? "/" : urlParts.length === 1 ? urlParts[0] : "/" + urlParts.join("/") };
114
114
  }
115
115
  if (normalizedPath !== normalizedPrefix && !normalizedPath.startsWith(`${normalizedPrefix}/`)) return null;
116
- const remainder = normalizedPath === normalizedPrefix ? "/" : normalizedPath.slice(normalizedPrefix.length + 1);
117
- const remainderParts = remainder === "/" ? [] : remainder.split("/").filter(Boolean);
116
+ const remainderParts = splitPathSegments(normalizedPath === normalizedPrefix ? "/" : normalizedPath.slice(normalizedPrefix.length));
118
117
  return { "*": remainderParts.length === 0 ? "/" : remainderParts.length === 1 ? remainderParts[0] : "/" + remainderParts.join("/") };
119
118
  }
120
119
  function computeMatchesFromRoutes(pathname, routesList) {
@@ -129,7 +128,7 @@ function computeMatchesFromRoutes(pathname, routesList) {
129
128
  }
130
129
  function findBestResolvedRouteFromRoutes(pathname, routeList) {
131
130
  const normalized = pathname.endsWith("/") && pathname !== "/" ? pathname.slice(0, -1) : pathname;
132
- const urlParts = normalized === "/" ? [] : normalized.split("/").filter(Boolean);
131
+ const urlParts = splitPathSegments(normalized);
133
132
  const sorted = cachedSortedList(routeList);
134
133
  let bestRoute = null;
135
134
  let bestParams = {};
@@ -201,6 +200,13 @@ function getCurrentPageChain() {
201
200
  function hasActivePageScope() {
202
201
  return registrationScopeStack.some((scope) => !!scope.page);
203
202
  }
203
+ function getCurrentPageScope() {
204
+ for (let index = registrationScopeStack.length - 1; index >= 0; index -= 1) {
205
+ const scope = registrationScopeStack[index];
206
+ if (scope.kind === "page") return scope;
207
+ }
208
+ return null;
209
+ }
204
210
  function getCurrentScopeKind() {
205
211
  return registrationScopeStack[registrationScopeStack.length - 1]?.kind ?? null;
206
212
  }
@@ -221,10 +227,24 @@ function lockRouteRegistration() {
221
227
  }
222
228
  function validateRoutePath(path) {
223
229
  if (!path.startsWith("/")) throw new Error(`Route path must begin with "/". Got: "${path}"`);
230
+ if (/\/{2,}/.test(path)) throw new Error("Route path cannot contain consecutive slashes.");
224
231
  if (/:([^/{}]+)/.test(path)) {
225
232
  const suggested = path.replace(/:([^/{}]+)/g, "{$1}");
226
233
  throw new Error(`Route parameter syntax uses {name} interpolation, not :name. Use "${suggested}" instead of "${path}".`);
227
234
  }
235
+ const segments = path.split("/").filter(Boolean);
236
+ const seenParamNames = /* @__PURE__ */ new Set();
237
+ for (const segment of segments) {
238
+ if (segment === "*") continue;
239
+ const hasOpenBrace = segment.includes("{");
240
+ const hasCloseBrace = segment.includes("}");
241
+ if (!hasOpenBrace && !hasCloseBrace) continue;
242
+ if (!(segment.startsWith("{") && segment.endsWith("}"))) throw new Error("Route parameter segments must use complete {name} interpolation.");
243
+ const paramName = segment.slice(1, -1).trim();
244
+ if (!paramName) throw new Error("Route parameter name cannot be empty.");
245
+ if (seenParamNames.has(paramName)) throw new Error(`Route path cannot reuse duplicate parameter name "${paramName}".`);
246
+ seenParamNames.add(paramName);
247
+ }
228
248
  }
229
249
  function normalizeAbsoluteRoutePath(path) {
230
250
  if (!path || path === "/") return "/";
@@ -283,7 +303,12 @@ function addRouteToStores(routeObj) {
283
303
  const pendingLazy = /* @__PURE__ */ new Set();
284
304
  const outletContext = defineContext(null);
285
305
  function Outlet() {
286
- return readContext(outletContext);
306
+ return {
307
+ $$typeof: ELEMENT_TYPE,
308
+ type: Fragment,
309
+ props: { children: readContext(outletContext) },
310
+ key: null
311
+ };
287
312
  }
288
313
  /**
289
314
  * Snapshot the current in-flight lazy() imports.
@@ -325,11 +350,11 @@ function lazy(factory) {
325
350
  pendingLazy.delete(promise);
326
351
  });
327
352
  pendingLazy.add(promise);
328
- return (params) => {
353
+ return ((params) => {
329
354
  if (loadError) throw loadError;
330
355
  if (!resolved) throw new Error("lazy() component used before it was resolved. Await createSPA() / hydrateSPA() to ensure all chunks load first.");
331
356
  return resolved(params);
332
- };
357
+ });
333
358
  }
334
359
  /**
335
360
  * Wait for all pending `lazy()` imports to settle.
@@ -351,6 +376,9 @@ function page(path, Component, optionsOrFn, maybeFn) {
351
376
  pushPageScope(path, Component, options, fn);
352
377
  }
353
378
  function index(Component, options) {
379
+ const pageScope = getCurrentPageScope();
380
+ if (pageScope?.hasIndex) throw new Error("page() cannot declare multiple index routes.");
381
+ if (pageScope) pageScope.hasIndex = true;
354
382
  registerRouteAtResolvedPath(resolveIndexPath(), Component, options);
355
383
  }
356
384
  function fallback(Component) {
@@ -401,6 +429,7 @@ function pushPageScope(path, Component, options, fn) {
401
429
  kind: "page",
402
430
  pathPrefix: resolvePageScopePath(path),
403
431
  page: Component,
432
+ hasIndex: false,
404
433
  policies,
405
434
  state: nextAccessScopeState(options, getCurrentAccessScopeState())
406
435
  }, fn);
@@ -451,15 +480,36 @@ function normalizeRouteOptions(options) {
451
480
  ...options.namespace ? { namespace: options.namespace } : {}
452
481
  };
453
482
  }
454
- function applyPageChain(pageChain, params, content) {
483
+ function applyPageChain(pageChain, params, content, deferComponents = false) {
455
484
  let nextContent = content;
456
485
  for (let i = pageChain.length - 1; i >= 0; i--) nextContent = outletContext.Scope({
457
486
  value: nextContent,
458
- children: pageChain[i].component(params)
487
+ children: deferComponents ? createRouteComponentVNode(pageChain[i].component, params) : pageChain[i].component(params)
459
488
  });
460
489
  return nextContent;
461
490
  }
491
+ function createRouteComponentVNode(component, params, routeRoot = false) {
492
+ return {
493
+ $$typeof: ELEMENT_TYPE,
494
+ type: component,
495
+ props: params,
496
+ key: null,
497
+ ...routeRoot ? { [ROUTE_ROOT_COMPONENT]: true } : {}
498
+ };
499
+ }
500
+ function createRouteHandler(component, pageChain, layoutChain, deferComponents = false) {
501
+ return (params) => {
502
+ let content = deferComponents ? createRouteComponentVNode(component, params, true) : component(params);
503
+ content = applyPageChain(pageChain, params, content, deferComponents);
504
+ for (let i = layoutChain.length - 1; i >= 0; i--) content = layoutChain[i].component({ children: content });
505
+ return content;
506
+ };
507
+ }
508
+ function getRenderHandler(record) {
509
+ return record.renderHandler ?? createRouteHandler(record.component, record.pageChain, record.layoutChain, true);
510
+ }
462
511
  function registerRouteAtResolvedPath(path, Component, options, metadata) {
512
+ validateRoutePath(path);
463
513
  validateAccessMetadata(options ?? {}, {
464
514
  authConfigured: getCurrentRegistrationSession().authConfigured,
465
515
  state: getCurrentAccessScopeState()
@@ -472,12 +522,8 @@ function registerRouteAtResolvedPath(path, Component, options, metadata) {
472
522
  const comp = Component;
473
523
  const normalizedOptions = normalizeRouteOptions(options);
474
524
  const policies = [...getCurrentInheritedPolicies(), ...normalizedOptions?.policies ?? []];
475
- const handler = (params) => {
476
- let content = comp(params);
477
- content = applyPageChain(pageChain, params, content);
478
- for (let i = chain.length - 1; i >= 0; i--) content = chain[i].component({ children: content });
479
- return content;
480
- };
525
+ const handler = createRouteHandler(comp, pageChain, chain);
526
+ const renderHandler = createRouteHandler(comp, pageChain, chain, true);
481
527
  insertRecordSorted({
482
528
  path,
483
529
  component: comp,
@@ -491,6 +537,7 @@ function registerRouteAtResolvedPath(path, Component, options, metadata) {
491
537
  } : policies.length > 0 ? { policies } : {},
492
538
  isFallback,
493
539
  handler,
540
+ renderHandler,
494
541
  ...metadata?.fallbackPrefix ? { fallbackPrefix: metadata.fallbackPrefix } : {}
495
542
  });
496
543
  addRouteToStores({
@@ -646,7 +693,7 @@ function findBestScopedFallbackRecord(pathname, routeRecords) {
646
693
  */
647
694
  function resolveRoute(pathname) {
648
695
  const normalized = pathname.endsWith("/") && pathname !== "/" ? pathname.slice(0, -1) : pathname;
649
- const urlParts = normalized === "/" ? [] : normalized.split("/").filter(Boolean);
696
+ const urlParts = splitPathSegments(normalized);
650
697
  for (const record of records) {
651
698
  if (record.fallbackPrefix) continue;
652
699
  const params = matchSegments(urlParts, record.segments);
@@ -664,7 +711,7 @@ function resolveRoute(pathname) {
664
711
  function getMatchingRecord(target, routeRecords) {
665
712
  const location = parseLocation(target);
666
713
  const normalized = location.pathname.endsWith("/") && location.pathname !== "/" ? location.pathname.slice(0, -1) : location.pathname;
667
- const urlParts = normalized === "/" ? [] : normalized.split("/").filter(Boolean);
714
+ const urlParts = splitPathSegments(normalized);
668
715
  for (const record of routeRecords) {
669
716
  if (record.fallbackPrefix) continue;
670
717
  const params = matchSegments(urlParts, record.segments);
@@ -684,32 +731,51 @@ function getDefaultRouteMode() {
684
731
  if (typeof window !== "undefined") return "spa";
685
732
  return "ssr";
686
733
  }
687
- function buildRenderResult(record, params) {
734
+ function createRenderDataAwareHandler(handler, data) {
735
+ return (params, context) => {
736
+ const renderContext = getRenderContext();
737
+ if (renderContext) renderContext.renderData = data ?? null;
738
+ return handler(params, context);
739
+ };
740
+ }
741
+ function buildRenderResult(record, params, mode) {
742
+ const renderHandler = getRenderHandler(record);
743
+ const loader = mode === "ssr" ? record.options?.loader : void 0;
744
+ if (loader) {
745
+ const loaded = loader({ params });
746
+ const finalize = (data) => ({
747
+ kind: "render",
748
+ handler: createRenderDataAwareHandler(renderHandler, data),
749
+ params
750
+ });
751
+ if (isPromiseLike(loaded)) return Promise.resolve(loaded).then((data) => finalize(data));
752
+ return finalize(loaded);
753
+ }
688
754
  return {
689
755
  kind: "render",
690
- handler: record.handler,
756
+ handler: renderHandler,
691
757
  params
692
758
  };
693
759
  }
694
760
  function continueRoutePolicies(policies, context, record, params, startIndex = 0) {
695
761
  for (let index = startIndex; index < policies.length; index += 1) {
696
762
  const policyResult = policies[index](context);
697
- if (isPromise(policyResult)) return policyResult.then((next) => {
763
+ if (isPromiseLike(policyResult)) return Promise.resolve(policyResult).then((next) => {
698
764
  if (next.kind !== "allow") return next;
699
765
  return continueRoutePolicies(policies, context, record, params, index + 1);
700
766
  });
701
767
  if (policyResult.kind !== "allow") return policyResult;
702
768
  }
703
- return buildRenderResult(record, params);
769
+ return buildRenderResult(record, params, context.mode);
704
770
  }
705
771
  function resolveRouteRequest(target, options = {}) {
706
772
  const match = getMatchingRecord(target, options.manifest?.records ?? records);
707
773
  if (!match) return null;
708
774
  const { record, params } = match;
709
775
  const policies = getRoutePolicies(record.options);
710
- if (policies.length === 0) return buildRenderResult(record, params);
711
- const signal = options.signal ?? getRenderContext()?.signal ?? new AbortController().signal;
712
776
  const mode = options.mode ?? getDefaultRouteMode();
777
+ if (policies.length === 0) return buildRenderResult(record, params, mode);
778
+ const signal = options.signal ?? getRenderContext()?.signal ?? new AbortController().signal;
713
779
  const auth = getActiveRouteAuthOptions(options.auth ?? options.manifest?.auth);
714
780
  const baseContext = buildRouteContextBase(target, params, {
715
781
  mode,
@@ -727,7 +793,7 @@ function resolveRouteRequest(target, options = {}) {
727
793
  user: null
728
794
  });
729
795
  const authState = auth.resolve(baseContext);
730
- if (isPromise(authState)) return authState.then((next) => finalize(next));
796
+ if (isPromiseLike(authState)) return Promise.resolve(authState).then((next) => finalize(next));
731
797
  return finalize(authState);
732
798
  }
733
799
  /**
@@ -753,6 +819,6 @@ function resolveRouteFromRoutes(pathname, routeList) {
753
819
  } : null;
754
820
  }
755
821
  //#endregion
756
- export { Outlet, _applyManifest, _drainLazy, _setActiveRouteAuthOptions, _snapshotLazy, clearRoutes, currentRoute, fallback, getManifest, getRoutes, group, index, lazy, lockRouteRegistration, page, registerRoutes, resolveRoute, resolveRouteFromRoutes, resolveRouteRequest, route, setServerLocation, syncCurrentRouteSnapshot };
822
+ export { Outlet, _applyManifest, _drainLazy, _setActiveRouteAuthOptions, _snapshotLazy, clearRoutes, currentRoute, fallback, getManifest, getRoutes, group, index, lazy, lockRouteRegistration, page, registerRoutes, resolveRouteFromRoutes, resolveRouteRequest, route, setServerLocation, syncCurrentRouteSnapshot };
757
823
 
758
824
  //# sourceMappingURL=route.js.map