@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
@@ -14,6 +14,7 @@ import type { MiddlewareEntry, MiddlewareFn } from "./middleware.js";
14
14
  import { RSC_ROUTER_BRAND } from "./router-registry.js";
15
15
  import type { RSCRouterOptions, RootLayoutProps } from "./router-options.js";
16
16
  import type { DefaultVars } from "../types/global-namespace.js";
17
+ import type { ResolvedTimeouts, OnTimeoutCallback } from "./timeout.js";
17
18
 
18
19
  /**
19
20
  * Options passed to router.fetch(), router.match(), and other request entrypoints.
@@ -47,19 +48,19 @@ type MergeRoutesWithResponses<
47
48
  };
48
49
 
49
50
  /**
50
- * RSC Router interface
51
- * TRoutes accumulates all registered route types through the builder chain
51
+ * Public RSC Router interface — the user-facing API surface.
52
+ *
53
+ * Users interact with this type when building and using routers.
54
+ * Internal framework code uses RSCRouterInternal (via toInternal()) to access
55
+ * matching, build-time, and configuration members that are not part of the
56
+ * public contract.
57
+ *
58
+ * TRoutes accumulates all registered route types through the builder chain.
52
59
  */
53
60
  export interface RSCRouter<
54
61
  TEnv = any,
55
62
  TRoutes extends Record<string, unknown> = Record<string, string>,
