@rangojs/router 0.0.0-experimental.8 → 0.0.0-experimental.80

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 (312) hide show
  1. package/AGENTS.md +9 -0
  2. package/README.md +942 -4
  3. package/dist/bin/rango.js +1689 -0
  4. package/dist/vite/index.js +4960 -935
  5. package/package.json +70 -60
  6. package/skills/breadcrumbs/SKILL.md +250 -0
  7. package/skills/cache-guide/SKILL.md +294 -0
  8. package/skills/caching/SKILL.md +93 -23
  9. package/skills/composability/SKILL.md +172 -0
  10. package/skills/debug-manifest/SKILL.md +12 -8
  11. package/skills/document-cache/SKILL.md +18 -16
  12. package/skills/fonts/SKILL.md +167 -0
  13. package/skills/handler-use/SKILL.md +362 -0
  14. package/skills/hooks/SKILL.md +334 -72
  15. package/skills/host-router/SKILL.md +218 -0
  16. package/skills/intercept/SKILL.md +151 -8
  17. package/skills/layout/SKILL.md +122 -3
  18. package/skills/links/SKILL.md +92 -31
  19. package/skills/loader/SKILL.md +404 -44
  20. package/skills/middleware/SKILL.md +205 -37
  21. package/skills/migrate-nextjs/SKILL.md +560 -0
  22. package/skills/migrate-react-router/SKILL.md +764 -0
  23. package/skills/mime-routes/SKILL.md +128 -0
  24. package/skills/parallel/SKILL.md +263 -1
  25. package/skills/prerender/SKILL.md +685 -0
  26. package/skills/rango/SKILL.md +87 -16
  27. package/skills/response-routes/SKILL.md +411 -0
  28. package/skills/route/SKILL.md +281 -14
  29. package/skills/router-setup/SKILL.md +210 -32
  30. package/skills/tailwind/SKILL.md +129 -0
  31. package/skills/theme/SKILL.md +9 -8
  32. package/skills/typesafety/SKILL.md +328 -89
  33. package/skills/use-cache/SKILL.md +324 -0
  34. package/src/__internal.ts +102 -4
  35. package/src/bin/rango.ts +321 -0
  36. package/src/browser/action-coordinator.ts +97 -0
  37. package/src/browser/action-response-classifier.ts +99 -0
  38. package/src/browser/app-version.ts +14 -0
  39. package/src/browser/event-controller.ts +92 -64
  40. package/src/browser/history-state.ts +80 -0
  41. package/src/browser/intercept-utils.ts +52 -0
  42. package/src/browser/link-interceptor.ts +24 -4
  43. package/src/browser/logging.ts +55 -0
  44. package/src/browser/merge-segment-loaders.ts +20 -12
  45. package/src/browser/navigation-bridge.ts +317 -560
  46. package/src/browser/navigation-client.ts +206 -68
  47. package/src/browser/navigation-store.ts +73 -55
  48. package/src/browser/navigation-transaction.ts +297 -0
  49. package/src/browser/network-error-handler.ts +61 -0
  50. package/src/browser/partial-update.ts +343 -316
  51. package/src/browser/prefetch/cache.ts +216 -0
  52. package/src/browser/prefetch/fetch.ts +206 -0
  53. package/src/browser/prefetch/observer.ts +65 -0
  54. package/src/browser/prefetch/policy.ts +48 -0
  55. package/src/browser/prefetch/queue.ts +160 -0
  56. package/src/browser/prefetch/resource-ready.ts +77 -0
  57. package/src/browser/rango-state.ts +112 -0
  58. package/src/browser/react/Link.tsx +253 -74
  59. package/src/browser/react/NavigationProvider.tsx +87 -11
  60. package/src/browser/react/context.ts +11 -0
  61. package/src/browser/react/filter-segment-order.ts +11 -0
  62. package/src/browser/react/index.ts +12 -12
  63. package/src/browser/react/location-state-shared.ts +95 -53
  64. package/src/browser/react/location-state.ts +60 -15
  65. package/src/browser/react/mount-context.ts +6 -1
  66. package/src/browser/react/nonce-context.ts +23 -0
  67. package/src/browser/react/shallow-equal.ts +27 -0
  68. package/src/browser/react/use-action.ts +29 -51
  69. package/src/browser/react/use-client-cache.ts +5 -3
  70. package/src/browser/react/use-handle.ts +30 -126
  71. package/src/browser/react/use-href.tsx +2 -2
  72. package/src/browser/react/use-link-status.ts +6 -5
  73. package/src/browser/react/use-navigation.ts +44 -65
  74. package/src/browser/react/use-params.ts +65 -0
  75. package/src/browser/react/use-pathname.ts +47 -0
  76. package/src/browser/react/use-router.ts +76 -0
  77. package/src/browser/react/use-search-params.ts +56 -0
  78. package/src/browser/react/use-segments.ts +80 -97
  79. package/src/browser/response-adapter.ts +73 -0
  80. package/src/browser/rsc-router.tsx +214 -58
  81. package/src/browser/scroll-restoration.ts +127 -52
  82. package/src/browser/segment-reconciler.ts +243 -0
  83. package/src/browser/segment-structure-assert.ts +16 -0
  84. package/src/browser/server-action-bridge.ts +510 -603
  85. package/src/browser/shallow.ts +6 -1
  86. package/src/browser/types.ts +141 -48
  87. package/src/browser/validate-redirect-origin.ts +29 -0
  88. package/src/build/generate-manifest.ts +235 -24
  89. package/src/build/generate-route-types.ts +39 -0
  90. package/src/build/index.ts +13 -0
  91. package/src/build/route-trie.ts +291 -0
  92. package/src/build/route-types/ast-helpers.ts +25 -0
  93. package/src/build/route-types/ast-route-extraction.ts +98 -0
  94. package/src/build/route-types/codegen.ts +102 -0
  95. package/src/build/route-types/include-resolution.ts +418 -0
  96. package/src/build/route-types/param-extraction.ts +48 -0
  97. package/src/build/route-types/per-module-writer.ts +128 -0
  98. package/src/build/route-types/router-processing.ts +618 -0
  99. package/src/build/route-types/scan-filter.ts +85 -0
  100. package/src/build/runtime-discovery.ts +231 -0
  101. package/src/cache/background-task.ts +34 -0
  102. package/src/cache/cache-key-utils.ts +44 -0
  103. package/src/cache/cache-policy.ts +125 -0
  104. package/src/cache/cache-runtime.ts +342 -0
  105. package/src/cache/cache-scope.ts +167 -309
  106. package/src/cache/cf/cf-cache-store.ts +571 -17
  107. package/src/cache/cf/index.ts +13 -3
  108. package/src/cache/document-cache.ts +116 -77
  109. package/src/cache/handle-capture.ts +81 -0
  110. package/src/cache/handle-snapshot.ts +41 -0
  111. package/src/cache/index.ts +1 -15
  112. package/src/cache/memory-segment-store.ts +191 -13
  113. package/src/cache/profile-registry.ts +73 -0
  114. package/src/cache/read-through-swr.ts +134 -0
  115. package/src/cache/segment-codec.ts +256 -0
  116. package/src/cache/taint.ts +153 -0
  117. package/src/cache/types.ts +72 -122
  118. package/src/client.rsc.tsx +3 -1
  119. package/src/client.tsx +135 -301
  120. package/src/component-utils.ts +4 -4
  121. package/src/components/DefaultDocument.tsx +5 -1
  122. package/src/context-var.ts +156 -0
  123. package/src/debug.ts +19 -9
  124. package/src/errors.ts +108 -2
  125. package/src/handle.ts +55 -29
  126. package/src/handles/MetaTags.tsx +73 -20
  127. package/src/handles/breadcrumbs.ts +66 -0
  128. package/src/handles/index.ts +1 -0
  129. package/src/handles/meta.ts +30 -13
  130. package/src/host/cookie-handler.ts +21 -15
  131. package/src/host/errors.ts +8 -8
  132. package/src/host/index.ts +4 -7
  133. package/src/host/pattern-matcher.ts +27 -27
  134. package/src/host/router.ts +61 -39
  135. package/src/host/testing.ts +8 -8
  136. package/src/host/types.ts +15 -7
  137. package/src/host/utils.ts +1 -1
  138. package/src/href-client.ts +119 -29
  139. package/src/index.rsc.ts +155 -19
  140. package/src/index.ts +251 -30
  141. package/src/internal-debug.ts +11 -0
  142. package/src/loader.rsc.ts +26 -157
  143. package/src/loader.ts +27 -10
  144. package/src/network-error-thrower.tsx +3 -1
  145. package/src/outlet-provider.tsx +45 -0
  146. package/src/prerender/param-hash.ts +37 -0
  147. package/src/prerender/store.ts +186 -0
  148. package/src/prerender.ts +524 -0
  149. package/src/reverse.ts +354 -0
  150. package/src/root-error-boundary.tsx +41 -29
  151. package/src/route-content-wrapper.tsx +7 -4
  152. package/src/route-definition/dsl-helpers.ts +1121 -0
  153. package/src/route-definition/helper-factories.ts +200 -0
  154. package/src/route-definition/helpers-types.ts +478 -0
  155. package/src/route-definition/index.ts +55 -0
  156. package/src/route-definition/redirect.ts +101 -0
  157. package/src/route-definition/resolve-handler-use.ts +149 -0
  158. package/src/route-definition.ts +1 -1428
  159. package/src/route-map-builder.ts +217 -123
  160. package/src/route-name.ts +53 -0
  161. package/src/route-types.ts +77 -8
  162. package/src/router/content-negotiation.ts +215 -0
  163. package/src/router/debug-manifest.ts +72 -0
  164. package/src/router/error-handling.ts +9 -9
  165. package/src/router/find-match.ts +160 -0
  166. package/src/router/handler-context.ts +438 -86
  167. package/src/router/intercept-resolution.ts +402 -0
  168. package/src/router/lazy-includes.ts +237 -0
  169. package/src/router/loader-resolution.ts +356 -128
  170. package/src/router/logging.ts +251 -0
  171. package/src/router/manifest.ts +163 -35
  172. package/src/router/match-api.ts +555 -0
  173. package/src/router/match-context.ts +5 -3
  174. package/src/router/match-handlers.ts +440 -0
  175. package/src/router/match-middleware/background-revalidation.ts +108 -93
  176. package/src/router/match-middleware/cache-lookup.ts +460 -10
  177. package/src/router/match-middleware/cache-store.ts +98 -26
  178. package/src/router/match-middleware/intercept-resolution.ts +57 -17
  179. package/src/router/match-middleware/segment-resolution.ts +80 -6
  180. package/src/router/match-pipelines.ts +10 -45
  181. package/src/router/match-result.ts +135 -35
  182. package/src/router/metrics.ts +240 -15
  183. package/src/router/middleware-cookies.ts +55 -0
  184. package/src/router/middleware-types.ts +220 -0
  185. package/src/router/middleware.ts +324 -369
  186. package/src/router/navigation-snapshot.ts +182 -0
  187. package/src/router/pattern-matching.ts +211 -43
  188. package/src/router/prerender-match.ts +502 -0
  189. package/src/router/preview-match.ts +98 -0
  190. package/src/router/request-classification.ts +310 -0
  191. package/src/router/revalidation.ts +137 -38
  192. package/src/router/route-snapshot.ts +245 -0
  193. package/src/router/router-context.ts +41 -21
  194. package/src/router/router-interfaces.ts +484 -0
  195. package/src/router/router-options.ts +618 -0
  196. package/src/router/router-registry.ts +24 -0
  197. package/src/router/segment-resolution/fresh.ts +748 -0
  198. package/src/router/segment-resolution/helpers.ts +268 -0
  199. package/src/router/segment-resolution/loader-cache.ts +199 -0
  200. package/src/router/segment-resolution/revalidation.ts +1379 -0
  201. package/src/router/segment-resolution/static-store.ts +67 -0
  202. package/src/router/segment-resolution.ts +21 -0
  203. package/src/router/segment-wrappers.ts +291 -0
  204. package/src/router/telemetry-otel.ts +299 -0
  205. package/src/router/telemetry.ts +300 -0
  206. package/src/router/timeout.ts +148 -0
  207. package/src/router/trie-matching.ts +239 -0
  208. package/src/router/types.ts +78 -3
  209. package/src/router.ts +740 -4252
  210. package/src/rsc/handler-context.ts +45 -0
  211. package/src/rsc/handler.ts +907 -797
  212. package/src/rsc/helpers.ts +140 -6
  213. package/src/rsc/index.ts +0 -20
  214. package/src/rsc/loader-fetch.ts +229 -0
  215. package/src/rsc/manifest-init.ts +90 -0
  216. package/src/rsc/nonce.ts +14 -0
  217. package/src/rsc/origin-guard.ts +141 -0
  218. package/src/rsc/progressive-enhancement.ts +391 -0
  219. package/src/rsc/response-error.ts +37 -0
  220. package/src/rsc/response-route-handler.ts +347 -0
  221. package/src/rsc/rsc-rendering.ts +246 -0
  222. package/src/rsc/runtime-warnings.ts +42 -0
  223. package/src/rsc/server-action.ts +356 -0
  224. package/src/rsc/ssr-setup.ts +128 -0
  225. package/src/rsc/types.ts +46 -11
  226. package/src/search-params.ts +230 -0
  227. package/src/segment-content-promise.ts +67 -0
  228. package/src/segment-loader-promise.ts +122 -0
  229. package/src/segment-system.tsx +134 -36
  230. package/src/server/context.ts +341 -61
  231. package/src/server/cookie-store.ts +190 -0
  232. package/src/server/fetchable-loader-store.ts +37 -0
  233. package/src/server/handle-store.ts +113 -15
  234. package/src/server/loader-registry.ts +24 -64
  235. package/src/server/request-context.ts +607 -81
  236. package/src/server.ts +35 -130
  237. package/src/ssr/index.tsx +103 -30
  238. package/src/static-handler.ts +126 -0
  239. package/src/theme/ThemeProvider.tsx +21 -15
  240. package/src/theme/ThemeScript.tsx +5 -5
  241. package/src/theme/constants.ts +5 -2
  242. package/src/theme/index.ts +4 -14
  243. package/src/theme/theme-context.ts +4 -30
  244. package/src/theme/theme-script.ts +21 -18
  245. package/src/types/boundaries.ts +158 -0
  246. package/src/types/cache-types.ts +198 -0
  247. package/src/types/error-types.ts +192 -0
  248. package/src/types/global-namespace.ts +100 -0
  249. package/src/types/handler-context.ts +791 -0
  250. package/src/types/index.ts +88 -0
  251. package/src/types/loader-types.ts +210 -0
  252. package/src/types/route-config.ts +170 -0
  253. package/src/types/route-entry.ts +120 -0
  254. package/src/types/segments.ts +150 -0
  255. package/src/types.ts +1 -1623
  256. package/src/urls/include-helper.ts +207 -0
  257. package/src/urls/index.ts +53 -0
  258. package/src/urls/path-helper-types.ts +372 -0
  259. package/src/urls/path-helper.ts +364 -0
  260. package/src/urls/pattern-types.ts +107 -0
  261. package/src/urls/response-types.ts +116 -0
  262. package/src/urls/type-extraction.ts +372 -0
  263. package/src/urls/urls-function.ts +98 -0
  264. package/src/urls.ts +1 -802
  265. package/src/use-loader.tsx +161 -81
  266. package/src/vite/discovery/bundle-postprocess.ts +181 -0
  267. package/src/vite/discovery/discover-routers.ts +348 -0
  268. package/src/vite/discovery/prerender-collection.ts +439 -0
  269. package/src/vite/discovery/route-types-writer.ts +258 -0
  270. package/src/vite/discovery/self-gen-tracking.ts +47 -0
  271. package/src/vite/discovery/state.ts +117 -0
  272. package/src/vite/discovery/virtual-module-codegen.ts +203 -0
  273. package/src/vite/index.ts +15 -1133
  274. package/src/vite/plugin-types.ts +103 -0
  275. package/src/vite/plugins/cjs-to-esm.ts +93 -0
  276. package/src/vite/plugins/client-ref-dedup.ts +115 -0
  277. package/src/vite/plugins/client-ref-hashing.ts +105 -0
  278. package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +72 -53
  279. package/src/vite/plugins/expose-id-utils.ts +299 -0
  280. package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
  281. package/src/vite/plugins/expose-ids/handler-transform.ts +209 -0
  282. package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
  283. package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
  284. package/src/vite/plugins/expose-ids/types.ts +45 -0
  285. package/src/vite/plugins/expose-internal-ids.ts +786 -0
  286. package/src/vite/plugins/performance-tracks.ts +88 -0
  287. package/src/vite/plugins/refresh-cmd.ts +127 -0
  288. package/src/vite/plugins/use-cache-transform.ts +323 -0
  289. package/src/vite/plugins/version-injector.ts +83 -0
  290. package/src/vite/plugins/version-plugin.ts +266 -0
  291. package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +23 -14
  292. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  293. package/src/vite/rango.ts +462 -0
  294. package/src/vite/router-discovery.ts +918 -0
  295. package/src/vite/utils/ast-handler-extract.ts +517 -0
  296. package/src/vite/utils/banner.ts +36 -0
  297. package/src/vite/utils/bundle-analysis.ts +137 -0
  298. package/src/vite/utils/manifest-utils.ts +70 -0
  299. package/src/vite/{package-resolution.ts → utils/package-resolution.ts} +25 -29
  300. package/src/vite/utils/prerender-utils.ts +221 -0
  301. package/src/vite/utils/shared-utils.ts +170 -0
  302. package/CLAUDE.md +0 -43
  303. package/src/browser/lru-cache.ts +0 -69
  304. package/src/browser/request-controller.ts +0 -164
  305. package/src/cache/memory-store.ts +0 -253
  306. package/src/href-context.ts +0 -33
  307. package/src/href.ts +0 -255
  308. package/src/server/route-manifest-cache.ts +0 -173
  309. package/src/vite/expose-handle-id.ts +0 -209
  310. package/src/vite/expose-loader-id.ts +0 -426
  311. package/src/vite/expose-location-state-id.ts +0 -177
  312. /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
