@rangojs/router 0.0.0-experimental.10 → 0.0.0-experimental.100

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 (329) hide show
  1. package/AGENTS.md +9 -0
  2. package/README.md +1037 -4
  3. package/dist/bin/rango.js +1619 -157
  4. package/dist/vite/index.js +5762 -2301
  5. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  6. package/package.json +71 -63
  7. package/skills/breadcrumbs/SKILL.md +252 -0
  8. package/skills/cache-guide/SKILL.md +294 -0
  9. package/skills/caching/SKILL.md +93 -23
  10. package/skills/composability/SKILL.md +172 -0
  11. package/skills/debug-manifest/SKILL.md +12 -8
  12. package/skills/document-cache/SKILL.md +18 -16
  13. package/skills/fonts/SKILL.md +6 -4
  14. package/skills/handler-use/SKILL.md +364 -0
  15. package/skills/hooks/SKILL.md +367 -71
  16. package/skills/host-router/SKILL.md +218 -0
  17. package/skills/i18n/SKILL.md +276 -0
  18. package/skills/intercept/SKILL.md +176 -8
  19. package/skills/layout/SKILL.md +124 -3
  20. package/skills/links/SKILL.md +304 -25
  21. package/skills/loader/SKILL.md +474 -47
  22. package/skills/middleware/SKILL.md +207 -37
  23. package/skills/migrate-nextjs/SKILL.md +562 -0
  24. package/skills/migrate-react-router/SKILL.md +769 -0
  25. package/skills/mime-routes/SKILL.md +15 -11
  26. package/skills/parallel/SKILL.md +272 -1
  27. package/skills/prerender/SKILL.md +467 -65
  28. package/skills/rango/SKILL.md +89 -21
  29. package/skills/response-routes/SKILL.md +152 -91
  30. package/skills/route/SKILL.md +305 -14
  31. package/skills/router-setup/SKILL.md +210 -32
  32. package/skills/server-actions/SKILL.md +739 -0
  33. package/skills/streams-and-websockets/SKILL.md +283 -0
  34. package/skills/theme/SKILL.md +9 -8
  35. package/skills/typesafety/SKILL.md +333 -86
  36. package/skills/use-cache/SKILL.md +324 -0
  37. package/skills/view-transitions/SKILL.md +212 -0
  38. package/src/__internal.ts +102 -4
  39. package/src/bin/rango.ts +312 -15
  40. package/src/browser/action-coordinator.ts +97 -0
  41. package/src/browser/action-response-classifier.ts +99 -0
  42. package/src/browser/app-shell.ts +52 -0
  43. package/src/browser/app-version.ts +14 -0
  44. package/src/browser/event-controller.ts +136 -68
  45. package/src/browser/history-state.ts +80 -0
  46. package/src/browser/intercept-utils.ts +52 -0
  47. package/src/browser/link-interceptor.ts +24 -4
  48. package/src/browser/logging.ts +55 -0
  49. package/src/browser/merge-segment-loaders.ts +20 -12
  50. package/src/browser/navigation-bridge.ts +374 -561
  51. package/src/browser/navigation-client.ts +228 -70
  52. package/src/browser/navigation-store.ts +97 -55
  53. package/src/browser/navigation-transaction.ts +297 -0
  54. package/src/browser/network-error-handler.ts +61 -0
  55. package/src/browser/partial-update.ts +376 -315
  56. package/src/browser/prefetch/cache.ts +314 -0
  57. package/src/browser/prefetch/fetch.ts +282 -0
  58. package/src/browser/prefetch/observer.ts +65 -0
  59. package/src/browser/prefetch/policy.ts +48 -0
  60. package/src/browser/prefetch/queue.ts +191 -0
  61. package/src/browser/prefetch/resource-ready.ts +77 -0
  62. package/src/browser/rango-state.ts +152 -0
  63. package/src/browser/react/Link.tsx +255 -71
  64. package/src/browser/react/NavigationProvider.tsx +152 -24
  65. package/src/browser/react/context.ts +11 -0
  66. package/src/browser/react/filter-segment-order.ts +55 -0
  67. package/src/browser/react/index.ts +15 -12
  68. package/src/browser/react/location-state-shared.ts +95 -53
  69. package/src/browser/react/location-state.ts +60 -15
  70. package/src/browser/react/mount-context.ts +6 -1
  71. package/src/browser/react/nonce-context.ts +23 -0
  72. package/src/browser/react/shallow-equal.ts +27 -0
  73. package/src/browser/react/use-action.ts +29 -51
  74. package/src/browser/react/use-client-cache.ts +5 -3
  75. package/src/browser/react/use-handle.ts +30 -120
  76. package/src/browser/react/use-link-status.ts +6 -5
  77. package/src/browser/react/use-navigation.ts +44 -65
  78. package/src/browser/react/use-params.ts +78 -0
  79. package/src/browser/react/use-pathname.ts +47 -0
  80. package/src/browser/react/use-reverse.ts +99 -0
  81. package/src/browser/react/use-router.ts +83 -0
  82. package/src/browser/react/use-search-params.ts +56 -0
  83. package/src/browser/react/use-segments.ts +85 -99
  84. package/src/browser/response-adapter.ts +73 -0
  85. package/src/browser/rsc-router.tsx +246 -64
  86. package/src/browser/scroll-restoration.ts +127 -52
  87. package/src/browser/segment-reconciler.ts +243 -0
  88. package/src/browser/segment-structure-assert.ts +16 -0
  89. package/src/browser/server-action-bridge.ts +510 -603
  90. package/src/browser/shallow.ts +6 -1
  91. package/src/browser/types.ts +158 -48
  92. package/src/browser/validate-redirect-origin.ts +29 -0
  93. package/src/build/generate-manifest.ts +84 -23
  94. package/src/build/generate-route-types.ts +39 -828
  95. package/src/build/index.ts +4 -5
  96. package/src/build/route-trie.ts +85 -32
  97. package/src/build/route-types/ast-helpers.ts +25 -0
  98. package/src/build/route-types/ast-route-extraction.ts +98 -0
  99. package/src/build/route-types/codegen.ts +102 -0
  100. package/src/build/route-types/include-resolution.ts +418 -0
  101. package/src/build/route-types/param-extraction.ts +48 -0
  102. package/src/build/route-types/per-module-writer.ts +128 -0
  103. package/src/build/route-types/router-processing.ts +618 -0
  104. package/src/build/route-types/scan-filter.ts +85 -0
  105. package/src/build/runtime-discovery.ts +231 -0
  106. package/src/cache/background-task.ts +34 -0
  107. package/src/cache/cache-key-utils.ts +44 -0
  108. package/src/cache/cache-policy.ts +125 -0
  109. package/src/cache/cache-runtime.ts +342 -0
  110. package/src/cache/cache-scope.ts +167 -307
  111. package/src/cache/cf/cf-cache-store.ts +573 -21
  112. package/src/cache/cf/index.ts +13 -3
  113. package/src/cache/document-cache.ts +116 -77
  114. package/src/cache/handle-capture.ts +81 -0
  115. package/src/cache/handle-snapshot.ts +41 -0
  116. package/src/cache/index.ts +1 -15
  117. package/src/cache/memory-segment-store.ts +191 -13
  118. package/src/cache/profile-registry.ts +73 -0
  119. package/src/cache/read-through-swr.ts +134 -0
  120. package/src/cache/segment-codec.ts +256 -0
  121. package/src/cache/taint.ts +153 -0
  122. package/src/cache/types.ts +72 -122
  123. package/src/client.rsc.tsx +6 -1
  124. package/src/client.tsx +118 -302
  125. package/src/component-utils.ts +4 -4
  126. package/src/components/DefaultDocument.tsx +5 -1
  127. package/src/context-var.ts +156 -0
  128. package/src/debug.ts +19 -9
  129. package/src/errors.ts +77 -7
  130. package/src/handle.ts +55 -10
  131. package/src/handles/MetaTags.tsx +73 -20
  132. package/src/handles/breadcrumbs.ts +66 -0
  133. package/src/handles/index.ts +1 -0
  134. package/src/handles/meta.ts +30 -13
  135. package/src/host/cookie-handler.ts +21 -15
  136. package/src/host/errors.ts +8 -8
  137. package/src/host/index.ts +4 -7
  138. package/src/host/pattern-matcher.ts +27 -27
  139. package/src/host/router.ts +61 -39
  140. package/src/host/testing.ts +8 -8
  141. package/src/host/types.ts +15 -7
  142. package/src/host/utils.ts +1 -1
  143. package/src/href-client.ts +65 -45
  144. package/src/index.rsc.ts +138 -21
  145. package/src/index.ts +206 -51
  146. package/src/internal-debug.ts +11 -0
  147. package/src/loader.rsc.ts +25 -143
  148. package/src/loader.ts +27 -10
  149. package/src/network-error-thrower.tsx +3 -1
  150. package/src/outlet-context.ts +1 -1
  151. package/src/outlet-provider.tsx +45 -0
  152. package/src/prerender/param-hash.ts +4 -2
  153. package/src/prerender/store.ts +159 -13
  154. package/src/prerender.ts +397 -29
  155. package/src/response-utils.ts +28 -0
  156. package/src/reverse.ts +231 -121
  157. package/src/root-error-boundary.tsx +41 -29
  158. package/src/route-content-wrapper.tsx +7 -4
  159. package/src/route-definition/dsl-helpers.ts +1134 -0
  160. package/src/route-definition/helper-factories.ts +200 -0
  161. package/src/route-definition/helpers-types.ts +483 -0
  162. package/src/route-definition/index.ts +55 -0
  163. package/src/route-definition/redirect.ts +101 -0
  164. package/src/route-definition/resolve-handler-use.ts +155 -0
  165. package/src/route-definition.ts +1 -1431
  166. package/src/route-map-builder.ts +162 -123
  167. package/src/route-name.ts +53 -0
  168. package/src/route-types.ts +66 -9
  169. package/src/router/content-negotiation.ts +215 -0
  170. package/src/router/debug-manifest.ts +72 -0
  171. package/src/router/error-handling.ts +9 -9
  172. package/src/router/find-match.ts +160 -0
  173. package/src/router/handler-context.ts +418 -86
  174. package/src/router/intercept-resolution.ts +35 -20
  175. package/src/router/lazy-includes.ts +237 -0
  176. package/src/router/loader-resolution.ts +359 -128
  177. package/src/router/logging.ts +251 -0
  178. package/src/router/manifest.ts +98 -32
  179. package/src/router/match-api.ts +196 -261
  180. package/src/router/match-context.ts +4 -2
  181. package/src/router/match-handlers.ts +441 -0
  182. package/src/router/match-middleware/background-revalidation.ts +108 -93
  183. package/src/router/match-middleware/cache-lookup.ts +415 -86
  184. package/src/router/match-middleware/cache-store.ts +91 -29
  185. package/src/router/match-middleware/intercept-resolution.ts +48 -21
  186. package/src/router/match-middleware/segment-resolution.ts +73 -9
  187. package/src/router/match-pipelines.ts +10 -45
  188. package/src/router/match-result.ts +154 -35
  189. package/src/router/metrics.ts +240 -15
  190. package/src/router/middleware-cookies.ts +55 -0
  191. package/src/router/middleware-types.ts +209 -0
  192. package/src/router/middleware.ts +373 -371
  193. package/src/router/navigation-snapshot.ts +182 -0
  194. package/src/router/pattern-matching.ts +292 -52
  195. package/src/router/prerender-match.ts +502 -0
  196. package/src/router/preview-match.ts +98 -0
  197. package/src/router/request-classification.ts +310 -0
  198. package/src/router/revalidation.ts +152 -39
  199. package/src/router/route-snapshot.ts +245 -0
  200. package/src/router/router-context.ts +41 -21
  201. package/src/router/router-interfaces.ts +484 -0
  202. package/src/router/router-options.ts +618 -0
  203. package/src/router/router-registry.ts +24 -0
  204. package/src/router/segment-resolution/fresh.ts +756 -0
  205. package/src/router/segment-resolution/helpers.ts +268 -0
  206. package/src/router/segment-resolution/loader-cache.ts +199 -0
  207. package/src/router/segment-resolution/revalidation.ts +1407 -0
  208. package/src/router/segment-resolution/static-store.ts +67 -0
  209. package/src/router/segment-resolution.ts +21 -1315
  210. package/src/router/segment-wrappers.ts +291 -0
  211. package/src/router/substitute-pattern-params.ts +56 -0
  212. package/src/router/telemetry-otel.ts +299 -0
  213. package/src/router/telemetry.ts +300 -0
  214. package/src/router/timeout.ts +148 -0
  215. package/src/router/trie-matching.ts +111 -39
  216. package/src/router/types.ts +17 -9
  217. package/src/router/url-params.ts +49 -0
  218. package/src/router.ts +642 -2011
  219. package/src/rsc/handler-context.ts +45 -0
  220. package/src/rsc/handler.ts +864 -1114
  221. package/src/rsc/helpers.ts +181 -19
  222. package/src/rsc/index.ts +0 -20
  223. package/src/rsc/loader-fetch.ts +229 -0
  224. package/src/rsc/manifest-init.ts +90 -0
  225. package/src/rsc/nonce.ts +14 -0
  226. package/src/rsc/origin-guard.ts +141 -0
  227. package/src/rsc/progressive-enhancement.ts +395 -0
  228. package/src/rsc/response-error.ts +37 -0
  229. package/src/rsc/response-route-handler.ts +360 -0
  230. package/src/rsc/rsc-rendering.ts +256 -0
  231. package/src/rsc/runtime-warnings.ts +42 -0
  232. package/src/rsc/server-action.ts +360 -0
  233. package/src/rsc/ssr-setup.ts +128 -0
  234. package/src/rsc/types.ts +52 -11
  235. package/src/search-params.ts +230 -0
  236. package/src/segment-content-promise.ts +67 -0
  237. package/src/segment-loader-promise.ts +122 -0
  238. package/src/segment-system.tsx +187 -38
  239. package/src/server/context.ts +333 -59
  240. package/src/server/cookie-store.ts +190 -0
  241. package/src/server/fetchable-loader-store.ts +37 -0
  242. package/src/server/handle-store.ts +113 -15
  243. package/src/server/loader-registry.ts +24 -64
  244. package/src/server/request-context.ts +603 -109
  245. package/src/server.ts +35 -155
  246. package/src/ssr/index.tsx +107 -30
  247. package/src/static-handler.ts +126 -0
  248. package/src/theme/ThemeProvider.tsx +21 -15
  249. package/src/theme/ThemeScript.tsx +5 -5
  250. package/src/theme/constants.ts +5 -2
  251. package/src/theme/index.ts +4 -14
  252. package/src/theme/theme-context.ts +4 -30
  253. package/src/theme/theme-script.ts +21 -18
  254. package/src/types/boundaries.ts +158 -0
  255. package/src/types/cache-types.ts +198 -0
  256. package/src/types/error-types.ts +192 -0
  257. package/src/types/global-namespace.ts +100 -0
  258. package/src/types/handler-context.ts +764 -0
  259. package/src/types/index.ts +88 -0
  260. package/src/types/loader-types.ts +209 -0
  261. package/src/types/request-scope.ts +126 -0
  262. package/src/types/route-config.ts +170 -0
  263. package/src/types/route-entry.ts +120 -0
  264. package/src/types/segments.ts +167 -0
  265. package/src/types.ts +1 -1757
  266. package/src/urls/include-helper.ts +207 -0
  267. package/src/urls/index.ts +53 -0
  268. package/src/urls/path-helper-types.ts +372 -0
  269. package/src/urls/path-helper.ts +364 -0
  270. package/src/urls/pattern-types.ts +107 -0
  271. package/src/urls/response-types.ts +108 -0
  272. package/src/urls/type-extraction.ts +372 -0
  273. package/src/urls/urls-function.ts +98 -0
  274. package/src/urls.ts +1 -1282
  275. package/src/use-loader.tsx +161 -81
  276. package/src/vite/debug.ts +184 -0
  277. package/src/vite/discovery/bundle-postprocess.ts +181 -0
  278. package/src/vite/discovery/discover-routers.ts +376 -0
  279. package/src/vite/discovery/gate-state.ts +171 -0
  280. package/src/vite/discovery/prerender-collection.ts +486 -0
  281. package/src/vite/discovery/route-types-writer.ts +258 -0
  282. package/src/vite/discovery/self-gen-tracking.ts +73 -0
  283. package/src/vite/discovery/state.ts +117 -0
  284. package/src/vite/discovery/virtual-module-codegen.ts +203 -0
  285. package/src/vite/index.ts +15 -2063
  286. package/src/vite/plugin-types.ts +103 -0
  287. package/src/vite/plugins/cjs-to-esm.ts +98 -0
  288. package/src/vite/plugins/client-ref-dedup.ts +131 -0
  289. package/src/vite/plugins/client-ref-hashing.ts +117 -0
  290. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  291. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  292. package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
  293. package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +107 -64
  294. package/src/vite/plugins/expose-id-utils.ts +299 -0
  295. package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
  296. package/src/vite/plugins/expose-ids/handler-transform.ts +209 -0
  297. package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
  298. package/src/vite/plugins/expose-ids/router-transform.ts +127 -0
  299. package/src/vite/plugins/expose-ids/types.ts +45 -0
  300. package/src/vite/plugins/expose-internal-ids.ts +816 -0
  301. package/src/vite/plugins/performance-tracks.ts +96 -0
  302. package/src/vite/plugins/refresh-cmd.ts +127 -0
  303. package/src/vite/plugins/use-cache-transform.ts +336 -0
  304. package/src/vite/plugins/version-injector.ts +109 -0
  305. package/src/vite/plugins/version-plugin.ts +266 -0
  306. package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +23 -14
  307. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  308. package/src/vite/rango.ts +497 -0
  309. package/src/vite/router-discovery.ts +1423 -0
  310. package/src/vite/utils/ast-handler-extract.ts +517 -0
  311. package/src/vite/utils/banner.ts +36 -0
  312. package/src/vite/utils/bundle-analysis.ts +137 -0
  313. package/src/vite/utils/manifest-utils.ts +70 -0
  314. package/src/vite/utils/package-resolution.ts +161 -0
  315. package/src/vite/utils/prerender-utils.ts +222 -0
  316. package/src/vite/utils/shared-utils.ts +170 -0
  317. package/CLAUDE.md +0 -43
  318. package/src/browser/lru-cache.ts +0 -69
  319. package/src/browser/request-controller.ts +0 -164
  320. package/src/cache/memory-store.ts +0 -253
  321. package/src/href-context.ts +0 -33
  322. package/src/router.gen.ts +0 -6
  323. package/src/urls.gen.ts +0 -8
  324. package/src/vite/expose-handle-id.ts +0 -209
  325. package/src/vite/expose-loader-id.ts +0 -426
  326. package/src/vite/expose-location-state-id.ts +0 -177
  327. package/src/vite/expose-prerender-handler-id.ts +0 -429
  328. package/src/vite/package-resolution.ts +0 -125
  329. /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
