@rangojs/router 0.0.0-experimental.dfdb0387 → 0.0.0-experimental.e16b7c00

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 (237) hide show
  1. package/README.md +120 -25
  2. package/dist/bin/rango.js +147 -57
  3. package/dist/vite/index.js +2106 -842
  4. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  5. package/package.json +13 -8
  6. package/skills/breadcrumbs/SKILL.md +3 -1
  7. package/skills/bundle-analysis/SKILL.md +159 -0
  8. package/skills/cache-guide/SKILL.md +222 -30
  9. package/skills/caching/SKILL.md +188 -8
  10. package/skills/composability/SKILL.md +27 -2
  11. package/skills/document-cache/SKILL.md +78 -55
  12. package/skills/handler-use/SKILL.md +364 -0
  13. package/skills/hooks/SKILL.md +229 -20
  14. package/skills/host-router/SKILL.md +45 -20
  15. package/skills/i18n/SKILL.md +276 -0
  16. package/skills/intercept/SKILL.md +46 -4
  17. package/skills/layout/SKILL.md +28 -7
  18. package/skills/links/SKILL.md +247 -17
  19. package/skills/loader/SKILL.md +219 -9
  20. package/skills/middleware/SKILL.md +47 -12
  21. package/skills/migrate-nextjs/SKILL.md +582 -0
  22. package/skills/migrate-react-router/SKILL.md +769 -0
  23. package/skills/mime-routes/SKILL.md +27 -0
  24. package/skills/observability/SKILL.md +137 -0
  25. package/skills/parallel/SKILL.md +71 -6
  26. package/skills/prerender/SKILL.md +14 -33
  27. package/skills/rango/SKILL.md +236 -22
  28. package/skills/react-compiler/SKILL.md +168 -0
  29. package/skills/response-routes/SKILL.md +66 -9
  30. package/skills/route/SKILL.md +57 -4
  31. package/skills/router-setup/SKILL.md +3 -3
  32. package/skills/server-actions/SKILL.md +751 -0
  33. package/skills/streams-and-websockets/SKILL.md +283 -0
  34. package/skills/typesafety/SKILL.md +319 -27
  35. package/skills/use-cache/SKILL.md +36 -5
  36. package/skills/view-transitions/SKILL.md +294 -0
  37. package/src/__augment-tests__/augment.ts +81 -0
  38. package/src/__augment-tests__/augmented.check.ts +117 -0
  39. package/src/browser/action-coordinator.ts +53 -36
  40. package/src/browser/app-shell.ts +52 -0
  41. package/src/browser/event-controller.ts +86 -70
  42. package/src/browser/history-state.ts +21 -0
  43. package/src/browser/index.ts +3 -3
  44. package/src/browser/navigation-bridge.ts +86 -11
  45. package/src/browser/navigation-client.ts +45 -25
  46. package/src/browser/navigation-store.ts +32 -9
  47. package/src/browser/navigation-transaction.ts +10 -28
  48. package/src/browser/partial-update.ts +61 -28
  49. package/src/browser/prefetch/cache.ts +124 -26
  50. package/src/browser/prefetch/fetch.ts +129 -37
  51. package/src/browser/prefetch/queue.ts +36 -5
  52. package/src/browser/rango-state.ts +53 -13
  53. package/src/browser/react/Link.tsx +18 -13
  54. package/src/browser/react/NavigationProvider.tsx +72 -31
  55. package/src/browser/react/filter-segment-order.ts +51 -7
  56. package/src/browser/react/index.ts +3 -0
  57. package/src/browser/react/location-state-shared.ts +175 -4
  58. package/src/browser/react/location-state.ts +39 -13
  59. package/src/browser/react/use-handle.ts +17 -9
  60. package/src/browser/react/use-navigation.ts +22 -2
  61. package/src/browser/react/use-params.ts +20 -8
  62. package/src/browser/react/use-reverse.ts +106 -0
  63. package/src/browser/react/use-router.ts +22 -2
  64. package/src/browser/react/use-segments.ts +11 -8
  65. package/src/browser/response-adapter.ts +25 -0
  66. package/src/browser/rsc-router.tsx +64 -22
  67. package/src/browser/scroll-restoration.ts +22 -14
  68. package/src/browser/segment-reconciler.ts +10 -14
  69. package/src/browser/segment-structure-assert.ts +2 -2
  70. package/src/browser/server-action-bridge.ts +23 -30
  71. package/src/browser/types.ts +21 -0
  72. package/src/build/collect-fallback-refs.ts +107 -0
  73. package/src/build/generate-manifest.ts +60 -35
  74. package/src/build/generate-route-types.ts +2 -0
  75. package/src/build/index.ts +2 -0
  76. package/src/build/route-trie.ts +52 -25
  77. package/src/build/route-types/codegen.ts +4 -4
  78. package/src/build/route-types/include-resolution.ts +1 -1
  79. package/src/build/route-types/per-module-writer.ts +7 -4
  80. package/src/build/route-types/router-processing.ts +55 -14
  81. package/src/build/route-types/scan-filter.ts +1 -1
  82. package/src/build/route-types/source-scan.ts +118 -0
  83. package/src/build/runtime-discovery.ts +9 -20
  84. package/src/cache/cache-error.ts +104 -0
  85. package/src/cache/cache-policy.ts +95 -1
  86. package/src/cache/cache-runtime.ts +79 -13
  87. package/src/cache/cache-scope.ts +77 -46
  88. package/src/cache/cache-tag.ts +135 -0
  89. package/src/cache/cf/cf-cache-store.ts +1067 -176
  90. package/src/cache/cf/index.ts +4 -1
  91. package/src/cache/document-cache.ts +59 -7
  92. package/src/cache/index.ts +6 -0
  93. package/src/cache/memory-segment-store.ts +158 -14
  94. package/src/cache/tag-invalidation.ts +206 -0
  95. package/src/cache/types.ts +27 -0
  96. package/src/client.rsc.tsx +3 -0
  97. package/src/client.tsx +92 -182
  98. package/src/context-var.ts +5 -5
  99. package/src/decode-loader-results.ts +36 -0
  100. package/src/errors.ts +30 -1
  101. package/src/handle.ts +4 -6
  102. package/src/host/index.ts +2 -2
  103. package/src/host/router.ts +129 -57
  104. package/src/host/types.ts +31 -2
  105. package/src/host/utils.ts +1 -1
  106. package/src/href-client.ts +140 -20
  107. package/src/index.rsc.ts +16 -4
  108. package/src/index.ts +65 -15
  109. package/src/loader-store.ts +500 -0
  110. package/src/loader.rsc.ts +2 -5
  111. package/src/loader.ts +3 -10
  112. package/src/missing-id-error.ts +68 -0
  113. package/src/outlet-context.ts +1 -1
  114. package/src/prerender.ts +4 -4
  115. package/src/response-utils.ts +37 -0
  116. package/src/reverse.ts +65 -36
  117. package/src/route-content-wrapper.tsx +6 -28
  118. package/src/route-definition/dsl-helpers.ts +384 -257
  119. package/src/route-definition/helper-factories.ts +29 -139
  120. package/src/route-definition/helpers-types.ts +100 -28
  121. package/src/route-definition/resolve-handler-use.ts +6 -0
  122. package/src/route-definition/use-item-types.ts +32 -0
  123. package/src/route-types.ts +26 -41
  124. package/src/router/content-negotiation.ts +15 -2
  125. package/src/router/error-handling.ts +1 -1
  126. package/src/router/handler-context.ts +21 -38
  127. package/src/router/intercept-resolution.ts +4 -18
  128. package/src/router/lazy-includes.ts +8 -8
  129. package/src/router/loader-resolution.ts +19 -2
  130. package/src/router/manifest.ts +22 -13
  131. package/src/router/match-api.ts +4 -3
  132. package/src/router/match-handlers.ts +1 -0
  133. package/src/router/match-middleware/cache-lookup.ts +46 -92
  134. package/src/router/match-middleware/cache-store.ts +3 -2
  135. package/src/router/match-result.ts +53 -32
  136. package/src/router/metrics.ts +1 -1
  137. package/src/router/middleware-types.ts +15 -26
  138. package/src/router/middleware.ts +99 -84
  139. package/src/router/pattern-matching.ts +101 -17
  140. package/src/router/prerender-match.ts +3 -1
  141. package/src/router/preview-match.ts +3 -1
  142. package/src/router/request-classification.ts +4 -28
  143. package/src/router/revalidation.ts +58 -2
  144. package/src/router/router-interfaces.ts +45 -28
  145. package/src/router/router-options.ts +25 -1
  146. package/src/router/router-registry.ts +2 -5
  147. package/src/router/segment-resolution/fresh.ts +27 -6
  148. package/src/router/segment-resolution/loader-cache.ts +8 -17
  149. package/src/router/segment-resolution/revalidation.ts +147 -106
  150. package/src/router/segment-resolution/view-transition-default.ts +36 -0
  151. package/src/router/substitute-pattern-params.ts +56 -0
  152. package/src/router/trie-matching.ts +18 -13
  153. package/src/router/types.ts +8 -0
  154. package/src/router/url-params.ts +49 -0
  155. package/src/router.ts +23 -18
  156. package/src/rsc/handler-context.ts +2 -2
  157. package/src/rsc/handler.ts +38 -70
  158. package/src/rsc/helpers.ts +72 -43
  159. package/src/rsc/index.ts +1 -1
  160. package/src/rsc/origin-guard.ts +28 -10
  161. package/src/rsc/progressive-enhancement.ts +4 -0
  162. package/src/rsc/response-route-handler.ts +54 -54
  163. package/src/rsc/rsc-rendering.ts +35 -51
  164. package/src/rsc/runtime-warnings.ts +9 -10
  165. package/src/rsc/server-action.ts +17 -37
  166. package/src/rsc/ssr-setup.ts +16 -0
  167. package/src/rsc/types.ts +8 -2
  168. package/src/search-params.ts +4 -4
  169. package/src/segment-content-promise.ts +67 -0
  170. package/src/segment-loader-promise.ts +122 -0
  171. package/src/segment-system.tsx +132 -116
  172. package/src/serialize.ts +243 -0
  173. package/src/server/context.ts +143 -53
  174. package/src/server/cookie-store.ts +28 -4
  175. package/src/server/request-context.ts +46 -44
  176. package/src/ssr/index.tsx +5 -1
  177. package/src/static-handler.ts +1 -1
  178. package/src/types/cache-types.ts +13 -4
  179. package/src/types/error-types.ts +5 -1
  180. package/src/types/global-namespace.ts +39 -26
  181. package/src/types/handler-context.ts +68 -50
  182. package/src/types/index.ts +1 -0
  183. package/src/types/loader-types.ts +5 -6
  184. package/src/types/request-scope.ts +126 -0
  185. package/src/types/route-entry.ts +11 -0
  186. package/src/types/segments.ts +35 -2
  187. package/src/urls/include-helper.ts +34 -67
  188. package/src/urls/index.ts +0 -3
  189. package/src/urls/path-helper-types.ts +41 -7
  190. package/src/urls/path-helper.ts +17 -52
  191. package/src/urls/pattern-types.ts +36 -19
  192. package/src/urls/response-types.ts +22 -29
  193. package/src/urls/type-extraction.ts +26 -116
  194. package/src/urls/urls-function.ts +1 -5
  195. package/src/use-loader.tsx +413 -42
  196. package/src/vite/debug.ts +185 -0
  197. package/src/vite/discovery/bundle-postprocess.ts +6 -6
  198. package/src/vite/discovery/discover-routers.ts +101 -51
  199. package/src/vite/discovery/discovery-errors.ts +194 -0
  200. package/src/vite/discovery/gate-state.ts +171 -0
  201. package/src/vite/discovery/prerender-collection.ts +67 -26
  202. package/src/vite/discovery/route-types-writer.ts +40 -84
  203. package/src/vite/discovery/self-gen-tracking.ts +27 -1
  204. package/src/vite/discovery/state.ts +33 -0
  205. package/src/vite/discovery/virtual-module-codegen.ts +13 -23
  206. package/src/vite/index.ts +2 -0
  207. package/src/vite/plugin-types.ts +67 -0
  208. package/src/vite/plugins/cjs-to-esm.ts +8 -7
  209. package/src/vite/plugins/client-ref-dedup.ts +16 -0
  210. package/src/vite/plugins/client-ref-hashing.ts +28 -5
  211. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  212. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  213. package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
  214. package/src/vite/plugins/expose-action-id.ts +54 -30
  215. package/src/vite/plugins/expose-id-utils.ts +12 -8
  216. package/src/vite/plugins/expose-ids/export-analysis.ts +100 -20
  217. package/src/vite/plugins/expose-ids/handler-transform.ts +8 -61
  218. package/src/vite/plugins/expose-ids/loader-transform.ts +3 -5
  219. package/src/vite/plugins/expose-ids/router-transform.ts +20 -3
  220. package/src/vite/plugins/expose-internal-ids.ts +496 -486
  221. package/src/vite/plugins/performance-tracks.ts +29 -25
  222. package/src/vite/plugins/use-cache-transform.ts +65 -50
  223. package/src/vite/plugins/version-injector.ts +39 -23
  224. package/src/vite/plugins/version-plugin.ts +59 -2
  225. package/src/vite/plugins/virtual-entries.ts +2 -2
  226. package/src/vite/rango.ts +116 -29
  227. package/src/vite/router-discovery.ts +750 -100
  228. package/src/vite/utils/ast-handler-extract.ts +15 -15
  229. package/src/vite/utils/banner.ts +1 -1
  230. package/src/vite/utils/bundle-analysis.ts +4 -2
  231. package/src/vite/utils/client-chunks.ts +190 -0
  232. package/src/vite/utils/forward-user-plugins.ts +193 -0
  233. package/src/vite/utils/manifest-utils.ts +21 -5
  234. package/src/vite/utils/package-resolution.ts +41 -1
  235. package/src/vite/utils/prerender-utils.ts +21 -6
  236. package/src/vite/utils/shared-utils.ts +107 -26
  237. package/src/browser/action-response-classifier.ts +0 -99
