@rangojs/router 0.0.0-experimental.20 → 0.0.0-experimental.204030a9

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 (293) hide show
  1. package/AGENTS.md +4 -0
  2. package/README.md +242 -55
  3. package/dist/bin/rango.js +277 -99
  4. package/dist/vite/index.js +2929 -1132
  5. package/dist/vite/index.js.bak +5448 -0
  6. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  7. package/package.json +68 -21
  8. package/skills/breadcrumbs/SKILL.md +252 -0
  9. package/skills/bundle-analysis/SKILL.md +159 -0
  10. package/skills/cache-guide/SKILL.md +243 -21
  11. package/skills/caching/SKILL.md +159 -10
  12. package/skills/composability/SKILL.md +27 -2
  13. package/skills/document-cache/SKILL.md +78 -55
  14. package/skills/handler-use/SKILL.md +364 -0
  15. package/skills/hooks/SKILL.md +262 -51
  16. package/skills/host-router/SKILL.md +243 -0
  17. package/skills/i18n/SKILL.md +276 -0
  18. package/skills/intercept/SKILL.md +46 -4
  19. package/skills/layout/SKILL.md +28 -7
  20. package/skills/links/SKILL.md +249 -17
  21. package/skills/loader/SKILL.md +291 -31
  22. package/skills/middleware/SKILL.md +49 -12
  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 +27 -0
  26. package/skills/observability/SKILL.md +137 -0
  27. package/skills/parallel/SKILL.md +197 -6
  28. package/skills/prerender/SKILL.md +125 -102
  29. package/skills/rango/SKILL.md +242 -23
  30. package/skills/react-compiler/SKILL.md +168 -0
  31. package/skills/response-routes/SKILL.md +66 -9
  32. package/skills/route/SKILL.md +91 -8
  33. package/skills/router-setup/SKILL.md +98 -8
  34. package/skills/server-actions/SKILL.md +751 -0
  35. package/skills/streams-and-websockets/SKILL.md +283 -0
  36. package/skills/testing/SKILL.md +511 -188
  37. package/skills/typesafety/SKILL.md +354 -50
  38. package/skills/use-cache/SKILL.md +34 -5
  39. package/skills/view-transitions/SKILL.md +294 -0
  40. package/src/__augment-tests__/augment.ts +81 -0
  41. package/src/__augment-tests__/augmented.check.ts +117 -0
  42. package/src/__internal.ts +92 -0
  43. package/src/browser/action-coordinator.ts +53 -36
  44. package/src/browser/app-shell.ts +52 -0
  45. package/src/browser/app-version.ts +14 -0
  46. package/src/browser/event-controller.ts +91 -70
  47. package/src/browser/history-state.ts +21 -0
  48. package/src/browser/index.ts +3 -3
  49. package/src/browser/link-interceptor.ts +4 -0
  50. package/src/browser/navigation-bridge.ts +183 -18
  51. package/src/browser/navigation-client.ts +187 -57
  52. package/src/browser/navigation-store.ts +75 -17
  53. package/src/browser/navigation-transaction.ts +21 -37
  54. package/src/browser/partial-update.ts +143 -40
  55. package/src/browser/prefetch/cache.ts +275 -28
  56. package/src/browser/prefetch/fetch.ts +191 -46
  57. package/src/browser/prefetch/policy.ts +6 -0
  58. package/src/browser/prefetch/queue.ts +123 -20
  59. package/src/browser/prefetch/resource-ready.ts +77 -0
  60. package/src/browser/rango-state.ts +53 -13
  61. package/src/browser/react/Link.tsx +98 -14
  62. package/src/browser/react/NavigationProvider.tsx +110 -33
  63. package/src/browser/react/context.ts +7 -2
  64. package/src/browser/react/filter-segment-order.ts +51 -7
  65. package/src/browser/react/index.ts +3 -0
  66. package/src/browser/react/location-state-shared.ts +175 -4
  67. package/src/browser/react/location-state.ts +39 -13
  68. package/src/browser/react/use-handle.ts +23 -64
  69. package/src/browser/react/use-navigation.ts +22 -2
  70. package/src/browser/react/use-params.ts +20 -8
  71. package/src/browser/react/use-reverse.ts +106 -0
  72. package/src/browser/react/use-router.ts +43 -10
  73. package/src/browser/react/use-segments.ts +11 -8
  74. package/src/browser/response-adapter.ts +25 -0
  75. package/src/browser/rsc-router.tsx +200 -75
  76. package/src/browser/scroll-restoration.ts +46 -39
  77. package/src/browser/segment-reconciler.ts +36 -9
  78. package/src/browser/segment-structure-assert.ts +2 -2
  79. package/src/browser/server-action-bridge.ts +31 -36
  80. package/src/browser/types.ts +81 -5
  81. package/src/build/collect-fallback-refs.ts +107 -0
  82. package/src/build/generate-manifest.ts +65 -40
  83. package/src/build/generate-route-types.ts +5 -0
  84. package/src/build/index.ts +2 -0
  85. package/src/build/route-trie.ts +69 -26
  86. package/src/build/route-types/codegen.ts +4 -4
  87. package/src/build/route-types/include-resolution.ts +9 -2
  88. package/src/build/route-types/per-module-writer.ts +7 -4
  89. package/src/build/route-types/router-processing.ts +278 -88
  90. package/src/build/route-types/scan-filter.ts +9 -2
  91. package/src/build/route-types/source-scan.ts +118 -0
  92. package/src/build/runtime-discovery.ts +9 -20
  93. package/src/cache/cache-runtime.ts +15 -11
  94. package/src/cache/cache-scope.ts +76 -49
  95. package/src/cache/cf/cf-cache-store.ts +501 -18
  96. package/src/cache/cf/index.ts +5 -1
  97. package/src/cache/document-cache.ts +17 -7
  98. package/src/cache/index.ts +1 -0
  99. package/src/cache/taint.ts +55 -0
  100. package/src/client.rsc.tsx +5 -1
  101. package/src/client.tsx +95 -284
  102. package/src/context-var.ts +72 -2
  103. package/src/debug.ts +2 -2
  104. package/src/decode-loader-results.ts +36 -0
  105. package/src/errors.ts +30 -1
  106. package/src/handle.ts +65 -12
  107. package/src/handles/breadcrumbs.ts +66 -0
  108. package/src/handles/index.ts +1 -0
  109. package/src/host/index.ts +2 -5
  110. package/src/host/router.ts +129 -57
  111. package/src/host/types.ts +31 -2
  112. package/src/host/utils.ts +1 -1
  113. package/src/href-client.ts +140 -20
  114. package/src/index.rsc.ts +15 -40
  115. package/src/index.ts +92 -76
  116. package/src/loader-store.ts +500 -0
  117. package/src/loader.rsc.ts +2 -5
  118. package/src/loader.ts +3 -10
  119. package/src/missing-id-error.ts +68 -0
  120. package/src/outlet-context.ts +1 -1
  121. package/src/prerender/store.ts +57 -15
  122. package/src/prerender.ts +141 -80
  123. package/src/response-utils.ts +37 -0
  124. package/src/reverse.ts +65 -15
  125. package/src/route-content-wrapper.tsx +6 -28
  126. package/src/route-definition/dsl-helpers.ts +435 -260
  127. package/src/route-definition/helper-factories.ts +29 -139
  128. package/src/route-definition/helpers-types.ts +110 -34
  129. package/src/route-definition/index.ts +3 -3
  130. package/src/route-definition/redirect.ts +11 -3
  131. package/src/route-definition/resolve-handler-use.ts +155 -0
  132. package/src/route-definition/use-item-types.ts +32 -0
  133. package/src/route-map-builder.ts +7 -1
  134. package/src/route-types.ts +37 -41
  135. package/src/router/basename.ts +14 -0
  136. package/src/router/content-negotiation.ts +113 -1
  137. package/src/router/error-handling.ts +1 -1
  138. package/src/router/find-match.ts +4 -2
  139. package/src/router/handler-context.ts +105 -39
  140. package/src/router/intercept-resolution.ts +15 -22
  141. package/src/router/lazy-includes.ts +12 -9
  142. package/src/router/loader-resolution.ts +175 -23
  143. package/src/router/logging.ts +5 -2
  144. package/src/router/manifest.ts +31 -16
  145. package/src/router/match-api.ts +129 -193
  146. package/src/router/match-handlers.ts +63 -20
  147. package/src/router/match-middleware/background-revalidation.ts +30 -2
  148. package/src/router/match-middleware/cache-lookup.ts +136 -106
  149. package/src/router/match-middleware/cache-store.ts +54 -10
  150. package/src/router/match-middleware/intercept-resolution.ts +9 -7
  151. package/src/router/match-middleware/segment-resolution.ts +61 -5
  152. package/src/router/match-result.ts +124 -18
  153. package/src/router/metrics.ts +239 -14
  154. package/src/router/middleware-types.ts +61 -31
  155. package/src/router/middleware.ts +226 -124
  156. package/src/router/navigation-snapshot.ts +182 -0
  157. package/src/router/pattern-matching.ts +118 -19
  158. package/src/router/prerender-match.ts +114 -10
  159. package/src/router/preview-match.ts +32 -102
  160. package/src/router/request-classification.ts +286 -0
  161. package/src/router/revalidation.ts +85 -9
  162. package/src/router/route-snapshot.ts +245 -0
  163. package/src/router/router-context.ts +6 -1
  164. package/src/router/router-interfaces.ts +91 -29
  165. package/src/router/router-options.ts +89 -19
  166. package/src/router/router-registry.ts +2 -5
  167. package/src/router/segment-resolution/fresh.ts +240 -23
  168. package/src/router/segment-resolution/helpers.ts +30 -25
  169. package/src/router/segment-resolution/loader-cache.ts +1 -0
  170. package/src/router/segment-resolution/revalidation.ts +483 -289
  171. package/src/router/segment-resolution/view-transition-default.ts +36 -0
  172. package/src/router/segment-wrappers.ts +2 -0
  173. package/src/router/substitute-pattern-params.ts +56 -0
  174. package/src/router/telemetry.ts +99 -0
  175. package/src/router/trie-matching.ts +38 -15
  176. package/src/router/types.ts +9 -0
  177. package/src/router/url-params.ts +49 -0
  178. package/src/router.ts +120 -32
  179. package/src/rsc/handler-context.ts +2 -2
  180. package/src/rsc/handler.ts +524 -370
  181. package/src/rsc/helpers.ts +91 -43
  182. package/src/rsc/index.ts +1 -21
  183. package/src/rsc/loader-fetch.ts +23 -3
  184. package/src/rsc/manifest-init.ts +5 -1
  185. package/src/rsc/origin-guard.ts +28 -10
  186. package/src/rsc/progressive-enhancement.ts +39 -10
  187. package/src/rsc/response-route-handler.ts +46 -53
  188. package/src/rsc/rsc-rendering.ts +69 -89
  189. package/src/rsc/runtime-warnings.ts +9 -10
  190. package/src/rsc/server-action.ts +39 -47
  191. package/src/rsc/ssr-setup.ts +144 -0
  192. package/src/rsc/types.ts +19 -3
  193. package/src/search-params.ts +20 -17
  194. package/src/segment-content-promise.ts +67 -0
  195. package/src/segment-loader-promise.ts +122 -0
  196. package/src/segment-system.tsx +219 -67
  197. package/src/serialize.ts +243 -0
  198. package/src/server/context.ts +285 -63
  199. package/src/server/cookie-store.ts +28 -4
  200. package/src/server/handle-store.ts +19 -0
  201. package/src/server/loader-registry.ts +9 -8
  202. package/src/server/request-context.ts +228 -65
  203. package/src/server.ts +6 -0
  204. package/src/ssr/index.tsx +9 -1
  205. package/src/static-handler.ts +19 -7
  206. package/src/testing/cache-status.ts +166 -0
  207. package/src/testing/collect-handle.ts +63 -0
  208. package/src/testing/dispatch.ts +440 -0
  209. package/src/testing/dom.entry.ts +22 -0
  210. package/src/testing/e2e/fixture.ts +154 -0
  211. package/src/testing/e2e/index.ts +149 -0
  212. package/src/testing/e2e/matchers.ts +51 -0
  213. package/src/testing/e2e/page-helpers.ts +272 -0
  214. package/src/testing/e2e/parity.ts +306 -0
  215. package/src/testing/e2e/server.ts +183 -0
  216. package/src/testing/flight-matchers.ts +104 -0
  217. package/src/testing/flight-runtime.d.ts +21 -0
  218. package/src/testing/flight.entry.ts +22 -0
  219. package/src/testing/flight.ts +182 -0
  220. package/src/testing/generated-routes.ts +223 -0
  221. package/src/testing/index.ts +98 -0
  222. package/src/testing/internal/context.ts +151 -0
  223. package/src/testing/render-route.tsx +536 -0
  224. package/src/testing/run-loader.ts +296 -0
  225. package/src/testing/run-middleware.ts +170 -0
  226. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  227. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  228. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  229. package/src/testing/vitest-stubs/version.ts +5 -0
  230. package/src/testing/vitest.ts +112 -0
  231. package/src/theme/index.ts +4 -13
  232. package/src/types/cache-types.ts +4 -4
  233. package/src/types/global-namespace.ts +39 -26
  234. package/src/types/handler-context.ts +197 -79
  235. package/src/types/index.ts +1 -0
  236. package/src/types/loader-types.ts +41 -15
  237. package/src/types/request-scope.ts +126 -0
  238. package/src/types/route-config.ts +17 -8
  239. package/src/types/route-entry.ts +19 -1
  240. package/src/types/segments.ts +37 -6
  241. package/src/urls/include-helper.ts +34 -67
  242. package/src/urls/index.ts +0 -3
  243. package/src/urls/path-helper-types.ts +50 -9
  244. package/src/urls/path-helper.ts +63 -63
  245. package/src/urls/pattern-types.ts +48 -19
  246. package/src/urls/response-types.ts +25 -22
  247. package/src/urls/type-extraction.ts +26 -116
  248. package/src/urls/urls-function.ts +1 -5
  249. package/src/use-loader.tsx +487 -44
  250. package/src/vite/debug.ts +185 -0
  251. package/src/vite/discovery/bundle-postprocess.ts +63 -91
  252. package/src/vite/discovery/discover-routers.ts +106 -53
  253. package/src/vite/discovery/discovery-errors.ts +194 -0
  254. package/src/vite/discovery/gate-state.ts +171 -0
  255. package/src/vite/discovery/prerender-collection.ts +222 -107
  256. package/src/vite/discovery/route-types-writer.ts +40 -84
  257. package/src/vite/discovery/self-gen-tracking.ts +27 -1
  258. package/src/vite/discovery/state.ts +50 -13
  259. package/src/vite/discovery/virtual-module-codegen.ts +13 -23
  260. package/src/vite/index.ts +10 -3
  261. package/src/vite/plugin-types.ts +111 -72
  262. package/src/vite/plugins/cjs-to-esm.ts +8 -7
  263. package/src/vite/plugins/client-ref-dedup.ts +16 -0
  264. package/src/vite/plugins/client-ref-hashing.ts +28 -5
  265. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  266. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  267. package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
  268. package/src/vite/plugins/expose-action-id.ts +55 -33
  269. package/src/vite/plugins/expose-id-utils.ts +24 -8
  270. package/src/vite/plugins/expose-ids/export-analysis.ts +100 -20
  271. package/src/vite/plugins/expose-ids/handler-transform.ts +12 -35
  272. package/src/vite/plugins/expose-ids/loader-transform.ts +3 -5
  273. package/src/vite/plugins/expose-ids/router-transform.ts +20 -3
  274. package/src/vite/plugins/expose-internal-ids.ts +544 -317
  275. package/src/vite/plugins/performance-tracks.ts +92 -0
  276. package/src/vite/plugins/refresh-cmd.ts +127 -0
  277. package/src/vite/plugins/use-cache-transform.ts +65 -50
  278. package/src/vite/plugins/version-injector.ts +39 -23
  279. package/src/vite/plugins/version-plugin.ts +72 -3
  280. package/src/vite/plugins/virtual-entries.ts +2 -2
  281. package/src/vite/rango.ts +265 -226
  282. package/src/vite/router-discovery.ts +924 -137
  283. package/src/vite/utils/ast-handler-extract.ts +15 -15
  284. package/src/vite/utils/banner.ts +4 -4
  285. package/src/vite/utils/bundle-analysis.ts +4 -2
  286. package/src/vite/utils/client-chunks.ts +190 -0
  287. package/src/vite/utils/forward-user-plugins.ts +193 -0
  288. package/src/vite/utils/manifest-utils.ts +21 -5
  289. package/src/vite/utils/package-resolution.ts +41 -1
  290. package/src/vite/utils/prerender-utils.ts +98 -5
  291. package/src/vite/utils/shared-utils.ts +109 -27
  292. package/src/browser/action-response-classifier.ts +0 -99
  293. package/src/route-definition/route-function.ts +0 -119