56
63
  > {
57
- /**
58
- * Brand marker for build-time discovery.
59
- * The Vite plugin uses this to identify router instances in module exports.
60
- */
61
- readonly __brand: typeof RSC_ROUTER_BRAND;
62
-
63
64
  /**
64
65
  * Unique identifier for this router instance.
65
66
  * Used to namespace static output and isolate route maps between routers.
@@ -134,6 +135,90 @@ export interface RSCRouter<
134
135
  */
135
136
  readonly routeMap: TRoutes;
136
137
 
138
+ /**
139
+ * Handle an RSC request.
140
+ *
141
+ * Uses the router's configuration (nonce, version, cache) automatically.
142
+ * The handler is lazily created on first call.
143
+ *
144
+ * @example Cloudflare Workers
145
+ * ```tsx
146
+ * import { router } from "./router";
147
+ *
148
+ * export default { fetch: router.fetch };
149
+ * ```
150
+ *
151
+ * @example Direct export
152
+ * ```tsx
153
+ * const router = createRouter({
154
+ * document: Document,
155
+ * urls: urlpatterns,
156
+ * nonce: () => true,
157
+ * });
158
+ *
159
+ * export const fetch = router.fetch;
160
+ * ```
161
+ */
162
+ fetch(request: Request, input?: RouterRequestInput<TEnv>): Promise<Response>;
163
+ }
164
+
165
+ /**
166
+ * Internal RSC Router interface — the full framework-facing API.
167
+ *
168
+ * This type includes all members used by the Vite plugin, RSC handler,
169
+ * pre-rendering pipeline, and other framework internals. It is NOT exported
170
+ * from the public package API.
171
+ *
172
+ * Use toInternal(router) to assert a public RSCRouter into this type
173
+ * at the boundary where framework code receives a user-provided router.
174
+ */
175
+ export interface RSCRouterInternal<
176
+ TEnv = any,
177
+ TRoutes extends Record<string, unknown> = Record<string, string>,
178
+ > {
179
+ /**
180
+ * Brand marker for build-time discovery.
181
+ * The Vite plugin uses this to identify router instances in module exports.
182
+ */
183
+ readonly __brand: typeof RSC_ROUTER_BRAND;
184
+
185
+ /**
186
+ * Unique identifier for this router instance.
187
+ * Used to namespace static output and isolate route maps between routers.
188
+ */
189
+ readonly id: string;
190
+
191
+ /**
192
+ * Register routes using URL patterns from urls()
193
+ */
194
+ routes<T extends UrlPatterns<TEnv, any>>(
195
+ patterns: T,
196
+ ): RSCRouter<
197
+ TEnv,
198
+ TRoutes &
199
+ (NonNullable<T["_routes"]> extends Record<string, unknown>
200
+ ? MergeRoutesWithResponses<NonNullable<T["_routes"]>, T["_responses"]>
201
+ : Record<string, string>)
202
+ >;
203
+
204
+ /**
205
+ * Add global middleware that runs on all routes
206
+ */
207
+ use(
208
+ patternOrMiddleware: string | MiddlewareFn<TEnv>,
209
+ middleware?: MiddlewareFn<TEnv>,
210
+ ): RSCRouter<TEnv, TRoutes>;
211
+
212
+ /**
213
+ * Type-safe URL builder for registered routes
214
+ */
215
+ reverse: ReverseFunction<TRoutes>;
216
+
217
+ /**
218
+ * Accumulated route map for typeof extraction
219
+ */
220
+ readonly routeMap: TRoutes;
221
+
137
222
  /**
138
223
  * Root layout component that wraps the entire application
139
224
  * Access this to pass to renderSegments
@@ -147,12 +232,12 @@ export interface RSCRouter<
147
232
  readonly onError?: RSCRouterOptions<TEnv>["onError"];
148
233
 
149
234
  /**
150
- * Cache configuration (for internal use by RSC handler)
235
+ * Cache configuration
151
236
  */
152
237
  readonly cache?: RSCRouterOptions<TEnv>["cache"];
153
238
 
154
239
  /**
155
- * Not found component to render when no route matches (for internal use by RSC handler)
240
+ * Not found component to render when no route matches
156
241
  */
157
242
  readonly notFound?: RSCRouterOptions<TEnv>["notFound"];
158
243
 
@@ -162,6 +247,21 @@ export interface RSCRouter<
162
247
  */
163
248
  readonly themeConfig: import("../theme/types.js").ResolvedThemeConfig | null;
164
249
 
250
+ /**
251
+ * Cache profiles for "use cache" per-request resolution.
252
+ * Always includes at least the "default" profile.
253
+ */
254
+ readonly cacheProfiles: Record<
255
+ string,
256
+ import("../cache/profile-registry.js").CacheProfile
257
+ >;
258
+
259
+ /**
260
+ * Cache-Control header value for prefetch responses.
261
+ * False means no browser caching of prefetch responses.
262
+ */
263
+ readonly prefetchCacheControl: string | false;
264
+
165
265
  /**
166
266
  * Whether connection warmup is enabled.
167
267
  * When true, the client sends HEAD /?_rsc_warmup after idle periods
@@ -169,40 +269,65 @@ export interface RSCRouter<
169
269
  */
170
270
  readonly warmupEnabled: boolean;
171
271
 
272
+ /**
273
+ * Whether router-wide performance debugging is enabled.
274
+ * Used by the request handler to create metrics before middleware runs.
275
+ */
276
+ readonly debugPerformance?: boolean;
277
+
172
278
  /**
173
279
  * Whether ?__debug_manifest is allowed in production.
174
280
  * Always enabled in development.
175
- * @internal
176
281
  */
177
282
  readonly allowDebugManifest: boolean;
178
283
 
179
284
  /**
180
- * App-level middleware entries (for internal use by RSC handler)
285
+ * Resolved timeout configuration (merged from shorthand + structured).
286
+ */
287
+ readonly timeouts: ResolvedTimeouts;
288
+
289
+ /**
290
+ * Custom timeout response handler.
291
+ */
292
+ readonly onTimeout?: OnTimeoutCallback<TEnv>;
293
+
294
+ /**
295
+ * App-level middleware entries
181
296
  * These wrap the entire request/response cycle
182
297
  */
183
298
  readonly middleware: MiddlewareEntry<TEnv>[];
184
299
 
185
300
  /**
186
- * Nonce provider for CSP (for internal use by createHandler)
301
+ * Nonce provider for CSP
187
302
  */
188
303
  readonly nonce?: NonceProvider<TEnv>;
189
304
 
190
305
  /**
191
- * RSC version string (for internal use by createHandler)
306
+ * RSC version string
192
307
  */
193
308
  readonly version?: string;
194
309
 
195
310
  /**
196
311
  * URL patterns reference for build-time manifest generation
197
- * @internal
198
312
  */
199
313
  readonly urlpatterns?: UrlPatterns<TEnv, any>;
200
314
 
315
+ /**
316
+ * SSR configuration. resolveStreaming determines stream vs allReady
317
+ * per HTML request (undefined = always stream).
318
+ */
319
+ readonly ssr?: import("./router-options.js").SSROptions<TEnv>;
320
+
321
+ /**
322
+ * Cross-origin request protection configuration.
323
+ * Default: true (enabled).
324
+ */
325
+ readonly originCheck: import("../rsc/origin-guard.js").OriginCheckConfig<TEnv>;
326
+
201
327
  /**
202
328
  * Source file path where createRouter() was called.
203
329
  * Set via Error.stack parsing at construction time.
204
330
  * Used by the Vite plugin to write per-router named-routes.gen.ts files.
205
- * @internal
206
331
  */
207
332
  readonly __sourceFile?: string;
208
333
 
@@ -215,12 +340,12 @@ export interface RSCRouter<
215
340
  * Build-time pre-render match. Resolves segments with a BuildContext
216
341
  * (no request/env/headers/cookies), skipping middleware and loaders.
217
342
  * Used by the Vite plugin to collect pre-render data at build time.
218
- * @internal
219
343
  */
220
344
  matchForPrerender(
221
345
  pathname: string,
222
346
  params: Record<string, string>,
223
347
  buildVars?: Record<string, any>,
348
+ isPassthroughRoute?: boolean,
224
349
  ): Promise<{
225
350
  segments: SerializedSegmentData[];
226
351
  handles: Record<string, SegmentHandleData>;
@@ -228,12 +353,12 @@ export interface RSCRouter<
228
353
  params: Record<string, string>;
229
354
  interceptSegments?: SerializedSegmentData[];
230
355
  interceptHandles?: Record<string, SegmentHandleData>;
356
+ passthrough?: true;
231
357
  } | null>;
232
358
 
233
359
  /**
234
360
  * Render a single Static handler at build time.
235
361
  * Returns the RSC-serialized component string and handle data, or null on failure.
236
- * @internal
237
362
  */
238
363
  renderStaticSegment(
239
364
  handler: Function,
@@ -293,7 +418,6 @@ export interface RSCRouter<
293
418
  ): Promise<MatchResult | null>;
294
419
 
295
420
  /**
296
- * @internal
297
421
  * Debug utility to serialize the manifest for inspection
298
422
  * Returns a JSON-friendly representation of all routes and layouts
299
423
  */
@@ -301,27 +425,21 @@ export interface RSCRouter<
301
425
 
302
426
  /**
303
427
  * Handle an RSC request.
304
- *
305
- * Uses the router's configuration (nonce, version, cache) automatically.
306
- * The handler is lazily created on first call.
307
- *
308
- * @example Cloudflare Workers
309
- * ```tsx
310
- * import { router } from "./router";
311
- *
312
- * export default { fetch: router.fetch };
313
- * ```
314
- *
315
- * @example Direct export
316
- * ```tsx
317
- * const router = createRouter({
318
- * document: Document,
319
- * urls: urlpatterns,
320
- * nonce: () => true,
321
- * });
322
- *
323
- * export const fetch = router.fetch;
324
- * ```
325
428
  */
326
429
  fetch(request: Request, input?: RouterRequestInput<TEnv>): Promise<Response>;
327
430
  }
