@rangojs/router 0.0.0-experimental.124 → 0.0.0-experimental.126

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 (235) hide show
  1. package/README.md +6 -4
  2. package/dist/bin/rango.js +3 -4
  3. package/dist/vite/index.js +315 -68
  4. package/package.json +19 -18
  5. package/skills/breadcrumbs/SKILL.md +60 -0
  6. package/skills/hooks/SKILL.md +2 -2
  7. package/skills/route/SKILL.md +6 -0
  8. package/skills/server-actions/SKILL.md +25 -1
  9. package/skills/testing/SKILL.md +17 -17
  10. package/skills/testing/cache-prerender.md +29 -3
  11. package/skills/testing/flight.md +13 -10
  12. package/skills/testing/render-handler.md +3 -0
  13. package/skills/testing/server-tree.md +1 -1
  14. package/skills/testing/setup.md +1 -1
  15. package/src/__internal.ts +0 -65
  16. package/src/browser/action-coordinator.ts +1 -1
  17. package/src/browser/action-fence.ts +10 -0
  18. package/src/browser/event-controller.ts +1 -83
  19. package/src/browser/navigation-store-handle.ts +3 -4
  20. package/src/browser/navigation-store.ts +0 -39
  21. package/src/browser/navigation-transaction.ts +0 -32
  22. package/src/browser/partial-update.ts +23 -84
  23. package/src/browser/prefetch/cache.ts +6 -45
  24. package/src/browser/prefetch/queue.ts +6 -3
  25. package/src/browser/rango-state.ts +2 -23
  26. package/src/browser/react/Link.tsx +0 -2
  27. package/src/browser/react/NavigationProvider.tsx +2 -1
  28. package/src/browser/react/ScrollRestoration.tsx +10 -6
  29. package/src/browser/react/filter-segment-order.ts +0 -2
  30. package/src/browser/react/index.ts +0 -45
  31. package/src/browser/react/location-state-shared.ts +0 -13
  32. package/src/browser/react/location-state.ts +0 -1
  33. package/src/browser/react/use-action.ts +6 -15
  34. package/src/browser/react/use-handle.ts +0 -5
  35. package/src/browser/react/use-link-status.ts +0 -4
  36. package/src/browser/react/use-navigation.ts +0 -3
  37. package/src/browser/react/use-params.ts +0 -2
  38. package/src/browser/react/use-router.ts +2 -1
  39. package/src/browser/react/use-search-params.ts +0 -5
  40. package/src/browser/react/use-segments.ts +0 -13
  41. package/src/browser/rsc-router.tsx +10 -3
  42. package/src/browser/server-action-bridge.ts +51 -3
  43. package/src/browser/types.ts +23 -5
  44. package/src/browser/validate-redirect-origin.ts +43 -16
  45. package/src/build/index.ts +8 -9
  46. package/src/build/route-trie.ts +46 -11
  47. package/src/build/route-types/param-extraction.ts +6 -3
  48. package/src/build/route-types/router-processing.ts +0 -8
  49. package/src/cache/cache-policy.ts +0 -54
  50. package/src/cache/cache-runtime.ts +48 -24
  51. package/src/cache/cache-scope.ts +0 -27
  52. package/src/cache/cache-tag.ts +0 -37
  53. package/src/cache/cf/cf-cache-store.ts +72 -45
  54. package/src/cache/cf/index.ts +0 -24
  55. package/src/cache/document-cache.ts +10 -36
  56. package/src/cache/handle-snapshot.ts +0 -40
  57. package/src/cache/index.ts +0 -27
  58. package/src/cache/memory-segment-store.ts +0 -52
  59. package/src/cache/profile-registry.ts +6 -30
  60. package/src/cache/read-through-swr.ts +41 -11
  61. package/src/cache/segment-codec.ts +0 -16
  62. package/src/cache/types.ts +0 -98
  63. package/src/client.rsc.tsx +4 -22
  64. package/src/client.tsx +19 -32
  65. package/src/context-var.ts +12 -0
  66. package/src/defer.ts +196 -0
  67. package/src/deps/ssr.ts +0 -1
  68. package/src/handle.ts +2 -12
  69. package/src/handles/MetaTags.tsx +0 -14
  70. package/src/handles/breadcrumbs.ts +16 -5
  71. package/src/handles/meta.ts +0 -39
  72. package/src/host/cookie-handler.ts +0 -36
  73. package/src/host/errors.ts +0 -24
  74. package/src/host/index.ts +6 -0
  75. package/src/host/pattern-matcher.ts +7 -50
  76. package/src/host/router.ts +1 -65
  77. package/src/host/testing.ts +0 -16
  78. package/src/host/types.ts +6 -2
  79. package/src/href-client.ts +0 -4
  80. package/src/index.rsc.ts +27 -2
  81. package/src/index.ts +7 -0
  82. package/src/internal-debug.ts +2 -4
  83. package/src/loader.rsc.ts +4 -15
  84. package/src/loader.ts +3 -9
  85. package/src/network-error-thrower.tsx +1 -6
  86. package/src/outlet-provider.tsx +1 -5
  87. package/src/prerender/param-hash.ts +10 -11
  88. package/src/prerender/store.ts +23 -30
  89. package/src/prerender.ts +34 -0
  90. package/src/redirect-origin.ts +100 -0
  91. package/src/root-error-boundary.tsx +1 -19
  92. package/src/route-content-wrapper.tsx +1 -44
  93. package/src/route-definition/dsl-helpers.ts +7 -19
  94. package/src/route-definition/helpers-types.ts +3 -3
  95. package/src/route-definition/redirect.ts +43 -9
  96. package/src/route-definition/resolve-handler-use.ts +6 -0
  97. package/src/route-map-builder.ts +0 -16
  98. package/src/router/content-negotiation.ts +0 -13
  99. package/src/router/error-handling.ts +12 -16
  100. package/src/router/find-match.ts +4 -31
  101. package/src/router/intercept-resolution.ts +10 -1
  102. package/src/router/lazy-includes.ts +1 -57
  103. package/src/router/loader-resolution.ts +25 -23
  104. package/src/router/logging.ts +0 -6
  105. package/src/router/manifest.ts +1 -25
  106. package/src/router/match-api.ts +0 -20
  107. package/src/router/match-context.ts +0 -22
  108. package/src/router/match-handlers.ts +0 -43
  109. package/src/router/match-middleware/background-revalidation.ts +0 -7
  110. package/src/router/match-middleware/cache-lookup.ts +96 -179
  111. package/src/router/match-middleware/cache-store.ts +0 -31
  112. package/src/router/match-middleware/intercept-resolution.ts +0 -22
  113. package/src/router/match-middleware/segment-resolution.ts +0 -22
  114. package/src/router/match-pipelines.ts +1 -42
  115. package/src/router/match-result.ts +1 -52
  116. package/src/router/metrics.ts +0 -34
  117. package/src/router/middleware-types.ts +0 -116
  118. package/src/router/middleware.ts +77 -60
  119. package/src/router/navigation-snapshot.ts +0 -51
  120. package/src/router/params-util.ts +23 -0
  121. package/src/router/pattern-matching.ts +5 -56
  122. package/src/router/prerender-match.ts +56 -51
  123. package/src/router/request-classification.ts +1 -38
  124. package/src/router/revalidation.ts +14 -62
  125. package/src/router/route-snapshot.ts +0 -1
  126. package/src/router/router-context.ts +0 -27
  127. package/src/router/router-interfaces.ts +10 -0
  128. package/src/router/segment-resolution/fresh.ts +25 -57
  129. package/src/router/segment-resolution/helpers.ts +34 -0
  130. package/src/router/segment-resolution/loader-cache.ts +35 -23
  131. package/src/router/segment-resolution/revalidation.ts +188 -283
  132. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  133. package/src/router/segment-resolution.ts +4 -1
  134. package/src/router/segment-wrappers.ts +0 -3
  135. package/src/router/telemetry-otel.ts +0 -20
  136. package/src/router/telemetry.ts +0 -22
  137. package/src/router/timeout.ts +0 -20
  138. package/src/router/trie-matching.ts +66 -45
  139. package/src/router/types.ts +1 -63
  140. package/src/router/url-params.ts +0 -5
  141. package/src/router.ts +8 -11
  142. package/src/rsc/handler-context.ts +1 -0
  143. package/src/rsc/handler.ts +20 -4
  144. package/src/rsc/helpers.ts +71 -3
  145. package/src/rsc/json-route-result.ts +38 -0
  146. package/src/rsc/origin-guard.ts +9 -15
  147. package/src/rsc/progressive-enhancement.ts +10 -1
  148. package/src/rsc/redirect-guard.ts +99 -0
  149. package/src/rsc/response-route-handler.ts +23 -18
  150. package/src/rsc/rsc-rendering.ts +2 -7
  151. package/src/rsc/runtime-warnings.ts +14 -0
  152. package/src/rsc/server-action.ts +34 -29
  153. package/src/rsc/types.ts +6 -3
  154. package/src/search-params.ts +0 -16
  155. package/src/segment-loader-promise.ts +14 -2
  156. package/src/segment-system.tsx +79 -88
  157. package/src/server/handle-store.ts +7 -24
  158. package/src/server/loader-registry.ts +5 -24
  159. package/src/server/request-context.ts +29 -92
  160. package/src/ssr/index.tsx +14 -14
  161. package/src/static-handler.ts +2 -27
  162. package/src/testing/cache-status.ts +44 -48
  163. package/src/testing/collect-handle.ts +1 -24
  164. package/src/testing/dispatch.ts +43 -6
  165. package/src/testing/e2e/index.ts +1 -22
  166. package/src/testing/e2e/matchers.ts +0 -16
  167. package/src/testing/flight-matchers.ts +0 -13
  168. package/src/testing/flight-normalize.ts +3 -30
  169. package/src/testing/flight.ts +46 -48
  170. package/src/testing/generated-routes.ts +1 -41
  171. package/src/testing/index.ts +1 -21
  172. package/src/testing/internal/context.ts +3 -45
  173. package/src/testing/internal/seed-vars.ts +0 -26
  174. package/src/testing/render-handler.ts +31 -61
  175. package/src/testing/render-route.tsx +75 -103
  176. package/src/testing/run-loader.ts +0 -96
  177. package/src/testing/run-middleware.ts +0 -26
  178. package/src/theme/ThemeProvider.tsx +0 -52
  179. package/src/theme/ThemeScript.tsx +0 -6
  180. package/src/theme/constants.ts +0 -12
  181. package/src/theme/index.ts +0 -7
  182. package/src/theme/theme-context.ts +1 -5
  183. package/src/theme/theme-script.ts +0 -14
  184. package/src/theme/use-theme.ts +0 -3
  185. package/src/types/boundaries.ts +0 -35
  186. package/src/types/error-types.ts +25 -89
  187. package/src/types/global-namespace.ts +4 -14
  188. package/src/types/handler-context.ts +28 -9
  189. package/src/types/index.ts +0 -10
  190. package/src/types/request-scope.ts +0 -19
  191. package/src/types/route-config.ts +6 -50
  192. package/src/types/route-entry.ts +0 -6
  193. package/src/types/segments.ts +0 -13
  194. package/src/urls/include-helper.ts +0 -4
  195. package/src/urls/index.ts +0 -6
  196. package/src/urls/path-helper-types.ts +2 -2
  197. package/src/urls/path-helper.ts +0 -54
  198. package/src/urls/urls-function.ts +0 -13
  199. package/src/use-loader.tsx +0 -186
  200. package/src/vite/discovery/bundle-postprocess.ts +2 -1
  201. package/src/vite/discovery/discover-routers.ts +28 -18
  202. package/src/vite/discovery/prerender-collection.ts +2 -4
  203. package/src/vite/discovery/state.ts +5 -0
  204. package/src/vite/discovery/virtual-module-codegen.ts +1 -11
  205. package/src/vite/plugin-types.ts +35 -9
  206. package/src/vite/plugins/cjs-to-esm.ts +0 -11
  207. package/src/vite/plugins/client-ref-dedup.ts +0 -11
  208. package/src/vite/plugins/client-ref-hashing.ts +0 -10
  209. package/src/vite/plugins/cloudflare-protocol-stub.ts +0 -20
  210. package/src/vite/plugins/expose-action-id.ts +2 -73
  211. package/src/vite/plugins/expose-id-utils.ts +0 -55
  212. package/src/vite/plugins/expose-ids/export-analysis.ts +0 -38
  213. package/src/vite/plugins/expose-ids/handler-transform.ts +0 -15
  214. package/src/vite/plugins/expose-ids/loader-transform.ts +0 -15
  215. package/src/vite/plugins/expose-ids/router-transform.ts +0 -13
  216. package/src/vite/plugins/expose-internal-ids.ts +10 -0
  217. package/src/vite/plugins/performance-tracks.ts +0 -3
  218. package/src/vite/plugins/refresh-cmd.ts +1 -1
  219. package/src/vite/plugins/use-cache-transform.ts +21 -46
  220. package/src/vite/plugins/version-injector.ts +0 -20
  221. package/src/vite/plugins/version-plugin.ts +1 -49
  222. package/src/vite/plugins/virtual-entries.ts +0 -15
  223. package/src/vite/rango.ts +2 -108
  224. package/src/vite/router-discovery.ts +9 -1
  225. package/src/vite/utils/ast-handler-extract.ts +0 -16
  226. package/src/vite/utils/bundle-analysis.ts +6 -13
  227. package/src/vite/utils/client-chunks.ts +0 -6
  228. package/src/vite/utils/forward-user-plugins.ts +0 -22
  229. package/src/vite/utils/manifest-utils.ts +0 -4
  230. package/src/vite/utils/package-resolution.ts +1 -73
  231. package/src/vite/utils/prerender-utils.ts +0 -35
  232. package/src/vite/utils/shared-utils.ts +3 -35
  233. package/src/browser/shallow.ts +0 -40
  234. package/src/handles/index.ts +0 -7
  235. package/src/router/middleware-cookies.ts +0 -55
