@rangojs/router 0.0.0-experimental.259 → 0.0.0-experimental.26

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 (225) hide show
  1. package/README.md +294 -28
  2. package/dist/bin/rango.js +355 -47
  3. package/dist/vite/index.js +1658 -1239
  4. package/package.json +3 -3
  5. package/skills/cache-guide/SKILL.md +9 -5
  6. package/skills/caching/SKILL.md +4 -4
  7. package/skills/document-cache/SKILL.md +2 -2
  8. package/skills/hooks/SKILL.md +40 -29
  9. package/skills/host-router/SKILL.md +218 -0
  10. package/skills/intercept/SKILL.md +79 -0
  11. package/skills/layout/SKILL.md +62 -2
  12. package/skills/loader/SKILL.md +229 -15
  13. package/skills/middleware/SKILL.md +109 -30
  14. package/skills/parallel/SKILL.md +57 -2
  15. package/skills/prerender/SKILL.md +189 -19
  16. package/skills/rango/SKILL.md +1 -2
  17. package/skills/response-routes/SKILL.md +3 -3
  18. package/skills/route/SKILL.md +44 -3
  19. package/skills/router-setup/SKILL.md +80 -3
  20. package/skills/theme/SKILL.md +5 -4
  21. package/skills/typesafety/SKILL.md +59 -16
  22. package/skills/use-cache/SKILL.md +16 -2
  23. package/src/__internal.ts +1 -1
  24. package/src/bin/rango.ts +56 -19
  25. package/src/browser/action-coordinator.ts +97 -0
  26. package/src/browser/event-controller.ts +29 -48
  27. package/src/browser/history-state.ts +80 -0
  28. package/src/browser/intercept-utils.ts +1 -1
  29. package/src/browser/link-interceptor.ts +19 -3
  30. package/src/browser/merge-segment-loaders.ts +9 -2
  31. package/src/browser/navigation-bridge.ts +66 -443
  32. package/src/browser/navigation-client.ts +34 -62
  33. package/src/browser/navigation-store.ts +4 -33
  34. package/src/browser/navigation-transaction.ts +295 -0
  35. package/src/browser/partial-update.ts +103 -151
  36. package/src/browser/prefetch/cache.ts +67 -0
  37. package/src/browser/prefetch/fetch.ts +137 -0
  38. package/src/browser/prefetch/observer.ts +65 -0
  39. package/src/browser/prefetch/policy.ts +42 -0
  40. package/src/browser/prefetch/queue.ts +88 -0
  41. package/src/browser/rango-state.ts +112 -0
  42. package/src/browser/react/Link.tsx +154 -44
  43. package/src/browser/react/NavigationProvider.tsx +32 -0
  44. package/src/browser/react/context.ts +6 -0
  45. package/src/browser/react/filter-segment-order.ts +11 -0
  46. package/src/browser/react/index.ts +2 -6
  47. package/src/browser/react/location-state-shared.ts +29 -11
  48. package/src/browser/react/location-state.ts +6 -4
  49. package/src/browser/react/nonce-context.ts +23 -0
  50. package/src/browser/react/shallow-equal.ts +27 -0
  51. package/src/browser/react/use-action.ts +23 -45
  52. package/src/browser/react/use-client-cache.ts +5 -3
  53. package/src/browser/react/use-handle.ts +21 -64
  54. package/src/browser/react/use-navigation.ts +7 -32
  55. package/src/browser/react/use-params.ts +5 -34
  56. package/src/browser/react/use-pathname.ts +2 -3
  57. package/src/browser/react/use-router.ts +3 -6
  58. package/src/browser/react/use-search-params.ts +2 -1
  59. package/src/browser/react/use-segments.ts +75 -114
  60. package/src/browser/response-adapter.ts +73 -0
  61. package/src/browser/rsc-router.tsx +46 -22
  62. package/src/browser/scroll-restoration.ts +10 -7
  63. package/src/browser/server-action-bridge.ts +458 -405
  64. package/src/browser/types.ts +21 -35
  65. package/src/browser/validate-redirect-origin.ts +29 -0
  66. package/src/build/generate-manifest.ts +38 -13
  67. package/src/build/generate-route-types.ts +4 -0
  68. package/src/build/index.ts +1 -0
  69. package/src/build/route-trie.ts +19 -3
  70. package/src/build/route-types/codegen.ts +13 -4
  71. package/src/build/route-types/include-resolution.ts +13 -0
  72. package/src/build/route-types/per-module-writer.ts +15 -3
  73. package/src/build/route-types/router-processing.ts +170 -18
  74. package/src/build/runtime-discovery.ts +13 -1
  75. package/src/cache/background-task.ts +34 -0
  76. package/src/cache/cache-key-utils.ts +44 -0
  77. package/src/cache/cache-policy.ts +125 -0
  78. package/src/cache/cache-runtime.ts +136 -123
  79. package/src/cache/cache-scope.ts +76 -83
  80. package/src/cache/cf/cf-cache-store.ts +12 -7
  81. package/src/cache/document-cache.ts +93 -69
  82. package/src/cache/handle-capture.ts +81 -0
  83. package/src/cache/index.ts +0 -15
  84. package/src/cache/memory-segment-store.ts +43 -69
  85. package/src/cache/profile-registry.ts +43 -8
  86. package/src/cache/read-through-swr.ts +134 -0
  87. package/src/cache/segment-codec.ts +140 -117
  88. package/src/cache/taint.ts +30 -3
  89. package/src/cache/types.ts +1 -115
  90. package/src/client.rsc.tsx +0 -1
  91. package/src/client.tsx +53 -76
  92. package/src/errors.ts +6 -1
  93. package/src/handle.ts +1 -1
  94. package/src/handles/MetaTags.tsx +5 -2
  95. package/src/host/cookie-handler.ts +8 -3
  96. package/src/host/index.ts +0 -3
  97. package/src/host/router.ts +14 -1
  98. package/src/href-client.ts +3 -1
  99. package/src/index.rsc.ts +53 -10
  100. package/src/index.ts +73 -43
  101. package/src/loader.rsc.ts +12 -4
  102. package/src/loader.ts +8 -0
  103. package/src/prerender/store.ts +60 -18
  104. package/src/prerender.ts +76 -18
  105. package/src/reverse.ts +11 -7
  106. package/src/root-error-boundary.tsx +30 -26
  107. package/src/route-definition/dsl-helpers.ts +9 -6
  108. package/src/route-definition/index.ts +0 -3
  109. package/src/route-definition/redirect.ts +15 -3
  110. package/src/route-map-builder.ts +38 -2
  111. package/src/route-name.ts +53 -0
  112. package/src/route-types.ts +7 -0
  113. package/src/router/content-negotiation.ts +1 -1
  114. package/src/router/debug-manifest.ts +16 -3
  115. package/src/router/handler-context.ts +96 -17
  116. package/src/router/intercept-resolution.ts +6 -4
  117. package/src/router/lazy-includes.ts +4 -0
  118. package/src/router/loader-resolution.ts +6 -11
  119. package/src/router/logging.ts +100 -3
  120. package/src/router/manifest.ts +32 -3
  121. package/src/router/match-api.ts +62 -54
  122. package/src/router/match-context.ts +3 -0
  123. package/src/router/match-handlers.ts +185 -11
  124. package/src/router/match-middleware/background-revalidation.ts +65 -85
  125. package/src/router/match-middleware/cache-lookup.ts +78 -10
  126. package/src/router/match-middleware/cache-store.ts +2 -0
  127. package/src/router/match-pipelines.ts +8 -43
  128. package/src/router/match-result.ts +0 -9
  129. package/src/router/metrics.ts +233 -13
  130. package/src/router/middleware-types.ts +34 -39
  131. package/src/router/middleware.ts +290 -130
  132. package/src/router/pattern-matching.ts +61 -10
  133. package/src/router/prerender-match.ts +36 -6
  134. package/src/router/preview-match.ts +7 -1
  135. package/src/router/revalidation.ts +61 -2
  136. package/src/router/router-context.ts +15 -0
  137. package/src/router/router-interfaces.ts +158 -40
  138. package/src/router/router-options.ts +223 -1
  139. package/src/router/router-registry.ts +5 -2
  140. package/src/router/segment-resolution/fresh.ts +165 -242
  141. package/src/router/segment-resolution/helpers.ts +263 -0
  142. package/src/router/segment-resolution/loader-cache.ts +102 -98
  143. package/src/router/segment-resolution/revalidation.ts +394 -272
  144. package/src/router/segment-resolution/static-store.ts +2 -2
  145. package/src/router/segment-resolution.ts +1 -3
  146. package/src/router/segment-wrappers.ts +3 -0
  147. package/src/router/telemetry-otel.ts +299 -0
  148. package/src/router/telemetry.ts +300 -0
  149. package/src/router/timeout.ts +148 -0
  150. package/src/router/trie-matching.ts +20 -2
  151. package/src/router/types.ts +7 -1
  152. package/src/router.ts +203 -18
  153. package/src/rsc/handler-context.ts +13 -2
  154. package/src/rsc/handler.ts +489 -438
  155. package/src/rsc/helpers.ts +125 -5
  156. package/src/rsc/index.ts +0 -20
  157. package/src/rsc/loader-fetch.ts +84 -42
  158. package/src/rsc/manifest-init.ts +3 -2
  159. package/src/rsc/origin-guard.ts +141 -0
  160. package/src/rsc/progressive-enhancement.ts +245 -19
  161. package/src/rsc/response-route-handler.ts +347 -0
  162. package/src/rsc/rsc-rendering.ts +47 -43
  163. package/src/rsc/runtime-warnings.ts +42 -0
  164. package/src/rsc/server-action.ts +166 -66
  165. package/src/rsc/ssr-setup.ts +128 -0
  166. package/src/rsc/types.ts +20 -2
  167. package/src/search-params.ts +38 -23
  168. package/src/server/context.ts +61 -7
  169. package/src/server/cookie-store.ts +190 -0
  170. package/src/server/fetchable-loader-store.ts +11 -6
  171. package/src/server/handle-store.ts +84 -12
  172. package/src/server/loader-registry.ts +11 -46
  173. package/src/server/request-context.ts +275 -49
  174. package/src/server.ts +6 -0
  175. package/src/ssr/index.tsx +67 -28
  176. package/src/static-handler.ts +7 -0
  177. package/src/theme/ThemeProvider.tsx +6 -1
  178. package/src/theme/index.ts +4 -18
  179. package/src/theme/theme-context.ts +1 -28
  180. package/src/theme/theme-script.ts +2 -1
  181. package/src/types/cache-types.ts +6 -1
  182. package/src/types/error-types.ts +3 -0
  183. package/src/types/global-namespace.ts +22 -0
  184. package/src/types/handler-context.ts +103 -16
  185. package/src/types/index.ts +1 -1
  186. package/src/types/loader-types.ts +9 -6
  187. package/src/types/route-config.ts +17 -26
  188. package/src/types/route-entry.ts +28 -0
  189. package/src/types/segments.ts +0 -5
  190. package/src/urls/include-helper.ts +49 -8
  191. package/src/urls/index.ts +1 -0
  192. package/src/urls/path-helper-types.ts +30 -12
  193. package/src/urls/path-helper.ts +17 -2
  194. package/src/urls/pattern-types.ts +21 -1
  195. package/src/urls/response-types.ts +29 -7
  196. package/src/urls/type-extraction.ts +23 -15
  197. package/src/use-loader.tsx +27 -9
  198. package/src/vite/discovery/bundle-postprocess.ts +32 -52
  199. package/src/vite/discovery/discover-routers.ts +52 -26
  200. package/src/vite/discovery/prerender-collection.ts +58 -41
  201. package/src/vite/discovery/route-types-writer.ts +7 -7
  202. package/src/vite/discovery/state.ts +7 -7
  203. package/src/vite/discovery/virtual-module-codegen.ts +5 -2
  204. package/src/vite/index.ts +10 -51
  205. package/src/vite/plugins/client-ref-dedup.ts +115 -0
  206. package/src/vite/plugins/client-ref-hashing.ts +3 -3
  207. package/src/vite/plugins/expose-internal-ids.ts +4 -3
  208. package/src/vite/plugins/refresh-cmd.ts +65 -0
  209. package/src/vite/plugins/use-cache-transform.ts +91 -3
  210. package/src/vite/plugins/version-plugin.ts +188 -18
  211. package/src/vite/rango.ts +61 -36
  212. package/src/vite/router-discovery.ts +173 -100
  213. package/src/vite/utils/prerender-utils.ts +81 -0
  214. package/src/vite/utils/shared-utils.ts +19 -9
  215. package/skills/testing/SKILL.md +0 -226
  216. package/src/browser/lru-cache.ts +0 -61
  217. package/src/browser/react/prefetch.ts +0 -27
  218. package/src/browser/request-controller.ts +0 -164
  219. package/src/cache/memory-store.ts +0 -253
  220. package/src/href-context.ts +0 -33
  221. package/src/route-definition/route-function.ts +0 -119
  222. package/src/router.gen.ts +0 -6
  223. package/src/static-handler.gen.ts +0 -5
  224. package/src/urls.gen.ts +0 -8
  225. /package/{CLAUDE.md → AGENTS.md} +0 -0
