@rangojs/router 0.0.0-experimental.b9cb8739 → 0.0.0-experimental.bd6e11bc

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 (285) hide show
  1. package/README.md +196 -43
  2. package/dist/bin/rango.js +277 -99
  3. package/dist/testing/vitest.js +48 -0
  4. package/dist/vite/index.js +2779 -1064
  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 +57 -11
  8. package/skills/breadcrumbs/SKILL.md +3 -1
  9. package/skills/bundle-analysis/SKILL.md +159 -0
  10. package/skills/cache-guide/SKILL.md +243 -21
  11. package/skills/caching/SKILL.md +155 -6
  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 +229 -20
  16. package/skills/host-router/SKILL.md +45 -20
  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 +273 -53
  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 +123 -100
  29. package/skills/rango/SKILL.md +242 -22
  30. package/skills/react-compiler/SKILL.md +168 -0
  31. package/skills/response-routes/SKILL.md +66 -9
  32. package/skills/route/SKILL.md +88 -4
  33. package/skills/router-setup/SKILL.md +90 -5
  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 +716 -0
  37. package/skills/typesafety/SKILL.md +329 -27
  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 +1 -1
  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/navigation-bridge.ts +102 -16
  50. package/src/browser/navigation-client.ts +164 -59
  51. package/src/browser/navigation-store.ts +75 -17
  52. package/src/browser/navigation-transaction.ts +21 -37
  53. package/src/browser/partial-update.ts +139 -38
  54. package/src/browser/prefetch/cache.ts +175 -15
  55. package/src/browser/prefetch/fetch.ts +180 -33
  56. package/src/browser/prefetch/queue.ts +123 -20
  57. package/src/browser/prefetch/resource-ready.ts +77 -0
  58. package/src/browser/rango-state.ts +53 -13
  59. package/src/browser/react/Link.tsx +81 -9
  60. package/src/browser/react/NavigationProvider.tsx +110 -33
  61. package/src/browser/react/context.ts +7 -2
  62. package/src/browser/react/filter-segment-order.ts +51 -7
  63. package/src/browser/react/index.ts +3 -0
  64. package/src/browser/react/location-state-shared.ts +175 -4
  65. package/src/browser/react/location-state.ts +39 -13
  66. package/src/browser/react/use-handle.ts +23 -64
  67. package/src/browser/react/use-navigation.ts +22 -2
  68. package/src/browser/react/use-params.ts +20 -8
  69. package/src/browser/react/use-reverse.ts +106 -0
  70. package/src/browser/react/use-router.ts +43 -10
  71. package/src/browser/react/use-segments.ts +11 -8
  72. package/src/browser/response-adapter.ts +25 -0
  73. package/src/browser/rsc-router.tsx +191 -74
  74. package/src/browser/scroll-restoration.ts +41 -14
  75. package/src/browser/segment-reconciler.ts +36 -9
  76. package/src/browser/segment-structure-assert.ts +2 -2
  77. package/src/browser/server-action-bridge.ts +31 -36
  78. package/src/browser/types.ts +57 -5
  79. package/src/build/collect-fallback-refs.ts +107 -0
  80. package/src/build/generate-manifest.ts +65 -40
  81. package/src/build/generate-route-types.ts +5 -0
  82. package/src/build/index.ts +2 -0
  83. package/src/build/route-trie.ts +52 -25
  84. package/src/build/route-types/codegen.ts +4 -4
  85. package/src/build/route-types/include-resolution.ts +9 -2
  86. package/src/build/route-types/per-module-writer.ts +7 -4
  87. package/src/build/route-types/router-processing.ts +278 -88
  88. package/src/build/route-types/scan-filter.ts +9 -2
  89. package/src/build/route-types/source-scan.ts +118 -0
  90. package/src/build/runtime-discovery.ts +9 -20
  91. package/src/cache/cache-runtime.ts +15 -11
  92. package/src/cache/cache-scope.ts +76 -49
  93. package/src/cache/cf/cf-cache-store.ts +501 -18
  94. package/src/cache/cf/index.ts +5 -1
  95. package/src/cache/document-cache.ts +17 -7
  96. package/src/cache/index.ts +1 -0
  97. package/src/cache/taint.ts +55 -0
  98. package/src/client.rsc.tsx +3 -0
  99. package/src/client.tsx +94 -238
  100. package/src/context-var.ts +72 -2
  101. package/src/debug.ts +2 -2
  102. package/src/decode-loader-results.ts +36 -0
  103. package/src/errors.ts +30 -1
  104. package/src/handle.ts +65 -12
  105. package/src/host/index.ts +2 -2
  106. package/src/host/router.ts +129 -57
  107. package/src/host/types.ts +31 -2
  108. package/src/host/utils.ts +1 -1
  109. package/src/href-client.ts +140 -20
  110. package/src/index.rsc.ts +12 -5
  111. package/src/index.ts +61 -11
  112. package/src/loader-store.ts +500 -0
  113. package/src/loader.rsc.ts +2 -5
  114. package/src/loader.ts +3 -10
  115. package/src/missing-id-error.ts +68 -0
  116. package/src/outlet-context.ts +1 -1
  117. package/src/prerender/store.ts +5 -4
  118. package/src/prerender.ts +141 -80
  119. package/src/response-utils.ts +37 -0
  120. package/src/reverse.ts +65 -15
  121. package/src/route-content-wrapper.tsx +6 -28
  122. package/src/route-definition/dsl-helpers.ts +435 -260
  123. package/src/route-definition/helper-factories.ts +29 -139
  124. package/src/route-definition/helpers-types.ts +110 -34
  125. package/src/route-definition/index.ts +3 -0
  126. package/src/route-definition/redirect.ts +11 -3
  127. package/src/route-definition/resolve-handler-use.ts +155 -0
  128. package/src/route-definition/use-item-types.ts +32 -0
  129. package/src/route-map-builder.ts +7 -1
  130. package/src/route-types.ts +37 -41
  131. package/src/router/basename.ts +14 -0
  132. package/src/router/content-negotiation.ts +113 -1
  133. package/src/router/error-handling.ts +1 -1
  134. package/src/router/find-match.ts +4 -2
  135. package/src/router/handler-context.ts +77 -38
  136. package/src/router/intercept-resolution.ts +15 -22
  137. package/src/router/lazy-includes.ts +12 -9
  138. package/src/router/loader-resolution.ts +174 -22
  139. package/src/router/logging.ts +5 -2
  140. package/src/router/manifest.ts +31 -16
  141. package/src/router/match-api.ts +128 -192
  142. package/src/router/match-handlers.ts +63 -20
  143. package/src/router/match-middleware/background-revalidation.ts +30 -2
  144. package/src/router/match-middleware/cache-lookup.ts +136 -106
  145. package/src/router/match-middleware/cache-store.ts +54 -10
  146. package/src/router/match-middleware/intercept-resolution.ts +9 -7
  147. package/src/router/match-middleware/segment-resolution.ts +61 -5
  148. package/src/router/match-result.ts +125 -10
  149. package/src/router/metrics.ts +7 -2
  150. package/src/router/middleware-types.ts +21 -34
  151. package/src/router/middleware.ts +103 -90
  152. package/src/router/navigation-snapshot.ts +182 -0
  153. package/src/router/pattern-matching.ts +101 -17
  154. package/src/router/prerender-match.ts +110 -10
  155. package/src/router/preview-match.ts +32 -102
  156. package/src/router/request-classification.ts +286 -0
  157. package/src/router/revalidation.ts +58 -2
  158. package/src/router/route-snapshot.ts +245 -0
  159. package/src/router/router-context.ts +6 -1
  160. package/src/router/router-interfaces.ts +77 -28
  161. package/src/router/router-options.ts +76 -11
  162. package/src/router/router-registry.ts +2 -5
  163. package/src/router/segment-resolution/fresh.ts +223 -24
  164. package/src/router/segment-resolution/helpers.ts +29 -24
  165. package/src/router/segment-resolution/loader-cache.ts +1 -0
  166. package/src/router/segment-resolution/revalidation.ts +466 -285
  167. package/src/router/segment-resolution/view-transition-default.ts +36 -0
  168. package/src/router/segment-wrappers.ts +2 -0
  169. package/src/router/substitute-pattern-params.ts +56 -0
  170. package/src/router/telemetry.ts +99 -0
  171. package/src/router/trie-matching.ts +18 -13
  172. package/src/router/types.ts +9 -0
  173. package/src/router/url-params.ts +49 -0
  174. package/src/router.ts +91 -23
  175. package/src/rsc/handler-context.ts +2 -2
  176. package/src/rsc/handler.ts +440 -381
  177. package/src/rsc/helpers.ts +91 -43
  178. package/src/rsc/index.ts +1 -1
  179. package/src/rsc/loader-fetch.ts +23 -3
  180. package/src/rsc/manifest-init.ts +5 -1
  181. package/src/rsc/origin-guard.ts +28 -10
  182. package/src/rsc/progressive-enhancement.ts +18 -2
  183. package/src/rsc/response-route-handler.ts +46 -53
  184. package/src/rsc/rsc-rendering.ts +41 -48
  185. package/src/rsc/runtime-warnings.ts +9 -10
  186. package/src/rsc/server-action.ts +25 -37
  187. package/src/rsc/ssr-setup.ts +18 -2
  188. package/src/rsc/types.ts +17 -3
  189. package/src/search-params.ts +4 -4
  190. package/src/segment-content-promise.ts +67 -0
  191. package/src/segment-loader-promise.ts +122 -0
  192. package/src/segment-system.tsx +219 -67
  193. package/src/serialize.ts +243 -0
  194. package/src/server/context.ts +277 -61
  195. package/src/server/cookie-store.ts +28 -4
  196. package/src/server/handle-store.ts +19 -0
  197. package/src/server/loader-registry.ts +9 -8
  198. package/src/server/request-context.ts +204 -60
  199. package/src/ssr/index.tsx +9 -1
  200. package/src/static-handler.ts +19 -7
  201. package/src/testing/cache-status.ts +166 -0
  202. package/src/testing/collect-handle.ts +63 -0
  203. package/src/testing/dispatch.ts +440 -0
  204. package/src/testing/dom.entry.ts +22 -0
  205. package/src/testing/e2e/fixture.ts +154 -0
  206. package/src/testing/e2e/index.ts +149 -0
  207. package/src/testing/e2e/matchers.ts +51 -0
  208. package/src/testing/e2e/page-helpers.ts +272 -0
  209. package/src/testing/e2e/parity.ts +306 -0
  210. package/src/testing/e2e/server.ts +183 -0
  211. package/src/testing/flight-matchers.ts +104 -0
  212. package/src/testing/flight-runtime.d.ts +21 -0
  213. package/src/testing/flight.entry.ts +22 -0
  214. package/src/testing/flight.ts +182 -0
  215. package/src/testing/generated-routes.ts +223 -0
  216. package/src/testing/index.ts +106 -0
  217. package/src/testing/internal/context.ts +255 -0
  218. package/src/testing/render-route.tsx +565 -0
  219. package/src/testing/run-loader.ts +296 -0
  220. package/src/testing/run-middleware.ts +179 -0
  221. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  222. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  223. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  224. package/src/testing/vitest-stubs/version.ts +5 -0
  225. package/src/testing/vitest.ts +183 -0
  226. package/src/types/cache-types.ts +4 -4
  227. package/src/types/global-namespace.ts +39 -26
  228. package/src/types/handler-context.ts +194 -72
  229. package/src/types/index.ts +1 -0
  230. package/src/types/loader-types.ts +41 -15
  231. package/src/types/request-scope.ts +126 -0
  232. package/src/types/route-entry.ts +19 -1
  233. package/src/types/segments.ts +37 -1
  234. package/src/urls/include-helper.ts +34 -67
  235. package/src/urls/index.ts +0 -3
  236. package/src/urls/path-helper-types.ts +50 -9
  237. package/src/urls/path-helper.ts +63 -63
  238. package/src/urls/pattern-types.ts +48 -19
  239. package/src/urls/response-types.ts +25 -22
  240. package/src/urls/type-extraction.ts +26 -116
  241. package/src/urls/urls-function.ts +1 -5
  242. package/src/use-loader.tsx +487 -44
  243. package/src/vite/debug.ts +185 -0
  244. package/src/vite/discovery/bundle-postprocess.ts +34 -37
  245. package/src/vite/discovery/discover-routers.ts +105 -51
  246. package/src/vite/discovery/discovery-errors.ts +194 -0
  247. package/src/vite/discovery/gate-state.ts +171 -0
  248. package/src/vite/discovery/prerender-collection.ts +188 -93
  249. package/src/vite/discovery/route-types-writer.ts +40 -84
  250. package/src/vite/discovery/self-gen-tracking.ts +27 -1
  251. package/src/vite/discovery/state.ts +46 -6
  252. package/src/vite/discovery/virtual-module-codegen.ts +13 -23
  253. package/src/vite/index.ts +6 -0
  254. package/src/vite/plugin-types.ts +111 -72
  255. package/src/vite/plugins/cjs-to-esm.ts +8 -7
  256. package/src/vite/plugins/client-ref-dedup.ts +16 -0
  257. package/src/vite/plugins/client-ref-hashing.ts +28 -5
  258. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  259. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  260. package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
  261. package/src/vite/plugins/expose-action-id.ts +55 -33
  262. package/src/vite/plugins/expose-id-utils.ts +24 -8
  263. package/src/vite/plugins/expose-ids/export-analysis.ts +100 -20
  264. package/src/vite/plugins/expose-ids/handler-transform.ts +12 -35
  265. package/src/vite/plugins/expose-ids/loader-transform.ts +3 -5
  266. package/src/vite/plugins/expose-ids/router-transform.ts +20 -3
  267. package/src/vite/plugins/expose-internal-ids.ts +544 -317
  268. package/src/vite/plugins/performance-tracks.ts +92 -0
  269. package/src/vite/plugins/refresh-cmd.ts +88 -26
  270. package/src/vite/plugins/use-cache-transform.ts +65 -50
  271. package/src/vite/plugins/version-injector.ts +39 -23
  272. package/src/vite/plugins/version-plugin.ts +72 -3
  273. package/src/vite/plugins/virtual-entries.ts +2 -2
  274. package/src/vite/rango.ts +265 -226
  275. package/src/vite/router-discovery.ts +920 -137
  276. package/src/vite/utils/ast-handler-extract.ts +15 -15
  277. package/src/vite/utils/banner.ts +4 -4
  278. package/src/vite/utils/bundle-analysis.ts +4 -2
  279. package/src/vite/utils/client-chunks.ts +190 -0
  280. package/src/vite/utils/forward-user-plugins.ts +193 -0
  281. package/src/vite/utils/manifest-utils.ts +21 -5
  282. package/src/vite/utils/package-resolution.ts +41 -1
  283. package/src/vite/utils/prerender-utils.ts +38 -5
  284. package/src/vite/utils/shared-utils.ts +109 -27
  285. package/src/browser/action-response-classifier.ts +0 -99