@@ -20,17 +20,33 @@ import type {
20
20
  DefaultRouteName,
21
21
  } from "../types/global-namespace.js";
22
22
  import type { Handle } from "../handle.js";
23
- import { type ContextVar, contextGet, contextSet } from "../context-var.js";
24
- import { createHandleStore, type HandleStore } from "./handle-store.js";
23
+ import {
24
+ type ContextVar,
25
+ contextGet,
26
+ contextSet,
27
+ isNonCacheable,
28
+ } from "../context-var.js";
29
+ import {
30
+ createHandleStore,
31
+ buildHandleSnapshot,
32
+ type HandleStore,
33
+ type HandleData,
34
+ } from "./handle-store.js";
25
35
  import { isHandle } from "../handle.js";
26
- import { track } from "./context.js";
36
+ import { track, type MetricsStore } from "./context.js";
27
37
  import { getFetchableLoader } from "./fetchable-loader-store.js";
28
38
  import type { SegmentCacheStore } from "../cache/types.js";
29
39
  import type { Theme, ResolvedThemeConfig } from "../theme/types.js";
40
+ import type { ExecutionContext, RequestScope } from "../types/request-scope.js";
41
+ import { fireAndForgetWaitUntil } from "../types/request-scope.js";
30
42
  import { THEME_COOKIE } from "../theme/constants.js";