package/src/router.ts CHANGED
@@ -22,10 +22,9 @@ import type { UrlPatterns } from "./urls.js";
22
22
  import type { UrlBuilder } from "./urls/pattern-types.js";
23
23
  import { urls } from "./urls.js";
24
24
  import {
25
- EntryData,
26
- InterceptSelectorContext,
25
+ type EntryData,
27
26
  getContext,
28
- RSCRouterContext,
27
+ RangoContext,
29
28
  type MetricsStore,
30
29
  } from "./server/context";
31
30
  import { createHandleStore, type HandleStore } from "./server/handle-store.js";
@@ -91,13 +90,10 @@ import {
91
90
  RouterRegistry,
92
91
  nextRouterAutoId,
93
92
  } from "./router/router-registry.js";
93
+ import type { RangoOptions, RootLayoutProps } from "./router/router-options.js";
94
94
  import type {
95
- RSCRouterOptions,
96
- RootLayoutProps,
97
- } from "./router/router-options.js";
98
- import type {
99
- RSCRouter,
100
- RSCRouterInternal,
95
+ Rango,
96
+ RangoInternal,
101
97
  RouterRequestInput,
102
98
  } from "./router/router-interfaces.js";
103
99
 
@@ -116,22 +112,22 @@ import {
116
112
  // Re-export public types and values from extracted modules
117
113
  export { RSC_ROUTER_BRAND, RouterRegistry } from "./router/router-registry.js";
118
114
  export type {
119
- RSCRouterOptions,
115
+ RangoOptions,
120
116
  RootLayoutProps,
121
117
  SSRStreamMode,
122
118
  SSROptions,
123
119
  ResolveStreamingContext,
124
120
  } from "./router/router-options.js";
125
121
  export type {
126
- RSCRouter,
127
- RSCRouterInternal,
122
+ Rango,
123
+ RangoInternal,
128
124
  RouterRequestInput,
129
125
  } from "./router/router-interfaces.js";
130
126
  export { toInternal } from "./router/router-interfaces.js";
131
127
 
132
128
  export function createRouter<TEnv = any>(
133
- options: RSCRouterOptions<TEnv> = {},
134
- ): RSCRouter<TEnv, {}> {
129
+ options: RangoOptions<TEnv> = {},
130
+ ): Rango<TEnv, {}> {
135
131
  const {
136
132
  id: userProvidedId,
137
133
  $$id: injectedId,
@@ -159,6 +155,7 @@ export function createRouter<TEnv = any>(
159
155
  timeouts: timeoutsOption,
160
156
  onTimeout,
161
157
  originCheck: originCheckOption,
158
+ viewTransition: viewTransitionOption = "auto",
162
159
  } = options;
163
160
 
164
161
  // Normalize basename: ensure leading slash, strip trailing slash.
@@ -538,6 +535,7 @@ export function createRouter<TEnv = any>(
538
535
  findNearestNotFoundBoundary,
539
536
  notFoundComponent: notFound,
540
537
  callOnError,
538
+ viewTransitionDefault: viewTransitionOption,
541
539
  };
542
540
 
543
541
  // Match API dependencies
@@ -674,7 +672,7 @@ export function createRouter<TEnv = any>(
674
672
  * The type system tracks accumulated routes through the builder chain
675
673
  * Initial TRoutes is {} (empty) to avoid poisoning accumulated types with Record<string, string>
676
674
  */
677
- const router: RSCRouterInternal<TEnv, {}> = {
675
+ const router: RangoInternal<TEnv, {}> = {
678
676
  __brand: RSC_ROUTER_BRAND,
679
677
  id: routerId,
680
678
  basename,
@@ -722,7 +720,7 @@ export function createRouter<TEnv = any>(
722
720
  };
723
721
 
724
722
  let handlerResult: AllUseItems[] = [];
725
- RSCRouterContext.run(
723
+ RangoContext.run(
726
724
  {
727
725
  manifest,
728
726
  patterns: routePatterns,
@@ -1000,6 +998,13 @@ export function createRouter<TEnv = any>(
1000
998
  // Expose basename for runtime manifest generation
1001
999
  __basename: basename,
1002
1000
 
1001
+ // Expose router-level boundary defaults for build-time clientChunks
1002
+ // discovery (so a "use client" default boundary lands in app-fallback).
1003
+ // These are createRouter options, never pushed onto EntryData.
1004
+ __defaultErrorBoundary: defaultErrorBoundary,
1005
+ __defaultNotFoundBoundary: defaultNotFoundBoundary,
1006
+ __notFound: notFound,
1007
+
1003
1008
  // RSC request handler (lazily created on first call)
1004
1009
  fetch: (() => {
1005
1010
  // Handler is created on first call and reused
@@ -1046,9 +1051,9 @@ export function createRouter<TEnv = any>(
1046
1051
 
1047
1052
  // If urls option was provided, auto-register them
1048
1053
  if (typeof urlsOption === "function") {
1049
- return router.routes(urlsOption) as RSCRouter<TEnv, {}>;
1054
+ return router.routes(urlsOption) as Rango<TEnv, {}>;
1050
1055
  } else if (urlsOption) {
1051
- return router.routes(urlsOption) as RSCRouter<TEnv, {}>;
1056
+ return router.routes(urlsOption) as Rango<TEnv, {}>;
1052
1057
  }
1053
1058
 
1054
1059
  return router;
@@ -6,14 +6,14 @@
6
6
  * RSC rendering) so they can be standalone modules without closure coupling.
7
7
  */
8
8
 
9
- import type { RSCRouterInternal } from "../router/router-interfaces.js";
9
+ import type { RangoInternal } from "../router/router-interfaces.js";
10
10
  import type { ErrorPhase } from "../types.js";
11
11
  import type { InvokeOnErrorContext } from "../router/error-handling.js";
12
12
  import type { RSCDependencies, LoadSSRModule } from "./types.js";
13
13
  import type { SSRStreamMode } from "../router/router-options.js";
14
14
 
15
15
  export interface HandlerContext<TEnv = unknown> {
16
- router: RSCRouterInternal<TEnv, any>;
16
+ router: RangoInternal<TEnv, any>;
17
17
  version: string;
18
18
  renderToReadableStream: RSCDependencies["renderToReadableStream"];
19
19
  decodeReply: RSCDependencies["decodeReply"];
@@ -8,7 +8,7 @@
8
8
  */
9
9
 
10
10
  import { createElement } from "react";
11
- import { RouteNotFoundError } from "../errors.js";
11
+ import { isRouteNotFoundError } from "../errors.js";
12
12
  import { matchMiddleware, executeMiddleware } from "../router/middleware.js";
13
13
  import {
14
14
  runWithRequestContext,
@@ -31,6 +31,7 @@ import {
31
31
  interceptRedirectForPartial,
32
32
  buildRouteMiddlewareEntries,
33
33
  } from "./helpers.js";
34
+ import { isWebSocketUpgradeResponse } from "../response-utils.js";
34
35
  import {
35
36
  handleResponseRoute,
36
37
  type ResponseRouteMatch,
@@ -56,6 +57,8 @@ import {
56
57
  getRouterTrie,
57
58
  } from "../route-map-builder.js";
58
59
  import type { HandlerContext } from "./handler-context.js";
60
+ import type { CacheErrorCategory } from "../cache/cache-error.js";
61
+ import type { SegmentCacheStore } from "../cache/types.js";
59
62
  import { buildRouterTrieFromUrlpatterns } from "./manifest-init.js";
60
63
  import { handleProgressiveEnhancement } from "./progressive-enhancement.js";
61
64
  import {
@@ -64,7 +67,10 @@ import {
64
67
  type ActionContinuation,
65
68
  } from "./server-action.js";
66
69
  import { handleLoaderFetch } from "./loader-fetch.js";
67
- import { checkRequestOrigin, type OriginCheckPhase } from "./origin-guard.js";
70
+ import {
71
+ checkRequestOrigin,
72
+ ORIGIN_CHECK_PHASE_BY_MODE,
73
+ } from "./origin-guard.js";
68
74
  import { handleRscRendering } from "./rsc-rendering.js";
69
75
  import {
70
76
  withTimeout,
@@ -81,6 +87,7 @@ import {
81
87
  startSSRSetup,
82
88
  getSSRSetup,
83
89
  mayNeedSSR,
90
+ isRscRequest,
84
91
  SSR_SETUP_VAR,
85
92
  } from "./ssr-setup.js";
86
93
  import {
@@ -128,6 +135,13 @@ export function createRSCHandler<
128
135
  >(options: CreateRSCHandlerOptions<TEnv, TRoutes>) {
129
136
  const { router, version = VERSION, nonce: nonceProvider } = options;
130
137
 
138
+ // Handler-owned registry of explicit per-scope stores from cache({ store }).
139
+ // Lives in the closure so it is scoped per handler (multi-router deployments
140
+ // get separate registries) and accumulates every explicit store this handler
141
+ // resolves across requests. updateTag()/revalidateTag() iterate it to reach
142
+ // stores not covered by the app-level ctx._cacheStore.
143
+ const explicitTaggedStores = new Set<SegmentCacheStore>();
144
+
131
145
  // Use provided deps or default to @vitejs/plugin-rsc/rsc exports
132
146
  const deps = options.deps ?? rscDeps;
133
147
  const {
@@ -352,7 +366,7 @@ export function createRSCHandler<
352
366
  // Resolve cache store configuration
353
367
  // Priority: options.cache (handler override) > router.cache (router default)
354
368
  // Store is enabled only if: config provided, enabled, and no ?__no_cache query param
355
- let cacheStore = undefined;
369
+ let cacheStore: SegmentCacheStore | undefined;
356
370
  const cacheOption = options.cache ?? router.cache;
357
371
  if (cacheOption && !url.searchParams.has("__no_cache")) {
358
372
  const cacheConfig =
@@ -419,6 +433,7 @@ export function createRSCHandler<
419
433
  url,
420
434
  variables,
421
435
  cacheStore,
436
+ explicitTaggedStores,
422
437
  cacheProfiles: router.cacheProfiles,
423
438
  executionContext: executionCtx,
424
439
  themeConfig: router.themeConfig,
@@ -431,7 +446,7 @@ export function createRSCHandler<
431
446
  // can surface non-fatal errors through the router's onError callback.
432
447
  requestContext._reportBackgroundError = (
433
448
  error: unknown,
434
- category: string,
449
+ category: CacheErrorCategory,
435
450
  ) => {
436
451
  callOnError(error, "cache", {
437
452
  request,
@@ -533,7 +548,9 @@ export function createRSCHandler<
533
548
  }
534
549
 
535
550
  const fullTiming = timingParts.join(", ");
536
- if (fullTiming) response.headers.set("Server-Timing", fullTiming);
551
+ if (fullTiming && !isWebSocketUpgradeResponse(response)) {
552
+ response.headers.set("Server-Timing", fullTiming);
553
+ }
537
554
 
538
555
  return response;
539
556
  });
@@ -593,10 +610,7 @@ export function createRSCHandler<
593
610
  routerId: router.id,
594
611
  });
595
612
  } catch (error) {
596
- if (
597
- error instanceof RouteNotFoundError ||
598
- (error instanceof Error && error.name === "RouteNotFoundError")
599
- ) {
613
+ if (isRouteNotFoundError(error)) {
600
614
  // Let the render path handle 404 — match()/matchPartial() will
601
615
  // re-throw RouteNotFoundError and the catch block in
602
616
  // executeRenderWithMiddleware renders the not-found page.
@@ -647,14 +661,7 @@ export function createRSCHandler<
647
661
  }
648
662
 
649
663
  // ---- 3. Origin guard (gate for action/loader/PE modes) ----
650
- const originPhase: OriginCheckPhase | null =
651
- plan.mode === "action"
652
- ? "action"
653
- : plan.mode === "loader"
654
- ? "loader"
655
- : plan.mode === "pe-render"
656
- ? "pe-form"
657
- : null;
664
+ const originPhase = ORIGIN_CHECK_PHASE_BY_MODE[plan.mode];
658
665
  if (originPhase) {
659
666
  const originResult = await checkRequestOrigin(
660
667
  request,
@@ -804,7 +811,7 @@ export function createRSCHandler<
804
811
  );
805
812
  }
806
813
  const response = responseOutcome.result;
807
- if (plan.negotiated) {
814
+ if (plan.negotiated && !isWebSocketUpgradeResponse(response)) {
808
815
  response.headers.append("Vary", "Accept");
809
816
  }
810
817
  return response;
@@ -921,47 +928,17 @@ export function createRSCHandler<
921
928
  );
922
929
  }
923
930
 
924
- // ---- Full render / Partial render (or PE that fell through) ----
925
- if (plan.mode === "full-render" || plan.mode === "partial-render") {
926
- const isPartial = plan.mode === "partial-render";
927
- return executeRenderWithMiddleware(
928
- plan.route.routeMiddleware,
929
- plan.negotiated,
930
- plan.route.routeKey,
931
- routeReverse,
932
- request,
933
- env,
934
- url,
935
- variables,
936
- nonce,
937
- handleStore,
938
- isPartial,
939
- );
940
- }
941
-
942
- // PE that fell through (handleProgressiveEnhancement returned null)
943
- // falls back to full render
944
- if (plan.mode === "pe-render") {
945
- return executeRenderWithMiddleware(
946
- plan.route.routeMiddleware,
947
- false,
948
- plan.route.routeKey,
949
- routeReverse,
950
- request,
951
- env,
952
- url,
953
- variables,
954
- nonce,
955
- handleStore,
956
- false,
957
- );
958
- }
959
-
960
- // Redirect plan that wasn't handled above (full-page redirect — let
961
- // the pipeline handle it via match() which returns { redirect: url })
931
+ // Full render, partial render, fallen-through PE, and full-page redirect all
932
+ // render through the same middleware-wrapped path. Only full/partial-render
933
+ // carry negotiation + the partial flag; pe/redirect render plainly.
934
+ const isPartial = plan.mode === "partial-render";
935
+ const negotiated =
936
+ plan.mode === "full-render" || plan.mode === "partial-render"
937
+ ? plan.negotiated
938
+ : false;
962
939
  return executeRenderWithMiddleware(
963
940
  plan.route.routeMiddleware,
964
- false,
941
+ negotiated,
965
942
  plan.route.routeKey,
966
943
  routeReverse,
967
944
  request,
@@ -970,7 +947,7 @@ export function createRSCHandler<
970
947
  variables,
971
948
  nonce,
972
949
  handleStore,
973
- false,
950
+ isPartial,
974
951
  );
975
952
  }
976
953
 
@@ -1014,7 +991,7 @@ export function createRSCHandler<
1014
991
  nonce,
1015
992
  );
1016
993
  }
1017
- if (negotiated) {
994
+ if (negotiated && !isWebSocketUpgradeResponse(response)) {
1018
995
  response.headers.append("Vary", "Accept");
1019
996
  }
1020
997
  return response;
@@ -1050,10 +1027,7 @@ export function createRSCHandler<
1050
1027
  }
1051
1028
 
1052
1029
  // Render 404 page for unmatched routes
1053
- const isRouteNotFound =
1054
- error instanceof RouteNotFoundError ||
1055
- (error instanceof Error && error.name === "RouteNotFoundError");
1056
- if (isRouteNotFound) {
1030
+ if (isRouteNotFoundError(error)) {
1057
1031
  callOnError(error, "routing", {
1058
1032
  request,
1059
1033
  url,
@@ -1100,13 +1074,7 @@ export function createRSCHandler<
1100
1074
  },
1101
1075
  });
1102
1076
 
1103
- const isRscRequest =
1104
- isPartial ||
1105
- (!request.headers.get("accept")?.includes("text/html") &&
1106
- !url.searchParams.has("__html")) ||
1107
- url.searchParams.has("__rsc");
1108
-
1109
- if (isRscRequest) {
1077
+ if (isRscRequest(request, url, isPartial)) {
1110
1078
  return createResponseWithMergedHeaders(rscStream, {
1111
1079
  status: 404,
1112
1080
  headers: { "content-type": "text/x-component;charset=utf-8" },
@@ -8,9 +8,50 @@ import {
8
8
  _getRequestContext,
9
9
  getLocationState,
10
10
  } from "../server/request-context.js";
11
+ import type { RequestContext } from "../server/request-context.js";
11
12
  import { resolveLocationStateEntries } from "../browser/react/location-state-shared.js";
13
+ import { isRedirectResponse } from "../response-utils.js";
12
14
  import type { MiddlewareEntry, MiddlewareFn } from "../router/middleware.js";
13
15
 
16
+ /**
17
+ * Copy stub headers from the request context onto a target Headers instance:
18
+ * append Set-Cookie entries, set everything else only if absent. Header
19
+ * mutation failures are swallowed so the same logic works against Response
20
+ * headers that may be immutable (e.g. Cloudflare protocol-switch responses).
21
+ */
22
+ function applyStubHeaders(target: Headers, stub: Headers): void {
23
+ stub.forEach((value, name) => {
24
+ try {
25
+ if (name.toLowerCase() === "set-cookie") {
26
+ target.append(name, value);
27
+ } else if (!target.has(name)) {
28
+ target.set(name, value);
29
+ }
30
+ } catch {
31
+ // Headers immutable — skip.
32
+ }
33
+ });
34
+ }
35
+
36
+ /**
37
+ * Drain ctx._onResponseCallbacks onto a response. Swapping the array before
38
+ * iteration prevents re-entrant registrations from double-firing and matches
39
+ * the contract that each callback runs at most once per request.
40
+ */
41
+ function drainOnResponseCallbacks(
42
+ ctx: RequestContext,
43
+ response: Response,
44
+ ): Response {
45
+ const callbacks = ctx._onResponseCallbacks;
46
+ if (callbacks.length === 0) return response;
47
+ ctx._onResponseCallbacks = [];
48
+ let result = response;
49
+ for (const callback of callbacks) {
50
+ result = callback(result) ?? result;
51
+ }
52
+ return result;
53
+ }
54
+
14
55
  /**
15
56
  * Check if a request body has content to decode
16
57
  */
@@ -39,40 +80,23 @@ export function createResponseWithMergedHeaders(
39
80
  return new Response(body, init);
40
81
  }
41
82
 
42
- // Merge headers from stub response into the new response.
43
- // Delete Set-Cookie from the stub after consuming so that downstream
44
- // merge points (e.g. executeMiddleware) do not duplicate them.
83
+ // Delete Set-Cookie from the stub after consuming so downstream merge
84
+ // points (e.g. executeMiddleware) don't duplicate them.
45
85
  const mergedHeaders = new Headers(init.headers);
46
- ctx.res.headers.forEach((value, name) => {
47
- if (name.toLowerCase() === "set-cookie") {
48
- mergedHeaders.append(name, value);
49
- } else if (!mergedHeaders.has(name)) {
50
- // Only set if not already present in init.headers
51
- mergedHeaders.set(name, value);
52
- }
53
- });
86
+ applyStubHeaders(mergedHeaders, ctx.res.headers);
54
87
  ctx.res.headers.delete("set-cookie");
55
88
 
56
- // Use ctx.res.status if it was set (e.g., 404 for notFound, 500 for error)
57
- // Otherwise use the status from init
89
+ // ctx.res.status overrides init.status when explicitly set (e.g. 404 for
90
+ // notFound, 500 for error). Default ctx.res.status is 200.
58
91
  const status = ctx.res.status !== 200 ? ctx.res.status : init.status;
59
92
 
60
- let response = new Response(body, {
93
+ const response = new Response(body, {
61
94
  ...init,
62
95
  status,
63
96
  headers: mergedHeaders,
64
97
  });
65
98
 
66
- // Run onResponse callbacks - each can inspect/modify the response.
67
- // Drain the array so that downstream callers (e.g. finalizeResponse)
68
- // do not re-execute the same callbacks on this response.
69
- const callbacks = ctx._onResponseCallbacks;
70
- ctx._onResponseCallbacks = [];
71
- for (const callback of callbacks) {
72
- response = callback(response) ?? response;
73
- }
74
-
75
- return response;
99
+ return drainOnResponseCallbacks(ctx, response);
76
100
  }
77
101
 
78
102
  /**
@@ -122,10 +146,10 @@ export function interceptRedirectForPartial(
122
146
  locationState?: Record<string, unknown>,
123
147
  ) => Response,
124
148
  ): Response | null {
125
- const redirectUrl = response.headers.get("Location");
126
- if (!(response.status >= 300 && response.status < 400 && redirectUrl)) {
149
+ if (!isRedirectResponse(response)) {
127
150
  return null;
128
151
  }
152
+ const redirectUrl = response.headers.get("Location")!;
129
153
  const locationState = getLocationState();
130
154
  let intercepted: Response;
131
155
  if (locationState) {
@@ -175,24 +199,29 @@ export function buildRouteMiddlewareEntries<TEnv>(
175
199
  }
176
200
 
177
201
  /**
178
- * Run onResponse callbacks on an existing Response.
179
- *
180
- * Used for code paths that bypass createResponseWithMergedHeaders(), such as
181
- * middleware short-circuits where the Response is already constructed but
182
- * ctx.onResponse() callbacks still need to fire.
202
+ * Merge stub headers from the request context onto an existing Response in
203
+ * place, then drain onResponse callbacks. Used when a Response cannot flow
204
+ * through `new Response()` status 101 is outside the constructor's
205
+ * 200-599 range, and the Cloudflare-specific `webSocket` property would be
206
+ * lost on reconstruction.
183
207
  */
184
- export function finalizeResponse(response: Response): Response {
208
+ export function mergeStubHeadersAndFinalize(response: Response): Response {
185
209
  const ctx = _getRequestContext();
186
- if (!ctx || ctx._onResponseCallbacks.length === 0) {
187
- return response;
188
- }
210
+ if (!ctx) return response;
189
211
 
190
- // Drain the array so callbacks run at most once per request.
191
- const callbacks = ctx._onResponseCallbacks;
192
- ctx._onResponseCallbacks = [];
193
- let result = response;
194
- for (const callback of callbacks) {
195
- result = callback(result) ?? result;
196
- }
197
- return result;
212
+ applyStubHeaders(response.headers, ctx.res.headers);
213
+ ctx.res.headers.delete("set-cookie");
214
+
215
+ return drainOnResponseCallbacks(ctx, response);
216
+ }
217
+
218
+ /**
219
+ * Run onResponse callbacks on an existing Response. Used by code paths that
220
+ * bypass createResponseWithMergedHeaders (e.g. middleware short-circuits)
221
+ * but still need ctx.onResponse() callbacks to fire.
222
+ */
223
+ export function finalizeResponse(response: Response): Response {
224
+ const ctx = _getRequestContext();
225
+ if (!ctx) return response;
226
+ return drainOnResponseCallbacks(ctx, response);
198
227
  }
package/src/rsc/index.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * RSC Router - RSC Entry Point
2
+ * Rango - RSC Entry Point
3
3
  *
4
4
  * This module provides RSC utilities for server-side rendering,
5
5
  * server actions, loader fetching, and progressive enhancement.
@@ -9,11 +9,29 @@
9
9
  * navigations, bookmarks, and non-browser clients don't send Origin.
10
10
  */
11
11
 
12
+ import type { RequestPlan } from "../router/request-classification.js";
13
+
12
14
  /**
13
15
  * Request phase that triggered the origin check.
14
16
  */
15
17
  export type OriginCheckPhase = "action" | "loader" | "pe-form";
16
18
 
19
+ // Exhaustive over RequestPlan modes so a new mode must be classified here (the
20
+ // security gate) instead of silently falling through to no origin check.
21
+ export const ORIGIN_CHECK_PHASE_BY_MODE: Record<
22
+ RequestPlan["mode"],
23
+ OriginCheckPhase | null
24
+ > = {
25
+ action: "action",
26
+ loader: "loader",
27
+ "pe-render": "pe-form",
28
+ "full-render": null,
29
+ "partial-render": null,
30
+ response: null,
31
+ redirect: null,
32
+ "version-mismatch": null,
33
+ };
34
+
17
35
  /**
18
36
  * Context passed to the originCheck callback.
19
37
  */
@@ -116,14 +134,15 @@ export async function checkRequestOrigin<TEnv = any>(
116
134
  // Disabled by explicit opt-out
117
135
  if (config === false) return null;
118
136
 
119
- // Default: built-in validation (config === true or undefined)
120
- if (config === true || config === undefined) {
121
- const allowed = defaultOriginCheck(request, url);
122
- if (allowed) return null;
123
- return createForbiddenResponse(request);
124
- }
137
+ // Default (true/undefined) becomes a callback returning boolean, so the
138
+ // Response|true|reject resolution below is written once.
139
+ const check: (
140
+ ctx: OriginCheckContext<TEnv>,
141
+ ) => boolean | Response | Promise<boolean | Response> =
142
+ config === true || config === undefined
143
+ ? () => defaultOriginCheck(request, url)
144
+ : config;
125
145
 
126
- // Custom function — build context and call
127
146
  const ctx: OriginCheckContext<TEnv> = {
128
147
  request,
129
148
  url,
@@ -133,9 +152,8 @@ export async function checkRequestOrigin<TEnv = any>(
133
152
  defaultCheck: () => defaultOriginCheck(request, url),
134
153
  };
135
154
 
136
- const result = await config(ctx);
155
+ const result = await check(ctx);
137
156
 
138
157
  if (result instanceof Response) return result;
139
- if (result === true) return null;
140
- return createForbiddenResponse(request);
158
+ return result === true ? null : createForbiddenResponse(request);
141
159
  }
@@ -248,6 +248,8 @@ export async function handleProgressiveEnhancement<TEnv>(
248
248
  segments: match.segments,
249
249
  matched: match.matched,
250
250
  diff: match.diff,
251
+ resolvedIds: match.resolvedIds,
252
+ params: match.params,
251
253
  isPartial: false,
252
254
  rootLayout: ctx.router.rootLayout,
253
255
  handles: handleStore.stream(),
@@ -353,6 +355,8 @@ async function renderPeErrorBoundary<TEnv>(
353
355
  segments: errorResult.segments,
354
356
  matched: errorResult.matched,
355
357
  diff: errorResult.diff,
358
+ resolvedIds: errorResult.resolvedIds,
359
+ params: errorResult.params,
356
360
  isPartial: false,
357
361
  isError: true,
358
362
  rootLayout: ctx.router.rootLayout,