@@ -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
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,24 +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 (with internal `_rsc*` params stripped) */
53
- url: URL;
54
- /**
55
- * The original request URL with all parameters intact, including
56
- * internal `_rsc*` transport params.
57
- */
58
- originalUrl: URL;
59
- /** URL pathname */
60
- pathname: string;
61
- /** URL search params (system params like _rsc* are NOT filtered here) */
62
- searchParams: URLSearchParams;
63
- /** Variables set by middleware (same as ctx.var) */
64
- var: Record<string, any>;
63
+ > extends RequestScope<TEnv> {
64
+ /** @internal Shared variable backing store for ctx.get()/ctx.set(). */
65
+ _variables: Record<string, any>;
65
66
  /** Get a variable set by middleware */
66
67
  get: {
67
68
  <T>(contextVar: ContextVar<T>): T | undefined;
@@ -69,8 +70,12 @@ export interface RequestContext<
69
70
  };
70
71
  /** Set a variable (shared with middleware and handlers) */
71
72
  set: {
72
- <T>(contextVar: ContextVar<T>, value: T): void;
73
- <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;
74
79
  };
75
80
  /**
76
81
  * Route params (populated after route matching)
@@ -141,20 +146,6 @@ export interface RequestContext<
141
146
  import("../cache/profile-registry.js").CacheProfile
142
147
  >;
143
148
 
144
- /**
145
- * Schedule work to run after the response is sent.
146
- * On Cloudflare Workers, uses ctx.waitUntil().
147
- * On Node.js, runs as fire-and-forget.
148
- *
149
- * @example
150
- * ```typescript
151
- * ctx.waitUntil(async () => {
152
- * await cacheStore.set(key, data, ttl);
153
- * });
154
- * ```
155
- */
156
- waitUntil(fn: () => Promise<void>): void;
157
-
158
149
  /**
159
150
  * Register a callback to run when the response is created.
160
151
  * Callbacks are sync and receive the response. They can:
@@ -258,6 +249,54 @@ export interface RequestContext<
258
249
  /** @internal Previous route key (from the navigation source), used for revalidation */
259
250
  _prevRouteKey?: string;
260
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
+
261
300
  /** @internal Per-request error dedup set for onError reporting */
262
301
  _reportedErrors: WeakSet<object>;
263
302
 
@@ -274,6 +313,24 @@ export interface RequestContext<
274
313
 
275
314
  /** @internal Request-scoped performance metrics store */
276
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[];
277
334
  }