@@ -28,8 +28,6 @@ import type { CacheProfile } from "../../cache/profile-registry.js";
28
28
 
29
29
  const DEFAULT_ORIGIN = "http://localhost/";
30
30
 
31
- // VarsInit + seedVariables live in ./seed-vars.js (react-server-safe) so the
32
- // Flight tier can seed vars too; re-exported here for existing importers.
33
31
  export type { VarsInit, StateCookieSeed };
34
32
  export { seedVariables };
35
33
 
@@ -39,10 +37,9 @@ export function toRequest(
39
37
  init?: RequestInit,
40
38
  ): Request {
41
39
  if (request instanceof Request) return request;
42
- if (typeof request === "string") {
43
- return new Request(new URL(request, DEFAULT_ORIGIN), init);
44
- }
45
- return new Request(DEFAULT_ORIGIN, init);
40
+ return typeof request === "string"
41
+ ? new Request(new URL(request, DEFAULT_ORIGIN), init)
42
+ : new Request(DEFAULT_ORIGIN, init);
46
43
  }
47
44
 
48
45
  export interface CreateTestContextOptions<TEnv> {
@@ -146,10 +143,6 @@ export function createTestRequestContext<TEnv>(
146
143
  const request = toRequest(opts.request, opts.requestInit);
147
144
  const url = new URL(request.url);
148
145
  const variables = seedVariables(opts.variables ?? {}, opts.vars);
149
- // Always seed a resolved name so invalidateClientCache() rotates (and emits
150
- // the Set-Cookie) like production instead of no-opping; opts.stateCookie
151
- // customizes the name/version. Surfaced on the result so a consumer asserts
152
- // the rotation against the same name without recomputing it.
153
146
  const stateCookieName = resolveSeededStateCookieName(opts.stateCookie);
154
147
  const ctx = createRequestContext<TEnv>({
155
148
  env: (opts.env ?? {}) as TEnv,
@@ -174,10 +167,6 @@ export function createTestRequestContext<TEnv>(
174
167
  opts.params ?? {},
175
168
  ) as RequestContext<TEnv>["reverse"];
176
169
  }
177
- // ctx.reverse is assigned the routeMap-scoped reverse (string-accepting) above;
178
- // expose it through the relaxed type so a test reverses a local route name
179
- // without casting. The runtime value matches; RequestContext's reverse is
180
- // declared against the narrower global route-name union, hence the cast.
181
170
  return {
182
171
  ctx: ctx as unknown as TestRequestContextObject<TEnv>,
183
172
  request,
@@ -246,13 +235,6 @@ export interface RunInRequestContextResult<T> {
246
235
  stateCookieName: string;
247
236
  }
248
237
 
249
- /**
250
- * Snapshot the observable effects a run left on `ctx` (cookies + location
251
- * state). Reads the fields directly off the ctx object, so it works both inside
252
- * and outside the AsyncLocalStorage scope (no `getRequestContext()`). Headers are
253
- * snapshotted separately from the final {@link Response} (via
254
- * {@link headersToObject}) so a thrown redirect's `Location` is included.
255
- */
256
238
  export function snapshotRunEffects<TEnv>(ctx: RequestContext<TEnv>): {
257
239
  cookies: Record<string, string>;
258
240
  locationState: Record<string, unknown>;
@@ -263,12 +245,6 @@ export function snapshotRunEffects<TEnv>(ctx: RequestContext<TEnv>): {
263
245
  };
264
246
  }
265
247
 
266
- /**
267
- * The response headers as a plain `{ name: value }` object, EXCLUDING
268
- * `set-cookie` (surfaced parsed on `cookies`). Names are lowercased (HTTP header
269
- * names are case-insensitive). Read from the final response so a thrown
270
- * redirect's `Location` and any `ctx.header(...)` both appear.
271
- */
272
248
  export function headersToObject(headers: Headers): Record<string, string> {
273
249
  const out: Record<string, string> = {};
274
250
  headers.forEach((value, name) => {
@@ -278,14 +254,6 @@ export function headersToObject(headers: Headers): Record<string, string> {
278
254
  return out;
279
255
  }
280
256
 
281
- /**
282
- * Build the observable response from what the run accumulated on `ctx.res`. When
283
- * `fn` threw a `Response` (a `redirect()`/`notFound()`), that Response IS the
284
- * response — merge the accumulated Set-Cookie/other headers into it (the
285
- * framework does this when it catches the thrown Response in production), with
286
- * its status/Location preserved. Otherwise snapshot the stub (status + headers).
287
- * The `Response`/`Headers` constructors copy, so the result is immutable.
288
- */
289
257
  export function buildRunResponse<TEnv>(
290
258
  ctx: RequestContext<TEnv>,
291
259
  thrown: unknown,
@@ -305,14 +273,6 @@ export function buildRunResponse<TEnv>(
305
273
  return new Response(null, { status: stub.status, headers: stub.headers });
306
274
  }
307
275
 
308
- /**
309
- * Snapshot the shared run-result envelope fields (everything except the primary
310
- * value) from what a run left on `ctx`: the captured `thrown`, the merged
311
- * `response`, the effective `cookies`/`headers` views, the `locationState`, and
312
- * the resolved `stateCookieName`. Shared by `runInRequestContext` and
313
- * `runLoaderResult` so the snapshot sequence lives once; each spreads it next to
314
- * its own primary field (`result` / `data`).
315
- */
316
276
  export function buildRunSnapshot<TEnv>(
317
277
  ctx: RequestContext<TEnv>,
318
278
  thrown: unknown,
@@ -382,8 +342,6 @@ export async function runInRequestContext<T, TEnv = unknown>(
382
342
  try {
383
343
  result = (await runWithRequestContext(ctx, () => fn(ctx))) as T;
384
344
  } catch (error) {
385
- // Capture (do NOT re-throw): a redirect/notFound action throws its Response
386
- // on the SUCCESS path, and its cookie/flash output must stay observable.
387
345
  thrown = error;
388
346
  }
389
347
  return { result, ...buildRunSnapshot(ctx, thrown, stateCookieName) };
@@ -9,14 +9,6 @@
9
9
  import { contextSet, type ContextVar } from "../../context-var.js";
10
10
  import { resolveStateCookieName } from "../../router/state-cookie-name.js";
11
11
 
12
- /**
13
- * Seed for the rango state cookie a handler/action/loader rotates when it calls
14
- * `invalidateClientCache()`. Production always resolves a name at router init
15
- * (so rotation always fires); the test stub did not, so the call silently
16
- * no-opped. Supplying this (or accepting the defaults) closes that gap. Lives in
17
- * the react-server-safe seed module so both the node tier (createTestRequestContext)
18
- * and the Flight tier (renderHandler) share one shape and one default.
19
- */
20
12
  export interface StateCookieSeed {
21
13
  /**
22
14
  * Cookie-name prefix, sanitized then composed with `routerId` exactly like
@@ -36,37 +28,19 @@ export interface StateCookieSeed {
36
28
  version?: string;
37
29
  }
38
30
 
39
- /**
40
- * Resolve the state cookie name a seed maps to, mirroring `createRouter`'s
41
- * `resolveStateCookieName` so a test asserts the SAME name production writes.
42
- * The default routerId `"router_0"` matches a single default router.
43
- */
44
31
  export function resolveSeededStateCookieName(seed?: StateCookieSeed): string {
45
32
  return resolveStateCookieName(seed?.prefix, seed?.routerId ?? "router_0");
46
33
  }
47
34
 
48
- /**
49
- * Initializer for seeded context variables (as a prior middleware would have
50
- * set, or a server component would read during render). Either a plain object
51
- * keyed by var name (the common, best-inferring form: `{ user: u }`) or a list
52
- * of `[key, value]` tuples where the key may be a `createVar()` handle or a
53
- * string (`[[userVar, u], ["flag", true]]`).
54
- */
55
35
  export type VarsInit =
56
36
  | Record<string, unknown>
57
37
  | ReadonlyArray<readonly [ContextVar<unknown> | string, unknown]>;
58
38
 
59
- /**
60
- * Preload variables as if set by upstream middleware (or visible to a rendered
61
- * server tree). Accepts entries keyed by either a ContextVar (from createVar) or
62
- * a string, matching ctx.set().
63
- */
64
39
  export function seedVariables(
65
40
  variables: Record<string, unknown>,
66
41
  vars?: VarsInit,
67
42
  ): Record<string, unknown> {
68
43
  if (!vars) return variables;
69
- // Array/iterable form -> use the tuples as-is; plain object -> its entries.
70
44
  const entries: Iterable<readonly [ContextVar<unknown> | string, unknown]> =
71
45
  Symbol.iterator in (vars as object)
72
46
  ? (vars as ReadonlyArray<
@@ -31,6 +31,7 @@ import {
31
31
  import { createHandlerContext } from "../router/handler-context.js";
32
32
  import { resolveLocationStateEntries } from "../browser/react/location-state-shared.js";
33
33
  import { isHandle, type Handle } from "../handle.js";
34
+ import { withDefer } from "../defer.js";
34
35
  import type { HandlerContext } from "../types/handler-context.js";
35
36
  import type { LoaderDefinition } from "../types.js";
36
37
  import {
@@ -41,7 +42,13 @@ import {
41
42
  } from "./internal/seed-vars.js";
42
43
 
43
44
  export type { StateCookieSeed } from "./internal/seed-vars.js";
44
- import { assertNoLegacyUrlOption, serializeNodeToFlight } from "./flight.js";
45
+ import {
46
+ assertNoLegacyUrlOption,
47
+ serializeNodeToFlight,
48
+ isServerOnlyStubError,
49
+ } from "./flight.js";
50
+ import type { SegmentCacheStore } from "../cache/types.js";
51
+ import type { CacheProfile } from "../cache/profile-registry.js";
45
52
  import {
46
53
  deserializeFlight,
47
54
  makeClientManifest,
@@ -94,6 +101,19 @@ export interface RenderHandlerOptions<TEnv = any> {
94
101
  * `result.stateCookieName`.
95
102
  */
96
103
  stateCookie?: StateCookieSeed;
104
+ /**
105
+ * Segment cache store backing a `"use cache"` function the handler invokes
106
+ * (e.g. `new MemorySegmentCacheStore()`). Without it, `registerCachedFunction`
107
+ * takes the uncached bypass and the cached path is NOT exercised (the runtime
108
+ * emits a one-time warning under the test runner). Pair with `cacheProfiles`
109
+ * so the profile the directive names resolves.
110
+ */
111
+ cacheStore?: SegmentCacheStore;
112
+ /**
113
+ * Cache profiles in the `createRouter({ cacheProfiles })` shape, required for
114
+ * `"use cache: profileName"` resolution once a `cacheStore` is wired.
115
+ */
116
+ cacheProfiles?: Record<string, CacheProfile>;
97
117
  }
98
118
 
99
119
  /** Result of {@link renderHandler}. */
@@ -134,11 +154,6 @@ export interface RenderHandlerResult {
134
154
  */
135
155
  class RenderHandlerSetupError extends Error {}
136
156
 
137
- // Local copy (not imported from internal/context.ts): that module is the NODE
138
- // tier and is deliberately NOT react-server-safe (the reason seed-vars.ts was
139
- // split out), and render-handler ships from the react-server ./testing/flight
140
- // entry. A 6-line pure projection is cheaper to duplicate than to route a
141
- // shared util across that boundary.
142
157
  function headersToObject(headers: Headers): Record<string, string> {
143
158
  const out: Record<string, string> = {};
144
159
  headers.forEach((value, name) => {
@@ -147,23 +162,6 @@ function headersToObject(headers: Headers): Record<string, string> {
147
162
  return out;
148
163
  }
149
164
 
150
- /**
151
- * Detect the server-only-API stub throw: when a handler/component imports
152
- * getRequestContext()/cookies()/etc. from the BARE `@rangojs/router` specifier
153
- * (the out-of-react-server stub in index.ts) instead of the react-server build.
154
- * In an rsc test this happens when the vitest.rsc.config.ts `resolve.alias` does
155
- * not map the bare specifier to `index.rsc.ts` (the `rangoTestAliases` preset).
156
- * The dual-substring match keeps a legitimate handler throw from being
157
- * reclassified as a setup error.
158
- */
159
- function isServerOnlyStubError(error: unknown): boolean {
160
- return (
161
- error instanceof Error &&
162
- error.message.includes("is only available from") &&
163
- error.message.includes("react-server")
164
- );
165
- }
166
-
167
165
  function toRequest(
168
166
  request: Request | string | undefined,
169
167
  headers?: HeadersInit,
@@ -175,20 +173,6 @@ function toRequest(
175
173
  return new Request(DEFAULT_URL, { headers });
176
174
  }
177
175
 
178
- /**
179
- * Build the result `response` from the request-context stub and, when present,
180
- * the Response the handler returned or threw (`source`). The stub cookies and
181
- * headers are merged in (Set-Cookie appended to preserve duplicates, other stub
182
- * headers filled in without clobbering the source), mirroring dispatch.ts's
183
- * rewrap.
184
- *
185
- * The source's BODY is carried over (not dropped): a response route returns a
186
- * `new Response(JSON.stringify(...))`, so callers reach for
187
- * `await result.response.text()`/`.json()`. Pre-fix this rewrapped to
188
- * `new Response(null, ...)` and the body was lost irrecoverably. A body is a
189
- * single-use stream; `source` is not read again here or by renderHandler, so
190
- * handing its body to the new Response is safe.
191
- */
192
176
  function buildResponse(reqCtx: RequestContext<any>, source: unknown): Response {
193
177
  const stub = reqCtx.res;
194
178
  if (source instanceof Response) {
@@ -231,9 +215,6 @@ export async function renderHandler<TEnv = any>(
231
215
  if (opts.clientComponents) registerClientComponents(opts.clientComponents);
232
216
  const request = toRequest(opts.request, opts.headers);
233
217
  const url = new URL(request.url);
234
- // Seed a resolved name so a handler calling invalidateClientCache() rotates
235
- // (emits the Set-Cookie) like production; opts.stateCookie customizes it. Also
236
- // surfaced on the result so a consumer asserts the rotation without recomputing.
237
218
  const stateCookieName = resolveSeededStateCookieName(opts.stateCookie);
238
219
  const reqCtx = createRequestContext<TEnv>({
239
220
  env: (opts.env ?? {}) as TEnv,
@@ -242,6 +223,8 @@ export async function renderHandler<TEnv = any>(
242
223
  variables: seedVariables({}, opts.vars),
243
224
  stateCookieName,
244
225
  version: opts.stateCookie?.version,
226
+ cacheStore: opts.cacheStore,
227
+ cacheProfiles: opts.cacheProfiles,
245
228
  });
246
229
 
247
230
  const loaderSeeds = new Map<unknown, unknown>(opts.loaders ?? []);
@@ -253,7 +236,11 @@ export async function renderHandler<TEnv = any>(
253
236
  let didThrow = false;
254
237
 
255
238
  await runWithRequestContext(reqCtx as RequestContext<TEnv>, async () => {
256
- setRequestContextParams(opts.params ?? {}, opts.routeName);
239
+ // Scope the request-context reverse to opts.routeMap too (not just the
240
+ // handler context built below), so a nested server component reading
241
+ // getRequestContext().reverse() resolves against the same map as the
242
+ // handler's ctx.reverse -- matching renderToFlightString/renderServerTree.
243
+ setRequestContextParams(opts.params ?? {}, opts.routeName, opts.routeMap);
257
244
  const hctx = createHandlerContext<TEnv>(
258
245
  opts.params ?? {},
259
246
  reqCtx.request,
@@ -264,17 +251,11 @@ export async function renderHandler<TEnv = any>(
264
251
  opts.routeMap ?? {},
265
252
  opts.routeName,
266
253
  );
267
- // Seed ctx.use: a handle returns a push fn that RECORDS (so ctx.use(Meta)
268
- // doesn't crash and pushes are assertable); a loader returns its seeded data
269
- // (no real loader run).
270
254
  (hctx as { use: unknown }).use = (item: unknown) => {
271
255
  if (isHandle(item)) {
272
256
  const handle = item as Handle<any, any>;
273
- return (dataOrFn: unknown) => {
274
- // Mirror production's push fn (loader-resolution.ts): a FUNCTION arg
275
- // (ctx.use(Meta)(() => fetchMeta())) is CALLED and its result is
276
- // recorded, not the function itself. An async callback records the
277
- // promise it returns, same as production (which does not await it).
257
+ // withDefer attaches .defer() so the harness mirrors production's push.
258
+ return withDefer((dataOrFn: unknown) => {
278
259
  const value =
279
260
  typeof dataOrFn === "function"
280
261
  ? (dataOrFn as () => unknown)()
@@ -282,7 +263,7 @@ export async function renderHandler<TEnv = any>(
282
263
  const pushed = handlePushes.get(handle) ?? [];
283
264
  pushed.push(value);
284
265
  handlePushes.set(handle, pushed);
285
- };
266
+ });
286
267
  }
287
268
  if (loaderSeeds.has(item)) return loaderSeeds.get(item);
288
269
  throw new RenderHandlerSetupError(
@@ -294,8 +275,6 @@ export async function renderHandler<TEnv = any>(
294
275
 
295
276
  try {
296
277
  out = await handler(hctx as HandlerContext<any, TEnv>);
297
- // Serialize the RSC in THIS context, so nested async server components see
298
- // getRequestContext()/cookies()/vars while they render.
299
278
  if (out !== undefined && !(out instanceof Response)) {
300
279
  flight = await serializeNodeToFlight(
301
280
  out as ReactNode,
@@ -304,13 +283,7 @@ export async function renderHandler<TEnv = any>(
304
283
  );
305
284
  }
306
285
  } catch (error) {
307
- // A harness misconfiguration (unseeded loader) is the consumer's mistake —
308
- // surface it as a rejection, not as a captured handler throw.
309
286
  if (error instanceof RenderHandlerSetupError) throw error;
310
- // Same for the server-only-API stub throw: the handler read
311
- // getRequestContext()/cookies() but the bare `@rangojs/router` resolved to
312
- // the throwing stub. Rethrow LOUDLY with the fix, instead of silently
313
- // capturing it (which surfaces as an opaque tree:undefined + bare throw).
314
287
  if (isServerOnlyStubError(error)) {
315
288
  throw new RenderHandlerSetupError(
316
289
  `renderHandler: the handler called a server-only API (getRequestContext/cookies/...) ` +
@@ -320,8 +293,6 @@ export async function renderHandler<TEnv = any>(
320
293
  `Original: ${(error as Error).message}`,
321
294
  );
322
295
  }
323
- // Otherwise captured, NOT re-thrown: a handler's success path is often
324
- // `throw redirect(...)`; its cookies/flash must stay observable.
325
296
  didThrow = true;
326
297
  thrown = error;
327
298
  }
@@ -342,7 +313,6 @@ export async function renderHandler<TEnv = any>(
342
313
  }
343
314
  )._locationState ?? [],
344
315
  );
345
- // Deserialize outside the context (the client deserializer needs no ctx).
346
316
  const tree =
347
317
  flight !== undefined ? await deserializeFlight(flight) : undefined;
348
318