package/src/ssr/index.tsx CHANGED
@@ -1,9 +1,8 @@
1
1
  import React from "react";
2
2
  import { renderSegments } from "../segment-system.js";
3
- import { initHandleDataSync } from "../browser/react/use-handle.js";
4
- import { initSegmentsSync } from "../browser/react/use-segments.js";
5
- import { initThemeConfigSync } from "../theme/theme-context.js";
3
+ import { filterSegmentOrder } from "../browser/react/filter-segment-order.js";
6
4
  import { ThemeProvider } from "../theme/ThemeProvider.js";
5
+ import { NonceContext } from "../browser/react/nonce-context.js";
7
6
  import { NavigationStoreContext } from "../browser/react/context.js";
8
7
  import type { NavigationStoreContextValue } from "../browser/react/context.js";
9
8
  import type { HandleData } from "../browser/types.js";
@@ -34,6 +33,13 @@ interface RenderToReadableStreamOptions {
34
33
  formState?: unknown;
35
34
  }
36
35
 
36
+ /**
37
+ * ReadableStream with the allReady promise added by react-dom/server.edge.
38
+ */
39
+ interface ReactDOMReadableStream extends ReadableStream<Uint8Array> {
40
+ allReady: Promise<void>;
41
+ }
42
+
37
43
  /**
38
44
  * Options for the renderHTML function
39
45
  */