431
+
432
+ /**
433
+ * Assert a public RSCRouter into the internal type.
434
+ *
435
+ * Use this at the boundary where framework code receives a user-provided
436
+ * router and needs access to internal members (match, config, build-time).
437
+ * The cast is safe because createRouter() always produces an object that
438
+ * satisfies RSCRouterInternal; the public type is just a narrower view.
439
+ */
440
+ export function toInternal<
441
+ TEnv = any,
442
+ TRoutes extends Record<string, unknown> = Record<string, string>,
443
+ >(router: RSCRouter<TEnv, TRoutes>): RSCRouterInternal<TEnv, TRoutes> {
444
+ return router as RSCRouterInternal<TEnv, TRoutes>;
445
+ }
@@ -9,6 +9,58 @@ import type { NonceProvider } from "../rsc/types.js";
9
9
  import type { ExecutionContext } from "../server/request-context.js";
10
10
  import type { UrlPatterns } from "../urls.js";
11
11
  import type { NamedRouteEntry } from "./content-negotiation.js";
12
+ import type { TelemetrySink } from "./telemetry.js";
13
+ import type { RouterTimeouts, OnTimeoutCallback } from "./timeout.js";
14
+
15
+ /**
16
+ * SSR stream mode returned by resolveStreaming.
17
+ *
18
+ * - `"stream"` — start flushing HTML as soon as the shell is ready
19
+ * (default React SSR behavior via `renderToReadableStream`).
20
+ * - `"allReady"` — wait for every Suspense boundary to resolve before
21
+ * sending any bytes (equivalent to awaiting `stream.allReady`).
22
+ */
23
+ export type SSRStreamMode = "stream" | "allReady";
24
+
25
+ /**
26
+ * Context passed to the resolveStreaming callback.
27
+ */
28
+ export interface ResolveStreamingContext<TEnv = unknown> {
29
+ request: Request;
30
+ env: TEnv;
31
+ url: URL;
32
+ }
33
+
34
+ /**
35
+ * SSR configuration options.
36
+ */
37
+ export interface SSROptions<TEnv = unknown> {
38
+ /**
39
+ * Determine whether an HTML response should stream progressively or
40
+ * wait for full readiness before flushing.
41
+ *
42
+ * Called once per HTML request, before the HTML response is produced.
43
+ * Does NOT apply to RSC responses (`__rsc`, partial navigation, prefetch).
44
+ *
45
+ * Return `"stream"` (default) for progressive streaming or `"allReady"`
46
+ * to buffer the complete HTML before sending.
47
+ *
48
+ * @example Bot detection
49
+ * ```ts
50
+ * createRouter({
51
+ * ssr: {
52
+ * resolveStreaming: async ({ request, env }) => {
53
+ * const bot = await detectBot(request, env);
54
+ * return bot.isBot && !bot.supportsStreaming ? "allReady" : "stream";
55
+ * },
56
+ * },
57
+ * });
58
+ * ```
59
+ */
60
+ resolveStreaming?: (
61
+ context: ResolveStreamingContext<TEnv>,
62
+ ) => SSRStreamMode | Promise<SSRStreamMode>;
63
+ }
12
64
 