@@ -28,8 +28,9 @@ import { use } from "react";
28
28
  import { useHandle } from "../browser/react/use-handle.js";
29
29
  import { Meta } from "./meta.js";
30
30
  import type { MetaDescriptor, MetaDescriptorBase } from "../router/types.js";
31
- import { getSSRThemeConfig } from "../theme/theme-context.js";
31
+ import { useThemeContext } from "../theme/theme-context.js";
32
32
  import { generateThemeScript } from "../theme/theme-script.js";
33
+ import { useNonce } from "../browser/react/nonce-context.js";
33
34
 
34
35
  // Type guards for MetaDescriptorBase variants
35
36
  function hasCharSet(d: MetaDescriptorBase): d is { charSet: "utf-8" } {
@@ -40,30 +41,53 @@ function hasTitle(d: MetaDescriptorBase): d is { title: string } {
40
41
  return "title" in d && typeof (d as { title?: unknown }).title === "string";
41
42
  }
42
43
 
43
- function hasNameContent(d: MetaDescriptorBase): d is { name: string; content: string } {
44
- return "name" in d && "content" in d &&
44
+ function hasNameContent(
45
+ d: MetaDescriptorBase,
46
+ ): d is { name: string; content: string } {
47
+ return (
48
+ "name" in d &&
49
+ "content" in d &&
45
50
  typeof (d as { name?: unknown }).name === "string" &&
46
- typeof (d as { content?: unknown }).content === "string";
51
+ typeof (d as { content?: unknown }).content === "string"
52
+ );
47
53
  }
48
54
 
49
- function hasPropertyContent(d: MetaDescriptorBase): d is { property: string; content: string } {
50
- return "property" in d && "content" in d &&
55
+ function hasPropertyContent(
56
+ d: MetaDescriptorBase,
57
+ ): d is { property: string; content: string } {
58
+ return (
59
+ "property" in d &&
60
+ "content" in d &&
51
61
  typeof (d as { property?: unknown }).property === "string" &&
52
- typeof (d as { content?: unknown }).content === "string";
62
+ typeof (d as { content?: unknown }).content === "string"
63
+ );
53
64
  }
54
65
 
55
- function hasHttpEquivContent(d: MetaDescriptorBase): d is { httpEquiv: string; content: string } {
56
- return "httpEquiv" in d && "content" in d &&
66
+ function hasHttpEquivContent(
67
+ d: MetaDescriptorBase,
68
+ ): d is { httpEquiv: string; content: string } {
69
+ return (
70
+ "httpEquiv" in d &&
71
+ "content" in d &&
57
72
  typeof (d as { httpEquiv?: unknown }).httpEquiv === "string" &&
58
- typeof (d as { content?: unknown }).content === "string";
73
+ typeof (d as { content?: unknown }).content === "string"
74
+ );
59
75
  }
60
76
 
61
- function hasScriptLdJson(d: MetaDescriptorBase): d is { "script:ld+json": object } {
77
+ function hasScriptLdJson(
78
+ d: MetaDescriptorBase,
79
+ ): d is { "script:ld+json": object } {
62
80
  return "script:ld+json" in d;
63
81
  }
64
82
 
65
- function hasTagName(d: MetaDescriptorBase): d is { tagName: "meta" | "link"; [name: string]: string } {
66
- return "tagName" in d && ((d as { tagName?: unknown }).tagName === "meta" || (d as { tagName?: unknown }).tagName === "link");
83
+ function hasTagName(
84
+ d: MetaDescriptorBase,
85
+ ): d is { tagName: "meta" | "link"; [name: string]: string } {
86
+ return (
87
+ "tagName" in d &&
88
+ ((d as { tagName?: unknown }).tagName === "meta" ||
89
+ (d as { tagName?: unknown }).tagName === "link")
90
+ );
67
91
  }
68
92
 
69
93
  /**
@@ -78,7 +102,7 @@ function isPromise(value: unknown): value is Promise<unknown> {
78
102
  */
79
103
  function renderMetaDescriptor(
80
104
  descriptor: MetaDescriptorBase,
81
- index: number
105
+ index: number,
82
106
  ): React.ReactNode {
83
107
  // charset
84
108
  if (hasCharSet(descriptor)) {
@@ -139,21 +163,42 @@ function renderMetaDescriptor(
139
163
  if (hasTagName(descriptor)) {
140
164
  const { tagName, ...rest } = descriptor;
141
165
  if (tagName === "link") {
142
- return <link key={`link-${index}`} {...(rest as React.LinkHTMLAttributes<HTMLLinkElement>)} />;
166
+ return (
167
+ <link
168
+ key={`link-${index}`}
169
+ {...(rest as React.LinkHTMLAttributes<HTMLLinkElement>)}
170
+ />
171
+ );
143
172
  }
144
173
  if (tagName === "meta") {
145
- return <meta key={`meta-${index}`} {...(rest as React.MetaHTMLAttributes<HTMLMetaElement>)} />;
174
+ return (
175
+ <meta
176
+ key={`meta-${index}`}
177
+ {...(rest as React.MetaHTMLAttributes<HTMLMetaElement>)}
178
+ />
179
+ );
146
180
  }
147
181
  }
148
182
 
149
183
  // Fallback: treat as meta attributes
150
- return <meta key={`meta-fallback-${index}`} {...(descriptor as React.MetaHTMLAttributes<HTMLMetaElement>)} />;
184
+ return (
185
+ <meta
186
+ key={`meta-fallback-${index}`}
187
+ {...(descriptor as React.MetaHTMLAttributes<HTMLMetaElement>)}
188
+ />
189
+ );
151
190
  }
152
191
 
153
192
  /**
154
193
  * Wrapper component to resolve a Promise<MetaDescriptorBase> using use().
155
194
  */
156
- function AsyncMetaTag({ promise, index }: { promise: Promise<MetaDescriptorBase>; index: number }): React.ReactNode {
195
+ function AsyncMetaTag({
196
+ promise,
197
+ index,
198
+ }: {
199
+ promise: Promise<MetaDescriptorBase>;
200
+ index: number;
201
+ }): React.ReactNode {
157
202
  const resolved = use(promise);
158
203
  return renderMetaDescriptor(resolved, index);
159
204
  }
@@ -172,19 +217,27 @@ function AsyncMetaTag({ promise, index }: { promise: Promise<MetaDescriptorBase>
172
217
  */
173
218
  export function MetaTags(): React.ReactNode {
174
219
  const descriptors = useHandle(Meta) as MetaDescriptor[];
175
- const themeConfig = getSSRThemeConfig();
220
+ const themeConfig = useThemeContext()?.config ?? null;
221
+ const nonce = useNonce();
176
222
 
177
223
  return (
178
224
  <>
179
225
  {/* Theme script must be first to prevent FOUC */}
180
226
  {themeConfig && (
181
227
  <script
228
+ nonce={nonce}
182
229
  dangerouslySetInnerHTML={{ __html: generateThemeScript(themeConfig) }}
183
230
  />
184
231
  )}
185
232
  {descriptors.map((descriptor, index) => {
186
233
  if (isPromise(descriptor)) {
187
- return <AsyncMetaTag key={`async-${index}`} promise={descriptor} index={index} />;
234
+ return (
235
+ <AsyncMetaTag
236
+ key={`async-${index}`}
237
+ promise={descriptor}
238
+ index={index}
239
+ />
240
+ );
188
241
  }
189
242
  return renderMetaDescriptor(descriptor, index);
190
243
  })}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Built-in Breadcrumbs handle for accumulating breadcrumb items across route segments.
3
+ *
4
+ * Each layout/route pushes breadcrumb items via `ctx.use(Breadcrumbs)`.
5
+ * Items are collected in parent-to-child order with automatic deduplication
6
+ * by `href` (last item for each href wins).
7
+ *
8
+ * @example
9
+ * ```tsx
10
+ * // In route handler
11
+ * route("/blog/:slug", (ctx) => {
12
+ * const breadcrumb = ctx.use(Breadcrumbs);
13
+ * breadcrumb({ label: "Blog", href: "/blog" });
14
+ * breadcrumb({ label: post.title, href: `/blog/${ctx.params.slug}` });
15
+ * });
16
+ *
17
+ * // In client component (consume with useHandle)
18
+ * const crumbs = useHandle(Breadcrumbs);
19
+ * crumbs.map((c) => <a href={c.href}>{c.label}</a>);
20
+ * ```
21
+ */
22
+
23
+ import type { ReactNode } from "react";
24
+ import { createHandle, type Handle } from "../handle.js";
25
+
26
+ /**
27
+ * A single breadcrumb item.
28
+ *
29
+ * @property label - Display text for the breadcrumb
30
+ * @property href - URL the breadcrumb links to
31
+ * @property content - Optional extra content (sync or async) rendered alongside the label
32
+ */
33
+ export interface BreadcrumbItem {
34
+ label: string;
35
+ href: string;
36
+ content?: ReactNode | Promise<ReactNode>;
37
+ }
38
+
39
+ /**
40
+ * Collect function for Breadcrumbs handle.
41
+ * Flattens segments in parent-to-child order with deduplication by href
42
+ * (last item for each href wins).
43
+ */
44
+ function collectBreadcrumbs(segments: BreadcrumbItem[][]): BreadcrumbItem[] {
45
+ const all = segments.flat();
46
+ const seen = new Map<string, number>();
47
+
48
+ for (let i = 0; i < all.length; i++) {
49
+ seen.set(all[i].href, i);
50
+ }
51
+
52
+ // Return items in order, keeping only the last occurrence per href
53
+ return all.filter((item, index) => seen.get(item.href) === index);
54
+ }
55
+
56
+ /**
57
+ * Built-in handle for accumulating breadcrumb navigation items.
58
+ *
59
+ * Use `ctx.use(Breadcrumbs)` in route handlers to push breadcrumb items.
60
+ * Use `useHandle(Breadcrumbs)` in client components to consume them.
61
+ */
62
+ export const Breadcrumbs: Handle<BreadcrumbItem, BreadcrumbItem[]> =
63
+ createHandle<BreadcrumbItem, BreadcrumbItem[]>(
64
+ collectBreadcrumbs,
65
+ "__rsc_router_breadcrumbs__",
66
+ );
@@ -4,3 +4,4 @@
4
4
 
5
5
  export { Meta } from "./meta.ts";
6
6
  export { MetaTags } from "./MetaTags.tsx";
7
+ export { Breadcrumbs, type BreadcrumbItem } from "./breadcrumbs.ts";
@@ -39,7 +39,7 @@ import type {
39
39
  * Type guard for unset descriptor
40
40
  */
41
41
  function isUnsetDescriptor(
42
- descriptor: MetaDescriptor
42
+ descriptor: MetaDescriptor,
43
43
  ): descriptor is UnsetDescriptor {
44
44
  return (
45
45
  typeof descriptor === "object" &&
@@ -53,7 +53,7 @@ function isUnsetDescriptor(
53
53
  * Type guard for title descriptor (any form)
54
54
  */
55
55
  function isTitleDescriptor(
56
- descriptor: MetaDescriptor
56
+ descriptor: MetaDescriptor,
57
57
  ): descriptor is { title: TitleDescriptor } {
58
58
  return (
59
59
  typeof descriptor === "object" &&
@@ -66,7 +66,7 @@ function isTitleDescriptor(
66
66
  * Type guard for title template descriptor
67
67
  */
68
68
  function isTitleTemplate(
69
- title: TitleDescriptor
69
+ title: TitleDescriptor,
70
70
  ): title is { template: string; default: string } {
71
71
  return (
72
72
  typeof title === "object" &&
@@ -79,7 +79,9 @@ function isTitleTemplate(
79
79
  /**
80
80
  * Type guard for absolute title descriptor
81
81
  */
82
- function isAbsoluteTitle(title: TitleDescriptor): title is { absolute: string } {
82
+ function isAbsoluteTitle(
83
+ title: TitleDescriptor,
84
+ ): title is { absolute: string } {
83
85
  return typeof title === "object" && title !== null && "absolute" in title;
84
86
  }
85
87
 
@@ -141,7 +143,7 @@ function addOrReplace(
141
143
  result: MetaDescriptor[],
142
144
  keyToIndex: Map<string, number>,
143
145
  descriptor: MetaDescriptor,
144
- key: string | undefined
146
+ key: string | undefined,
145
147
  ): void {
146
148
  if (key !== undefined && keyToIndex.has(key)) {
147
149
  result[keyToIndex.get(key)!] = descriptor;
@@ -158,7 +160,7 @@ function addOrReplace(
158
160
  */
159
161
  function updateIndicesAfterRemoval(
160
162
  keyToIndex: Map<string, number>,
161
- removedIndex: number
163
+ removedIndex: number,
162
164
  ): void {
163
165
  for (const [key, index] of keyToIndex) {
164
166
  if (index > removedIndex) {
@@ -208,13 +210,23 @@ function collectMeta(segments: MetaDescriptor[][]): MetaDescriptor[] {
208
210
  // Store template for subsequent title descriptors in child segments
209
211
  titleTemplate = titleValue.template;
210
212
  // Set the default title
211
- addOrReplace(result, keyToIndex, { title: titleValue.default }, "title");
213
+ addOrReplace(
214
+ result,
215
+ keyToIndex,
216
+ { title: titleValue.default },
217
+ "title",
218
+ );
212
219
  continue;
213
220
  }
214
221
 
215
222
  if (isAbsoluteTitle(titleValue)) {
216
223
  // Absolute title bypasses any template
217
- addOrReplace(result, keyToIndex, { title: titleValue.absolute }, "title");
224
+ addOrReplace(
225
+ result,
226
+ keyToIndex,
227
+ { title: titleValue.absolute },
228
+ "title",
229
+ );
218
230
  continue;
219
231
  }
220
232
 
@@ -222,7 +234,12 @@ function collectMeta(segments: MetaDescriptor[][]): MetaDescriptor[] {
222
234
  const finalTitle = titleTemplate
223
235
  ? titleTemplate.replace("%s", titleValue as string)
224
236
  : titleValue;
225
- addOrReplace(result, keyToIndex, { title: finalTitle as string }, "title");
237
+ addOrReplace(
238
+ result,
239
+ keyToIndex,
240
+ { title: finalTitle as string },
241
+ "title",
242
+ );
226
243
  continue;
227
244
  }
228
245
 
@@ -241,7 +258,7 @@ function collectMeta(segments: MetaDescriptor[][]): MetaDescriptor[] {
241
258
  * Use `ctx.use(Meta)` in route handlers to push meta descriptors.
242
259
  * Use `<MetaTags />` component to render them in the document head.
243
260
  */
244
- export const Meta: Handle<MetaDescriptor, MetaDescriptor[]> = createHandle<MetaDescriptor, MetaDescriptor[]>(
245
- collectMeta,
246
- "__rsc_router_meta__"
247
- );
261
+ export const Meta: Handle<MetaDescriptor, MetaDescriptor[]> = createHandle<
262
+ MetaDescriptor,
263
+ MetaDescriptor[]
264
+ >(collectMeta, "__rsc_router_meta__");
@@ -4,30 +4,36 @@
4
4
  * Manages cookie-based host override for development environments.
5
5
  */
6
6
 
7
- import type { HostOverrideConfig } from './types.js';
8
- import { matchPattern, parseRequest } from './pattern-matcher.js';
7
+ import type { HostOverrideConfig } from "./types.js";
8
+ import type { RouterRequestInput } from "../router/router-interfaces.js";
9
+ import { matchPattern, parseRequest } from "./pattern-matcher.js";
9
10
  import {
10
11
  HostOverrideNotAllowedError,
11
12
  InvalidHostnameError,
12
13
  HostValidationError,
13
- } from './errors.js';
14
+ } from "./errors.js";
14
15
 
15
16
  /**
16
17
  * Parse cookies from request
17
18
  */
18
19
  export function parseCookies(request: Request): Record<string, string> {
19
- const cookieHeader = request.headers.get('cookie');
20
+ const cookieHeader = request.headers.get("cookie");
20
21
  if (!cookieHeader) {
21
22
  return {};
22
23
  }
23
24
 
24
25
  const cookies: Record<string, string> = {};
25
- const pairs = cookieHeader.split(';');
26
+ const pairs = cookieHeader.split(";");
26
27
 
27
28
  for (const pair of pairs) {
28
- const [key, value] = pair.trim().split('=');
29
- if (key && value) {
30
- cookies[key] = decodeURIComponent(value);
29
+ const [name, ...rest] = pair.trim().split("=");
30
+ if (name && rest.length > 0) {
31
+ const value = rest.join("=");
32
+ try {
33
+ cookies[name] = decodeURIComponent(value);
34
+ } catch {
35
+ cookies[name] = value;
36
+ }
31
37
  }
32
38
  }
33
39
 
@@ -54,7 +60,7 @@ export function createDeleteCookieHeader(name: string): string {
54
60
  */
55
61
  export function createCookieErrorResponse(
56
62
  cookieName: string,
57
- message: string
63
+ message: string,
58
64
  ): Response {
59
65
  return new Response(
60
66
  JSON.stringify({
@@ -64,10 +70,10 @@ export function createCookieErrorResponse(
64
70
  {
65
71
  status: 400,
66
72
  headers: {
67
- 'Content-Type': 'application/json',
68
- 'Set-Cookie': createDeleteCookieHeader(cookieName),
73
+ "Content-Type": "application/json",
74
+ "Set-Cookie": createDeleteCookieHeader(cookieName),
69
75
  },
70
- }
76
+ },
71
77
  );
72
78
  }
73
79
 
@@ -76,7 +82,7 @@ export function createCookieErrorResponse(
76
82
  */
77
83
  export function isHostAllowed(
78
84
  request: Request,
79
- allowedHosts: string[]
85
+ allowedHosts: string[],
80
86
  ): boolean {
81
87
  const { hostname, pathname, parts } = parseRequest(request);
82
88
 
@@ -98,7 +104,7 @@ export function isHostAllowed(
98
104
  export function handleCookieOverride(
99
105
  request: Request,
100
106
  config: HostOverrideConfig | undefined,
101
- context: any
107
+ input: RouterRequestInput<any>,
102
108
  ): string {
103
109
  if (!config) {
104
110
  const { hostname } = parseRequest(request);
@@ -127,7 +133,7 @@ export function handleCookieOverride(
127
133
  // If allowed and has custom validation, run it
128
134
  if (validate) {
129
135
  try {
130
- const validatedHostname = validate(request, cookieValue, context);
136
+ const validatedHostname = validate(request, cookieValue, input);
131
137
  return validatedHostname;
132
138
  } catch (error) {
133
139
  // Wrap in HostValidationError
@@ -22,7 +22,7 @@ export class HostRouterError extends Error {
22
22
  if (options?.cause) {
23
23
  this.cause = options.cause;
24
24
  }
25
- this.name = 'HostRouterError';
25
+ this.name = "HostRouterError";
26
26
  Object.setPrototypeOf(this, HostRouterError.prototype);
27
27
  }
28
28
  }
@@ -33,7 +33,7 @@ export class HostRouterError extends Error {
33
33
  export class InvalidPatternError extends HostRouterError {
34
34
  constructor(pattern: string, reason: string, options?: ErrorOptions) {
35
35
  super(`Invalid pattern "${pattern}": ${reason}`, options);
36
- this.name = 'InvalidPatternError';
36
+ this.name = "InvalidPatternError";
37
37
  Object.setPrototypeOf(this, InvalidPatternError.prototype);
38
38
  }
39
39
  }
@@ -45,9 +45,9 @@ export class HostOverrideNotAllowedError extends HostRouterError {
45
45
  constructor(currentHost: string, cookieName: string, options?: ErrorOptions) {
46
46
  super(
47
47
  `Host override not allowed on "${currentHost}" (cookie: ${cookieName})`,
48
- options
48
+ options,
49
49
  );
50
- this.name = 'HostOverrideNotAllowedError';
50
+ this.name = "HostOverrideNotAllowedError";
51
51
  Object.setPrototypeOf(this, HostOverrideNotAllowedError.prototype);
52
52
  }
53
53
  }
@@ -58,7 +58,7 @@ export class HostOverrideNotAllowedError extends HostRouterError {
58
58
  export class InvalidHostnameError extends HostRouterError {
59
59
  constructor(hostname: string, options?: ErrorOptions) {
60
60
  super(`Invalid hostname format: "${hostname}"`, options);
61
- this.name = 'InvalidHostnameError';
61
+ this.name = "InvalidHostnameError";
62
62
  Object.setPrototypeOf(this, InvalidHostnameError.prototype);
63
63
  }
64
64
  }
@@ -69,7 +69,7 @@ export class InvalidHostnameError extends HostRouterError {
69
69
  export class HostValidationError extends HostRouterError {
70
70
  constructor(message: string, cause?: unknown) {
71
71
  super(message, { cause });
72
- this.name = 'HostValidationError';
72
+ this.name = "HostValidationError";
73
73
  Object.setPrototypeOf(this, HostValidationError.prototype);
74
74
  }
75
75
  }
@@ -80,7 +80,7 @@ export class HostValidationError extends HostRouterError {
80
80
  export class NoRouteMatchError extends HostRouterError {
81
81
  constructor(hostname: string, pathname: string, options?: ErrorOptions) {
82
82
  super(`No route matched for ${hostname}${pathname}`, options);
83
- this.name = 'NoRouteMatchError';
83
+ this.name = "NoRouteMatchError";
84
84
  Object.setPrototypeOf(this, NoRouteMatchError.prototype);
85
85
  }
86
86
  }
@@ -91,7 +91,7 @@ export class NoRouteMatchError extends HostRouterError {
91
91
  export class InvalidHandlerError extends HostRouterError {
92
92
  constructor(handler: unknown, options?: ErrorOptions) {
93
93
  super(`Invalid handler type: ${typeof handler}`, options);
94
- this.name = 'InvalidHandlerError';
94
+ this.name = "InvalidHandlerError";
95
95
  Object.setPrototypeOf(this, InvalidHandlerError.prototype);
96
96
  }
97
97
  }
package/src/host/index.ts CHANGED
@@ -23,13 +23,10 @@
23
23
  */
24
24
 
25
25
  // Core router
26
- export { createHostRouter } from './router.js';
27
-
28
- // Host router registry for build-time discovery
29
- export { HostRouterRegistry, type HostRouterRegistryEntry } from './router.js';
26
+ export { createHostRouter } from "./router.js";
30
27
 
31
28
  // Utilities
32
- export { defineHosts } from './utils.js';
29
+ export { defineHosts } from "./utils.js";
33
30
 
34
31
  // Errors
35
32
  export {
@@ -40,7 +37,7 @@ export {
40
37
  HostValidationError,
41
38
  NoRouteMatchError,
42
39
  InvalidHandlerError,
43
- } from './errors.js';
40
+ } from "./errors.js";
44
41
 
45
42
  // Types
46
43
  export type {
@@ -53,4 +50,4 @@ export type {
53
50
  HostPattern,
54
51
  HostMatchResult,
55
52
  HostOverrideConfig,
56
- } from './types.js';
53
+ } from "./types.js";