@@ -50,6 +56,14 @@ export interface SSRRenderOptions {
50
56
  * Nonce for Content Security Policy (CSP)
51
57
  */
52
58
  nonce?: string;
59
+
60
+ /**
61
+ * SSR stream mode.
62
+ *
63
+ * - `"stream"` (default) — start flushing HTML immediately.
64
+ * - `"allReady"` — await `stream.allReady` before returning.
65
+ */
66
+ streamMode?: import("../router/router-options.js").SSRStreamMode;
53
67
  }
54
68
 
55
69
  /**
@@ -69,7 +83,7 @@ export interface SSRDependencies<TEnv = unknown> {
69
83
  renderToReadableStream: (
70
84
  element: React.ReactNode,
71
85
  options?: RenderToReadableStreamOptions,
72
- ) => Promise<ReadableStream<Uint8Array>>;
86
+ ) => Promise<ReactDOMReadableStream>;
73
87
 
74
88
  /**
75
89
  * injectRSCPayload from rsc-html-stream/server
@@ -117,6 +131,7 @@ interface RscPayload {
117
131
  params?: Record<string, string>;
118
132
  themeConfig?: ResolvedThemeConfig | null;
119
133
  initialTheme?: Theme;
134
+ version?: string;
120
135
  };
121
136
  }
122
137
 
@@ -138,8 +153,18 @@ async function consumeAsyncGenerator(
138
153
  * Create a minimal event controller for SSR.
139
154
  * This provides the correct pathname so useNavigation returns the right value during SSR.
140
155
  */