13
65
  /**
14
66
  * Props passed to the root layout component
@@ -187,7 +239,7 @@ export interface RSCRouterOptions<TEnv = any> {
187
239
  *
188
240
  * @example Static config
189
241
  * ```typescript
190
- * import { MemorySegmentCacheStore } from "rsc-router/rsc";
242
+ * import { MemorySegmentCacheStore } from "@rangojs/router/cache";
191
243
  *
192
244
  * const router = createRouter({
193
245
  * cache: {
@@ -338,6 +390,16 @@ export interface RSCRouterOptions<TEnv = any> {
338
390
  * nonce: (request, env) => env.nonce,
339
391
  * });
340
392
  * ```
393
+ *
394
+ * @example Access nonce in middleware
395
+ * ```tsx
396
+ * import { nonce } from "@rangojs/router";
397
+ *
398
+ * const cspMiddleware: Middleware = async (ctx, next) => {
399
+ * const value = ctx.get(nonce); // string | undefined
400
+ * await next();
401
+ * };
402
+ * ```
341
403
  */
342
404
  nonce?: NonceProvider<TEnv>;
343
405
 
@@ -352,6 +414,18 @@ export interface RSCRouterOptions<TEnv = any> {
352
414
  */
353
415
  version?: string;
354
416
 
417
+ /**
418
+ * Cache-Control header value for prefetch responses.
419
+ * Only applied to non-intercept partial responses that include the
420
+ * `X-Rango-Prefetch` header (sent by the Link component's prefetch fetch).
421
+ * Navigation responses are never cached by the browser.
422
+ *
423
+ * Set to `false` to disable browser caching of prefetch responses entirely.
424
+ *
425
+ * @default "private, max-age=300"
426
+ */
427
+ prefetchCacheControl?: string | false;
428
+
355
429
  /**
356
430
  * Enable connection warmup to keep TCP+TLS alive after idle periods.
357
431
  *
@@ -362,4 +436,152 @@ export interface RSCRouterOptions<TEnv = any> {
362
436
  * @default true
363
437
  */
364
438
  warmup?: boolean;
439
+
440
+ /**
441
+ * Shorthand timeout (ms) applied to both action execution and render start.
442
+ * Does NOT apply to streamIdleMs.
443
+ * Overridden by individual values in `timeouts`.
444
+ *
445
+ * @example
446
+ * ```typescript
447
+ * createRouter({ timeout: 10_000 });
448
+ * ```
449
+ */
450
+ timeout?: number;
451
+
452
+ /**
453
+ * Structured timeout configuration per phase.
454
+ * Values here override the `timeout` shorthand.
455
+ *
456
+ * @example
457
+ * ```typescript
458
+ * createRouter({
459
+ * timeouts: {
460
+ * actionMs: 10_000,
461
+ * renderStartMs: 8_000,
462
+ * },
463
+ * });
464
+ * ```
465
+ */
466
+ timeouts?: RouterTimeouts;
467
+
468
+ /**
469
+ * Custom handler invoked when a timeout occurs.
470
+ * Receives context about which phase timed out and must return a Response.
471
+ * If not provided, returns a plain 504 with "Request timed out" body
472
+ * and X-Rango-Timeout-Phase header.
473
+ *
474
+ * If the callback throws, the default 504 response is used as fallback.
475
+ *
476
+ * @example
477
+ * ```typescript
478
+ * createRouter({
479
+ * timeout: 10_000,
480
+ * onTimeout: (ctx) => {
481
+ * return new Response(
482
+ * JSON.stringify({ error: "timeout", phase: ctx.phase }),
483
+ * { status: 504, headers: { "Content-Type": "application/json" } },
484
+ * );
485
+ * },
486
+ * });
487
+ * ```
488
+ */
489
+ onTimeout?: OnTimeoutCallback<TEnv>;
490
+
491
+ /**
492
+ * Telemetry sink for structured lifecycle events.
493
+ *
494
+ * When provided, the router emits events for request start/end,
495
+ * loader start/end/error, handler errors, cache decisions, and
496
+ * revalidation decisions.
497
+ *
498
+ * No-op when not configured (zero overhead).
499
+ *
500
+ * @example Console logging
501
+ * ```typescript
502
+ * import { createConsoleSink } from "@rangojs/router";
503
+ *
504
+ * const router = createRouter({
505
+ * telemetry: createConsoleSink(),
506
+ * });
507
+ * ```
508
+ *
509
+ * @example Custom sink
510
+ * ```typescript
511
+ * const router = createRouter({
512
+ * telemetry: {
513
+ * emit(event) {
514
+ * myTracer.record(event);
515
+ * },
516
+ * },
517
+ * });
518
+ * ```
519
+ */
520
+ telemetry?: TelemetrySink;
521
+
522
+ /**
523
+ * SSR configuration options.
524
+ *
525
+ * @example
526
+ * ```typescript
527
+ * createRouter({
528
+ * ssr: {
529
+ * resolveStreaming: async ({ request, env }) => {
530
+ * const bot = await detectBot(request, env);
531
+ * return bot.isBot ? "allReady" : "stream";
532
+ * },
533
+ * },
534
+ * });
535
+ * ```
536
+ */
537
+ ssr?: SSROptions<TEnv>;
538
+
539
+ /**
540
+ * Cross-origin request protection for server actions, loader fetches,
541
+ * and progressive enhancement form submissions.
542
+ *
543
+ * When enabled, the router validates that the request's Origin header
544
+ * (or Referer fallback) matches the Host before executing actions,
545
+ * loaders, or PE submissions. Requests without Origin/Referer are
546
+ * allowed (same-origin navigations, non-browser clients).
547
+ *
548
+ * The built-in check compares Origin against the Host header and
549
+ * url.protocol. It does NOT trust X-Forwarded-Host/Proto headers
550
+ * (they are client-controllable without a trusted proxy). On standard
551
+ * deployments (Cloudflare Workers, Node behind nginx/caddy) the Host
552
+ * header is already set to the public-facing host by the platform or
553
+ * proxy. For non-standard proxy setups where Host differs from the
554
+ * public origin, use a custom function that reads the appropriate
555
+ * forwarded headers from your trusted proxy.
556
+ *
557
+ * - `true` (default) -- enable built-in origin validation
558
+ * - `false` -- disable
559
+ * - function -- full custom control with access to env, phase,
560
+ * and the built-in check via `ctx.defaultCheck()`
561
+ *
562
+ * The callback receives `OriginCheckContext` with `request`, `url`,
563
+ * `env`, `routerId`, `phase` ("action" | "loader" | "pe-form"),
564
+ * and `defaultCheck()`. Return `true` to allow, `false` for default
565
+ * 403 rejection, or a `Response` for custom rejection.
566
+ *
567
+ * @default true
568
+ *
569
+ * @example Trusted proxy with X-Forwarded-Host
570
+ * ```ts
571
+ * createRouter({
572
+ * originCheck({ request, url, env, defaultCheck }) {
573
+ * if (env.TRUST_PROXY) {
574
+ * const origin = request.headers.get("origin");
575
+ * if (!origin) return true;
576
+ * if (origin === "null") return false;
577
+ * const host = request.headers.get("x-forwarded-host")
578
+ * ?? request.headers.get("host") ?? url.host;
579
+ * return origin.toLowerCase() === `${url.protocol}//${host}`.toLowerCase();
580
+ * }
581
+ * return defaultCheck();
582
+ * },
583
+ * });
584
+ * ```
585
+ */
586
+ originCheck?: import("../rsc/origin-guard.js").OriginCheckConfig<TEnv>;
365
587
  }
@@ -1,4 +1,4 @@
1
- import type { RSCRouter } from "./router-interfaces.js";
1
+ import type { RSCRouterInternal } from "./router-interfaces.js";
2
2
 
3
3
  /**
4
4
  * Brand marker for identifying router instances at build time.
@@ -12,7 +12,10 @@ export const RSC_ROUTER_BRAND = "__rsc_router__" as const;
12
12
  * Used by the Vite plugin at build time to discover routers and extract
13
13
  * manifests, prefix trees, and pre-render candidates.
14
14
  */
15
- export const RouterRegistry: Map<string, RSCRouter<any, any>> = new Map();
15
+ export const RouterRegistry: Map<
16
+ string,
17
+ RSCRouterInternal<any, any>
18
+ > = new Map();
16
19
 
17
20
  export let routerAutoId = 0;
18
21