278
335
 
279
336
  /**
@@ -300,10 +357,21 @@ export type PublicRequestContext<
300
357
  | "_routeName"
301
358
  | "_prevRouteKey"
302
359
  | "_reportedErrors"
360
+ | "_renderBarrier"
361
+ | "_resolveRenderBarrier"
362
+ | "_renderBarrierSegmentOrder"
363
+ | "_treeHasStreaming"
364
+ | "_renderBarrierWaiters"
365
+ | "_handlerLoaderDeps"
366
+ | "_renderBarrierHandleSnapshot"
303
367
  | "_reportBackgroundError"
304
368
  | "_debugPerformance"
305
369
  | "_metricsStore"
370
+ | "_basename"
306
371
  | "_setStatus"
372
+ | "_variables"
373
+ | "_classifiedRoute"
374
+ | "_cacheSignal"
307
375
  | "res"
308
376
  >;
309
377
 
@@ -413,13 +481,7 @@ export function requireRequestContext<
413
481
  return getRequestContext<TEnv>();
414
482
  }
415
483
 
416
- /**
417
- * Cloudflare Workers ExecutionContext (subset we need)
418
- */
419
- export interface ExecutionContext {
420
- waitUntil(promise: Promise<any>): void;
421
- passThroughOnException(): void;
422
- }
484
+ export type { ExecutionContext };
423
485
 