31
43
  import type { LocationStateEntry } from "../browser/react/location-state-shared.js";
32
44
  import { NOCACHE_SYMBOL, assertNotInsideCacheExec } from "../cache/taint.js";
33
- import { createReverseFunction } from "../router/handler-context.js";
45
+ import { isInsideCacheScope } from "./context.js";
46
+ import {
47
+ createReverseFunction,
48
+ stripInternalParams,
49
+ } from "../router/handler-context.js";
34
50
  import { getGlobalRouteMap, isRouteRootScoped } from "../route-map-builder.js";
35
51
  import { invariant } from "../errors.js";
36
52
  import { isAutoGeneratedRouteName } from "../route-name.js";
@@ -44,19 +60,9 @@ import { isAutoGeneratedRouteName } from "../route-name.js";
44
60
  export interface RequestContext<
45
61
  TEnv = DefaultEnv,
46
62
  TParams = Record<string, string>,
47
- > {
48
- /** Platform bindings (Cloudflare env, etc.) */
49
- env: TEnv;
50
- /** Original HTTP request */
51
- request: Request;
52
- /** Parsed URL (system params like _rsc* are NOT filtered here) */
53
- url: URL;
54
- /** URL pathname */
55
- pathname: string;
56
- /** URL search params (system params like _rsc* are NOT filtered here) */
57
- searchParams: URLSearchParams;
58
- /** Variables set by middleware (same as ctx.var) */
59
- var: Record<string, any>;
63
+ > extends RequestScope<TEnv> {
64
+ /** @internal Shared variable backing store for ctx.get()/ctx.set(). */
65
+ _variables: Record<string, any>;
60
66
  /** Get a variable set by middleware */
61
67
  get: {
62
68
  <T>(contextVar: ContextVar<T>): T | undefined;
@@ -64,20 +70,19 @@ export interface RequestContext<
64
70
  };
65
71
  /** Set a variable (shared with middleware and handlers) */
66
72
  set: {
67
- <T>(contextVar: ContextVar<T>, value: T): void;
68
- <K extends string>(key: K, value: any): void;
73
+ <T>(
74
+ contextVar: ContextVar<T>,
75
+ value: T,
76
+ options?: { cache?: boolean },
77
+ ): void;
78
+ <K extends string>(key: K, value: any, options?: { cache?: boolean }): void;
69
79
  };
70
80
  /**
71
81
  * Route params (populated after route matching)
72
82
  * Initially empty, then set to matched params
73
83
  */
74
84
  params: TParams;
75
- /**
76
- * Stub response for setting headers/cookies (read-only).
77
- * Headers set here are merged into the final response.
78
- * Use header() or setStatus() to mutate response headers/status.
79
- * Use cookies().set()/cookies().delete() for cookie mutations.
80
- */
85
+ /** @internal Stub response for collecting headers/cookies. Use ctx.headers or ctx.header() instead. */
81
86
  readonly res: Response;
82
87
 
83
88
  /** @internal Get a cookie value (effective: request + response mutations). Use cookies().get() instead. */
@@ -95,6 +100,8 @@ export interface RequestContext<
95
100
  header(name: string, value: string): void;
96
101
  /** Set the response status code */
97
102
  setStatus(status: number): void;
103
+ /** @internal Set status bypassing cache-exec guard (for framework error handling) */
104
+ _setStatus(status: number): void;
98
105
 
99
106
  /**
100
107
  * Access loader data or push handle data.
@@ -139,20 +146,6 @@ export interface RequestContext<
139
146
  import("../cache/profile-registry.js").CacheProfile
140
147
  >;
141
148
 
142
- /**
143
- * Schedule work to run after the response is sent.
144
- * On Cloudflare Workers, uses ctx.waitUntil().
145
- * On Node.js, runs as fire-and-forget.
146
- *
147
- * @example
148
- * ```typescript
149
- * ctx.waitUntil(async () => {
150
- * await cacheStore.set(key, data, ttl);
151
- * });
152
- * ```
153
- */
154
- waitUntil(fn: () => Promise<void>): void;
155
-
156
149
  /**
157
150
  * Register a callback to run when the response is created.
158
151
  * Callbacks are sync and receive the response. They can:
@@ -256,6 +249,54 @@ export interface RequestContext<
256
249
  /** @internal Previous route key (from the navigation source), used for revalidation */
257
250
  _prevRouteKey?: string;
258
251
 
252
+ /**
253
+ * @internal Render barrier for experimental `rendered()` API.
254
+ * Resolves when all non-loader segments have settled and handle data
255
+ * is available. Used by DSL loaders that call `ctx.rendered()`.
256
+ */
257
+ _renderBarrier: Promise<void>;
258
+
259
+ /**
260
+ * @internal Resolve the render barrier. Accepts resolved segments, filters
261
+ * out loaders, and captures non-loader segment IDs as the handle ordering.
262
+ * Called after segment resolution (fresh) or handle replay (cache/prerender).
263
+ */
264
+ _resolveRenderBarrier: (
265
+ segments: Array<{ type: string; id: string }>,
266
+ ) => void;
267
+
268
+ /**
269
+ * @internal Segment order at barrier resolution time, used by loader
270
+ * ctx.use(handle) to collect handle data in correct order.
271
+ */
272
+ _renderBarrierSegmentOrder?: string[];
273
+
274
+ /**
275
+ * @internal Set to true when the matched entry tree contains any `loading()`
276
+ * entries (streaming). Used by rendered() to fail fast.
277
+ */
278
+ _treeHasStreaming?: boolean;
279
+
280
+ /**
281
+ * @internal Loader IDs that have called rendered() and are waiting for the
282
+ * barrier. Used to detect deadlocks when a handler tries to await the same
283
+ * loader via ctx.use(Loader).
284
+ */
285
+ _renderBarrierWaiters?: Set<string>;
286
+
287
+ /**
288
+ * @internal Loader IDs that handlers have started awaiting via ctx.use().
289
+ * Used for bidirectional deadlock detection: if a loader later calls
290
+ * rendered() and a handler already awaits it, we can detect the deadlock.
291
+ */
292
+ _handlerLoaderDeps?: Set<string>;
293
+
294
+ /**
295
+ * @internal Cached HandleData snapshot built at barrier resolution time.
296
+ * Avoids rebuilding the snapshot on every loader ctx.use(handle) call.
297
+ */
298
+ _renderBarrierHandleSnapshot?: HandleData;
299
+
259
300
  /** @internal Per-request error dedup set for onError reporting */
260
301
  _reportedErrors: WeakSet<object>;
261
302
 
@@ -266,6 +307,30 @@ export interface RequestContext<
266
307
  * errors without failing the response.
267
308
  */
268
309
  _reportBackgroundError?: (error: unknown, category: string) => void;
310
+
311
+ /** @internal Per-request debug performance override (set via ctx.debugPerformance()) */
312
+ _debugPerformance?: boolean;
313
+
314
+ /** @internal Request-scoped performance metrics store */
315
+ _metricsStore?: MetricsStore;
316
+
317
+ /** @internal Router basename for this request (used by redirect()) */
318
+ _basename?: string;
319
+
320
+ /**
321
+ * @internal RouteSnapshot from classifyRequest, reused by match/matchPartial
322
+ * to avoid a second resolveRoute call. Cleared on HMR invalidation.
323
+ */
324
+ _classifiedRoute?: import("../router/route-snapshot.js").RouteSnapshot;
325
+
326
+ /**
327
+ * @internal Coarse route-level cache signal for the X-Rango-Cache debug
328
+ * header. Populated by match/matchPartial only when the debug cache signal
329
+ * gate is enabled (debugCacheSignal option or RANGO_TEST_SIGNALS=1). Read by
330
+ * the response-finalization path (createResponseWithMergedHeaders). Undefined
331
+ * when the gate is off, so no header is emitted.
332
+ */
333
+ _cacheSignal?: import("../router/telemetry.js").CacheSegmentSignal[];
269
334
  }
270
335
 
271
336
  /**
@@ -292,7 +357,22 @@ export type PublicRequestContext<
292
357
  | "_routeName"
293
358
  | "_prevRouteKey"
294
359
  | "_reportedErrors"
360
+ | "_renderBarrier"
361
+ | "_resolveRenderBarrier"
362
+ | "_renderBarrierSegmentOrder"
363
+ | "_treeHasStreaming"
364
+ | "_renderBarrierWaiters"
365
+ | "_handlerLoaderDeps"
366
+ | "_renderBarrierHandleSnapshot"
295
367
  | "_reportBackgroundError"
368
+ | "_debugPerformance"
369
+ | "_metricsStore"
370
+ | "_basename"
371
+ | "_setStatus"
372
+ | "_variables"
373
+ | "_classifiedRoute"
374
+ | "_cacheSignal"
375
+ | "res"
296
376
  >;
297
377
 
298
378
  // AsyncLocalStorage instance for request context
@@ -401,13 +481,7 @@ export function requireRequestContext<
401
481
  return getRequestContext<TEnv>();
402
482
  }
403
483
 
404
- /**
405
- * Cloudflare Workers ExecutionContext (subset we need)
406
- */
407
- export interface ExecutionContext {
408
- waitUntil(promise: Promise<any>): void;
409
- passThroughOnException(): void;
410
- }
484
+ export type { ExecutionContext };
411
485
 
412
486
  /**
413
487
  * Options for creating a request context
@@ -491,6 +565,18 @@ export function createRequestContext<TEnv>(
491
565
  responseCookieCache = null;
492
566
  };
493
567
 
568
+ // Guard: throw if a response-level side effect is called inside a cache() scope.
569
+ // Uses ALS to detect the scope (set during segment resolution).
570
+ function assertNotInsideCacheScopeALS(methodName: string): void {
571
+ if (isInsideCacheScope()) {
572
+ throw new Error(
573
+ `ctx.${methodName}() cannot be called inside a cache() boundary. ` +
574
+ `On cache hit the handler is skipped, so this side effect would be lost. ` +
575
+ `Move ctx.${methodName}() to a middleware or layout outside the cache() scope.`,
576
+ );
577
+ }
578
+ }
579
+
494
580
  // Effective cookie read: response stub Set-Cookie wins, then original header.
495
581
  // The stub IS the source of truth for same-request mutations.
496
582
  const effectiveCookie = (name: string): string | undefined => {
@@ -543,19 +629,31 @@ export function createRequestContext<TEnv>(
543
629
  invalidateResponseCookieCache();
544
630
  };
545
631
 
632
+ // Strip internal _rsc* params so userland sees a clean URL.
633
+ const cleanUrl = stripInternalParams(url);
634
+
546
635
  // Build the context object first (without use), then add use
547
636
  const ctx: RequestContext<TEnv> = {
548
637
  env,
549
638
  request,
550
- url,
639
+ url: cleanUrl,
640
+ originalUrl: new URL(request.url),
551
641
  pathname: url.pathname,
552
- searchParams: url.searchParams,
553
- var: variables,
554
- get: ((keyOrVar: any) =>
555
- contextGet(variables, keyOrVar)) as RequestContext<TEnv>["get"],
556
- set: ((keyOrVar: any, value: any) => {
642
+ searchParams: cleanUrl.searchParams,
643
+ _variables: variables,
644
+ get: ((keyOrVar: any) => {
645
+ if (isNonCacheable(variables, keyOrVar) && isInsideCacheScope()) {
646
+ throw new Error(
647
+ `ctx.get() for a non-cacheable variable cannot be called inside a cache() boundary. ` +
648
+ `The variable was created with { cache: false } or set with { cache: false }, ` +
649
+ `and its value would be stale on cache hit. Move the read outside the cached scope.`,
650
+ );
651
+ }
652
+ return contextGet(variables, keyOrVar);
653
+ }) as RequestContext<TEnv>["get"],
654
+ set: ((keyOrVar: any, value: any, options?: any) => {
557
655
  assertNotInsideCacheExec(ctx, "set");
558
- contextSet(variables, keyOrVar, value);
656
+ contextSet(variables, keyOrVar, value, options);
559
657
  }) as RequestContext<TEnv>["set"],
560
658
  params: {} as Record<string, string>,
561
659
 
@@ -593,6 +691,7 @@ export function createRequestContext<TEnv>(
593
691
 
594
692
  setCookie(name: string, value: string, options?: CookieOptions): void {
595
693
  assertNotInsideCacheExec(ctx, "setCookie");
694
+ assertNotInsideCacheScopeALS("setCookie");
596
695
  stubResponse.headers.append(
597
696
  "Set-Cookie",
598
697
  serializeCookieValue(name, value, options),
@@ -605,6 +704,7 @@ export function createRequestContext<TEnv>(
605
704
  options?: Pick<CookieOptions, "domain" | "path">,
606
705
  ): void {
607
706
  assertNotInsideCacheExec(ctx, "deleteCookie");
707
+ assertNotInsideCacheScopeALS("deleteCookie");
608
708
  stubResponse.headers.append(
609
709
  "Set-Cookie",
610
710
  serializeCookieValue(name, "", { ...options, maxAge: 0 }),
@@ -614,13 +714,20 @@ export function createRequestContext<TEnv>(
614
714
 
615
715
  header(name: string, value: string): void {
616
716
  assertNotInsideCacheExec(ctx, "header");
717
+ assertNotInsideCacheScopeALS("header");
617
718
  stubResponse.headers.set(name, value);
618
719
  },
619
720
 
620
721
  setStatus(status: number): void {
621
722
  assertNotInsideCacheExec(ctx, "setStatus");
622
- // Response.status is read-only, so we must create a new Response.
623
- // Headers are passed by reference — no cookie cache invalidation needed.
723
+ assertNotInsideCacheScopeALS("setStatus");
724
+ stubResponse = new Response(null, {
725
+ status,
726
+ headers: stubResponse.headers,
727
+ });
728
+ },
729
+
730
+ _setStatus(status: number): void {
624
731
  stubResponse = new Response(null, {
625
732
  status,
626
733
  headers: stubResponse.headers,
@@ -638,20 +745,19 @@ export function createRequestContext<TEnv>(
638
745
 
639
746
  waitUntil(fn: () => Promise<void>): void {
640
747
  if (executionContext?.waitUntil) {
641
- // Cloudflare Workers: use native waitUntil
642
748
  executionContext.waitUntil(fn());
643
749
  } else {
644
- // Node.js / dev: fire-and-forget with error logging
645
- fn().catch((err) =>
646
- console.error("[waitUntil] Background task failed:", err),
647
- );
750
+ fireAndForgetWaitUntil(fn);
648
751
  }
649
752
  },
650
753
 
754
+ executionContext,
755
+
651
756
  _onResponseCallbacks: [],
652
757
 
653
758
  onResponse(callback: (response: Response) => Response): void {
654
759
  assertNotInsideCacheExec(ctx, "onResponse");
760
+ assertNotInsideCacheScopeALS("onResponse");
655
761
  this._onResponseCallbacks.push(callback);
656
762
  },
657
763
 
@@ -677,10 +783,60 @@ export function createRequestContext<TEnv>(
677
783
  _locationState: undefined,
678
784
 
679
785
  _reportedErrors: new WeakSet<object>(),
786
+ _metricsStore: undefined,
787
+
788
+ // Render barrier: deferred promise resolved after non-loader segments settle.
789
+ _renderBarrier: null as any, // set below
790
+ _resolveRenderBarrier: null as any, // set below
791
+ _renderBarrierSegmentOrder: undefined,
680
792
 
681
793
  reverse: createReverseFunction(getGlobalRouteMap(), undefined, {}),
682
794
  };
683
795
 
796
+ // Lazy render barrier: only allocate the Promise when a loader actually
797
+ // calls rendered(). Requests that don't use rendered() pay zero cost.
798
+ let barrierResolved = false;
799
+ let resolveBarrier: (() => void) | undefined;
800
+ ctx._renderBarrier = null as any; // lazy — created on first access
801
+ ctx._resolveRenderBarrier = (
802
+ segments: Array<{ type: string; id: string }>,
803
+ ) => {
804
+ if (barrierResolved) return;
805
+ barrierResolved = true;
806
+ const segOrder = segments
807
+ .filter((s) => s.type !== "loader")
808
+ .map((s) => s.id);
809
+ ctx._renderBarrierSegmentOrder = segOrder;
810
+ // Build and cache handle snapshot so loader ctx.use(handle) calls
811
+ // don't rebuild it on every invocation.
812
+ ctx._renderBarrierHandleSnapshot = buildHandleSnapshot(
813
+ handleStore,
814
+ segOrder,
815
+ );
816
+ ctx._renderBarrierWaiters = undefined;
817
+ ctx._handlerLoaderDeps = undefined;
818
+ if (resolveBarrier) resolveBarrier();
819
+ };
820
+ Object.defineProperty(ctx, "_renderBarrier", {
821
+ get() {
822
+ // Barrier already resolved (cache/prerender hit) or first lazy access.
823
+ // Either way, replace the getter with a concrete value to avoid
824
+ // repeated Promise.resolve() allocations on subsequent reads.
825
+ const p = barrierResolved
826
+ ? Promise.resolve()
827
+ : new Promise<void>((resolve) => {
828
+ resolveBarrier = resolve;
829
+ });
830
+ Object.defineProperty(ctx, "_renderBarrier", {
831
+ value: p,
832
+ writable: false,
833
+ configurable: false,
834
+ });
835
+ return p;
836
+ },
837
+ configurable: true,
838
+ });
839
+
684
840
  // Now create use() with access to ctx
685
841
  ctx.use = createUseFunction({
686
842
  handleStore,
@@ -862,15 +1018,17 @@ export function createUseFunction<TEnv>(
862
1018
  search: (ctx as any).search ?? {},
863
1019
  pathname: ctx.pathname,
864
1020
  url: ctx.url,
1021
+ originalUrl: ctx.originalUrl,
865
1022
  env: ctx.env as any,
866
- var: ctx.var as any,
1023
+ waitUntil: ctx.waitUntil.bind(ctx),
1024
+ executionContext: ctx.executionContext,
867
1025
  get: ctx.get as any,
868
- use: <TDep, TDepParams = any>(
1026
+ use: (<TDep, TDepParams = any>(
869
1027
  dep: LoaderDefinition<TDep, TDepParams>,
870
1028
  ): Promise<TDep> => {
871
1029
  // Recursive call - will start dep loader if not already started
872
1030
  return ctx.use(dep);
873
- },
1031
+ }) as LoaderContext["use"],
874
1032
  method: "GET",
875
1033
  body: undefined,
876
1034
  reverse: createReverseFunction(
@@ -879,10 +1037,15 @@ export function createUseFunction<TEnv>(
879
1037
  ctx.params as Record<string, string>,
880
1038
  ctx._routeName ? isRouteRootScoped(ctx._routeName) : undefined,
881
1039
  ),
1040
+ rendered: () => {
1041
+ throw new Error(
1042
+ `ctx.rendered() is only available in DSL loaders (registered via loader() in urls()). ` +
1043
+ `It cannot be used from request-context loaders or server actions.`,
1044
+ );
1045
+ },
882
1046
  };
883
1047
 
884
- // Start loader execution with tracking
885
- const doneLoader = track(`loader:${loader.$$id}`);
1048
+ const doneLoader = track(`loader:${loader.$$id}`, 2);
886
1049
  const promise = Promise.resolve(loaderFn(loaderCtx)).finally(() => {
887
1050
  doneLoader();
888
1051
  });
package/src/server.ts CHANGED
@@ -11,6 +11,12 @@
11
11
  // Router registry (used by Vite plugin for build-time discovery)
12
12
  export { RSC_ROUTER_BRAND, RouterRegistry } from "./router.js";
13
13
 
14
+ // Host router registry (used by Vite plugin for host-router lazy discovery)
15
+ export {
16
+ HostRouterRegistry,
17
+ type HostRouterRegistryEntry,
18
+ } from "./host/router.js";
19
+
14
20
  // Route map builder (Vite plugin injects these via virtual modules)
15
21
  export {
16
22
  registerRouteMap,
package/src/ssr/index.tsx CHANGED
@@ -129,6 +129,7 @@ interface RscPayload {
129
129
  matched?: string[];
130
130
  pathname?: string;
131
131
  params?: Record<string, string>;
132
+ basename?: string;
132
133
  themeConfig?: ResolvedThemeConfig | null;
133
134
  initialTheme?: Theme;
134
135
  version?: string;
@@ -161,13 +162,18 @@ function createSsrEventController(opts: {
161
162
  }): EventController {
162
163
  const location = new URL(opts.pathname, "http://localhost");
163
164
  let params = opts.params ?? {};
165
+ const rawMatched = opts.matched ?? [];
164
166
  const handleState = {
165
167
  data: opts.handleData ?? {},
166
- segmentOrder: filterSegmentOrder(opts.matched ?? []),
168
+ segmentOrder: filterSegmentOrder(rawMatched),
169
+ routeSegmentIds: rawMatched.filter(
170
+ (id) => !id.includes(".@") && !/D\d+\./.test(id),
171
+ ),
167
172
  };
168
173
  const state: DerivedNavigationState = {
169
174
  state: "idle",
170
175
  isStreaming: false,
176
+ isNavigating: false,
171
177
  location,
172
178
  pendingUrl: null,
173
179
  inflightActions: [],
@@ -260,6 +266,7 @@ export function createSSRHandler<TEnv = unknown>(deps: SSRDependencies<TEnv>) {
260
266
  function SsrRoot() {
261
267
  payload ??= createFromReadableStream<RscPayload>(rscStream1);
262
268
  const resolved = React.use(payload);
269
+
263
270
  const themeConfig = resolved.metadata?.themeConfig ?? null;
264
271
  const pathname = resolved.metadata?.pathname ?? "/";
265
272
 
@@ -285,6 +292,7 @@ export function createSSRHandler<TEnv = unknown>(deps: SSRDependencies<TEnv>) {
285
292
  navigate: async () => {},
286
293
  refresh: async () => {},
287
294
  version: resolved.metadata?.version,
295
+ basename: resolved.metadata?.basename,
288
296
  };
289
297
 
290
298
  // Build content tree from segments.
@@ -32,11 +32,21 @@
32
32
  */
33
33
  import type { ReactNode } from "react";
34
34
  import type { Handler } from "./types.js";
35
- import type { PrerenderOptions, StaticBuildContext } from "./prerender.js";
35
+ import type { StaticBuildContext } from "./prerender.js";
36
+ import type { UseItems, HandlerUseItem } from "./route-types.js";
36
37
  import { isCachedFunction } from "./cache/taint.js";
37
38
 
38
39
  // -- Types ------------------------------------------------------------------
39
40
 
41
+ export interface StaticHandlerOptions {
42
+ /**
43
+ * Keep handler in server bundle for live fallback (default: false).
44
+ * false: handler replaced with stub, source-only APIs excluded from bundle.
45
+ * true: handler stays in bundle, renders live at request time.
46
+ */
47
+ passthrough?: boolean;
48
+ }
49
+
40
50
  export interface StaticHandlerDefinition<
41
51
  TParams extends Record<string, any> = any,
42
52
  > {
@@ -46,14 +56,16 @@ export interface StaticHandlerDefinition<
46
56
  /** In dev mode, the actual handler function that layout/path/parallel can call. */
47
57
  handler: Handler<TParams>;
48
58
  /** Static handler options (passthrough support). */
49
- options?: PrerenderOptions;
59
+ options?: StaticHandlerOptions;
60
+ /** Composable default DSL items merged when the handler is mounted. */
61
+ use?: () => UseItems<HandlerUseItem>;
50
62
  }
51
63
 
52
64
  // -- Function ---------------------------------------------------------------
53
65
 
54
66
  export function Static<TParams extends Record<string, any> = {}>(
55
67
  handler: (ctx: StaticBuildContext) => ReactNode | Promise<ReactNode>,
56
- options?: PrerenderOptions,
68
+ options?: StaticHandlerOptions,
57
69
  __injectedId?: string,
58
70
  ): StaticHandlerDefinition<TParams>;
59
71
 
@@ -61,7 +73,7 @@ export function Static<TParams extends Record<string, any> = {}>(
61
73
 
62
74
  export function Static<TParams extends Record<string, any>>(
63
75
  handler: Function,
64
- optionsOrId?: PrerenderOptions | string,
76
+ optionsOrId?: StaticHandlerOptions | string,
65
77
  maybeId?: string,
66
78
  ): StaticHandlerDefinition<TParams> {
67
79
  if (isCachedFunction(handler)) {
@@ -72,19 +84,19 @@ export function Static<TParams extends Record<string, any>>(
72
84
  );
73
85
  }
74
86
 
75
- let options: PrerenderOptions | undefined;
87
+ let options: StaticHandlerOptions | undefined;
76
88
  let id: string;
77
89
 
78
90
  if (typeof optionsOrId === "string") {
79
91
  id = optionsOrId;
80
92
  } else {
81
- options = optionsOrId as PrerenderOptions | undefined;
93
+ options = optionsOrId as StaticHandlerOptions | undefined;
82
94
  id = maybeId ?? "";
83
95
  }
84
96
 
85
97
  if (!id) {
86
98
  throw new Error(
87
- "[rsc-router] Static: missing $$id. " +
99
+ "[rango] Static: missing $$id. " +
88
100
  "Ensure the exposeInternalIds Vite plugin is configured.",
89
101
  );
90
102
  }