@@ -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";
@@ -14,17 +14,17 @@
14
14
  * - `example.com/admin` - specific domain with path prefix
15
15
  */
16
16
 
17
- import { InvalidPatternError } from './errors.js';
17
+ import { InvalidPatternError } from "./errors.js";
18
18
 
19
19
  /**
20
20
  * Normalize a pattern by removing trailing slashes from paths
21
21
  */
22
22
  export function normalizePattern(pattern: string): string {
23
23
  // If pattern has a path component, remove trailing slash
24
- const slashIndex = pattern.indexOf('/');
24
+ const slashIndex = pattern.indexOf("/");
25
25
  if (slashIndex !== -1) {
26
26
  const domain = pattern.slice(0, slashIndex);
27
- const path = pattern.slice(slashIndex).replace(/\/$/, '');
27
+ const path = pattern.slice(slashIndex).replace(/\/$/, "");
28
28
  return domain + path;
29
29
  }
30
30
  return pattern;
@@ -41,7 +41,7 @@ export function parseRequest(request: Request): {
41
41
  const url = new URL(request.url);
42
42
  const hostname = url.hostname;
43
43
  const pathname = url.pathname;
44
- const parts = hostname.split('.');
44
+ const parts = hostname.split(".");
45
45
 
46
46
  return { hostname, pathname, parts };
47
47
  }
@@ -70,12 +70,12 @@ export function matchPattern(
70
70
  pattern: string,
71
71
  hostname: string,
72
72
  pathname: string,
73
- parts: string[]
73
+ parts: string[],
74
74
  ): boolean {
75
75
  const normalized = normalizePattern(pattern);
76
76
 
77
77
  // Check if pattern has path component
78
- const slashIndex = normalized.indexOf('/');
78
+ const slashIndex = normalized.indexOf("/");
79
79
  const hasPath = slashIndex !== -1;
80
80
  const domainPattern = hasPath ? normalized.slice(0, slashIndex) : normalized;
81
81
  const pathPattern = hasPath ? normalized.slice(slashIndex) : null;
@@ -88,7 +88,7 @@ export function matchPattern(
88
88
 
89
89
  // Then match path (prefix match)
90
90
  if (pathPattern) {
91
- return pathname === pathPattern || pathname.startsWith(pathPattern + '/');
91
+ return pathname === pathPattern || pathname.startsWith(pathPattern + "/");
92
92
  }
93
93
 
94
94
  return true;
@@ -100,7 +100,7 @@ export function matchPattern(
100
100
  function matchDomainPattern(
101
101
  pattern: string,
102
102
  hostname: string,
103
- parts: string[]
103
+ parts: string[],
104
104
  ): boolean {
105
105
  // Exact match
106
106
  if (pattern === hostname) {
@@ -108,48 +108,48 @@ function matchDomainPattern(
108
108
  }
109
109
 
110
110
  // `.` or `*` - any apex domain
111
- if (pattern === '.' || pattern === '*') {
111
+ if (pattern === "." || pattern === "*") {
112
112
  return isApexDomain(parts);
113
113
  }
114
114
 
115
115
  // `**` - any domain (apex + all subdomains)
116
- if (pattern === '**') {
116
+ if (pattern === "**") {
117
117
  return true;
118
118
  }
119
119
 
120
120
  // `*.` - any single-level subdomain
121
- if (pattern === '*.') {
121
+ if (pattern === "*.") {
122
122
  return getSubdomainLevel(parts) === 1;
123
123
  }
124
124
 
125
125
  // `**.` - any multi-level subdomain (2+ levels)
126
- if (pattern === '**.') {
126
+ if (pattern === "**.") {
127
127
  return getSubdomainLevel(parts) >= 2;
128
128
  }
129
129
 
130
130
  // `*.tld` - any apex domain with specific TLD (e.g., *.com)
131
- if (pattern.startsWith('*.') && !pattern.includes('.', 2)) {
131
+ if (pattern.startsWith("*.") && !pattern.includes(".", 2)) {
132
132
  const tld = pattern.slice(2);
133
- return isApexDomain(parts) && hostname.endsWith('.' + tld);
133
+ return isApexDomain(parts) && hostname.endsWith("." + tld);
134
134
  }
135
135
 
136
136
  // `*.example.com` - single subdomain of specific domain
137
- if (pattern.startsWith('*.')) {
137
+ if (pattern.startsWith("*.")) {
138
138
  const baseDomain = pattern.slice(2);
139
- if (hostname.endsWith('.' + baseDomain)) {
139
+ if (hostname.endsWith("." + baseDomain)) {
140
140
  // Count parts: if pattern is *.example.com (3 parts),
141
141
  // hostname should have exactly 4 parts (www.example.com)
142
- const patternParts = baseDomain.split('.');
142
+ const patternParts = baseDomain.split(".");
143
143
  return parts.length === patternParts.length + 1;
144
144
  }
145
145
  return false;
146
146
  }
147
147
 
148
148
  // `**.example.com` - any depth subdomain of specific domain
149
- if (pattern.startsWith('**.')) {
149
+ if (pattern.startsWith("**.")) {
150
150
  const baseDomain = pattern.slice(3);
151
- if (hostname.endsWith('.' + baseDomain)) {
152
- const patternParts = baseDomain.split('.');
151
+ if (hostname.endsWith("." + baseDomain)) {
152
+ const patternParts = baseDomain.split(".");
153
153
  // Must have more parts than the base domain (i.e., has subdomains)
154
154
  return parts.length > patternParts.length;
155
155
  }
@@ -158,7 +158,7 @@ function matchDomainPattern(
158
158
 
159
159
  // `subdomain.*` - specific subdomain of any apex domain
160
160
  // e.g., admin.* matches admin.example.com, admin.google.com
161
- if (pattern.endsWith('.*')) {
161
+ if (pattern.endsWith(".*")) {
162
162
  const subdomain = pattern.slice(0, -2);
163
163
  // Must be single-level subdomain (3 parts total)
164
164
  if (parts.length === 3 && parts[0] === subdomain) {
@@ -169,7 +169,7 @@ function matchDomainPattern(
169
169
 
170
170
  // `subdomain.**` - specific subdomain of any domain (including multi-level)
171
171
  // e.g., admin.** matches admin.example.com, admin.sub.example.com
172
- if (pattern.endsWith('.**')) {
172
+ if (pattern.endsWith(".**")) {
173
173
  const subdomain = pattern.slice(0, -3);
174
174
  if (parts.length >= 3 && parts[0] === subdomain) {
175
175
  return true;
@@ -179,7 +179,7 @@ function matchDomainPattern(
179
179
 
180
180
  // `subdomain.` - specific subdomain of any apex domain (no wildcard)
181
181
  // e.g., admin. matches admin.example.com, admin.google.com
182
- if (pattern.endsWith('.') && !pattern.includes('*')) {
182
+ if (pattern.endsWith(".") && !pattern.includes("*")) {
183
183
  const subdomain = pattern.slice(0, -1);
184
184
  // Must be exactly 3 parts (subdomain.domain.tld)
185
185
  if (parts.length === 3 && parts[0] === subdomain) {
@@ -195,17 +195,17 @@ function matchDomainPattern(
195
195
  * Validate pattern format
196
196
  */
197
197
  export function validatePattern(pattern: string): void {
198
- if (!pattern || typeof pattern !== 'string') {
198
+ if (!pattern || typeof pattern !== "string") {
199
199
  throw new InvalidPatternError(
200
200
  pattern,
201
- 'Pattern must be a non-empty string',
202
- { cause: { type: typeof pattern, value: pattern } }
201
+ "Pattern must be a non-empty string",
202
+ { cause: { type: typeof pattern, value: pattern } },
203
203
  );
204
204
  }
205
205
 
206
206
  // Check for invalid characters (spaces, etc.)
207
207
  if (/\s/.test(pattern)) {
208
- throw new InvalidPatternError(pattern, 'contains whitespace', {
208
+ throw new InvalidPatternError(pattern, "contains whitespace", {
209
209
  cause: { pattern },
210
210
  });
211
211
  }