141
- function createSsrEventController(pathname: string): EventController {
142
- const location = new URL(pathname, "http://localhost");
156
+ function createSsrEventController(opts: {
157
+ pathname: string;
158
+ params?: Record<string, string>;
159
+ handleData?: HandleData;
160
+ matched?: string[];
161
+ }): EventController {
162
+ const location = new URL(opts.pathname, "http://localhost");
163
+ let params = opts.params ?? {};
164
+ const handleState = {
165
+ data: opts.handleData ?? {},
166
+ segmentOrder: filterSegmentOrder(opts.matched ?? []),
167
+ };
143
168
  const state: DerivedNavigationState = {
144
169
  state: "idle",
145
170
  isStreaming: false,
@@ -150,6 +175,7 @@ function createSsrEventController(pathname: string): EventController {
150
175
 
151
176
  return {
152
177
  getState: () => state,
178
+ getLocation: () => location,
153
179
  subscribe: () => () => {},
154
180
  getActionState: () => ({
155
181
  state: "idle",
@@ -161,9 +187,11 @@ function createSsrEventController(pathname: string): EventController {
161
187
  subscribeToAction: () => () => {},
162
188
  subscribeToHandles: () => () => {},
163
189
  setHandleData: () => {},
164
- getHandleState: () => ({ data: {}, segmentOrder: [] }),
165
- setParams: () => {},
166
- getParams: () => ({}),
190
+ getHandleState: () => handleState,
191
+ setParams: (nextParams) => {
192
+ params = nextParams;
193
+ },
194
+ getParams: () => params,
167
195
  setLocation: () => {},
168
196
  startNavigation: () => {
169
197
  throw new Error("Navigation not supported during SSR");
@@ -175,6 +203,7 @@ function createSsrEventController(pathname: string): EventController {
175
203
  abortAllActions: () => {},
176
204
  getCurrentNavigation: () => null,
177
205
  getInflightActions: () => new Map(),
206
+ hadAnyConcurrentActions: () => false,
178
207
  };
179
208
  }
180
209
 
@@ -216,7 +245,7 @@ export function createSSRHandler<TEnv = unknown>(deps: SSRDependencies<TEnv>) {
216
245
  rscStream: ReadableStream<Uint8Array>,
217
246
  options?: SSRRenderOptions,
218
247
  ): Promise<ReadableStream<Uint8Array>> {
219
- const { nonce, formState } = options ?? {};
248
+ const { nonce, formState, streamMode } = options ?? {};
220
249
 
221
250
  try {
222
251
  // Tee the stream:
@@ -231,35 +260,31 @@ export function createSSRHandler<TEnv = unknown>(deps: SSRDependencies<TEnv>) {
231
260
  function SsrRoot() {
232
261
  payload ??= createFromReadableStream<RscPayload>(rscStream1);
233
262
  const resolved = React.use(payload);
234
-
235
- // Initialize segments state before children render (for useSegments hook)
236
- initSegmentsSync(
237
- resolved.metadata?.matched,
238
- resolved.metadata?.pathname,
239
- resolved.metadata?.params,
240
- );
241
-
242
- // Initialize theme config for MetaTags to render theme script
243
263
  const themeConfig = resolved.metadata?.themeConfig ?? null;
244
- initThemeConfigSync(themeConfig);
264
+ const pathname = resolved.metadata?.pathname ?? "/";
245
265
 
246
- // Await handles and initialize state before children render
266
+ // Await handles before creating SSR event controller so hooks can
267
+ // read request-local handle data via NavigationStoreContext.
247
268
  // The handles property is an async generator that yields on each push
248
269
  // Memoize the promise since async generators can only be iterated once
270
+ let handleData: HandleData = {};
249
271
  if (resolved.metadata?.handles) {
250
272
  handlesPromise ??= consumeAsyncGenerator(resolved.metadata.handles);
251
- const handleData = React.use(handlesPromise);
252
- initHandleDataSync(handleData, resolved.metadata.matched);
273
+ handleData = React.use(handlesPromise);
253
274
  }
254
275
 
255
- // Create SSR context with correct pathname for useNavigation
276
+ // Create SSR context with request-local pathname/params/handles.
256
277
  ssrContextValue ??= {
257
278
  store: null as any,
258
- eventController: createSsrEventController(
259
- resolved.metadata?.pathname ?? "/",
260
- ),
279
+ eventController: createSsrEventController({
280
+ pathname,
281
+ params: resolved.metadata?.params,
282
+ handleData,
283
+ matched: resolved.metadata?.matched,
284
+ }),
261
285
  navigate: async () => {},
262
286
  refresh: async () => {},
287
+ version: resolved.metadata?.version,
263
288
  };
264
289
 
265
290
  // Build content tree from segments.
@@ -287,9 +312,16 @@ export function createSSRHandler<TEnv = unknown>(deps: SSRDependencies<TEnv>) {
287
312
  );
288
313
  }
289
314
 
315
+ // Wrap with NonceContext so client components (e.g. MetaTags) can
316
+ // apply CSP nonces to inline scripts during SSR. Always present to
317
+ // match the browser-side NavigationProvider tree shape for hydration.
318
+ content = (
319
+ <NonceContext.Provider value={nonce}>{content}</NonceContext.Provider>
320
+ );
321
+
290
322
  // Wrap with NavigationStoreContext for useNavigation hook
291
323
  return (
292
- <NavigationStoreContext.Provider value={ssrContextValue}>
324
+ <NavigationStoreContext.Provider value={ssrContextValue!}>
293
325
  {content}
294
326
  </NavigationStoreContext.Provider>
295
327
  );
@@ -307,6 +339,13 @@ export function createSSRHandler<TEnv = unknown>(deps: SSRDependencies<TEnv>) {
307
339
  nonce,
308
340
  });
309
341
 
342
+ // Wait for all Suspense boundaries to resolve when streamMode is "allReady".
343
+ // This buffers the entire HTML before flushing — used for bots that
344
+ // cannot process streamed HTML.
345
+ if (streamMode === "allReady") {
346
+ await htmlStream.allReady;
347
+ }
348
+
310
349
  // Inject RSC payload into HTML as <script nonce="...">__FLIGHT_DATA__</script>
311
350
  return htmlStream.pipeThrough(injectRSCPayload(rscStream2, { nonce }));
312
351
  } catch (error) {
@@ -82,6 +82,13 @@ export function Static<TParams extends Record<string, any>>(
82
82
  id = maybeId ?? "";
83
83
  }
84
84
 
85
+ if (!id) {
86
+ throw new Error(
87
+ "[rsc-router] Static: missing $$id. " +
88
+ "Ensure the exposeInternalIds Vite plugin is configured.",
89
+ );
90
+ }
91
+
85
92
  return {
86
93
  __brand: "staticHandler" as const,
87
94
  $$id: id,
@@ -50,7 +50,12 @@ function readThemeFromCookie(storageKey: string): string | null {
50
50
  for (const cookie of cookies) {
51
51
  const [name, ...rest] = cookie.trim().split("=");
52
52
  if (name === storageKey) {
53
- return decodeURIComponent(rest.join("="));
53
+ const raw = rest.join("=");
54
+ try {
55
+ return decodeURIComponent(raw);
56
+ } catch {
57
+ return raw;
58
+ }
54
59
  }
55
60
  }
56
61
  return null;
@@ -1,9 +1,10 @@
1
1
  /**
2
2
  * Theme module exports for @rangojs/router/theme
3
3
  *
4
- * This module provides theme management for rsc-router:
4
+ * This module provides the public theme API:
5
5
  * - useTheme: Hook for accessing theme state in client components
6
6
  * - ThemeProvider: Component for manual theme provider setup (typically not needed)
7
+ * - ThemeScript: FOUC-prevention script component for document/head usage
7
8
  * - Types for theme configuration
8
9
  *
9
10
  * @example
@@ -43,20 +44,5 @@ export type {
43
44
  ThemeContextValue,
44
45
  } from "./types.js";
45
46
 
46
- // Constants (for advanced use cases)
47
- export {
48
- THEME_DEFAULTS,
49
- THEME_COOKIE,
50
- resolveThemeConfig,
51
- } from "./constants.js";
52
-
53
- // Script generation (for advanced SSR use cases)
54
- export { generateThemeScript, getNonceAttribute } from "./theme-script.js";
55
-
56
- // Context (for advanced use cases)
57
- export {
58
- ThemeContext,
59
- useThemeContext,
60
- initThemeConfigSync,
61
- getSSRThemeConfig,
62
- } from "./theme-context.js";
47
+ // Constants
48
+ export { THEME_DEFAULTS, THEME_COOKIE } from "./constants.js";
@@ -10,7 +10,7 @@
10
10
  */
11
11
 
12
12
  import { createContext, useContext, type Context } from "react";
13
- import type { ResolvedThemeConfig, ThemeContextValue } from "./types.js";
13
+ import type { ThemeContextValue } from "./types.js";
14
14
 
15
15
  /**
16
16
  * React context for theme state
@@ -19,33 +19,6 @@ import type { ResolvedThemeConfig, ThemeContextValue } from "./types.js";
19
19
  export const ThemeContext: Context<ThemeContextValue | null> =
20
20
  createContext<ThemeContextValue | null>(null);
21
21
 
22
- /**
23
- * SSR module-level state for theme config.
24
- * Populated by initThemeConfigSync before React renders.
25
- * Used by MetaTags during SSR to render the theme script.
26
- */
27
- let ssrThemeConfig: ResolvedThemeConfig | null = null;
28
-
29
- /**
30
- * Initialize theme config synchronously for SSR.
31
- * Called before rendering to populate state for MetaTags.
32
- *
33
- * @param config - Theme config from router, or null if theme is disabled
34
- */
35
- export function initThemeConfigSync(config: ResolvedThemeConfig | null): void {
36
- ssrThemeConfig = config;
37
- }
38
-
39
- /**
40
- * Get theme config for SSR/hydration.
41
- * Used by MetaTags to render the theme script.
42
- *
43
- * @returns Theme config if available, null otherwise
44
- */
45
- export function getSSRThemeConfig(): ResolvedThemeConfig | null {
46
- return ssrThemeConfig;
47
- }
48
-
49
22
  /**
50
23
  * Get theme context (internal use)
51
24
  * Returns null if theme is not enabled
@@ -40,7 +40,8 @@ export function generateThemeScript(config: ResolvedThemeConfig): string {
40
40
  for (var i = 0; i < cookies.length; i++) {
41
41
  var cookie = cookies[i].trim();
42
42
  if (cookie.indexOf(storageKey + '=') === 0) {
43
- return decodeURIComponent(cookie.substring(storageKey.length + 1));
43
+ try { return decodeURIComponent(cookie.substring(storageKey.length + 1)); }
44
+ catch (e) { return cookie.substring(storageKey.length + 1); }
44
45
  }
45
46
  }
46
47
  // Fall back to localStorage
@@ -134,7 +134,7 @@ export interface CacheOptions<TEnv = unknown> {
134
134
  * key: (ctx) => `${ctx.env.REGION}:product:${ctx.params.id}`
135
135
  *
136
136
  * // Include cookies
137
- * key: (ctx) => `${ctx.cookie('locale')}:${ctx.pathname}`
137
+ * key: (ctx) => `${cookies().get('locale')?.value ?? 'en'}:${ctx.pathname}`
138
138
  * ```
139
139
  */
140
140
  key?: (
@@ -145,6 +145,11 @@ export interface CacheOptions<TEnv = unknown> {
145
145
  * Tags for cache invalidation.
146
146
  * Can be a static array or a function that returns tags.
147
147
  *
148
+ * Note: Tags are passed through to the store but built-in stores
149
+ * (MemorySegmentCacheStore, CFCacheStore) do not yet index or
150
+ * invalidate by tag. Effective tag-based invalidation requires a
151
+ * custom store implementation with secondary indices.
152
+ *
148
153
  * @example
149
154
  * ```typescript
150
155
  * // Static tags
@@ -10,6 +10,7 @@
10
10
  * - "rendering": Invoked during SSR rendering errors (ssr/index.tsx, separate callback)
11
11
  * - "action": Invoked when server action execution fails (rsc/handler.ts, router.ts)
12
12
  * - "revalidation": Invoked when revalidation fails (router.ts, conditional with action)
13
+ * - "origin": Invoked when cross-origin request validation rejects a request (rsc/handler.ts)
13
14
  * - "unknown": Fallback for unclassified errors (not currently invoked)
14
15
  */
15
16
  export type ErrorPhase =
@@ -21,8 +22,10 @@ export type ErrorPhase =
21
22
  | "rendering" // During RSC/SSR rendering (SSR handler uses separate callback)
22
23
  | "action" // During server action execution
23
24
  | "revalidation" // During revalidation evaluation
25
+ | "cache" // During "use cache" background operations (stale revalidation, async cache writes)
24
26
  | "prerender" // During build-time pre-rendering (Vite closeBundle)
25
27
  | "static" // During build-time static handler rendering (Vite closeBundle)
28
+ | "origin" // During cross-origin request validation (CSRF protection)
26
29
  | "unknown"; // Fallback for unclassified errors
27
30
 
28
31
  /**
@@ -52,6 +52,19 @@ export type GetRegisteredRoutes = keyof RSCRouter.RegisteredRoutes extends never
52
52
  ? Record<string, string>
53
53
  : RSCRouter.RegisteredRoutes;
54
54
 
55
+ /**
56
+ * Default route map for reverse() surfaces.
57
+ * Prefers GeneratedRouteMap to avoid router.tsx -> urls.tsx -> types -> router.tsx
58
+ * cycles, but falls back to RegisteredRoutes for manual augmentation and then to
59
+ * a permissive record when no route types are available.
60
+ */
61
+ export type DefaultReverseRouteMap =
62
+ keyof RSCRouter.GeneratedRouteMap extends never
63
+ ? keyof RSCRouter.RegisteredRoutes extends never
64
+ ? Record<string, string>
65
+ : RSCRouter.RegisteredRoutes
66
+ : RSCRouter.GeneratedRouteMap;
67
+
55
68
  /**
56
69
  * Default route map for Handler type.
57
70
  * Uses GeneratedRouteMap (from gen file) instead of RegisteredRoutes to avoid
@@ -76,3 +89,12 @@ export type DefaultEnv = keyof RSCRouter.Env extends never
76
89
  export type DefaultVars = keyof RSCRouter.Vars extends never
77
90
  ? Record<string, any>
78
91
  : RSCRouter.Vars;
92
+
93
+ /**
94
+ * Default route name type for public `routeName` on contexts.
95
+ * When GeneratedRouteMap is augmented, narrows to the known route names.
96
+ * Otherwise falls back to `string` for untyped usage.
97
+ */
98
+ export type DefaultRouteName = keyof RSCRouter.GeneratedRouteMap extends never
99
+ ? string
100
+ : keyof RSCRouter.GeneratedRouteMap & string;
@@ -9,8 +9,9 @@ import type { LocationStateEntry } from "../browser/react/location-state-shared.
9
9
  import type {
10
10
  DefaultEnv,
11
11
  DefaultHandlerRouteMap,
12
+ DefaultReverseRouteMap,
13
+ DefaultRouteName,
12
14
  DefaultVars,
13
- GetRegisteredRoutes,
14
15
  } from "./global-namespace.js";
15
16
  import type {
16
17
  ExtractParams,
@@ -67,6 +68,65 @@ type ExtractSearchFromEntry<TMap, TKey> = TKey extends keyof TMap
67
68
  : {}
68
69
  : {};
69
70
 
71
+ type IsEmptyObject<T> = keyof T extends never ? true : false;
72
+
73
+ type AutofillParamsFromEntry<TEntry> = TEntry extends string
74
+ ? string extends TEntry
75
+ ? Record<string, string>
76
+ : Partial<ExtractParams<TEntry>>
77
+ : TEntry extends { readonly path: infer P extends string }
78
+ ? string extends P
79
+ ? Record<string, string>
80
+ : Partial<ExtractParams<P>>
81
+ : Record<string, string>;
82
+
83
+ type AutofillSearchFromEntry<TMap, TKey> = TKey extends keyof TMap
84
+ ? TMap[TKey] extends { readonly search: infer S extends SearchSchema }
85
+ ? ResolveSearchSchema<S>
86
+ : Record<string, unknown>
87
+ : Record<string, unknown>;
88
+
89
+ type AutofillAwareReverseFunction<TLocalRoutes, TGlobalRoutes> =
90
+ ScopedReverseFunction<TLocalRoutes, TGlobalRoutes> & {
91
+ <TName extends keyof TGlobalRoutes & string>(
92
+ name: TName,
93
+ params?: AutofillParamsFromEntry<TGlobalRoutes[TName]>,
94
+ search?: AutofillSearchFromEntry<TGlobalRoutes, TName>,
95
+ ): string;
96
+ <TName extends keyof TLocalRoutes & string>(
97
+ name: `.${TName}`,
98
+ params?: AutofillParamsFromEntry<TLocalRoutes[TName]>,
99
+ search?: AutofillSearchFromEntry<TLocalRoutes, TName>,
100
+ ): string;
101
+ };
102
+
103
+ type StrictLocalParamsWithExtras<TEntry> =
104
+ IsEmptyObject<ExtractParamsFromEntry<TEntry, {}>> extends true
105
+ ? Record<string, string>
106
+ : ExtractParamsFromEntry<TEntry, {}> & Record<string, string>;
107
+
108
+ // HandlerContext.reverse is the only reverse surface with runtime param autofill
109
+ // from the current matched request. Middleware/loaders/request context do not
110
+ // have the same local-route guarantees, so they keep plain ScopedReverseFunction.
111
+ //
112
+ // When a handler has an explicit local route map, enforce that local route
113
+ // params declared by that map are present while still allowing extra mount
114
+ // params to be passed through. Global names remain autofill-friendly because
115
+ // parent include() params are often unknown at the module definition site.
116
+ type StrictLocalAutofillGlobalReverseFunction<TLocalRoutes, TGlobalRoutes> =
117
+ ScopedReverseFunction<TLocalRoutes, TGlobalRoutes> & {
118
+ <TName extends keyof TGlobalRoutes & string>(
119
+ name: TName,
120
+ params?: AutofillParamsFromEntry<TGlobalRoutes[TName]>,
121
+ search?: AutofillSearchFromEntry<TGlobalRoutes, TName>,
122
+ ): string;
123
+ <TName extends keyof TLocalRoutes & string>(
124
+ name: `.${TName}`,
125
+ params: StrictLocalParamsWithExtras<TLocalRoutes[TName]>,
126
+ search?: AutofillSearchFromEntry<TLocalRoutes, TName>,
127
+ ): string;
128
+ };
129
+
70
130
  export type Handler<
71
131
  T extends
72
132
  | keyof DefaultHandlerRouteMap
@@ -107,14 +167,12 @@ export type Handler<
107
167
  *
108
168
  * Provides type-safe access to:
109
169
  * - Route params (from URL pattern)
110
- * - Request data (request, searchParams, pathname, url)
170
+ * - Cleaned route URL (`url`, `searchParams`, `pathname` — no `_rsc*` params)
171
+ * - Original request (`request` — raw transport URL, headers, method, body)
111
172
  * - Platform bindings (env.DB, env.KV, env.SECRETS)
112
173
  * - Middleware variables (var.user, var.permissions)
113
174
  * - Getter/setter for variables (get('user'), set('user', ...))
114
175
  *
115
- * **Note:** System parameters (query params starting with `_rsc`) are automatically
116
- * filtered from `url`, `searchParams`, and `request.url` for cleaner access.
117
- *
118
176
  * @example
119
177
  * ```typescript
120
178
  * const handler = (ctx: HandlerContext<{ slug: string }, AppEnv>) => {
@@ -125,6 +183,7 @@ export type Handler<
125
183
  * ctx.set('user', {...}) // Setter
126
184
  * ctx.url // Clean URL (no _rsc* params)
127
185
  * ctx.searchParams // Clean params (no _rsc* params)
186
+ * ctx.request // Raw transport request (original URL intact)
128
187
  * }
129
188
  * ```
130
189
  */
@@ -143,13 +202,15 @@ export type HandlerContext<
143
202
  readonly _paramCheck?: (params: TParams) => TParams;
144
203
  /**
145
204
  * True during build-time pre-rendering, false at runtime.
146
- * In dev mode, Prerender handlers run live so build is false.
147
- * In production passthrough (live fallback), build is also false.
205
+ * Build-time collection and dev on-demand prerender use `true`.
206
+ * Live request rendering, including passthrough fallback, uses `false`.
148
207
  */
149
208
  build: boolean;
150
209
  /**
151
- * The incoming Request object.
152
- * System params (`_rsc*`) are filtered from the URL for cleaner access.
210
+ * The original incoming Request object (transport URL intact).
211
+ * Use `ctx.url` / `ctx.searchParams` for application logic those have
212
+ * internal `_rsc*` params stripped. `ctx.request` preserves the raw URL
213
+ * for cases where you need original headers, method, or body.
153
214
  */
154
215
  request: Request;
155
216
  /**
@@ -320,12 +381,26 @@ export type HandlerContext<
320
381
  * @example
321
382
  * ```typescript
322
383
  * route("product", (ctx) => {
323
- * ctx.setLocationState([ServerInfo({ data: "value" })]);
384
+ * ctx.setLocationState(ServerInfo({ data: "value" }));
385
+ * return <ProductPage />;
386
+ * });
387
+ * ```
388
+ */
389
+ setLocationState(entries: LocationStateEntry | LocationStateEntry[]): void;
390
+ /**
391
+ * The matched route name, if the route has an explicit name.
392
+ * Undefined for unnamed routes (those without a `name` option in path()).
393
+ * Includes the namespace prefix from include() (e.g., "blog.post").
394
+ *
395
+ * @example
396
+ * ```typescript
397
+ * route("product", (ctx) => {
398
+ * ctx.routeName // "product"
324
399
  * return <ProductPage />;
325
400
  * });
326
401
  * ```
327
402
  */
328
- setLocationState(entries: LocationStateEntry[]): void;
403
+ routeName?: DefaultRouteName;
329
404
  /**
330
405
  * Generate URLs from route names.
331
406
  *
@@ -341,8 +416,14 @@ export type HandlerContext<
341
416
  * ```
342
417
  */
343
418
  reverse: [TRouteMap] extends [never]
344
- ? ScopedReverseFunction<GetRegisteredRoutes>
345
- : ScopedReverseFunction<TRouteMap, GetRegisteredRoutes>;
419
+ ? AutofillAwareReverseFunction<
420
+ Record<string, string>,
421
+ DefaultReverseRouteMap
422
+ >
423
+ : StrictLocalAutofillGlobalReverseFunction<
424
+ TRouteMap,
425
+ DefaultReverseRouteMap
426
+ >;
346
427
  };
347
428
 
348
429
  /**
@@ -355,12 +436,14 @@ export type InternalHandlerContext<
355
436
  TEnv = DefaultEnv,
356
437
  TSearch extends SearchSchema = {},
357
438
  > = HandlerContext<TParams, TEnv, TSearch> & {
358
- /** Raw request with all system parameters intact. */
359
- _originalRequest: Request;
439
+ /** Prerender-only control flow helper, attached when the runtime context supports it. */
440
+ passthrough?: () => unknown;
360
441
  /** Current segment ID for handle data attribution. */
361
442
  _currentSegmentId?: string;
362
443
  /** Response type tag (json, text, html, etc.) for cache key differentiation. */
363
444
  _responseType?: string;
445
+ /** Route name for cache key scoping (prevents cross-route collisions). */
446
+ _routeName?: string;
364
447
  };
365
448
 
366
449
  /**
@@ -457,7 +540,11 @@ export type ShouldRevalidateFn<TParams = GenericParams, TEnv = any> = (args: {
457
540
  actionResult?: any; // Return value from action execution
458
541
  formData?: FormData; // FormData from action request
459
542
  method?: string; // Request method: 'GET' for navigation, 'POST' for actions
460
- routeName?: string; // Route name where action was executed (e.g., "products.detail")
543
+ routeName?: DefaultRouteName; // Route name of the navigation target (alias for toRouteName)
544
+ // Named-route identity for both ends of a navigation transition.
545
+ // Undefined for unnamed internal routes (those without a `name` option).
546
+ fromRouteName?: DefaultRouteName; // Route name being navigated away from
547
+ toRouteName?: DefaultRouteName; // Route name being navigated to
461
548
  // Stale cache revalidation (SWR pattern):
462
549
  stale?: boolean; // True if this is a stale cache revalidation request
463
550
  }) => boolean | { defaultShouldRevalidate: boolean };
@@ -2,6 +2,7 @@
2
2
  export type {
3
3
  GetRegisteredRoutes,
4
4
  DefaultHandlerRouteMap,
5
+ DefaultReverseRouteMap,
5
6
  DefaultEnv,
6
7
  } from "./global-namespace.js";
7
8
  // Ensure the global namespace declaration is evaluated
@@ -10,7 +11,6 @@ import "./global-namespace.js";
10
11
  // Route configuration
11
12
  export type {
12
13
  DocumentProps,
13
- RouterEnv,
14
14
  ExtractParams,
15
15
  TrailingSlashMode,
16
16
  RouteConfig,
@@ -4,7 +4,7 @@ import type { ScopedReverseFunction } from "../reverse.js";
4
4
  import type { SearchSchema, ResolveSearchSchema } from "../search-params.js";
5
5
  import type {
6
6
  DefaultEnv,
7
- DefaultHandlerRouteMap,
7
+ DefaultReverseRouteMap,
8
8
  DefaultVars,
9
9
  } from "./global-namespace.js";
10
10
 
@@ -40,6 +40,13 @@ export type LoaderContext<
40
40
  TSearch extends SearchSchema = {},
41
41
  > = {
42
42
  params: TParams;
43
+ /**
44
+ * Route params extracted from the URL pattern match (server-side only).
45
+ * Unlike `params`, these cannot be overridden by client-provided loader params.
46
+ * Use this when you need trusted, server-matched route params for auth or
47
+ * resource scoping.
48
+ */
49
+ routeParams: Record<string, string>;
43
50
  request: Request;
44
51
  searchParams: URLSearchParams;
45
52
  search: {} extends TSearch ? {} : ResolveSearchSchema<TSearch>;
@@ -50,10 +57,6 @@ export type LoaderContext<
50
57
  get: {
51
58
  <T>(contextVar: ContextVar<T>): T | undefined;
52
59
  } & (<K extends keyof DefaultVars>(key: K) => DefaultVars[K]);
53
- /** Get a cookie value from the request */
54
- cookie(name: string): string | undefined;
55
- /** Get all cookies from the request */
56
- cookies(): Record<string, string>;
57
60
  /**
58
61
  * Access another loader's data (returns promise since loaders run in parallel)
59
62
  */
@@ -82,7 +85,7 @@ export type LoaderContext<
82
85
  */
83
86
  reverse: ScopedReverseFunction<
84
87
  Record<string, string>,
85
- DefaultHandlerRouteMap
88
+ DefaultReverseRouteMap
86
89
  >;
87
90
  };
88
91