424
486
  /**
425
487
  * Options for creating a request context
@@ -503,6 +565,18 @@ export function createRequestContext<TEnv>(
503
565
  responseCookieCache = null;
504
566
  };
505
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
+
506
580
  // Effective cookie read: response stub Set-Cookie wins, then original header.
507
581
  // The stub IS the source of truth for same-request mutations.
508
582
  const effectiveCookie = (name: string): string | undefined => {
@@ -555,20 +629,31 @@ export function createRequestContext<TEnv>(
555
629
  invalidateResponseCookieCache();
556
630
  };
557
631
 
632
+ // Strip internal _rsc* params so userland sees a clean URL.
633
+ const cleanUrl = stripInternalParams(url);
634
+
558
635
  // Build the context object first (without use), then add use
559
636
  const ctx: RequestContext<TEnv> = {
560
637
  env,
561
638
  request,
562
- url,
639
+ url: cleanUrl,
563
640
  originalUrl: new URL(request.url),
564
641
  pathname: url.pathname,
565
- searchParams: url.searchParams,
566
- var: variables,
567
- get: ((keyOrVar: any) =>
568
- contextGet(variables, keyOrVar)) as RequestContext<TEnv>["get"],
569
- 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) => {
570
655
  assertNotInsideCacheExec(ctx, "set");
571
- contextSet(variables, keyOrVar, value);
656
+ contextSet(variables, keyOrVar, value, options);
572
657
  }) as RequestContext<TEnv>["set"],
573
658
  params: {} as Record<string, string>,
574
659
 
@@ -606,6 +691,7 @@ export function createRequestContext<TEnv>(
606
691
 
607
692
  setCookie(name: string, value: string, options?: CookieOptions): void {
608
693
  assertNotInsideCacheExec(ctx, "setCookie");
694
+ assertNotInsideCacheScopeALS("setCookie");
609
695
  stubResponse.headers.append(
610
696
  "Set-Cookie",
611
697
  serializeCookieValue(name, value, options),
@@ -618,6 +704,7 @@ export function createRequestContext<TEnv>(
618
704
  options?: Pick<CookieOptions, "domain" | "path">,
619
705
  ): void {
620
706
  assertNotInsideCacheExec(ctx, "deleteCookie");
707
+ assertNotInsideCacheScopeALS("deleteCookie");
621
708
  stubResponse.headers.append(
622
709
  "Set-Cookie",
623
710
  serializeCookieValue(name, "", { ...options, maxAge: 0 }),
@@ -627,11 +714,13 @@ export function createRequestContext<TEnv>(
627
714
 
628
715
  header(name: string, value: string): void {
629
716
  assertNotInsideCacheExec(ctx, "header");
717
+ assertNotInsideCacheScopeALS("header");
630
718
  stubResponse.headers.set(name, value);
631
719
  },
632
720
 
633
721
  setStatus(status: number): void {
634
722
  assertNotInsideCacheExec(ctx, "setStatus");
723
+ assertNotInsideCacheScopeALS("setStatus");
635
724
  stubResponse = new Response(null, {
636
725
  status,
637
726
  headers: stubResponse.headers,
@@ -656,20 +745,19 @@ export function createRequestContext<TEnv>(
656
745
 
657
746
  waitUntil(fn: () => Promise<void>): void {
658
747
  if (executionContext?.waitUntil) {
659
- // Cloudflare Workers: use native waitUntil
660
748
  executionContext.waitUntil(fn());
661
749
  } else {
662
- // Node.js / dev: fire-and-forget with error logging
663
- fn().catch((err) =>
664
- console.error("[waitUntil] Background task failed:", err),
665
- );
750
+ fireAndForgetWaitUntil(fn);
666
751
  }
667
752
  },
668
753
 
754
+ executionContext,
755
+
669
756
  _onResponseCallbacks: [],
670
757
 
671
758
  onResponse(callback: (response: Response) => Response): void {
672
759
  assertNotInsideCacheExec(ctx, "onResponse");
760
+ assertNotInsideCacheScopeALS("onResponse");
673
761
  this._onResponseCallbacks.push(callback);
674
762
  },
675
763
 
@@ -697,9 +785,58 @@ export function createRequestContext<TEnv>(
697
785
  _reportedErrors: new WeakSet<object>(),
698
786
  _metricsStore: undefined,
699
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,
792
+
700
793
  reverse: createReverseFunction(getGlobalRouteMap(), undefined, {}),
701
794
  };
702
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
+
703
840
  // Now create use() with access to ctx
704
841
  ctx.use = createUseFunction({
705
842
  handleStore,
@@ -881,15 +1018,17 @@ export function createUseFunction<TEnv>(
881
1018
  search: (ctx as any).search ?? {},
882
1019
  pathname: ctx.pathname,
883
1020
  url: ctx.url,
1021
+ originalUrl: ctx.originalUrl,
884
1022
  env: ctx.env as any,
885
- var: ctx.var as any,
1023
+ waitUntil: ctx.waitUntil.bind(ctx),
1024
+ executionContext: ctx.executionContext,
886
1025
  get: ctx.get as any,
887
- use: <TDep, TDepParams = any>(
1026
+ use: (<TDep, TDepParams = any>(
888
1027
  dep: LoaderDefinition<TDep, TDepParams>,
889
1028
  ): Promise<TDep> => {
890
1029
  // Recursive call - will start dep loader if not already started
891
1030
  return ctx.use(dep);
892
- },
1031
+ }) as LoaderContext["use"],
893
1032
  method: "GET",
894
1033
  body: undefined,
895
1034
  reverse: createReverseFunction(
@@ -898,9 +1037,14 @@ export function createUseFunction<TEnv>(
898
1037
  ctx.params as Record<string, string>,
899
1038
  ctx._routeName ? isRouteRootScoped(ctx._routeName) : undefined,
900
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
+ },
901
1046
  };
902
1047
 
903
- // Start loader execution with tracking
904
1048
  const doneLoader = track(`loader:${loader.$$id}`, 2);
905
1049
  const promise = Promise.resolve(loaderFn(loaderCtx)).finally(() => {
906
1050
  doneLoader();
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
  }